feat(tui): OSC 11 background-color query for theme auto-detect (theme iter 3)#183
Merged
feat(tui): OSC 11 background-color query for theme auto-detect (theme iter 3)#183
Conversation
Closes #176. Iter 3 of the theme auto-detect chain. Resolver order is now: flag > env > config > COLORFGBG > OSC 11 > dark default Iter 2 (#151) shipped the COLORFGBG step and explicitly deferred OSC 11 because it needed raw-tty handling and a millisecond-grained timeout. This lands that piece on its own. ## What - New `theme::osc11` submodule. Pure helpers (`parse_osc11_reply`, `classify_rgb`) plus the unix `query_terminal_background` driver: emit `\x1b]11;?\x07`, time-bound the stdin read with `libc::poll`, parse the `rgb:RRRR/GGGG/BBBB` (4-byte-hex) or `rgb:RR/GG/BB` (2-byte) reply with either BEL or ST terminator, classify by CCIR-601 luminance against threshold 128. - Theme directory: `theme.rs` -> `theme/mod.rs` so the OSC 11 helper can live next to it without polluting `lib.rs`. - `detect_terminal_background` now chains COLORFGBG, then OSC 11. Each probe is isolated for testability. - 150ms timeout const at module top with a breadcrumb to promote it to `SCITADEL_OSC11_TIMEOUT_MS` later if slow ssh links complain. ## Safety / correctness - Probe is gated behind `IsTerminal` on stdin AND stdout — no query fires under cargo test, cron, redirected I/O. - Termios is saved with `tcgetattr` before flipping to non-canonical/ no-echo and unconditionally restored before return; OSC reply bytes arrive immediately and aren't echoed back to the user. - `libc::poll` enforces the millisecond deadline; `read` is only called after `POLLIN` so the call is non-blocking by construction. - Read loops on partial replies (tmux pass-through can fragment) but always honors the deadline. - On any failure path (non-tty, timeout, malformed, stdio error) the function returns `None` and the resolver falls through to dark. ## Tests (12 new, 50 total in scitadel-tui) Pure-function tests: - `parse_osc11_reply` x6: 4-byte BEL light bg, 2-byte BEL dark bg, 4-byte ST terminator, malformed (5 sub-cases), empty buffer, leading-noise tolerance. - `classify_rgb` x7: black, white, warm-cream Dalton-bright bg, Solarized-dark bg, pure red, pure green, threshold edge case. Integration: - `osc11_skipped_when_stdin_not_a_tty`: confirms `resolve(None, "auto")` returns under 50ms (well below the 150ms OSC 11 timeout) when stdin is a pipe — guards against the resolver ever hanging the test runner. Reuses the existing `ENV_LOCK` so it serializes with the COLORFGBG / SCITADEL_THEME mutators (#169 carry-over). End-to-end on a live OSC-11-capable terminal is intentionally manual: the round-trip needs a real tty that answers the query. Verify with: SCITADEL_THEME= COLORFGBG= scitadel tui on foot/kitty/alacritty/modern xterm — toast should report `dalton-bright (auto)` on a light terminal, `dalton-dark (auto)` on a dark one, and `dalton-dark (auto)` (after ~150ms) on a terminal that ignores OSC 11. ## Dependencies Zero new entries in `Cargo.lock` — `libc` is already pulled in transitively by crossterm/tokio/mio. The change adds it as a direct target-gated (`cfg(unix)`) dep on scitadel-tui so we can call `libc::poll` / `libc::tcgetattr` / `libc::tcsetattr` without reaching through another crate's surface. [tape-exempt: detection runs at startup before TUI render; observable behavior verified by parser/luminance unit tests; full e2e requires a real OSC-11-supporting terminal]
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #176.
Summary
Iter 3 of the theme auto-detect chain. Resolver order is now:
Iter 2 (#151) shipped the COLORFGBG step and explicitly deferred OSC 11 because it needed raw-tty handling and a millisecond-grained timeout. This lands that piece on its own.
theme::osc11submodule. Pure helpers (parse_osc11_reply,classify_rgb) plus a unixquery_terminal_backgrounddriver that emits\x1b]11;?\x07, time-bounds the stdin read withlibc::poll, parses thergb:RRRR/GGGG/BBBBorrgb:RR/GG/BBreply with BEL or ST terminator, and classifies by CCIR-601 luminance against threshold 128.theme.rs->theme/mod.rs.detect_terminal_backgroundnow chains COLORFGBG then OSC 11; each probe is isolated for testability.SCITADEL_OSC11_TIMEOUT_MSlater if slow ssh links complain.Safety / correctness
IsTerminalon stdin AND stdout — no query fires under cargo test, cron, or redirected I/O.tcgetattrbefore flipping to non-canonical / no-echo and unconditionally restored before return.libc::pollenforces the millisecond deadline;readis only called afterPOLLINso the call is non-blocking by construction.Noneand the resolver falls through to dark.Dependencies
Zero new entries in
Cargo.lock—libcis already transitive via crossterm/tokio/mio. Added as a directcfg(unix)-gated dep onscitadel-tuiso the module can calllibc::poll/tcgetattr/tcsetattrdirectly.Test plan
Automated (12 new tests; 50 total in
scitadel-tui, full workspace green):parse_osc11_replyx6: 4-byte BEL light bg, 2-byte BEL dark bg, 4-byte ST terminator, malformed (5 sub-cases), empty buffer, leading-noise tolerance.classify_rgbx7: black, white, warm-cream Dalton-bright bg, Solarized-dark bg, pure red, pure green, threshold edge case.osc11_skipped_when_stdin_not_a_tty:resolve(None, "auto")returns under 50ms (well below the 150ms timeout) when stdin is a pipe — fences against the resolver ever hanging the test runner. Reuses the existingENV_LOCK(fix(tui): serialize env-mutating theme tests (#165) #169 carry-over) so it serializes with the COLORFGBG / SCITADEL_THEME mutating tests.cargo fmt --all -- --checkcargo clippy --workspace --tests -- -D warningscargo test --workspaceManual (requires a real OSC-11-supporting tty; not fakeable in CI):
SCITADEL_THEME= COLORFGBG= scitadel tuion foot/kitty/alacritty/modern xterm — light terminal toast reportsdalton-bright (auto), dark terminal reportsdalton-dark (auto).dalton-dark (auto).[tape-exempt: detection runs at startup before TUI render; observable behavior verified by parser/luminance unit tests; full e2e requires a real OSC-11-supporting terminal]