Files
ledgrab/RELEASE_NOTES.md
T
alexei.dolgolyov 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
chore: release v0.7.0
2026-05-26 00:35:38 +03:00

23 KiB
Raw Permalink Blame History

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.ps1 argument handling + clearer logs (45d12b2)

Bug Fixes

  • Survive PC restart — SQLite was running WAL with synchronous=NORMAL and Database.close() was never called, so an unclean Windows shutdown rolled the DB back to the last checkpoint and silently lost recent edits. Now uses synchronous=FULL + wal_autocheckpoint=100 + explicit wal_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_color was double-applying brightness_scale below 1 (quartered output for a half-scale) and skipping it above 1 (boost lost). Now one clamp(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 — userModified is now driven by actual edits, not deep-equal drift from defaults (e4bf58d)
  • Transport-bar uptime repaints on /health response instead of waiting up to ~10 s for the next poll (f1b0f0e)
  • Pre-merge device-support review passupdate_device no longer double-encrypts secrets in memory; GET /devices strips paired-only secrets behind boolean flags; SSRF validation on every new driver; corrupt-envelope decrypt returns "" instead of deleting the device row; update_device URL 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_event raises NotImplementedError (preserves the historical store contract) and create_source runs build_source before _check_name_unique so an invalid source_type raises the right error (c1aa2eb)
  • utils/url_scheme + utils/net_classify were referenced but untracked on a clean checkout — server failed to start with ModuleNotFoundError. Now committed (7736bc6)

Performance

  • Capture hot paths vectorised — WGC swaps per-frame ~30 MB BGRA→RGB fancy-index allocations for cv2.cvtColor into a 3-slot pre-allocated pool; MSS uses screenshot.raw + cv2.cvtColor with 256-byte change-detection; DXcam/BetterCam fixes a silent name-mangling factory leak; dominant-colour reduction is ~10× faster via packed-RGB np.bincount (f184ef0)
  • Event-driven frame hand-offLiveStream gains a frame_id + Condition, consumers wait instead of polling, ring buffer grows 3 → 5 slots, _blend_u16 uses cv2.addWeighted. Up to one frame_time of glass-to-LED latency saved at matched FPS (ee4fa81)
  • WLED brightness threshold caches per-frame np.max keyed 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 dispatchColorStripStreamManager.acquire() and ws_stream._create_stream() now share a STREAM_BUILDERS registry keyed by source type, with import-time coverage assertion against _SOURCE_TYPE_MAP. CSS response builder gets the same treatment (563cbac)
  • Value-source create / updateValueSourceStore.create_source shrinks from ~260 → ~25 lines via per-type builder/applier functions in a new storage.value_source_factories module (3b8f00e)
  • SystemMetricsValueStream — three parallel if/elif chains collapse into a MetricSpec(name, read_psutil, read_fallback, normalize, prime) registry in core.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 _RENDERERS dict replace per-frame dict-rebuild + silent fire fallback (97dae2c)
  • Output-target response buildersisinstance ladder + silent fabricated-LED fallback replaced with _TARGET_RESPONSE_BUILDERS dict and a runtime RuntimeError for unknown subclasses (2f15fbb)
  • Versioned data migrations — replaces a naked blob.replace(...) migration with storage.data_migrations.MigrationRunner backed by a data_migrations audit table and atomic transactions (563cbac)

Dedup / refactor

  • Edge-to-LED kernels in PixelMapper + AdvancedPixelMapper deduped into a shared core.capture.edge_interpolation module (5fec8db)
  • HA/Z2M _swap_color_source unified behind a shared light_target_helpers.swap_color_source helper (29bdacf)
  • Single-pixel _average_color lifted out of 6 LED drivers into core.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 wider types.ts god-module split is staged for a follow-up frontend sprint (05f73ee)
  • WebSocket auth — 11 except Exception sites around handshake replaced with a narrow _WS_SEND_BENIGN_EXC tuple; 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, /docs auth-gate (898912f)
  • Frontend infra — inbound-event allowlist mirroring the server side, closeIfPristine adoption across editors, MiniSelect markup for filter pickers (ddae571)
  • PEP-604 union sweepruff --select UP007,UP045 --fix converted ~1760 sites from Optional[T] / Union[X, Y] to T | None / X | Y. Hooks bumped to ruff v0.15.12 to recognise UP045 (888f8fd)
  • Typed window globals — 59 (window as any).foo sites across 19 feature modules switched to typed window.foo against global-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 spurious sqlite3.ProgrammingError setup 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.toml makes vex the project's primary code-search backend with auto-update + semantic embeddings (06273ba)
  • REVIEW_TODO.md captures audit items deliberately deferred; TODO.md records 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)