Defines the config format, data model, and daemon integration approach for theme-aware styling. Made-with: Cursor
2.6 KiB
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:
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
ThemeStylesdataclass — groupsrunning: StyleConfigandcompleted: StyleConfigfor one theme. Config— replacesrunning/completedwithdark: ThemeStylesandlight: ThemeStyles. Addstheme: strfield. Exposesactive_styles(system_appearance: str) -> ThemeStylesmethod that resolves the correct theme based on thethemesetting 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:
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.runningrather thanrunning.dark. - Old format not supported — top-level
running/completedkeys 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.