feat(desktop): per-message automatic RTL/bidi text direction#44065
feat(desktop): per-message automatic RTL/bidi text direction#44065Adolanium wants to merge 1 commit into
Conversation
|
Linking #44150 here too, since it was filed shortly after this PR was opened and requests exactly this behavior. Since alt-glitch noted on #44169 that a maintainer should pick one of the two desktop RTL approaches, here is a factual comparison to make that easier. Both PRs rely on first-strong-character direction detection; the difference is the mechanism. This PR resolves a
Whichever approach gets picked, glad to converge the remaining edge cases from the other one. |
8037b08 to
12a7271
Compare
Resolve each message block's direction from its prose (code spans and math do not vote, blocks with no prose fall back to dir="auto") and right-align RTL blocks via text-align:start. Inline code and KaTeX output inside resolved blocks render as isolated LTR runs so their neutrals keep their order in RTL sentences. Both composers carry dir="auto" and flip as you type. Code blocks stay LTR and LTR content renders unchanged.
12a7271 to
6ae2859
Compare
|
Superseded by #44596, which unifies this with #44169 into a CSS-only change. I verified in Chromium that |
|
Superseded by #44596 (unified CSS-only RTL/bidi approach). Closing per the superseding PR. |
Arabic/Hebrew/Persian/Urdu chat text rendered left-to-right and left-aligned, and mixed RTL/English technical messages (the common case) read backwards. Resolve each chat block's base direction from its own first strong character (UAX#9) with pure CSS, scoped to the chat surfaces only: - `unicode-bidi: plaintext` + `text-align: start` on assistant prose blocks (p, h1-h6, li, blockquote), the user bubble's text lines, and both composers (main + edit share the composer-rich-input slot). RTL blocks read and right-align RTL; English stays LTR; mixed conversations resolve per block. `text-align: start` is required because the user bubble hardcodes `text-left`. - Inline `code` and KaTeX are pinned `direction: ltr; unicode-bidi: isolate`, so the bidi first-strong heuristic skips them: a sentence that *starts* with a command (`./run.sh ...`) followed by Arabic still resolves RTL, and the command's own neutrals keep their order. - Fenced code surfaces (code-card, user fences) are pinned LTR so they never mirror or right-align inside an RTL list item or blockquote. `direction` is never forced, so app chrome, layout, and list indent stay LTR per the issue's request not to flip the whole UI. English-only content is byte-for-byte unchanged. Salvaged and unified from NousResearch#44065 and NousResearch#44169; verified in Chromium that isolate removes inline code from the paragraph direction vote (the code-first case), making the JS dir-resolution in NousResearch#44065 unnecessary. Fixes NousResearch#44150 Co-authored-by: Adolanium <Adolanium@users.noreply.github.com> Co-authored-by: Adalsteinn Helgason <AIalliAI@users.noreply.github.com>
What does this PR do?
Adds automatic bidirectional text support to the desktop app. Hebrew, Arabic and other RTL-script messages currently render left-aligned with mangled punctuation order, because nothing in the renderer sets a text direction: the user bubble hardcodes
text-left(thread.tsx,USER_BUBBLE_BASE_CLASS), the markdown surface inherits LTR, and the composer is a plain LTR contenteditable.The fix resolves direction per block with the first-strong-character heuristic, applied to the block's prose:
p,h1-h4,li,ul/ol,blockquote) and every user message text segment gets adirattribute resolved from its own text, so mixed Hebrew/English answers render each paragraph correctly./run.sh ...followed by an Arabic/Hebrew explanation), and plain first-strong would flip that whole block to LTR; blocks with no prose at all fall back todir="auto"codeand KaTeX output inside resolved blocks are pinned to an isolated LTR run, so their neutrals (dots, slashes, dashes) keep their order inside RTL sentences; fenced code blocks keep LTR entirelydir="auto"and re-resolve on every input, so the box flips as you typetext-startaccompanies thedirattribute where an ancestor pinstext-align(the user bubble'stext-left), so RTL blocks actually right-alignborder-l-2/pl-3to the logicalborder-s-2/ps-3, and list markers already follow direction because Tailwind Typography usespadding-inline-startLTR content resolves to LTR, so English-only users see no change. No new dependencies, no settings, no locale work. App chrome stays LTR.
Related Issue
Fixes #44150 (filed shortly after this PR was opened; requests exactly this behavior).
Related work on other surfaces, no overlap with this PR:
Type of Change
Changes Made
apps/desktop/src/lib/text-direction.ts: first-strong direction detection over a caller-chosen slice of textapps/desktop/src/components/assistant-ui/markdown-text.tsx: prose block component overrides resolvedirfrom their prose (code spans and math excluded) withtext-start; logical border/padding on blockquoteapps/desktop/src/components/assistant-ui/user-message-text.tsx: same per text segment, from the segment's non-code text; fences untouched so code stays LTRapps/desktop/src/components/assistant-ui/thread.tsx:dir="auto"+text-starton the inline edit composer's contenteditableapps/desktop/src/app/chat/composer/index.tsx:dir="auto"on the main composer contenteditableapps/desktop/src/components/assistant-ui/message-direction.test.tsx: pins the contract for user and assistant messages: prose blocks carry the resolved direction, code-first blocks follow their prose, code blocks never carry oneapps/desktop/src/styles.css: inlinecodeand KaTeX output inside direction-resolved blocks are pinned to an isolated LTR run, so their neutrals (dots, slashes, dashes) keep their order inside RTL sentences; fenced code cards are pinned LTR so their chrome and code lines don't mirror when they sit inside an RTL list itemHow to Test
cd apps/desktop && npx vitest run --environment jsdom src/components/assistant-ui/message-direction.test.tsx- 4 passednpm run typecheck- cleannpx eslinton the touched files - clean (repo-widenpm run linthas pre-existing findings on main, none in these files)מה שלומך?- the bubble right-aligns; ask for a Hebrew answer with a list and a code block - paragraphs, headings and bullets right-align with markers on the right, the code block stays LTR`./scripts/run.sh -v` מה הפקודה הזאת עושה?- the block still right-aligns (code doesn't vote on direction) and the chip keeps its internal orderChecklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests pass (renderer-only change; desktop vitest suite run instead, failures on main are unchanged)npm run dev)Documentation & Housekeeping
docs/, docstrings) — or N/Acli-config.yaml.exampleif I added/changed config keys — or N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — or N/AScreenshots / Logs
Before / after on the same conversation (Hebrew answer with heading, list and code block; English follow-up unchanged):
Before:

After:

Composer auto-directing while typing Hebrew:

A Hebrew paragraph that starts with code: plain first-strong would resolve it LTR (before); resolved from prose it follows the sentence, and the chips keep their internal order (after):
Before:

After:
