From f085042deeaf8b2265f372c8046eaf13d45872a7 Mon Sep 17 00:00:00 2001 From: Gene Johnson Date: Mon, 16 Mar 2026 02:40:32 -0700 Subject: [PATCH] docs: add color scheme switcher design spec --- .../specs/2026-03-16-color-scheme-design.md | 204 ++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 docs/superpowers/specs/2026-03-16-color-scheme-design.md diff --git a/docs/superpowers/specs/2026-03-16-color-scheme-design.md b/docs/superpowers/specs/2026-03-16-color-scheme-design.md new file mode 100644 index 0000000..beebeb1 --- /dev/null +++ b/docs/superpowers/specs/2026-03-16-color-scheme-design.md @@ -0,0 +1,204 @@ +# Color Scheme Switcher Design + +**Date:** 2026-03-16 +**Status:** Draft +**Scope:** Interactive color theme selection for Mainline news ticker + +--- + +## Overview + +Mainline currently renders news headlines with a fixed white-hot → deep green gradient. This feature adds an interactive theme picker at startup that lets users choose between three precise color schemes (green, orange, purple), each with complementary message queue colors. + +The implementation uses a dedicated `Theme` class to encapsulate gradients and metadata, enabling future extensions like random rotation, animation, or additional themes without architectural changes. + +--- + +## Requirements + +**Functional:** +1. User selects a color theme from an interactive menu at startup (green, orange, or purple) +2. Main headline gradient uses the selected primary color (white → color) +3. Message queue (ntfy) gradient uses the precise complementary color (white → opposite) +4. Selection is fresh each run (no persistence) +5. Design supports future "random rotation" mode without refactoring + +**Complementary colors (precise opposites):** +- Green (38;5;22) → Magenta (38;5;89) *(current, unchanged)* +- Orange (38;5;208) → Blue (38;5;21) +- Purple (38;5;129) → Yellow (38;5;226) + +**Non-functional:** +- Reuse the existing font picker pattern for UI consistency +- Zero runtime overhead during streaming (theme lookup happens once at startup) +- Boot messages and title render in the selected theme + +--- + +## Architecture + +### New Module: `engine/themes.py` + +```python +class Theme: + """Encapsulates a color scheme: name, main gradient, message gradient.""" + + def __init__(self, name: str, main_gradient: list[str], message_gradient: list[str]): + self.name = name + self.main_gradient = main_gradient # white → primary color + self.message_gradient = message_gradient # white → complementary +``` + +**Theme Registry:** +Three instances registered by ID: `"verdant"`, `"molten"`, `"violet"`. + +Each gradient is a list of 12 ANSI 256-color codes matching the current format in `render.py`: +``` +[ + "\033[1;38;5;231m", # white (bold) + "\033[1;38;5;195m", # pale white-tint + ..., + "\033[2;38;5;235m", # near black +] +``` + +**Public API:** +- `get_theme(theme_id: str) -> Theme` — lookup by ID, raises if not found +- `THEME_REGISTRY` — dict of all available themes (for picker) + +--- + +### Modified: `engine/config.py` + +**New globals:** +```python +ACTIVE_THEME = None # set by set_active_theme() after picker +``` + +**New function:** +```python +def set_active_theme(theme_id: str): + """Set the active theme after user selection.""" + global ACTIVE_THEME + from engine import themes + ACTIVE_THEME = themes.get_theme(theme_id) +``` + +**Removal:** +- Delete hardcoded `GRAD_COLS` and `MSG_GRAD_COLS` — these move to `themes.py` + +--- + +### Modified: `engine/render.py` + +**Updated gradient access:** + +Old pattern: +```python +def lr_gradient(rows, offset, cols=None): + cols = cols or GRAD_COLS # module-level constant +``` + +New pattern: +```python +def lr_gradient(rows, offset, cols=None): + if cols is None: + from engine import config + cols = config.ACTIVE_THEME.main_gradient if config.ACTIVE_THEME else _DEFAULT_GRAD + # ... rest of function unchanged +``` + +**Fallback:** Define `_DEFAULT_GRAD` (current green) for cases where theme isn't selected yet. + +**Message gradient lookup:** +```python +def msg_gradient_rows(rows, offset): + from engine import config + return lr_gradient(rows, offset, config.ACTIVE_THEME.message_gradient) +``` + +--- + +### Modified: `engine/app.py` + +**New function: `pick_color_theme()`** + +Mirrors `pick_font_face()` pattern: +1. Display menu: `[1] Verdant Green [2] Molten Orange [3] Violet Purple` +2. Read arrow keys (↑/↓) or j/k to navigate +3. Return selected theme ID +4. Call `config.set_active_theme(theme_id)` + +**Placement in `main()`:** +```python +def main(): + # ... signal handler setup ... + pick_color_theme() # NEW — before title/subtitle + pick_font_face() + # ... rest of boot sequence ... +``` + +This ensures boot messages (title, subtitle, status lines) render in the selected theme color. + +--- + +## Data Flow + +``` +User starts: mainline.py + ↓ +main() called + ↓ +pick_color_theme() + → Display menu + → Read user input + → config.set_active_theme(theme_id) + ↓ +pick_font_face() — renders with active theme + ↓ +Boot messages (title, status, etc.) — all use active theme + ↓ +stream() — news + messages use active theme gradients + ↓ +On exit: no persistence +``` + +--- + +## Implementation Notes + +### Color Selection +ANSI 256-color palette is used throughout. Each gradient has 12 steps from white to deep primary/complementary color, matching the current green gradient structure exactly. + +Orange gradient: `231 → 195 → 214 → 208 → 202 → 166 → 130 → 94 → 58 → 94 → 94 (dim) → 235` +Purple gradient: `231 → 225 → 183 → 177 → 171 → 165 → 129 → 93 → 57 → 57 → 57 (dim) → 235` +(Exact codes TBD via manual testing for visual balance) + +### Future Extensions +The Theme class can be extended with: +- `animation_delay`: for strobing/pulsing themes +- `metadata`: tags like `["cyberpunk", "retro", "ethereal"]` +- `random_pick()`: returns a random theme (enables future `--color=random` flag) + +The picker can offer a "Random" option without any code changes to render or config. + +### Testing +- Unit test `themes.py` for Theme construction and registry lookup +- Integration test that boot sequence respects active theme +- No changes needed to existing gradient tests (they now pull from config.ACTIVE_THEME) + +--- + +## Files Changed +- `engine/themes.py` (new) +- `engine/config.py` (add `ACTIVE_THEME`, `set_active_theme()`) +- `engine/render.py` (update gradient access to use config.ACTIVE_THEME) +- `engine/app.py` (add `pick_color_theme()`, call in main) +- `tests/test_themes.py` (new unit tests) + +## Acceptance Criteria +1. ✓ Color picker displays 3 theme options at startup +2. ✓ Selection applies to all headline and message gradients +3. ✓ Boot messages render in selected theme +4. ✓ No persistence between runs +5. ✓ Architecture supports future random/animation modes