From 3a1b500cefdcdcac688110b2ab218b0ed3c7dc21 Mon Sep 17 00:00:00 2001 From: Turtle Date: Mon, 20 Jul 2026 14:48:05 +0800 Subject: [PATCH] fix(tui): detect terminal color scheme and apply light-optimised palette --- packages/ui/tui/src/index.ts | 37 +++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/packages/ui/tui/src/index.ts b/packages/ui/tui/src/index.ts index ae0c480dde..41abe9c7d8 100644 --- a/packages/ui/tui/src/index.ts +++ b/packages/ui/tui/src/index.ts @@ -30,6 +30,7 @@ import { type OverlayHandle, type SelectListTheme, type Terminal, + type TerminalColorScheme, } from '@earendil-works/pi-tui' import type { Context } from 'cordis' import z from 'schemastery' @@ -199,17 +200,21 @@ function displayText(text: string): string { * backgrounds alike; grouping uses foreground-only gutter bars and reverse * video rather than fixed background fills. */ -function createPalette(enabled: boolean): Palette { +function createPalette(enabled: boolean, scheme: TerminalColorScheme = 'dark'): Palette { return { accent: ansi('94', '39', enabled), accent2: ansi('95', '39', enabled), text: text => text, muted: ansi('90', '39', enabled), - dim: ansi('2', '22', enabled), + // SGR 2 (dim) lightens text on a light background — substitute ANSI 90 + // (bright black / gray) which renders as a readable muted tone on any scheme. + dim: scheme === 'light' ? ansi('90', '39', enabled) : ansi('2', '22', enabled), success: ansi('32', '39', enabled), warning: ansi('33', '39', enabled), error: ansi('31', '39', enabled), - code: ansi('36', '39', enabled), + // ANSI 36 (cyan) is difficult to read on a light background — use + // ANSI 34 (blue) which is legible on both light and dark schemes. + code: scheme === 'light' ? ansi('34', '39', enabled) : ansi('36', '39', enabled), added: ansi('32', '39', enabled), removed: ansi('31', '39', enabled), bold: ansi('1', '22', enabled), @@ -1123,6 +1128,31 @@ export function createTuiChat( { name: 'exit', description: 'Exit after the active turn reaches idle' }, ], agent.session.header.cwd ?? process.cwd())) + /** Swap the palette and all derived themes for the given terminal color scheme. */ + const applyColorScheme = (scheme: TerminalColorScheme): void => { + if (scheme === currentScheme) return + currentScheme = scheme + Object.assign(palette, createPalette(resolved.color, scheme)) + Object.assign(mdTheme, markdownTheme(palette)) + editor.borderColor = text => palette.dim(text) + rebuildTranscript(false) + setStatus(agent.status) + requestRender() + } + let currentScheme: TerminalColorScheme = 'dark' + + // Detect the terminal's color scheme via device-status report. Most terminals + // do not respond, so the promise settles with `undefined` and we keep the + // dark-optimised palette. + ui.queryTerminalColorScheme({ timeoutMs: 2000 }).then((scheme) => { + if (scheme !== undefined) applyColorScheme(scheme) + }).catch(() => { + // Timeout or query failure — keep dark default. + }) + + // Live-update when the user switches their terminal theme behind us. + const disposeSchemeListener = ui.onTerminalColorSchemeChange(applyColorScheme) + const toggleTools = (): void => { toolsExpanded = !toolsExpanded for (const card of allToolCards) card.setExpanded(toolsExpanded) @@ -1271,6 +1301,7 @@ export function createTuiChat( disposeStatus() disposeError() disposeAgent() + disposeSchemeListener() } rebuildTranscript(true)