Skip to content

fix(web): render Hebrew/Arabic chat markdown right-to-left

MacroscopeApp / Macroscope - UI Consistency failed Aug 19, 2026 in 5m 9s

UI Consistency: 2 issues found

Two bidi correctness issues in the changed lines of apps/web/src/components/ChatMarkdown.tsx:

  • apps/web/src/components/ChatMarkdown.tsx:1575 — dir="auto" on the GitHub-alert container can never resolve RTL because the first strong character is always the hard-coded English label (Note, Warning, …). A Hebrew alert keeps its border-s-2 ps-3 bar on the left while the body paragraphs align right via the new unicode-bidi: plaintext rule, leaving bar and text on opposite sides. Suggested fix: exclude the title from the container's auto resolution (dir="ltr" on the title <p>, or <bdi> around the label).
  • apps/web/src/components/ChatMarkdown.tsx:1748 — dir="auto" is applied to the <table> only; the enclosing ScrollArea viewport stays LTR. Combined with .chat-markdown table { min-width: max-content }, an overflowing RTL table opens scrolled to the last column with the first column off-screen. Suggested fix: resolve the direction on the scroll container as well, or verify the initial scroll position with a wide Hebrew table.

The CSS side of the change (logical properties, unicode-bidi: plaintext + text-align: start on leaf blocks, LTR isolation for code) is scoped to generated markdown and is appropriately kept in the global .chat-markdown block; no ownership or primitive-consistency problems were found there.

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: apps/web/src/components/ChatMarkdown.tsx (blockquote/alert, ol/ul, table renderers, MarkdownTable, root .chat-markdown wrapper), apps/web/src/index.css (.chat-markdown block, lines ~1385–1885), apps/web/src/components/ui/scroll-area.tsx.

Checks performed:

  • Verified MarkdownTable spreads props (including dir) onto the <table>, not onto the container or ScrollArea viewport.
  • Verified alert.borderClassName values are color-only (border-blue-500/70, etc.), so the border-l-2 pl-3 → border-s-2 ps-3 migration is safe.
  • Checked specificity of the new .chat-markdown :is(p, li, h1…h6, td, th, dt, dd) rule (0,1,1) against the later .chat-markdown th, td { text-align: start } rule and against Tailwind text-align utilities; no call site applies text-left/center/right inside .chat-markdown, and GFM column alignment is emitted as an inline style, so it still wins.
  • Confirmed unicode-bidi: plaintext + text-align: start is the standard auto-alignment recipe (start/end resolve from the plaintext-resolved paragraph direction), so no regression there.
  • Confirmed remaining physical values in the .chat-markdown block are direction-neutral (background-position: left bottom with repeat-x, symmetric paddings) or inside the now force-LTR code-block subtree.
  • Confirmed .chat-markdown is applied only by ChatMarkdown's root, so the new rules do not leak to other surfaces (PullRequestMarkdown, ProposedPlanCard, FilePreviewPanel, MessagesTimeline all render through ChatMarkdown).

🔍 Agent activity — 1 tool call

💭 I'm checking the diff for prop ordering issues—whether spreading props after dir could let callers override it, and whether that's consistent across the blockquote, ordered list, and table elements. I also need to verify whether MarkdownTable actually accepts and forwards a dir prop at all. Beyond that, I'm examining whether .chat-markdown is the real class being applied and whether the CSS rules for text direction, alignment, and unicode-bidi: plaintext actually interact the way I expect—since text-align: start with plaintext bidi resolution has a subtle spec nuance where alignment may resolve against the element's declared direction rather than the content-detected paragraph direction. I also want to check how dir="auto" behaves on tables and lists, since it affects column and marker ordering, and whether the existing pre/code LTR overrides conflict with other rules in index.css. I should just go look at the actual files to confirm these details. Now I want to check whether MarkdownTable forwards the dir prop to the table element and how it handles rendering. I'm checking that the dir attribute spreads correctly onto the table element so column order follows RTL, with only a minor concern about scroll position inside the ScrollArea. I'm also reviewing the code rule forcing inline and block code to stay LTR with isolated bidi behavior, which matches the intent of keeping code readable regardless of surrounding text direction. One thing I want to verify is whether the new text-align: start rule on table cells conflicts with GFM table column alignment, since react-markdown renders :---: syntax as inline text-align styles on th/td elements, and inline styles take precedence over CSS classes anyway. I'm also checking the specificity math between the new :is() selector and later th/td rules, and whether this global rule could override Tailwind utility classes like text-center applied to paragraphs or headings inside chat markdown, since the new rule's specificity is higher than a single utility class. I'm also considering how unicode-bidi: plaintext interacts with list markers when a list's overall direction differs from an individual item's content direction, and reviewing where dir="auto" gets applied across the JSX elements like blockquote. ol, ul, and the table wrapper to confirm consistency. I want to check the blockquote handler around line 1566, particularly the alert-blockquote branch, to see if dir="auto" was applied there too. I'm checking whether forcing LTR direction on code elements could unintentionally affect other rendered components like file link chips, but those use anchor tags rather than code elements so they should be unaffected. Now I'm considering how GFM table alignment interacts with this styling. For table cells, mdast-util-to-hast emits inline text-align styles for aligned columns, and since inline styles take precedence, that alignment should still work correctly regardless of the bidi rules applied elsewhere. The trickier case is how unicode-bidi: plaintext combined with text-align: start behaves on table cells — CSS Text Level 3 has a specific note that start/end alignment should resolve based on the paragraph's detected direction rather than the computed direction property when unicode-bidi: plaintext is set, so I need to verify how browsers actually implement that edge case. I'm also checking list items: since unicode-bidi: plaintext doesn't alter the direction property itself, task-list checkbox margins (which use logical margin-inline-start) should still resolve correctly from the inherited dir="auto" direction on the parent list, so Hebrew or Arabic lists should stay consistent with their markers. For paragraphs, direction stays LTR while text-align: start follows the paragraph's own resolved direction per spec, which seems fine, and I don't think other markdown surfaces like terminal diffs need the same treatment. Since this CSS is scoped to generated markdown and fairly complex, putting it in global CSS rather than static Tailwind classes in the owning module seems like the right call. Now I'm looking at the table component, where dir="auto" gets passed through to the <table> element itself while its ScrollArea container stays LTR — for a Hebrew table that overflows horizontally inside that LTR-scrolling wrapper, I want to trace whether the table's overflow direction actually matches user expectations or creates a mismatch with the scroll fade. I'm confirming that dir="auto" is a valid global attribute that works on any element including <table>, so that part checks out. Then I'm considering the alert component's title paragraph, which uses a flex layout with unicode-bidi: plaintext applied — since that property is meant for inline-level content within a block, I'm questioning whether it has any real effect when the element itself is a flex container rather than producing meaningful bidi isolation for its flex items. So the border bar stays on the left regardless, since dir="auto" always resolves LTR from the English label text — meaning the alert container's dir="auto" is effectively a no-op for flipping the decorative bar side, even though the body text itself aligns correctly via plaintext bidi. Since the alert icon is aria-hidden with no text, the label itself determines direction, so it's always LTR — confirmed. Checking other dir="auto" cases like blockquotes, ordered lists, and markdown tables, they all seem to resolve correctly based on their first strong character. I'm now checking whether the paragraph renderer or inline markdown children wrapper has a similar issue. Moving on, I'm verifying the code block styling — the LTR direction rule on pre elements and the isolate unicode-bidi on inline code both look correct, and the codeblock header with its buttons should render fine too. I'm also checking that the shiki-rendered pre elements inherit the same LTR rule, and confirming dt/dd elements aren't a concern. Then I'm looking at the text-align left-to-start conversion for table headers and cells, verifying it correctly flips to right-aligned under RTL with dir=auto tables, before moving on to check other padding-left, margin-left, and text-align properties that might need similar treatment. Details/summary elements aren't touched, and other markdown-rendering components like plan or diff views are out of scope. I'm also noting that unicode-bidi: plaintext on p elements applies within chat-markdown too. I'm considering whether file link chips inside RTL paragraphs could render oddly since MarkdownFileLink isn't bidi-isolated, but this seems like a minor edge case rather than a real issue. More importantly, I want to check whether the new text-align: start rule on paragraphs and list items might conflict with any existing text-center or text-right utility classes used inside ChatMarkdown.tsx or its child components. Checking that direction: ltr on inline code doesn't cause issues, and that unicode-bidi: isolate on block elements like pre is fine. I'm also verifying the dir="auto" behavior on ordered lists works correctly in RTL with the gutter styling, and looking at how task list checkbox margins are handled with inline-direction properties. Now I'm tracing through a tricky edge case: a checkbox inside an RTL list item inherits direction correctly since it sits at the start of content, which is the right side in RTL. But I'm worried about mixed-direction task lists — say an English list (resolving ltr via dir=auto) containing one Hebrew item where unicode-bidi: plaintext makes that item's text render RTL while the checkbox's negative margin still follows the inherited ltr direction, potentially misplacing it relative to the text. This seems like a deep edge case, so I'm moving on to scan the remaining physical-direction CSS properties in the chat-markdown scope that the PR touches, like the link underline background-position, confirming those are fine since they tile across the whole box. I've now reviewed the relevant ranges in index.css and I'm shifting to check MarkdownDetails next. The details/summary component uses border-y and likely a chevron with physical padding, and since Hebrew content inside details doesn't get dir=auto, that's a scope decision rather than a regression. I want to grep across index.css for any remaining text-align: left or physical padding-left within .chat-markdown, plus check other dir= usages and whether the ChatMarkdown root needs dir handling. I should check whether consumers of ChatMarkdown pass className with text alignment utilities, and whether the new .chat-markdown selector's specificity could override Tailwind classes applied to elements inside it. I'll grep ChatMarkdown.tsx for text-center/text-right/text-left usage, check consumer components for className overrides, and look for existing bidi tests to understand intent. No text-align utilities found inside chat-markdown content, which is good. Now I want to check how MessagesTimeline uses ChatMarkdown for user message bubbles, to see if those rely on text-align, and confirm no other markdown surfaces render these elements outside the .chat-markdown scope. Thinking about CSS ownership rules, the global .chat-markdown styles for generated markdown are fine to stay global. But I'm spotting a real inconsistency with the GitHub-alert branch's dir="auto": since the first strong character there is always the hard-coded English label, dir="auto" can never resolve to RTL, so the alert's left border bar stays on the left while Hebrew body text (via unicode-bidi: plaintext) aligns right — a visible mismatch between bar and text direction that these changed lines introduce. The cleanest fix is to keep dir="auto" on the container but add dir="ltr" to the title <p>, since per the HTML spec the auto-direction algorithm skips descendants that have their own explicit dir attribute when picking the first strong character — so the container would then resolve direction from the body text instead of the always-English label. I'm weighing whether this is actually a concrete violation worth flagging, since it's more of an incomplete feature than a regression against main. Let me check if there's a stronger issue instead — maybe the new CSS rule targeting code blocks could unintentionally affect the file-link chip rendering, since that's also rendered as an anchor element within inline code. I'm also checking whether unicode-bidi: plaintext applied broadly to paragraphs, list items, and table cells causes any conflicts with truncation or flex layouts, but nothing seems problematic there. Now I'm looking at whether dir="auto" was also added to list elements like ul/ol. The footer row with the expand and copy buttons stays LTR since dir=auto only applies to the table itself, which seems consistent enough. I'm also confirming the ChatMarkdown root div should stay LTR per block, and double-checking whether text-align: start paired with unicode-bidi: plaintext is actually spec-compliant per CSS Text Module Level 3. I'm fairly confident this is the standard recipe browsers support for auto-aligned mixed-direction text, so not a finding. Tests aren't really needed since this is mostly visual, and I don't see the markdown serialization or copy handler being affected by dir attributes either. Now I'm checking whether chat-markdown styles might be duplicated elsewhere, like a print or export stylesheet. Looking more closely, I'm worried about the global rule forcing direction: ltr on all code blocks, including inline code — that would force Hebrew or other RTL text inside inline code to render left-to-right, which seems like a real regression worth flagging. I'm also checking whether other components share this markdown CSS and inherit the bidi behavior, but a search confirms only ChatMarkdown applies that class, so the scope is contained. The remaining question is whether the CSS changes respect the project's styling conventions — the bidi rules are properly scoped to generated markdown, while the alert's spacing tweak looks like a legitimate Tailwind logical-property update. Now I want to confirm border-s-2 and ps-3 actually map to the inline-start border width and padding in Tailwind v4, which they do. The remaining concern is whether alert.borderClassName uses a physical directional color class like border-l-blue-500 rather than a logical one, so I need to check the alert presentation definitions for that. The border styling with border-s-2 and a blue accent color works fine. But the real issue is the dir="auto" attribute on the alert label div — since the label text is always hardcoded English (Note/Tip/Important/Warning/Caution), it can never resolve to RTL, making that attribute dead code that fails to achieve its intended effect of positioning the bar correctly for Hebrew alerts. Now I'm weighing whether the visual mismatch — a left-positioned bar with right-aligned Hebrew body text — is actually a meaningful inconsistency worth flagging, or just an acceptable quirk since GitHub-style alerts typically render their labels in English regardless of content direction. I want to check if there's a stronger issue elsewhere, so I'm now considering the ScrollArea component wrapping RTL tables — specifically whether the scroll container starts at the correct horizontal position when its content is right-to-left but the container itself is LTR. This turns out to be a real problem: since the scroll container stays LTR while the table inside is RTL, the initial scroll position shows the table's last columns instead of the first, forcing users to scroll right just to see the beginning of the data. The fix would involve matching the scroll container's direction to the table's resolved direction so the initial view aligns correctly. I should weigh confidence levels here—the dir="auto" no-op issue is clearly demonstrable from the deterministic HTML spec algorithm, while the table scroll issue requires runtime verification, so I'll lean toward reporting the stronger one. Checking the spec again confirms that dir="auto" directionality excludes descendants that are bdi, script, style, textarea, or have their own dir attribute set. I'm also scanning other components—the ol/ul dir="auto" usage, the markdown table prop types, and the icon svg with no text—to confirm they're consistent and not worth flagging. Now I want to dig into the ScrollArea implementation to see whether it handles dir or overflow in a way that could affect the table-scroll behavior. I'm reconsidering whether the table is truly the direct child of the scroll viewport, and recalling that RTL content inside an LTR overflow container is known to start scrolled at the wrong end—this is exactly why dir needs to be set on the scroll container itself. The practical trigger depends on whether the table's width actually exceeds the viewport, given its min-width and expanded state. Given that I can't verify runtime scroll behavior and the review guidance says not to treat screenshots as proof, I'm leaning toward flagging this as something needing evidence rather than asserting it confidently. I'll plan to post inline comments, starting with the dir="auto" issue on the alert element that can never resolve correctly. Let me check ScrollArea's implementation quickly. I'm checking whether Base UI's scroll-area overflow-start/end variables map physically to left/right, since that would cause the fade mask to appear on the wrong side in RTL layouts. But since only the table itself has dir=rtl while the viewport stays LTR, the overflow variables remain tied to physical left/right, so the fades actually line up correctly with the real scroll position — no issue there. So the real consequence of dir=auto is just about column ordering inside the table plus the initial scroll offset, since the table (set to max-content width) overflows its LTR container to the right and I need to work out where the scrollbar starts by default. Since scrollLeft starts at 0, the viewer initially sees the leftmost portion of the table box, but because the table's own content is RTL, that leftmost portion is actually the last columns — meaning users have to scroll right just to reach column one. That's a real UX regression for wide RTL tables caused by this change, so I want to flag it, though I should frame it as something to verify rather than a certainty. I'm also checking whether Base UI's ScrollArea components actually honor the dir attribute via its DirectionProvider, since that affects whether the simplest fix is to set dir on the ScrollArea root (letting it cascade to the table) rather than only on the table element itself. Since the fix touches both the ScrollArea wrapper and the table element across different line ranges than the original changed line, I'm deciding how to present this — probably as a small unified diff or just a clear written suggestion rather than an inline diff, since it spans a separate location in the file. For the first comment, I'm anchoring it at line 1575 since that's the actual changed line in the diff, and I'm drafting the explanation: the dir="auto" on the alert container can't actually resolve to RTL because the auto-detection algorithm looks at the first strong-direction character in the element's text, which is always the hard-coded English label rendered before the children content. I'm proposing the fix of adding dir="ltr" to the title paragraph so the auto-direction detection skips over the label and picks up the body's actual direction instead. But now I'm second-guessing the fix itself — if the container resolves to RTL and the title gets dir="ltr", I need to think through how unicode-bidi: plaintext interacts with an explicit dir attribute on that nested element to make sure the alignment actually comes out correct rather than introducing a new mismatch. I think the safest recommendation is just to suggest marking the title dir="ltr" (or wrapping the label in <bdi>) so the container's auto-direction resolution still reaches the body text, while noting that forcing dir="ltr" keeps text-align at left even in an RTL alert, which could look misplaced against a right-side bar—so it's a tradeoff worth flagging rather than a perfect fix. Since this check defaults to failure when there are no findings, and I do have findings, the state should be failure. The first finding is deterministic and clear-cut, while the second is more of a verification point, so I'll present it that way and post the review.

1. modify_pr — Two bidi issues in ChatMarkdown.tsx where the new dir="auto" does not actually produce the direction the CSS rules assume.


Agent Credits: 126 credits