Repository navigation
UI Consistency: 1 issue found
apps/web/src/components/chat/MessagesTimeline.logic.ts (lines 707-710, used at 845 and 856) — the new workEntryIsInActiveRun predicate omits the active-region guard that its sibling entryBelongsToActiveTurn applies. When isWorking is true while unsettledTurnId is null (the send-acknowledgment window this PR handles via the new latestUserMessageAt fallback), the unsettledTurnId === null || clause makes every stale inProgress / task.progress entry anywhere in the thread count as active. Since nothing force-settles toolLifecycleStatus when a turn ends, an older expanded turn's group then becomes visible again and flips from the settled work-toggle summary to the animated work-live "Running …" row, flipping back once the turn id arrives.
An inline comment with a one-line suggested fix (require a known unsettled turn) has been posted; it keeps the new keeps separated in-progress tool runs visible test passing and leaves the trailing active-run path, which goes through entryBelongsToActiveTurn / isVisibleActiveToolEntry, untouched.
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: files matching apps/web/src/**/*.{ts,tsx,css} in the merge-base..head diff — ChatView.tsx, chat/MessagesTimeline.tsx, chat/MessagesTimeline.logic.ts, session-logic.ts, index.css, and the associated tests.
Verified as addressed since earlier commits on this branch, so not re-reported:
isExpandedToolGroupHeadernow also requiresrow.summary !== null, so the overflow toggle no longer collapses the spacing of rows it follows.TurnFoldTimelineRowandWorkingTimelineRownow sharetext-sm leading-relaxed text-muted-foreground tabular-nums, so the two headers that occupy the same slot match.- Expanded children of a live group re-filter with
workEntryIsVisibleInGroup(entry, true), sotask.progressrows no longer disappear on expand. - Contiguous in-progress runs separated by commentary now each render a
work-liverow instead of vanishing.
Findings previously raised on earlier commits of this branch and still present were deliberately not duplicated: the live row's entry-scoped label vs. group-scoped failure affordance, the summary group's failure icon not being gated on collapse (unlike the overflow toggle's showHiddenFailure), the live row lacking the hover:bg-accent/20 affordance its sibling disclosures carry, the still-required but unread activeTurnInProgress prop, and the two owners of tool-category classification (toolGroupAction vs. workEntryIconName).
CSS review: the new live-activity-focus* utilities in index.css are mask + animation composition with a prefers-reduced-motion fallback, which belongs in global CSS under the stated ownership rules; no finding.
🔍 Agent activity — 1 tool call
💭 I'm checking the current PR head at 615b474 against prior review comments to see what's new, focusing on the new timeline components, the live-activity CSS utilities, and the PlainWorkEntryRow changes. The Thinking row icon alignment issue seems addressed differently than expected—they aligned it with the working timer's padding instead of reserving an icon slot, which is a deliberate but different fix. I still need to verify whether the activeTurnInProgress prop was actually removed from the props interface and call sites, and I'm noticing the work-toggle failure icon gating only applies to the overflow toggle branch, not the summary branch, which could be an inconsistency worth flagging. The aggregate failure icon in the summary branch remains ungated on collapse state, matching a previously flagged issue, so I won't repeat that unless something changed. The isExpandedToolGroupHeader logic now correctly includes a null-summary check, so that one's resolved, and the in-progress tool runs visibility issue also appears fixed with the new test and prop threading. The label/failure mismatch in LiveWorkEntryTimelineRow is still present as before, and now I'm looking at how the working row typography compares against the turn-fold styling. Since most previously flagged issues are either fixed or unchanged, I should pull up the current MessagesTimeline.tsx to look for anything genuinely new that hasn't been caught yet. This PR only has 4 commits, unlike prior runs that showed more—likely from force-pushed history on other branches. I'll focus on the latest commit, 2f6812a to 615b474, "keep separated tool runs visible," and look at its diff. I'm comparing the two work-live row producers added across commits to check they stay consistent—one uses the active-run anchor with the latest active tool entry, while the newer branch builds its id from the timeline entry group anchor and picks the last in-progress entry instead, so I need to verify these don't diverge in behavior. Checking for potential id collisions: since the group path skips entries already covered by activeWorkEntryIds, the trailing active run shouldn't overlap with the group branch's anchors, so duplicate ids seem unlikely unless tool call ids coincide. I also want to confirm the new branch renders expanded children and respects the same rendering conventions as the summary branch, which it appears to do, so now I should actually open the file to verify this directly. I'm trying to reconcile prior review findings against the current commit history — several previously flagged issues (live row label mismatch, ungated failure icon, thinking icon alignment, unused prop, duplicated icon logic) were tied to commits that no longer appear in this branch's history, suggesting a rebase happened. I need to check whether those same issues persist in the current head despite the rebase, starting with whether the unused
activeTurnInProgressprop is still present and whether the summary branch's failure icon is still ungated on the expanded state. Now I'm shifting focus to what's genuinely new in the latest commit — the mid-timeline live work branch usingLiveWorkEntryTimelineRow, and whether the shimmer animation makes sense there since the group is still in-progress. I also want to check the actual rendered markup for accessibility concerns, like a button wrapping another interactive element. Checking that expanded children rows for live groups correctly filter with the active-run semantics, confirming the earlier fix holds. Now I need to verify the same filtering logic applies correctly for expanded children under a settled summary group, not just live ones. I'm now comparing the two live-row producers to see if they render consistently, and I'm spotting a potential styling mismatch: the summary toggle row has hover background and padding treatment, while the live entry row's button lacks that same hover/padding styling, which could create an inconsistent feel for what's functionally the same expand/collapse control. But I recall that hover-treatment differences between live and settled tool rows were likely already flagged in earlier runs, so raising this again risks being a repeat. I should keep searching for a genuinely new, unflagged issue instead. Checking other candidates: the live row button's aria-label logic seems fine since content provides the accessible name when not failed. I'm also examining whetheraria-expandedgets applied to rows that aren't actually expandable, and confirming that the duplicated aria-hidden overlay content in the live activity row doesn't double-announce failures. Now I'm looking at the new CSS utilities for live-activity-focus to see if the masking rules there introduce anything worth flagging. The positioning and reduced-motion handling for these utilities look correct, and the new search icon addition seems straightforward. I want to pull up the current PlainWorkEntryRow code to verify how aria-expanded is actually applied. I'm checking the icon slot logic in PlainWorkEntryRow for expanded tool group entries without failures—the invisible/aria-hidden handling looks fine since the span already wraps an aria-hidden svg. Now I'm shifting to look at the button logic in LiveWorkEntryTimelineRow. The live row's button lacks the hover background treatment that the other disclosure controls have, but since this was already flagged in earlier passes about live vs. settled hover treatment, I'll skip it. I'm also noticing a width mismatch: the button is full-width but its child content is only as wide as its contents, so the focus ring spans further than the visible element—worth comparing against how the toggle buttons handle this same pattern. Now I'm digging into a more substantive inconsistency: the trailing active run picks its representative entry as the last visible entry (which could be completed), while the mid-timeline live group picks the last in-progress entry specifically. That's a real semantic divergence in how these two live-row variants select which entry to display, not just a cosmetic styling difference. I'm also checking padding rules for expanded live groups and confirming they match the expanded tool group header treatment, then turning to whether the minimap or row-key logic needs to account for a live row appearing anywhere in the timeline rather than just at the end. I should check other places that consume rows by kind, like the minimap, scroll anchoring, onToggleWorkGroup, and row key extraction — searching for row.kind usage and estimatedItemSize in MessagesTimeline.tsx, plus the minimap logic that may enumerate row kinds for markers. I'm checking whether expanded child rows and collapsed work rows could end up with duplicate IDs, but tracing through the branches shows they use different id sources so there's no actual conflict there. Shifting focus now to broader UI consistency issues instead. One candidate: the live row's id is derived from the group's first entry's toolCallId, matching the trailing-run anchor, so those stay consistent. But when a live row settles into its toggle state, the row id changes format even though the groupId stays the same — meaning expansion state persists but the row id shift could cause LegendList to remount or trigger a scroll jump, since the second argument to onToggleWorkGroup likely anchors scroll position. Actually, this could be intentional for keeping separated in-progress tool runs visible, but I'm more concerned about the settled-turn edge case. IfunsettledTurnIdis null whileisWorkingis true, every staleinProgressentry anywhere in the timeline—even from old, interrupted turns—would get treated as an active live row, sinceworkEntryIsInActiveRunlacks the index guard thatentryBelongsToActiveTurnhas. I want to checkderiveUnsettledTurnIdto see exactly when it returns null and whether stale inProgress entries from aborted turns can actually persist that way. I'm trying to pin down the strongest regression case for the newest commit, thinking through a scenario where a trailing completed run coexists with an earlier in-progress entry group, and whether groups positioned above the active turn might be mishandled visually. Actually, the real bug seems to be in the window right after sending a message, before the server creates a turn —unsettledTurnIdcan be null even whileisWorkingis true, since the previous turn is settled and no running turn ID exists yet. In that gap,workEntryIsInActiveRunwould incorrectly treat any in-progress entry anywhere in the thread as part of the active run, including stale entries from long-finished or aborted turns that never got settled. This would cause historical work groups to briefly flip from a collapsed "Ran 3 commands" summary into a live shimmering "Running rg" row and then flip back once the turn is actually created — a visible glitch in scrollback. Though I'm now second-guessing how reachable this is, since older turns get folded behind "Worked for" headers and their entries are hidden unless expanded, so the flip would only be visible if the user had that fold expanded. Still a real but narrower issue. I also need to check whether this interacts withactiveTurnHeaderIndex, which marks the boundary for the first entry of the unsettled turn. The cleaner bug to flag is thatworkEntryIsInActiveRunis missing the same turn-membership guard thatentryBelongsToActiveTurnenforces, so whenunsettledTurnIdis null during the brief send-acknowledgment window, any stale in-progress entry anywhere in the scrollback — not just the current turn — renders as live and shimmering. The fix is to require the entry's turnId to match the active unsettled turn before treating it as live. I want to verify this is a genuine regression from the changed lines and then check the session logic to see whether stale in-progress entries actually persist across turn boundaries or get settled automatically. I don't see any global logic that settles all inProgress entries when a turn ends, so I'm searching for something like "turnEnded" or a settling mechanism. There's a prior review comment suggesting that gating inProgress on toolCallId could make an id-less tool.started row incorrectly settle as successful, which implies some persistence logic exists—I want to check deriveWorkLogEntries for anything that clears inProgress at turn end. I'm confirming stale inProgress entries persist, and checking whetherworkEntryIsInActiveRuncan return true for them whenunsettledTurnIdis null butisWorkingis true. I need to trace howisWorkinggets computed in ChatView and whether the running turn ID can be null while still settled, looking at the new session-logic test covering the fallback to the latest user message during turn acknowledgment. In the test scenario,activeTurnIdis turn-2 whilelatestTurnlags at turn-1, sorunningTurnIdderives from the active turn and correctly excludes stale entries from turn-1. But there's another window before the server assigns an active turn ID at all — during send-in-flight with session status "starting," whereactiveTurnIdis null,latestTurnis the previous settled turn, yetisWorkingis still true becausesendStartedAtis set. That's precisely the gap the newlatestUserMessageAtfallback is meant to handle. In that null-turn window,activeTurnHeaderIndexcorrectly points to the row after the last user message, andentryBelongsToActiveTurnenforces an index guard so only the trailing run counts. ButworkEntryIsInActiveRunhas no such index guard — so any stale in-progress entry anywhere in the scrollback gets swept into the active run, causing a shimmering "Running…" line to replace a settled summary and making a previously-hidden stale entry visible again. I also need to check how folding interacts here: folds only hide entries from settled turns with a terminal assistant message, so expanded or unfoldable turns would still expose this, while collapsed entries get skipped earlier in the loop via thecollapsedEntryIdscheck. This still feels like a real but narrow inconsistency — introduced specifically by the new predicate lacking the position/turn guard that its sibling function already has. The fix would be to pass index/turn context into the check so it only treats an entry as part of the active run when its index is at or beyondactiveTurnHeaderIndex, mirroring the guard already used elsewhere. Tracing through the test case confirms gating on entryBelongsToActiveTurn keeps index 0 correctly flagged as part of the active turn, so that test still passes. I also want to check whether the hover affordance issue was already flagged elsewhere — it seems the live/summary tool row distinction was already raised in a prior comment, so I can skip re-reporting that. Checking the button wrapper for LiveActivityRow, the shimmer effect only covers the content width since the inner div is fit-sized while the button spans full width, which seems fine as-is. Looking at the new mid-timeline branch, the live rows there aren't preceded by a "working" header like the trailing active row is, but that seems irrelevant, and the createdAt handling stays consistent between branches. I'm settling on reporting one finding: the missing active-region guard in workEntryIsInActiveRun, which causes a rendering-treatment leak where an animated live row and a newly visible stale entry both show up in scrollback — this looks like pre-existing behavior that the patch surfaces rather than a new bug. Before writing that up, I want to verify exactly how the collapsedEntryIds skip interacts with the group loop's position, so I'm going to look at lines 800-900 to get the comment precise. I need to place the inline comment on a line that's actually part of the diff—likely around line 707-710 where the new helper is defined, since that whole block is added in the merge-base diff. I should also verify the PR-level diff includes these same lines before finalizing. Thinking through the fix itself, gating the filter onentryBelongsToActiveTurnwould require the index, which isn't available inside the predicate. But that alone wouldn't be enough, since the earlier visibility filter also lets stale in-progress entries through, so a single-line suggestion wouldn't fully resolve the issue—I'd need to touch two separate filters or restructure the predicate to capture positional context. Instead, I realize I can simplify by dropping theunsettledTurnId === null ||clause so the predicate only treats entries as active when there's a known unsettled turn matching the entry's turnId. This keeps it a clean one-line change, doesn't affect the trailing active row logic (which uses different predicates), and still passes the new test case whereunsettledTurnIdequalsturn-1. I'm drafting this as an inline suggestion spanning the relevant two lines, now figuring out the correct diff line numbers to attach the comment to. I'll leave that second inconsistency alone since it's arguably intentional behavior rather than a bug, and raising it would dilute the focus of the review. I'll finalize with the single inline comment and mark the review as a failing check.
1. modify_pr — UI consistency review — one finding on the new mid-timeline live-run branch added in fix(web): keep separated tool runs visible. Findings already raised on earlier commits of this branch (live row label vs. group-scoped failure affordance, summary-group failure icon while expanded, live vs. settled tool-row hover treatment, unused activeTurnInProgress prop, icon-classification duplication) are not repeated.
Agent Credits: 214 credits