Add configurable Arabic chat font settings and improve RTL chat rendering#5
Conversation
Add a client setting for chat font selection and apply it across chat markdown, user messages, and the composer. Keep Arabic content readable in RTL while preserving code blocks and default app typography. Also fix the settings hook typing so components can subscribe to setting slices cleanly.
There was a problem hiding this comment.
Pull request overview
Adds a user-configurable chat font setting (including an Arabic-optimized option) and improves RTL chat rendering by applying automatic text direction + logical CSS properties across the chat surface.
Changes:
- Introduces
chatFontFamilyas a client setting (Auto / DM Sans / Noto Sans Arabic) and wires it into the Appearance settings UI. - Adds shared helpers for resolving text direction and mapping direction/font selection to CSS classes; applies these to composer, user messages, and assistant markdown.
- Updates chat markdown CSS to use logical properties and enforces LTR rendering for code/monospace areas.
Reviewed changes
Copilot reviewed 13 out of 14 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/contracts/src/settings.ts | Adds ChatFontFamily setting + default to client settings schema. |
| apps/web/package.json | Adds @fontsource/noto-sans-arabic dependency. |
| bun.lock | Locks the new font dependency. |
| apps/web/src/main.tsx | Imports Noto Sans Arabic font weights globally. |
| apps/web/src/lib/textDirection.ts | Adds direction resolution helpers (resolveTextDirection, isRtlText). |
| apps/web/src/lib/textDirection.test.ts | Adds unit tests for direction detection. |
| apps/web/src/lib/chatTypography.ts | Centralizes mapping of direction/font selection to CSS class names. |
| apps/web/src/index.css | Introduces chat font CSS variables + RTL/LTR direction classes; converts markdown styles to logical properties. |
| apps/web/src/hooks/useSettings.ts | Updates useSettings typing to better support slice selectors. |
| apps/web/src/components/settings/SettingsGeneralPanel.tsx | Includes the new setting in “dirty settings” detection. |
| apps/web/src/components/settings/SettingsAppearancePanel.tsx | Adds “Chat font” select control + reset behavior. |
| apps/web/src/components/chat/MessagesTimeline.tsx | Applies resolved direction + chat typography classes to user message bubbles. |
| apps/web/src/components/ComposerPromptEditor.tsx | Applies resolved direction + chat typography classes to the composer and placeholder. |
| apps/web/src/components/ChatMarkdown.tsx | Applies resolved direction + chat typography classes to assistant markdown output. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| export function useSettings<T extends UnifiedSettings = UnifiedSettings>( | ||
| selector?: (s: UnifiedSettings) => T, | ||
| ): T { | ||
| export function useSettings<T = UnifiedSettings>(selector?: (s: UnifiedSettings) => T): T { |
There was a problem hiding this comment.
The new generic signature allows useSettings<SomeType>() to be called without a selector, which becomes an unsound cast at runtime. To keep the improved selector typing while preventing misuse, consider using overloads (no selector → UnifiedSettings, selector → inferred return type) instead of an unconstrained generic default.
| <div | ||
| dir={textDirection} | ||
| lang={textDirection === "rtl" ? "ar" : undefined} | ||
| className={bodyClassName} |
There was a problem hiding this comment.
lang is set to "ar" whenever the resolved direction is RTL, but resolveTextDirection treats multiple scripts as RTL (e.g., Hebrew). This can mislabel content language for screen readers and text shaping. Consider either omitting lang entirely here, or setting it based on an Arabic-script detection (separate regex) rather than direction alone.
| <div | ||
| dir={textDirection} | ||
| lang={textDirection === "rtl" ? "ar" : undefined} | ||
| className={bodyClassName} |
There was a problem hiding this comment.
Same issue as above: lang is derived from RTL direction and hardcoded to "ar", which can mislabel RTL-but-non-Arabic content. Prefer omitting lang or deriving it from Arabic-script detection instead of direction.
| <div className="chat-markdown w-full min-w-0 text-sm leading-relaxed text-foreground/80"> | ||
| <div | ||
| dir={textDirection} | ||
| lang={textDirection === "rtl" ? "ar" : undefined} |
There was a problem hiding this comment.
lang is hardcoded to "ar" for any RTL text, but direction != language (e.g., Hebrew is RTL). This can degrade screen reader pronunciation and font shaping. Consider removing lang here or computing it from Arabic-script detection separately from resolveTextDirection.
| lang={textDirection === "rtl" ? "ar" : undefined} |
| dir={composerDirection} | ||
| lang={composerDirection === "rtl" ? "ar" : undefined} | ||
| className={cn( |
There was a problem hiding this comment.
lang is set to "ar" whenever the composer direction is RTL. Since RTL direction can come from non-Arabic scripts, this can mislabel the user’s input for assistive tech. Prefer omitting lang or setting it only when Arabic script is detected.
| <div className="pointer-events-none absolute inset-0 text-[14px] leading-relaxed text-muted-foreground/35"> | ||
| <div | ||
| dir={composerDirection} | ||
| lang={composerDirection === "rtl" ? "ar" : undefined} |
There was a problem hiding this comment.
Placeholder uses the same RTL→lang="ar" mapping, which can mislabel the placeholder text language for assistive technologies. Consider removing lang here or deriving it from Arabic-script detection instead of direction.
| lang={composerDirection === "rtl" ? "ar" : undefined} |
|
Hey @Mo999salah, thank you for the contribution. I appreciate you taking the time to improve Kodo Code and put this PR together. I reviewed the changes here and decided to continue the work in a follow-up branch so the feature can support a broader set of users, not just Arabic/RTL-specific cases. You can follow that work in PR #6. The main additions on top of this PR are:
For the best experience with this update, I recommend using
I’ll be closing PR #5 in favor of PR #6 so the discussion and follow-up work stay in one place. If you have any additional changes, questions, or comments, please add them on PR #6. If all goes as planned, this will ship in |

Summary
useSettingstyping so components can subscribe to individual settings slices without castsWhy
The current chat UI had no way to choose an Arabic-friendly font and still assumed mostly LTR typography. That made Arabic conversations look inconsistent across the composer, user messages, and assistant output.
User impact
Validation
bun fmtbun lintbun typecheckbun run test src/hooks/useSettings.test.tsbun run test src/lib/textDirection.test.ts