frontend: plugins: docs: e2e-tests: a11y: Fix terminals for themes#5671
Conversation
Introduce xtermTheme.ts to derive xterm colour palettes from the active MUI theme, so Terminal and LogViewer automatically switch between light and dark (and custom) themes instead of using hard-coded colours. - Add xtermTheme.ts with palette derivation and unit tests - Add terminalTheme / logViewerTheme fields to AppTheme and themes.ts so plugin authors can provide per-theme xterm overrides - Update Terminal.tsx and LogViewer.tsx to consume the theme-derived palette; update useTerminalStream to propagate theme changes - Update custom-theme example plugin with themeAccessibility helpers, an xterm theme demo, and expanded README - Export new theme utilities from headlamp-plugin index - Add e2e tests (themedXterm.spec.ts) and docs screenshots covering light, dark, and custom theme variants - Update Terminal storyshots and PodLogs storyshots for new defaults
There was a problem hiding this comment.
Pull request overview
This PR makes the xterm.js-based terminal/log viewers (pod logs, pod exec, node shell) follow the active MUI theme instead of rendering as hardcoded black boxes. It also extends the plugin AppTheme API with an optional terminal field so plugin authors can override terminal colors inline, with sensible auto-derived defaults that auto-clamp for contrast against the chosen background.
Changes:
- New internal
getXtermTheme(muiTheme)helper that derives anIThemefrom the MUI palette, picks a light/dark ANSI reference palette by actual background luminance, and contrast-clamps colors viadarken/lighten. - Wires the derived theme into
Terminal,useTerminalStreamandLogViewer(including live updates on theme change), re-themes theSearchPopoverchrome, and exposesAppTheme.terminalplus the newpalette.terminaltype augmentation. - Adds unit tests (
xtermTheme.test.ts), Playwright + axe e2e coverage (themedXterm.spec.ts), an extendedcustom-themeplugin example, docs, and updated Storybook snapshot.
Reviewed changes
Copilot reviewed 14 out of 31 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/src/components/common/xtermTheme.ts | New helper that builds the xterm ITheme from the MUI palette with auto-derived/contrast-clamped ANSI colors. |
| frontend/src/components/common/xtermTheme.test.ts | Unit tests asserting WCAG-AA foreground/cursor and visibility of all ANSI colors. |
| frontend/src/components/common/Terminal.tsx | Reads xtermTheme from the MUI theme, applies it on creation and on theme change. |
| frontend/src/components/common/LogViewer.tsx | Same wiring for LogViewer; de-hardcodes search popover/decoration colors; re-runs search-decoration effect on resolved color changes. |
| frontend/src/lib/k8s/useTerminalStream.ts | Applies the derived xterm theme inside the streaming terminal. |
| frontend/src/lib/themes.ts | Adds HeadlampTerminal interface and palette.terminal augmentation; copies currentTheme.terminal into light/dark palettes. |
| frontend/src/lib/AppTheme.ts | Adds the public optional terminal field on AppTheme with docs. |
| frontend/src/components/common/index.test.ts | Excludes xtermTheme from the public common-exports check. |
| frontend/src/components/common/snapshots/Terminal.TerminalDisconnected.stories.storyshot | Snapshot updated for the new themed defaults. |
| plugins/headlamp-plugin/src/index.ts | Re-exports the AppTheme type from the plugin entry. |
| plugins/examples/custom-theme/src/themes.ts | Extracts themes and adds customThemeWithTerminal example using inline terminal: overrides. |
| plugins/examples/custom-theme/src/index.tsx | Registers both example themes. |
| plugins/examples/custom-theme/README.md | Documents the terminal override and accessibility guidance. |
| docs/development/plugins/functionality/index.md | Adds an "App Theme → terminal" paragraph and a screenshot. |
| e2e-tests/tests/themedXterm.spec.ts | Playwright + axe coverage of logs/exec/nodeShell × light/dark. |
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: illume, skoeva The full list of commands accepted by this bot can be found here. The pull request process is described here DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
Summary
This PR makes the xterm-based terminal/log surfaces (pod log viewer, pod exec terminal, node shell) follow the active MUI theme so they no longer render as a hardcoded black box inside light or custom themes. It also extends the public plugin theme API so plugin authors can override terminal colors inline on
AppTheme, with sensible auto-derived defaults that stay readable on any background.Changes
AppTheme.terminal(public plugin API)Optional
{ background, foreground, cursor, ansi: { …16 colors } }field onAppTheme. Anything unset is auto-derived; plugins only specify what they want to change. Inline on the sameregisterAppTheme(...)call, no extra function needed.Internal plumbing
HeadlampTerminaladded to the MUIPaletteinterface;createMuiThemewritescurrentTheme.terminalontopalette.terminalfor both light and dark variants. Wired into the three xterm consumers (Terminal,useTerminalStream,LogViewer) with a separate effect that mutatesxterm.options.themeon theme change so live sessions don't tear down.getXtermThemeis internal (removed fromcomponents/commonbarrel +pluginLib.snapshot);AppThemeis re-exported from@kinvolk/headlamp-plugin/libso plugin authors can type their themes.Auto-derived ANSI palette
getXtermTheme()picks one of two reference palettes (light-bg-friendly / dark-bg-friendly) based ongetLuminance(background) > 0.5rather thanpalette.mode, so a "light" theme that pins a darkterminal.backgroundstill gets a usable palette automatically. Each reference color is then run through anensureVisible()helper that darkens (light bg) or lightens (dark bg) in 10% steps via MUI'sdarken/lightenuntil it has ≥ 2.5:1 contrast against the actual background. Foreground/cursor get the same auto-clamp when the MUI-derived value would be unreadable on the chosen bg.Selection overlay luminance-aware
selectionBackgroundandselectionInactiveBackgroundnow key off the actual terminal background's luminance (bgIsLight) rather thanpalette.mode, so a dark terminal embedded in a light app theme still gets a visible selection highlight.Light-bg ANSI table tuned
white→#888a85(was#d3d7cf),brightWhite→#2e3436(was#eeeeec) so ANSI 37 / 97 output remains visible on light surfaces.LogViewer search popover de-hardcoded
#cccccc(text),#252526(popover bg),#3c3c3c(input bg) now usepalette.text.primary,palette.background.paper,palette.action.hover, so plugin-provided dark themes flow through the search UI too.Search decorations re-theme on theme toggle
The LogViewer find effect now depends on the four resolved decoration colors (
matchBackground,activeMatchBackground,matchOverviewRuler,activeMatchColorOverviewRuler), so existing match highlights and overview-ruler markers update when the user switches themes mid-search instead of waiting for the next find action.Frontend accessibility unit tests
frontend/src/components/common/xtermTheme.test.ts(8 tests) covers built-in light/dark, two plugin-registered custom themes, an inlineterminal:override, thebackground.mutedfallback, and two regression tests:white/brightWhitestay visible on a light bg, and the auto-palette picks dark colors whenterminal.backgroundis dark in abase: 'light'theme. Asserts WCAG 2.1 AA (4.5:1) on foreground+cursor and ≥ 2:1 on every ANSI color against the chosen background.Custom-theme plugin example extended
Themes moved to
plugins/examples/custom-theme/src/themes.ts; a second registrationcustomThemeWithTerminaldemonstrates the canonical use case (a dark terminal inside an otherwise light theme). The example importsAppThemedirectly from@kinvolk/headlamp-plugin/lib/lib/AppThemeand intersects it with a localterminal?:shape so it builds against the currently-publishedheadlamp-plugin. README updated with "Overriding terminal (xterm) colors" and "Making sure the colors are accessible" sections.Plugin docs updated
docs/development/plugins/functionality/index.md"App Theme" subsection now includes a short note about the optionalterminalfield onAppTheme, links to thecustom-themeexample, and a screenshot of the log viewer in the built-in light theme (committed underdocs/development/plugins/functionality/images/themed-xterm/).Playwright e2e + axe a11y coverage
e2e-tests/tests/themedXterm.spec.tsparametrized overroute ∈ {logs, exec, nodeShell}×theme ∈ {light, dark}(6 tests, covering all three xterm consumers includingNodeShellTerminal). SeedslocalStorage.headlampThemePreferenceviaaddInitScript, asserts.xterm-viewportluminance matches the chosen theme, and runsAxeBuilderagainst the full open xterm activity, excluding only xterm.js's own canvas/text/helper layers and the reused sidebar/topbar, so a11y regressions in the surrounding chrome are also caught.Steps to Test
custom-themeexample). The rest of the app stays light while the terminal stays dark and stays readable (white/brightWhiteANSI output is still visible).e2e-tests/:npx playwright test themedXterm.spec.ts— 6 cases (logs/exec/nodeShell × light/dark) verify viewport luminance and run axe against the full open xterm activity chrome.docs/development/plugins/functionality/index.mdand confirm the new "App Theme" paragraph mentioning theterminalfield appears in the correct spot.Screenshots (if applicable)
Built-in:
light— xterm bgrgb(245,245,245)≈#f5f5f5, ANSI 37/97 stay readableBuilt-in:
dark— xterm bgrgb(51,51,51)≈#333333Plugin:
my custom theme(light app, noterminal:override — terminal auto-derives from MUI palette)Plugin:
my custom theme with terminal(light app chrome atrgb(255,255,255), dark terminal atrgb(30,30,30)≈#1e1e1evia inlineterminal:field — the canonical "dark terminal in a light theme" case)