Skip to content

TUI: add opt-in "auto" theme that follows the terminal's light/dark background (incl. live switching) #3500

Description

@aheritier

Problem

docker-agent always starts with the dark built-in default theme. Users whose terminal follows the OS appearance (e.g. Ghostty with theme = light:...,dark:..., switching by time of day) get a dark TUI on a light terminal background — illegible and incoherent. There is currently no terminal-background detection anywhere: the theme is resolved once, pre-TUI, in cmd/root/run.go applyTheme() (--theme flag > settings.theme > default).

Proposal (no change to default behavior)

Add an explicit, opt-in theme value auto (accepted in settings.theme, --theme auto, and as an "Auto (match terminal)" entry in the /theme picker). When — and only when — auto is selected:

  1. Startup detection (works in any terminal answering OSC 11): synchronous lipgloss.BackgroundColor(os.Stdin, os.Stdout) query in applyTheme(), gated on TTY, short timeout, dark fallback. One implementation point covers the full TUI, --lean, docker agent board, the agent picker and docker-agent new, and avoids a wrong-theme first frame.
  2. Live switching (Ghostty, kitty, contour, …): in the full TUI, enable DEC mode 2031 (tea.Raw(ansi.SetModeLightDark)) in appModel.Init and handle the resulting uv.DarkColorSchemeEvent / uv.LightColorSchemeEvent (bubbletea v2.0.8 passes them through translateInputEvent untyped; the ultraviolet decoder already parses CSI ?997;1|2 n). Apply the theme on the TUI goroutine via the existing styles.ApplyTheme + ThemeChangedMsg invalidation path. Reset mode 2031 on exit. Optionally re-issue tea.RequestBackgroundColor() on tea.FocusMsg for terminals without 2031.
  3. Theme pair resolution: auto resolves dark → settings.theme_dark (default default), light → settings.theme_light (default: new bundled default-light theme, to be authored with contrast checks against pkg/tui/styles/colorutil.go helpers).

Explicitly out of scope / guarantees

  • Unset settings.theme keeps meaning the dark default — zero behavior change and zero extra startup queries for existing users.
  • tmux/pipes/CI: query is skipped (non-TTY) or times out → dark fallback.
  • Live 2031 handling inside the lean TUI's custom event loop is a follow-up.

Implementation plan

A detailed step-by-step plan (files, functions, tests, docs, risks) exists in the team's shared plan store under adaptive-terminal-theme; summary of steps: (1) AutoThemeRef sentinel + polarity state + resolver in pkg/tui/styles; (2) author themes/default-light.yaml + contrast test; (3) theme_dark/theme_light in pkg/userconfig; (4) startup detection in applyTheme + --theme auto validation/completion; (5) live switching + mode reset in pkg/tui/tui.go; (6) picker "Auto" entry + persistence; (7) tests; (8) docs (docs/features/tui/index.md, docs/features/cli/index.md). Can ship as one PR or split: startup coherence first, live switching second.

Acceptance criteria

  • Without config changes, behavior and emitted escape sequences are byte-identical to today.
  • theme: auto + light Ghostty → light theme at launch; dark Ghostty → dark theme.
  • Flipping the terminal/OS appearance while running switches the TUI live (mode 2031 terminals).
  • --theme auto accepted, validated, shell-completed; /theme picker offers Auto and persists it.
  • Non-TTY / tmux: dark fallback, no hang; mode 2031 reset on exit (no stray ?997 reports in the shell afterwards).
  • task build && task test && task lint clean; docs updated in the same PR.

Metadata

Metadata

Assignees

Labels

area/cliCLI commands, flags, output formattingarea/configFor configuration parsing, YAML, environment variablesarea/docsDocumentation changesarea/tuiFor features/issues/fixes related to the TUIkind/featPR adds a new feature (maps to feat:). Use on PRs only.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions