diff --git a/web/package-lock.json b/web/package-lock.json index 7e9739d92..5bd97eaee 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -77,7 +77,6 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -1128,7 +1127,6 @@ "resolved": "https://registry.npmjs.org/@observablehq/plot/-/plot-0.6.17.tgz", "integrity": "sha512-/qaXP/7mc4MUS0s4cPPFASDRjtsWp85/TbfsciqDgU1HwYixbSbbytNuInD8AcTYC3xaxACgVX06agdfQy9W+g==", "license": "ISC", - "peer": true, "dependencies": { "d3": "^7.9.0", "interval-tree-1d": "^1.0.0", @@ -4367,7 +4365,6 @@ "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.6.0.tgz", "integrity": "sha512-90abYK2q5/qDM+GACs9zRvc5KhEEpEWqWlHSd64zTPNxg+9wCJvTfyD9x2so7hlQhjRYO1Fa6flR3BC/kpTFkA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.17.8", "@types/webxr": "*", @@ -5073,7 +5070,6 @@ "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -5083,7 +5079,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -5094,7 +5089,6 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -5159,7 +5153,6 @@ "integrity": "sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.59.1", "@typescript-eslint/types": "8.59.1", @@ -5488,7 +5481,6 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5653,7 +5645,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -6161,7 +6152,6 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -6487,7 +6477,6 @@ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -6902,8 +6891,7 @@ "version": "3.15.0", "resolved": "https://registry.npmjs.org/gsap/-/gsap-3.15.0.tgz", "integrity": "sha512-dMW4CWBTUK1AEEDeZc1g4xpPGIrSf9fJF960qbTZmN/QwZIWY5wgliS6JWl9/25fpTGJrMRtSjGtOmPnfjZB+A==", - "license": "Standard 'no charge' license: https://gsap.com/standard-license.", - "peer": true + "license": "Standard 'no charge' license: https://gsap.com/standard-license." }, "node_modules/has-flag": { "version": "4.0.0", @@ -7218,7 +7206,6 @@ "resolved": "https://registry.npmjs.org/leva/-/leva-0.10.1.tgz", "integrity": "sha512-BcjnfUX8jpmwZUz2L7AfBtF9vn4ggTH33hmeufDULbP3YgNZ/C+ss/oO3stbrqRQyaOmRwy70y7BGTGO81S3rA==", "license": "MIT", - "peer": true, "dependencies": { "@radix-ui/react-portal": "^1.1.4", "@radix-ui/react-tooltip": "^1.1.8", @@ -7626,7 +7613,6 @@ "resolved": "https://registry.npmjs.org/motion/-/motion-12.38.0.tgz", "integrity": "sha512-uYfXzeHlgThchzwz5Te47dlv5JOUC7OB4rjJ/7XTUgtBZD8CchMN8qEJ4ZVsUmTyYA44zjV0fBwsiktRuFnn+w==", "license": "MIT", - "peer": true, "dependencies": { "framer-motion": "^12.38.0", "tslib": "^2.4.0" @@ -7699,7 +7685,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": "^20.0.0 || >=22.0.0" } @@ -7827,7 +7812,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -8041,7 +8025,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -8061,7 +8044,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -8491,8 +8473,7 @@ "version": "0.180.0", "resolved": "https://registry.npmjs.org/three/-/three-0.180.0.tgz", "integrity": "sha512-o+qycAMZrh+TsE01GqWUxUIKR1AL0S8pq7zDkYOQw8GqfX8b8VoCKYUoHbhiX5j+7hr8XsuHDVU6+gkQJQKg9w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tinyglobby": { "version": "0.2.16", @@ -8557,7 +8538,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -8714,7 +8694,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -8836,7 +8815,6 @@ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/web/src/pages/ChatPage.tsx b/web/src/pages/ChatPage.tsx index af993c670..5d62f8d68 100644 --- a/web/src/pages/ChatPage.tsx +++ b/web/src/pages/ChatPage.tsx @@ -36,6 +36,7 @@ import { usePageHeader } from "@/contexts/usePageHeader"; import { useI18n } from "@/i18n"; import { api } from "@/lib/api"; import { PluginSlot } from "@/plugins"; +import { useTheme } from "@/themes"; function buildWsUrl( authParam: [string, string], @@ -66,8 +67,9 @@ function generateChannelId(): string { // with cream foreground — we intentionally don't pick monokai or a loud // theme, because the TUI's skin engine already paints the content; the // terminal chrome just needs to sit quietly inside the dashboard. -const TERMINAL_THEME = { - background: "#0d2626", +// `background` is omitted here — it's supplied dynamically from the active +// theme's `terminalBackground` field so users can control it via YAML themes. +const TERMINAL_THEME_STATIC = { foreground: "#f0e6d2", cursor: "#f0e6d2", cursorAccent: "#0d2626", @@ -157,6 +159,13 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) { : false, ); + const { theme } = useTheme(); + const terminalBg = theme.terminalBackground ?? "#000000"; + const terminalTheme = useMemo( + () => ({ ...TERMINAL_THEME_STATIC, background: terminalBg }), + [terminalBg], + ); + // The dashboard keeps ChatPage mounted persistently so the PTY survives tab // switches. That is great for ordinary /chat navigation, but it means query // param changes do NOT remount the component. Resume-in-chat from the @@ -312,7 +321,7 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) { // Browser-embedded chat runs the TUI in inline mode. Keep transcript // history in xterm.js so the browser wheel can scroll it directly. scrollback: 5000, - theme: TERMINAL_THEME, + theme: terminalTheme, }); termRef.current = term; @@ -721,6 +730,14 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) { }; }, [isActive]); + // Keep the live xterm theme in sync when the active theme's terminal + // background changes (e.g. user switches to a custom YAML theme mid-session). + useEffect(() => { + const term = termRef.current; + if (!term) return; + term.options.theme = { ...TERMINAL_THEME_STATIC, background: terminalBg }; + }, [terminalBg]); + // Layout: // outer flex column — sits inside the dashboard's content area // row split — terminal pane (flex-1) + sidebar (fixed width, lg+) @@ -828,7 +845,7 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) { "p-2 sm:p-3", )} style={{ - backgroundColor: TERMINAL_THEME.background, + backgroundColor: terminalBg, boxShadow: "0 8px 32px rgba(0, 0, 0, 0.4)", }} > @@ -852,7 +869,7 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) { "bottom-2 right-2 px-2 py-1 text-xs sm:bottom-3 sm:right-3 sm:px-2.5 sm:py-1.5", "lg:bottom-4 lg:right-4", )} - style={{ color: TERMINAL_THEME.foreground }} + style={{ color: TERMINAL_THEME_STATIC.foreground }} > diff --git a/web/src/themes/context.tsx b/web/src/themes/context.tsx index 3af7fbe5b..4d5adf864 100644 --- a/web/src/themes/context.tsx +++ b/web/src/themes/context.tsx @@ -297,6 +297,12 @@ function applyTheme(theme: DashboardTheme) { injectFontStylesheet(theme.typography.fontUrl); applyCustomCSS(theme.customCSS); applyLayoutVariant(theme.layoutVariant); + + // Terminal background — read by ChatPage via useTheme(); also available as CSS var. + root.style.setProperty( + "--theme-terminal-background", + theme.terminalBackground ?? "#0d2626", + ); } // --------------------------------------------------------------------------- diff --git a/web/src/themes/presets.ts b/web/src/themes/presets.ts index 7baf6319d..35f5e028b 100644 --- a/web/src/themes/presets.ts +++ b/web/src/themes/presets.ts @@ -51,6 +51,7 @@ export const defaultTheme: DashboardTheme = { }, typography: DEFAULT_TYPOGRAPHY, layout: DEFAULT_LAYOUT, + terminalBackground: "#000000", }; export const midnightTheme: DashboardTheme = { diff --git a/web/src/themes/types.ts b/web/src/themes/types.ts index 2f0dd2983..dc9d69041 100644 --- a/web/src/themes/types.ts +++ b/web/src/themes/types.ts @@ -162,6 +162,9 @@ export interface DashboardTheme { /** Per-component CSS-var overrides. See `ThemeComponentStyles`. */ componentStyles?: ThemeComponentStyles; colorOverrides?: ThemeColorOverrides; + /** Background color for the embedded terminal pane (xterm.js). + * Hex string. Defaults to `"#0d2626"` when absent. */ + terminalBackground?: string; } /**