# dsh-hebrew-rtl English | [עברית](./README.he.md) Correct Hebrew RTL rendering for the [DeepSeek Harness](https://github.com/deepseek-harness/harness) web GUI: per-block direction by dominant script, bidi-safe input fields, and RTL-aware `Cmd`+`←`/`→` line navigation. ## The problem The GUI renders every block left-to-right. For Hebrew that produces three distinct failures: 1. **Whole paragraphs read backwards.** A Hebrew sentence is laid out as an LTR paragraph, so its lines start on the wrong side and punctuation lands at the wrong end. 2. **Typing into a field runs the wrong way.** The `ask_user_question` custom-answer field inherits the page direction, so Hebrew typed into it flows left-to-right. 3. **`Cmd`+`←`/`→` feel inverted.** These shortcuts are *logical* in every browser: `←` goes to the line's logical start, which on an RTL line sits at the visual **right** edge. Pressing `←` jumps the caret to the far right. The obvious CSS answer — `unicode-bidi: plaintext` — fixes only the easy half. It picks a block's direction from its **first strong directional character**, so a Hebrew sentence that opens with a Latin product name, a list number, or an emoji is still rendered LTR and still reads garbled. ## What this plugin does ### Per-block direction by dominant script For each block element (`p`, `li`, `h1`–`h6`, `blockquote`, `table`) it drops code-like tokens from the block's text, then counts Hebrew-majority **words** against Latin-majority words in what remains: | Content | Result | | --- | --- | | No Hebrew prose | No override — `unicode-bidi: plaintext` stays in charge | | Hebrew words > Latin words | `direction: rtl` + `text-align: right` | | Latin words > Hebrew words | `direction: ltr` + `text-align: left` | | Equal (both present) | No override — falls back to first-strong | Forced blocks get `unicode-bidi: isolate`, so the Unicode Bidi algorithm still lays out the minority-script runs correctly **inside** the chosen paragraph direction: ``` היום בדקנו את הפלאגין החדש עם DSH והכול עבד מצוין. → RTL, and "DSH" stays in place, unreversed This is a test sentence with שלום in the middle. → LTR, and "שלום" stays in place, mid-sentence ``` Dominance is the rule because both simpler alternatives fail in an obvious way. First-strong alone mis-renders any Hebrew paragraph that does not *begin* in Hebrew. "Any Hebrew character forces RTL" over-corrects in the other direction: an English sentence carrying one Hebrew word flips to RTL and its English runs come out reversed. **Why words, and why code tokens are excluded.** Counting raw letters breaks on technical Hebrew prose. A single identifier can outweigh a whole paragraph — `git+https://git@github.com:kfirsch/...#` contributes 44 Latin letters by itself, and a commit sha another 40 — so a Hebrew sentence that merely *cites* a URL was rendered LTR. Identifiers, URLs, paths, shas and `15/15`-style ratios are therefore stripped before counting; they are still laid out normally, they just no longer vote on the paragraph's direction. Counting whole words rather than letters follows from the same reasoning: a three-letter Hebrew word says as much about the sentence's language as `credential` does. The stripping is deliberately conservative — an ordinary Latin word standing alone is never treated as code, so genuine English prose still counts in full. The heuristic is not infallible on a block that is genuinely half-and-half after stripping (a short line of mostly commit hashes with two Hebrew words, say). Those fall back to first-strong rather than guessing. **A table is one unit, not a grid of independent cells.** Judging each `td` separately gave a six-row status table three different verdicts — Hebrew label cells went RTL while the `HEAD` and `Commits` cells beside them stayed LTR — so the label column changed edges from row to row and the eye had no single alignment to follow. Column *order* is also a property of the table rather than the cell, so per-cell directions end up fighting the column order itself. The direction is therefore decided once, from the table's whole text, and every cell inherits it; `unicode-bidi: isolate` still lays out each cell's own content correctly within that direction. Blocks are re-evaluated through a `MutationObserver` as text streams in, coalesced with `requestAnimationFrame` so streaming does not trigger a scan per character. ### Bidi-safe input fields The composer and the `ask_user_question` custom-answer field are each a **two-layer stack**: a visible or measuring mirror layer plus a real `