fix(web): Keep running, stopped, and failed rows visible in the work log - #4762
fix(web): Keep running, stopped, and failed rows visible in the work log#4762mwolson wants to merge 244 commits into
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Keeping these rows in the work log exposed that the row component had no treatment for them, so they borrowed the wrong one. A stopped tool rendered a green check with a "Completed" tooltip once the turn settled, because the settled-turn fallback treats any neutral row as success. A running tool rendered the "Empty" dash, which is meant for rows with no content. Both now render their own state: a spinner tooltipped "Running" while the turn is live, and a square tooltipped "Stopped" for an interrupted tool. An `inProgress` row that outlives its turn is also shown as stopped rather than completed, since the turn ended without the tool ever reporting a result. Reported by Macroscope on pingdotgg#4762, which caught the stopped-as-completed half.
Keeping these rows in the work log exposed that the row component had no treatment for them, so they borrowed the wrong one. A stopped tool rendered a green check with a "Completed" tooltip once the turn settled, because the settled-turn fallback treats any neutral row as success. A running tool rendered the "Empty" dash, which is meant for rows with no content. Both now render their own state: a spinner tooltipped "Running" while the turn is live, and a square tooltipped "Stopped" for an interrupted tool. An `inProgress` row that outlives its turn is also shown as stopped rather than completed, since the turn ended without the tool ever reporting a result. Reported by Macroscope on pingdotgg#4762, which caught the stopped-as-completed half.
caf6f38 to
d30b4a7
Compare
Codex review of pingdotgg#4762 caught that inferring "stopped" from a settled turn is wrong. `activeTurnInProgress` is thread-wide, and background work legitimately outlives the root run: `RunExecutionService` explicitly keeps ingesting a late background command completion after root terminal, in the order running -> root-finalized -> completed. The inference would have labelled that live work "Stopped" and then jumped it to "Completed". Both indicators now read the item's own lifecycle and nothing else. A genuine interrupt already terminalizes the item at the projection layer, which is what made the inference look unnecessary rather than harmful. Also from that review: - The work group's filter is now `workEntryShouldRenderInWorkLog`, a production helper, so the predicate tests exercise the shipped policy instead of a copy of the expression that could drift from it. - The lifecycle joins the row's accessible name ("Ran command …, running"), since the indicator sits inside the row button and did not contribute to it. The spinner is decorative rather than a nested role="status" announcing a generic "Loading". - Component tests cover running during an active turn, running after settle, stopped in both states, and a signal-less row staying hidden. Tooltip text is absent from static markup, so they assert on the icon and accessible name, as the existing failed-indicator test does.
Keeping these rows in the work log exposed that the row component had no treatment for them, so they borrowed the wrong one. A stopped tool rendered a green check with a "Completed" tooltip once the turn settled, because the settled-turn fallback treats any neutral row as success. A running tool rendered the "Empty" dash, which is meant for rows with no content. Both now render their own state: a spinner tooltipped "Running" while the turn is live, and a square tooltipped "Stopped" for an interrupted tool. An `inProgress` row that outlives its turn is also shown as stopped rather than completed, since the turn ended without the tool ever reporting a result. Reported by Macroscope on pingdotgg#4762, which caught the stopped-as-completed half.
Codex review of pingdotgg#4762 caught that inferring "stopped" from a settled turn is wrong. `activeTurnInProgress` is thread-wide, and background work legitimately outlives the root run: `RunExecutionService` explicitly keeps ingesting a late background command completion after root terminal, in the order running -> root-finalized -> completed. The inference would have labelled that live work "Stopped" and then jumped it to "Completed". Both indicators now read the item's own lifecycle and nothing else. A genuine interrupt already terminalizes the item at the projection layer, which is what made the inference look unnecessary rather than harmful. Also from that review: - The work group's filter is now `workEntryShouldRenderInWorkLog`, a production helper, so the predicate tests exercise the shipped policy instead of a copy of the expression that could drift from it. - The lifecycle joins the row's accessible name ("Ran command …, running"), since the indicator sits inside the row button and did not contribute to it. The spinner is decorative rather than a nested role="status" announcing a generic "Loading". - Component tests cover running during an active turn, running after settle, stopped in both states, and a signal-less row staying hidden. Tooltip text is absent from static markup, so they assert on the icon and accessible name, as the existing failed-indicator test does.
…eachable Codex round 2 on pingdotgg#4762. Once `workEntryShouldRenderInWorkLog` admits neutral rows only when they carry a reported lifecycle, the row's neutral handling can never fire: `showNeutralIndicator` and the settled-turn success fallback both require the opposite. So `turnSettled`, `hasReportedLifecycle`, `showNeutralIndicator`, the "Empty" dash branch, and the compound success expression were all dead. Success is now just `workEntryIndicatesToolSuccess`, and the row no longer reads thread-wide turn state at all. Also from that round: - Tests pin "no stopped indicator" properly. A stopped row could have regressed to the dash while every assertion still passed; there is now a negative `lucide-minus` check, and the running case asserts the spinner carries neither `role="status"` nor `aria-label="Loading"`, so the accessibility fix cannot silently revert. - The comment claiming both states get their own indicator is gone, as is the overstatement that a "Run interrupted" divider always sits beneath a stopped row: `cancelled` maps to stopped too and has no such row.
…eachable Codex round 2 on pingdotgg#4762. Once `workEntryShouldRenderInWorkLog` admits neutral rows only when they carry a reported lifecycle, the row's neutral handling can never fire: `showNeutralIndicator` and the settled-turn success fallback both require the opposite. So `turnSettled`, `hasReportedLifecycle`, `showNeutralIndicator`, the "Empty" dash branch, and the compound success expression were all dead. Success is now just `workEntryIndicatesToolSuccess`, and the row no longer reads thread-wide turn state at all. The running test also asserts the spinner carries neither `role="status"` nor `aria-label="Loading"`, so the accessibility fix cannot silently revert. The comment claiming both states get their own indicator is gone, as is the overstatement that a "Run interrupted" divider always sits beneath a stopped row: `cancelled` maps to stopped too and has no such row.
5a7eae3 to
257f810
Compare
…eachable Codex round 2 on pingdotgg#4762. Once `workEntryShouldRenderInWorkLog` admits neutral rows only when they carry a reported lifecycle, the row's neutral handling can never fire: `showNeutralIndicator` and the settled-turn success fallback both require the opposite. So `turnSettled`, `hasReportedLifecycle`, `showNeutralIndicator`, the "Empty" dash branch, and the compound success expression were all dead. Success is now just `workEntryIndicatesToolSuccess`, and the row no longer reads thread-wide turn state at all. The running test also asserts the spinner carries neither `role="status"` nor `aria-label="Loading"`, so the accessibility fix cannot silently revert. The comment claiming both states get their own indicator is gone, as is the overstatement that a "Run interrupted" divider always sits beneath a stopped row: `cancelled` maps to stopped too and has no such row.
257f810 to
365949a
Compare
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — UI bug fix with well-scoped changes to work log display logic. Adds helper functions and smarter collapsing to keep running/stopped tools visible. Includes comprehensive test coverage. No backend or API changes. You can add or adjust custom eligibility rules. Learn more. |
Macroscope on pingdotgg#4762. `projectedWorkEntryStatus` collapses `pending`, `running`, and `waiting` into `inProgress`, so keying the spinner off `toolLifecycleStatus` made a queued tool and an approval request blocked on the user both claim to be running, with a "running" tooltip and accessible suffix to match. That was a regression from this PR: before it, those rows reached the indicator chain with no branch that applied and rendered nothing. `workEntryIsExecuting` reads the projected item's real status instead, so only a genuinely running tool spins. `pending` and `waiting` rows still render, since showing in-flight work is the point of the PR, but they claim nothing. The test helper now derives the projected status from the collapsed lifecycle value, so a fixture cannot assert on a pairing the projection never emits; the previous version let a `stopped` row carry a `running` item.
Dismissing prior approval to re-evaluate 7d5e55c
…eachable Codex round 2 on pingdotgg#4762. Once `workEntryShouldRenderInWorkLog` admits neutral rows only when they carry a reported lifecycle, the row's neutral handling can never fire: `showNeutralIndicator` and the settled-turn success fallback both require the opposite. So `turnSettled`, `hasReportedLifecycle`, `showNeutralIndicator`, the "Empty" dash branch, and the compound success expression were all dead. Success is now just `workEntryIndicatesToolSuccess`, and the row no longer reads thread-wide turn state at all. The running test also asserts the spinner carries neither `role="status"` nor `aria-label="Loading"`, so the accessibility fix cannot silently revert. The comment claiming both states get their own indicator is gone, as is the overstatement that a "Run interrupted" divider always sits beneath a stopped row: `cancelled` maps to stopped too and has no such row.
Macroscope on pingdotgg#4762. `projectedWorkEntryStatus` collapses `pending`, `running`, and `waiting` into `inProgress`, so keying the spinner off `toolLifecycleStatus` made a queued tool and an approval request blocked on the user both claim to be running, with a "running" tooltip and accessible suffix to match. That was a regression from this PR: before it, those rows reached the indicator chain with no branch that applied and rendered nothing. `workEntryIsExecuting` reads the projected item's real status instead, so only a genuinely running tool spins. `pending` and `waiting` rows still render, since showing in-flight work is the point of the PR, but they claim nothing. The test helper now derives the projected status from the collapsed lifecycle value, so a fixture cannot assert on a pairing the projection never emits; the previous version let a `stopped` row carry a `running` item.
…gger The trigger's icon falls through to the Button default (size-4) while the right cluster hard-coded size-3.5, so the two ends of the titlebar read a pixel apart on every edge. All five layout-control icons now use size-4, matching the trigger and the pull requests page's refresh icon. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Omit transcript bodies from shell rows - Query archived threads separately and stream compact resume metadata
Re-applies the deltas that mid-stack blob reverts discarded, and merges main's work into the v2-owned surfaces: - keybindings: main's STATIC_KEYBINDING_COMMANDS rename plus both new commands (rightPanel.toggleMaximized alongside threadPanel.toggle) - OpenInPicker: main's remote-open/SSH routing and favorite-editor shortcut layered onto the branch's panel/toolbar variants; the extracted shouldShowOpenInPicker now takes remoteOpenMode - ChatMarkdown: main's bare-filename resolver (pingdotgg#6297) ported into the branch's module-level component factory, plus pingdotgg#4133 title-attribute stripping on links and images - ComposerPrimaryActions: main's pingdotgg#4781 model (stop stays reachable, send joins it when Enter-to-send is unavailable) carrying the branch's steering send button - ComposerPendingUserInputPanel: main's collapsible redesign with the v2 RuntimeRequestId and responseCapability gate - ChatComposer: main's oversized-prompt submission guard wrapping the branch's dispatch-mode send - preview shell: main's container-aware width clamp ported into the branch's usePreviewPanelInlineSize hook - MessagesTimeline/Sidebar: main's day-aware timestamps, code-font tool bodies and provider accent badges on the v2 runtime shell - index.css: main's @variant dark migration (pingdotgg#6381) replaces the branch's standalone .dark block - contracts: main's send-turn image mime allowlist re-homed to chatAttachment.ts, where v2 keeps the other send-turn limits Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports v1's pingdotgg#5246 guard into the v2 dispatcher: a stored receipt only proves that this exact command already ran for the thread it was recorded against, so returning it for a command aimed at a different thread reports success for work that never happened there. The check is extracted as canReplayCommandReceipt so the rule is unit-testable, and reuse now fails with OrchestratorCommandIdConflictError like the v1 path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Keep prominent activity rows visible with lifecycle status and provider metadata - Move feed sizing logic into tested helpers and preserve native measurement for activity groups
Re-applies the deltas that mid-stack blob reverts discarded, and merges main's round-9 work into the v2-owned surfaces: - settings: main's Integrations page (pingdotgg#7082) coexists with the branch's Scheduled Tasks page in the path union, section labels, icons, and search catalog - contracts: main's preview appearance/zoom/viewport settings imports restored beside the branch's modelSelection home for ModelSelection - mobile: main's built-in themes (pingdotgg#6619) re-applied to the v2 thread screens and work log (useThemeColor over hand-rolled color-scheme ternaries) - MessagesTimeline: main's pingdotgg#7157 cleanup adopted (toolCallExpandedBody class name unexported, implementation-detail test dropped) - ChangedFilesTree: main's styled tooltip (pingdotgg#7209) carrying the v2 runId - pullRequestDetail tests: branch's row-action coverage renamed onto main's buildAddSelectionToAgentHandoff (pingdotgg#6597) - lint: migrated the six branch-owned native title tooltips that main's new no-native-title-tooltip rule (pingdotgg#7209) flags to styled Tooltips (GitActionsControl, QueuedRunsControl, TimelineSystemDivider, MessagesTimeline intent badge and MCP tool logo) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports pingdotgg#7083 into the v2 session path, which replaced the v1 ProviderService where main's gate lives. Instead of withholding the whole t3-code MCP credential — on this branch it also carries the thread orchestration and worktree toolkits — the credential is minted without the "preview" capability when enableAgentBrowserAccess is off, so every preview tool call rejects while orchestration stays available. ProviderSessionManager reads the setting at prepare time (deny on an unreadable settings file, matching main), rotates a reused credential whose capability set no longer reflects the setting, and the session config now carries browserToolsAvailable so the Codex adapter keeps its developer instructions truthful via main's parameterized instruction builders instead of the removed constants. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
519c42a to
4c55679
Compare
…s style simplification Main's pingdotgg#6381 deleted the shared .workspace-topbar and scroll-fade rules from index.css after inlining them at main's own call sites, but this branch's slim chat chrome still references both classes. The round-8 rebase took the deletion without migrating the branch call sites, so the header collapsed to zero height — the breadcrumb sat on the window edge, timeline rows scrolled unfaded through it, and the thread-details popover anchored to the collapsed header. Restores both as composable utilities in pingdotgg#6381's own style: a workspace-topbar utility for the titlebar rows, and the branch's chat-timeline-scroll-fade mask (soft ramp plus a full-height scrollbar column). Also drops the duplicated media override and its dead settings-page-scroll-fade selector. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ation A follow-up sweep against pingdotgg#6381 found the branch still carrying the pre-simplification forms it replaced, which my earlier fix had papered over with a compat utility instead of finishing the migration: - ChatView now uses main's inlined titlebar sizing and the data-workspace-titlebar-controls hook on both control clusters. The class-based markup was silently missing the themed-toggle bridge (html[data-theme-id] [data-workspace-titlebar-controls] …), so custom themes lost their titlebar accent in the thread view. - The scroll-to-end pill becomes main's Button size="xs" variant="glass" instead of a hand-rolled button recreating it. - MessagesTimeline uses main's consolidated topbar-scroll-fade utility; the byte-identical chat-timeline-scroll-fade copy and the workspace-topbar compat utility are gone. - The composer-glass dark rules move into nested @variant dark like main's (the raw .dark duplicates could drift from the nested copies they shadowed), including the branch-only queue strip. - The pre-pingdotgg#6381 dialog-glass/dialog-backdrop/dropdown-glass class rules and their .dark variants are deleted: the pingdotgg#6381 utilities plus call-site shadow utilities own every declaration, and the stale dropdown rule still had the saturate-less backdrop-filter. The dead model-picker-surface dark rule goes with them. index.css now has zero raw .dark selectors outside the variant definitions, matching the doctrine in .macroscope/check-run-agents/ui-consistency.md. Verified against the emitted production CSS: dark variants compile to :is(.dark,.dark *) with their @supports color-mix fallbacks intact. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Complete retry items when provider activity resumes - Keep retry progress visible across web and mobile clients
b387e85 to
ede1305
Compare
There was a problem hiding this comment.
One issue found: the removal of the activeTurnInProgress prop leaves a stale call site in this file's own test, which no longer typechecks. Everything else in the diff (the Spinner running indicator, the , running/, stopped accessible-name suffixes, and the pinned collapsed work-group selection) is consistent with the existing indicator branches in SimpleWorkEntryRow and the shared UI primitives.
Posted via Macroscope — UI Consistency
Dismissing prior approval to re-evaluate dd1f712
CI Check fails on current CTM because layerTest now surfaces ServerSettingsError while the ProviderSessionManager test layer requires never. orDie matches the other test layers.
dd1f712 to
19d3389
Compare
Dismissing prior approval to re-evaluate 19d3389
Dismissing prior approval to re-evaluate 38109f8
2d623ac to
37f40d9
Compare
What Changed
The web work log keeps the rows that tell you what a turn is doing. Running tools stay in the log with a spinner. Interrupted tools stay visible without a success check. Collapsed work logs still show an active or interrupted tool even when newer rows would have pushed it out.
When a provider fails and the turn has no assistant message, the failed error stays in the timeline instead of only the sidebar. Recovered retries still fold like ordinary work.
Why
The server already had the right items. The web timeline treated running and interrupted tools like noise. When a finished turn collapsed its work log, it kept a final assistant message but hid a failed error, so the error only showed in the sidebar.
This is web-only. Mobile does not share these filters.
UI Changes
Running and stopped tool rows remain visible, including in compact work logs. Failed provider errors remain visible on settled turns with no assistant message. Stopped tools have no extra glyph. The interrupt row still carries the stop marker.
No before/after screenshots. Checked on a Codex turn in the web app: a foreground command showed a spinner while running, and after Stop the row stayed without a spinner or success check. A live failed provider turn had the error folded away before this change.
Checklist
Note
Low Risk
UI-only timeline presentation and tests. No auth, data, or orchestration behavior changes beyond a tiny test-layer
.pipe(Layer.orDie)tweak.Overview
Keeps in-flight and interrupted tool rows visible in the chat work log, and stops inferring their status from the whole turn.
Neutral rows with a reported
inProgressorstoppedlifecycle now render; only rows with no lifecycle signal stay hidden. Collapsed groups pin executing and stopped tools so a later completed row cannot bury them. Pending/waiting tools stay unpinned and show no glyph (the old minus/"Empty" indicator is gone).Row indicators now follow the item’s own status: a spinner only when
structuredPayload.status === "running", no success check for stopped tools, andaria-labelsuffixes for running/stopped/failed.activeTurnInProgressis removed so a settled turn cannot mark live background work as complete.Failed provider-error work entries also stay unfolded when there is no assistant message.
Reviewed by Cursor Bugbot for commit 38109f8. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Keep in-flight and stopped tool rows visible in collapsed work log groups
selectCollapsedWorkLogEntriesin MessagesTimeline.logic.ts to pin executing and stopped entries while showing only the most recent ordinary entries withinMAX_VISIBLE_WORK_LOG_ENTRIESworkEntryHasReportedLifecycle,workEntryIsExecuting, andworkEntryShouldRenderInWorkLogin session-logic.ts to distinguish genuinely running tools from neutral rows with no lifecycle signalSimpleWorkEntryRowin MessagesTimeline.tsx to show a spinner only for running tools, a checkmark for success, an X for failure, and no icon for pending/waiting; stopped state is conveyed via accessible name suffixactiveTurnInProgressprop fromMessagesTimelineandChatView; lifecycle indicators are now driven solely by entry dataderiveTurnFoldskeeps terminal provider error entries visible in settled turns instead of folding them awayworkEntryShouldRenderInWorkLognow hides neutral work log rows that lack any lifecycle signal — any caller expecting those rows to appear in collapsed groups will no longer see themMacroscope summarized 38109f8.