Skip to content

fix(web): keep the profile chrome from crowding its neighbours

MacroscopeApp / Macroscope - UI Consistency failed Aug 16, 2026 in 7m 39s

UI Consistency: 1 issue found

apps/web/src/components/preview/PreviewView.tsx — The new profile Badge in the preview chrome row uses max-w-28 … truncate, but Badge is inline-flex justify-center whitespace-nowrap, so text-overflow never applies to the anonymous flex item holding the name. A long profile name is hard-clipped with no ellipsis, and justify-center clips it at both ends. Suggested the repo's pill convention: max-w-* on the badge with truncate on an inner span (plus title for the full name).

No other in-scope findings: the MenuSubTrigger leading-icon alignment now matches MenuItem and is scoped away from the chevron (:not(:last-child)), so the icon-less Appearance and Turn sub-triggers keep the chevron's ms-auto; the Clear cookies/cache heading is correctly wrapped in MenuGroup; the browser-profile settings rows reuse SettingsRow, ITEM_ROW_INNER_CLASSNAME, DraftInput, Button, Badge, Tooltip, and gate deletion behind the same AlertDialog pattern used elsewhere in settings; the default-profile control uses the shared Select with a SettingResetButton.

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 beyond the diff for context: apps/web/src/components/ui/menu.tsx (MenuItem/MenuSubTrigger/MenuPopup contracts), apps/web/src/components/ui/badge.tsx, apps/web/src/components/ui/input.tsx, apps/web/src/components/ui/draft-input.tsx, apps/web/src/hooks/useCommitOnBlur.ts, apps/web/src/components/settings/settingsLayout.tsx, apps/web/src/components/settings/itemRows.ts, apps/web/src/components/settings/settingsSearch.ts, apps/web/src/components/preview/PreviewChromeRow.tsx, apps/web/src/components/preview/PreviewMoreMenu.tsx, apps/web/src/browser/browserDefaults.ts, and the other two MenuSubTrigger call sites (DiffPanel.tsx, PreviewMoreMenu.tsx).

Checks performed: sub-trigger selector scoping against both existing icon-less sub-triggers; MenuGroupLabel context requirement; required-prop propagation for onAddBrowserInProfile across all RightPanelTabs consumers (ChatView, pull-requests route, test); nativeInput precedent for DraftInput; empty-name rename behaviour via useCommitOnBlur (draft resets, no invalid commit); settings-search catalog entries added for both new rows.

Prior findings from earlier runs (icon-less sub-trigger chevron regression, unbounded badge width, unconfirmed destructive profile deletion) are resolved in this head commit and were not re-reported.


🔍 Agent activity — 1 tool call

💭 I'm checking the current state of the PR to see what's been fixed since the last run—the menu.tsx selector and badge capping issues appear to be resolved. Now I need to examine the changed files in the web app, particularly focusing on menu.tsx, RightPanelTabs, IntegrationsSettings, and Preview to catch any remaining issues. Let me pull the actual file contents for MoreMenu, PreviewChromeRow, and PreviewView to continue the review. Now I'm checking MenuItem to see how it handles icons and whether it has cursor-pointer styling, then I'll look at MenuPopup for comparison. I'm noticing the selector differences between MenuSubTrigger and MenuItem—one uses a descendant combinator for SVGs while the other uses a child combinator. The new selector with :last-child should work reliably since the chevron is always positioned last, so leading icons will get the negative margin adjustment correctly. Looking at the other components in scope, RightPanelTabs handles browser profiles from the defaults, and SurfaceMenuItem shows a disabled tooltip when unavailable. But there's a potential keyboard accessibility issue: if the MenuSubTrigger's onClick handler both opens the submenu and closes the menu, pressing Enter on the trigger could trigger both actions simultaneously, which might confuse keyboard users trying to navigate the submenu. Now I need to check PreviewMoreMenu for consistency in how it uses MenuGroup and MenuGroupLabel, and then review IntegrationsSettings to see how the new BrowserProfilesSetting component uses DraftInput, Button, AlertDialog, and other elements compared to similar patterns elsewhere. I need to verify whether SettingsRow expects a title prop and check if searchableSetting is providing it automatically when it wraps BrowserProfilesSetting. Let me trace through both function signatures to see how the title gets passed down. I need to check the SettingsRow component's signature to understand how children are positioned and whether there's a similar list pattern used elsewhere in the ConnectionsSettings component. The row layout checks out, so now I'm looking at PreviewMoreMenu and PreviewChromeRow to verify the badge styling. I need to examine how MenuGroupLabel is being used in PreviewMoreMenu and whether there are other sections that don't use groups. I'm noticing a UI consistency issue where the profile name gets repeated — it shows in the group label like "Profile: Work" and then again in each menu item as "Clear cookies (Work)". This redundancy becomes problematic because profile names can be up to 48 characters long, and since the menu items don't truncate text, long names would cause the popup to expand beyond a reasonable width. The MenuItem uses flexbox without text wrapping constraints, so the text will expand the container rather than wrap. The popup itself has no max-width constraint, only a max-height, so a doubled 48-character name could stretch it quite wide. It's a mild issue — menus with long labels aren't uncommon — but the redundancy is more of an aesthetic concern than a functional violation. Now I'm checking the PreviewView badge and PreviewChromeRow leadingActions placement. I'm thinking through how the Badge component with outline variant handles text truncation — checking whether the default classes use inline-flex with proper gap and whitespace-nowrap, and whether the truncate utility (overflow-hidden, text-overflow ellipsis, whitespace-nowrap) works correctly on a flex container when the text is wrapped in an anonymous flex item. The Badge component uses inline-flex with justify-center, and when truncate is applied at the call site, the text becomes an anonymous flex item within that flex container. Since text-overflow: ellipsis only works on block containers, it doesn't apply here—instead the flex item's overflow gets clipped without the ellipsis effect, which is the typical behavior when truncating text inside a flex parent. I'm realizing that text-overflow isn't inherited, so even though white-space: nowrap would apply to the text, the ellipsis won't show up. This means long profile names in the badge get hard-clipped mid-character rather than properly ellipsized. Let me search the codebase to see how other badges handle this—checking if there are patterns where an inner span applies truncate instead. I'm looking at how other Badge components handle text truncation with inline-flex layouts, checking for patterns where truncate classes are applied to inner spans alongside Badge usage. I'm working through how the truncate utilities interact with the badge's flex layout—overflow hidden clips the content, but I need to verify whether text-overflow ellipsis actually applies when the badge itself is a flex container with a text child. Looking at the CSS spec and browser behavior: text-overflow only works on block containers, and while the anonymous flex item is technically a block container, it doesn't inherit the text-overflow property since it's non-inherited. So in practice, the text gets hard-clipped without an ellipsis showing. I'm also realizing that justify-center on the badge compounds this—when content overflows, centering clips from both sides rather than just one. This is actually a regression from my earlier suggestion of that exact code pattern. The proper Tailwind convention for this is wrapping with min-w-0 max-w-* overflow-hidden on the flex container and applying truncate to an inner span instead. It's a real issue, though I'm weighing whether it's significant enough to flag given that I'm the one who suggested the problematic approach in the first place. Writing the Badge component fix... Now I'm checking other potential issues in the codebase. The submenu items in RightPanelTabs show profile names without truncation, which could be a problem if names get long, but that's minor. I'm also noticing there's no "Default" indicator on submenu items, which is more of an aesthetic concern. The onAddBrowser handler is still properly connected to SurfaceMenuItem, and the disabled state is handled correctly. There's a consistency issue I need to look at with how the Browser row is structured in the available case. The MenuGroupLabel styling looks consistent with the rest of the component. There's some redundancy in the "Profile: X" labels paired with "(X)" in the items, which adds visual noise, but that's more of an aesthetic concern than a functional issue. Now I need to check the SurfaceMenuItem definition to see if MenuSubTrigger loses any important behavior like coarse-pointer or keyboard handling that SurfaceMenuItem provides. I'm checking how keyboard interactions work with the new MenuSub component in RightPanelTabs, specifically whether Base UI's SubmenuTrigger properly handles Enter and Space keys to open submenus through its synthetic click behavior. The real concern is that clicking the submenu trigger both fires the onClick handler (which adds a tab and closes the menu) and toggles the submenu open — so I need to verify if there's an existing pattern in the codebase where MenuSubTrigger has an onClick action to understand how this should be handled. The DiffPanel and PreviewMoreMenu sub-triggers both lack leading icons, so the chevron is the only SVG element, and the :not(:last-child) selector keeps them properly scoped. For the Menu component, I'm making it fully controlled by passing open and onOpenChange props to MenuPrimitive.Root, which means the menu can only open when the state change handler fires. Base UI triggers the onOpenChange callback when the trigger is clicked, so that works fine. When a profile MenuItem in the submenu gets clicked, Base UI closes the entire menu and fires the root's onOpenChange with false, which is the expected behavior. RightPanelTabs also renders the empty-state launcher with onAddBrowser, but that doesn't need the profile submenu since it's not required there. I should verify that onAddBrowserInProfile is wired up at all the call sites—ChatView appears twice, the pull-requests route, tests—and check if there are any other consumers of RightPanelTabs by grepping for onAddBrowser assignments to make sure the required prop passes type checking. Now I'm checking whether the DraftInput primitive supports properties like nativeInput, size, maxLength, onCommit, disabled, aria-label, and value. Let me check the input component to see if it supports nativeInput and size props. I'm also wondering about the behavior when renameProfile gets an empty string — the draft would display locally in the input, but the commit might be skipped on blur, which could leave the empty text visible if useCommitOnBlur doesn't reset the draft back to the original prop value. I'm checking whether the className prop passed to Input actually gets applied to the wrapper, and whether the nativeInput variant properly handles the className from the call site. I also need to verify if the rename function allows empty strings. When the user clears the input and blurs, the draft becomes empty, which triggers onCommit("") — but renameProfile returns early without updating, so the draft resets to null and the input re-renders with the original value. Now I'm looking for other uses of nativeInput DraftInput in the settings to check for consistency. I'm wondering why this DraftInput specifically uses nativeInput when other inputs don't—probably to support maxLength, though the Base UI Input primitive likely handles that too. The issue is that nativeInput bypasses onValueChange, which means it's not using the Base UI primitive at all. Let me search for other nativeInput call sites to see if there's a pattern here. Looking at the nativeInput usage and the previewBridge?.clearCookies() call in IntegrationsSettings — that's guarded properly since previewBridge is desktop-only. But in BrowserProfilesSetting, I'm noticing the profile rows are rendered as children of SettingsRow, and the disabled prop is being handled through opacity styling in DesktopOnlyBrowserDefaults. The issue is that the wrapper dims h3 and p elements, but the profile rows themselves are just plain text spans and badges that don't get dimmed — though the controls are disabled. On non-desktop, the built-in profiles show at full contrast inside what's supposed to be a disabled block, which creates a visual inconsistency. It's a pre-existing pattern though, and the actual controls (Select/Switch) do get proper disabled styling, so it's marginal. On web, the "Add profile" button correctly shows as disabled. Now I'm looking at PreviewView to see if browserDefaults comes from useBrowserDefaults() and whether there's a missing tooltip for the truncated badge. The author did follow my earlier suggestion to cap the badge with max-w-28 shrink-0 truncate, but that creates a subtle issue — the text gets center-clipped without an ellipsis when it overflows. The justify-center on the inline-flex badge combined with overflow hidden causes clipping from both sides, which isn't ideal for readability. Looking at the flex behavior more carefully: the anonymous flex item inside has flex-shrink: 1 by default, but with white-space: nowrap the min-content width becomes the full text width, so it can't actually shrink below that. This forces overflow, and with justify-content: center the text gets clipped symmetrically rather than from one side. The inner-span approach fixes this by applying truncate to the span itself, which sets overflow: hidden and resets the automatic minimum size to 0, allowing it to shrink and ellipsize properly. This is a real visual defect for longer names, and since the codebase already has this convention established, it's worth flagging as a single comment. Now I'm weighing whether to mark this as a failure. The check defaults to failure when findings exist, but the guidance says not to fail for optional aesthetic preferences. This feels like a genuine consistency issue rather than just a style preference, so I'm leaning toward reporting it as a failure. Let me scan the other changed lines to see if there's anything more substantive before I decide. I'm noticing that useClientSettings with the toBrowserDefaults selector creates a fresh object on every call, which could trigger unnecessary re-renders if it's using useSyncExternalStore. The selector also builds a new profiles array each time via resolveBrowserProfiles, following the same pre-existing pattern that was already there. Looking at the rest of the changes: RightPanelTabs calls useBrowserDefaults() unconditionally even in the web component, which is fine. The submenu lists all profiles including "Default" and "Incognito" consistently. There's a minor inconsistency where the PreviewView badge only shows when the profile differs from default, but PreviewMoreMenu always displays "Profile: X" when a profile is selected — including for Default — but that's not a real defect. The test file looks good, so I'll focus on posting an inline comment about the Badge truncation issue. Actually, let me verify my understanding of the truncate behavior on inline-flex containers. The key issue is that text-overflow requires a block container, and flex containers don't create block containers for their contents — the contents become flex items. So <div class="inline-flex truncate">text</div> won't render ellipsis. Combined with justify-center potentially clipping both ends, the solution is to wrap the text in an inner span where truncate can actually work. I'm confident enough to comment on this. The fix wraps the name in a span with truncate, adds max-w-28 and shrink-0 to the Badge itself (following the repo's pattern in ThreadSyncStatusPill and PullRequestSummaryTab), and includes a title attribute for hover reveal since Badge accepts span props. I'm also weighing whether to flag the MenuSubTrigger keyboard interaction issue — where Enter might trigger both the submenu open and a click, causing unexpected navigation — but without being able to verify Base UI's internals, I'm hesitant to raise it as noise rather than a concrete concern. On the mouse interaction side, Base UI's default hover-to-open behavior with click-to-toggle should work fine here even if the submenu's already visible. For the final assessment, I'm seeing one minor visual finding around the badge width constraint, so I'll mark this as a failure with a clear title rather than an all-clear.

1. modify_pr — One finding on the new profile badge in the preview chrome row: the truncate cap doesn't actually ellipsize because Badge is a flex container. Everything else in scope (the MenuSubTrigger icon-column fix, the MenuGroup/MenuGroupLabel wrapping in PreviewMoreMenu, the profile list rows reusing SettingsRow/ITEM_ROW_INNER_CLASSNAME/DraftInput/Button/AlertDialog, and the Select for the default profile) follows the existing primitives and settings conventions.


Agent Credits: 424 credits