diff --git a/src/features/terminal/ui/TerminalView.test.ts b/src/features/terminal/ui/TerminalView.test.ts index 55b23251b..d2eccbfe1 100644 --- a/src/features/terminal/ui/TerminalView.test.ts +++ b/src/features/terminal/ui/TerminalView.test.ts @@ -12,6 +12,7 @@ const pty = vi.hoisted(() => ({ getPtyStatus: vi.fn(async () => ({ foreground: null })), })); vi.mock("../../../platform/tauri/pty", () => pty); +const xterm = vi.hoisted(() => ({ options: [] as { fontFamily?: string }[] })); vi.mock("../model/terminalLayout", () => ({ fitTerminal: () => null, applyTerminalChrome: () => {}, @@ -19,6 +20,9 @@ vi.mock("../model/terminalLayout", () => ({ })); vi.mock("@xterm/xterm", () => ({ Terminal: class { + constructor(options: { fontFamily?: string }) { + xterm.options.push(options); + } cols = 80; rows = 24; options = {}; @@ -44,6 +48,7 @@ vi.mock("@xterm/xterm", () => ({ import { TerminalView } from "./TerminalView"; afterEach(() => { + xterm.options.length = 0; vi.clearAllMocks(); vi.unstubAllGlobals(); }); @@ -176,3 +181,23 @@ it("does not hold a different terminal behind another one's teardown", async () host.remove(); } }); + +it("uses the terminal-specific font stack", async () => { + const { host, root } = setup(); + const stack = '"Test Nerd Font", monospace'; + document.documentElement.style.setProperty("--font-terminal", stack); + try { + await act(async () => { + root.render( + createElement(TerminalView, { id: "font", cwd: "/tmp", active: true }), + ); + }); + expect(xterm.options[0]?.fontFamily).toBe(stack); + } finally { + await act(async () => { + root.unmount(); + }); + host.remove(); + document.documentElement.style.removeProperty("--font-terminal"); + } +}); diff --git a/src/features/terminal/ui/TerminalView.tsx b/src/features/terminal/ui/TerminalView.tsx index 4a888c5bd..58ad22711 100644 --- a/src/features/terminal/ui/TerminalView.tsx +++ b/src/features/terminal/ui/TerminalView.tsx @@ -113,9 +113,9 @@ function terminalTheme(light: boolean) { }; } -function monoFont(): string { +function terminalFont(): string { const fromCss = getComputedStyle(document.documentElement) - .getPropertyValue("--font-mono") + .getPropertyValue("--font-terminal") .trim(); return fromCss || "ui-monospace, SFMono-Regular, Menlo, Monaco, monospace"; } @@ -163,7 +163,7 @@ export function TerminalView({ id, cwd, active, onMetaChange }: Props) { const term = new Terminal({ cursorBlink: true, cursorStyle: "bar", - fontFamily: monoFont(), + fontFamily: terminalFont(), fontSize: 13, lineHeight: 1, letterSpacing: 0, diff --git a/src/styles/index.css b/src/styles/index.css index d595b839c..244d1865d 100644 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -99,6 +99,21 @@ ); } +/* Static so Tailwind always emits it: only xterm reads it, via JS. */ +@theme static { + /* Terminal-only stack. JetBrains Mono Nerd Font leads so prompt text and + icons share one face; the Nerd Font families after the system monos fill + Private Use Area glyphs (powerline arrows, git/folder icons) because + WebKit's fallback never reaches user-installed icon fonts on its own. */ + --font-terminal: + "JetBrainsMono NFM", "JetBrainsMono Nerd Font Mono", "JetBrainsMono NF", + ui-monospace, SFMono-Regular, Menlo, Monaco, "Symbols Nerd Font Mono", + "Symbols Nerd Font", "MesloLGS NF", "MesloLGS Nerd Font Mono", + "FiraCode Nerd Font Mono", "Hack Nerd Font Mono", + "CaskaydiaCove Nerd Font Mono", Consolas, "Liberation Mono", "Courier New", + monospace; +} + :root { --motion-reorder-duration: 160ms; --motion-tab-close-duration: 200ms;