Skip to content

feat(tui): runtime theme-toggle hotkey (theme iter 3)#184

Merged
gerchowl merged 1 commit intodevfrom
feat/175-runtime-theme-toggle
Apr 28, 2026
Merged

feat(tui): runtime theme-toggle hotkey (theme iter 3)#184
gerchowl merged 1 commit intodevfrom
feat/175-runtime-theme-toggle

Conversation

@gerchowl
Copy link
Copy Markdown
Contributor

Summary

Why T?

Both t and T were unused across the entire TUI keymap (verified by full-crate grep). Picked T so it parallels the other capital action keys — D (download), R (reader), O (open externally), E (export). Lowercase letters are reserved for navigation / list controls.

Mechanical changes

  • theme::ACTIVE moved from OnceLock<Theme> to RwLock<Theme>; theme() now returns Theme by value (Copy, ~24 fields), so view callsites don't hold the lock across the hot draw path.
  • New theme::set(t) is the in-session swap.
  • App::cycle_theme() + App::in_text_input_context() — the latter mirrors the prompt-gating pattern already used in handle_paper_detail_key / handle_question_dashboard_key.

Test plan

  • cargo test -p scitadel-tui — 51/51 passing (4 new cycle_next cases + theme_name_round_trips_concrete_palettes + set_swaps_active_theme + 3 new App keybind tests).
  • cargo test --workspace --exclude scitadel-tui — all green.
  • just lint — fmt + clippy clean (-D warnings).
  • tests/vhs/theme-toggle.tape ships dark → light → wrap-back-to-dark and the post-toggle status-bar toast.
  • Manual: launch scitadel tui --theme dark, press T, confirm light palette + toast; press T again, confirm wrap to dark.
  • Manual: open the bib-export prompt (Q-tab → Enter → E), press T, confirm theme is unchanged and T lands in the path buffer.

Closes #175

Press uppercase T from any non-text-input context to cycle through the
registered palettes (today: dalton-dark <-> dalton-bright). The swap is
session-only — it never writes back to [ui] theme in config.toml, and
the next launch resolves theme exactly as before through the layered
CLI / env / config / auto-detect chain (#137 contract preserved).

What landed:
- Theme::cycle_next(current) + Theme::concrete() table — deterministic
  order driven by the registry, wraps last → first, defensive against
  unknown current.
- Theme::name(theme) for the status-bar toast label.
- Global ACTIVE moved from OnceLock<Theme> to RwLock<Theme>; theme()
  returns by value (Theme: Copy) so callers don't hold a lock across
  the hot draw path. New theme::set() is the in-session swap path.
- App::cycle_theme() reuses the existing set_status_ok toast slot
  (#135-B / #137 P1) — no new toast subsystem.
- Gated by App::in_text_input_context() to avoid hijacking T while the
  user is editing an annotation prompt or bib-export path.

Tests:
- cycle_next coverage: two-theme round-trip, single-theme no-op,
  three-theme full cycle, unknown-current → first.
- theme::name round-trips both palettes.
- App::handle_key('T') outside input → toggle + toast.
- App::handle_key('T') inside an open BibExportPrompt → no toggle,
  prompt absorbs the keystroke as a path char.
- Toggle does not rewrite config.toml on disk.

VHS tape (tests/vhs/theme-toggle.tape) captures dark → light → dark
transitions plus the post-toggle status-bar toast.

Closes #175
@gerchowl gerchowl force-pushed the feat/175-runtime-theme-toggle branch from 8ed58d1 to d203c62 Compare April 28, 2026 13:14
@gerchowl gerchowl merged commit b49507b into dev Apr 28, 2026
11 of 12 checks passed
@gerchowl gerchowl deleted the feat/175-runtime-theme-toggle branch April 28, 2026 13:16
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