Skip to content

Merge branch 'main' into main

MacroscopeApp / Macroscope - UI Consistency failed Aug 31, 2026 in 7m 15s

UI Consistency: 1 issue found

Findings

  • apps/web/src/components/files/FileBreadcrumbs.tsx (lines 240-244): the new directory-crumb menu trigger is a raw <button> that reimplements the shared ghost-control treatment (cursor, hover:bg-accent, focus ring, data-popup-open state) while dropping the pointer-coarse:after:* hit-target expansion carried by every interactive primitive in apps/web/src/components/ui (Button, Toggle, Badge, number-field, autocomplete). Breadcrumb segments were previously plain text, so this is a new tap affordance sized to a 16px text line with 2px of horizontal padding. Suggested fix posted inline: keep the inline breadcrumb geometry and add the shared coarse-pointer expansion bounded to the 40px subheader.

Checked and clean

  • Environment scoping: FileBreadcrumbs receives an explicit environmentId from FilePreviewPanel and threads it into useProjectEntriesQuery and the files:{environmentId}:{cwd} refresh key — no active-environment fallback introduced.
  • MenuPopup defaulting contract preserved: w-[min(19rem,var(--available-width))] is a genuine width utility, so suppressing min-w-32 is intentional and the consumer class still merges last.
  • Breadcrumb scroll behavior preserved: the data-current-file-crumb marker and scrollIntoView effect in FilePreviewPanel still work with the extracted component, and the portaled popup (z-[130]) is not clipped by the surrounding ScrollArea.
  • Icon tone preserved: PierreEntryIcon keeps its inline style.color inside MenuItem's [&>svg:not([class*='text-'])]:text-muted-foreground rule; the fallback lucide icons pick up the standard menu icon treatment.

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

  • Read the new FileBreadcrumbs.tsx in full and diffed the removed inline breadcrumb block in FilePreviewPanel.tsx to confirm no behavior (tooltips, current-crumb marker, scroll-into-view, unused imports) was lost.
  • Compared the new trigger against existing inline menu triggers (DraftHeroHeadline, DiffPanel, PullRequestCodeTab) and against the control primitives (ui/button.tsx, ui/toggle.tsx, ui/badge.tsx, ui/number-field.tsx, ui/autocomplete.tsx, ui/select.tsx). Inline-text triggers styled directly are an accepted pattern here, which is why only the missing coarse-pointer target is reported; the trigger's focus-visible:ring-2 focus-visible:ring-ring (no offset) matches the existing inline-trigger precedent and was not flagged.
  • Verified MenuPopup's hasExplicitWidthClass logic in ui/menu.tsx against the popup class used here, and confirmed --available-width is inherited from the Base UI positioner.
  • Verified useProjectEntriesQuery in projectFilesQueryState.ts binds the atom to the passed environmentId/cwd (same atom FileBrowserPanel uses, so the cache is shared), and that the drill-down/back-navigation guards (canGoBack, rootPath) cannot escape above the clicked crumb.
  • Pure helpers fileBreadcrumbChildren / fileBreadcrumbParent are covered by the added tests in filePath.test.ts; no additional test gate requested for the visual composition.

🔍 Agent activity — 21 tool calls

💭 I want to dig into the repo to see how these components fit together. I'm checking whether FileBreadcrumbs.tsx's raw button inside MenuTrigger matches other Menu trigger patterns, examining the MenuPopup className sizing convention, and tracing how environmentId gets routed through FileBreadcrumbs. The useProjectEntriesQuery call also passes cwd explicitly, which seems worth verifying against the actual source files.

1. browse_code — apps/web/src/components/files/FileBreadcrumbs.tsx

2. browse_code — apps/web/src/components/ui/menu.tsx

💭 I want to check FilePreviewPanel.tsx for how props like cwd, environmentId, onOpenFile, and workspaceMutationId are passed, and verify whether wrapping MenuTrigger with Tooltip matches existing patterns elsewhere. I should also look at useProjectEntriesQuery's signature to confirm it handles cwd listing correctly, and check the conditional rendering of BreadcrumbMenuContent. I'm noting a few marginal findings: the raw button in DirectoryBreadcrumb might duplicate a ghost/link button primitive but has different geometry needs, the focus-visible ring styling might not match the codebase's usual ring conventions, and I should verify the aria-current and aria-label attributes are applied correctly on the breadcrumb elements. Now I want to check that FileBreadcrumbs correctly passes environmentId and trace how onOpenFile flows from FilePreviewPanel, so I'll look at those relevant sections next.

3. browse_code — apps/web/src/components/files/projectFilesQueryState.ts

4. browse_code — apps/web/src/components/files/FilePreviewPanel.tsx

💭 I'm checking how FileBreadcrumbs handles environment routing since it takes environmentId explicitly. Now I want to compare against FileBrowserPanel to see how it lists entries, handles truncation and directories using useProjectEntriesQuery and PierreEntryIcon, and I'm noting that FileBreadcrumbs renders inside a ScrollArea with hidden scrollbars and fade effects. I'm reviewing whether conditionally rendering the menu popup skips Base UI's close animation, and whether the raw button styling intentionally diverges from the shared Button component's padding and height for breadcrumb-specific geometry — neither seems like a strong issue. I want to check how similar breadcrumb-style menu triggers are handled elsewhere in the codebase (chat header, project pickers) to see if there's a conventional pattern, and also compare the focus ring styling against button.tsx's convention. I'm also considering whether the small breadcrumb button's hit target is too small for touch, given MenuItem enforces minimum heights for coarse pointers. Beyond that, I'm checking whether the Tooltip/MenuTrigger nesting matches the usual pattern, and looking for a possible real bug in how breadcrumb child entries or truncated project queries are handled. Then I'm considering PierreEntryIcon inside MenuItem — the icon sizing rule only targets direct SVG children, so if this component renders an img or wrapped span instead, its size might not stay consistent with other menu icons. I want to check how it's rendered elsewhere to confirm. I should look at where PierreEntryIcon is used, and how theme (resolvedTheme) gets passed into it, by checking its implementation directly.

5. browse_code — Show the implementation of PierreEntryIcon in apps/web/src/components/chat/PierreEntryIcon.tsx and list all existing call sites with their surrounding markup (especially any inside MenuItem or list rows), including how size classes are applied.

6. git_tools — data-popup-open:bg-accent

💭 Let me check the actual PierreEntryIcon implementation directly.

7. browse_code — apps/web/src/components/chat/PierreEntryIcon.tsx

8. git_tools — PierreEntryIcon

💭 I'm checking that the icon's inline color style takes precedence over the muted-foreground text class from the menu item's CSS selector, and confirming the opacity selector still applies correctly since the icon's size class doesn't match the "size-" exclusion pattern. Now I'm scanning for other consistency issues: the focus ring convention on the trigger button versus other buttons in the codebase, whether the breadcrumb control meets minimum touch-target height compared to existing button patterns, and whether there's existing precedent elsewhere for combining a tooltip trigger with a menu trigger. I'm also checking smaller things like duplicate padding classes on the group label, whether the --available-width CSS variable properly inherits from the Positioner into the Popup, and confirming that pathLabel for the current file crumb always has a non-empty path so it won't regress to showing the project name unexpectedly. The wrapping <span aria-current="page"> around BreadcrumbLabel looks slightly redundant since BreadcrumbLabel already renders its own span, but that's harmless. I'm confirming the environment query call passes explicit arguments and that the effect closing the menu on path change looks correct, then I want to check if there's already a shared Breadcrumb primitive or similar path-menu component elsewhere in the codebase, like in FileBrowserPanel or preview headers.

9. browse_code — apps/web/src/components/ui/*.tsx

10. git_tools — MenuTrigger$

💭 There's precedent for using MenuTrigger with a bare className, like DraftHeroHeadline's inline text trigger, and DropdownMenuTrigger with classes in DiffPanel/PullRequestCodeTab—so a raw button trigger for inline text fits within existing patterns, though I should note MenuTrigger renders its own button by default. The focus ring styling matches conventions from DiffPanel and DraftHeroHeadline, so that's consistent. Checking for real regressions: the data-current-file-crumb attribute and scroll behavior are preserved, and the breadcrumb type narrowing works correctly since relativePath is confirmed non-null in that branch. I'm also noticing the tooltip behavior changed slightly—previously all crumbs showed either the path or project name, but now the current file crumb tooltip only shows the path when non-empty, which is a subtle difference worth flagging. I'm also considering whether the tooltip could remain visible over the menu popup when it opens, but since PreviewMoreMenu follows the same Tooltip-wrapping-MenuTrigger pattern, this should be consistent existing behavior. The menu popup itself is portaled with a high z-index, so it won't get clipped by the ScrollArea container—that's fine. The remaining concern is whether the focus ring on breadcrumb buttons could get clipped by the ScrollArea's overflow settings, since the ring extends slightly outside the button bounds and the container might have overflow-y hidden. Now I'm shifting to think about the icon color indirection issue—checking whether the MenuItem primitive routes SVG icon colors through a shared text-muted class unless the icon already has an explicit text color class, which matters for ensuring migrated icons keep their intended tone. This is a minor inconsistency shared with other menus like ComposerCommandMenu that also skip sizing PierreEntryIcon, so it's probably fine. I want to check the Button primitive too, looking at its ghost/xs variant and focus-visible conventions to make sure hover colors align.

11. browse_code — apps/web/src/components/ui/button.tsx

12. browse_code — apps/web/src/components/files/FileBrowserPanel.tsx

💭 I notice the Button component has built-in coarse-pointer hit-target expansion that the new raw button breadcrumb trigger lacks, which matters since the guidance emphasizes preserving accessibility semantics during migrations and flags raw buttons that recreate Button styling as a concrete finding. That said, I'm second-guessing whether this is actually a violation, since the breadcrumb is inline truncated text in a scroll strip rather than a standard button shape, and the codebase already has precedent — DraftHeroHeadline and DiffPanel/PullRequestCodeTab both apply inline text classes directly to trigger elements without wrapping in the Button primitive, suggesting this is an accepted pattern for text-style triggers rather than a bug to flag. I'm also checking whether MenuTrigger's render prop merging with TooltipTrigger and the Menu's open/close handling could hide any real animation or exit-transition regressions, but the popup styling shows no animation classes to begin with, so there's nothing to lose there. I don't see obvious issues with the tooltip trigger interfering with pointer events, the portal setup, or the pending/error states for entries queries — those all look handled correctly. I also confirm the breadcrumb menu shares the same query atom as the file browser panel, which is good for cache reuse, though I keep scanning for other potential problems. The touch target sizing seems worth flagging though: the subheader control uses tiny padding and no minimum height, which stands in contrast to how the codebase elsewhere clearly cares about coarse-pointer hit targets, like the menu item and button primitives that enforce larger minimum sizes for touch. This inconsistency could make the breadcrumb harder to tap on tablets. The missing focus ring offset seems minor since it matches other inline triggers like DiffPanel's, so I might skip flagging it or just note it briefly. I want to double check FileBreadcrumbs' environment routing and the onOpenFile prop chain, but both look fine — they're wired through as expected. One thing worth flagging is the CheckIcon's aria-label — since it's an SVG without role="img", that label likely won't be exposed to assistive tech, and lucide icons typically render with aria-hidden anyway, making the label ineffective. An SVG with just aria-label but no role="img" isn't reliably announced across browsers, so this decorative chevron usage is a minor issue at best, especially since the current-file state is already conveyed via aria-current on the MenuItem itself. Stepping back, the concrete finding worth flagging is the raw <button> reconstructing the Button primitive for the breadcrumb trigger — it loses the pointer-coarse hit target and ring offset, and is redundant since MenuTrigger already renders a button, so the className should go directly on MenuTrigger or use Button variant="ghost" instead. But swapping to Button variant="ghost-muted" size="micro" would change the crumb's geometry, so the minimal fix is probably just restoring the touch target and ring offset on the existing markup rather than replacing it outright. I also checked whether the non-interactive file crumb (rendered as a plain span while directory crumbs are buttons) is an inconsistency, but that seems intentional since the file itself has no children to browse. Now I'm considering whether the focus ring on the leftmost crumb could get clipped by the ScrollArea's horizontal overflow clipping at the strip's left edge, since there's no ring-offset to compensate. This is minor and speculative though, maybe better folded into a broader hit-target/focus comment rather than called out separately. I want to check the ScrollArea component to see how overflow is handled and whether it has a fade mask.

13. browse_code — apps/web/src/components/ui/scroll-area.tsx

14. git_tools — pointer-coarse

💭 The coarse-pointer hit target expansion is a deliberate repo-wide convention across every interactive primitive, but the new breadcrumb trigger is a raw hand-built button missing both that expanded hit target and the focus-visible ring offset used elsewhere. That's a legitimate inconsistency worth flagging. I'm weighing whether swapping in the Button primitive with a ghost-muted micro variant would alter the geometry slightly versus just adding the missing coarse-pointer and ring-offset classes to the existing raw button. I'll leave an inline comment suggesting either the primitive swap or restoring those shared treatment classes, since every other interactive control carries them. I also want to sanity-check whether there's a more critical functional bug I'm missing before finalizing, so I'm looking again at the directory availability and truncation logic. I checked whether wrapping a tooltip trigger in a span inside a menu item causes any accessibility gaps, since keyboard focus wouldn't land on a non-focusable span, but this matches existing patterns elsewhere in the codebase so it's not a new issue. The aria-current span around the breadcrumb label also looks harmless since it's non-interactive. I also considered whether the boolean data attribute for the current file crumb renders correctly as a string for the selector to match, which it does, and whether focus management breaks when the menu closes and breadcrumbs re-render with different crumbs after opening a file — focus could get dropped to body, but that seems like a minor issue rather than a real bug. Looking at the ArrowLeft handler on the menu popup, I confirm it's properly stopping propagation and preventing default, so it won't conflict with Base UI's built-in submenu/RTL handling; there's just an asymmetry where ArrowRight has no analogous "enter directory" behavior, though Enter covers that case. I'm leaning toward flagging the missing coarse-pointer hit target and ring offset on this hand-rolled interactive control as a legitimate consistency finding worth reporting. But checking other examples like DraftHeroHeadline, DiffPanel, and PullRequestCodeTab, I notice these inline text menu triggers also skip the Button wrapper and coarse-pointer target, using the same ring style without offset — this is actually the established convention for inline text triggers throughout the codebase, which weakens my case for calling this a violation. I'm reconsidering: the truncating breadcrumb segment sits in a tight 12px strip, and Button's minimum height and border would genuinely break that geometry, matching the exception for primitives that intentionally differ from the core component's behavior. So flagging this Button substitution risks being a false positive given three existing precedents doing exactly this — though I do note those prior cases apply classes directly to MenuTrigger rather than wrapping a separate button element, which is a slight structural difference worth weighing. Now I'm digging for an actual behavioral gap instead. I'm checking whether MenuPopup mounting only when open, combined with Menu being controlled, creates any positioning or timing issue with Base UI's Menu.Root — that seems fine on inspection. I'm also considering the tooltip trigger overlapping the menu trigger during hover, and whether tooltips in similar precedent components auto-close correctly when the menu opens. I'm weighing whether recomputing directoryAvailable over all entries every render is a real performance concern for large workspaces, though that's more of a perf nitpick than a UI-consistency issue, so I'm moving on to check what truncated actually represents in the ProjectListEntriesResult type. I'm also verifying that non-interactive file crumbs stay consistent with interactive parent directory crumbs, checking whether the small added padding on breadcrumb labels causes any visible alignment shift, and scanning for any project convention around default exports or nav/list semantics that might apply here — none of these turn out to be real findings. Now I'm considering whether the new component needs its own interaction tests, since it introduces keyboard handling and drill-down state that existing test files like menu.test.tsx, button.test.tsx, and sidebar.test.tsx don't cover — checking that file to see how they structure component-level tests. Actually, rethinking the applicability of the rule: this PR composes an existing primitive rather than changing its behavior, and the pure helper functions do have new tests, so pushing for more coverage feels like a weak ask. I'm now checking the breadcrumb popup's scrollable directory list for virtualization or overflow-fade concerns, but it looks fine as-is, so I'm turning to whether canGoBack or rootPath might have an actual logic bug. Tracing through canGoBack for various crumb positions, drilling down and up between root and nested paths, everything seems to correctly prevent escaping above the crumb boundary, and the "no longer available" fallback for truncated directories also checks out. Now I'm looking at whether wrapping the breadcrumb strip in a ScrollArea with hidden scrollbars could cause a regression. Then I'm considering whether remounting MenuGroup via a key reset causes composite item registration issues in Base UI, but that seems intentional. I'm now shifting focus to check whether removing the inline breadcrumb code left unused imports in FilePreviewPanel.tsx that would break lint or typecheck, and verifying whether Tooltip-related imports are still actually used elsewhere in that file.

15. git_tools — useMemo|cn(|TooltipPopup|ChevronRight

16. browse_code — apps/web/src/components/ui/menu.test.tsx

💭 No unused imports here either. I'm double-checking whether MenuPopup's width calc using --available-width actually works, tracing how Base UI sets that custom property on the Positioner and confirming it inherits down to the popup styling correctly. Scanning for a genuine issue elsewhere: BreadcrumbMenuContent wraps every menu item in its own Tooltip, which is fine performance-wise, and nesting a TooltipTrigger inside a MenuItem shouldn't break hover behavior either. The trigger's aria-label overriding the visible crumb text also seems fine since it just uses the project name. The most plausible real finding left is that the new interactive trigger lacks a pointer-coarse touch target, but since three existing triggers in the codebase already skip that treatment, this looks like it's just following existing convention rather than being a genuine regression. Actually, comparing hit sizes more carefully — DiffPanel and PullRequestCodeTab triggers are 24px tall, while this breadcrumb segment is only about 16px with no vertical padding, which is meaningfully smaller and the first time these labels are clickable at all. That does seem like a legitimate, specific issue: a new primary navigation control with a hit target well under the usual minimum, unlike other interactive elements in the app. I'm thinking the fix would be adding vertical padding or a min-height along with the shared coarse-pointer touch-target expansion, though that expansion technique requires the button to have relative positioning, so I need to check whether the base Button styles already include that. I'm reasoning through why the pseudo-element ends up centered: since abspos children in flexbox get their static position determined by align-self/justify-content, the after element naturally centers within the button rather than sitting at the top-left. For a block-level button this wouldn't hold true, so making the button consistently inline-flex items-center justify-center (matching the primitive) is the more reliable fix. I'm also checking whether truncate behaves the same way across block vs inline-flex elements, since text-ellipsis depends on how the text node forms an anonymous flex item. Given the complexity, I think the practical recommendation is to keep it simple — give the trigger a real vertical hit area, like padding or min-height combined with flex alignment, and pair that with the standard focus-ring offset styles used elsewhere. This does count as an accessibility concern rather than a cosmetic one, since coarse-pointer hit targets and focus-visible rings are explicitly part of what this check is meant to catch. Checking how consistent the ring-offset usage actually is across components — Toggle, Button, Badge, and the ScrollArea viewport all use ring-offset, but the select's ghost variant and the DiffPanel trigger don't. So it's mixed in practice, and I realize adding an offset inside a clipped ScrollArea could actually make clipping worse rather than better, so I shouldn't lead with recommending the ring-offset fix. I'll instead flag the hit target on the new breadcrumb trigger: it hand-rolls the shared control styling but drops the coarse-pointer hit-area expansion that every other interactive primitive in components/ui includes, leaving a roughly 16px target that's hard to hit on touch or trackpad. The simplest fix is giving it a min-height plus the shared pointer-coarse expansion treatment, or just routing it through the existing Button ghost variant instead. I'm also double-checking whether there's a more obvious concrete issue in the diff, particularly around filePath.ts and how FilePreviewPanel handles cwd/environmentId routing, but those all look explicit and fine. Checking useProjectEntriesQuery, I notice that if the explorer panel is closed, opening a breadcrumb menu still triggers a full workspace listing fetch — not ideal, but acceptable. Let me also check AGENTS.md for any guidance about menu triggers or hit targets, like references to pointer-coarse handling, that might strengthen or weaken my finding.

17. git_tools — pointer-coarse|hit target|tap target

18. git_tools — raw |native button|use the Button primitive|components/ui

💭 No specific docs guidance found, so I should check the AGENTS.md in apps/web for general UI conventions.

19. browse_code — **/AGENTS.md

20. git_tools — ui|button|tailwind|css

💭 There's no specific UI-primitive rule against this, but AGENTS.md mentions multi-surface support including mobile web browsers, and the pointer-coarse variants in the primitives confirm touch is a supported target for the web client. I feel ready to post a single, focused finding about the new breakpoint. I've also double-checked several other things - the MenuGroupLabel usage, the disabled loading state on MenuItems, and the conditional rendering of popup content - and none of these seem like genuine issues. Now I'm verifying whether the PierreEntryIcon inside MenuItem correctly picks up the opacity styling from the primitive's selector logic. I'm confirming that the icon's inline color is preserved despite the muted-foreground class, and the negative margin styling stays consistent with other menu icons. I'm settling on treating the small hit target as the one real finding, though I'm still weighing whether it counts as a genuine consistency/regression risk versus just a minor preference. Looking closer, the check explicitly calls out coarse-pointer hit targets and hand-rolled buttons replicating primitive treatments as concrete findings — and this new trigger recreates ghost-button styling manually, missing the primitive's hit-target and ring-offset guarantees. I'll report it as a failing finding, accepting the small risk of being overly strict, and now I'm identifying the specific lines in the new file where the button trigger is defined to anchor the comment. I'll suggest keeping the layout classes but adding relative to the button so the pseudo-element hit-target expansion works properly, along with restoring the missing pointer-coarse and focus-visible ring-offset treatments to match the shared component contract. But then I realize the naive after:size-full combined with a block element's static positioning would make the 44px touch target extend downward past the header, potentially overlapping the adjacent OpenInPicker toggle on touch devices—since Button centers this via flex but this element can't. I start weighing alternatives like vertically centering the pseudo-element with a translate, or using inset-based expansion instead, to avoid that overlap. I'll tighten the framing: the raw button reimplements the ghost control's visual treatment but drops the coarse-pointer hit-target expansion that every control primitive carries. Now I'm drafting the actual replacement button element with the aria-label, ghost styling, and truncation classes applied. I'll set the suggestion range from line 240 to 244, covering the whole button element through its closing tag. Now I'm drafting the review title and summary noting the one UI consistency issue found, then posting it.

21. modify_pr — Reviewed the new breadcrumb menu against the shared control system, MenuPopup's width-defaulting contract, and environment scoping. Environment routing is correct: FileBreadcrumbs takes an explicit environmentId prop from FilePreviewPanel and threads it into useProjectEntriesQuery / the workspace-mutation resource key, so nothing falls back to the active environment. The popup's w-[min(19rem,var(--available-width))] is a real width utility, so suppressing min-w-32 is intended, and the Pierre icon keeps its inline color inside MenuItem's [&>svg:not([class*='text-'])] rule. One finding on the new directory-crumb trigger.


Agent Credits: 191 credits

Token usage: 26 uncached input · 937,031 cache-read input · 117,427 cache-write input · 24,350 output
Agent Credits may also include non-token charges from external tools such as web research.