Repository navigation
UI Consistency: 2 issues found
Two consistency issues were found in the new mermaid UI.
apps/web/src/index.css(lines 1861-1877): the new dark-mode override uses a raw.dark .chat-markdown-mermaid-overlay-sceneselector. Project convention keeps raw.darkinside thedark/lightcustom-variant definitions and uses@variant darkfor theme-only declarations; this is the only other raw.darkselector in the file. Suggested fix nests the dark background inside the base rule via@variant dark. The scene/canvas element's own presentation is single-owner and could alternatively live as static Tailwind inMermaidOverlay.tsx; the injected-SVG descendant rules reasonably stay global.apps/web/src/components/chat/MermaidOverlay.tsx(lines 241-243, plus 268, 287, 307): the overlay toolbar buttons carryclassName="chat-markdown-chrome-action", but that class is only styled under a.chat-markdownancestor (including thehtml[data-theme-id]themed bridge). The overlay is portaled todocument.body, so the selector can never match: the buttons get plain ghost chrome instead of the muted chat-chrome tone and lose the custom-theme bridge. Suggested fix is the sharedghost-mutedButton variant with the dead class dropped.
Both findings were posted as inline review comments with suggested fixes.
Details
Note
Your check run agent prompt is: .macroscope/check-run-agents/ui-consistency.md
More information about how Check Run Agents work can be found in our Help Center.
Scope reviewed: apps/web/src/components/ChatMarkdown.tsx, apps/web/src/components/chat/MermaidDiagram.tsx, apps/web/src/components/chat/MermaidOverlay.tsx, apps/web/src/index.css, apps/web/src/lib/mermaidRendering.ts, apps/web/src/lib/mermaidViewport.ts, apps/web/src/markdown-clipboard.ts and their tests.
Checks performed:
- Compared the new overlay against the shared primitives (
ui/dialog.tsx,ui/button.tsx,ui/tooltip.tsx):Dialog/DialogPopup/DialogHeader/DialogTitle/DialogDescription,Buttonwithsize="icon-xs", andTooltipare used rather than reconstructed;DIALOG_POPUP_CLASSalready providesflex flex-col min-h-0, so the scene'sflex: 1has a valid parent. - Traced
chat-markdown-chrome-actionconsumers across TSX and CSS (index.css:1789,1793,1797, andhtml[data-theme-id]rules at1402/1428) and confirmed every rule is scoped under.chat-markdown, whileDialogPortalmounts outside that subtree. - Verified
Buttonexposes aghost-mutedvariant matching the muted chrome treatment (text-muted-foregroundwithhover:text-foreground), so no new variant is required. - Grepped
index.cssfor@variant dark(19 existing usages) versus raw.darkselectors (only the newly added one) and confirmed the custom-variant definitions at lines 3-4. - Reviewed inline mermaid CSS (
.chat-markdown-mermaid,-pending,-error) for cascade conflicts with the existing.chat-markdown prerules: the new selectors have higher specificity and appear later, so the pending<pre>reset behaves as intended. - Checked accessibility/interaction preservation in the changed code-block chrome:
aria-pressed,aria-label, toolbar role, and disabled/tooltip behavior are retained; the wrap toggle is intentionally hidden while the diagram view is active.
No issues were raised for the copy/clipboard fence handling, viewport math, or the lockfile/docs changes.
🔍 Agent activity — 1 tool call
💭 I'm checking whether the scene and canvas CSS classes in MermaidOverlay should really live in global index.css, since they're only used by that one component and positioning/geometry styles like these are supposed to stay local as Tailwind classes per the project's conventions. I also notice the dark-mode styling uses a raw
.darkselector instead of the@variant darkpattern the codebase is supposed to follow, which looks like a clear violation worth flagging. I want to check index.css to confirm how dark-mode variants are handled elsewhere and whether the SVG descendant selectors for the mermaid diagrams are legitimately necessary since they target dynamically injected markup. I've confirmed the only raw.darkselector in index.css is the new one I added, so now I want to check how the custom-variant definitions are set up. I confirm that the raw.darkselector should be swapped for the@variant darksyntax, since they're functionally equivalent in how they target descendants of the dark class. Writing the CSS fix... Then I pause to consider whether this overlay scene styling really belongs in global CSS at all, versus being moved to Tailwind classes at the call site since it's ordinary one-owner geometry like position, flex, and background — only the canvas's SVG descendant styling clearly justifies staying global due to the imperative DOM content. I'm weighing whether to flag this as a finding, but decide the clearest rule-backed issue is the raw.darkselector, and note that fixing it by moving the scene's presentation into Tailwind would also resolve that. Then I move on to check other potential findings, starting with how the wrap toggle behaves when a diagram is shown in MarkdownCodeBlock. I'm also checking whether the overlay's custom close button duplicates functionality already built into DialogPopup, and whether other dialogs follow the same convention. I notice the overlay icons use a slightly different size than the chat markdown chrome actions, and more importantly that thechat-markdown-chrome-actionclass is scoped under.chat-markdownin CSS, but the overlay renders in a portal outside that scope — meaning the styling likely doesn't apply where it's used. I'm realizing the.chat-markdown-chrome-actionclass only applies styling within.chat-markdown, but MermaidOverlay renders through a Dialog portal attached to document.body, outside that scope. So those four className references in MermaidOverlay are effectively dead code—the buttons won't get their intended muted/hover styling. This is a real consistency finding: a scoped selector referenced where it can never match, causing tone inconsistency with the inline chrome actions. The cleanest fix is applying the muted-foreground/hover-foreground Tailwind classes directly at the overlay call site rather than relying on the scoped rule. I'm also checking whether DialogPortal renders outside the scoped container — it does, into document.body by default. That raises another finding: the close button uses a plain Button with an onClick calling onOpenChange(false) instead of Base UI's DialogClose render prop, bypassing its close semantics like focus return and close reason tracking. I'm weighing whether this is a real behavioral gap, since the controlled onOpenChange path likely still routes through similar state logic, but it's worth flagging as a moderate finding suggesting DialogClose with a render prop instead. I want to be careful not to over-flag this as a false positive, so I'm going to check how other parts of the codebase handle custom close buttons in dialog headers before deciding how strongly to call it out. I'm noting the MermaidOverlay close button deviates slightly from the repo's DialogClose convention, but that's a minor point. The stronger issue is that the overlay's chrome-action buttons use a class scoped under.chat-markdown, so the selector never actually matches them since they render in a portal outside that container. I should check whether the ghost Button variant already applies muted-foreground styling by default, since that would make the missing scope less impactful. I also want to check the raw.darkselector and the custom theme bridge rules around lines 1390-1470 in index.css, since those also scope chrome-action colors under.chat-markdown— meaning overlay buttons might fall back to default ghost hover colors under custom themes instead of the intended theme-aware styling. I notice there's already a "ghost-muted" variant that matches the exact treatment the custom class was providing. So the proper fix for the overlay buttons is swapping to that named variant instead of combining "ghost" with the mismatched custom class—a cleaner, more consistent approach. I also want to check the--control-icon-colorindirection to see how icons actually pick up their color, since the ghost variant sets it to muted-foreground already. I need to look at the button base classes to determine whether icon color would end up the same regardless of variant, or if hover states genuinely differ between ghost and ghost-muted. Let me pull up the full button component to verify. I'm checking that the overlay icons always resolve to the muted-foreground color for ghost variants since--control-icon-colordoesn't change on hover, so the icon color stays consistent regardless of where it's rendered outside.chat-markdown. So the svg color is actually set by that arbitrary variant targeting the svg directly, meaning the parent's color rule doesn't inherit down to it. That means the chrome-action class's only real visible effect is the aria-pressed background tint, but the overlay buttons never set aria-pressed, so adding that class there is essentially dead weight with no visual consequence — still worth flagging as a misleading dead class since it implies styling it doesn't actually receive. I'm confirming the hover color rule is also irrelevant for icon-only buttons, so the class is truly inert in the portal context. The cleanest fix is to just drop the dead class rather than swap in a different variant, since that keeps the change minimal and avoids subtly altering appearance. Next I'm checking the explicit icon-size override against the default icon-xs sizing—it's a minor deviation not worth flagging compared to the inline chat usage. The raw.darkselector in index.css is a solid finding since it should use@variant dark, and that file is within scope, so I'll report it inline at that line. I'm also considering whether the overlay's canvas/SVG presentation being styled with global CSS rather than Tailwind utilities is worth flagging as a third issue. I'm weighing whether to fold the scene's own presentation rules into the same comment as the.darkfix, suggesting they move to the owner component as static Tailwind classes, but that feels like too much for one comment—so I'll keep the primary ask focused on the@variant darkfix and mention the Tailwind-ownership option as a secondary alternative. Next I want to check the actual close button implementation in MermaidOverlay around lines 219-320 to see if it's using DialogClose properly, and also look at how the inline mermaid markdown container's CSS is being applied. I'm also considering whether the diagram view still preserves the copy button and chrome consistency when the wrap button is removed—I should check ChatMarkdown around lines 620-800 to confirm thedata-mermaid-viewattribute and codeblock styling carry over correctly to the diagram rendering. I notice the dialog popup's className doesn't include flex column styling, but the scene uses flex:1 which needs a flex parent, so I should check whether DIALOG_POPUP_CLASS actually provides that. The popup's flex-col layout looks fine. Checking the DialogHeader override, it switches to a row layout with centered items and custom padding, overriding the base flex-col/padding defaults; the leftover space-y-0 is a harmless no-op since the base uses gap spacing, so I won't flag it as an issue. I'm weighing whether the max-sm:pb-4 from the base component could override the call-site's py-2.5 on mobile due to Tailwind's variant ordering, but this is a subtle edge case that likely exists elsewhere too, so I'll skip flagging it and move on to checking the chat-markdown-chrome-action component. I'll decide to flag the overlay's portal rendering once at its first occurrence rather than repeating it for each instance, confirming that Base UI's Dialog.Portal defaults to rendering into document.body. I also note the overlay toolbar is missing toolbar role/aria-label compared to the code-block toolbar, but treat that as too minor to flag, and confirm the Maximize2Icon usage is likely already imported elsewhere so typecheck would catch any issue. I'm also tracing a subtle bug in the clipboard logic: when copying a mermaid error-boundary fallback that lacks adata-markdown-copyattribute, the code silently skips pushing any text or HTML for that segment, meaning the copied selection drops that content entirely instead of falling back to plain text. It's a minor edge case in markdown-clipboard.ts, arguably within scope but more of a behavioral quirk than a clear UI inconsistency worth flagging strongly. Other findings I'm noting: a raw.darkselector in index.css that should use the@variant darkconvention, a chrome-action class in MermaidOverlay that's dead code outside.chat-markdown, and a close button that bypasses theDialogCloseprimitive in favor of a manual onClick handler — worth suggesting since DialogClose is the established convention, though the current approach still works functionally. Now I'm mapping out exactly which diff lines to anchor each comment to, confirming the added lines in index.css and the new MermaidOverlay.tsx file are all visible on the right side of the diff so comments will land correctly. For the first comment, I'm drafting a suggestion that nests the dark-mode override inside the base rule using@variant darkinstead of a standalone.darkselector, matching the convention used elsewhere in the file, and preserving the background fallback to--secondarywith a color-mix adjustment for dark mode. For the second comment, I'm noting that the overlay buttons in MermaidOverlay.tsx render inside a Dialog portal outside the.chat-markdownsubtree, so the chrome class they rely on for styling never applies there — I'll suggest swapping to the sharedghost-mutedvariant and dropping the now-useless class, checking the exact indentation so the inline suggestion matches the surrounding lines. For the close button, switching it to use DialogClose requires an import change alongside the render-prop wrapping, so a simple inline suggestion won't cover it cleanly — I'm working out whether a small unified diff with a note about the import is the better way to present that fix. No regression there, so I'll skip that point. Checking other potential issues: disabled buttons inside tooltip triggers won't fire pointer events so tooltips won't show, but that's expected standard behavior, not a bug. The mermaid container styling and the transparent SVG background rule mirroring the existing shiki rule both look fine too. Looking at the pending-state pre element for mermaid diagrams, the specificity ordering works out correctly since the more specific pending selector comes after the base pre rule. The wrap toggle removal leaving a harmless data-wrap attribute is also fine. I'm now just double-checking minor styling consistency between the overlay's chrome action class and the header's muted text color utility. Now I'm finalizing the review: both findings default to failure state since issues exist. I'm verifying the line numbers against the head commit for both comments—one spanning lines 1861-1877 in the CSS diff block, and another at lines 241-243—confirming both ranges fall within the added diff content before posting the review with the suggested CSS fix.
1. modify_pr — UI consistency review of the mermaid diagram/overlay work. Two findings, both about ownership of the new overlay styling rather than the feature itself.
Agent Credits: 162 credits