diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 5cd6d7f..dc0ee74 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,72 +1,54 @@ -## v0.8.0 (2026-05-28) +## v0.8.1 (2026-05-28) ### User-facing changes #### Features -##### Android TV — production-readiness pass +##### Multi-broker MQTT devices -- **Security:** per-install random API key (persisted, threaded into the embedded server via env, embedded in the pairing QR as a URL-fragment so it never reaches HTTP logs); root-shell injection eliminated via POSIX-quoted `runAsRoot(argv)` overload; broadcast receivers locked to the app package; release builds refuse to silently sign with the debug keystore; crash log retention capped at 10 entries -- **Performance:** single reusable RGBA buffer in `ScreenCapture` / `RootScreenrecord` (eliminates ~15 MB/s GC churn at 30 fps); frame pacer switched to `elapsedRealtimeNanos` with catch-up accumulator (fixes ~30.3 fps drift); capture dimensions derived from source aspect ratio so non-16:9 displays aren't squashed; QR bitmap cached by URL -- **Compatibility:** compileSdk/targetSdk → 35 (Play Store requirement); armeabi-v7a build path; foreground service type declared as `mediaProjection|specialUse` with proper `ServiceCompat.startForeground` promotion; Ethernet > Wi-Fi > VPN > cellular selection in `NetworkUtils`; Android 15 predictive-back via `enableOnBackInvokedCallback`; splash screen API hides Chaquopy cold-start delay -- **UI/UX:** all hardcoded English strings localised across en/ru/zh; monochrome notification icon; 320×180 TV banner; ViewStub-based running panel; pulse animator on Running dot; "Starting…" button while probing root; autostart checkbox hidden on unrooted devices -- **Lifecycle hardening:** `processLock` serialises EOF respawn vs `stop()` to prevent orphaned screenrecord; publish-before-start under `@Synchronized` in `CaptureService.restartRootPipeline` closes the orphan window during watchdog restarts; watchdog give-up bound corrected ([ef1f9ea](https://git.dolgolyov-family.by/alexei.dolgolyov/ledgrab/commit/ef1f9ea)) +- The device editor now shows an MQTT **broker picker** for `device_type=mqtt` (in both the add-device and device-settings modals), wired into load / save / validate / dirty-check / clone. An empty selection means "first available broker" +- `mqtt_source_id` is now threaded end-to-end through `DeviceCreate` / `DeviceUpdate` / `DeviceResponse` and the device routes; the referenced broker is validated on create **and** update ([a5effba](https://git.dolgolyov-family.by/alexei.dolgolyov/ledgrab/commit/a5effba)) -##### Backup format — bundled DB + assets ZIP +##### Schema-driven wiring-graph editor -- Auto-backups now produce a `.zip` containing `ledgrab.db` plus every file from the assets directory under `assets/` — matching the manual `GET /api/v1/system/backup` download. Restore accepts both `.zip` and legacy `.db` interchangeably -- Partial-write hardening: writes stage to `.partial` then `os.replace` into place — a crash mid-write never leaves a corrupt backup masquerading as valid. Stale `.partial` files from prior crashes are swept on the next run -- Symlinks inside the assets directory are skipped (security guard against link targets outside the dir) -- Backups over 500 MB log a warning so operators notice unbounded asset growth before disk fills up -- `restart.py` redirects spawned restart script stdout/stderr to `restart.log` and bails out early if the script is missing — silent failures used to vanish into a detached child ([85da2e5](https://git.dolgolyov-family.by/alexei.dolgolyov/ledgrab/commit/85da2e5)) +- The visual graph editor now renders ports and edges generically from a backend-served schema (`GET /api/v1/graph/schema`) instead of hard-coding the connectable-field topology in two places — so client and server can no longer drift +- New `GET /api/v1/graph` returns the full nodes + edges + validation topology, and `GET /api/v1/graph/dependents/{kind}/{id}` reports what references an entity ([a5effba](https://git.dolgolyov-family.by/alexei.dolgolyov/ledgrab/commit/a5effba)) -##### Spectrum-aperture icon set +##### Aggregated snapshot endpoint -- Regenerated icon family from a single Pillow script: rounded-square aperture traced by a continuous RGB color-wheel stroke over a vignette canvas with chromatic bloom. 4× supersampled then downsampled per output for crispness -- New 256 px transparent-background **tray variant** — taskbar icon reads cleanly against light themes instead of showing a dark tile -- `icon.ico` now embeds 16/24/32/48/64/128/256 frames sourced from the transparent master (fixes the dark-square halo on light Windows themes) -- Maskable 512 variant safe-area padded for PWA round-crops ([3645216](https://git.dolgolyov-family.by/alexei.dolgolyov/ledgrab/commit/3645216)) +- New `GET /api/v1/snapshot` returns all output targets (with processing state + metrics), devices (with brightness), the source / preset / clock lists, and the system block in a **single response** — collapsing the Home Assistant integration's previous ~2N+M request fan-out into one round trip +- `?include=` fetches only a subset of sections, and an excluded section also skips its server-side work (e.g. cold-cache hardware brightness probes or the blocking NVML performance query) ([a5effba](https://git.dolgolyov-family.by/alexei.dolgolyov/ledgrab/commit/a5effba)) #### Bug Fixes -- **Notification sound dropdowns:** both the per-app override list and the main row now always render the EntitySelect (was silently inert before any sound assets were registered) and offer "no sound" as a first-class option via `allowNone` ([1f95993](https://git.dolgolyov-family.by/alexei.dolgolyov/ledgrab/commit/1f95993)) -- **CSS editor:** `notification_sound` and `notification_volume` are now persisted on save — they were silently dropped from the payload before ([66b85b0](https://git.dolgolyov-family.by/alexei.dolgolyov/ledgrab/commit/66b85b0)) -- **Python 3.13 ctypes:** Win32 message-pump prototypes (`GetMessageW` / `TranslateMessage` / `DispatchMessageW`) now share a single `LPMSG = POINTER(wintypes.MSG)` class across `WindowsShutdownGuard` and `PlatformDetector` — fixes the `expected LP_MSG instance instead of pointer to _MSG` error and the resulting shutdown-guard / display-power-monitor failure on 3.13 ([e4d24a0](https://git.dolgolyov-family.by/alexei.dolgolyov/ledgrab/commit/e4d24a0), [0d840ad](https://git.dolgolyov-family.by/alexei.dolgolyov/ledgrab/commit/0d840ad)) +- **Graceful shutdown no longer hangs:** uvicorn's graceful-shutdown wait is now bounded (`GRACEFUL_SHUTDOWN_TIMEOUT`, shared by the desktop, Android, and demo launchers). A lingering events WebSocket (which the browser auto-reconnects) used to keep connections from draining, so the lifespan shutdown never ran — leaving LED targets lit and blocking process exit. Ctrl+C / OS shutdown with the UI open now reliably stops targets and checkpoints the DB ([a5effba](https://git.dolgolyov-family.by/alexei.dolgolyov/ledgrab/commit/a5effba)) +- **Device update error codes:** `update_device` no longer masks an intentional 4xx (e.g. an unknown `mqtt_source_id` or failed group validation) as a generic 500 ([a5effba](https://git.dolgolyov-family.by/alexei.dolgolyov/ledgrab/commit/a5effba)) --- ### Development / Internal -#### CI/Build +#### Backend -- `release.yml` now creates the Gitea release as a **draft** and only flips `draft=false` once every build job (Windows, Linux, Docker) has uploaded its artifacts and sha256 sidecars — users never see a release page that's missing assets, which would have broken the in-app updater ([bc42604](https://git.dolgolyov-family.by/alexei.dolgolyov/ledgrab/commit/bc42604)) +- **Wiring-graph schema engine** (`api/graph_schema.py`): a pure, unit-tested module that is the single source of truth for which reference fields connect which entity kinds; builds the topology and performs dependency lookup plus cycle / dangling-reference detection without booting the app or any store. The route layer only gathers serialized entities and delegates ([a5effba](https://git.dolgolyov-family.by/alexei.dolgolyov/ledgrab/commit/a5effba)) +- **Structured access log:** a new middleware emits one structured line per request, attributing it to the authenticated token's friendly label (the key name, **never** the secret) so traffic can be traced to a client (e.g. `homeassistant` vs `android`). uvicorn's own access log is disabled to avoid duplicate lines ([a5effba](https://git.dolgolyov-family.by/alexei.dolgolyov/ledgrab/commit/a5effba)) +- Shared `validate_mqtt_source_exists` (`_mqtt_validation.py`) deduplicates the MQTT-source existence check between the device and output-target routes ([a5effba](https://git.dolgolyov-family.by/alexei.dolgolyov/ledgrab/commit/a5effba)) -#### Refactoring +#### Frontend -- **Shared API client + automations registry (audit M7, H8):** new `core/api-client.ts` wraps `fetchWithAuth` with typed `apiGet` / `apiPost` / `apiPut` / `apiPatch` / `apiDelete`; 35 feature/core files migrated. FastAPI validation-array detail unwrap hardened. Automations editor's two hand-rolled `RuleType` dispatch ladders converted to `Record` registries with an import-time exhaustiveness check ([bb3a316](https://git.dolgolyov-family.by/alexei.dolgolyov/ledgrab/commit/bb3a316)) -- **types.ts split (audit H6):** 1140 LOC `types.ts` split into 18 per-entity files under `types/`, original file kept as a pure re-export barrel — 102 type exports preserved with no import sites changed ([49c35a2](https://git.dolgolyov-family.by/alexei.dolgolyov/ledgrab/commit/49c35a2)) +- Service-worker refresh for the new bundle ([a5effba](https://git.dolgolyov-family.by/alexei.dolgolyov/ledgrab/commit/a5effba)) -#### Documentation +#### Tests -- `REVIEW_RECONCILE_NOTES.md` — design doc for the dashboard innerHTML reconciliation work: bug-class analysis, latent-site inventory, decision ladder (helper / hand-rolled cells / Lit), and recommendation to migrate polling-heavy modules to Lit with `entity-events.ts` tab reconciliation sequenced first ([10eb24b](https://git.dolgolyov-family.by/alexei.dolgolyov/ledgrab/commit/10eb24b)) +- New suites: graph routes + schema engine, snapshot routes, access-log middleware, `mqtt_source_id` device regressions, and the bounded-shutdown entrypoint. Full suite: **1614 passing** ([a5effba](https://git.dolgolyov-family.by/alexei.dolgolyov/ledgrab/commit/a5effba)) ---
-All Commits (11) +All Commits (1) | Hash | Message | Author | | ---- | ------- | ------ | -| [0d840ad](https://git.dolgolyov-family.by/alexei.dolgolyov/ledgrab/commit/0d840ad) | fix(ctypes): share wintypes.MSG with platform_detector to avoid argtype races | alexei.dolgolyov | -| [1f95993](https://git.dolgolyov-family.by/alexei.dolgolyov/ledgrab/commit/1f95993) | fix(notification): allow clearing the sound on per-app overrides and main row | alexei.dolgolyov | -| [10eb24b](https://git.dolgolyov-family.by/alexei.dolgolyov/ledgrab/commit/10eb24b) | docs: dashboard innerHTML reconciliation review notes | alexei.dolgolyov | -| [66b85b0](https://git.dolgolyov-family.by/alexei.dolgolyov/ledgrab/commit/66b85b0) | fix(css-editor): persist notification_sound + notification_volume | alexei.dolgolyov | -| [bc42604](https://git.dolgolyov-family.by/alexei.dolgolyov/ledgrab/commit/bc42604) | ci(release): publish release only after every build job uploads assets | alexei.dolgolyov | -| [3645216](https://git.dolgolyov-family.by/alexei.dolgolyov/ledgrab/commit/3645216) | feat(icons): spectrum aperture icon set + dedicated tray variant | alexei.dolgolyov | -| [85da2e5](https://git.dolgolyov-family.by/alexei.dolgolyov/ledgrab/commit/85da2e5) | feat(backup): bundle assets in ZIP + partial-write hardening + restart log | alexei.dolgolyov | -| [e4d24a0](https://git.dolgolyov-family.by/alexei.dolgolyov/ledgrab/commit/e4d24a0) | fix(ctypes): pin LPMSG across MSG-pump prototypes for Python 3.13 | alexei.dolgolyov | -| [bb3a316](https://git.dolgolyov-family.by/alexei.dolgolyov/ledgrab/commit/bb3a316) | refactor(frontend): shared API client + automations registry (audit M7, H8) | alexei.dolgolyov | -| [49c35a2](https://git.dolgolyov-family.by/alexei.dolgolyov/ledgrab/commit/49c35a2) | refactor(frontend): split types.ts into 18 per-entity files (audit H6) | alexei.dolgolyov | -| [ef1f9ea](https://git.dolgolyov-family.by/alexei.dolgolyov/ledgrab/commit/ef1f9ea) | feat(android): production-readiness pass — security, perf, compat, UI/UX | alexei.dolgolyov | +| [a5effba](https://git.dolgolyov-family.by/alexei.dolgolyov/ledgrab/commit/a5effba) | feat: aggregated snapshot + wiring-graph APIs, MQTT device brokers | alexei.dolgolyov |
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 1b4f663..abdb34a 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -41,7 +41,7 @@ android { // in CI). See ledgrabVersionCode above. Was stuck at 1 before — // sideload updates silently refused to install. versionCode = ledgrabVersionCode - versionName = "0.8.0" + versionName = "0.8.1" // ABI selection. Detect armeabi-v7a wheel presence and opt the // ABI in only when the matching pydantic-core wheel is on disk — diff --git a/server/pyproject.toml b/server/pyproject.toml index a6f7b34..026b22e 100644 --- a/server/pyproject.toml +++ b/server/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "ledgrab" -version = "0.8.0" +version = "0.8.1" description = "Ambient lighting system that captures screen content and drives LED strips in real time" authors = [ {name = "Alexei Dolgolyov", email = "dolgolyov.alexei@gmail.com"} diff --git a/server/src/ledgrab/__init__.py b/server/src/ledgrab/__init__.py index 60f082e..57c046f 100644 --- a/server/src/ledgrab/__init__.py +++ b/server/src/ledgrab/__init__.py @@ -9,7 +9,7 @@ from pathlib import Path # In dev (running from source without `pip install -e .`) and on Android # (Chaquopy embeds the source directly with no dist-info), we additionally # read pyproject.toml so the version is always correct without manual sync. -_FALLBACK_VERSION = "0.8.0" +_FALLBACK_VERSION = "0.8.1" def _read_pyproject_version() -> str | None: