## What and why CodeNomad had no RTL (right-to-left) support, so users writing in Hebrew or Arabic would see their messages displayed left-to-right — misaligned text, broken reading flow, wrong punctuation placement. This PR adds automatic direction detection to all elements that display user or model text. The browser detects direction from the first strong character in each text block: Hebrew/Arabic → RTL, Latin/code → LTR. No configuration needed — it just works per message, per paragraph. ## Technical notes The natural fix is `dir="auto"` on the containing elements. However, Chromium does not propagate direction detection from a parent `<div>` into its `<p>` children — so Hebrew inside `<p>` rendered via `innerHTML` (as markdown is) was still detected as LTR. The fix is to apply `unicode-bidi: plaintext` via CSS directly on the block-level elements (`p`, `li`, headings, etc.), which has the same auto-detection semantics but applies per element. ## Summary - Add `dir="auto"` to all elements containing user-generated or model-generated text (message content, prompt input, session names, tool outputs) so the browser auto-detects text direction - Add `unicode-bidi: plaintext` via CSS to markdown block elements (`p`, `li`, headings, `blockquote`, `td`/`th`) to fix per-paragraph RTL detection in Chromium (where `dir="auto"` on a parent div does not recurse into block children) - Convert physical CSS properties to logical equivalents in `markdown.css`: `border-left` → `border-inline-start`, `padding-left` → `padding-inline-start`, `text-align: left` → `text-align: start`, `margin-left` → `margin-inline-start` ## Affected components - `markdown.tsx` — main markdown renderer - `message-part.tsx` — text part wrapper and plain-text fallback - `message-item.tsx` — message body and error blocks - `prompt-input.tsx` — user input textarea - `session-list.tsx` — session titles in sidebar - `session-rename-dialog.tsx` — session rename input - `instance-welcome-view.tsx` — Resume Session dialog - `tool-call/markdown-render.tsx` — tool output markdown fallback - `tool-call/ansi-render.tsx` — ANSI output - `tool-call/diagnostics-section.tsx` — diagnostic messages ## Test plan - [ ] Send a Hebrew-only message → text right-aligned - [ ] Send a mixed Hebrew + English message → correct per-paragraph direction - [ ] Message containing a code block → code stays LTR - [ ] Type Hebrew in the prompt textarea → input flows right-to-left - [ ] Hebrew session name in sidebar → right-aligned - [ ] Hebrew session name in Resume Session dialog → right-aligned 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
274 lines
6.2 KiB
CSS
274 lines
6.2 KiB
CSS
@import "github-markdown-css/github-markdown-light.css" layer(github-markdown-base);
|
|
|
|
@layer components {
|
|
.markdown-body {
|
|
max-width: none;
|
|
background-color: transparent;
|
|
font-family: var(--font-family-sans);
|
|
font-size: var(--font-size-base);
|
|
line-height: var(--line-height-normal);
|
|
font-weight: var(--font-weight-regular);
|
|
color: var(--text-primary);
|
|
/* Message containers may use `whitespace-pre-wrap` for plain text.
|
|
Markdown should always match assistant rendering (normal whitespace). */
|
|
white-space: normal;
|
|
}
|
|
|
|
.markdown-body p,
|
|
.markdown-body ul,
|
|
.markdown-body ol,
|
|
.markdown-body blockquote,
|
|
.markdown-body table {
|
|
font-size: inherit;
|
|
line-height: inherit;
|
|
color: inherit;
|
|
}
|
|
|
|
/* Auto-detect text direction per block element for RTL language support (e.g. Hebrew, Arabic) */
|
|
.markdown-body p,
|
|
.markdown-body li,
|
|
.markdown-body h1,
|
|
.markdown-body h2,
|
|
.markdown-body h3,
|
|
.markdown-body h4,
|
|
.markdown-body h5,
|
|
.markdown-body h6,
|
|
.markdown-body blockquote,
|
|
.markdown-body td,
|
|
.markdown-body th {
|
|
unicode-bidi: plaintext;
|
|
}
|
|
|
|
.markdown-body h1,
|
|
.markdown-body h2,
|
|
.markdown-body h3,
|
|
.markdown-body h4,
|
|
.markdown-body h5,
|
|
.markdown-body h6 {
|
|
font-family: inherit;
|
|
color: var(--markdown-heading-color, inherit);
|
|
font-weight: var(--font-weight-semibold);
|
|
line-height: 1.3;
|
|
margin-top: 0.9em;
|
|
margin-bottom: 0.55em;
|
|
}
|
|
|
|
.markdown-body h6 {
|
|
font-size: calc(var(--font-size-base) + 0.5px);
|
|
}
|
|
|
|
.markdown-body h5 {
|
|
font-size: calc(var(--font-size-base) + 1px);
|
|
}
|
|
|
|
.markdown-body h4 {
|
|
font-size: calc(var(--font-size-base) + 1.5px);
|
|
}
|
|
|
|
.markdown-body h3 {
|
|
font-size: calc(var(--font-size-base) + 2px);
|
|
}
|
|
|
|
.markdown-body h2 {
|
|
font-size: calc(var(--font-size-base) + 2.5px);
|
|
}
|
|
|
|
.markdown-body h1 {
|
|
font-size: calc(var(--font-size-base) + 3px);
|
|
}
|
|
|
|
.markdown-body h1,
|
|
.markdown-body h2 {
|
|
border-bottom: none;
|
|
padding-bottom: 0;
|
|
}
|
|
|
|
.markdown-body hr {
|
|
display: none;
|
|
}
|
|
|
|
.markdown-body strong {
|
|
font-weight: var(--font-weight-regular);
|
|
color: var(--markdown-accent, var(--message-assistant-border));
|
|
}
|
|
|
|
.markdown-body em {
|
|
font-style: italic;
|
|
border-bottom: none;
|
|
font-size: calc(var(--font-size-base) + 1.5px);
|
|
margin-top: 0.3em;
|
|
margin-bottom: 0.3em;
|
|
display: inline;
|
|
}
|
|
|
|
.markdown-body a {
|
|
color: var(--accent-primary);
|
|
text-decoration: none;
|
|
}
|
|
|
|
.markdown-body a:hover {
|
|
color: var(--accent-hover);
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.markdown-body code {
|
|
font-family: var(--font-family-mono);
|
|
font-size: var(--font-size-sm);
|
|
background-color: var(--surface-muted);
|
|
border: 1px solid var(--border-base);
|
|
border-radius: 4px;
|
|
padding: 0.15em 0.35em;
|
|
}
|
|
|
|
.markdown-body pre code {
|
|
padding: 0;
|
|
border: none;
|
|
background: transparent;
|
|
}
|
|
|
|
.markdown-body pre:not(.shiki) {
|
|
font-family: var(--font-family-mono);
|
|
font-size: var(--font-size-sm);
|
|
line-height: var(--line-height-normal);
|
|
background-color: var(--surface-code);
|
|
color: var(--text-primary);
|
|
border: 1px solid var(--border-base);
|
|
border-radius: 8px;
|
|
padding: 0.75rem;
|
|
margin: 1rem 0;
|
|
}
|
|
|
|
.markdown-body pre:not(.shiki) code,
|
|
.markdown-code-block pre:not(.shiki) code {
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.markdown-body blockquote {
|
|
border-inline-start: 3px solid var(--border-base);
|
|
color: var(--text-secondary);
|
|
background-color: var(--surface-muted);
|
|
padding: 0.5rem 1rem;
|
|
border-start-start-radius: 0;
|
|
border-start-end-radius: 8px;
|
|
border-end-end-radius: 8px;
|
|
border-end-start-radius: 0;
|
|
}
|
|
|
|
.markdown-body ul,
|
|
.markdown-body ol {
|
|
padding-inline-start: 1.5rem;
|
|
margin: 0.5rem 0;
|
|
}
|
|
|
|
.markdown-body ul {
|
|
list-style-type: disc;
|
|
}
|
|
|
|
.markdown-body ol {
|
|
list-style-type: decimal;
|
|
}
|
|
|
|
.markdown-body ul li,
|
|
.markdown-body ol li {
|
|
margin: 0.25rem 0;
|
|
}
|
|
|
|
.markdown-body table {
|
|
border-collapse: collapse;
|
|
width: 100%;
|
|
margin: 1rem 0;
|
|
background-color: transparent;
|
|
}
|
|
|
|
.markdown-body th,
|
|
.markdown-body td {
|
|
border: 1px solid var(--border-base);
|
|
padding: 0.5rem 0.75rem;
|
|
text-align: start;
|
|
color: var(--text-primary);
|
|
background-color: transparent;
|
|
}
|
|
|
|
.markdown-body th {
|
|
background-color: var(--surface-secondary);
|
|
}
|
|
|
|
.markdown-body tbody > tr:nth-child(odd) > td {
|
|
background-color: var(--markdown-table-row-odd);
|
|
}
|
|
|
|
.markdown-body tbody > tr:nth-child(even) > td {
|
|
background-color: var(--markdown-table-row-even);
|
|
}
|
|
|
|
.markdown-code-block {
|
|
position: relative;
|
|
margin: 10px 0;
|
|
border-radius: 6px;
|
|
background-color: var(--surface-muted);
|
|
border: 1px solid var(--border-base);
|
|
}
|
|
|
|
.code-block-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 4px 8px;
|
|
background-color: var(--surface-secondary);
|
|
border-bottom: 1px solid var(--border-base);
|
|
/* border-top-left-radius: 6px;
|
|
border-top-right-radius: 6px; */
|
|
}
|
|
|
|
.code-block-language {
|
|
font-family: var(--font-family-mono);
|
|
color: var(--text-secondary);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.04em;
|
|
font-size: var(--font-size-sm);
|
|
}
|
|
|
|
.code-block-copy {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
padding: 4px 8px;
|
|
background-color: transparent;
|
|
border: 1px solid var(--border-base);
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
color: var(--text-secondary);
|
|
transition: background-color 150ms ease, color 150ms ease, border-color 150ms ease;
|
|
margin-inline-start: auto;
|
|
font-size: var(--font-size-sm);
|
|
}
|
|
|
|
.code-block-copy:hover {
|
|
background-color: var(--surface-hover);
|
|
border-color: var(--accent-primary);
|
|
color: var(--accent-primary);
|
|
}
|
|
|
|
.code-block-copy .copy-icon {
|
|
width: 14px;
|
|
height: 14px;
|
|
}
|
|
|
|
.code-block-copy .copy-text {
|
|
font-family: var(--font-family-mono);
|
|
}
|
|
|
|
.markdown-code-block pre {
|
|
margin: 0 !important;
|
|
padding: 12px !important;
|
|
overflow-x: auto;
|
|
background-color: transparent !important;
|
|
border-radius: 0;
|
|
}
|
|
|
|
.markdown-code-block code {
|
|
background: transparent !important;
|
|
padding: 0 !important;
|
|
}
|
|
}
|