Skip to content

Add configurable terminal font settings - #3946

Closed
xiaogwu wants to merge 1 commit into
pingdotgg:mainfrom
xiaogwu:ui/terminal-font
Closed

Add configurable terminal font settings#3946
xiaogwu wants to merge 1 commit into
pingdotgg:mainfrom
xiaogwu:ui/terminal-font

Conversation

@xiaogwu

@xiaogwu xiaogwu commented Jul 13, 2026

Copy link
Copy Markdown

What Changed

  • Added client-local terminal font family and font size settings under Settings > General > Terminal.
  • Added an automatic fallback stack that prefers installed Nerd Font and Powerline-compatible fonts.
  • Updated open xterm instances when appearance settings change without recreating the terminal or restarting its shell.
  • Guarded delayed font loading and animation-frame work against stale or disposed terminal instances.
  • Added schema, normalization, persistence, and asynchronous lifecycle unit coverage.

Why

The integrated terminal used a hardcoded font stack beginning with SF Mono. Powerlevel10k prompts rely on private-use Powerline glyphs that SF Mono does not provide, which caused malformed segment caps and dangling artifacts.

Terminal fonts are a client rendering concern rather than a shell setting. Keeping these preferences client-local lets local and remote-backed terminals use fonts installed on the machine rendering T3 Code. Automatic mode provides a compatible fallback, while explicit family and size controls allow an exact match with another terminal.

UI Changes

A new Terminal section in General settings provides:

  • Font family, with blank input selecting automatic Nerd Font-compatible fallback.
  • Font size, constrained to 8 through 32 pixels with a 12-pixel default.
  • Reset controls for both settings.

Changes apply live to existing terminals while preserving the shell session and scroll position. Powerlevel10k rendering and live updates were manually verified in the desktop app with JetBrainsMono Nerd Font.

Validation

  • Targeted tests: 41 passed.
  • vp check.
  • vp run typecheck.
  • vp run build:desktop.
  • git diff --check.
  • Manual Powerlevel10k rendering and live-update verification.

Checklist

  • This PR is small and focused.
  • I explained what changed and why.

Note

Medium Risk
Touches integrated terminal lifecycle and client settings persistence; behavior changes on session exit could surprise users, but scope is UI/rendering with solid unit coverage.

Overview
Adds client-local terminal appearance via terminalFontFamily and terminalFontSize on ClientSettingsSchema, with schema defaults for legacy configs and patch validation (integer size 8–32).

Settings → Appearance gains a Terminal section (family override with blank = auto Nerd Font stack, pixel size with reset/restore-defaults). Search catalog and restore-defaults flows include the new keys. terminalAppearance normalizes inputs and quotes font family names for safe CSS/Ghostty font lists.

TerminalViewport reads those settings, passes font into GhosttyTerminalSurface.create, and calls setFont when preferences change without recreating the surface.

Separately refactors terminal exit behavior: classifyTerminalExitTransition and shouldHandleLiveTerminalExit split exit notices from auto-closing the drawer, so reopening or hydrating an already-ended session shows a message but stays visible; only a live runningexited/closed transition dismisses the drawer. Status-driven close runs in its own effect so exits without new buffer output still close.

Reviewed by Cursor Bugbot for commit c63b5ac. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add configurable terminal font family and size settings

  • Adds terminalFontFamily and terminalFontSize fields to ClientSettingsSchema and ClientSettingsPatch in settings.ts, with bounds MIN=8, MAX=32, DEFAULT=12.
  • Adds a Terminal section to the Appearance settings panel with validated inputs and reset-to-default support.
  • TerminalViewport reads the new settings via useClientSettings, builds a GhosttyTerminalFont, passes it to GhosttyTerminalSurface.create, and calls terminal.setFont when settings change.
  • Introduces classifyTerminalExitTransition and shouldHandleLiveTerminalExit helpers to distinguish initial hydrated exits from live exits, fixing a bug where the drawer closed on already-ended sessions.
  • Adds terminal font entries to the settings search catalog so they are discoverable by queries like 'font' or 'terminal'.

Macroscope summarized c63b5ac.

@coderabbitai

coderabbitai Bot commented Jul 13, 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: 4fefd39f-672e-45a6-aa79-d01fb508ab0a

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
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 13, 2026
@xiaogwu
xiaogwu marked this pull request as ready for review July 13, 2026 22:21
Comment thread apps/web/src/terminalAppearance.ts Outdated
Comment thread apps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment thread apps/web/src/components/ThreadTerminalDrawer.tsx Outdated
@macroscopeapp

macroscopeapp Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

2 blocking correctness issues found. This PR introduces new terminal font settings (a new feature) and refactors terminal exit handling logic. Multiple unresolved review comments identify potential bugs including race conditions during surface creation and spurious 'Terminal closed' messages, warranting human review.

You can customize Macroscope's approvability policy. Learn more.

Comment thread apps/web/src/components/ThreadTerminalDrawer.tsx
Comment thread apps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment thread apps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment thread apps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment thread apps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment thread apps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment thread apps/web/src/components/ThreadTerminalDrawer.tsx Outdated

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.

🟡 Medium

terminal.setTheme(terminalThemeFromApp(mount));

If terminal font settings change while GhosttyTerminalSurface.create is still awaiting font/WASM loading, the surface is created with the stale font and never updates. The setFont effect runs while terminalRef.current is still null (creation hasn't completed), so it no-ops, and the effect isn't retriggered once terminalRef.current is set. The surface then stays on the old font until the next unrelated settings change. After create resolves, apply the latest font from requestedTerminalFontRef.current via setFont.

Suggested change
terminal.setTheme(terminalThemeFromApp(mount));
terminal.setFont(requestedTerminalFontRef.current);
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/ThreadTerminalDrawer.tsx around line 442:

If terminal font settings change while `GhosttyTerminalSurface.create` is still awaiting font/WASM loading, the surface is created with the stale font and never updates. The `setFont` effect runs while `terminalRef.current` is still `null` (creation hasn't completed), so it no-ops, and the effect isn't retriggered once `terminalRef.current` is set. The surface then stays on the old font until the next unrelated settings change. After `create` resolves, apply the latest font from `requestedTerminalFontRef.current` via `setFont`.

Comment thread apps/web/src/components/ThreadTerminalDrawer.tsx
Comment thread apps/web/src/components/ThreadTerminalDrawer.tsx
Comment thread apps/web/src/components/ThreadTerminalDrawer.tsx
Comment on lines +455 to +460
if (latestSession.status === "closed" || latestSession.status === "exited") {
writeSystemMessage(
terminal,
latestSession.status === "closed" ? "Terminal closed" : "Process exited",
);
}

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.

🟡 Medium components/ThreadTerminalDrawer.tsx:455

The setup path prints Terminal closed for a live terminal when the surface finishes creating before the attach query hydrates. At that point latestSession is still the synthetic EMPTY_TERMINAL_SESSION_STATE (status "closed", version 0), so the check on line 455 treats it as a finished session and writes the exit notice. When the real running snapshot arrives later, the buffer sync effect writes the output but never removes the stale notice, leaving a false Terminal closed message permanently in the terminal. Consider guarding the initial notice so it only fires for an ended session that has real buffer content (version > 0) or skipping it when previousVersion === 0 and the status is "closed", letting the latest classification in the buffer effect handle only genuinely initial ended sessions.

Suggested change
if (latestSession.status === "closed" || latestSession.status === "exited") {
writeSystemMessage(
terminal,
latestSession.status === "closed" ? "Terminal closed" : "Process exited",
);
}
if (
latestSession.version > 0 &&
(latestSession.status === "closed" || latestSession.status === "exited")
) {
writeSystemMessage(
terminal,
latestSession.status === "closed" ? "Terminal closed" : "Process exited",
);
}
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/ThreadTerminalDrawer.tsx around lines 455-460:

The setup path prints `Terminal closed` for a live terminal when the surface finishes creating before the attach query hydrates. At that point `latestSession` is still the synthetic `EMPTY_TERMINAL_SESSION_STATE` (status `"closed"`, version `0`), so the check on line 455 treats it as a finished session and writes the exit notice. When the real `running` snapshot arrives later, the buffer sync effect writes the output but never removes the stale notice, leaving a false `Terminal closed` message permanently in the terminal. Consider guarding the initial notice so it only fires for an ended session that has real buffer content (version > 0) or skipping it when `previousVersion === 0` and the status is `"closed"`, letting the `latest` classification in the buffer effect handle only genuinely initial ended sessions.

@xiaogwu
xiaogwu force-pushed the ui/terminal-font branch 2 times, most recently from 54126e9 to 8b2aed7 Compare August 4, 2026 23:42

@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 8b2aed7. Configure here.

Comment thread apps/web/src/components/ThreadTerminalDrawer.tsx
Add `terminalFontFamily` and `terminalFontSize` to client settings, wire
them through the settings panel, and apply them to the Ghostty terminal
surface via its `font` option and `setFont()`.

The contracts size range (8..32) sits inside the renderer's own 6..32
clamp so no accepted value is silently changed on the way to the canvas.
Family names are quoted before they reach the surface, which appends its
Nerd Font glyph fallbacks after whatever it is handed and does not quote.

Also split the terminal exit handling in two: the "[terminal] ..." notice
now fires when attaching to a session that had already ended, while
closing the drawer still requires an observed running -> ended
transition. Previously, opening a drawer on a finished terminal either
dismissed it immediately or explained nothing.
@xiaogwu xiaogwu closed this Aug 5, 2026
@xiaogwu
xiaogwu deleted the ui/terminal-font branch August 5, 2026 18:02
@xiaogwu

xiaogwu commented Aug 5, 2026

Copy link
Copy Markdown
Author

Closing this: upstream now ships configurable terminal font family and size under Settings → Appearance via #5103 and #5397, which supersedes this PR. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 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