Repository navigation
UI Consistency: 1 issue found
apps/web/src/index.css — The new KaTeX scroll-lane rules (scrollbar-color at line 2132, ::-webkit-scrollbar-thumb background at line 2152) read raw var(--border), while all other .chat-markdown chrome — including the pre scrollbar treatment they mirror — reads the runtime contrast-adjusted var(--contrast-border) (which is also what Tailwind's border-* resolves to via --color-border). With the appearance contrast boost enabled, math scroll lanes stay unadjusted next to adjusted code-block lanes. Inline comment posted with the suggested fix.
No other findings: display-math left alignment now overrides KaTeX's centering, the error color uses var(--destructive) with the native title stripped (consistent with the sanitize schema), inline math has an overflow owner with the shared thin-scrollbar geometry, and the clipboard serializer round-trips math to explicit LaTeX delimiters before the HTML branch is prepared on a cloned fragment.
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 at head 118927f7:
apps/web/src/index.css— new.katex-display/ inline.katexrules, margin-rhythm group, globalkatex/dist/katex.min.cssimport. Global CSS ownership is appropriate here (generated markdown DOM). Specificity checked against KaTeX's own.katex-display > .katex { text-align: center }(0,3,0 vs 0,2,0, so the left-alignment override wins); both stylesheets are unlayered, so cascade order is not a factor for these selectors.apps/web/src/components/ChatMarkdown.tsx—rehypeKatexnow runs on both the raw-HTML and non-raw-HTML pipelines, so the two rendering paths no longer disagree; sanitize runs before KaTeX and thecodeclassName allowlist admitsmath-inline/math-display; the"*"filter that dropstitleis still respected becauserehypeStripKatexErrorTitleremoves KaTeX's post-sanitize tooltip.apps/web/src/markdown-math.ts/.test.ts— delimiter normalization is length-preserving (task-list offsets intact) and skips code, HTML attributes, link destinations, and autolinks.apps/web/src/markdown-clipboard.ts/.test.ts— text serialization runs beforeprepareKatexHtmlForClipboardmutates the cloned fragment, so removing the MathML branch does not affect the live message DOM or the markdown round-trip.
Role indirection was verified against the :root, [data-app-sidebar] contrast block (line ~1905) and --color-border: var(--contrast-border) (line 170).
🔍 Agent activity — 11 tool calls
💭 I'm checking the current PR diff against prior review findings, and it looks like the fixes are in place — text alignment, error color variable, KaTeX error title stripping, the thin scrollbar for inline math overflow, and clipboard serialization all appear addressed. Now I'm scanning the CSS changes and ChatMarkdown component for any new issues to flag. I want to check what the
.chat-markdown-table-containerrule contains now that.katex-displaywas added to it, and I'm also thinking through the KaTeX CSS import in index.css — since Tailwind v4 wraps everything in cascade layers, an unlayered global import could end up overriding Tailwind's own utility styles depending on how our other rules are layered. I should pull up the actual files to verify this isn't a real conflict.
1. git_tools — apps/web/src/index.css f6f2be3..118927f
2. git_tools — katex
💭 I'm checking the CSS selector logic for distinguishing inline versus display KaTeX math, confirming that rehype-katex wraps display math in a katex-display span, and thinking through how the margin rules for display equations should be applied. Since both katex.min.css and our custom
.chat-markdownrules sit unlayered outside Tailwind's cascade layers, specificity alone determines which margin rule wins for.katex-display, and our more specific selector takes priority. I'm also verifying that katex's internal classes like.base,.strut, and.mordare scoped tightly enough to avoid clashing with any existing app class names. I'm now revisiting the earlier fix where rehypeKatex was only applied on the parseRawHtml branch — that's been corrected so both branches include it, though I need to check whether the plugin list constant for the non-raw-HTML branch was updated consistently.
3. browse_code — apps/web/src/components/ChatMarkdown.tsx
4. browse_code — apps/web/src/markdown-clipboard.ts
💭 I notice the raw HTML branch is where sanitization applies since that's where the risk is introduced, and I'm checking how the sanitize schema extends the default code className pattern to include the math code class names. I trace through the pipeline order — raw parsing, normalization, sanitize, then katex — confirming katex output isn't stripped since it runs after sanitization, while checking that the math-display and math-inline classNames set by the bracket-promotion logic survive the sanitize allowlist. I then consider a UI consistency issue: setting inline katex spans to overflow-auto with inline-block display for scrollability could clip tall elements like square roots or accents, since there's a known KaTeX quirk where overflow hidden on the container cuts off content that extends beyond the computed strut height. I also weigh whether a horizontal scrollbar on inline math could visually overlap surrounding text in constrained line-heights, but that seems minor. Checking the katex-display margin and text-align overrides, I confirm the
.chat-markdown .katex-display > .katexselector has higher specificity than KaTeX's default centering rule, so that override correctly wins and resolves the earlier concern. Now I'm comparing specificity between our.chat-markdown :not(.katex-display) > .katex { display: inline-block }rule and KaTeX's own display-math rule — since ours only targets non-display math, it doesn't conflict. I should also verify whether every markdown renderer in the app actually gets wrapped in the.chat-markdownclass, since these styles only apply within that scope. I notice recreating the remark plugin array on every text change means react-markdown reruns its full pipeline during streaming, sincenormalizeLatexMathDelimitersdoes a full remark parse per token — a real perf concern but outside the stated scope of the UI consistency check, so I'll set it aside and stay focused on that scope. Now I'm checking whether there's already a shared scrollbar contract in the CSS, since the new rules duplicate the same thin-scrollbar declaration acrosspre, katex-display, and inline katex selectors — which looks like exactly the kind of duplication the check flags for overflow/scroll treatments that should use a shared class instead of repeating themselves per selector.
5. git_tools — scrollbar
💭 I notice the existing scrollbar styling uses a contrast-adjusted border variable, while the new katex rules reference the plain border variable instead — that inconsistency is exactly what the audit is flagging, since it risks splitting markdown chrome into adjusted and unadjusted colors. Let me check where
--contrast-borderis actually defined to confirm this divergence.
6. git_tools — --contrast-border
💭 This confirms a real inconsistency: all the
.chat-markdownchrome uses the runtime contrast-adjusted--contrast-bordervariable, but the new katex scrollbar rules reference the rawvar(--border)instead, so they'll fail to respond to the "increase contrast" setting the way headings, menus, and previews do. I'm also checking the error color variable to see if it has the same unadjusted-literal problem.
7. browse_code — apps/web/src/index.css
💭 I've confirmed this finding: the new markdown chrome reads raw
var(--border)instead of--contrast-border, breaking the contrast-adjusted role, so I need to swap invar(--contrast-border)across the scrollbar-color and thumb background rules. Checking further, KaTeX's use of currentColor looks fine, and I'm now scanning for another potential issue around an import statement. Since KaTeX's CSS imports are all scoped to.katex-prefixed selectors, that unlayered import doesn't clash with Tailwind's base layer in any meaningful way. I'm now looking closer at a selector combining:not(.katex-display)with.katexto see if it accidentally matches nested cases it shouldn't. So for display math, the<pre><code>block gets replaced entirely by KaTeX's rendered span structure, while inline math just swaps the code element for a katex span directly. The bracket-promotion case is trickier though: whenremarkPromoteBracketDisplayMathforces an inline math node into display mode via hProperties, it produces a<code>element with math-display class but sitting inside a<p>, not a<pre>. That means the katex plugin's parent-detection logic treats the paragraph itself as the scope's parent, so the resulting.katex-displayspan ends up nested inside a<p>tag rather than replacing a full block element. I'm confirming that being a valid HTML pattern -- a block-level span inside a paragraph still parses fine, splitting the paragraph's text visually around the block math, which is the intended rendering. I also check that this nested case doesn't accidentally trigger the inline-math selector rule, since.katex-display > .katexstill excludes it correctly. Then I start reasoning about margin collapsing between the paragraph's own margin and the nested block span's margin, since inline-context block elements can behave unpredictably with margin collapse.
8. git_tools — "chat-markdown
💭 Since
.chat-markdownis always present and KaTeX inherits currentColor fine, the new CSS rules placed above.chat-markdown h1make sense. This is generated markdown content, so it fits the criteria for genuinely reusable, behaviorally complex global CSS rather than one-owner presentation logic. Looking at the inline math styling more closely, I realizeoverflow-x: autocombined withoverflow-y: hiddenon an inline-block could cause a real issue: when a horizontal scrollbar appears, it takes up space from the content box, shrinking the vertical content area by roughly 7px. Since overflow-y is hidden, that clipped bottom portion (like descenders in subscripts) can't be scrolled into view, meaning long inline expressions with subscripts could have their bottoms visually cut off. But I decide this is too speculative without actually rendering it, and since I've already recommended the scrollbar approach in an earlier review pass, flagging it now would be somewhat self-contradictory, so I'll let it go. Turning to--borderusage, I check whether.chat-markdownever uses rawvar(--border)versus Tailwind'sborder-border/70class, which resolves through--color-borderto--contrast-border. That confirms Tailwind's border token is the contrast-adjusted variable, so any rawvar(--border)in the new CSS is indeed the unadjusted one — a solid finding worth keeping. I also verify the.katex-displaymargin rule doesn't need a chat-markdown rhythm variable instead, and that seems fine as is. Checkingmarkdown-math.ts, it's pure logic — therehypeStripKatexErrorTitlefunction removing the title attribute aligns with the sanitize schema's own title removal, so that's consistent. Then I start looking at whethererrorColor: "var(--destructive)"might also affect KaTeX's color fallback behavior for\colorcommands. Checking whether--destructivehas a contrast-adjusted variant, I find it doesn't —--color-destructivejust maps directly to--destructive, so that's fine. I then trace through the WITHOUT_RAW_HTML branch, noting that rehypeKatex now runs on user messages without sanitize, but since there's no raw HTML in that path, the sanitize schema's className allowances aren't relevant there anyway. I also confirm that withparseRawHtmltrue, sanitize runs before katex and the promoted display math className passes through correctly in both branches. I decide one finding is probably sufficient, but I want to rule out a second real issue — checking whether the:not(.katex-display) > .katexselector might unintentionally apply inside.katex-error, which it doesn't. Then I turn to the clipboard handling, wondering whetherprepareKatexHtmlForClipboardmutates the live DOM directly from the selection range's container. I need to trace wheresanitizedHtmlFromgets its container from — if it operates on a clone, there's no issue, but if it's working on the live DOM, stripping.katex-mathmlfor clipboard purposes could permanently damage the accessibility of the actual rendered message.
9. browse_code — apps/web/src/markdown-clipboard.ts
💭 Confirming it's a clone, so text serialization happens before the sanitization step mutates it, and the ordering is correct. I'm now checking a subtlety around how serializeKatex handles the annotation element inside the MathML branch, and whether isSkippedElement's aria-hidden check conflicts with serializeNode's katex class detection. My real finding is the
var(--border)vsvar(--contrast-border)inconsistency, which does violate the stated rule about not splitting markdown chrome into adjusted and unadjusted colors — minor but genuine, so I'll flag it as a failure with an inline comment. I need to map this to the correct diff line numbers, targeting the scrollbar-color and thumb background declarations around lines 2126-2151, and I'm working out how to structure a multi-line comment covering both occurrences.
10. browse_code — apps/web/src/index.css
💭 I'll go with a single inline comment on line 2132 with a suggested fix, and just mention that line 2152 needs the same change rather than duplicating the comment.
11. modify_pr — One finding: the new KaTeX scroll lanes read the unadjusted --border role instead of the contrast-adjusted --contrast-border used by the rest of .chat-markdown. Everything else from the earlier rounds (display-math left alignment, themed error color without a native title, inline overflow ownership with the shared thin-scrollbar treatment, KaTeX round-trip in the clipboard serializer) looks consistent now.
Agent Credits: 102 credits