diff --git a/.changeset/rtl-rendered-markdown.md b/.changeset/rtl-rendered-markdown.md new file mode 100644 index 00000000..0b73d74b --- /dev/null +++ b/.changeset/rtl-rendered-markdown.md @@ -0,0 +1,5 @@ +--- +'aicodeman': patch +--- + +Persian, Arabic and Hebrew now read right to left in the Response Viewer and in the File Viewer's markdown view (#715). Each paragraph, heading, list, quote and table takes its direction from its own first letter, so a right-to-left answer is right-aligned with its punctuation, list bullets and quote bar on the right, while an English paragraph beside it stays left to right. Code blocks, inline code and file paths stay left to right and no longer decide a sentence's direction, so a Persian sentence that starts with `npm install` still reads right to left. The phone's Compose dialog lays a right-to-left prompt out the same way. The terminal pane is unchanged: xterm.js has no right-to-left support yet (xtermjs/xterm.js#701). diff --git a/src/web/public/app.js b/src/web/public/app.js index db655131..7a3e52bd 100644 --- a/src/web/public/app.js +++ b/src/web/public/app.js @@ -2835,6 +2835,29 @@ class CodemanApp { a.setAttribute('target', '_blank'); a.setAttribute('rel', 'noopener noreferrer'); }); + // Right-to-left text (issue #715). With no `dir`, every block was laid + // out left to right, so a Persian, Arabic or Hebrew answer was + // left-aligned, its punctuation landed on the wrong end and its lists + // and quotes hung off the left edge. Each OUTERMOST prose block now + // takes its direction from its own first letter (`dir="auto"`, what + // GitHub does for rendered markdown), so an RTL paragraph beside an + // English one reads correctly in both; nested blocks inherit, which + // keeps a list's markers and indent on one side. A `dir` the agent + // wrote (the sanitizer allows it) is kept, and so is what it covers. + // + // Code and preformatted text are always left to right. `dir="ltr"` + // also isolates them, and `dir="auto"` skips any descendant that + // carries a `dir`, so `npm install` at the head of a Persian sentence + // no longer turns the whole sentence left to right. + tmpl.content + .querySelectorAll('p, h1, h2, h3, h4, h5, h6, ul, ol, dl, blockquote, .rv-table-wrap') + .forEach((el) => { + if (el.parentElement?.closest('[dir]') || el.hasAttribute('dir')) return; + el.setAttribute('dir', 'auto'); + }); + tmpl.content.querySelectorAll('pre, code').forEach((el) => { + if (!el.hasAttribute('dir')) el.setAttribute('dir', 'ltr'); + }); return tmpl.innerHTML; } catch { /* fall through */ } } @@ -3064,6 +3087,9 @@ class CodemanApp { link.dataset.path = path; link.dataset.linkified = '1'; if (folder) link.dataset.folder = '1'; + // A path reads left to right, and in a right-to-left sentence it must + // not vote on the sentence's direction (`_renderMarkdown`, issue #715). + link.dir = 'ltr'; link.title = path; link.textContent = path; frag.appendChild(link); diff --git a/src/web/public/keyboard-accessory.js b/src/web/public/keyboard-accessory.js index 7ae3213c..abfcf8f1 100644 --- a/src/web/public/keyboard-accessory.js +++ b/src/web/public/keyboard-accessory.js @@ -1296,7 +1296,7 @@ const KeyboardAccessoryBar = { Compose prompt Enter adds a new line - +
@@ -1309,6 +1309,8 @@ const KeyboardAccessoryBar = { this._composerOverlay = overlay; const textarea = overlay.querySelector('.prompt-composer-textarea'); + // The textarea's dir="auto" lays a prompt written in Persian, Arabic or + // Hebrew out right to left, from its first letter (#715). // i18n.js skips