From 8532dcab9c1726475e0b247e3948d0f3268d90cf Mon Sep 17 00:00:00 2001 From: badcuban <108198679+badcuban@users.noreply.github.com> Date: Sun, 6 Sep 2026 04:44:55 -0400 Subject: [PATCH 1/2] fix(web): activity popover task dots sit on the line and count finished steps The header activity popover's task list had four small defects: the spine dots rendered a pixel left of the line because the line sat on a half-pixel and snapped differently from the dots; the badge counted the step being worked on (2/3 with one step done) instead of finished work; done dots were so faint they read like not-started; and the accent glow faded symmetrically around the current step, so the line toward the next step lit up too. Put the spine on whole pixels (line at x=7, node box 15px wide so odd-sized dots centre on the line), make done dots solid, and only warm the line down to the live row. The badge now shows finished/total and pulses while a step runs. Background runs become rows on their own spine like the task steps instead of filled cards, with a quiet Stop text button. --- .../components/chat/ThreadActivityPopover.tsx | 209 ++++++++---------- apps/web/src/components/ui/threadline.tsx | 52 +++-- apps/web/src/planPanelState.test.ts | 38 ++++ apps/web/src/planPanelState.ts | 17 +- 4 files changed, 177 insertions(+), 139 deletions(-) create mode 100644 apps/web/src/planPanelState.test.ts diff --git a/apps/web/src/components/chat/ThreadActivityPopover.tsx b/apps/web/src/components/chat/ThreadActivityPopover.tsx index 0237c1e9c..5304aa4ad 100644 --- a/apps/web/src/components/chat/ThreadActivityPopover.tsx +++ b/apps/web/src/components/chat/ThreadActivityPopover.tsx @@ -10,11 +10,9 @@ import { } from "react"; import { ChevronDownIcon, - ClockIcon, ExternalLinkIcon, FileTextIcon, ListTodoIcon, - LoaderIcon, RadarIcon, SquareIcon, TerminalSquareIcon, @@ -28,7 +26,13 @@ import { cn } from "~/lib/utils"; import { useHorizontalOverflow } from "../../hooks/useHorizontalOverflow"; import { Button } from "../ui/button"; import { Popover, PopoverPopup, PopoverTrigger } from "../ui/popover"; -import { SpineNode, SpineRow, spineAccentRowStyle, type SpineNodeKind } from "../ui/threadline"; +import { + LiveNode, + SpineNode, + SpineRow, + spineAccentRowStyle, + type SpineNodeKind, +} from "../ui/threadline"; import { Tooltip, TooltipPopup, TooltipTrigger, TooltipWrapper } from "../ui/tooltip"; import { backgroundRunCommandText, @@ -530,11 +534,7 @@ function TaskSection({ nodeOffset={TASK_STEP_NODE_OFFSET_PX} connectTop={index > 0} connectBottom={index < visiblePlanStepRows.length - 1} - style={ - liveStepIndex >= 0 - ? spineAccentRowStyle(Math.abs(index - liveStepIndex)) - : undefined - } + style={liveStepIndex >= 0 ? spineAccentRowStyle(liveStepIndex - index) : undefined} >
{backgroundRuns.length}
-
- {backgroundRuns.map((run) => { - const metaItems = backgroundRunMetaItems(run); + {/* Runs are rows on their own spine, drawn the same way as the task + steps above so the popover reads as one surface. Every run is live, + so each row carries the halo node. */} +
+ {backgroundRuns.map((run, index) => { + const metaItems = [backgroundRunSourceLabel(run), ...backgroundRunMetaItems(run)]; const commandText = backgroundRunCommandText(run); const showCommandText = isInformativeBackgroundRunCommand(commandText); const primaryUrl = run.urls[0] ?? null; const extraUrlCount = Math.max(0, run.urls.length - 1); return ( -
} + nodeOffset={TASK_STEP_NODE_OFFSET_PX} + connectTop={index > 0} + connectBottom={index < backgroundRuns.length - 1} > -
- - - -
-
-
- {run.label} -
-
- - {run.statusLabel} - - {run.terminalId ? ( - - - - ) : null} - {run.canStop ? ( - - - - ) : null} -
+
+
+
+ {run.label}
- -
- - {backgroundRunSourceLabel(run)} - - {metaItems.map((item) => ( - + {run.statusLabel} + + {run.terminalId ? ( + +
+
- {primaryUrl || showCommandText ? ( -
- {primaryUrl ? ( - - ) : null} - {showCommandText ? ( - -
- {commandText} -
-
+
+ {metaItems.map((item, itemIndex) => ( + + {itemIndex > 0 ? ( + ) : null} -
- ) : null} + 0 ? "font-mono" : undefined}>{item} + + ))}
+ + {primaryUrl ? ( + + ) : null} + {showCommandText ? ( + +
+ {commandText} +
+
+ ) : null}
-
+ ); })}
diff --git a/apps/web/src/components/ui/threadline.tsx b/apps/web/src/components/ui/threadline.tsx index b0cc06c82..8b283b94e 100644 --- a/apps/web/src/components/ui/threadline.tsx +++ b/apps/web/src/components/ui/threadline.tsx @@ -123,25 +123,29 @@ function SpineRow({ }) { return (
-
+ {/* Whole-pixel geometry: the 1px line sits at x=7 (no transform, so the + browser cannot snap it to a neighbouring column) and the node is + centred in a 15px box, i.e. on x=7.5, the line's own centre. Odd node + sizes then land every edge on a whole pixel. */} +
{connectTop ? (