From 2db46b2cf57b9a9800360497d54eb0cd7549fc3f Mon Sep 17 00:00:00 2001 From: Maarten Goet Date: Sat, 1 Aug 2026 10:32:45 -0700 Subject: [PATCH] fix(terminal): apply configured font size on startup loadConfig seeds tabBarPosition, hideTabTitles, terminalOpacity and the AI session limits from config, but never terminal.fontSize, so the store kept its hardcoded initial 14. TerminalPanel does construct xterm with the configured size, but its fontSize effect immediately writes the stale store value back over it on mount. Any configured size other than 14 was therefore lost on every launch - through the Settings control just as much as through the config file - and only came back via updateConfig or zoomReset (Ctrl+0). The status-bar zoom badge divides the store value by the configured baseline, so the mismatch also surfaced as a wrong percentage: a configured 17.5 rendered at 14px and displayed 80%. Seed fontSize from config.terminal.fontSize in loadConfig, guarded on a positive number so a malformed value falls back to the default. Co-Authored-By: Claude Opus 5 (1M context) --- src/renderer/state/terminal-store.ts | 10 +++ .../renderer/load-config-font-size.test.ts | 82 +++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 tests/unit/renderer/load-config-font-size.test.ts diff --git a/src/renderer/state/terminal-store.ts b/src/renderer/state/terminal-store.ts index ed5ed08..97585f8 100644 --- a/src/renderer/state/terminal-store.ts +++ b/src/renderer/state/terminal-store.ts @@ -1413,6 +1413,16 @@ export const useTerminalStore = create((set, get) => ({ updates.terminalOpacity = (config as any).terminalOpacity; document.documentElement.style.setProperty('--terminal-opacity', String((config as any).terminalOpacity)); } + // Seed the live zoom size from the configured baseline. Without this the + // store keeps its hardcoded initial 14 and TerminalPanel's fontSize effect + // writes that back over the xterm instance on mount, so a configured size + // only ever took effect via updateConfig (Settings) or zoomReset (Ctrl+0) + // - never on startup. It also left the status-bar zoom badge showing + // 14 / configured instead of 100%. + const baseFontSize = config?.terminal?.fontSize; + if (typeof baseFontSize === 'number' && baseFontSize > 0) { + updates.fontSize = baseFontSize; + } // Seed AI session load limits from config so subsequent // loadCopilotSessions / loadClaudeCodeSessions calls in App.init // honor the user's preference (0 = no scan). diff --git a/tests/unit/renderer/load-config-font-size.test.ts b/tests/unit/renderer/load-config-font-size.test.ts new file mode 100644 index 0000000..bed1e72 --- /dev/null +++ b/tests/unit/renderer/load-config-font-size.test.ts @@ -0,0 +1,82 @@ +// loadConfig must seed the store's live zoom size from terminal.fontSize. +// +// Without it the store kept its hardcoded initial 14 while the status-bar +// badge divided by the configured baseline, so a configured 17.5 rendered at +// 14px and showed 80%. terminal-store reads window at module load, so the +// stubs go in before a dynamic import (test env is 'node'). +import { describe, test, expect, beforeAll, beforeEach } from 'vitest'; + +const getConfigResult: { value: unknown } = { value: {} }; + +(globalThis as any).window = (globalThis as any).window ?? {}; +(globalThis as any).window.terminalAPI = { + getConfig: async () => getConfigResult.value, +}; +(globalThis as any).document = (globalThis as any).document ?? { + hasFocus: () => true, + documentElement: { style: { setProperty: () => {} } }, +}; + +let useTerminalStore: typeof import('../../../src/renderer/state/terminal-store').useTerminalStore; + +beforeAll(async () => { + ({ useTerminalStore } = await import('../../../src/renderer/state/terminal-store')); +}); + +beforeEach(() => { + // The hardcoded initial state, so each case starts from the pre-load value. + useTerminalStore.setState({ fontSize: 14 }); +}); + +describe('loadConfig - terminal font size', () => { + test('seeds fontSize from the configured baseline', async () => { + getConfigResult.value = { terminal: { fontSize: 17.5 } }; + + await useTerminalStore.getState().loadConfig(); + + expect(useTerminalStore.getState().fontSize).toBe(17.5); + }); + + test('a configured baseline equal to the default is still applied', async () => { + getConfigResult.value = { terminal: { fontSize: 14 } }; + + await useTerminalStore.getState().loadConfig(); + + expect(useTerminalStore.getState().fontSize).toBe(14); + }); + + test('keeps the default when config carries no terminal section', async () => { + getConfigResult.value = {}; + + await useTerminalStore.getState().loadConfig(); + + expect(useTerminalStore.getState().fontSize).toBe(14); + }); + + test('ignores a non-numeric font size', async () => { + getConfigResult.value = { terminal: { fontSize: '18' } }; + + await useTerminalStore.getState().loadConfig(); + + expect(useTerminalStore.getState().fontSize).toBe(14); + }); + + test('ignores a zero or negative font size', async () => { + getConfigResult.value = { terminal: { fontSize: 0 } }; + await useTerminalStore.getState().loadConfig(); + expect(useTerminalStore.getState().fontSize).toBe(14); + + getConfigResult.value = { terminal: { fontSize: -4 } }; + await useTerminalStore.getState().loadConfig(); + expect(useTerminalStore.getState().fontSize).toBe(14); + }); + + test('config itself is still stored alongside the seeded size', async () => { + getConfigResult.value = { terminal: { fontSize: 20 }, defaultShellId: 'zsh' }; + + await useTerminalStore.getState().loadConfig(); + + expect(useTerminalStore.getState().fontSize).toBe(20); + expect(useTerminalStore.getState().config?.terminal?.fontSize).toBe(20); + }); +});