Repository navigation
UI Consistency: 3 issues found
Three findings, all in apps/web/src/components/chat/MessagesTimeline.tsx (inline comments posted):
- Order-dependent cascade conflict (line 1407) — the live-activity sweep overlay combines
absolute inset-0with the newlive-activity-focusutility, which ownsleft/right/width.inset-0emits theinsetshorthand at the same specificity in the same utilities layer, so the overlay's-4.5remoffset can be overridden depending on emission order, misaligning the highlighted duplicate label by 4.5rem againstlive-activity-focus-aligned'smargin-left. Fix: useinset-y-0. - Viewport breakpoints instead of container queries (lines 2410, 2414, 2431) — the rebuilt
AgentSpawnCtaRowcopiesChangedFilesCard's shell but hides its workflow name, status block and button label withmd:inline/sm:flex/sm:inline. The chat column shares a flex row with the resizable inline right panel and can shrink to ~360px on a wide viewport, so these labels stay visible and crowd the primary label.ChangedFilesCardhandles the same case with@container/changed-files+@[24rem]/changed-files:inline. - Failure state becomes color-only (line 2527) — after the "Failed" tooltip removal,
aria-label="Tool call failed"sits on a generic<span>, and the row's ownaria-label={displayText}(when expandable) replaces descendant text, so the destructive X is the only failure signal exposed. Fold the state into the row's accessible name or give the spanrole="img".
No issues found with the shared-primitive migration in AgentSpawnCtaRow (Button size="xs" variant="outline" with matching aria-label), the ChangedFilesCard header/preview restyle, or keeping the new mask/keyframe/animation utilities in index.css.
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.
Files examined: apps/web/src/components/chat/MessagesTimeline.tsx, apps/web/src/components/chat/MessagesTimeline.logic.ts, apps/web/src/components/chat/ChangedFilesTree.tsx, apps/web/src/index.css, plus apps/web/src/components/ui/button.tsx and the ChatView / right-panel layout shells for context.
Checks performed:
- Verified
Buttonsize="xs"andvariant="outline"exist in the primitive contract and that no call-site class string overrides the primitive's height, radius, focus ring or base colors in the new subagent card. - Confirmed the new raw
<button>rows (LiveWorkEntryTimelineRow, the tool-group summary row, the changed-files header/preview rows) implement semantic timeline rows witharia-expandedand focus-visible rings, so they are not localButtonreconstructions. - Confirmed the new keyframes are declared alongside existing ones in
@theme, and that mask/animation behavior legitimately stays in global CSS;--live-activity-focus-widthis declared on the masking element and correctly inherited bylive-activity-focus-aligned, andprefers-reduced-motionis handled. - Traced whether the chat timeline establishes a container context (it does not) and whether its column can be much narrower than the viewport (it can: the inline right panel is drag-resizable down to a 360px sibling column, and can be maximized to collapse the chat column), which is what makes the
sm:/md:variants incorrect for this card. - Noted but did not report:
summarizeChangedFileScopesinchangedFilesPresentation.tsnow has only its own test as a consumer after the scope summary row was removed, and theworkingStepLabelcontext field is no longer rendered byWorkingTimelineRow. Both are dead-plumbing cleanups rather than consistency or regression issues.
Not verifiable with the available tools: emitted production CSS ordering for the custom @utility versus core inset-0, and real-app light/dark visual evidence for the restyled rows. The cascade finding is reported as an ownership/order-dependence issue that holds regardless of the current build output.
🔍 Agent activity — 1 tool call
💭 Looking at the diff areas to identify what might be breaking the rules. The
LiveWorkEntryTimelineRowuses a raw button element for a semantic row toggle, which seems acceptable. The newlive-activity-focusCSS utilities with masks and animations are allowed in global styles per the guidelines. InAgentSpawnCtaRow, a raw button got replaced with aButtonprimitive wrapped in a div, which is an improvement, though I'm noticing the outer wrapper might have some styling implications I need to examine. Now I'm checking whether certain imports likeCheckIcon,ChevronDownIcon, andTooltipare still being used elsewhere in the file since they were removed from these components, or if they're now unused and could trigger linting issues. I also need to verify that the CSS custom properties inlive-activity-focusare properly defined. Now I'm checking where CheckIcon and ChevronDownIcon are being used throughout the file. Looking at the timeline components more closely, I'm noticing thatworkingStepLabelappears to be passed into WorkingTimelineRow but isn't actually being used anywhere in the component — it's sitting in the context and props without being consumed. For the new row toggle buttons inLiveWorkEntryTimelineRowandWorkGroupToggleTimelineRow, they're using raw button elements with hover states and focus rings, which fits the semantic row exception. InLiveActivityRow, the duplicated highlighted content is wrapped in anaria-hiddendiv, which is the right approach to prevent screen readers from picking it up twice. There's a potential CSS cascade issue though: the customlive-activity-focusutility is trying to overrideinset-0's positioning with explicit left/right/width declarations, but depending on how Tailwind v4 orders custom utilities relative to built-ins, theinset-0might win if it's emitted later in the cascade. The author did includeright: autointentionally to counteract this, which suggests they were aware of the risk, so it's hard to flag without seeing the actual emitted CSS. I should also note that the mask-image approach here is component-specific for a shimmer effect, which might violate the pattern of using a shared virtualized-scroll-fade contract for overflow fades. Now looking at the ChangedFilesTree changes — the "Show all N files" button was removed andsummarizeChangedFileScopesis no longer imported, so I need to verify whether that function is now dead code or still being used elsewhere. There's also a question about whetheruseMemobecame an unused import in this file. Checking the ChangedFilesCard header button, theflex-1class was removed from the button styling while the parent container still hasjustify-between, which is fine. The preview row changed from a wrapping layout to a single-row layout without wrapping, and the "Show all N files" button text is gone entirely — so now the collapsed state only shows a chevron for affordance. I should verify that the chevron has properaria-expandedattributes for accessibility. I'm looking at how the preview row handles overflow with flexbox and fixed widths — items shrink and get clipped when they exceed the container, but there's no visual indicator like the previous "+1 more" text. The test confirms this is intentional, asserting that the "+1 more" label shouldn't appear, though it does mean content gets silently cut off mid-item. ThechangedFileNamefield includes the directory prefix based on what the test expects. I should check ifsummarizeChangedFileScopesis still being used anywhere in the codebase, then dive into MessagesTimeline to look at the region around lines 1100-1600. I'm noticing thatsummarizeChangedFileScopeshas become dead code—it's only used in its own test now since this PR removed its last consumer. It matches the file patterns we're checking, so it's a legitimate finding, though the rule about not demanding unrelated cleanup doesn't quite apply here since removing the consumer was part of this PR itself. Rather than flag this as a UI consistency issue (which it isn't), I'll move on to examining the WorkingTimelineRow and LiveActivity components in the next section. I'm looking at an accessibility issue wherearia-labelis being applied to a plain<span>element without a proper role, which means the label won't be exposed to assistive technology. This is replacing a previous pattern that also had the same problem with a span inside a TooltipTrigger. The real regression here is that the tooltip showing "Failed" was removed entirely, and now the failure state is only conveyed by an icon with no accessible text alternative. To fix this, I should either addrole="img"to the SVG and put the aria-label there, or use screen-reader-only text instead. I'm seeing the same issue in PlainWorkEntryRow around line 2524. LiveActivityRow does have duplicate label text visually, but it's properly hidden witharia-hiddenso there's no double announcement. Now I need to check whether PlainWorkEntryRow is renderingaria-expandedeven whencanExpandis false — that would be another semantic issue. Let me look at that section of the code. I'm noticing the chevron indicator and hover background styling were removed from expandable rows, leaving them with only a cursor pointer as a visual cue. This feels like a regression in interaction affordance—the guidelines emphasize preserving accessibility semantics like focus states and pointer behavior, though the hover feedback removal seems to be a deliberate design choice for a cleaner aesthetic. The more substantive accessibility issue is the aria-label placed on generic<span>elements that screen readers ignore. In WorkGroupToggleTimelineRow, the failure state relies solely on a destructive-colored X icon with no text alternative, and the previous Tooltip is gone. I should flag that the aria-label needs to move to either arole="img"span, the SVG itself, or a screen-reader-only span. PlainWorkEntryRow has a similar pattern where aria-label on the row div overrides the inner text, which is acceptable but worth noting. There's also the removal of success/neutral indicator logic that was previously tied to work entry context. I'm noticing thatworkingStepLabelis now dead code throughout the context — it's still passed as a prop and plumbed through but nothing renders it anymore since they switched to ThinkingActivityRow. That's a deliberate UI change, so leaving the unused prop is minor and probably not worth flagging as a violation. Now I need to examine the CSS additions more carefully to see if there are any other issues. The keyframes look good in the @theme block. Now I'm checking where the new @Utility block sits in index.css. I'm noticing the CSS utilities lack explanatory comments like the existing ones have, which is a minor issue. There's also a potential concern with howlive-activity-focusdeclares the--live-activity-focus-widthvariable on itself whilelive-activity-focus-alignedtries to read it as an inherited value from its ancestor, though this should work since the variable is scoped to the mask element. The bigger question is the cascade order in Tailwind v4 — specifically whetherinset-0(which sets left and right) might override the width declaration in absolutely positioned elements, since both constraints together could create layout conflicts. The misalignment would be quite visible — the highlighted text would shift 4.5rem to the right of the actual label, which suggests the developer tested this and confirmed the utility order works as intended. Since I can't inspect the production CSS output, I'll trust their implementation rather than flag a potential issue. That said, there's a fragile dependency here: the custom utility needs to win againstinset-0purely through source order in the same layer. A cleaner approach would be to removeinset-0from the JSX and instead definetopandbottompositioning directly in the utility, or swap to usingtop-0 bottom-0individually — that way the positioning logic lives entirely within the utility without relying on cascade order to override a conflicting class. I'm also noticing a second issue with aria-labels on generic spans used for the failure indicator. These appear in two places and seem to be a new addition in the changed lines, but I need to check whether they're actually replacing a previous accessible pattern or introducing a new accessibility concern. The problem is that aria-label on a span with no explicit role gets ignored by browsers and assistive technology since the span defaults to a generic role. The fix is straightforward: either addrole="img"to the span or move the aria-label to an SVG element withrole="img". This is a real but low-severity accessibility issue with a minimal fix available. Looking at the test, it does check foraria-label="Tool call failed", so adding the role would preserve that test coverage. I'm wondering if there are other substantive issues to flag. There's a dead code path inPlainWorkEntryRow'siconWrapperClasslogic. The conditionworkEntry.tone === "tool" || showFailedIndicatoris unreachable becauseshowFailedIndicatoris already handled in an earlier branch. Before this change, that condition could execute when the row wasn't destructive, but now the first branch capturesshowFailedIndicatorcases, leaving the later check unreachable. TheAgentSpawnCtaRowcomponent properly uses the Button primitive with appropriate sizing and aria-label that matches the visible text, and the icon sizing is consistent with other parts of the codebase. The status text has been updated from a checkmark to "Completed" and uses responsive classes to hide/show elements at different breakpoints, though there's a potential layout concern with how the status and workflow name stack inside the chat timeline container. I should check whether other chat components use container queries and if the MessagesTimeline itself renders in a resizable panel, then search the file to see ifsm:breakpoints are already established elsewhere. I'm noticing the file uses container queries for responsive behavior in the ChangedFilesCard, specifically hiding labels on narrow viewports with@container/changed-filesand@[24rem]/changed-files:inline. The new AgentSpawnCtaRow should follow the same pattern rather than relying on viewport-level breakpoints likesm:inline, since it mirrors that card's visual design. Let me check if the chat column itself is resizable or if there's an inline preview mode that would make the MessagesTimeline narrower, which would determine whether container queries are actually necessary here. The chat column can get as narrow as 360px even in a wide viewport, so standard breakpoint-based hiding won't work for the AgentSpawnCtaRow labels. I'm noticing that ChangedFilesCard already solves this with container queries, so I should apply the same pattern here—declaring a container context on the card and using a container query breakpoint to toggle the layout inline when space is tight. The truncation and min-width constraints are already in place (labels havemin-w-0 truncate, the button isshrink-0), and since flex children get blockified, thedisplay:inlineon the workflow name actually becomesdisplay:blockin the flex context, so truncation will work correctly. In the worst case with a 360px chat column, both the workflow name and status will compress, but the layout stays intact. However, this creates an inconsistency — the sibling card pattern in the same PR uses container queries instead of breakpoints (the ChangedFilesCard test explicitly removed thesm:inlineassertion and replaced it with@[24rem]/changed-files:inline). This PR is usingmd:inlinefor the same "hide label when narrow" pattern, which violates the established consistency in the codebase. For theinset-0vs custom utility conflict, I need to verify the CSS emission order — if the custom utility comes after core utilities, it'll override correctly, but if not, there's a potential misalignment. The safest recommendation is to useinset-y-0instead to avoid the specificity conflict entirely, since both classes sit at the same layer level. I'm identifying an order-dependent conflict where a custom inset utility overrides Tailwind'sinset-0, so I'll suggest usinginset-y-0instead to avoid the collision since the custom utility controls the horizontal properties. I'm also verifying that Tailwind v4'sinset-0generates the logicalinsetshorthand property. Now I'm checking how Tailwind v4 sorts custom utilities—they're ordered by property count, with utilities that set more properties coming first so that more specific single-property utilities can override them. Solive-activity-focuswith its multiple properties (mask, animation, and others) would come before the inset utility in the cascade. But wait, that meansinset-0would actually override the left and right offsets fromlive-activity-focus, which would break the mask positioning—the left offset would collapse to 0 instead of staying at -4.5rem, and the width constraint would become over-constrained. The highlighted text sweep would then be misaligned by 72px from the base text. I'm not entirely certain this property-count sorting is how Tailwind v4 handles custom utilities, but either way this feels fragile and order-dependent. Let me phrase the fix as a same-layer specificity conflict:inset-0might override the utility's left and right depending on emission order, so switching toinset-y-0keeps left, right, and width fully owned by the utility and removes that risk. Now I should verify thatWorkEntryIconSvgactually supports all the icon names thattoolGroupSummaryIconNameis trying to use—eye, square-pen, terminal, globe, wrench, hammer. Looking at the session-logic changes, the main update is thattool.startedentries are now being included in the tracking, which doesn't affect the UI consistency. For the new live work row, the button layout looks solid with theLiveActivityRowcomponent set to fit its content width while the button itself spans full width with centered alignment. The accessibility implementation covers the expansion toggle with properaria-expandedattributes and a descriptive label. I'm noticing a few details: the disclosure content when expanded isn't explicitly associated with the trigger, which is minor. TheLiveActivityRowduplicates the label text in the DOM with the highlighted copy marked asaria-hidden, so screen readers handle it correctly and text assertions in tests will work fine. The reduced-motion handling properly disables animations and sets opacity to zero. The mask element haswill-change: transformapplied while the animation runs on the masked element itself, which is fine. The-webkit-mask-imageuses the default position, so that's not an issue. For the color scheme, the highlighted copy usestext-foregroundfor both label and icon while the non-highlighted version usestext-secondary-labelandtext-icon-muted, which maintains good contrast. I'm identifying two findings: theabsolute inset-0positioning at line 1407 conflicts with thelive-activity-focusleft/right/width constraints due to specificity and order, so I should change it toinset-y-0instead. The second issue is starting to emerge around lines 2410-2431... Actually, looking at the next part, I see there's a viewport breakpoint problem where the card uses media queries (sm:/md:) for content-width-dependent hiding, but the chat column can be around 360px on a wide viewport with a resizable right panel, so labels won't hide properly. I need to switch to container queries likeChangedFilesCarddoes, adding@container/agent-spawnon the wrapper and using@[24rem]/agent-spawn:...for the responsive behavior. There's also an accessibility issue at lines 1510 and 2527 wherearia-labelis on a generic<span>with no role, making it ignored by assistive technology — the failure state is now only conveyed by color after the Tooltip was removed. I should addrole="img"to that span or use sr-only text instead. The third finding is solid, though I'm still verifying whether the span's aria-label inside a button actually contributes to the button's accessible name during name computation. does get announced via aria-describedby. So removing the tooltip means losing that announcement, which is a real accessibility regression for the failure state in PlainWorkEntryRow. I'll flag all three issues but keep the third one focused: the destructive X icon's failure label isn't exposed to screen readers when the row has an aria-label, making the failure state color-only. I could suggest either including the failure state in the row's accessible name or adding role="img" to the icon, though I want to be careful not to be too speculative about the fix. Now I need to verify the line numbers in the diff for the inline comments — they should all be on the RIGHT side of the PR diff. Line 1407 is in the added LiveActivityRow section, and lines 2410, 2414, and 2431 are within the added AgentSpawnCtaRow hunk, while 2527 is in the added PlainWorkEntryRow section. Let me double-check these against the actual diff to make sure they're accurate. I'm adding the first inline comment on line 1407 about howinset-0andlive-activity-focusboth compile to utilities in the same layer, causing a specificity conflict where the sweep overlay's left offset gets overridden depending on emission order, which then misaligns the highlighted copy. For the second comment around line 2414, I need to reference the multi-line changes across the hunk (the wrapper needs a container class and three variants need updating), so I'll describe the fix without a diff since it spans multiple locations. The third comment flags an accessibility failure on line 2527.
1. modify_pr — 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.
Agent Credits: 294 credits