Skip to content

fix(web): parse wrapped command labels safely

MacroscopeApp / Macroscope - UI Consistency failed Aug 15, 2026 in 17m 40s

UI Consistency: 3 issues found

Three findings were posted as inline review comments:

  • apps/web/src/components/chat/MessagesTimeline.tsx — the rebuilt agent-spawn CTA hides its label below sm, leaving an icon-only Button with no accessible name (the previous full-row button always exposed its text).
  • apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx — the condensed chrome hides the author login with [&>span:last-child]:sr-only, a structural selector into PullRequestActorLabel's internals; the exported PullRequestActorAvatar already covers the avatar-only case.
  • apps/web/src/components/ThreadTerminalDrawer.tsx — the new terminal tab reveals its close affordance only on hover/focus-visible, so on coarse pointers the leading glyph is an unannounced destructive control.

Previously reported issues (snooze tooltip, PanelRightIcon sizing, live-work row accessible name, changed-files container query, terminal sidebar minimum width, PROVIDER_COLOR text tint, stale provider-order comment) are resolved at this head and were not re-reported.

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.

Methodology: reviewed the full merge-base→head diff, then verified the affected primitives and consumers at head — components/ui/button.tsx (icon-micro, icon-xs, ghost-muted, --control-icon-color), components/ui/toggle.tsx and the new components/ui/segmented-tabs.tsx, components/ui/badge.tsx (render-as-button supports focus ring, disabled:pointer-events-none), WorkspacePageContainer.tsx against the --workspace-topbar-height / --workspace-native-controls-inset tokens in index.css, .drag-region child no-drag rules, RightPanelResizeHandle reuse for the terminal sidebar, contextMenuFallback.ts icon coverage for every keyword emitted by threadActionMenu.logic.ts (13/13 present), and the new live-activity-focus mask/keyframes (correctly kept in global CSS).

Checked and found clean: header geometry parity for Settings, project settings, Usage, Pull Requests, chat and the onboarding state after the WorkspacePageHeader migration; PullRequestListFilters migration to Button size="icon" variant="outline"; duplicate <h1> risk in the PR detail chrome (both title layers carry aria-hidden/inert, so only one is exposed); SidebarUtilityMenu extraction and the settings footer reuse; SegmentedTab pressed-state semantics via Base UI Toggle; SettingsPageContainer width mapping (readable/wide/expanded).

Not re-reported: the terminal sidebar toolbar can still exceed the 144px minimum on sub-sm viewports, but the minimum was raised from 96px to 144px in direct response to the earlier note and now fits at sm and up.


🔍 Agent activity — 1 tool call

💭 I'm checking the current head diff against previous run commits to identify what's changed and what issues remain unresolved. Looking at the specific changes, I notice UsagePage no longer applies PROVIDER_COLOR to text styling, which addresses a prior comment. Continuing through the diff, I see several items have been addressed: usageProviders.ts now has clearer documentation about provider ordering, the SnoozePopoverButton includes a proper title attribute, PanelRightIcon uses the correct size class, and MessagesTimeline removed the problematic aria-label. The ChangedFilesTree container query has been restored with the expected media query, and ThreadTerminalDrawer's minimum width has been increased to 144 pixels. I'm also noticing that PlainWorkEntryRow's hover styling has been modified, removing the accent background color and applying something different. Now I need to dig into the actual current files to spot any remaining issues. I'm checking whether WorkspacePageContainer has tests now since the prior run flagged deleted tests, looking at the new SegmentedTab primitive to verify it properly handles Toggle props like aria-pressed and size, confirming that the compact toolbar's size="icon-xs" is a valid Button size, and reviewing the contextMenuFallback styling. I already noted the missing test files from that earlier commit, so I'll skip repeating that. Now I'm checking the toggle.tsx file to understand how the segmented variant is supposed to work. The Base UI Toggle component works fine with a controlled pressed prop even without an onPressedChange handler, since the parent's onClick handlers manage the state updates. The toggle will still render the correct aria-pressed attribute. I'm noticing that tabs in PullRequestDetailPanel and UsagePage previously used aria-pressed on buttons, and now Toggle is handling that instead. The SegmentedTabList uses role="group" with an aria-label, which is valid. The Toggle component has appropriate touch target sizing with the pointer-coarse media query. Now I need to check what's actually changed between the last reviewed commit and the current head to identify any genuinely new issues introduced. I need to do a fresh review of the diff since it was force-pushed, focusing on UI consistency issues across the key files like ThreadTerminalDrawer, Sidebar, and PullRequestDetailPanel to catch any new problems without repeating previous findings. Looking at the Sidebar.tsx changes, the settle button has been stripped of its Tooltip wrapper and now appears as a plain button element with custom classes instead of the previous tooltip-wrapped version. The key issue is that it lost the <TooltipPopup> label entirely, leaving it without a hover-discoverable name — while the snooze and unpin buttons both got title attributes for accessibility. So the settle button is now inconsistent with its siblings in the same row. Let me check if there's a visible label on the button itself or if it's truly missing hover text. The settle button shows "Settle" as visible text, so it doesn't need a tooltip for clarity. But I'm noticing it's missing focus-visible styling — there's no outline or focus ring defined in the current classes, whereas previously it had those focus states when wrapped in a TooltipTrigger. The snooze button has been refactored to use Button size="icon-micro" variant="ghost-muted" inside a PopoverTrigger, replacing the previous inline-flex approach with h-full padding. The icon-micro size is now 5 units, which works fine with the parent flex container. Looking at ThreadTerminalDrawer's implementation, I'm examining the rename input and close button behavior — the tab shows a terminal icon normally, but hovering the tab swaps it to an X icon for closing, with the button's aria-label indicating "Close ". The tab-row itself isn't a button, just a container. There's a potential usability concern here: the close button looks like a decorative terminal icon at rest, but clicking it closes the terminal entirely. This could lead to accidental closes, though it's clearly an intentional VS Code-like design pattern rather than a UI inconsistency issue. Now I'm checking the terminal drawer sidebar region. I'm working through the sidebar header layout constraints—the compact toolbar with its icon buttons and divider is pushing right up against the minimum width limit, causing content to overflow slightly at 144px. So the 144px minimum does handle the sm breakpoint case, but below that the toolbar still overflows at 161px. The prior fix addressed the issue partially, so I shouldn't duplicate that flag unless something changed. Let me check whether the terminal drawer even renders on mobile before diving deeper. Actually, let me scan the other files first to see if there are stronger issues to catch. I'm noticing the PullRequestDetailPanel has a Badge rendered as a button with specific sizing and padding, and I need to check how that's being handled. Now I'm looking at whether the Badge properly supports disabled styling — there's a disabled button inside with a hover class that applies destructive styling, but disabled buttons don't trigger pointer events in most browsers, so the hover state could still visually apply and create a misleading interaction state. The Badge itself probably isn't styled for interactive states either. The real issue is that they're using Badge as a button in the primary action slot — it's missing the focus ring, proper disabled handling, cursor styling, and adequate hit target that a real Button component would provide. This violates the principle of using core UI primitives instead of reconstructing them locally, especially when it's sitting alongside actual Buttons like the Ready for review and Merge buttons. I should look at what the Badge component actually supports to see if it has any interactive or render capabilities that might mitigate this. Badge supports rendering as a button with proper cursor styling, focus-visible rings, and disabled state handling through pointer-events and opacity rules. The custom hover styling on error variant Badges is safe because the disabled state prevents hover from matching. So that's not an issue. Now I'm looking at the checkout split button, which uses a Button with outline variant and completely overrides padding and gap to zero, then manually builds internal spans with custom padding values and a divider. This is a common pattern for split buttons — padding override plus internal spacing. I should check if there's already an established split-button component or pattern in the codebase that I should reference instead of flagging this as a violation. I'm noticing the checkout split-button is manually reconstructing what already exists as a shared ButtonGroup and ButtonGroupSeparator primitive in the codebase—it's zeroing padding and hand-rolling a separator with basic styling when there's a reusable component available. This is a UI consistency issue worth consolidating. Actually, looking closer at the context, the whole control is a single MenuTrigger button that opens a menu rather than two separate actions, so the split-button appearance might be misleading about its actual functionality. The "Check out" button is styled as a single control with a decorative divider, but it's overriding the base button's padding and hand-rolling custom values like ps-[8.5px] along with a custom separator, when ButtonGroup and ButtonGroupSeparator components already exist for this pattern. Since it's really just one interactive element rather than two separate controls, using ButtonGroup doesn't quite fit semantically either, and the arbitrary pixel values feel more like aesthetic tweaking than a principled design decision. There's also a UX concern here: the split-look divider visually suggests two independent actions, but the whole thing triggers the same menu, which could be misleading to users about what they're clicking. I'm also checking whether the size="xs" with p-0 padding might affect the hit target size for coarse pointers, and I need to review the button size definitions to understand the full impact. Let me scan through the other significant file changes first to prioritize what's worth flagging, then I'll dig into the button component specifics and check how the PullRequestListFilters migration handles the icon color styling with the outline variant. I'm noticing that the Button base class handles SVG sizing with a fallback, and icons at call sites override with explicit sizing, which works fine. But there's an inconsistency in PanelLayoutControls where the diff adds text color and a CSS variable to Toggle-based controls, except Toggle doesn't actually use that --control-icon-color variable anywhere in its implementation, so I need to verify what's actually happening in PanelLayoutControls. The Toggle component isn't actually using the --control-icon-color variable since it's missing the selector that would apply it to SVG elements. The theme does set this variable globally on the titlebar controls, but it's getting cut off mid-thought about how that's being applied. I'm looking at how the CSS variable for control icon color changed from referencing the toolbar foreground to a more specific control foreground variable. The TSX is now using Tailwind utilities to set the icon color to currentColor, and there's a global rule with high specificity targeting the toggle element within panel layout controls. The key issue is that the global rule sits outside any Tailwind layer, which means it wins over the layered utility classes regardless of specificity. So under a custom theme, the global rule takes precedence and sets the icon color from the theme variable. Without a custom theme, the inline Tailwind classes apply instead. This creates different behavior depending on whether a theme is active, which might not align with the intent that icons should always use the themed foreground. But I need to check whether the Toggle component even references the --control-icon-color variable in the first place — if it doesn't, then setting it on the Toggle wouldn't matter at all unless some other global rule or descendant is reading it. I'm realizing that Button is the only component actually reading the --control-icon-color variable, while Toggle ignores it entirely. On the PanelLayoutControls Toggles, that custom property ends up being inert since nothing reads it, but the theme rule sets color instead, which the SVG picks up through stroke="currentColor" from lucide. So color is what actually controls the icon appearance on toggles, not the custom property. Adding text-foreground [--control-icon-color:currentColor] to Toggles would set the text color, but the custom property part is dead weight—it's an inert declaration that could mislead future maintainers into thinking it's being used. Even more interesting: the base toggleVariants already includes text-foreground, so adding it at the call site is redundant in the default theme. Under a themed HTML, the global unlayered rule overrides with --toolbar-control-foreground and wins over the layered utility classes, making the call-site classes entirely inert in both scenarios. Let me verify whether that global rule is actually unlayered by checking the index.css structure around line 1400. The real question is whether [--control-icon-color:currentColor] causes any actual harm. In the disabled state for the right panel toggle, they've added disabled:opacity-100 with the icon getting text-muted-foreground opacity-100, and the base has disabled:opacity-64 which gets overridden by the call-site declaration via tailwind-merge's deduplication. The base also has a selector for SVG icons without opacity classes that sets them to 80% opacity, but the unavailable case explicitly sets opacity-100 so it keeps full opacity with just the muted color—that's fine. This isn't worth flagging as a finding. Let me shift focus to more substantive issues and check how WorkspacePageHeader is being used in the UsagePage. The header's h-auto py-2 class overrides the shared header's fixed height, but tailwind-merge keeps the min-h-[var(--workspace-topbar-height)] constraint, so the header can grow beyond the minimum. That's reasonable. In Electron where the header is the drag region, having it wrap with interactive controls is acceptable. The WorkspacePageHeaderEdgeControl wrapping a small ghost button is fine. There's a potential issue where UsagePage has an <h1> inside WorkspaceBreadcrumbItem and PullRequestDetailPanel changed its title from <span> to <h1>, so two h1s could coexist on the same page if both panels are visible, but that's not a major concern. Let me check MessagesTimeline.tsx next. Looking at the shimmer utilities and global CSS rules — masks and animations belong in global CSS, which is the right approach. LiveActivityRow renders the label twice with the duplicate hidden in an aria-hidden container, which is correct. The WorkGroupToggleTimelineRow and LiveWorkEntryTimelineRow have the live row as a button wrapping LiveActivityRow with no hover affordance or chevron, and the test confirms it doesn't contain the hover styling class. Now examining ChangedFilesTree.tsx to verify the container-query implementation and the new preview row. I'm also looking at contextMenuFallback.ts where there's a separator with both Tailwind classes and inline styles duplicating the same styling — need to check if this file builds DOM outside of Tailwind's scanning. Looking at the contextMenuFallback to understand the conventions used elsewhere in the file. The separator follows the established pattern of combining class names with inline styles for defensive coverage, which is consistent with the codebase. There's a pre-existing issue where SVG icons depend on Tailwind classes without inline fallbacks, and I'm noticing the chevron has shifted from a text ">" character to using createIconElement("chevron-right"), which could return null in environments like jsdom where document.createElementNS isn't available. In real browsers this isn't a problem since createElementNS always exists, but in test environments submenu items would silently lose their chevron indicator. The "Copy" submenu in threadActionMenu.logic.ts looks fine with its parent-child structure across both Electron and fallback menus. For WorkspacePageHeader adoption, I'm checking for regressions: the "No active thread" text previously had a specific padding class that's now being applied through the header's reserveNativeControls prop when electron mode is enabled, which achieves roughly the same result. Moving into ProjectSettingsPanel to verify the height handling... The shared header now properly reproduces the old electron geometry, with some refinements to padding and safe-area handling that align across breakpoints. I'm considering an important aspect of the implementation that's starting to emerge. The UsagePage removed the legend, so provider identification now depends on the summary list and chart hover tooltips. More importantly, I'm spotting an accessibility issue in MessagesTimeline: the AgentSpawnCtaRow changed from a full-row button to a div with a nested Button, and on small screens that Button only shows an icon without any text or aria-label, making it unnamed for screen readers—previously the entire row was a button with always-visible text. I'm seeing that on mobile viewports below 640px, the Button only renders an eye icon without any text, aria-label, or title attribute, leaving the accessible name empty so screen readers just announce "button" with no context. This is a regression from the previous design where the entire row was a button with visible "Open Agents ▸" text. I need to add an aria-label to restore the accessible name for screen reader users. Now I'm checking the ThreadTerminalDrawer's new tab close button and rename functionality. The close button has a proper aria-label, and the tab row's select button has a title, which is good. The rename input is a raw HTML input element with custom styling classes rather than using the core Input primitive — this might violate the preference for using UI primitives, though inline rename inputs are often treated as exceptions since they function as editor surfaces. I should look at how the codebase handles inline rename inputs elsewhere, like in the Sidebar, to see if there's an established pattern I should follow. I should look at how other inline rename features work in the app—like the thread rename in the Sidebar or the tab rename in the RightPanel—to see if there's already a pattern I can follow. The new terminal rename input follows the same pattern as three existing inline renames in the codebase, so it's consistent with convention, though it does add a fourth custom class string. The linter check prefers core UI primitives and flags raw inputs that duplicate Input's behavior, but it also carves out exceptions for raw elements that implement distinct interactions—like a tab rename at h-6 inside a 24px tab row, which has different geometry than a standard Input. There's a DraftInput primitive available that might be worth considering here. Looking at the commit-on-blur and Enter semantics, this inline rename does re-implement logic that DraftInput already covers, though the Escape-cancel behavior here differs from what DraftInput provides. It's a plausible finding, but given that DraftInput doesn't fully match the h-6 geometry or Escape-cancel requirements, flagging it feels weakly justified. I'll move on to check for more concrete issues. Now examining the terminal sidebar tab hover-swap icons... The group-focus-visible/close selector works correctly since the group is the button itself and the children sit inside it. The size-4 button creates a small 16px touch target on coarse pointers, which is suboptimal but not something I'd flag. The hover:bg-muted styling looks fine. Let me verify the sidebar width constraint more carefully by checking if the drawer actually renders on mobile widths. In ChatView, the drawer shows on desktop, but if the browser is under 640px, the toolbar buttons (5 at 28px plus spacing) would overflow the 144px sidebar since they're set to not shrink, and the aside has no overflow-hidden to contain them. The author already addressed the sub-640px overflow concern by raising the minimum width to 144px, so I won't re-flag that. Now I need to check if DiffStatLabel actually supports the layout="inline" prop that's being used in the preview row of ChangedFilesTree. Looking at the changes to ChangedFilesTree.tsx, the "Show all N files" button that previously expanded the list is gone, though the header toggle still works to expand. The scope summary was also removed and tests have been updated accordingly. The preview files now function as buttons that open the diff view rather than expanding inline. Over in composerInlineChip.ts, I'm seeing a shift from the responsive padding class py-[0.08em] to fixed height and line-height values with h-[1.41em] and leading-none. The icon class now uses block, self-center, and ensures SVG children are also block-level. The skill chip label class no longer inherits from the chat inline chip label class, so it's lost the leading-tight styling — that's fine since chat rendering presumably still uses the original chat class elsewhere. These class strings are consumed by ProseMirror decorations in the editor, so the fixed em height makes sense there. The TerminalActionButton that provided hover tooltips is being replaced with native title attributes on buttons, which aligns with how the compact toolbar is now structured. The repo does have Tooltip primitives, but using title attributes is consistent with the direction taken on sidebar buttons previously. Now I need to check how RightPanelResizeHandle is being reused in the terminal sidebar context with edge: "left" and useResizableWidth — I should verify whether it's positioned correctly for a right-panel scenario (like with absolute left-0) and whether that positioning actually works when repurposed here. The resizable panel reuse looks correct with the left-edge resize behavior. I'm verifying that useResizableWidth supports the edge: "left" configuration, which it should. The chromeVariant prop has been consistently removed from both PullRequestDetailPanel and ChatView, so that's aligned. The PullRequestDetailGhost is now rendered as an early return before the panel chrome gets set up. I'm noticing the condensed chrome row uses a class selector hack on PullRequestActorLabel to hide the name text via a child selector — this feels fragile since it's reaching into the component's internal DOM structure rather than using a proper prop. Let me check if PullRequestActorLabel has an avatar-only mode built in. I'm using a child selector to hide the login text in the condensed row by targeting the last span, which shows only the avatar. But this approach is fragile since it depends on the internal DOM structure of PullRequestActorLabel — if that component's rendering ever changes, this styling breaks. The better fix would be to check if PullRequestActorAvatar is exported separately so I can use it directly, or add an avatarOnly prop to the component. The current behavior is functionally okay since the text stays in the accessibility tree and the parent has a title attribute for hover tooltips, but relying on clever selectors into a shared component's internals isn't ideal. I'm noticing that PullRequestActorAvatar is already being used directly in other places like the summary and timeline tabs when only the avatar is needed, so adding a CSS hack with sr-only feels inconsistent with that pattern and potentially fragile. But the condensed row layout needs both the avatar and the relative time together, and using sr-only on the login keeps it accessible for screen readers—though switching to PullRequestActorAvatar directly would strip that out entirely. A cleaner approach would be adding an explicit <span className="sr-only">{login}</span> alongside the avatar component, or introducing an avatarOnly prop to control what gets rendered. The login value is already available from detail.author?.login, so either solution is feasible. It's a legitimate finding but moderate in severity—the fragility of the selector is the stronger argument. Now I'm checking the PullRequestListFilters migration to see what behaviors might have shifted. The previous styling had specific sizing and border classes, while the new Button component with size="icon" and variant="outline" provides similar dimensions and borders but adds a bg-popover background where there was previously transparency—that's a visual change worth noting. The hover and filtered states map cleanly: the outline variant handles the hover styling, and the filtered state now uses a CSS variable approach instead of direct class application. The intent is preserved. However, I'm noticing the import statement for Button appears out of alphabetical order relative to InputGroup, which could trigger linting rules if the project uses sorted imports—that's a CI concern rather than a UI issue, but worth flagging as a minor oversight. Now I'm checking the semantic structure of the SegmentedTab components replacing the previous aria-pressed buttons. In PullRequestDetailPanel, the tabs sit inside a nav with aria-label, and SegmentedTabList adds role="group"—that's correct. But in UsagePage, the WorkspacePageHeader uses drag-region utilities for Electron compatibility, and the interactive controls inside (SegmentedTabs, buttons) need to explicitly disable the drag behavior so they remain clickable. So the PR route header already has the drag-region class with interactive controls inside it, which means this isn't a new problem I introduced. I should check if there's a global CSS rule that automatically makes buttons no-drag within drag regions, or if those controls are individually marked. The drag region styling looks solid for buttons and inputs. I'm thinking through the UsagePage header with its flexible height and padding — in Electron's Window Controls Overlay mode, the drag region uses the titlebar area height, so extending the header taller means content spills below that area, which is fine since it's still within the draggable region. But there's a potential issue with WCO where the header content could overlap with the window controls. The reserveNativeControls setting defaults to true for Electron, which applies the right padding to avoid the native controls — that's handled correctly. Now I'm checking the SettingsSidebarNav refactor where the Back menu got replaced with SidebarUtilityMenu. Since the settings page matches the regex, the utility menu renders the Back button the same way it did before, plus it adds the update pill. The concern is that the old implementation called handleBackClick to close the mobile sidebar, but the new shared utility menu uses closeMobileSidebar instead — I need to verify those are equivalent. Also, useCallback might now be unused in SettingsSidebarNav and could trigger a lint warning. Looking at the SidebarUtilityMenu in SidebarChrome's settings footer—it returns a flex-row menu with a Back item when currentFooterPage is set, but I need to verify this doesn't break the layout since the previous settings footer used a different flex container wrapping that same back item. Let me check if there's any duplication or layout issues, like with the SidebarUpdatePill. I'm tracking how the component structure changed in both the sidebar footer and settings navigation—SidebarUtilityMenu now wraps the menu instead of being a sibling, and in the settings footer it's positioned alongside the avatar within a flex container. The width behavior should be fine since SidebarMenu has w-full and min-w-0 in its base styles, so even with flex-row it won't overflow. I also notice the settings footer now includes SidebarUpdatePill through SidebarUtilityMenu, which wasn't there before, but that's not a duplicate since the settings sidebar replaces the chrome footer entirely. Moving on to check MessagesTimeline's PlainWorkEntryRow—the role="button" and aria-expanded are in place, and since the team already addressed the hover affordance concern, I'll skip that. Now I need to verify whether workingStepLabel from context is still being used anywhere or if it's become a dead prop. The workingStepLabel is wired into the context but nothing actually uses it—it's leftover from removing the plan-step display in the UI. That's intentional behavior removal rather than a consistency issue, so I'll skip flagging it. Now I need to look at the RightPanel icon-color concerns... The MessagesTimeline and LiveActivityRow use -webkit-mask-image for animations in the global CSS, which is appropriate. The mask animation creates a visual duplicate of the label text, but it's properly hidden with aria-hidden. Looking at WorkspacePageHeader in ChatView, the safe-area-inset-based padding now applies consistently, and in Electron on macOS where safe-area insets are zero, that's fine. The reserveNativeControls prop is now handling the title bar control inset logic that previously used conditional CSS classes, though I need to verify how this behaves in non-Electron contexts where the old code never applied that padding. Now checking whether test files for WorkspacePageContainer, segmented-tabs, and PanelLayoutControls were actually deleted in this PR, since the diff doesn't show those deletions due to the force-push. I need to check whether settingsLayout.test.tsx still exists at the current head and verify it works conceptually with the new WorkspacePageContainer structure, since the test might be asserting on styling classes like max-w-4xl and padding that have been moved around. I'm noticing a behavioral shift in SettingsPageContainer where the scroll container's padding has been removed entirely, with WorkspacePageContainer now handling the spacing instead—this means settings pages have less top padding and the horizontal padding has been reduced on smaller screens. The scrollbar gutter positioning remains consistent since scrollbar-gutter-both stays on the scroller, and moving padding to the inner content doesn't visually change where the scrollbar sits. I'm also seeing that DiagnosticsSettings and KeybindingsSettings now use consistent width mappings with expanded and wide respectively, and there's a gap conflict resolution happening where WorkspacePageContainer defaults to gap-6 but the settings container overrides it with gap-12 through the className merge. Now I'm deciding which findings are worth flagging: the AgentSpawnCtaRow button losing its accessible name below small breakpoints is a concrete accessibility regression that should be posted. The [&>span:last-child]:sr-only selector reaching into PullRequestActorLabel internals is moderate and maybe worth mentioning. I'm also noticing that PullRequestDetailPanel removed the transition animation on the fold grid tracks, which eliminates the animated collapse effect, though the scroll compensation code that was tied to it still remains. I'm noticing there are two <h1> elements being rendered simultaneously—one for the condensed top row and another for the expanded title—which creates a duplicate heading in the accessibility tree. While inert does remove content from the a11y tree in supporting browsers, both headings are still present in the DOM at the same time. The issue is that the condensed <h1> at line 1127 isn't inside an inert container; it only has pointer-events-none opacity-0 applied when expanded, so it remains exposed to assistive technology even though it's visually hidden. This means screen reader users will hear the PR title announced twice, which is a real accessibility problem introduced by changing that <span> to an <h1>. The fix is straightforward: either revert the <h1> back to a <span> or add aria-hidden to prevent it from being announced. I should look at the specific lines around 1100-1135 to write a precise inline suggestion. The condensed container uses aria-hidden and inert to hide itself from accessibility trees when expanded, preventing duplicate heading exposure. But when condensed, the expanded fold only has inert without aria-hidden, which should still work since inert removes content from the accessibility tree in modern browsers. So there's no duplicate h1 in the accessibility tree despite having two in the DOM. Going back to my findings — I should verify finding A by checking if small screens actually use this component. The app runs on mobile viewports with sm: breakpoints, so the button definitely lacks a name on narrow widths. The Button component doesn't provide a fallback title either, so that's a real issue. For finding B with the arbitrary-variant selector coupling to the component's internal DOM structure — this feels fragile. The repo already exports PullRequestActorAvatar for avatar-only use cases, which suggests the author intended a cleaner approach. I'll flag this as a suggestion about preferring explicit logic over clever selectors, though I need to note that the login name is still necessary for assistive technology. Replacing with the dedicated avatar component would lose the hover title, but that's minor. This is worth including as a finding since it directly relates to maintainability and ownership concerns. Let me scan the rest of the diff for any other solid issues — checking the timeline rows and usage page components for consistency. Files are now clipped silently without a "+1 more" indicator since the test explicitly asserts it shouldn't appear — that's intentional per the test expectations. I should check whether summarizeChangedFileScopes is now dead code after being removed from the imports in ChangedFilesTree, or if it's still used elsewhere. Looking at the ThreadTerminalDrawer compact toolbar in the sidebar header versus the floating toolbar, they have different container heights (h-9 with justify-center versus h-8 with p-0.5) but render the same buttons — a minor inconsistency. The same compactTerminalToolbar element isn't reused in both places since only one renders at a time depending on whether the terminal sidebar exists. Now I need to verify the exact line numbers for the inline comment on finding A. The added lines in the diff are 2415-2418 for the AgentSpawnCtaRow, and I should comment on line 2417 with a start_line of 2415 to cover the relevant span. For finding B, I've identified line 1427 with the className="shrink-0 [&>span:last-child]:sr-only" attribute as an added line in the diff. I'm crafting the first suggestion now — adding an aria-label to the Button element that spans lines 2415-2418 with proper indentation. For the second suggestion around line 1427, I need to check whether PullRequestActorAvatar is already imported in this file, since the suggestion would need to render the avatar alongside a screen-reader-only span containing the author's login. I'm weighing whether to flag the missing import for PullRequestActorAvatar — since it requires a cross-line change, I'll use a unified diff to show both the JSX update and the import addition rather than an inline suggestion. The question is whether this issue is significant enough to mention at all, given how minor it seems. Let me also reconsider the other changes: the shared WorkspacePageHeader adds gap-3 spacing between children, which could subtly alter the chat header layout since the old version relied on individual margin spacing. That's worth noting as a potential visual shift, though the author likely reviewed it. The CSS additions for animations and the state/logic changes are all appropriate for their respective scopes. Now I'm checking the icon definitions in threadActionMenu.logic.ts — several new icons were added to the fallback ICON_PATHS, including message-square-plus, pin, pin-off, circle-check, clock, pencil, refresh-cw, mail-open, copy, folder, git-branch, hash, and trash. I need to verify that "pencil" is actually defined in the ICON_PATHS object, since the diff shows additions but I want to make sure nothing's missing. Checking that all the icons are accounted for, and verifying how Electron handles menu icons since it doesn't natively render lucide icons. Now I'm looking at the web context menu to see if there's a React component that maps those icon names like "message-square-plus", "pin", "pin-off", and the others to their corresponding lucide components. I've identified two key findings: an unnamed CTA button on the agent component at small breakpoints, and issues with the actor label's internal selectors. Now I'm examining the ThreadTerminalDrawer tab close button—on touch devices where hover doesn't work, the X icon never appears, yet the button still exists with an aria-label and closes the terminal when tapped. The problem is worse because the visible icon stays as a terminal symbol, so users tap what looks like a terminal control and it closes the drawer instead. The repo does account for coarse pointers elsewhere, so I could flag this as a regression: on touch devices, the leading glyph becomes an unlabeled destructive action. I'm weighing whether this third finding is solid enough to include—it's plausible that the drawer gets used on tablets and mobile, and the risk is real, though somewhat speculative. Actually, there's a stronger angle: group-hover/tab:hidden can trigger on tap in some browsers (iOS applies hover on tap), so a first tap might show the X and fire the click simultaneously, closing the terminal without ever displaying a close affordance. That's a genuine hazard, not just aesthetic. I'll include it as a third comment, framed as a design-level suggestion without a diff. But I should double-check whether the terminal drawer is even rendered on mobile—if ChatView hides it for isMobile, this concern evaporates. No mobile gating is visible, which works for now, though "coarse pointer" does catch touchscreen laptops too. I'm planning to post three comments and need to verify the exact line numbers for the terminal tab close button around lines 1555-1564, so let me check the range 1545-1570 to be precise. I'm drafting a code review with three inline comments and a summary. The first comment flags an accessibility issue in MessagesTimeline.tsx where a button loses its visible label on smaller screens, leaving only an icon without an aria-label for screen readers—I'll suggest adding one. The second comment is starting to address something in PullRequestDetailPanel but got cut off. I need to import PullRequestActorAvatar from the pull request presentation module since it's already being used elsewhere in the codebase, then finalize the review with the suggested changes and post it.

1. modify_pr — Three findings, all in apps/web. The shared-primitive migrations (WorkspacePageHeader, SegmentedTab, Button/Badge adoption in the sidebar, terminal toolbar and PR chrome) otherwise look consistent with the component system, and the earlier notes on the snooze tooltip, PanelRightIcon sizing, the live-work row's accessible name, the changed-files container query and the terminal sidebar minimum width are addressed at this head.


Agent Credits: 1025 credits