diff --git a/src/gateway/terminal/intro-banner.test.ts b/src/gateway/terminal/intro-banner.test.ts index c93ea812c1b1..8eafcccd860d 100644 --- a/src/gateway/terminal/intro-banner.test.ts +++ b/src/gateway/terminal/intro-banner.test.ts @@ -25,9 +25,9 @@ describe("composeTerminalIntroBanner", () => { const banner = composeTerminalIntroBanner(); expect(banner).toBe( - `\r\n\x1b[38;5;223mWelcome to the Claw.\x1b[0m\r\n\r\n\x1b[38;5;216m${EXPECTED_ART.join("\r\n")}\r\n\r\n\x1b[0m`, + `\r\n\x1b[33mWelcome to the Claw.\x1b[0m\r\n\r\n\x1b[91m${EXPECTED_ART.join("\r\n")}\r\n\r\n\x1b[0m`, ); - expect(banner.startsWith("\r\n\x1b[38;5;223mWelcome to the Claw.\x1b[0m")).toBe(true); + expect(banner.startsWith("\r\n\x1b[33mWelcome to the Claw.\x1b[0m")).toBe(true); expect(banner.endsWith("\r\n\r\n\x1b[0m")).toBe(true); expect(banner.replaceAll("\r\n", "")).not.toContain("\n"); }); diff --git a/src/gateway/terminal/intro-banner.ts b/src/gateway/terminal/intro-banner.ts index 95b225651c9c..0e8b6c6d3005 100644 --- a/src/gateway/terminal/intro-banner.ts +++ b/src/gateway/terminal/intro-banner.ts @@ -22,8 +22,11 @@ const TERMINAL_INTRO_ART = [ // Always full art: open-time request.cols is the pre-fit boot grid (the client // resizes immediately after open), so width gating keyed on it suppressed the // art on real, wide terminals. +// ANSI-16 colors only: the server can't know the client's light/dark mode, and +// fixed 256-color indices (223/216) bypass the client theme and vanish on light +// backgrounds. Yellow/bright-red stay warm on dark and darken on light. export function composeTerminalIntroBanner(): string { - const headline = `\x1b[38;5;223mWelcome to the Claw.${RESET}`; - const art = `\x1b[38;5;216m${TERMINAL_INTRO_ART.join("\r\n")}\r\n\r\n`; + const headline = `\x1b[33mWelcome to the Claw.${RESET}`; + const art = `\x1b[91m${TERMINAL_INTRO_ART.join("\r\n")}\r\n\r\n`; return `\r\n${headline}\r\n\r\n${art}${RESET}`; } diff --git a/ui/src/components/terminal/terminal-theme.ts b/ui/src/components/terminal/terminal-theme.ts index a756432658c3..ac8b9bbaeaf0 100644 --- a/ui/src/components/terminal/terminal-theme.ts +++ b/ui/src/components/terminal/terminal-theme.ts @@ -8,9 +8,10 @@ type TerminalTheme = NonNullable< NonNullable["theme"] >; -// ANSI palette tuned to sit on the Control UI's near-black / near-white surfaces. -// Shared 8 + bright 8; foreground/background/cursor are overridden per mode below. -const ANSI = { +// ANSI palettes tuned per mode: colors that read on the near-black surface sit +// at 1.4-2.6:1 on the light surface, so light gets its own darkened set +// (>=4.5:1 on #f7f8fa) instead of sharing the dark palette. +const DARK_ANSI = { black: "#1b1e26", red: "#ff6b6b", green: "#4ec9a8", @@ -29,6 +30,29 @@ const ANSI = { brightWhite: "#ffffff", } as const; +// Same hue identities as DARK_ANSI, darkened for the light surface. Bright +// variants go darker than normal ones so bold/bright text stays emphatic +// instead of washing out; brightWhite maps to the strongest ink, matching +// the light-theme convention in Ghostty/iTerm paired themes. +const LIGHT_ANSI = { + black: "#3a3f4b", + red: "#c62f3d", + green: "#177a5e", + yellow: "#8f6400", + blue: "#1e66d0", + magenta: "#94439c", + cyan: "#0f7487", + white: "#1b1e26", + brightBlack: "#5c6370", + brightRed: "#a3242f", + brightGreen: "#0f664e", + brightYellow: "#755200", + brightBlue: "#1a55ab", + brightMagenta: "#7c3382", + brightCyan: "#0c6070", + brightWhite: "#0a0c10", +} as const; + // Dark mirrors the claw tokens in styles/base.css (`--bg` #0e1015, // `--accent` #ff5c5c) — keep them in sync when the tokens change. Light is a // deliberate neutral cool white: the light theme families diverge (ivory, @@ -47,16 +71,14 @@ export function terminalTheme(mode: "dark" | "light"): TerminalTheme { const colors = terminalDynamicColors(mode); if (mode === "light") { return { - ...ANSI, + ...LIGHT_ANSI, ...colors, cursorAccent: "#f7f8fa", - selectionBackground: "rgba(90, 162, 255, 0.30)", - black: "#3a3f4b", - white: "#1b1e26", + selectionBackground: "rgba(30, 102, 208, 0.30)", }; } return { - ...ANSI, + ...DARK_ANSI, ...colors, cursorAccent: "#0e1015", selectionBackground: "rgba(90, 162, 255, 0.32)",