feat(web): configurable font families under Settings → Appearance - #5103
feat(web): configurable font families under Settings → Appearance#5103StiensWout wants to merge 14 commits into
Conversation
Adds a Fonts section to Appearance with four client-persisted preferences — interface (sans), composer, code, and terminal font families. Family only; sizing is deliberately left for a follow-up. - contracts: four FontFamilyPreference client settings (empty string means default), patchable per key - the font theme tokens move out of the inline Tailwind theme so utilities reference the variables, and a FontAppearanceSync applies custom families as CSS variable overrides with the default stacks kept as fallbacks - the composer gets its own --font-composer variable (defaults to the sans stack) applied on the editor surface, so prompt writers can use a mono face without changing the rest of the app - the terminal preference feeds the Ghostty surface's font option and live setFont from pingdotgg#4860's groundwork; the bundled Nerd Font symbols fallback keeps prompt glyphs on any custom face - each settings row shows a live preview rendered in the resolved stack, with per-row reset; custom names are normalized and quoted through tested pure helpers Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…views Replaces the free-text family inputs with dropdowns of curated faces filtered through document.fonts.check so only fonts that will actually render are offered (bundled webfonts always appear); a Custom… option keeps arbitrary families possible. Each option renders in its own face, and every row gains a preview card styled like the real surface — chat text, a mini composer, a highlighted code snippet, and a dark terminal prompt — rendered in the resolved stack, matching mobile's Appearance screen style. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The pre and code elements in the preview cards carried the preflight mono font-family, overriding the card's resolved stack, so those two previews never changed with the dropdown. They inherit now; also brace the comment-looking literal flagged by lint. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…views document.fonts.check() answers true for families that are not installed (nothing needs loading), so the dropdowns offered fonts that silently fell back to the default stack and the sans-side previews appeared not to update. Availability now comes from canvas metric probing: a family counts as available when substituting it before a generic changes the measured advance. The terminal preview card also follows the app theme now — the real terminal renders on the app background in light mode, so a hardcoded dark card was inconsistent theming. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The base styles hardcoded literal font stacks on body and pre/code, so the runtime --font-sans/--font-mono overrides from Settings -> Appearance only reached the preview cards. Reference the theme tokens instead; the literal stacks stay single-sourced in the @theme block. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ApprovabilityVerdict: Needs human review This PR introduces a new user-facing feature (configurable font families) with substantial new code: ~175 lines of font handling logic, ~280 lines of new settings UI, 4 new persisted settings, and runtime CSS variable manipulation. New features introducing new capabilities warrant human review. You can customize Macroscope's approvability policy. Learn more. |
Client settings hydrate after mount, which dropped two things on reload: the terminal font (setFont fired while the surface was still loading, so the preference never reached the created terminal) and the custom-font input (customMode initialized from the pre-hydration empty value, leaving the field hidden behind a "Custom" trigger). The terminal re-applies the current preference once the surface exists, and the input visibility now derives from the value itself. Also groups mixed dropdowns (composer) into labeled Sans serif / Monospace sections and removes the composer's redundant default entry - the reset affordance already returns it to following the interface font. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…om names Every font dropdown now offers the full catalog split into bold Sans serif / Monospace sections, so any surface can point at any face. The custom input edits a draft that only commits once the typed name probes as an installed font - the current font holds (and the field flags invalid) while a partial or unknown name is typed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… tokens The @pierre/diffs surfaces (file previews, chat diffs, annotatable code, search lines) render inside shadow roots with their own literal font stack as fallback, so the code font preference never reached them. The library consults --diffs-font-family/--diffs-header-font-family as override hooks and custom properties inherit across the shadow boundary, so defining the hooks once at :root covers every consumer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…r preview Restore defaults now lists and resets the four font-family preferences (the General page's restore is intentionally global), and the composer preview falls back to the resolved interface stack so it matches the runtime var(--font-composer) chain instead of the bare default sans stack. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit df68214. Configure here.
Per review direction the interface and code fonts stay on the theme defaults; only the prompt textarea (people who prefer writing prompts in a mono face) and the terminal remain configurable. The textarea setting is retitled to make its scope explicit - the rest of the composer follows the interface font. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
An emptied custom font field now resets the preference to the default instead of silently keeping the previous font behind an empty-looking input; partial or unknown names still hold the current font while typing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The final split keeps every surface configurable: interface (labels, controls, messages, composer chrome), prompt (textarea and placeholder only), code (code blocks, diffs, file previews), and terminal. Every dropdown now offers the same Default entry, fixing the prompt/terminal inconsistency. The custom input no longer commits on every keystroke: it edits a local draft and commits 400ms after typing pauses (or on Enter/blur), so the app does not reflow mid-word and the unknown-name flag waits for the pause. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

What changed
Settings → Appearance gains a Fonts section with four preferences, each with a curated categorized dropdown (Sans serif / Monospace sections, filtered to fonts actually installed via canvas probing), an optional custom family input, a live preview rendered in the resolved stack, and a per-row reset:
--font-sans)--font-composer(defaults to the interface font; for people who prefer writing prompts in a mono face)--font-mono), including the shadow-root@pierre/diffssurfaces via their font hookssetFontre-measure path added in feat(web): render terminals with libghostty-vt #4860; the bundled Nerd Font symbols fallback keeps prompt glyphs working on any custom faceCustom names edit a local draft that commits 400ms after typing pauses (or on Enter/blur) and only once the name probes as an installed font, so the app never reflows mid-word; an explicit clear resets to the default. All four preferences participate in the global Restore defaults flow.
Under the hood:
FontFamilyPreferenceclient settings in the contracts schema (empty = default), persisted client-side like the other appearance preferences--font-sans/--font-monotheme tokens move out of the@theme inlineblock so Tailwind utilities reference the variables, making runtime overrides effective;body,pre/code, and the@pierre/diffsshadow-root hooks route through the tokensValidation
🤖 Generated with Claude Code
Note
Add configurable font families for interface, composer, code, and terminal under Settings → Appearance
fontFamilySans,fontFamilyComposer,fontFamilyCode,fontFamilyTerminal) stored as strings up to 200 chars, defined in settings.ts.applyAppearanceFontVariablesto write--font-sans,--font-mono, and--font-composeronto the document root.Macroscope summarized d932f98.