Skip to content

feat(web): group live tool activity - #7152

Open
maria-rcks wants to merge 2 commits into
ui-refresh/tool-activityfrom
ui-refresh/tool-activity-web
Open

feat(web): group live tool activity#7152
maria-rcks wants to merge 2 commits into
ui-refresh/tool-activityfrom
ui-refresh/tool-activity-web

Conversation

@maria-rcks

@maria-rcks maria-rcks commented Aug 16, 2026

Copy link
Copy Markdown
Collaborator

What changed

  • Folds interleaved tool lifecycle events by stable call ID and preserves start commands when completion payloads omit them.
  • Renders live and completed tool groups with stable expansion identity, failure state, wrapped command parsing, and compact one-line activity summaries.
  • Filters duplicate plan and terminal-input activity from the visible work history.

Screenshots

Direct parent on the left; this PR on the right. Same viewport and copied application state.

BeforeAfter
Tool activity before Grouped tool activity after
## Why

This is the client consumer of #7151. Keeping it at the top isolates the larger tool-call discussion from the five UI PRs below it.

Review notes

  • Row IDs and persisted expansion identity change for tool groups.
  • The lifecycle fold can join non-adjacent start, update, and completion events.
  • Mobile now consumes the same top-level stable tool ID and collapses interleaved lifecycle rows consistently with web.

Validation

  • Session work-log and command-output tests
  • Timeline logic and render tests
  • Mobile thread-activity compatibility test
  • Affected-package typechecks
  • Changed-file lint and formatting checks

Stack order

  1. feat(web): unify workspace navigation #7153 workspace and navigation
  2. feat(web): redesign usage insights #7147 Usage
  3. feat(web): refresh pull request details #7148 Pull Requests
  4. feat(web): improve terminal workspace controls #7149 terminal
  5. feat(web): attach composer state drawers #7150 composer
  6. fix(server): preserve tool lifecycle identity #7151 tool lifecycle projection
  7. feat(web): group live tool activity #7152 tool activity UI, this PR

Built with GPT-5.6-sol in the Codex harness.

Note

Group live tool activity into an expandable, summarized row in the chat timeline

  • Introduces a new work-live timeline row that aggregates in-progress tool calls for the active turn into a single expandable header, replacing per-entry rendering while a turn is running.
  • Settled tool-only groups are now summarized with a human-readable sentence (e.g. "Read 2 files, ran 1 command") and a matching icon instead of exposing raw work entries.
  • Adds liveWorkEntryLabel, summarizeToolGroup, commandProgramName, and tokenizeShellCommand utilities to derive labels and icons from tool call metadata.
  • Adds a live-activity-focus CSS animation with a horizontal sweep effect and reduced-motion support for the live activity pill UI.
  • Improves tool lifecycle collapsing in both web and mobile work logs: entries are now collapsed by (turnId, toolCallId) identity rather than adjacency, preventing cross-turn and cross-tool merges.
  • Removes workingStepLabel and activeTurnInProgress props from MessagesTimeline; the working row now derives its state internally.
  • Behavioral Change: deriveWorkLogEntries now includes tool.started events as in-progress entries and drops Codex terminal interaction updates; this changes which rows appear in the work log during active tool execution.

Macroscope summarized 96c8a4d.


Note

Medium Risk
Large presentation and session-projection changes across web and mobile timelines; row IDs and expansion keys change, and lifecycle merging rules affect what users see during parallel tool calls.

Overview
Reworks how in-progress and settled tool work appears in web and mobile chat timelines: lifecycle events fold by turnId + toolCallId, including interleaved starts/updates/completions, and tool.started rows stay visible with commands while runs are active.

On web, the timeline adds a work-live row that batches the active turn’s tool calls into one expandable line (program-aware labels like “Running npm”, failure affordances, shimmer styling). Settled tool-only groups collapse to summary toggles (“Read 2 files”, local grep vs web search) instead of per-entry rows. Turn folds anchor differently when collapsed vs expanded (steer/superseded turns), the working header drops plan step text in favor of optional Thinking, and deriveActiveWorkStartedAt can use the latest user message when session/turn pointers lag. Session logic also hides duplicate plan rows, Codex terminal-input noise, and splits workEntryDisplayIndicatesToolFailure so command text isn’t treated as error output.

Mobile mirrors the same toolCallId-scoped collapse for interleaved lifecycle rows in threadActivity.

Reviewed by Cursor Bugbot for commit 96c8a4d. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai

coderabbitai Bot commented Aug 16, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7f15cbf5-b407-41f6-ae9c-35cc74ec27e7

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Aug 16, 2026
@maria-rcks maria-rcks mentioned this pull request Aug 16, 2026
Comment thread apps/web/src/session-logic.ts
Comment thread apps/web/src/components/chat/MessagesTimeline.logic.ts Outdated
@github-actions

github-actions Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ No successful main baseline artifact is available yet. This run establishes the initial measurement.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 13.4 KiB 15.1 KiB
Codex Thread snapshot wire 6.9 KiB 7.3 KiB
Codex Live turn WebSocket wire 6.5 KiB 7.8 KiB
Codex Live turn WebSocket decoded 55.0 KiB 66.4 KiB
Codex Live turn messages 16 21
Claude Total thread wire 13.5 KiB 15.1 KiB
Claude Thread snapshot wire 6.9 KiB 7.3 KiB
Claude Live turn WebSocket wire 6.6 KiB 7.8 KiB
Claude Live turn WebSocket decoded 55.8 KiB 66.4 KiB
Claude Live turn messages 16 21

Baseline: unavailable · PR result: 96c8a4d · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Comment thread apps/web/src/components/chat/MessagesTimeline.logic.ts
Comment thread apps/web/src/session-logic.ts
Comment thread apps/web/src/components/chat/MessagesTimeline.logic.ts

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the changed chat timeline UI (ChangedFilesTree.tsx, MessagesTimeline.tsx, index.css) for shared-primitive usage, Tailwind/CSS ownership and preserved interaction semantics. The AgentSpawnCtaRow migration to Button and the new mask/animation utilities living in index.css both look right. Three concrete issues below: one order-dependent cascade conflict on the new live-activity overlay, one responsive-mode inconsistency in the rebuilt subagent card, and one lost failure affordance.

Posted via Macroscope — UI Consistency

Comment thread apps/web/src/components/chat/MessagesTimeline.tsx Outdated
return (
<div className="relative min-h-6 w-fit max-w-full min-w-0 overflow-hidden rounded-md text-sm leading-relaxed">
<LiveActivityContent label={label} iconName={iconName} />
<div aria-hidden className="live-activity-focus pointer-events-none absolute inset-0">

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inset-0 compiles to the inset shorthand and sits in the same utilities layer (same specificity) as live-activity-focus, which owns left, right and width. Whichever Tailwind emits last wins, so the overlay can lose its -4.5rem left offset while live-activity-focus-aligned still applies margin-left: 4.5rem, misaligning the highlighted copy from the base label. Suggest limiting the JSX utility to the axis the CSS utility does not own so the outcome no longer depends on emission order.

Suggested change
<div aria-hidden className="live-activity-focus pointer-events-none absolute inset-0">
<div aria-hidden className="live-activity-focus pointer-events-none absolute inset-y-0">

Posted via Macroscope — UI Consistency

Comment thread apps/web/src/components/chat/MessagesTimeline.tsx Outdated
@macroscopeapp

macroscopeapp Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

5 blocking correctness issues found. This PR introduces a new feature for grouping live tool activity, adding new row types, summarization logic, and CSS animations. New feature capabilities introducing user-facing behavior changes warrant human review. Additionally, multiple Medium-severity findings remain unresolved.

You can customize Macroscope's approvability policy. Learn more.

@maria-rcks
maria-rcks force-pushed the ui-refresh/tool-activity-web branch from 34a0b63 to 40c4a79 Compare August 16, 2026 01:24
Comment thread apps/web/src/index.css
Comment thread apps/web/src/components/chat/MessagesTimeline.tsx Outdated
@maria-rcks
maria-rcks force-pushed the ui-refresh/tool-activity-web branch from 40c4a79 to 967e2ea Compare August 16, 2026 01:28
Comment thread apps/web/src/components/chat/MessagesTimeline.tsx
@maria-rcks
maria-rcks force-pushed the ui-refresh/tool-activity-web branch from 967e2ea to 63a901e Compare August 16, 2026 01:32

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the timeline/changed-files refresh for shared-primitive use, Tailwind ownership, and interaction/accessibility preservation. The three findings from the previous run are addressed (the shimmer overlay no longer sets inset-0 against the CSS utility's own left/width, the subagent card now uses @container/agent-group queries, and the failure icon is exposed via role="img"). Three new issues introduced by this diff are below.

Posted via Macroscope — UI Consistency

Comment thread apps/web/src/components/chat/MessagesTimeline.tsx
Comment thread apps/web/src/components/chat/ChangedFilesTree.tsx Outdated
Comment thread apps/web/src/components/chat/MessagesTimeline.tsx Outdated

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the timeline/changed-files refresh for shared-primitive use, Tailwind ownership, and interaction/accessibility preservation. The three findings from the previous run are addressed (the shimmer overlay no longer sets inset-0 against the CSS utility's own left/width, the subagent card now uses @container/agent-group queries, and the failure icon is exposed via role="img"). Three new issues introduced by this diff are below.

Posted via Macroscope — UI Consistency

Comment thread apps/web/src/components/chat/MessagesTimeline.tsx
Comment thread apps/web/src/components/chat/ChangedFilesTree.tsx Outdated
Comment thread apps/web/src/components/chat/MessagesTimeline.tsx Outdated
@maria-rcks
maria-rcks force-pushed the ui-refresh/tool-activity-web branch from 63a901e to 32e00fa Compare August 16, 2026 01:36
@maria-rcks
maria-rcks force-pushed the ui-refresh/tool-activity-web branch from 32e00fa to d2095f2 Compare August 16, 2026 01:39
@maria-rcks
maria-rcks force-pushed the ui-refresh/tool-activity-web branch from d2095f2 to 2cfabfe Compare August 16, 2026 01:44
Comment thread apps/web/src/components/chat/MessagesTimeline.tsx
@macroscopeapp

macroscopeapp Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

UI consistency review — inline comments could not be attached to the diff, so the four findings are below.


1. apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx (lines 1097-1099)

if (detailQuery.isPending && !detail) {
  return <PullRequestDetailGhost />;
}

This early return replaces the whole panel while detailQuery is pending, so the chrome goes with it. The Collapse pull request panel button (line 1467) and the tab bar are unmounted, which leaves a right-panel reader (context="thread" passes onClose) with no in-panel way out until the request resolves; previously the chrome rendered with detail === null and only the content region showed a ghost.

The same move also drops the per-tab ghost shape that the deleted comment described — PullRequestTimelineGhost and DiffPanelLoadingState are still imported and used further down (lines 1882, 1902), but the initial load now always shows the summary-shaped ghost, so switching to Timeline or Code mid-load flashes a summary outline.

Suggest keeping the ghost inside the content region (where detailQuery.error is already handled) and selecting it by tab, so the header keeps its close control in every loading state.


2. apps/web/src/hooks/useResizableWidth.ts (line 56)

Swapping the manual getLocalStorageItem/setLocalStorageItem pair for useLocalStorage also subscribes to the cross-window storage event (useLocalStorage.ts lines 116-120), which the comment removed in this diff ruled out on purpose: "No cross-tab subscription: panel width is per-window state."

PreviewPanelShell (line 72) shares this hook, so committing a drag in one Electron window or browser tab now snaps the right panel in every other open window — a behavior change to an existing consumer that the terminal sidebar did not need.

If the goal is only to keep the drawer-mode and panel-mode terminal sidebars (both on t3code:terminal-sidebar-width) in step within one window, suggest syncing on the in-page t3code:local_storage_change event only and leaving the storage listener out, so per-window width is preserved.


3. apps/web/src/components/chat/PanelLayoutControls.tsx (line 65)

The <span> wrapper is what makes the unavailable right-panel tooltip hoverable, but the terminal toggle directly above (lines 42-56) still uses render={<Toggle disabled={!terminalAvailable} />}. toggleVariants carries disabled:pointer-events-none (ui/toggle.tsx line 9), so its Terminal drawer is unavailable tooltip can never be reached by pointer — exactly the case this change repairs.

Two adjacent controls with the same "toggle that can be unavailable and must explain why" contract now compose two different ways. Suggest giving the terminal toggle the same wrapper, or extracting one small local helper both use, so the availability tooltip behaves the same for either panel.


4. apps/web/src/components/chat/ThreadSyncStatusPill.tsx (line 11)

.chat-composer-drawer-slot owns the attached drawer's width inset and centering (index.css line 833), but the cap that only makes sense together with it — max-w-[calc(48rem-2.75rem)] — is hardcoded here and again in ComposerBannerStack.tsx line 103, as is pb-[calc(var(--chat-composer-attachment-overlap)_+_0.375rem)] (also ComposerBannerStack.tsx line 205).

That 48rem is the composer form's max-w-3xl, so the shared geometry has three owners: the utility, and each consumer's copy of the cap. Changing the composer width would silently desync these drawers from the surface they attach to.

Suggest folding the cap into .chat-composer-drawer-slot and the attachment padding into .chat-composer-drawer-attached, leaving only contextual classes at the call sites.

Posted via Macroscope — UI Consistency

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI consistency review of the changed timeline rows. Previously flagged items (agent-card geometry/status visibility, live-row failure announcement, expanded-group landmarks, working-row spacing/token, count-toggle metrics, duplicate selectable shimmer label, icon-only agent action) are resolved at this head. One introduced misalignment remains, noted inline.

Posted via Macroscope — UI Consistency

return (
<div
className={cn(
"flex min-h-6 min-w-0 items-center gap-1.5 px-1 py-0.5",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new live rows inset at px-1, but every settled row in the same activity column insets at px-0.5: the tool-group summary button (line 1533) and PlainWorkEntryRow (line 2646, whose WorkGroupSection wrapper is -mx-1 px-1, i.e. net zero). So the running tool's icon and label sit 2px right of the identical call once it settles into Used 1 tool, and expanding the live row indents its own children 2px left of the header that discloses them — the column this PR unifies to size-6/size-4/text-sm ends up with two x-origins.

Suggest matching the column's px-0.5 here; the shimmer geometry comes from the live-activity-focus* utilities, so it is unaffected. The gap-1.5 px-1 py-0.5 assertion in MessagesTimeline.test.tsx needs the same update.

Suggested change
"flex min-h-6 min-w-0 items-center gap-1.5 px-1 py-0.5",
"flex min-h-6 min-w-0 items-center gap-1.5 px-0.5 py-0.5",

Posted via Macroscope — UI Consistency

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI consistency review: one finding on the activity column's type scale and row target for expanded tool-group children.

Posted via Macroscope — UI Consistency

Comment thread apps/web/src/components/chat/MessagesTimeline.tsx Outdated

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI consistency review of the timeline activity column.

One new finding, posted inline. Several findings from earlier runs on this branch are still present at this head and are not re-commented (see the check details): expanded tool-group children still render at text-[12px] leading-5 / py-0 while every header in the column moved to text-sm leading-relaxed + min-h-6; the live row still insets at px-1 against px-0.5 for the settled rows it turns into; toolGroupSummaryIconName still maps tone-only tool entries to wrench where workEntryIconName gives zap/bot; and the working / turn-fold headers still resolve text-muted-foreground while every activity line they introduce resolves text-secondary-label.

Posted via Macroscope — UI Consistency

Comment on lines +898 to +899
hasFailure: visibleGroupedEntries.some((entry) =>
workEntryDisplayIndicatesToolFailure(entry),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hasFailure is only ever rendered by the summary branch of WorkGroupToggleTimelineRow (destructive icon plus the …, tool call failed accessible name, MessagesTimeline.tsx lines 1538-1550). This overflow branch produces the chevron variant (summary: null), which ignores the field entirely, so two collapsed disclosures of the same batch disagree: an all-tool group that hides a failed call announces the failure, while a mixed group that hides the same call stays silent.

The value is also computed over visibleGroupedEntries rather than hiddenEntries, so it would attribute a still-visible failure (which already renders its own X on its own row) to the collapsed set. Suggest scoping it to hiddenEntries and rendering the same failure affordance in the chevron branch, or dropping the field for this variant so the row type only carries state its renderer uses.

Posted via Macroscope — UI Consistency

Comment thread apps/web/src/components/chat/MessagesTimeline.tsx Outdated

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI consistency review: one finding — the active-turn live tool batch does not apply the error-row exclusion that the settled grouping path applies, so error/denial rows lose their dedicated row treatment while a turn is running.

Posted via Macroscope — UI Consistency

Comment thread apps/web/src/components/chat/MessagesTimeline.logic.ts

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI consistency review of the live tool-activity column: two findings, both on rows introduced by this PR.

Posted via Macroscope — UI Consistency

Comment on lines +1572 to 1590
<span
className={cn(
"flex size-6 shrink-0 items-center justify-center",
row.hasFailure ? "text-destructive" : "text-icon-muted",
)}
role={row.hasFailure ? "img" : undefined}
aria-label={row.hasFailure ? "Hidden work includes a failure" : undefined}
>
{row.hasFailure ? (
<WorkEntryIconSvg name="x" className="size-4 shrink-0 stroke-[1.8] opacity-70" />
) : (
<ChevronDownIcon
className={cn(
"size-4 shrink-0 opacity-70 transition-transform duration-200",
row.expanded && "rotate-180",
)}
/>
)}
</span>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this branch the icon slot is the disclosure affordance (the chevron rotates on expand), so swapping it for the failure X drops the expand/collapse indicator — and the state it announces goes stale once the row is open. hasFailure is derived from hiddenEntries regardless of expanded (MessagesTimeline.logic.ts line 902), so an expanded overflow group renders a red X named Hidden work includes a failure while nothing is hidden and every failing row below already carries its own X. The summary branch above is different: there the icon slot is an identity glyph, so replacing it is coherent.

Suggest showing the failure glyph only while collapsed, keeping the chevron as the disclosure indicator when the group is open.

Suggested change
<span
className={cn(
"flex size-6 shrink-0 items-center justify-center",
row.hasFailure ? "text-destructive" : "text-icon-muted",
)}
role={row.hasFailure ? "img" : undefined}
aria-label={row.hasFailure ? "Hidden work includes a failure" : undefined}
>
{row.hasFailure ? (
<WorkEntryIconSvg name="x" className="size-4 shrink-0 stroke-[1.8] opacity-70" />
) : (
<ChevronDownIcon
className={cn(
"size-4 shrink-0 opacity-70 transition-transform duration-200",
row.expanded && "rotate-180",
)}
/>
)}
</span>
<span
className={cn(
"flex size-6 shrink-0 items-center justify-center",
row.hasFailure && !row.expanded ? "text-destructive" : "text-icon-muted",
)}
role={row.hasFailure && !row.expanded ? "img" : undefined}
aria-label={row.hasFailure && !row.expanded ? "Hidden work includes a failure" : undefined}
>
{row.hasFailure && !row.expanded ? (
<WorkEntryIconSvg name="x" className="size-4 shrink-0 stroke-[1.8] opacity-70" />
) : (
<ChevronDownIcon
className={cn(
"size-4 shrink-0 opacity-70 transition-transform duration-200",
row.expanded && "rotate-180",
)}
/>
)}
</span>

Posted via Macroscope — UI Consistency

className={cn("block size-4 shrink-0 stroke-[1.8]", !highlighted && "opacity-70")}
/>
</span>
) : null}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ThinkingActivityRow passes no iconName, and the icon slot is only emitted when an icon exists, so Thinking renders with no gutter while every other line in this column reserves one: the work-live row (line 1492), the tool-group summary button (line 1540) and PlainWorkEntryRow (line 2668) all render a size-6 slot before the label. showThinking flips to false the moment the first in-progress tool call lands in the same slot under the working header, so the label visibly jumps ~30px right on the first tool call of a turn.

This PR already establishes the fix pattern one level down — expanded child rows keep the icon slot and mark it invisible instead of dropping it (line 2669). Suggest reserving the gutter here too (the not.toContain('aria-hidden="true" class="size-6 shrink-0"') assertion in MessagesTimeline.test.tsx would need to move to whatever spacer class is chosen).

Suggested change
) : null}
) : (
<span aria-hidden className="flex size-6 shrink-0" />
)}

Posted via Macroscope — UI Consistency

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI consistency review of the timeline activity column. Most of the earlier findings are resolved at this head (icon classifiers now agree across live/summary/expanded rows, the failure heuristic is display-only in every renderer, the expanded children moved to the text-sm scale, the working header matches the turn fold, the agent card matches the changed-files card shell, and announceFailure is gated). One remaining interaction gap is inline below.

Posted via Macroscope — UI Consistency

return (
<button
type="button"
className="group/live-work flex min-h-6 w-full max-w-full cursor-pointer items-center rounded-md text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/70"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hit area here is the full column (w-full button) but the visible box is the w-fit LiveActivityRow inside it, and the button carries no hover state — so the empty space to the right of Running psql toggles the group with nothing indicating it is interactive. Every other clickable row in this column tints on hover: the tool-group summary button (line 1535), the overflow toggle (line 1569) and PlainWorkEntryRow (line 2664). The summary row is what this same batch becomes the moment the turn settles, so the affordance appears out of nowhere on settle.

Suggest giving the live button the same hover treatment (or dropping w-full so the hit area matches the visible box). Note the live-row test's not.toContain("hover:bg-accent/20") assertion is there to prove no toggle row rendered — tighten it to a work-toggle check if you add the tint. While here, group/live-work (and group/tool-group on line 1535) has no group-*/… consumer anywhere, so the marker can go.

Suggested change
className="group/live-work flex min-h-6 w-full max-w-full cursor-pointer items-center rounded-md text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/70"
className="flex min-h-6 w-full max-w-full cursor-pointer items-center rounded-md text-left transition-colors duration-150 hover:bg-accent/20 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/70"

Posted via Macroscope — UI Consistency

if (input.isWorking && activeTurnHeaderIndex === input.timelineEntries.length) {
appendWorkingRow();
}
if (input.isWorking) appendActiveWorkRows();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium chat/MessagesTimeline.logic.ts:972

The consolidated work-live row is always rendered after the entire timeline, so tool activity appears after later assistant output, plans, or messages even when those entries follow the tool chronologically. Because appendActiveWorkRows() runs only at line 972, insert the row at activeWorkAnchor during the loop and remove the unconditional end append.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/web/src/components/chat/MessagesTimeline.logic.ts around line 972:

The consolidated `work-live` row is always rendered after the entire timeline, so tool activity appears after later assistant output, plans, or messages even when those entries follow the tool chronologically. Because `appendActiveWorkRows()` runs only at line 972, insert the row at `activeWorkAnchor` during the loop and remove the unconditional end append.

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI consistency review — one finding on the expandable tool row's lost disclosure affordance.

Posted via Macroscope — UI Consistency

Comment thread apps/web/src/components/chat/MessagesTimeline.tsx

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI consistency review — one finding on the reworked activity rows.

Posted via Macroscope — UI Consistency

Comment thread apps/web/src/components/chat/MessagesTimeline.tsx

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI consistency review — two shared-treatment findings in the composer drawer work. Both are ownership/consistency suggestions rather than functional breaks.

Posted via Macroscope — UI Consistency

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The compact 11px drawer action now has two owners: this inline string and APPROVAL_ACTION_CLASS_NAME in chat/ComposerPendingApprovalActions.tsx:14. Both render size="micro" variant="ghost-muted" at 11px in a composer-attached drawer, so the banner action and the approval actions can drift apart even though they sit in the same stack.

sm:text-[11px] is also inert here: micro sets text-xs with no sm: counterpart (ui/button.tsx:32), so the base override already applies at every width.

Suggest expressing this once — a shared exported class constant or a named button size/variant — and leaving only contextual classes at the call site.

Posted via Macroscope — UI Consistency

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI consistency review — two findings on the composer's new attached-drawer controls.

Posted via Macroscope — UI Consistency

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI consistency review — one finding on the expanded tool-group child rows.

Posted via Macroscope — UI Consistency

Comment on lines +2638 to +2646
: workLogEntryIsToolLike(workEntry)
? "text-secondary-label"
: "text-foreground/80";
const showEntryIcon = !isExpandedToolGroupEntry || showWarningIndicator || showFailedIndicator;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blanking the icon slot for expanded tool-group children removes the last action indicator these rows have. Line 2615 now collapses the row text to workEntryPreview(...) ?? toolWorkEntryHeading(...), so a child that has a preview no longer renders its verb (Read file, Ran command) as text — and with the slot invisible/aria-hidden an expanded child renders only a bare path, command string, or raw detail.

The same entry keeps its icon when it is revealed by the other disclosure in this column: the overflow +N previous tool calls toggle pushes its rows with isExpandedToolGroupEntry: false (MessagesTimeline.logic.ts line 886), so whether a tool row shows an action glyph depends on which toggle revealed it. It hurts most when summaryKind is mixed (Read 2 files and ran 1 command, header icon hammer): the header describes the batch, not the rows, so nothing tells a read child from a command child.

Suggest keeping the per-entry icon for expanded children — at least when the group summary is mixed — instead of hiding the slot; the slot geometry is already size-6 in both states, so alignment under the header is unaffected.

Posted via Macroscope — UI Consistency

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI consistency review: two findings on the composer controls introduced here.

Posted via Macroscope — UI Consistency

@macroscopeapp

macroscopeapp Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

UI consistency review — inline comments could not be attached to the diff, so the two findings are below.

1. apps/web/src/components/chat/ComposerPendingApprovalActions.tsx (line 14 and the four size="micro" buttons)

size="micro" is h-5 at every width (ui/button.tsx lines 32-33), and these are the same buttons the collapsed-mobile approval branch renders (ChatComposer.tsx line 2766), so on touch the approve/decline decision controls drop from the previous size="sm" (h-8 below sm) to a 20px-tall target. Suggest keeping the compact desktop scale but restoring a coarse-pointer height below sm. sm:text-[11px] is also a no-op — micro carries a single unprefixed text-xs, so the base override already wins — and the same text-[11px] sm:text-[11px] override is repeated at ChatView.tsx line 4458; if 11px is the drawer's action scale it is worth owning once rather than in two call sites.

-const APPROVAL_ACTION_CLASS_NAME = "font-normal text-[11px] sm:text-[11px]";
+const APPROVAL_ACTION_CLASS_NAME = "font-normal text-[11px] max-sm:h-7 max-sm:px-2";

2. apps/web/src/components/chat/ComposerCommandMenu.tsx (line 134)

This row and the stash drawer row (ComposerStashMenu.tsx line 128) now repeat the same treatment — cursor-pointer select-none gap-3 rounded-lg px-3 … hover:bg-transparent hover:text-inherit data-highlighted:bg-transparent data-highlighted:text-inherit — but diverge on the one value that sets row height: py-2! here vs py-1.5! there. Both drawers open in the same anchored slot from the same composer, so switching between / results and the stash shows two densities for the same list row. Matching the stash value also matches CommandItem's own default (ui/command.tsx line 180), which makes the ! override unnecessary here.

-        "cursor-pointer select-none gap-3 rounded-lg px-3 py-2! hover:bg-transparent hover:text-inherit data-highlighted:bg-transparent data-highlighted:text-inherit",
+        "cursor-pointer select-none gap-3 rounded-lg px-3 py-1.5 hover:bg-transparent hover:text-inherit data-highlighted:bg-transparent data-highlighted:text-inherit",

Posted via Macroscope — UI Consistency

@macroscopeapp

macroscopeapp Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

UI consistency review — one finding could not be attached inline, so it is below.

apps/web/src/components/chat/ComposerCommandMenu.tsx (line 134)

"cursor-pointer select-none gap-3 rounded-lg px-3 py-2! hover:bg-transparent hover:text-inherit data-highlighted:bg-transparent data-highlighted:text-inherit",

This row and the stash drawer's row (chat/ComposerStashMenu.tsx:110) are the same control opening in the same attached-drawer slot, and the durable treatment is now copied in both places — cursor-pointer select-none gap-3 rounded-lg px-3 py-*!, the neutralized hover/highlight states, and bg-accent! text-accent-foreground! for the active row. The two copies have already diverged: py-2! here vs py-1.5! there, so the command drawer and the stash drawer render at different row heights from the same composer shoulder. py-1.5 is also CommandItem's own default (ui/command.tsx:180), so the stash copy's ! overrides nothing.

Suggest expressing this once — a shared class constant, or a CommandItem variant for the composer drawer row — and keeping only per-list content classes at each call site, so the two drawers cannot drift apart in density.

Posted via Macroscope — UI Consistency

@macroscopeapp

macroscopeapp Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

UI consistency review — inline comments could not be attached to the diff, so the two findings are below.


1. apps/web/src/components/chat/ComposerPendingApprovalActions.tsx (lines 32-35, and 50-53)

        variant="ghost-muted"
        className={`${APPROVAL_ACTION_CLASS_NAME} text-destructive-foreground [:hover,[data-pressed]]:text-destructive-foreground`}

Decline and Approve pick ghost-muted and then override the variant's base and hover text colors at the call site, which is the contract ghost already encodes (border-transparent text-foreground, hover tints only the background). The [:hover,[data-pressed]]:text-destructive-foreground duplicate exists solely to undo ghost-muted's [:hover,[data-pressed]]:text-foreground; with variant="ghost" the destructive tone survives hover without the second declaration:

       <Button
         size="micro"
-        variant="ghost-muted"
-        className={`${APPROVAL_ACTION_CLASS_NAME} text-destructive-foreground [:hover,[data-pressed]]:text-destructive-foreground`}
+        variant="ghost"
+        className={`${APPROVAL_ACTION_CLASS_NAME} text-destructive-foreground`}

Approve (lines 50-53) is the same case: variant="ghost" replaces ghost-muted + text-foreground exactly, so the row keeps one muted default (Cancel / Always allow this session) and two explicit non-muted actions without fighting the variant.


2. apps/web/src/components/chat/ComposerStashBadge.tsx (line 32)

        props.menuOpen && "invisible pointer-events-none",

Making the tab invisible pointer-events-none while menuOpen removes the only way to collapse this disclosure from its own trigger: the stash drawer is a z-[70] portal that covers the tab's shoulder, and visibility: hidden also drops the button from hit testing and tab order, so aria-expanded="true" is reported on a control that can never be activated again.

It also strands the guard added in ComposerStashMenu.tsx (lines 49-51), which exempts [data-prompt-stash-badge="true"] from outside-pointer dismissal precisely so the badge can toggle the drawer closed — that branch is now unreachable.

Suggest either keeping the tab hit-testable above the open drawer so a second click collapses it (matching the aria-expanded contract), or, if the tab is meant to disappear behind the drawer, dropping the dead badge exemption in ComposerStashMenu so both modules describe the same dismissal path.

Posted via Macroscope — UI Consistency

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI consistency review — one new finding on the live tool row; see the inline comment. Findings already posted on earlier commits of this branch are not repeated.

Posted via Macroscope — UI Consistency

return (
<button
type="button"
className="group/live-work flex min-h-6 w-full max-w-full cursor-pointer items-center rounded-md text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/70"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This button and the settled tool-group summary button (line 1535) are the same disclosure — both call ctx.onToggleWorkGroup with the same work-group:tool:<turn>:<callId> id and reveal the same children — but only the settled one gets pointer feedback (transition-colors hover:bg-accent/20, also used by the overflow toggle at line 1569 and by expandable PlainWorkEntryRow at line 2664). The live row declares cursor-pointer and aria-expanded with no hover or press state and no chevron, so while a turn is running the control looks inert, then grows a hover tint the moment the batch settles into Used 1 tool. The new test at MessagesTimeline.test.tsx (expect(markup).not.toContain("hover:bg-accent/20")) pins that divergence, so it would need updating with the fix.

group/live-work also has no group-*/live-work consumer anywhere, so it can go.

Suggested change
className="group/live-work flex min-h-6 w-full max-w-full cursor-pointer items-center rounded-md text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/70"
className="flex min-h-6 w-full max-w-full cursor-pointer items-center rounded-md text-left transition-colors hover:bg-accent/20 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/70"

Posted via Macroscope — UI Consistency

Comment thread apps/web/src/components/chat/MessagesTimeline.tsx Outdated

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI consistency review — one finding on the reworked activity column.

Posted via Macroscope — UI Consistency

Comment thread apps/web/src/components/chat/MessagesTimeline.tsx
Comment thread apps/web/src/session-logic.ts Outdated

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI consistency review — one new finding on the overflow work-toggle's onlyToolEntries contract. A previously reported item (the disclosure chevron in PlainWorkEntryRow losing its text-icon-muted owner) is still open; not re-posted here.

Posted via Macroscope — UI Consistency

Comment thread apps/web/src/components/chat/MessagesTimeline.logic.ts Outdated
entry.toolCallId = toolCallId;
}
let toolLifecycleStatus = extractWorkLogToolLifecycleStatus(payload);
if (!toolLifecycleStatus && activity.kind === "tool.started") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium src/session-logic.ts:904

Id-less tool.started activities leave an extra permanently running row after the tool completes. This fallback marks every start as inProgress, but shouldCollapseToolLifecycleEntries refuses the id-less started -> updated sequence, so the update/completion fold separately instead of replacing the original row; only assign the fallback status when toolCallId is present.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/web/src/session-logic.ts around line 904:

Id-less `tool.started` activities leave an extra permanently running row after the tool completes. This fallback marks every start as `inProgress`, but `shouldCollapseToolLifecycleEntries` refuses the id-less `started -> updated` sequence, so the update/completion fold separately instead of replacing the original row; only assign the fallback status when `toolCallId` is present.

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI consistency review — one finding on the approval drawer's accessible state.

Posted via Macroscope — UI Consistency

@macroscopeapp

macroscopeapp Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

UI consistency review — the inline comment could not be attached to the diff, so the finding is below.

apps/web/src/components/chat/ComposerPendingApprovalPanel.tsx (lines 30-41)

    <div
      aria-label={detailAriaLabel}
      className={cn("flex min-w-0 flex-1 items-center gap-2", className)}
      role="group"
    >

The removed heading (PENDING APPROVAL / Command approval requested) was the only text stating that the composer is blocked on an approval, and the replacement group is named Command / File to read / File change. fallbackLabel only renders when detail is empty, so in the normal case the word "approval" never appears anywhere in this drawer — the remaining cue is the data-variant="warning" tint on the drawer (ChatComposer.tsx:2728), i.e. a color-only state, and a screen reader hears a group called "Command" holding a code block plus Cancel / Decline / Approve.

Suggest naming the group with the approval kind so the state survives regardless of detail, and moving the field label onto the focusable <code> it describes:

     <div
-      aria-label={detailAriaLabel}
+      aria-label={fallbackLabel}
       className={cn("flex min-w-0 flex-1 items-center gap-2", className)}
       role="group"
     >
       <code
+        aria-label={detailAriaLabel}
         className="block max-h-20 min-w-0 flex-1 overflow-auto whitespace-pre font-mono text-[11px] text-foreground/85 [scrollbar-width:thin] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/70 [&::-webkit-scrollbar]:h-1.5"
         data-approval-detail="complete"
         tabIndex={0}
       >

Posted via Macroscope — UI Consistency

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI consistency review — one finding on the new tool-group disclosures.

Posted via Macroscope — UI Consistency

Comment on lines +1540 to +1549
<span
className={cn(
"flex size-6 shrink-0 items-center justify-center",
row.hasFailure ? "text-destructive" : "text-icon-muted",
)}
role={row.hasFailure ? "img" : undefined}
aria-label={row.hasFailure ? "Tool call failed" : undefined}
>
<WorkEntryIconSvg
name={row.hasFailure ? "x" : toolGroupSummaryIconName(row.summaryKind)}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The aggregate failure icon here is not gated on collapse, so an expanded summary group swaps its category icon (eye / terminal / square-pen) for a destructive x and announces …, tool call failed while every failing child row below already renders its own x plus Tool call failed (PlainWorkEntryRow, lines 2620-2686).

The overflow toggle immediately below now avoids exactly that with showHiddenFailure = row.hasFailure && !row.expanded (line 1564), so the two disclosures in the same activity column disagree about aggregate failure state — one drops back to its normal icon when the failing rows become visible, the other doesn't. LiveWorkEntryTimelineRow (line 1482) has the same gap for its expanded batch.

Suggest deriving const showGroupFailure = row.hasFailure && !row.expanded; and using it for the icon name, wrapper color, role/aria-label, and the button's aria-label here (and the equivalent in the live row), so the aggregate marker only stands in for hidden rows.

Posted via Macroscope — UI Consistency

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI consistency review — one finding on the expanded work-group spacing predicate.

Posted via Macroscope — UI Consistency

Comment thread apps/web/src/components/chat/MessagesTimeline.tsx
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant