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
This commit is contained in:
84
docs/plans/2026-03-10-dark-light-theme-design.md
Normal file
84
docs/plans/2026-03-10-dark-light-theme-design.md
Normal file
@@ -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.
|
||||
Reference in New Issue
Block a user