Repository navigation
Description
Summary
In the desktop/web app, bidirectional text inside nested Markdown blocks (<li>, <td>, <th>, and <p> inside <blockquote>) is laid out LTR when the surrounding message resolves to LTR, which scrambles Persian/Arabic/Hebrew content. Only direct children of the Markdown container get unicode-bidi: plaintext, so nested blocks fall back to the container's dir="auto" direction.
Environment
- opencode version: 2.0.22 (desktop app,
@opencodedesktop, renderer bundle[email protected]) - OS: Windows_NT 10.0.26300 (win32 x64)
- Terminal: Unavailable: not applicable — this is the Electron desktop app, not a TUI session
- Shell: C:\WINDOWS\system32\cmd.exe
- Install/channel: release (desktop installer)
- Active plugins: none found in config
Reproduction
-
Use the desktop app with an RTL language for assistant output (e.g. Persian).
-
Ask a question where the assistant reply begins with an English heading or code and then contains RTL body content, for example:
## Project setup Run `npm install` first, then: - گام اول: دانلود ابزار از سایت رسمی - گام دوم: اجرای دستور نصب و بررسی نسخه > توجه: این مرحله را حتماً قبل از اجرا انجام دهید. | نام | مقدار | |-----|-------| | نسخه | 2.0.22 |
-
Inspect the rendered result, or measure the computed styles of the nested elements.
Expected Behavior
<li>, <td>, <th>, and <blockquote> > p containing RTL text should lay out right-to-left, matching how a top-level RTL paragraph in the same message is laid out.
Actual Behavior
They lay out left-to-right, because they inherit the container's dir="auto"-resolved direction instead of resolving from their own first strong character.
Measured with the shipped CSS (assets/main-40jLuXnQ.css) and the real Markdown DOM (<div data-component=markdown dir=auto>), for a container that resolved to LTR:
| Element | unicode-bidi |
computed direction |
visual start |
|---|---|---|---|
<p> (direct child) |
plaintext |
ltr | right ✅ |
<li> (Persian) |
isolate |
ltr | left ❌ |
<blockquote> > p (Persian) |
isolate |
ltr | left ❌ |
<th> / <td> (Persian) |
isolate |
ltr | left ❌ |
<code> |
isolate |
ltr | left ✅ (correct) |
Additional Context
Root cause. The Markdown component renders <div data-component=markdown dir=auto> and the stylesheet contains:
[data-component=markdown]>* { unicode-bidi: plaintext }The * only matches direct children. Descendants such as li, td, th, and p inside blockquote keep the UA default unicode-bidi: isolate (verified on bare elements in Chromium) and inherit direction from the container. unicode-bidi: plaintext on an ancestor does not change its computed direction, so nested blocks never inherit an RTL direction — they only inherit whatever dir="auto" derived from the message's first strong character.
Because dir="auto" is evaluated per message, any reply whose first strong character is Latin (very common: headings such as ## Project setup, or code such as npm install) makes the whole container LTR, and every nested RTL block inside is scrambled. Replies that begin with an RTL character render correctly, which makes the bug look intermittent.
Suggested fix. Extend plaintext to nested block-level elements while leaving code isolated:
[data-component=markdown] :where(ul,ol,li,menu,dl,dt,dd,table,thead,tbody,tfoot,tr,td,th,blockquote,figure,figcaption,p,h1,h2,h3,h4,h5,h6,div){unicode-bidi:plaintext}This was verified against the shipped stylesheet: li / td / th / blockquote > p flip to correct RTL alignment, English-only blocks flip to correct LTR alignment, and :not(pre)>code and [data-component=markdown-code] keep direction:ltr; unicode-bidi:isolate because their rules have higher specificity.
Workarounds tried, neither helps.
- Setting the app language to Persian: this sets
document.documentElement.dir = "rtl", butdir="auto"on each Markdown container re-derives LTR per message, so nested RTL blocks stay scrambled. Only the surrounding UI chrome becomes RTL. - Forcing layout direction from the diagnostics bar (
setDirection("rtl")): same result; it only writesdocument.documentElement.dir, and it is not persisted across restarts (ma({direction: void 0})is an in-memory store).
Activity
opencode-agent commentedon Oct 11, 2026
Thanks for the detailed report. I was able to reproduce this.
I loaded the shipped
markdown.cssfrom v2.0.22 into headless Chromium and rendered the same<div data-component="markdown" dir="auto">markup the Markdown component produces, using your example: an English heading and inline code first, then Persian text in a paragraph, a list, a blockquote and a table. I didn't run the packaged desktop app itself.<p>hasunicode-bidi: plaintextand lays out right-to-left.<li>,<blockquote> > p,<th>and<td>keepunicode-bidi: isolate, inheritdirection: ltrfrom the container and lay out left-to-right.The result is the same on the latest
dev(e31136bd): the stylesheet still appliesplaintextonly to direct children. With the nested-block rule you suggested added, all of those blocks lay out right-to-left.The script below reproduces it, and the screenshot shows the result on v2.0.22.
Reproduction script (repro.ts)
Run it:
bun repro.tsargszero commentedon Oct 11, 2026
I'd like to work on this.
Confirmed the cause on the current
dev: the rule inpackages/session-ui/src/components/markdown.cssisso only the container's direct children resolve a direction from their own first strong character.
<li>,<td>,<th>and<p>inside<blockquote>keep the UA defaultunicode-bidi: isolateand inherit whatever the container'sdir="auto"resolved to, which is why the scramble only shows up when the reply starts with Latin text (a heading or inline code).Plan: extend the
plaintextrule to nested block-level elements, keeping code blocks ([data-component="markdown-code"],:not(pre) > code) and Mermaid diagrams explicitly isolated and LTR, and cover it with a component test that measures the computedunicode-bidion those nested elements so it fails on the unmodified stylesheet.argszero commentedon Oct 11, 2026
Opened #54636 for this.
markdown.cssextendedunicode-bidi: plaintextonly to the direct children of the markdown container ([data-component="markdown"] > *), andunicode-bididoes not inherit, so nested blocks (li,td,th, apinside ablockquote) kept the browser default and inherited the container'sdir="auto"direction. The PR extends the plaintext rule to the set the UA stylesheet isolates, in a low-specificity:where()list, and leavesdivout so code blocks and Mermaid diagrams keepdirection: ltr; unicode-bidi: isolate.component-tests/markdown.spec.tscovers the nested case and the code-block exclusion.