From eb68d66ff9bb5e86c4a1ffaf27ebe84d2bfabe91 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Sat, 16 May 2026 20:40:55 -0500 Subject: [PATCH] feat(desktop): theme xterm with active light/dark mode The right-sidebar terminal hardcoded a light palette, which read poorly on the dark glass surface. Subscribe to `useTheme().resolvedMode` and hot-swap `term.options.theme` so Shift+X (and any other mode change) updates the terminal in place without tearing down the PTY session. Dark mode uses xterm's built-in defaults (white fg/cursor + vivid ANSI 16) with just a transparent background so the glass shows through; light mode keeps the existing hand-tuned overrides for legibility on a bright surface. --- .../app/right-sidebar/terminal/selection.ts | 13 ++++++++--- .../terminal/use-terminal-session.ts | 23 ++++++++++++------- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/apps/desktop/src/app/right-sidebar/terminal/selection.ts b/apps/desktop/src/app/right-sidebar/terminal/selection.ts index 23c989dda6..99169fe26f 100644 --- a/apps/desktop/src/app/right-sidebar/terminal/selection.ts +++ b/apps/desktop/src/app/right-sidebar/terminal/selection.ts @@ -1,14 +1,21 @@ -import type { Terminal } from '@xterm/xterm' +import type { ITheme, Terminal } from '@xterm/xterm' import type { CSSProperties } from 'react' -export const TERMINAL_THEME = { - background: '#00000000', +// xterm's defaults (white fg/cursor + vivid ANSI 16) already match a dark +// terminal — we just punch the background transparent so the glass shows. +// Light mode has no built-in, so hand-tune fg/cursor for a bright surface. +const TRANSPARENT_BG: ITheme = { background: '#00000000' } + +const LIGHT_OVERRIDES: ITheme = { cursor: '#6f6f6f', cursorAccent: '#f7f7f7', foreground: '#4d4d4d', selectionBackground: '#8c8c8c33' } +export const terminalTheme = (mode: 'light' | 'dark'): ITheme => + mode === 'dark' ? TRANSPARENT_BG : { ...TRANSPARENT_BG, ...LIGHT_OVERRIDES } + export const isMacPlatform = () => navigator.platform.toLowerCase().includes('mac') export const addSelectionShortcutLabel = () => (isMacPlatform() ? '⌘L' : 'Ctrl+L') diff --git a/apps/desktop/src/app/right-sidebar/terminal/use-terminal-session.ts b/apps/desktop/src/app/right-sidebar/terminal/use-terminal-session.ts index f3a1a1f06d..cedb05416b 100644 --- a/apps/desktop/src/app/right-sidebar/terminal/use-terminal-session.ts +++ b/apps/desktop/src/app/right-sidebar/terminal/use-terminal-session.ts @@ -6,13 +6,9 @@ import { useCallback, useEffect, useRef, useState } from 'react' import type { CSSProperties } from 'react' import { triggerHaptic } from '@/lib/haptics' +import { useTheme } from '@/themes/context' -import { - isAddSelectionShortcut, - TERMINAL_THEME, - terminalSelectionAnchor, - terminalSelectionLabel -} from './selection' +import { isAddSelectionShortcut, terminalSelectionAnchor, terminalSelectionLabel, terminalTheme } from './selection' type TerminalStatus = 'closed' | 'open' | 'starting' @@ -29,6 +25,8 @@ export function useTerminalSession({ cwd, onAddSelectionToChat }: UseTerminalSes const selectionLabelRef = useRef('') const selectionRef = useRef('') const onAddSelectionToChatRef = useRef(onAddSelectionToChat) + const { resolvedMode } = useTheme() + const resolvedModeRef = useRef(resolvedMode) const [status, setStatus] = useState('starting') const [selection, setSelection] = useState('') const [selectionStyle, setSelectionStyle] = useState(null) @@ -38,11 +36,20 @@ export function useTerminalSession({ cwd, onAddSelectionToChat }: UseTerminalSes onAddSelectionToChatRef.current = onAddSelectionToChat }, [onAddSelectionToChat]) + useEffect(() => { + resolvedModeRef.current = resolvedMode + + if (termRef.current) { + termRef.current.options.theme = terminalTheme(resolvedMode) + } + }, [resolvedMode]) + const addSelectionToChat = useCallback(() => { const selectedText = selectionRef.current || termRef.current?.getSelection() || '' const label = - selectionLabelRef.current || (termRef.current ? terminalSelectionLabel(termRef.current, shellNameRef.current, selectedText) : 'selection') + selectionLabelRef.current || + (termRef.current ? terminalSelectionLabel(termRef.current, shellNameRef.current, selectedText) : 'selection') const trimmed = selectedText.trim() @@ -102,7 +109,7 @@ export function useTerminalSession({ cwd, onAddSelectionToChat }: UseTerminalSes lineHeight: 1.12, macOptionIsMeta: true, scrollback: 1000, - theme: TERMINAL_THEME + theme: terminalTheme(resolvedModeRef.current) }) const fit = new FitAddon()