Conversation
…n list PR nesquena#6560 added _applyAutomaticMessageDirections() to resolve RTL/LTR bidi for chat messages, but two surfaces outside .msg-body were missed: 1. The thinking-card body (_thinkingCardHtml in static/ui.js) renders reasoning text in a plain <pre> for whitespace preservation. Without dir="auto" on that element, the browser inherits the chat's base direction instead of resolving per-paragraph bidi for Hebrew/Arabic reasoning text. 2. The sidebar session list (.session-title / .session-preview in static/style.css) is rendered by sessions.js, a separate code path that never received any dir attribute or bidi-aware CSS rule, so Hebrew/Arabic session titles and previews always rendered left-aligned with no bidi resolution. Fixes both by: - adding dir="auto" to the thinking-card <pre>, plus a .chat-content-rtl .thinking-card-body pre[dir="auto"] rule so the visual alignment follows the resolved direction - adding a .chat-content-rtl .session-title, .session-preview rule using unicode-bidi:plaintext (the CSS equivalent of dir="auto") with text-align:right as the RTL-skin default Includes regression tests pinning both fixes so they cannot silently regress if _thinkingCardHtml or the sidebar selectors are touched later. Related: nesquena#6560
|
| Filename | Overview |
|---|---|
| static/style.css | Adds RTL rules for thinking cards and the actual session-list classes, while explicitly isolating Markdown tables as LTR. |
| static/ui.js | Adds automatic direction processing across rendered message prose, preserves thinking-card auto direction, and classifies table structures as machine-oriented LTR content. |
| tests/test_issue6560_mixed_direction_rendering.py | Extends regression coverage to verify table structural elements remain LTR after automatic direction processing. |
| tests/test_sessions_thinking_rtl_bidi.py | Pins the thinking-card attribute and the renderer-backed session title, metadata, and search-preview selectors. |
Reviews (5): Last reviewed commit: "Merge branch 'master' into fix/rtl-bidi-..." | Re-trigger Greptile
SummaryI read the four-file patch at head Code referenceThe sidebar renderer creates The second mismatch is inside const blockSelector="p,li,blockquote,h1,h2,h3,h4,h5,h6,ul,ol,table,thead,tbody,tfoot,tr,th,td";
for(const block of blocks) block.setAttribute("dir","auto");
const machineSelector=[
"pre","code","kbd","samp","tt",".hljs",".code-block",
".diff-block",".csv-table-wrap",".csv-table"
].join(",");Plain Diagnosis / recommendationPlease change the sidebar selector to the real preview class or classes, most likely For tables, either remove Verification stepA focused DOM fixture should render one title, one search preview, and one ordinary Markdown table under |
…bles Review feedback on PR nesquena#7135 caught two real bugs: 1. static/sessions.js never creates a .session-preview element (it creates .session-title, .session-meta, .session-search-preview instead), so the sidebar CSS rule from the prior commit reached the title but never the rendered preview/metadata text. Fixed the selector to target the classes the renderer actually emits, and added a test that reads the classNames sessions.js assigns so a future rename can't cause the same silent mismatch again. 2. Plain Markdown table/thead/tbody/tfoot/tr/th/td were added to blockSelector (per-element dir="auto", prose treatment) but the only forced-LTR CSS/JS coverage was for .csv-table, which ordinary Markdown tables never carry -- so a real Markdown table's direction was left to whatever dir="auto" resolved to, i.e. content-dependent, exactly the opposite of the documented "tables must stay LTR" contract. Moved these tags out of blockSelector into machineSelector (forced dir="ltr", same treatment as code/csv), and added a CSS rule forcing .chat-content-rtl .msg-body table (and descendants) to direction:ltr. Replaced the CSS-substring test for the mixed-direction helper with one that actually executes _applyAutomaticMessageDirections in a minimal Node DOM shim against a real table/paragraph tree and asserts the resulting dir attributes -- a substring check can't catch a selector/tag ending up in the wrong bucket, only running the helper against real markup can. uv run pytest tests/test_sessions_thinking_rtl_bidi.py tests/test_issue6560_mixed_direction_rendering.py -v -> 12 passed uv run pytest tests/ -q -k 'rtl or bidi or direction or session_list or thinking or 6560' -> 261 passed, 6 skipped (pre-existing, unrelated)
|
Thanks for the review — both issues were real, fixed in d7673d0:
Also replaced the CSS-substring test for the mixed-direction helper with one that actually executes Verification: |
nesquena-hermes
left a comment
There was a problem hiding this comment.
Gate: no XSS and the approach is right, but an existing postprocessor overwrites dir="auto" back to ltr — so the fix doesn't take effect yet
Thanks @zagee1 — the direction and security are sound (the dir="auto" value is a literal, and a script/image injection payload stays escaped through the unchanged esc(clean) path — no new XSS sink). But reproducing it in Chromium shows the feature doesn't actually reach the rendered DOM, plus one surface is missed:
SILENT 1 — the machine-classification pass rewrites the thinking <pre> back to dir="ltr" (static/ui.js:1556/1566)
Right after your dir="auto" is applied, the machine-content selector at line 1556 includes 'pre', and the loop at 1566 does:
node.setAttribute('dir','ltr');
node.classList.add('message-machine-ltr');_thinkingCardHtml()'s <pre dir="auto"> matches that pre selector, so it's forced to dir="ltr" (class message-machine-ltr) — I confirmed in Chromium the element ends up dir="ltr", not auto. This both defeats the new mixed-direction behavior AND regresses the prior RTL inheritance. Fix: exclude .thinking-card-body pre from the machine-classification selector, and add a final-DOM assertion (after _applyAutomaticMessageDirections()) that the thinking <pre> is still dir="auto".
SILENT 2 — the live/legacy thinking path still emits a bare <pre> (static/ui.js:20120)
appendThinking() reaches the _thinkingMarkup() constructor via _renderThinkingInto(), which emits <pre> with no dir="auto" — so live-streaming Hebrew/Arabic reasoning has no automatic direction (and would be forced LTR by the same postprocessor). Fix: add dir="auto" to the live/legacy _thinkingMarkup() <pre> too, so both the cached-render and live paths behave identically.
Verified working (baseline)
- No new XSS: injection payload stayed escaped through
esc(clean). - Before the postprocessor runs, English
dir="auto"has identical LTR geometry to baseline; Hebrew/English/code lines resolve independently — so the approach is correct, it's just being overwritten. - 21 bidi/RTL + 116 neighboring thinking/activity tests pass — but they don't assert the FINAL thinking-card DOM after postprocessing, which is why the overwrite slipped through.
Add a test that checks the thinking <pre> is still dir="auto" AFTER the machine-classification pass (both the cached-render and live constructors), plus an escaping-preservation assertion, and this should be straightforward to land. The concept is good and needed — it just needs to survive the postprocessor.
…dir=auto Review on PR nesquena#7135 (Chromium repro) caught that _applyAutomaticMessageDirections's machine-classification pass runs AFTER the dir="auto" fix and matches plain 'pre' unconditionally, silently forcing the thinking-card <pre> back to dir="ltr" (class message-machine-ltr) right after dir="auto" was applied -- so the feature never reached the rendered DOM despite the source-level fix being correct and non-XSS-introducing. Also caught: the live/legacy streaming path (_thinkingMarkup(), used while a response is still streaming) never got dir="auto" added at all -- only the cached/history render path (_thinkingCardHtml()) had it, so streaming Hebrew/Arabic reasoning had no bidi resolution regardless of the postprocessor bug. Fixes: - _applyAutomaticMessageDirections: skip nodes inside .thinking-card-body in the machine-classification loop (node.closest('.thinking-card-body')), mirroring how .csv-table is excluded from prose reprocessing. - _thinkingMarkup(): add dir="auto" to its <pre>, matching _thinkingCardHtml(). Added TestThinkingCardFinalDom: runs the REAL constructors (_thinkingCardHtml/_thinkingMarkup) AND the REAL _applyAutomaticMessageDirections in a Node DOM shim, then asserts the FINAL dir attribute on the resulting <pre> -- this is what the prior tests missed, since they only checked constructor source for the attribute and never exercised the full pipeline where the overwrite happened. Also asserts script/image injection payloads stay escaped through the unchanged esc() path (no new XSS sink). uv run pytest tests/test_sessions_thinking_rtl_bidi.py tests/test_issue6560_mixed_direction_rendering.py -v -> 17 passed uv run pytest tests/ -q -k 'rtl or bidi or direction or session_list or thinking or 6560' -> 266 passed, 6 skipped (pre-existing, unrelated)
|
Thanks for reproducing in Chromium and pinpointing exactly where — this was the real bug. Fixed in dc7ae1f: SILENT 1 — confirmed: SILENT 2 — confirmed: Added This should now actually reach the rendered DOM in Chromium too — happy to hear if you want to re-verify. |
nesquena-hermes
left a comment
There was a problem hiding this comment.
Re-gate: both cycle-10 findings are fixed and the RTL rendering is correct — but the new renderMessages call breaks an existing test (real regression, full suite caught it)
Great progress @zagee1 — both prior findings are genuinely resolved (Codex + a live Chromium capture confirm it):
- The thinking-card
<pre>now retainsdir="auto"after the machine-classification pass (the.thinking-card-bodyexclusion works), and both_thinkingCardHtmland the live_thinkingMarkupemitdir="auto". - I rendered a mixed Hebrew/Arabic/English/code thinking card on a live server: Hebrew and Arabic lines resolve RTL (right-aligned), English and code lines stay LTR, each line per-line correct — exactly the intended bidi. No XSS (payloads produced zero executable nodes).
But the full suite caught a real regression that the RTL-focused checks didn't:
tests/test_anchor_fallback_ownership.py::test_render_messages_keeps_anchor_owned_turn_out_of_legacy_activity_rebuilds — FAILS on this branch, PASSES on clean master
Root cause (I isolated it — reproduces standalone on your branch, green on origin/master same box):
ReferenceError: _applyAutomaticMessageDirections is not defined
at renderMessages (…ui.js:1617) ← your new bare call `_applyAutomaticMessageDirections(inner);`
That test extracts renderMessages and runs it in isolated Node. Your new call site inside renderMessages (static/ui.js:18190, _applyAutomaticMessageDirections(inner);) is unguarded, so when the harness runs the extracted function without the helper in scope, it throws. Note the sibling line right above it already uses the safe pattern:
if(typeof _syncLiveRunStatusAfterRender==='function') _syncLiveRunStatusAfterRender();
_applyAutomaticMessageDirections(inner); // ← this one is bareAnd you already guarded your OTHER call elsewhere with if(window._applyAutomaticMessageDirections) — this call site (and the one at ui.js:19425) just missed the same treatment.
Fix (either, prefer the guard for consistency with the sibling)
- Guard the two call sites the way the sibling call is guarded:
at
if(typeof _applyAutomaticMessageDirections==='function') _applyAutomaticMessageDirections(inner);
static/ui.js:18190and:19425. This matches the existingrenderMessagesidiom and keeps the extraction harness green. - Or update
test_anchor_fallback_ownership.py's function-extraction to include the_applyAutomaticMessageDirectionshelper (heavier, and doesn't help any other harness that extractsrenderMessages).
The guard (option 1) is the smaller, safer change and mirrors what's already there. Once the two call sites are guarded and the full suite is green, this is ready — the feature itself is correct and needed. Thanks for the persistence.
…ion in isolated render tests Ensures renderMessages and postProcessRenderedMessages don't throw ReferenceError when extracted into isolated test harnesses (e.g. test_anchor_fallback_ownership.py) that may not include the helper in scope. Mirrors the existing guard pattern used by sibling calls like _syncLiveRunStatusAfterRender. Verified: tests/test_anchor_fallback_ownership.py now passes.
SummaryI re-read the four-file head at Code referenceThe complete latest delta is the two guards at if(typeof _applyAutomaticMessageDirections==='function')
_applyAutomaticMessageDirections(inner);
if(typeof _applyAutomaticMessageDirections==='function')
_applyAutomaticMessageDirections(container);That matches the defensive pattern already used immediately above the render call for I also checked the original behavior chain remains intact: Diagnosis / recommendationThe previous failure was a harness-scope regression, not a browser RTL defect. Guarding the optional postprocessor is the smaller and safer correction because Verification stepThe required check is the previously failing anchor fallback test plus the two bidi files. The focused direction tests at VerdictThe blocker from my prior review is resolved on static inspection. With the focused CI checks green, this is ready to merge from my side. |
|
Thanks @zagee1 — this is careful, well-commented work, and the core approach is right: the adversarial gate confirmed the thinking-card carve-out works in the real DOM (Hebrew reasoning gets 1. Live-streamed messages miss direction handling until the final rerender (
|
Summary
Extends the automatic RTL/bidi handling from #6560 to two surfaces that were missed because they render outside
.msg-body:_thinkingCardHtmlinstatic/ui.js) — reasoning text renders in a plain<pre>for whitespace preservation. Withoutdir="auto", the browser inherits the chat's base direction instead of resolving per-paragraph bidi for Hebrew/Arabic reasoning text, so it never right-aligns or reorders correctly..session-title/.session-previewinstatic/style.css) — rendered bysessions.js, a completely separate code path from the chat message pipeline that_applyAutomaticMessageDirections()walks. It never got anydirattribute or bidi-aware CSS, so Hebrew/Arabic session titles/previews always render left-aligned.Changes
static/ui.js: adddir="auto"to the thinking-card<pre>.static/style.css:.chat-content-rtl .thinking-card-body pre[dir="auto"]{ text-align:right; }so the visual alignment follows the resolved direction..chat-content-rtl .session-title, .chat-content-rtl .session-preview{ direction:rtl; unicode-bidi:plaintext; text-align:right; }—unicode-bidi:plaintextis the CSS equivalent ofdir="auto"(resolves base direction per-element from its first strong character);text-align:rightmatches the RTL-skin default used elsewhere for.msg-body.Testing
Added
tests/test_sessions_thinking_rtl_bidi.pypinning both fixes at the source level (asserts thedir="auto"attribute and the specific CSS rules exist), so a future refactor of_thinkingCardHtmlor the sidebar selectors can't silently regress this.No build step involved — plain CSS/JS edits per the project's no-bundler architecture.
Related: #6560 (this PR only extends its bidi coverage to two surfaces outside its scope; does not modify anything from that PR).