From 49c03e4b71d650f75bf2d568d6b47d802b1722c3 Mon Sep 17 00:00:00 2001 From: cottongin Date: Tue, 10 Mar 2026 08:34:43 -0400 Subject: [PATCH] Add design doc for dark/light theme support Defines the config format, data model, and daemon integration approach for theme-aware styling. Made-with: Cursor --- .../2026-03-10-dark-light-theme-design.md | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 docs/plans/2026-03-10-dark-light-theme-design.md diff --git a/docs/plans/2026-03-10-dark-light-theme-design.md b/docs/plans/2026-03-10-dark-light-theme-design.md new file mode 100644 index 0000000..f1bd8b6 --- /dev/null +++ b/docs/plans/2026-03-10-dark-light-theme-design.md @@ -0,0 +1,84 @@ +# Dark/Light Theme Support — Design + +## Summary + +Add dark/light mode theme support to cursor-flasher. Users can define separate border styles for each OS appearance mode via new `dark` and `light` config sections. A `theme` option controls which styles are active: `"dark"`, `"light"`, or `"auto"` (follows macOS appearance in real-time). + +## Config Format + +The old top-level `running`/`completed` format is replaced (breaking change). Modes are now nested under theme sections: + +```yaml +theme: auto # "dark" | "light" | "auto" + +dark: + running: + color: "#FF9500" + width: 4 + opacity: 0.85 + pulse_speed: 1.5 + sound: "Glass" + volume: 0.5 + completed: + color: "#00FF00" + width: 4 + opacity: 0.85 + duration: 1.5 + sound: "" + volume: 0.0 + +light: + running: + color: "#3B82F6" + width: 4 + opacity: 0.9 + pulse_speed: 1.5 + sound: "Glass" + volume: 0.5 + completed: + color: "#22C55E" + width: 4 + opacity: 0.9 + duration: 1.5 + sound: "" + volume: 0.0 + +flash: + mode: "screen" + +approval_tools: + - Shell + - Write + - Delete + +general: + approval_delay: 2.5 + cooldown: 2.0 +``` + +## Data Model + +- `StyleConfig` — unchanged (color, width, opacity, duration, pulse_speed, sound, volume). +- New `ThemeStyles` dataclass — groups `running: StyleConfig` and `completed: StyleConfig` for one theme. +- `Config` — replaces `running`/`completed` with `dark: ThemeStyles` and `light: ThemeStyles`. Adds `theme: str` field. Exposes `active_styles(system_appearance: str) -> ThemeStyles` method that resolves the correct theme based on the `theme` setting and the passed-in system appearance string. + +## Appearance Detection + +The daemon detects macOS appearance via `NSApplication.sharedApplication().effectiveAppearance().name()`. If the name contains "Dark", the appearance is `"dark"`; otherwise `"light"`. This check happens at flash/pulse trigger time (not polled), so it picks up OS appearance changes between flashes with zero overhead. + +## Daemon Integration + +Two call sites change: `_check_pending()` and `_handle_stop()`. Each resolves the active theme styles at trigger time: + +```python +styles = self.config.active_styles(_get_system_appearance()) +self.overlay.pulse(frames, styles.running) +play_alert(styles.running) +``` + +## Decisions + +- **Modes under themes** (not themes under modes) — `dark.running` rather than `running.dark`. +- **Old format not supported** — top-level `running`/`completed` keys are ignored. +- **Real-time detection** — appearance checked at each flash trigger, not just at startup. +- **Config stays pure** — no Cocoa imports in config.py; appearance detection lives in daemon.py.