8bdcc17799
Build Release / create-release (push) Successful in 3s
Build Android APK / build-android (push) Failing after 11s
Build Release / build-linux (push) Successful in 2m54s
Build Release / build-docker (push) Successful in 3m50s
Build Release / build-windows (push) Successful in 4m36s
23 KiB
23 KiB
v0.7.0 (2026-05-26)
A device-support release: seven new device families, a unified pairing UX, a brand-new HTTP-endpoint output type, multi-broker MQTT + Zigbee2MQTT support, a major shutdown / data-safety fix, and a deep architectural refactor pass that landed registry patterns for every dispatch hot path.
Features
New device types
- DDP — standalone Open-Pixel-Control-style target for Pixelblaze / ESPixelStick / xLights / Falcon endpoints, port 4048 (8f1140a)
- Yeelight — Xiaomi/Yeelight bulbs and lightstrips over JSON-RPC on port 55443, SSDP discovery (4b65005)
- WiZ Connected — Philips WiZ smart bulbs over UDP on port 38899, broadcast discovery (ede627b)
- LIFX — LIFX bulbs and lightstrips over the binary LIFX LAN protocol on port 56700 (8f9d490)
- Govee LAN — Govee Wi-Fi bulbs and ambient kits, multicast discovery (requires "LAN Control" enabled in the Govee Home app) (887131d)
- Open Pixel Control (OPC) — Fadecandy boards, xLights/Falcon, OPC bridges, port 7890 with channel addressing (31c6c3a)
- Nanoleaf — Light Panels / Canvas / Shapes / Lines / Elements over the documented HTTP REST API on port 16021 (426484a)
New output type
- HTTP endpoint output target — POST live strip frames to any user-configured HTTP endpoint, alongside WLED / MQTT / Hue. Full editor + storage + routes (d6cc800)
Pairing flow
- Generic pairing UX scaffold — 30-second SVG ring + countdown, instructions, retry/cancel. First concrete consumer is Nanoleaf; Tuya/Twinkly slot into the same shape later (2f31680)
MQTT / Zigbee2MQTT
- Multi-broker MQTT + new Zigbee2MQTT light output target sharing the HA-Light editor. Legacy single-broker YAML/env config auto-migrates to a "Default Broker" MQTTSource on startup (530316c)
Editor experience
- Live preview for color-strip sources of every type that can render without external calibration (audio, math_wave, weather, game_event, api_input, mapped, composite, processed) (337984c)
- Expanded automations — new rule shapes + matching UI inputs + 285 lines of dispatch coverage (3fe66d8)
- Expanded value sources — storage + schema + UI for the new value-source kinds the per-type factory refactor introduced (737fd72)
- Card icon picker expanded from 44 → 120 icons across 5 new categories (weather, nature, controls, status, office) (cdf7d94)
- closeIfPristine modal save-guard — editing an unchanged entity now silently closes the modal instead of firing a misleading "updated" toast (f03cb30)
- New MiniSelect primitive for compact dropdowns that don't justify the full IconSelect grid; IconSelect gains a defence-in-depth XSS sanitiser on the icon channel (9ff83bd, 507e138)
Updater
- SSRF-validated redirect chain in the update service so a hostile mirror can't bounce the updater to a private IP. Stricter
restart.ps1argument handling + clearer logs (45d12b2)
Bug Fixes
- Survive PC restart — SQLite was running WAL with
synchronous=NORMALandDatabase.close()was never called, so an unclean Windows shutdown rolled the DB back to the last checkpoint and silently lost recent edits. Now usessynchronous=FULL+wal_autocheckpoint=100+ explicitwal_checkpoint(TRUNCATE)on close, and a hidden WM_QUERYENDSESSION / WM_ENDSESSION window keeps Windows from force-killing the process before the lifespan can finish (e24f9d3) - Devices PATCH preserves URL — PATCH-without-
url(rename / icon-only) used to drop the address into the processor as None (0dd8d43) - HA Light brightness scale —
_send_entity_colorwas double-applyingbrightness_scalebelow 1 (quartered output for a half-scale) and skipping it above 1 (boost lost). Now oneclamp(max(r,g,b) * bs * vs, 0, 255)pass with regression coverage (ad84b60) - Dashboard "MODIFIED" badge no longer fires retroactively on un-edited legacy layouts —
userModifiedis now driven by actual edits, not deep-equal drift from defaults (e4bf58d) - Transport-bar uptime repaints on
/healthresponse instead of waiting up to ~10 s for the next poll (f1b0f0e) - Pre-merge device-support review pass —
update_deviceno longer double-encrypts secrets in memory;GET /devicesstrips paired-only secrets behind boolean flags; SSRF validation on every new driver; corrupt-envelope decrypt returns""instead of deleting the device row;update_deviceURL trim matches create; Govee discovery port-4002 collision serialised behind a module lock; Nanoleaf mDNS scan cleans up tasks on cancel; pair endpoint stops logging userinfo / exception bodies (0e3ae78) - value_source factory contract —
_build_game_eventraisesNotImplementedError(preserves the historical store contract) andcreate_sourcerunsbuild_sourcebefore_check_name_uniqueso an invalidsource_typeraises the right error (c1aa2eb) utils/url_scheme+utils/net_classifywere referenced but untracked on a clean checkout — server failed to start withModuleNotFoundError. Now committed (7736bc6)
Performance
- Capture hot paths vectorised — WGC swaps per-frame ~30 MB BGRA→RGB fancy-index allocations for
cv2.cvtColorinto a 3-slot pre-allocated pool; MSS usesscreenshot.raw + cv2.cvtColorwith 256-byte change-detection; DXcam/BetterCam fixes a silent name-mangling factory leak; dominant-colour reduction is ~10× faster via packed-RGBnp.bincount(f184ef0) - Event-driven frame hand-off —
LiveStreamgains aframe_id+Condition, consumers wait instead of polling, ring buffer grows 3 → 5 slots,_blend_u16usescv2.addWeighted. Up to oneframe_timeof glass-to-LED latency saved at matched FPS (ee4fa81) - WLED brightness threshold caches per-frame
np.maxkeyed on frame identity instead of reducing the LED array every loop (6e4c1b6) - Dashboard FPS charts now diff target ids and only recreate added/removed/detached charts (skipping the history fetch when local samples already exist), and spark SVGs are mutated in place instead of
innerHTML-rewritten every poll. Memoised patches/devices rendering by content signature so unchanged ticks no longer restart CSS animations (f6486f9)
Development / Internal
Architecture audit — registry patterns everywhere
- Color-strip stream dispatch —
ColorStripStreamManager.acquire()andws_stream._create_stream()now share aSTREAM_BUILDERSregistry keyed by source type, with import-time coverage assertion against_SOURCE_TYPE_MAP. CSS response builder gets the same treatment (563cbac) - Value-source create / update —
ValueSourceStore.create_sourceshrinks from ~260 → ~25 lines via per-type builder/applier functions in a newstorage.value_source_factoriesmodule (3b8f00e) - SystemMetricsValueStream — three parallel
if/elifchains collapse into aMetricSpec(name, read_psutil, read_fallback, normalize, prime)registry incore.processing.metric_readers(9f3f346) - Automation engine — per-rule-type bodies become
_handle_<kind>methods, dispatch table built once at class-creation, unknown-type fallback logs instead of silently returning False (98fb61d) - Effect renderer dispatch —
@_effect_renderer("fire")decorators + class-level_RENDERERSdict replace per-frame dict-rebuild + silent fire fallback (97dae2c) - Output-target response builders —
isinstanceladder + silent fabricated-LED fallback replaced with_TARGET_RESPONSE_BUILDERSdict and a runtimeRuntimeErrorfor unknown subclasses (2f15fbb) - Versioned data migrations — replaces a naked
blob.replace(...)migration withstorage.data_migrations.MigrationRunnerbacked by adata_migrationsaudit table and atomic transactions (563cbac)
Dedup / refactor
- Edge-to-LED kernels in
PixelMapper+AdvancedPixelMapperdeduped into a sharedcore.capture.edge_interpolationmodule (5fec8db) - HA/Z2M
_swap_color_sourceunified behind a sharedlight_target_helpers.swap_color_sourcehelper (29bdacf) - Single-pixel
_average_colorlifted out of 6 LED drivers intocore.devices.pixel_reduce.average_color(cc87fba) - Static → single rename for the color-strip source kind. Storage keeps backward-compatible serialisation (826e680)
- Bindable types extracted into
types/bindable.ts; the widertypes.tsgod-module split is staged for a follow-up frontend sprint (05f73ee) - WebSocket auth — 11
except Exceptionsites around handshake replaced with a narrow_WS_SEND_BENIGN_EXCtuple; receive path adds explicit observability (ea7ee88) - Backend hardening bundle — MQTT task tracking + drain resilience, credential encryption with auto-migration, devices watcher task tracking, WLED scheme inference at boundaries, streaming-upload caps,
asyncio.gather(return_exceptions=True)on broadcast loops, WebSocket Origin allow-list,/docsauth-gate (898912f) - Frontend infra — inbound-event allowlist mirroring the server side,
closeIfPristineadoption across editors, MiniSelect markup for filter pickers (ddae571) - PEP-604 union sweep —
ruff --select UP007,UP045 --fixconverted ~1760 sites fromOptional[T]/Union[X, Y]toT | None/X | Y. Hooks bumped to ruff v0.15.12 to recognise UP045 (888f8fd) - Typed window globals — 59
(window as any).foosites across 19 feature modules switched to typedwindow.fooagainstglobal-types.d.ts(0035172) - Processing magic numbers lifted to named module constants so tests can monkeypatch them (d38021f)
Database.ensure_open()— module-level singleton reopens cleanly across lifespan cycles, fixing 65 spurioussqlite3.ProgrammingErrorsetup failures on Windows pytest aggregate runs (f591e25)
Tests
- WLED URL scheme integration + IPv6 regression coverage (907bdaf)
- Lifespan reopen invariants on
Database(f591e25) - Hundreds of new tests covering every registry / factory / migration introduced above
Tooling / docs
.vex.tomlmakes vex the project's primary code-search backend with auto-update + semantic embeddings (06273ba)REVIEW_TODO.mdcaptures audit items deliberately deferred;TODO.mdrecords the architecture-audit remainder (06273ba, 628c6b2)- Locale + CLAUDE.md upkeep alongside the new features (fd46c51, 48dbdb9, 17684af, 390d2b4)
All Commits (55)
| Hash | Message |
|---|---|
| f591e25 | fix(storage/database): reopen connection on lifespan restart |
| f6486f9 | perf(dashboard): diff FPS charts + cache spark SVG nodes; i18n perf strings |
| 48dbdb9 | docs(review-todo): check off items addressed in 2026-05-23 autonomous pass |
| 0035172 | refactor(types): migrate (window as any) statics to typed window globals |
| 888f8fd | refactor(types): PEP-604 union sweep + UP007/UP045 enforcement |
| ea7ee88 | refactor(api/auth): narrow WS exception catches + observability log |
| d38021f | refactor(processing): hot-path magic numbers -> named module constants |
| 507e138 | feat(ui/icon-select): defence-in-depth XSS sanitiser on icon channel |
| 907bdaf | test(url-scheme): WLED route-level integration + IPv6 regression |
| 0dd8d43 | fix(devices): preserve existing URL on PATCH-without-url |
| fd46c51 | docs: TODO + CLAUDE.md notes + locale keys for new features |
| ddae571 | chore(frontend-infra): inbound-event allowlist + storage/state touch-ups |
| 898912f | chore(backend): MQTT/WLED/devices/capture/utils + api routes hardening |
| 45d12b2 | feat(update-service): SSRF-validated redirects + restart hardening |
| 826e680 | refactor(color-strip): rename static -> single + frontend follow-through |
| 737fd72 | feat(value-sources): extend storage + schema + UI alongside new kinds |
| 3fe66d8 | feat(automations): expand automation rules + UI + engine coverage |
| f03cb30 | feat(modal): closeIfPristine save-guard + per-editor adoption |
| 9ff83bd | feat(ui): MiniSelect primitive + IconSelect XSS hardening + typed globals |
| d6cc800 | feat(http-endpoints): introduce HTTP endpoint output target stack |
| 06273ba | chore(tooling): vex semantic-search config + REVIEW_TODO backlog |
| 628c6b2 | docs: capture architecture-audit remainder for follow-up sessions |
| 2f15fbb | refactor(output-targets): registry + coverage assertion for response builders |
| c1aa2eb | fix(value-source): preserve store contract for game_event + error precedence |
| 3b8f00e | refactor(value-source): per-type factories for create / update dispatch |
| 05f73ee | refactor(types): extract bindable primitives into types/bindable.ts (H6 partial) |
| 9f3f346 | refactor(value-source): MetricSpec registry for SystemMetricsValueStream |
| 98fb61d | refactor(automations): rule dispatch via class-level handler table |
| 5fec8db | refactor(capture): lift duplicated edge-to-LED kernels into shared module |
| 97dae2c | refactor(processing): replace inline effect dispatch with @_effect_renderer registry |
| 29bdacf | refactor(processing): dedupe HA/Z2M _swap_color_source via shared helper |
| 563cbac | refactor(storage,processing): kind registries + versioned data migrations |
| e24f9d3 | fix(shutdown): survive PC restart with WAL fsync + Win32 session-end guard |
| e4bf58d | fix(dashboard): stop showing perpetual MODIFIED for un-edited legacy layouts |
| f1b0f0e | fix(ui): repaint transport-bar uptime as soon as /health responds |
| 17684af | docs: record review-fix pass in TODO.md |
| 0e3ae78 | fix(devices): address pre-merge review findings |
| 7736bc6 | fix(utils): commit url_scheme + net_classify dependencies |
| 390d2b4 | docs: mark expand-device-support branch ready for merge |
| cc87fba | refactor(devices): extract _average_color to pixel_reduce |
| 426484a | feat(devices): Nanoleaf OpenAPI target type + first pair-flow user |
| 2f31680 | feat(devices): pairing-UX scaffold (Phase 2) |
| 31c6c3a | feat(devices): Open Pixel Control (OPC) target type |
| 887131d | feat(devices): Govee LAN target type |
| 8f9d490 | feat(devices): LIFX LAN target type |
| ede627b | feat(devices): WiZ Connected LAN target type |
| 4b65005 | feat(devices): Yeelight LAN target type |
| 8f1140a | feat(devices): standalone DDP target type |
| 337984c | feat(color-strips): in-editor live preview for all viable source types |
| 530316c | feat(mqtt): multi-broker MQTT + Zigbee2MQTT light target |
| 6e4c1b6 | perf(wled): cache per-frame max-pixel for brightness threshold |
| ee4fa81 | perf(processing): event-driven frame hand-off and scheduling fixes |
| f184ef0 | perf(capture): vectorize hot paths and fix engine bugs |
| ad84b60 | fix(ha-light): apply brightness_scale once and respect boost multipliers |
| cdf7d94 | feat(ui): expand card icon picker (44 -> 120 icons, +5 categories) |