Skip to content

feat(coding/tui): follow the terminal background and the desktop appearance - #34

Merged
rsbin1178 merged 3 commits into
mainfrom
feat/tui-theme-detection
Oct 8, 2026
Merged

rsbin1178 merged 3 commits into
mainfrom
feat/tui-theme-detection

Conversation

@rsbin1178

Copy link
Copy Markdown
Owner

Follow the terminal background and the desktop appearance

auto followed whatever the startup probe answered, so a profile switch during a
session left the picker's marks — and the surface stamp — describing a terminal the
user had already left, and a desktop that switched light/dark while the profile
stayed put changed nothing at all.

This consolidates what was reviewed as #28, #29 and #30 into one pull request, kept
separate from the theme-system work because it is the only part here whose
behaviour depends on what the terminal implements.

What changes

The picker re-asks (re-ask for the terminal background when the picker opens).
Opening /theme returns the same background query the startup probe uses, so the
marks describe the terminal as it is now; the answer takes the existing path,
re-stamping the canvas, re-rendering the visible transcript and redrawing the marks.
Bubble Tea gates that query on input being enabled, so no reply is left for the
shell, and a run with NO_COLOR asks for nothing because it paints no surfaces. The
cost is one eight-byte write and one asynchronous answer per picker open.

The desktop appearance (follow the desktop's light/dark appearance for auto).
The standard VT extension reports the desktop's preference: CSI ? 2031 h enables
unsolicited reports, and the terminal answers CSI ? 997 ; 1 n for dark and
CSI ? 997 ; 2 n for light. Startup enables the mode through a raw write, the model
handles the two decoded ultraviolet events, and the mode is reset with the other
interaction modes on entry and exit. A report sets the canvas and then asks for the
terminal's own background, which is what the surfaces and the marks describe: the
reported value stands when the terminal answers no query, and a background answer
that follows wins, so a dark desktop behind a light terminal profile keeps the light
canvas. The extension's one-shot query is deliberately not sent, because it expects
a reply and Bubble Tea only gates its own reply-carrying queries on input being
enabled. Nothing is polled.

Docs (say what auto follows and when the picker re-asks).

Evidence

  • A program-level test feeds ansi.LightDarkReport(true) and (false) through the
    real input stack and requires the ultraviolet events to arrive at Update (three
    consecutive runs, ~20 ms each). That is the one assumption the mechanism rests on,
    because Bubble Tea leaves the event unmapped and hands it through.
  • The model test pins that auto re-resolves on a report while an explicit
    selection keeps its id and re-stamps its surfaces, and that NO_COLOR ignores the
    report exactly as it ignores the background answer.
  • The terminal-mode test asserts the CSI ? 2031 l reset on exit.
  • The picker test asserts the request marker is among the commands /theme returns
    and that a NO_COLOR run asks for nothing at all.

TERM=xterm-256color go test ./... (89 packages), go test -race ./internal/coding/tui/ and the CI's last-commit lint scope (--new-from-rev=HEAD~)
are clean.

Terminal support for the extension varies — its own adoption table lists Contour,
Ghostty, Kitty, tmux, VTE 0.82+, Zellij, Helix, Neovim and Yazi, and not WezTerm,
Vim or iTerm2 — which is why the terminal background stays the primary reading and
its fallback, and why this work ships as its own pull request.

Based on the theme-system pull request, which must merge first.

@rsbin1178
rsbin1178 changed the base branch from feat/tui-theme-system to main October 8, 2026 13:06
@rsbin1178
rsbin1178 marked this pull request as ready for review October 8, 2026 13:06
…opens

The picker marks every row whose declared background is the opposite of the
terminal's, and it read the canvas from whatever the startup probe answered, so a
profile switch during a session left all of its marks describing the terminal the
user had already left. Opening the picker now returns the same background query
the startup probe uses: the answer takes the existing path, re-stamping the
canvas, re-rendering the visible transcript and redrawing the marks. Bubble Tea
gates that query on input being enabled, so no reply is left behind for the shell,
and a run with NO_COLOR still asks for nothing because it paints no surfaces.
The cost is one eight-byte write and one asynchronous answer per picker open.
`auto` followed the terminal's background colour, which a terminal reports once
and only re-reports when asked, so a desktop that switched appearance while the
profile stayed put left the theme where it was. The terminal can report that
preference: `CSI ? 2031 h` enables unsolicited reports and the terminal answers
`CSI ? 997 ; 1 n` for dark and `; 2 n` for light. Startup enables the mode through
a raw write, the model handles the two decoded events, and the mode is reset with
the other interaction modes on entry and exit.

A report sets the canvas and then asks for the terminal's own background, which is
what the canvas stamp, the painted surfaces and the picker marks describe: the
reported value stands when the terminal answers no query, and a background answer
that follows wins, so a dark desktop behind a light terminal profile keeps the
light canvas. The extension's one-shot query is deliberately not sent, because
that one does expect a reply and Bubble Tea only gates its own reply-carrying
queries on input being enabled. Nothing is polled: the terminal speaks when
something changes.

Measured: three runs of a program test that feeds `CSI ? 997 ; 1 n` and `; 2 n`
through the real input stack deliver the ultraviolet events to Update, and the
model test pins that auto re-resolves while an explicit selection keeps its id and
re-stamps its surfaces.
The theme section said `auto` follows "Bubble Tea's terminal background
detection" and left it there, which no longer describes the behaviour: the
terminal answers the background query itself, the picker re-asks it when it opens
so its marks describe the current terminal, and a terminal that reports the
desktop's light/dark appearance re-resolves `auto` when that changes.
@rsbin1178
rsbin1178 force-pushed the feat/tui-theme-detection branch from 3b3c4e4 to 0f4f934 Compare October 8, 2026 13:07
@rsbin1178
rsbin1178 merged commit 6e123fb into main Oct 8, 2026
8 checks passed
@rsbin1178
rsbin1178 deleted the feat/tui-theme-detection branch October 8, 2026 13:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant