Add tags to all entity types with chip-based input and autocomplete

- Add `tags: List[str]` field to all 13 entity types (devices, output targets,
  CSS sources, picture sources, audio sources, value sources, sync clocks,
  automations, scene presets, capture/audio/PP/pattern templates)
- Update all stores, schemas, and route handlers for tag CRUD
- Add GET /api/v1/tags endpoint aggregating unique tags across all stores
- Create TagInput component with chip display, autocomplete dropdown,
  keyboard navigation, and API-backed suggestions
- Display tag chips on all entity cards (searchable via existing text filter)
- Add tag input to all 14 editor modals with dirty check support
- Add CSS styles and i18n keys (en/ru/zh) for tag UI
- Also includes code review fixes: thread safety, perf, store dedup

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-09 22:20:19 +03:00
parent 2712c6682e
commit 30fa107ef7
120 changed files with 2471 additions and 1949 deletions

46
TODO.md
View File

@@ -51,11 +51,51 @@ Priority: `P1` quick win · `P2` moderate · `P3` large effort
- Impact: medium — enables phone screen mirroring to ambient lighting; appeals to mobile gaming use case
- [x] `P3` **Camera / webcam** — Border-sampling from camera feed for video calls or room-reactive lighting
## Code Health (from review 2026-03-09)
### Bugs
- [x] `P1` **Thread safety: dict mutation during iteration** — composite_stream.py / mapped_stream.py `_sub_streams.clear()` crashes processing loop
- [x] `P1` **Thread safety: SyncClockRuntime.get_time() race** — compound read without lock causes time double-counting
- [x] `P1` **Thread safety: SyncClockManager unprotected dicts**`_runtimes`/`_ref_counts` mutated from multiple threads without lock
- [x] `P1` **Clock ref-count corruption on hot-swap**`_release_clock` reads new clock_id from store instead of old one
- [x] `P1` **Path traversal guard**`auto_backup.py` uses string checks instead of `Path.resolve().is_relative_to()`
- [x] `P2` **Crash doesn't fire state_change event** — fatal exception path in `wled_target_processor.py` doesn't notify dashboard
- [x] `P2` **WS broadcast client mismatch**`kc_target_processor.py` `zip(clients, results)` can pair wrong clients after concurrent removal
### Performance
- [x] `P1` **Triple FFT for mono audio**`analysis.py` runs 3 identical FFTs when audio is mono (2x wasted CPU)
- [x] `P2` **Per-frame np.array() from list**`ddp_client.py:195` allocates new numpy array every frame
- [x] `P2` **frame_time recomputed every loop iteration**`1.0/fps` in 8 stream files, should be cached
- [x] `P2` **Effect/composite/mapped streams hardcoded to 30 FPS** — ignores target FPS, bottlenecks 60 FPS targets
- [x] `P3` **Spectrum .copy() per audio chunk**`analysis.py` ~258 array allocations/sec for read-only consumers
### Code Quality
- [x] `P2` **12 store classes with duplicated boilerplate** — no base class; `BaseJsonStore[T]` would eliminate ~60%
- [x] `P2` **DeviceStore.save() uses unsafe temp file** — fixed-path `.tmp` instead of `atomic_write_json`
- [x] `P2` **Route code directly mutates ProcessorManager internals**`devices.py` accesses `manager._devices` in 13+ places
- [x] `P2` **scene_activator.py accesses ProcessorManager._processors directly** — bypasses public API
- [x] `P3` **datetime.utcnow() deprecated** — 88 call sites in 42 files, should use `datetime.now(timezone.utc)`
- [x] `P3` **color-strips.js 1900+ lines** — should be split into separate modules
- [x] `P3` **No DataCache for color strip sources** — fetched with raw fetchWithAuth in 5+ places
### Features
- [ ] `P1` **Auto-restart crashed processing loops** — add backoff-based restart when `_processing_loop` dies
- [ ] `P1` **"Start All" targets button** — "Stop All" exists but "Start All" is missing
- [ ] `P2` **Manual backup trigger endpoint**`POST /system/auto-backup/trigger` (~5 lines)
- [ ] `P2` **Scene snapshot should capture device brightness**`software_brightness` not saved/restored
- [ ] `P2` **Device health WebSocket events** — eliminate 5-30s poll latency for online/offline detection
- [ ] `P2` **Distinguish "crashed" vs "stopped" in dashboard**`metrics.last_error` is already populated
- [ ] `P3` **Home Assistant MQTT discovery** — publish auto-discovery payloads; MQTT infra already exists
- [ ] `P3` **CSS source import/export** — share individual sources without full config backup
- [ ] `P3` **Exponential backoff on events WS reconnect** — currently fixed 3s retry
## UX
- [ ] `P2` **Tags / groups for cards** — Assign tags to devices, targets, and sources; filter and group cards by tag
- Complexity: medium — new `tags: List[str]` field on all card entities; tag CRUD API; filter bar UI per section; tag badge rendering on cards; persistence migration
- Impact: medium-high — essential for setups with many devices/targets; enables quick filtering (e.g. "bedroom", "desk", "gaming")
- [x] `P2` **Tags / groups for cards** — Assign tags to devices, targets, and sources; filter and group cards by tag
- [x] `P3` **PWA / mobile layout** — Mobile-first layout + "Add to Home Screen" manifest
- [ ] `P1` **Collapse dashboard running target stats** — Show only FPS chart by default; uptime, errors, and pipeline timings in an expandable section collapsed by default
- [x] `P1` **Review protocol badge on LED target cards** — Review and improve the protocol badge display on LED target cards