Repository navigation
UI Consistency: 1 issue found
apps/web/src/components/chat/MessagesTimeline.logic.ts (lines 727-740, 839-842) — the new backward scan that builds activeToolEntries stops at the first non-work entry, so an inProgress tool call that is not part of the trailing contiguous run never reaches the work-live row. It falls through to the ordinary group path, where workEntryIsVisibleInGroup(entry) treats an in-progress entry as neutral and filters it out, leaving visibleGroupedEntries empty and emitting no row at all. activeTurnHasVisibleContent still counts that entry, so the Thinking row is suppressed too, and the call only reappears (as a summarized Ran 1 command row) once it completes. The same running tool call therefore renders as a prominent animated live row in one arrangement and as nothing in another, depending only on whether a commentary message follows it. An inline suggestion keeping active-turn in-progress entries visible on the group path was posted.
Details
Note
Your check run agent prompt is: .macroscope/check-run-agents/ui-consistency.md
More information about how Check Run Agents work can be found in our Help Center.
Scope reviewed: apps/web/src/components/ChatView.tsx, apps/web/src/components/chat/MessagesTimeline.logic.ts, apps/web/src/components/chat/MessagesTimeline.tsx, apps/web/src/index.css, apps/web/src/session-logic.ts and their tests, at head 2f6812a.
Checked and not flagged:
- New
live-activity-focus*utilities inindex.cssare mask + keyframe composition with aprefers-reduced-motionfallback, which belongs in global CSS under the repository's stated ownership rules; the overlay isaria-hidden, so the animated duplicate label does not affect accessible names. - Icon-column geometry is consistent across the live row, summary toggle, overflow toggle and expanded child rows (
px-0.5+size-6slot +gap-1.5); the iconlessThinkingrow intentionally aligns to thepx-1working timer. - The
turn-foldlabel was moved totext-sm leading-relaxed, resolving the earlier type-scale mismatch with the working header, andisExpandedToolGroupHeadernow matches the renderer'ssummary !== null && onlyToolEntriescondition.
Not repeated from earlier commits of this branch (unchanged code, already commented):
LiveWorkEntryTimelineRowderives its label fromrow.entrybut its failure affordance from the whole group.activeTurnInProgressremains a required prop onMessagesTimelinePropswith no reader inside the component.
🔍 Agent activity — 1 tool call
💭 I should check the current PR head at commit 2f6812a to see what's changed relative to the previously flagged commits, then look at the actual files there. The branch appears to have been rebased or squashed, now down to just 3 commits, so earlier references to other commits no longer apply. The last flagged issue was about live row label scoping in commit 2a5ac86, and since head is now 2f6812a, I want to check the current code to see if that finding still holds. The last commit fixed the text size mismatch from run 5addb76, but the live row failure scoping issue flagged in run 2a5ac86 is from a commit that no longer exists after rebasing, so I need to check if that problem still exists at head by examining the current LiveWorkEntryTimelineRow implementation. Confirming the label is entry-scoped while failure detection is group-scoped — that's the same issue already flagged previously, so I shouldn't repeat it since the code hasn't changed. Since the branch was rebased and squashed, some previously-flagged code (ComposerStashBadge, ThreadTerminalDrawer, ChangedFilesTree) isn't even in the current diff anymore, so I need to focus only on what's actually in scope now: ChatView, MessagesTimeline components, index.css, and session-logic, and look for genuinely new findings starting with the LiveActivityRow and ThinkingActivityRow areas. Checking the thinking row alignment fix — it now uses conditional padding based on whether there's an icon, with the iconless thinking row matching the working timer's padding, which resolves the earlier alignment finding. I want to verify this doesn't introduce a new misalignment with icon-bearing tool rows, but it seems intentional since the thinking row is meant to align with the timer text specifically. Now I'm checking whether WorkingTimelineRow and TurnFoldTimelineRow duplicate the same border and padding styling, which would be worth flagging as redundant chrome across components — I need to pull up both regions to compare. I'm now looking at the WorkingTimelineRow component at the head of the file. I'm noticing that the live work entry timeline row's toggle button lacks the hover treatment that the work group toggle rows have, even though both control the same kind of disclosure state—this inconsistency in hover affordance seems worth flagging as a new finding. Next I want to check whether
PlainWorkEntryRowsetsaria-expandedunconditionally on the trigger even whencanExpandis false, so I'll look at its current implementation. I'm looking at how the newest commit changes activeToolEntries to only capture the trailing contiguous run of tool entries by walking backwards from the end, rather than flatMapping over all active entries—this matches the test expecting adjacent active tool calls to stay in one replacing row. Now I'm tracing how activeWorkAnchor and the placement entry ID get derived from that trailing run. I'm checking an edge case: if all entries in the trailing run get filtered out as invisible (neutral, not in-progress), latestActiveToolEntry becomes undefined and the whole activeWorkRow disappears from the timeline while working—but since settled rendering also filters neutral entries via workEntryIsVisibleInGroup, this seems consistent rather than a bug. Now I'm shifting to look for genuine UI-consistency issues, starting with icon column alignment in LiveActivityContent where the padding, icon size, and gap combine to offset the text by a specific pixel amount. Checking the other row types—WorkGroupToggleTimelineRow and PlainWorkEntryRow—they use the same padding, gap, and icon size, so they land at the same 32px offset, while the working and thinking rows use a smaller padding that puts their text at 4px, meaning thinking rows align with the timer text but not with the live tool row that replaces them (already noted). I'm also noting that LiveWorkEntryTimelineRow's button is missing the hover background styling present on the other row types, and that it defines a group class name that appears to have no corresponding group-hover consumers anywhere—worth grepping to confirm it's dead code. I'm also reviewing the live-activity-focus CSS utility, checking its positioning logic for masks and animations, which should be fine per the global CSS rules. The reduced-motion branch correctly hides the highlighted overlay by setting opacity to zero, and the counter/aligned variants seem only used internally, so that's fine. Now I want to check for unused imports—MinusIcon was removed from WorkEntryIconSvg in favor of SearchIcon, but CheckIcon, XIcon, and Tooltip might now be orphaned after the indicator block was removed from PlainWorkEntryRow, which would break lint if unused. I also want to confirm workEntryIndicatesToolSuccess and workEntryIndicatesToolNeutralStatus are still properly used elsewhere since they were removed from MessagesTimeline.tsx's imports—logic.ts still references the neutral status one, so that's fine, but I should grep MessagesTimeline.tsx for remaining CheckIcon/XIcon usage to be sure. The unused group class names seem like a minor nit, not worth flagging as a real violation. I want to focus on bigger issues instead, so I'm checking the logic around active-turn/work-live derivation and how it interacts with MAX_VISIBLE_WORK_LOG_ENTRIES and potential id collisions, especially since rows are keyed by row.id in LegendList. Tracing through, the live group's expanded child rows use the work entry's own id, same as the settled group, and since the main loop skips timeline entries already in activeWorkEntryIds, there shouldn't be duplicate keys there. Now I'm looking at the row-padding logic in TimelineRowContent after the recent commit that added a border and bottom padding to the working row, checking whether isExpandedToolGroupHeader correctly handles the work-live expanded case and whether the earlier mismatch between onlyToolEntries conditions was actually fixed at head. Confirming that fix is in place now, I'm turning to what's new in the latest commit: changing activeToolEntries to only grab the trailing contiguous run of tool entries rather than all of them. This means earlier tool entries in the same turn now fall back to the normal path and render as a collapsed "work-toggle" summary row instead of being absorbed into the live row, which matches what the test expects. I'm now considering whether this creates a visual inconsistency — the live row shows just the latest entry while the group is running, but once the turn settles it collapses into a summary like "Ran 3 commands," which could look like two different renderings of the same data depending on timing. I'm checking how expanded children are pushed for the live work group and confirming the ordering (header row, then children) matches the settled toggle summary's ordering, so that part stays consistent. But I notice a discoverability gap: the settled summary toggle row has no chevron indicator even though it's expandable, and neither does the live row, whereas the overflow toggle row does show a chevron — so users might not realize those rows can be expanded. The live row also lacks any hover background at all, unlike the summary row which has a subtle hover state, making the inconsistency worse there. I recall this hover/discoverability issue was already flagged in a prior review pass, so I'd rather not raise it again and should look for something new — maybe in ChatView.tsx or the active-work-start derivation logic. The earlier run already flagged the deadactiveTurnInProgressprop, so I want to verify whether it's still unused at head by checking MessagesTimeline.tsx for any remaining references. I realize my grep was against the default branch HEAD rather than the PR's head commit, which explains the discrepancy with the diff — I need to redo the query using the correct tree URL for the PR head. So on main,activeTurnInProgressis actively used, and the PR removes its usage from TimelineRowActivityState while keeping the prop declaration — something flagged before in a3edfd3. I need to check whetherMessagesTimelinePropsstill requires this prop even though the diff didn't show that line being removed, so I should grep the PR head directly to confirm. That unused prop issue was already flagged as minor in the prior review and the code hasn't changed, so I shouldn't repeat it. I need to focus on what's new since the last reviewed commit, and I'm trying to map out the commit history to figure out exactly which commits are new versus already reviewed. Since 2a5ac86 isn't in the current history, it looks like it was amended into 2f6812a, which includes the text-size fix addressing the earlier finding plus a new change to how active tool entries are grouped into contiguous runs. I can't diff directly against the old amended commit, so I'll need to review the current head holistically and only surface genuinely new findings, starting with the TurnFold text size change. The turn-fold label now uses text-sm to match assistant message text, though its chevron icon stays at size-3.5 while other rows use size-4 — a minor inconsistency, not worth flagging. I'm now checking the work-toggle and work-live rows to see how they handle toggling. I'm also checking whether removing the success/neutral indicators from PlainWorkEntryRow left any unused Tooltip imports, and verifying accessibility: the collapsed summary toggle only sets aria-label on failure, otherwise relying on the summary text for its accessible name, which seems fine. Similarly, the live row's aria-label is conditional on failure, and since LiveActivityRow's duplicate label overlay is aria-hidden, the accessible name resolves correctly to a single instance of the label. Now I'm confirming the overlay div is indeed aria-hidden, and that LiveActivityContent's announceFailure defaults to false so there's no duplicate "Tool call failed" announcement — the button's aria-label takes precedence over the inner icon's role="img" label, avoiding duplication. ThinkingActivityRow isn't a button so it's unaffected. Writing CSS utility styles... I'm checking that the mask animation sweeps across the full width including the extra offset, and confirming the positioning properties don't conflict with the absolute/inset-y-0 classes applied alongside the custom utility. The counter-animation keeps the inner content stationary while the outer shimmer moves, and reduced-motion settings disable it by setting opacity to zero. I'm also verifying the mask color works fine across both light and dark themes since it's just used as a mask, and confirming the shimmer stays confined to the overlay so the base text remains readable underneath. Now I'm checking the padding logic for the work-live row inside TimelineRowContent, tracing through the conditional chain for expanded versus collapsed states, and I want to look more closely at the wrapper component for any data attributes or hover group scoping I might be missing. I'm noticing the working row's header chrome duplicates the TurnFold header verbatim — the same "two owners" issue flagged in the earlier 5addb76 finding, which noted the chrome was copied byte-for-byte but with a different type scale. That fix aligned the type scale but left the duplication itself unresolved, so flagging it again risks being a re-hash of a point already raised. I'm now tracing the contiguous-run loop for the newest change more carefully, checking the boundary condition whenisWorkingis false so the loop correctly doesn't execute, and verifying thatentryBelongsToActiveTurnand the fold-skip ordering interact correctly. I'm also examining whetheractiveWorkPlacementEntryIdgets pushed properly when the main loop reaches that entry, since it might get skipped viacontinueif it's already counted among the active work entries — that could be a real bug worth digging into further. I'm now checking edge cases aroundlatestActiveToolEntrybeing filtered byomitSupersededLifecycleMarkers, whether placement entries inside fold-anchored groups behave correctly, and theMAX_VISIBLE_WORK_LOG_ENTRIESbranch logic — these all seem fine. More interesting is a change inPlainWorkEntryRowwheredisplayTextnow prefersworkEntryPreview(...)over the heading entirely, and a test was updated to expect "Changed 1 file" instead of a full path, so I want to check whatworkEntryPreviewactually returns for file-change entries now. The workEntryPreview function itself stays the same, returning a path for a file change, but the test expecting "Changed 1 file" suggests the rendering now collapses into a summary row rather than showing individual work entries. I should look at the test around lines 741-790 to confirm this behavior. I notice a single settled tool entry collapses into a summarized "Changed 1 file" row instead of showing the individual file path directly — that's a deliberate product choice to group even one-item results. Clicking it expands to reveal the actual row with the path, so I'm now checking whether this summarization behavior stays consistent across similar cases. Looking closer, the disclosure indicators are inconsistent: the summary row has no chevron at all, while the overflow toggle and expandable rows do show one, so users get no visual cue that the summary is clickable. I also want to verify the horizontal alignment between the live tool row and the "Thinking" row, since they occupy the same slot but have different padding and icon offsets — this seems to be the deliberate fix the author made in response to my earlier alignment comment. I should check whether the width calculations for the live activity mask overlay and its focus-aligned variant actually line up correctly with the parent container, and then look in index.css to see if this new shimmer/mask utility duplicates an existing animation utility already defined elsewhere in the codebase. I notice the existing theme convention registers animations as tokens for use with animate-* utilities, while the new code hardcodes the animation directly inside a compound utility—which seems acceptable given it combines mask and animation. I also notice the existing keyframes live inside the theme block, while the new keyframes are defined at the top level instead, which is a minor inconsistency worth flagging. I'm reconsidering whether there's a genuine regression worth flagging in the latest commit, specifically looking at howshowThinkingdepends onactiveWorkRowbeing null andactiveTurnHasVisibleContentbeing false, and tracing through how that visibility check iterates active entries to determine if a work entry with tool content exists. With the new contiguous-run logic, an inProgress work entry that isn't part of the trailing contiguous run (say, followed by an assistant message) would still makeactiveTurnHasVisibleContenttrue, suppressing the Thinking row—but I need to check where that inProgress entry actually renders, since it's not inactiveWorkEntryIds. That routes through the normal work-group visibility path, so I'm now checking whetherworkEntryIndicatesToolNeutralStatustreats inProgress entries as neutral in the session logic. I'm realizing that an in-progress tool entry not part of the trailing contiguous run gets hidden entirely from the timeline while the turn is still running. With the new backward-scan logic, something like a running tool followed by an assistant message followed by another running tool means the first tool breaks out of the contiguous run and falls into the ordinary group path, where it ends up treated as neutral/hidden rather than visible. This does look like a real regression from this commit — previously all active-turn tool entries were flattened into the live group and stayed visible, but now an in-progress entry outside the trailing run gets dropped until it completes. This scenario is realistic too, since parallel tool calls with commentary streamed in between are common. I'm also checking whether other non-tool entries like agentSpawn rows or error-toned entries could break contiguity the same way, and whether the group summary counts undercount while a tool is hidden. I need to trace through the rendering branch to see what happens when all grouped entries get filtered out — whether nothing renders or some fallback kicks in. I've confirmed the visibility gate requires non-empty grouped entries, so filtered-out entries render nothing. This reveals the bug: an in-progress tool entry outside the trailing contiguous run vanishes from the timeline while the turn is active, whereas before this commit every active-turn entry stayed visible since the older visibility check explicitly preserved in-progress entries. Concretely, with parallel tool calls interleaved with streamed commentary — the same shape as the new test but with the earlier call still running — the rows become "working", "message", "work-live" with no row at all for the still-running command, and since the working header's showThinking is false, nothing represents it. That entry then pops back into view once it completes as a "Ran 1 command" summary, producing a visible inconsistency where the same entry renders differently or disappears depending on timing — this is a real regression worth flagging since it's a rendering/visibility issue introduced by the changed lines in scope. I'm tracing through the visibility logic to confirm: for an in-progress tool entry, success is false and it's treated as neutral, so it gets hidden from the group view — confirming the bug. There's also a related case where two tool entries get separated by an error-toned work entry. The smallest fix would be to make in-progress entries visible in the group path too, rather than only in the live batch. Wait, so this was actually hidden on main too, meaning it's not a regression against main but only against the prior PR commit. I need to check how main actually displayed running tools — whether neutral entries were filtered out by WorkGroupSection in a way that still let in-progress rows through. Maybe on main, the lifecycle collapses so an in-progress entry is transient and only shows once completed, meaning running tools were never individually visible there either — the "Working for" dots were the only activity signal. So this PR is actually introducing the live row concept for the first time, and the question is whether a running tool outside the trailing run being invisible counts as a regression against the PR's own stated goal versus against main's prior behavior. Given the instruction to focus on concrete violations tied to changed lines, I think this is a legitimate finding: the break-on-non-tool scan added in this commit narrows the live batch so the same running tool call can render as a prominent animated row in one arrangement and as nothing in another, depending solely on whether a commentary message follows it — that inconsistency is newly introduced by this commit's changed lines, and it wasn't something flagged in earlier review comments. I want to verify this against the prior commit's code to confirm that the earlier version would have included that entry in the active batch. Now I'm checking whetheractiveTurnHasVisibleContentstill treats that dropped entry as visible content even though it no longer gets a rendered row, which would create a further inconsistency — I'm tracing through the contiguous-run logic for a shape like an in-progress tool call followed by assistant commentary text to see how that plays out. Working through it, the commentary text alone already counts as visible content, so the Thinking row gets suppressed regardless, and the running tool ends up with no visible row at all — previously it would have rendered as the live row after the commentary. This confirms the tool entry becomes invisible in this common shape, which strengthens the case that something's off, and I want to write this up clearly. I'm pinpointing the exact lines to comment on in the diff — the new loop around lines 727-740 in MessagesTimeline.logic.ts, confirming these fall within the added hunk so they're commentable. Now I'm weighing fix options: either stop breaking the scan on non-tool entries (which would undo the intent of grouping adjacent runs), or make a smaller one-line change to the filter a bit further down that keeps running tools visible on the group path — the second option seems cleaner since it's isolated to another hunk already in the diff. I'm deciding where to place the comment itself: the root cause is the scan narrowing, but the actual fix belongs at the filter location further down. Since that's a separate hunk, I need to figure out whether a unified diff or inline suggestion format is appropriate given the review rules about cross-hunk fixes. I'm checking that passing the inProgress condition into the visibility helper still yields correct results, since it returns true for inProgress, task progress, or non-neutral entries—this minimal change should be fine, but I need to verify it doesn't break the existing "collapsedRows" test expectations. I'm worried this could resurrect stale "running" rows if a turn ends but an entry never receives a completion signal from the provider, sincederiveWorkLogEntriesmight leave inProgress rows dangling—there's a related session-logic test about not leaving id-less tool lifecycles running that suggests this edge case is already handled elsewhere. I'll suggest gating the filter on the active, unsettled turn so only entries tied to that turn while still working are shown, rather than any inProgress entry regardless of turn. I confirmunsettledTurnIdis in scope andentry.turnIdtypes check out fine against it. Nothing else in this commit seems worth flagging beyond the one concrete finding, so given the check defaults to failure, I'll mark this as a fail. I'm drafting the comment explaining that an in-progress tool call can disappear from the timeline when it's not part of the trailing contiguous run — the backward scan stops at the first non-work entry, so an earlier in-progress entry falls through to the group path where it gets hidden as neutral status, leaving nothing rendered for it even though it's still counted elsewhere. I'm checking the exact indentation of the existing filter code so I can write a suggestion that keeps running calls visible while the turn is active, matching the surrounding formatting.
1. modify_pr — UI consistency review of the tool-activity refresh. One finding on the new active-run scan; findings raised on earlier commits of this branch (live-row failure scoping vs. entry-scoped label, the dangling activeTurnInProgress prop) are not repeated.
Agent Credits: 203 credits