diff --git a/TODO-css-improvements.md b/TODO-css-improvements.md new file mode 100644 index 0000000..5c7bdbe --- /dev/null +++ b/TODO-css-improvements.md @@ -0,0 +1,106 @@ +# TODO + +## IMPORTANT: Remove WLED naming throughout the app + +- [ ] Rename all references to "WLED" in user-facing strings, class names, module names, config keys, file paths, and documentation +- [ ] The app is **LedGrab** — not tied to WLED specifically. WLED is just one of many supported output protocols +- [ ] Audit: i18n keys, page titles, tray labels, installer text, pyproject.toml description, README, CLAUDE.md, context files, API docs +- [ ] Rename `wled_controller` package → decide on new package name (e.g. `ledgrab`) +- [ ] Update import paths, entry points, config references, build scripts, Docker, CI/CD +- [ ] **Migration required** if renaming storage paths or config keys (see data migration policy in CLAUDE.md) + +--- + +# Color Strip Source Improvements + +## New Source Types + +- [ ] **`weather`** — Weather-reactive ambient: maps weather conditions (rain, snow, clear, storm) to colors/animations via API +- [ ] **`music_sync`** — Beat-synced patterns: BPM detection, energy envelope, drop detection (higher-level than raw `audio`) +- [ ] **`math_wave`** — Mathematical wave generator: user-defined sine/triangle/sawtooth expressions, superposition +- [ ] **`text_scroll`** — Scrolling text marquee: bitmap font rendering, static text or RSS/API data source *(delayed)* + +### Discuss: `home_assistant` + +Need to research HAOS communication options first (WebSocket API, REST API, MQTT, etc.) before deciding scope. + +### Deferred + +- `image` — Static image sampler *(not now)* +- `clock` — Time display *(not now)* + +## Improvements to Existing Sources + +### `effect` (now 12 types) + +- [x] Add effects: rain, comet, bouncing ball, fireworks, sparkle rain, lava lamp, wave interference +- [x] Custom palette support: user-defined [[pos,R,G,B],...] stops via JSON textarea +- [ ] Custom palette editor: replace raw JSON textarea with a proper visual editor (reuse gradient editor pattern with color pickers + position sliders) + +### `gradient` + +- [x] Noise-perturbed gradient: value noise displacement on stop positions (`noise_perturb` animation type) +- [x] Gradient hue rotation: `hue_rotate` animation type — preserves S/V, rotates H +- [x] Easing functions between stops: linear, ease_in_out (smoothstep), step, cubic + +### `audio` + +- [ ] New audio source type: band extractor (bass/mid/treble split) — responsibility of audio source layer, not CSS +- [ ] Peak hold indicator: global option on audio source (not per-mode), configurable decay time + +### `daylight` + +- [x] Longitude support for accurate solar position (NOAA solar equations) +- [x] Season awareness (day-of-year drives sunrise/sunset via solar declination) + +### `candlelight` + +- [x] Wind simulation: correlated flicker bursts across all candles (wind_strength 0.0-2.0) +- [x] Candle type presets: taper (steady), votive (flickery), bonfire (chaotic) — applied at render time +- [x] Wax drip effect: localized brightness dips with fade-in/fade-out recovery + +### `composite` + +- [ ] Allow nested composites (with cycle detection) +- [ ] More blend modes: overlay, soft light, hard light, difference, exclusion +- [ ] Per-layer LED range masks + +### `notification` + +- [x] Chase effect (light bounces across strip with glowing tail) +- [x] Gradient flash (bright center fades to edges, exponential decay) +- [x] Queue priority levels (color_override = high priority, interrupts current) + +### `api_input` + +- [ ] Crossfade transition when new data arrives +- [ ] Interpolation when incoming LED count differs from strip count +- [ ] Last-write-wins from any client (no multi-source blending) + +## Architectural / Pipeline + +### Processing Templates (CSPT) + +- [x] HSL shift filter (hue rotation + lightness adjustment) +- [x] ~~Color temperature filter~~ — already exists as `color_correction` +- [x] Contrast filter +- [x] ~~Saturation filter~~ — already exists +- [x] ~~Pixelation filter~~ — already exists as `pixelate` +- [x] Temporal blur filter (blend frames over time) + +### Transition Engine + +Needs deeper design discussion. Likely a new entity type `ColorStripSourceTransition` that defines how source switches happen (crossfade, wipe, etc.). Interacts with automations when they switch a target's active source. + +### Deferred + +- Global BPM sync *(not sure)* +- Recording/playback *(not now)* +- Source preview in editor modal *(not needed — overlay preview on devices is sufficient)* + +--- + +## Remaining Open Discussion + +1. **`home_assistant` source** — Need to research HAOS communication protocols first +2. **Transition engine** — Design as `ColorStripSourceTransition` entity: what transition types? (crossfade, wipe, dissolve?) How does a target reference its transition config? How do automations trigger it? diff --git a/server/src/wled_controller/__main__.py b/server/src/wled_controller/__main__.py index af5c687..1f22cc7 100644 --- a/server/src/wled_controller/__main__.py +++ b/server/src/wled_controller/__main__.py @@ -5,6 +5,7 @@ shows a system-tray icon with **Show UI** / **Exit** actions. """ import asyncio +import os import sys import threading import time @@ -36,6 +37,11 @@ def _open_browser(port: int, delay: float = 2.0) -> None: webbrowser.open(f"http://localhost:{port}") +def _is_restart() -> bool: + """Detect if this is a restart (vs first launch).""" + return os.environ.get("WLED_RESTART", "") == "1" + + def main() -> None: config = get_config() @@ -60,12 +66,13 @@ def main() -> None: ) server_thread.start() - # Browser after a short delay - threading.Thread( - target=_open_browser, - args=(config.server.port,), - daemon=True, - ).start() + # Browser after a short delay (skip on restart — user already has a tab) + if not _is_restart(): + threading.Thread( + target=_open_browser, + args=(config.server.port,), + daemon=True, + ).start() # Tray on main thread (blocking) tray = TrayManager( diff --git a/server/src/wled_controller/tray.py b/server/src/wled_controller/tray.py index c45ba68..9e15dd6 100644 --- a/server/src/wled_controller/tray.py +++ b/server/src/wled_controller/tray.py @@ -65,6 +65,7 @@ class TrayManager: return self._icon.stop() self._on_exit() + os.environ["WLED_RESTART"] = "1" os.execv(sys.executable, [sys.executable, "-m", "wled_controller"]) def _shutdown(self, icon: "pystray.Icon", item: "pystray.MenuItem") -> None: