Skip to content

[codex] Add RTL direction detection helpers#3674

Draft
ekrako wants to merge 1 commit into
pingdotgg:mainfrom
ekrako:1771-rtl-bidi-detection
Draft

[codex] Add RTL direction detection helpers#3674
ekrako wants to merge 1 commit into
pingdotgg:mainfrom
ekrako:1771-rtl-bidi-detection

Conversation

@ekrako

@ekrako ekrako commented Jul 3, 2026

Copy link
Copy Markdown

Summary

  • Add shared RTL direction detection helpers for plain text and markdown source.
  • Ignore markdown code spans/fences and URLs when deciding markdown direction.
  • Cover Hebrew/Arabic/Persian, mixed-majority text, neutral text, presentation forms, and CJK false positives.

Part of #1771.

Stack

This PR is the first small slice of the RTL fix stack:

  1. [codex] Add RTL direction detection helpers #3674 - shared RTL direction helpers.
  2. 1771-rtl-markdown-rendering - markdown/list/blockquote rendering, stacked on this branch.
  3. 1771-rtl-composer-message-direction - composer and user-message direction, stacked on the markdown branch.

The follow-up branches are pushed to the fork. They should be opened against upstream after the prior slice lands, because this fork does not have permission to create upstream base branches for true stacked PRs.

Validation

  • pnpm --dir apps/web exec vp test run src/lib/rtl.test.ts --passWithNoTests --project unit
  • pnpm exec vp run typecheck
  • pnpm exec vp check (passes with existing warnings)

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e9498cea-1e94-4a2d-ae93-edaedddc9ba7

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Jul 3, 2026
@ekrako ekrako force-pushed the 1771-rtl-bidi-detection branch from b29c4f8 to 2c2d462 Compare July 3, 2026 13:11
Comment thread apps/web/src/index.css Outdated
</h6>
);
},
ul({ node, children, ...props }) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium components/ChatMarkdown.tsx:1427

The ul, ol, and li component overrides compute dir by calling blockDirForMarkdownNode(text, node, children), but a list node's position spans the entire subtree including nested lists. A top-level English list that contains a long nested Hebrew/Arabic sublist will resolve the outer <ul>/<ol> and parent <li> as dir="rtl", placing the top-level bullets/numbers on the wrong side even though the parent items are LTR. Consider computing the direction from only the node's own direct text rather than the full subtree range.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/ChatMarkdown.tsx around line 1427:

The `ul`, `ol`, and `li` component overrides compute `dir` by calling `blockDirForMarkdownNode(text, node, children)`, but a list node's `position` spans the entire subtree including nested lists. A top-level English list that contains a long nested Hebrew/Arabic sublist will resolve the outer `<ul>`/`<ol>` and parent `<li>` as `dir="rtl"`, placing the top-level bullets/numbers on the wrong side even though the parent items are LTR. Consider computing the direction from only the node's own direct text rather than the full subtree range.

Comment thread apps/web/src/index.css Outdated
Comment thread apps/web/src/components/chat/MessagesTimeline.tsx Outdated
@ekrako ekrako force-pushed the 1771-rtl-bidi-detection branch from bfba339 to e3f7120 Compare July 5, 2026 08:15
},
[createAssetUrl, openPreview, preparedConnection, threadRef],
);
const markdownComponents = useMemo<Components>(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium components/ChatMarkdown.tsx:1424

The dir computation calls blockDirForMarkdownNode(text, node, children), which derives direction from offsets into the raw markdown text. Because rehypeRaw is enabled, raw HTML inside markdown like <blockquote>שלום</blockquote> contributes ASCII tag names (blockquote, kbd, etc.) to the scanned source slice, so dirForMarkdown counts that ASCII content as visible LTR text and the node is assigned dir="ltr" even when the visible content is purely RTL. As a result, raw-HTML RTL blocks render with the wrong text direction. Consider computing direction from the rendered text content (children) rather than the raw markdown source offsets.

Also found in 2 other location(s)

apps/web/src/components/ComposerPromptEditor.tsx:865

composerDirectionText() formats ComposerTerminalContextNode with formatTerminalContextLabel(), which injects the English words line/lines into the direction text. For an RTL-only terminal chip such as { terminalLabel: &#34;מסוף&#34;, lineStart: 1, lineEnd: 1 }, the plugin computes dirFor(&#34;מסוף line 1&#34;), and dirFor falls back to LTR on the 4-vs-4 tie. The visible chip text is actually the compact inline form (@מסוף:1), which is RTL. As a result, a composer containing only RTL terminal-context chips is still rendered LTR, so this change fails to fix RTL direction for that case.

apps/web/src/components/chat/userMessageTerminalContexts.ts:27

buildUserMessageDirectionText() always appends buildInlineTerminalContextText(contexts) to text, even when props.text already contains those inline terminal labels. In the embedded-label path, UserMessageBody renders each label only once, but dirFor(directionText) counts the RTL header characters twice. A mixed message like done @מסוף:1 will therefore be classified as rtl even though the rendered content has an LTR majority/tie, so the whole user message bubble gets the wrong base direction.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/ChatMarkdown.tsx around line 1424:

The `dir` computation calls `blockDirForMarkdownNode(text, node, children)`, which derives direction from offsets into the raw markdown `text`. Because `rehypeRaw` is enabled, raw HTML inside markdown like `<blockquote>שלום</blockquote>` contributes ASCII tag names (`blockquote`, `kbd`, etc.) to the scanned source slice, so `dirForMarkdown` counts that ASCII content as visible LTR text and the node is assigned `dir="ltr"` even when the visible content is purely RTL. As a result, raw-HTML RTL blocks render with the wrong text direction. Consider computing direction from the rendered text content (`children`) rather than the raw markdown source offsets.

Also found in 2 other location(s):
- apps/web/src/components/ComposerPromptEditor.tsx:865 -- `composerDirectionText()` formats `ComposerTerminalContextNode` with `formatTerminalContextLabel()`, which injects the English words `line`/`lines` into the direction text. For an RTL-only terminal chip such as `{ terminalLabel: "מסוף", lineStart: 1, lineEnd: 1 }`, the plugin computes `dirFor("מסוף line 1")`, and `dirFor` falls back to LTR on the 4-vs-4 tie. The visible chip text is actually the compact inline form (`@מסוף:1`), which is RTL. As a result, a composer containing only RTL terminal-context chips is still rendered LTR, so this change fails to fix RTL direction for that case.
- apps/web/src/components/chat/userMessageTerminalContexts.ts:27 -- `buildUserMessageDirectionText()` always appends `buildInlineTerminalContextText(contexts)` to `text`, even when `props.text` already contains those inline terminal labels. In the embedded-label path, `UserMessageBody` renders each label only once, but `dirFor(directionText)` counts the RTL header characters twice. A mixed message like ``done @מסוף:1`` will therefore be classified as `rtl` even though the rendered content has an LTR majority/tie, so the whole user message bubble gets the wrong base direction.

@ekrako ekrako force-pushed the 1771-rtl-bidi-detection branch from e3f7120 to 58fe92c Compare July 5, 2026 08:24
@ekrako ekrako changed the title [codex] Fix RTL chat message rendering [codex] Add RTL direction detection helpers Jul 5, 2026
@github-actions github-actions Bot added size:M 30-99 changed lines (additions + deletions). and removed size:L 100-499 changed lines (additions + deletions). labels Jul 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 30-99 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant