Skip to content

feat(web): configurable font families under Settings → Appearance - #5103

Open
StiensWout wants to merge 14 commits into
pingdotgg:mainfrom
StiensWout:agent/web-appearance-fonts
Open

feat(web): configurable font families under Settings → Appearance#5103
StiensWout wants to merge 14 commits into
pingdotgg:mainfrom
StiensWout:agent/web-appearance-fonts

Conversation

@StiensWout

@StiensWout StiensWout commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

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:

  • Interface font — labels, controls, messages, and composer chrome (--font-sans)
  • Prompt font — the composer textarea and its placeholder only, via --font-composer (defaults to the interface font; for people who prefer writing prompts in a mono face)
  • Code font — code blocks, diffs, and file previews (--font-mono), including the shadow-root @pierre/diffs surfaces via their font hooks
  • Terminal font — feeds the Ghostty surface's font option and live setFont re-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 face

Custom 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:

  • four FontFamilyPreference client settings in the contracts schema (empty = default), persisted client-side like the other appearance preferences
  • the --font-sans / --font-mono theme tokens move out of the @theme inline block so Tailwind utilities reference the variables, making runtime overrides effective; body, pre/code, and the @pierre/diffs shadow-root hooks route through the tokens
  • custom input is normalized through tested helpers (single names or comma lists, quoting where needed) and always composed in front of the default stacks so glyph coverage never regresses
  • terminal font application survives async client-settings hydration (re-applied once the surface finishes creating)

Validation

  • web unit tests (settings/appearance + terminal + helper tests), desktop settings tests, contracts typecheck
  • web + desktop typecheck, lint, formatting
  • end-to-end verified against a live dev server (dropdown filtering, preview rendering, debounced draft-commit, reload persistence, reset)

🤖 Generated with Claude Code

Note

Add configurable font families for interface, composer, code, and terminal under Settings → Appearance

  • Adds four new client settings (fontFamilySans, fontFamilyComposer, fontFamilyCode, fontFamilyTerminal) stored as strings up to 200 chars, defined in settings.ts.
  • Adds appearanceFonts.ts with font catalog data, CSS sanitization helpers, a canvas-based font availability probe, and applyAppearanceFontVariables to write --font-sans, --font-mono, and --font-composer onto the document root.
  • Renders a new

Macroscope summarized d932f98.

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>
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d9032f24-5b7b-4167-a13c-14bc9ebd80fa

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Jul 31, 2026
Comment thread apps/web/src/components/ThreadTerminalDrawer.tsx
StiensWout and others added 2 commits July 31, 2026 13:36
…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>
Comment thread apps/web/src/appearanceFonts.ts
@StiensWout StiensWout changed the title feat(web): configurable font families under Settings → Appearance [WIP] feat(web): configurable font families under Settings → Appearance Jul 31, 2026
StiensWout and others added 3 commits July 31, 2026 13:40
…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>
Comment thread apps/web/src/components/settings/SettingsPanels.tsx Outdated
Comment thread apps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment thread apps/web/src/components/settings/SettingsPanels.tsx
Comment thread apps/web/src/components/settings/SettingsPanels.tsx
@macroscopeapp

macroscopeapp Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: 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>
@github-actions github-actions Bot added size:XL 500-999 changed lines (additions + deletions). and removed size:L 100-499 changed lines (additions + deletions). labels Jul 31, 2026
StiensWout and others added 3 commits July 31, 2026 14:16
…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>

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ 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.

Comment thread apps/web/src/components/settings/SettingsPanels.tsx
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>
@StiensWout StiensWout changed the title [WIP] feat(web): configurable font families under Settings → Appearance feat(web): configurable prompt textarea and terminal fonts under Settings → Appearance Jul 31, 2026
@StiensWout StiensWout changed the title feat(web): configurable prompt textarea and terminal fonts under Settings → Appearance feat(web): configurable font families under Settings → Appearance Jul 31, 2026
StiensWout and others added 3 commits July 31, 2026 14:40
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL 500-999 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant