diff --git a/extension/sidepanel-terminal.js b/extension/sidepanel-terminal.js index e6287abcae..5cd82d0dd9 100644 --- a/extension/sidepanel-terminal.js +++ b/extension/sidepanel-terminal.js @@ -39,8 +39,18 @@ ended: document.getElementById('terminal-ended'), restart: document.getElementById('terminal-restart'), restartNow: document.getElementById('terminal-restart-now'), + fontSize: document.getElementById('terminal-fontsize'), }; + // Sidebar text-size selector. sm = 13px is the shipped default (do not change it — it is + // what every existing session renders at); the others step around it. Persisted per user + // in chrome.storage.local and applied to the live xterm. + const FONT_SIZES = { xs: 11, sm: 13, md: 15, lg: 17, xl: 20 }; + const FONT_SIZE_DEFAULT = 'sm'; + const FONT_SIZE_KEY = 'terminalFontSize'; + let fontSizeKey = FONT_SIZE_DEFAULT; // the chosen T-shirt size; px comes from FONT_SIZES + const fontPx = () => FONT_SIZES[fontSizeKey] || FONT_SIZES[FONT_SIZE_DEFAULT]; + /** State machine. */ const STATE = { IDLE: 'idle', @@ -392,7 +402,7 @@ if (term) return; term = new Terminal({ fontFamily: '"JetBrains Mono", "SF Mono", Menlo, "Noto Sans Mono CJK KR", "Malgun Gothic", monospace', - fontSize: 13, + fontSize: fontPx(), // the persisted size (default 13 = sm); see FONT_SIZES theme: { background: '#0a0a0a', foreground: '#e5e5e5' }, cursorBlink: true, scrollback: 5000, @@ -931,6 +941,40 @@ els.restart?.addEventListener('click', forceRestart); els.restartNow?.addEventListener('click', forceRestart); + // Re-measure the terminal and tell the PTY its new size. Same body the first-fit and the + // ResizeObserver use — a bare fitAddon.fit() is not enough: without the resize message + // claude keeps wrapping to the old cols/rows. + const refitTerminal = () => { + try { + fitAddon && fitAddon.fit(); + if (term && ws && ws.readyState === WebSocket.OPEN) { + ws.send(JSON.stringify({ type: 'resize', cols: term.cols, rows: term.rows })); + } + } catch {} + }; + + // Sidebar text-size selector: restore the saved size, then apply on change. Changing the + // font is asynchronous inside xterm — it re-measures the character cell on the next render + // frame, so fitting in the same tick reads the OLD cell and leaves the grid clipped (the + // bug that made a live change need a close/reopen). Two rAFs let the new metrics land + // before we fit + resize. If the terminal isn't up yet, ensureXterm reads fontPx() at + // creation, so no refit is needed then. + const applyFontSize = (key, persist) => { + fontSizeKey = FONT_SIZES[key] ? key : FONT_SIZE_DEFAULT; + if (els.fontSize) els.fontSize.value = fontSizeKey; + if (term) { + term.options.fontSize = fontPx(); + requestAnimationFrame(() => requestAnimationFrame(refitTerminal)); + } + if (persist) { try { chrome.storage.local.set({ [FONT_SIZE_KEY]: fontSizeKey }); } catch {} } + }; + els.fontSize?.addEventListener('change', (e) => applyFontSize(e.target.value, true)); + try { + chrome.storage.local.get([FONT_SIZE_KEY], (r) => applyFontSize(r?.[FONT_SIZE_KEY] || FONT_SIZE_DEFAULT, false)); + } catch { + applyFontSize(FONT_SIZE_DEFAULT, false); + } + // Live browser-tab state. background.js → sidepanel.js → us. We // forward over the live PTY WebSocket; terminal-agent.ts writes diff --git a/extension/sidepanel.css b/extension/sidepanel.css index 0bc306b256..bb70b822e0 100644 --- a/extension/sidepanel.css +++ b/extension/sidepanel.css @@ -719,6 +719,38 @@ body::after { color: #f59e0b; border-color: #f59e0b; } +/* Right cluster: text-size selector + Restart, kept together on the toolbar's right end. */ +.terminal-toolbar-right { + display: flex; + align-items: center; + gap: 6px; + flex-shrink: 0; +} +.terminal-fontsize { + display: flex; + align-items: center; + gap: 3px; + color: #a1a1aa; +} +/* The glyph is a size hint, not decoration — a small 'A' beside the picker. */ +.terminal-fontsize-icon { + font-family: 'JetBrains Mono', monospace; + font-size: 13px; + line-height: 1; +} +.terminal-fontsize select { + background: #0a0a0a; + color: #a1a1aa; + border: 1px solid #27272a; + border-radius: 3px; + padding: 3px 4px; + font-size: 11px; + font-family: 'JetBrains Mono', monospace; + cursor: pointer; + color-scheme: dark; /* keep the native option list dark, not a white flash over the panel */ +} +.terminal-fontsize select:hover { color: #f59e0b; border-color: #f59e0b; } +.terminal-fontsize select:focus-visible { outline: 2px solid #f59e0b; outline-offset: 1px; } .terminal-bootstrap { flex: 1; display: flex; diff --git a/extension/sidepanel.html b/extension/sidepanel.html index b2ce8a1b58..f99eed9f70 100644 --- a/extension/sidepanel.html +++ b/extension/sidepanel.html @@ -40,7 +40,19 @@ - +