Skip to content

frontend: plugins: docs: e2e-tests: a11y: Fix terminals for themes#5671

Merged
illume merged 1 commit into
kubernetes-sigs:mainfrom
illume:xterm-a11y
May 19, 2026
Merged

frontend: plugins: docs: e2e-tests: a11y: Fix terminals for themes#5671
illume merged 1 commit into
kubernetes-sigs:mainfrom
illume:xterm-a11y

Conversation

@illume
Copy link
Copy Markdown
Contributor

@illume illume commented May 15, 2026

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 on AppTheme. Anything unset is auto-derived; plugins only specify what they want to change. Inline on the same registerAppTheme(...) call, no extra function needed.

Internal plumbing

HeadlampTerminal added to the MUI Palette interface; createMuiTheme writes currentTheme.terminal onto palette.terminal for both light and dark variants. Wired into the three xterm consumers (Terminal, useTerminalStream, LogViewer) with a separate effect that mutates xterm.options.theme on theme change so live sessions don't tear down. getXtermTheme is internal (removed from components/common barrel + pluginLib.snapshot); AppTheme is re-exported from @kinvolk/headlamp-plugin/lib so plugin authors can type their themes.

Auto-derived ANSI palette

getXtermTheme() picks one of two reference palettes (light-bg-friendly / dark-bg-friendly) based on getLuminance(background) > 0.5 rather than palette.mode, so a "light" theme that pins a dark terminal.background still gets a usable palette automatically. Each reference color is then run through an ensureVisible() helper that darkens (light bg) or lightens (dark bg) in 10% steps via MUI's darken/lighten until 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

selectionBackground and selectionInactiveBackground now key off the actual terminal background's luminance (bgIsLight) rather than palette.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 use palette.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 inline terminal: override, the background.muted fallback, and two regression tests: white/brightWhite stay visible on a light bg, and the auto-palette picks dark colors when terminal.background is dark in a base: '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 registration customThemeWithTerminal demonstrates the canonical use case (a dark terminal inside an otherwise light theme). The example imports AppTheme directly from @kinvolk/headlamp-plugin/lib/lib/AppTheme and intersects it with a local terminal?: shape so it builds against the currently-published headlamp-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 optional terminal field on AppTheme, links to the custom-theme example, and a screenshot of the log viewer in the built-in light theme (committed under docs/development/plugins/functionality/images/themed-xterm/).

Playwright e2e + axe a11y coverage

e2e-tests/tests/themedXterm.spec.ts parametrized over route ∈ {logs, exec, nodeShell} × theme ∈ {light, dark} (6 tests, covering all three xterm consumers including NodeShellTerminal). Seeds localStorage.headlampThemePreference via addInitScript, asserts .xterm-viewport luminance matches the chosen theme, and runs AxeBuilder against 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.

registerAppTheme({
  name: 'my custom theme with terminal',
  base: 'light',
  // …palette overrides…
  terminal: {
    background: '#1e1e1e',
    foreground: '#f5f5f5',
    cursor: '#ffcc00',
    ansi: { red: '#ff5555', green: '#50fa7b', blue: '#8be9fd' /* … */ },
  },
});

Steps to Test

  1. Open a pod's log viewer, exec terminal, and a node shell in light mode. Backgrounds should match the surrounding surface instead of being black.
  2. Toggle between light/dark/custom themes while a viewer/terminal is open. Colors should update live without disconnecting the session. With the LogViewer find popover open and an active search, existing match highlights and overview-ruler markers should also re-color immediately on theme toggle.
  3. Open the LogViewer find popover in light and dark mode. Input, toggle buttons, match highlights, text, popover/input backgrounds, and disabled-state colors should all match the active theme.
  4. Select text in a dark terminal that's embedded in a light app theme (or vice versa). The selection overlay should stay visible against the terminal background.
  5. In Settings → General select "my custom theme with terminal" (after running the custom-theme example). The rest of the app stays light while the terminal stays dark and stays readable (white/brightWhite ANSI output is still visible).
  6. From 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.
  7. Render the updated docs/development/plugins/functionality/index.md and confirm the new "App Theme" paragraph mentioning the terminal field appears in the correct spot.

Screenshots (if applicable)

Built-in: light — xterm bg rgb(245,245,245)#f5f5f5, ANSI 37/97 stay readable

themed-xterm-light

Built-in: dark — xterm bg rgb(51,51,51)#333333

themed-xterm-dark

Plugin: my custom theme (light app, no terminal: override — terminal auto-derives from MUI palette)

themed-xterm-custom

Plugin: my custom theme with terminal (light app chrome at rgb(255,255,255), dark terminal at rgb(30,30,30)#1e1e1e via inline terminal: field — the canonical "dark terminal in a light theme" case)

themed-xterm-custom-with-terminal

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
@k8s-ci-robot k8s-ci-robot added size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. labels May 15, 2026
@illume illume added frontend Issues related to the frontend a11y Accessibility related issues labels May 15, 2026
@illume illume requested a review from Copilot May 15, 2026 12:47
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 an ITheme from the MUI palette, picks a light/dark ANSI reference palette by actual background luminance, and contrast-clamps colors via darken/lighten.
  • Wires the derived theme into Terminal, useTerminalStream and LogViewer (including live updates on theme change), re-themes the SearchPopover chrome, and exposes AppTheme.terminal plus the new palette.terminal type augmentation.
  • Adds unit tests (xtermTheme.test.ts), Playwright + axe e2e coverage (themedXterm.spec.ts), an extended custom-theme plugin 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.

Copy link
Copy Markdown
Contributor

@skoeva skoeva left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@k8s-ci-robot
Copy link
Copy Markdown
Contributor

[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

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@illume illume merged commit 77dbdf4 into kubernetes-sigs:main May 19, 2026
15 of 16 checks passed
@illume illume deleted the xterm-a11y branch May 19, 2026 06:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a11y Accessibility related issues approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. frontend Issues related to the frontend needs review size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants