Desktop app: font selection doesn't work reliably (ghostty-web font loading) #8779

Open
opened 2026-02-16 18:10:49 -05:00 by yindo · 2 comments
Owner

Originally created by @mrm007 on GitHub (Feb 7, 2026).

Originally assigned to: @adamdotdevin on GitHub.

Bug

Selecting a font in Desktop Settings → General → Font doesn't work reliably. Some fonts never render, others only render correctly after an app restart, and some work fine. The terminal falls back to IBM Plex Mono (the default) when it fails.

Investigation

Font wiring is correct

The three layers of the font system are internally consistent:

  1. @font-face registration (packages/ui/src/components/font.tsx) — bundles Nerd Fonts as woff2 and registers them (e.g. font-family: "Fira Code Nerd Font")
  2. Font family lookup (packages/app/src/context/settings.tsx) — monoFonts map references matching names (e.g. "Fira Code Nerd Font", ...)
  3. Terminal application (packages/app/src/components/terminal.tsx) — passes fontFamily to ghostty-web Terminal constructor and updates it reactively

The @font-face names and monoFonts lookup names match. The CSS custom property --font-family-mono is set correctly. The wiring is not the problem.

The problem: ghostty-web canvas rendering

The terminal uses ghostty-web (Ghostty compiled to WebAssembly), NOT xterm.js:

import type { Ghostty, Terminal as Term, FitAddon } from "ghostty-web"

The canvas renderer doesn't inherit CSS font-family — it needs fonts passed explicitly, which the code does. But there's no document.fonts.ready check before Terminal creation. The @font-face woff2 fonts use font-display: swap, meaning:

  1. Terminal is created immediately
  2. ghostty-web measures character cells and builds glyph cache using fallback font metrics
  3. Bundled woff2 fonts finish loading later
  4. ghostty-web never re-measures or rebuilds its glyph cache

This is a known class of bug with canvas-based terminal renderers and web fonts (see xtermjs/xterm.js#1164 for the xterm.js equivalent).

Evidence: font-specific behavior

Testing from the font dropdown:

Font Behavior
IBM Plex Mono Works (it's the default/fallback, always loaded)
Iosevka Works
Fira Code Falls back to IBM Plex Mono
Inconsolata ⚠️ Renders better after app restart (supports race condition theory)

Iosevka working suggests it loads faster or ghostty-web handles it differently. Inconsolata improving after restart supports the timing theory — cached/preloaded fonts from the previous session are available immediately on restart.

DOM elements also affected

The font issue isn't canvas-only. Inspecting the text input area shows:

  • Parent div has class .font-mono with font-family: var(--font-mono)
  • The CSS variable contains the correct font stack (e.g. "Fira Code Nerd Font", ...)
  • Yet the text input also renders in the fallback font

This suggests the @font-face woff2 fonts may not be loading/resolving correctly in the Tauri webview for all font families, or there's a timing issue affecting both DOM and canvas rendering.

Suggested fix

Wait for fonts to load before creating the Terminal:

// In terminal.tsx, before `new mod.Terminal(...)`:
await document.fonts.ready

const t = new mod.Terminal({
  fontFamily: monoFontFamily(settings.appearance.font()),
  // ...
})

And/or preload the selected font specifically:

const fontFamily = monoFontFamily(settings.appearance.font())
await document.fonts.load(`14px ${fontFamily}`)

Reproduction

  1. Open Desktop app → Settings → General → Font → select "Fira Code"
  2. Open a terminal tab
  3. Terminal renders in IBM Plex Mono despite the selection
  4. Switch to "Iosevka" → terminal renders correctly
  5. Switch back to "Fira Code" → falls back to IBM Plex Mono again

Environment

  • macOS Sequoia 15.7.3 (arm64, Apple M4)
  • OpenCode Desktop v1.1.53
Originally created by @mrm007 on GitHub (Feb 7, 2026). Originally assigned to: @adamdotdevin on GitHub. ## Bug Selecting a font in Desktop Settings → General → Font doesn't work reliably. Some fonts never render, others only render correctly after an app restart, and some work fine. The terminal falls back to IBM Plex Mono (the default) when it fails. ## Investigation ### Font wiring is correct The three layers of the font system are internally consistent: 1. **`@font-face` registration** (`packages/ui/src/components/font.tsx`) — bundles Nerd Fonts as woff2 and registers them (e.g. `font-family: "Fira Code Nerd Font"`) 2. **Font family lookup** (`packages/app/src/context/settings.tsx`) — `monoFonts` map references matching names (e.g. `"Fira Code Nerd Font", ...`) 3. **Terminal application** (`packages/app/src/components/terminal.tsx`) — passes `fontFamily` to ghostty-web Terminal constructor and updates it reactively The `@font-face` names and `monoFonts` lookup names **match**. The CSS custom property `--font-family-mono` is set correctly. The wiring is not the problem. ### The problem: ghostty-web canvas rendering The terminal uses `ghostty-web` (Ghostty compiled to WebAssembly), NOT xterm.js: ```ts import type { Ghostty, Terminal as Term, FitAddon } from "ghostty-web" ``` The canvas renderer doesn't inherit CSS `font-family` — it needs fonts passed explicitly, which the code does. **But there's no `document.fonts.ready` check before Terminal creation.** The `@font-face` woff2 fonts use `font-display: swap`, meaning: 1. Terminal is created immediately 2. ghostty-web measures character cells and builds glyph cache using fallback font metrics 3. Bundled woff2 fonts finish loading later 4. ghostty-web never re-measures or rebuilds its glyph cache This is a known class of bug with canvas-based terminal renderers and web fonts (see [xtermjs/xterm.js#1164](https://github.com/xtermjs/xterm.js/issues/1164) for the xterm.js equivalent). ### Evidence: font-specific behavior Testing from the font dropdown: | Font | Behavior | |------|----------| | IBM Plex Mono | ✅ Works (it's the default/fallback, always loaded) | | Iosevka | ✅ Works | | Fira Code | ❌ Falls back to IBM Plex Mono | | Inconsolata | ⚠️ Renders better after app restart (supports race condition theory) | Iosevka working suggests it loads faster or ghostty-web handles it differently. Inconsolata improving after restart supports the timing theory — cached/preloaded fonts from the previous session are available immediately on restart. ### DOM elements also affected The font issue isn't canvas-only. Inspecting the text input area shows: - Parent div has class `.font-mono` with `font-family: var(--font-mono)` - The CSS variable contains the correct font stack (e.g. `"Fira Code Nerd Font", ...`) - Yet the text input also renders in the fallback font This suggests the `@font-face` woff2 fonts may not be loading/resolving correctly in the Tauri webview for all font families, or there's a timing issue affecting both DOM and canvas rendering. ## Suggested fix Wait for fonts to load before creating the Terminal: ```ts // In terminal.tsx, before `new mod.Terminal(...)`: await document.fonts.ready const t = new mod.Terminal({ fontFamily: monoFontFamily(settings.appearance.font()), // ... }) ``` And/or preload the selected font specifically: ```ts const fontFamily = monoFontFamily(settings.appearance.font()) await document.fonts.load(`14px ${fontFamily}`) ``` ## Reproduction 1. Open Desktop app → Settings → General → Font → select "Fira Code" 2. Open a terminal tab 3. Terminal renders in IBM Plex Mono despite the selection 4. Switch to "Iosevka" → terminal renders correctly 5. Switch back to "Fira Code" → falls back to IBM Plex Mono again ## Environment - macOS Sequoia 15.7.3 (arm64, Apple M4) - OpenCode Desktop v1.1.53
yindo added the web label 2026-02-16 18:10:49 -05:00
Author
Owner

@github-actions[bot] commented on GitHub (Feb 7, 2026):

This issue might be a duplicate of or related to existing issues. Please check:

  • #11955: [FEATURE] Custom UI Font in Desktop/Web - discusses hardcoded font limitations and the desire for custom font support
  • #12503: [FEATURE] Add font customization support to opencode.json config - specifically mentions Fira Code, JetBrains Mono, and Cascadia Code which are affected by this bug
  • #10103: [FEATURE] Desktop app ought include the Iosevka font - mentions that Iosevka is already in the hardcoded list (note: Iosevka is one of the fonts that remained unchanged in Nerd Fonts v3)

The core issue is that the hardcoded font family names in the font selector use Nerd Fonts v2 naming conventions. These related issues suggest improving the font system overall by allowing dynamic/custom fonts rather than maintaining a hardcoded list.

Feel free to ignore if these don't address your specific case.

@github-actions[bot] commented on GitHub (Feb 7, 2026): This issue might be a duplicate of or related to existing issues. Please check: - #11955: [FEATURE] Custom UI Font in Desktop/Web - discusses hardcoded font limitations and the desire for custom font support - #12503: [FEATURE] Add font customization support to opencode.json config - specifically mentions Fira Code, JetBrains Mono, and Cascadia Code which are affected by this bug - #10103: [FEATURE] Desktop app ought include the Iosevka font - mentions that Iosevka is already in the hardcoded list (note: Iosevka is one of the fonts that remained unchanged in Nerd Fonts v3) The core issue is that the hardcoded font family names in the font selector use Nerd Fonts v2 naming conventions. These related issues suggest improving the font system overall by allowing dynamic/custom fonts rather than maintaining a hardcoded list. Feel free to ignore if these don't address your specific case.
Author
Owner

@mrm007 commented on GitHub (Feb 7, 2026):

This isn't a feature request for custom fonts or a dynamic font system. It's a bug: the existing hardcoded names are wrong.

The font selector already lists 11 Nerd Fonts. Users select one, expect it to work, and it silently falls back to IBM Plex Mono with no indication anything went wrong. The fix is updating the strings in the monoFonts map to match Nerd Fonts v3 naming — a one-line-per-entry change in packages/app/src/context/settings.tsx.

@mrm007 commented on GitHub (Feb 7, 2026): This isn't a feature request for custom fonts or a dynamic font system. It's a bug: the existing hardcoded names are wrong. The font selector already lists 11 Nerd Fonts. Users select one, expect it to work, and it silently falls back to IBM Plex Mono with no indication anything went wrong. The fix is updating the strings in the `monoFonts` map to match Nerd Fonts v3 naming — a one-line-per-entry change in `packages/app/src/context/settings.tsx`.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#8779