31c6c3abb22ea12fefd1d56169efe7c76091abc6
637 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
31c6c3abb2 |
feat(devices): Open Pixel Control (OPC) target type
Adds support for Open Pixel Control receivers (Fadecandy boards, xLights/Falcon endpoints, OPC bridges, art-installation controllers, hobbyist LED driver software). OPC is a tiny TCP protocol on port 7890 with a 4-byte header [channel][cmd][len_hi][len_lo] + RGB body. Backend: - OPCClient opens one persistent TCP connection and streams frames as header+body byte pairs. Channel 0 broadcasts to every output on the OPC server; channels 1-255 address a specific channel on multi-output servers (Fadecandy with multiple Open Pixel chains). - supports_fast_send=True with a synchronous send_pixels_fast hot path. The fast path skips the async drain so the OS write-buffer flushes on its own schedule -- exactly what ambilight streaming wants. - Brightness applies client-side before the frame is sent (OPC has no reply channel for hardware-side brightness). - Health check opens a TCP connection and closes it. - OPCConfig joins the typed config union; storage gains an opc_channel field; full to_dict/from_dict/to_config wiring. - 36 unit tests cover URL parsing, header construction, send_pixels emitting header+body in order, brightness application, list and flat-array input shapes, drain behavior, connection lifecycle, provider validate/discover/capabilities, Device.to_config round-trip. Frontend: - 'opc' in DEVICE_TYPE_KEYS (next to 'ddp'), paper-plane icon -- same as DDP since both are open pixel-streaming protocols. - isOpcDevice predicate + per-type field show/hide. - Optional channel number input (default 0 = broadcast) with hint copy explaining the channel semantics. - Locale strings in en/ru/zh. No native discovery (OPC has no discovery protocol); users supply the receiver IP manually. |
||
|
|
887131d4af |
feat(devices): Govee LAN target type
Adds support for Govee Wi-Fi smart bulbs and ambient-lighting kits via their LAN API (opened in 2023). Discovery is multicast UDP on 239.255.255.250:4001; control commands go unicast to the device's port 4003; responses arrive on port 4002. Each device requires "LAN Control" toggled ON in the Govee Home app (Device -> settings -> LAN Control). Devices with LAN Control disabled silently fail to appear in discovery and won't respond to commands; the UI hint copy reminds users. Backend: - GoveeClient is a single-pixel UDP adapter: averages the strip to one RGB triple and pushes a 'colorwc' command with colorTemInKelvin=0 to select pure RGB mode (non-zero kelvin would switch the bulb to CCT mode and ignore the RGB values). - Brightness folds into the RGB scaling so we burn one packet per frame instead of two. - supports_fast_send=True with a synchronous send_pixels_fast hot path. Default rate gate 50 ms (~20 Hz); UDP fire-and-forget tolerates it. - Multicast discovery: scan request to 239.255.255.250:4001, listen on port 4002, parse the inner data dict for IP + device-id + SKU + firmware version. Degrades to [] when port 4002 is already bound or network is unavailable. - Health check sends devStatus and waits 1.5s for any reply; the error message points at the LAN-Control toggle since that's the #1 root cause of silent failures. - GoveeConfig joins the typed config union; storage gains govee_min_interval_ms; full to_dict/from_dict/to_config wiring. - 40 unit tests cover URL parsing, scan-reply parsing (rejecting non-scan commands and malformed JSON), payload builders (colorwc with colorTemInKelvin=0, brightness clamping, power as 1/0 not true/false), strip averaging, rate limiting, fast-send hot path, provider validate/discover/health, Device.to_config round-trip. Frontend: - 'govee' in DEVICE_TYPE_KEYS (next to 'lifx'), lightbulb icon (deliberate smart-bulb family grouping). - isGoveeDevice predicate + per-type field show/hide. - Rate-limit number input (default 50 ms). - URL hint copy explicitly instructs users to enable LAN Control in the Govee Home app -- the #1 source of "why isn't my Govee responding?" support churn. - Locale strings in en/ru/zh. |
||
|
|
8f9d490063 |
feat(devices): LIFX LAN target type
Adds support for LIFX smart bulbs and lightstrips that speak the LIFX binary UDP protocol on port 56700, with broadcast LAN discovery via the standard GetService/StateService probe. Backend: - LIFXClient is a single-pixel UDP adapter: averages the strip to one RGB triple, converts to LIFX HSBK (16-bit hue/saturation/brightness + kelvin), and pushes a tagged SetColor packet so all bulbs on the subnet act on it. Brightness folds into the HSBK brightness channel. - Hand-rolled packet builder: 36-byte LIFX header (frame + frame-address + protocol-header) + variable-length payload. Source ID 'LGGR' identifies LedGrab in protocol logs. - supports_fast_send=True with a synchronous send_pixels_fast hot path -- UDP costs nothing, so the default rate gate is 50 ms (~20 Hz) to match LIFX's documented <=20 cmd/sec recommendation. - Broadcast discovery sends GetService and parses StateService replies back into IP + MAC + service-port triples. Broadcast failures yield [] rather than raising. - Health check sends GetService and waits 1.5s for any reply on a one-shot UDP socket. - LIFXConfig joins the typed config union; Device storage gains a lifx_min_interval_ms field; full to_dict/from_dict/to_config wiring. - 47 unit tests cover URL parsing, RGB->HSBK conversion (red/green/ blue/white/black/clamping), packet construction (size, msg type, tagged flag, target MAC, sequence byte), SetColor and SetPower payload layouts, StateService reply parsing (including rejection of wrong msg types and runt payloads), strip averaging, rate limiting, fast-send hot path, provider validate/discover/health, and Device.to_config round-trip. Frontend: - 'lifx' in DEVICE_TYPE_KEYS (next to 'wiz'), lightbulb icon (deliberate smart-bulb family grouping with Hue + Yeelight + WiZ). - isLifxDevice predicate + per-type field show/hide in create and settings modals. - Rate-limit number input (default 50 ms) in both modals with hint text referencing LIFX's documented <=20 cmd/sec ceiling. - Locale strings in en/ru/zh. LIFX bulbs are reachable from the existing "Scan network" button -- no new discovery UI affordance was needed. No brightness_control capability exposed; LIFX brightness is folded into the HSBK on the wire. |
||
|
|
ede627b4ac |
feat(devices): WiZ Connected LAN target type
Adds support for WiZ Connected (Philips' budget-tier) smart bulbs that accept JSON commands as UDP datagrams on port 38899 with broadcast LAN discovery on 255.255.255.255:38899. Backend: - WiZClient is a single-pixel UDP adapter: averages the incoming strip to one RGB triple and pushes it via setPilot with r/g/b params. Brightness folds into the RGB scaling so we burn one packet per frame instead of two. - UDP fire-and-forget tolerates high update rates with no ack overhead, so the default rate gate is 50 ms (~20 Hz) -- 10x faster than Yeelight. - supports_fast_send=True with a synchronous send_pixels_fast hot path. - Broadcast discovery sends the standard registration envelope; bulb replies are parsed for IP+MAC and surfaced as DiscoveredDevice entries. Broadcast failures (no network, firewall) yield [] rather than raising. - Health check sends getPilot and waits 1.5s for any reply on a one-shot UDP socket. - WiZConfig joins the typed config union; Device storage gains a wiz_min_interval_ms field; full to_dict/from_dict/to_config wiring. - 36 unit tests cover URL parsing, MAC extraction, strip averaging, rate limiting, fast-send hot path, provider validate/discover/health, and Device.to_config round-trip. Frontend: - 'wiz' in DEVICE_TYPE_KEYS (next to 'yeelight'), lightbulb icon (deliberate smart-bulb family grouping with Hue + Yeelight). - isWizDevice predicate + per-type field show/hide in create and settings modals. - Rate-limit number input (default 50 ms) in both modals with hint text noting the UDP fire-and-forget characteristic. - Locale strings in en/ru/zh. WiZ bulbs are reachable from the existing "Scan network" button -- no new discovery UI affordance was needed. |
||
|
|
4b65005823 |
feat(devices): Yeelight LAN target type
Adds support for Xiaomi/Yeelight smart bulbs and lightstrips that speak the bulb-vendor's JSON-RPC protocol over TCP port 55443 with SSDP-style LAN discovery on 239.255.255.250:1982. Backend: - YeelightClient is a single-pixel adapter: it averages the incoming strip down to one RGB triple, packs it into the 24-bit color int the bulb expects, and pushes it via set_rgb with sudden+0ms effect. - Brightness folds into the RGB scaling on the wire so we burn one command per frame instead of two. - A configurable client-side rate gate (yeelight_min_interval_ms, default 500) keeps us under the bulb's ~1 cmd/sec cap. Frames that arrive inside the gate no-op without TX. Music mode (~60 Hz via reverse-TCP) is deferred -- the MVP caps at ~2 Hz and that's fine for a strip-to- single-pixel averaging device. - SSDP discovery scans 239.255.255.250:1982 with the bulb-specific ST: wifi_bulb header; replies are parsed into DiscoveredDevice entries. Multicast failures (no network, firewall) yield [] rather than raising -- discovery is best-effort. - Health check opens a TCP socket to the bulb and closes it. - YeelightConfig joins the typed config union; Device storage gains a yeelight_min_interval_ms field; full to_dict/from_dict/to_config wiring. - 34 unit tests cover URL parsing, RGB packing, strip averaging, rate limiting, SSDP response parsing, provider validate/discover/health, and Device.to_config round-trip. Frontend: - 'yeelight' in DEVICE_TYPE_KEYS (next to 'hue'), lightbulb icon (intentional family-grouping signal with Hue). - isYeelightDevice predicate + per-type field show/hide in create and settings modals. - Rate-limit number input (default 500 ms) in both modals with hint text explaining the trade-off. - Locale strings in en/ru/zh. - Drive-by: types.ts DeviceType union backfilled with 'ddp' and 'ble' for type-safety consistency. Yeelight bulbs are now reachable from the existing "Scan network" button -- no new discovery UI affordance was needed. |
||
|
|
8f1140abad |
feat(devices): standalone DDP target type
Promotes the existing DDP packet layer (previously WLED-internal) to a
first-class device type so any DDP-speaking receiver (Pixelblaze,
ESPixelStick, xLights/Falcon endpoints, generic firmware) can be driven
directly without WLED in the path.
Backend:
- New DDPLEDClient wraps the DDPClient transport as a proper LEDClient
with supports_fast_send=True (synchronous UDP push on the hot loop).
- New DDPDeviceProvider — no native discovery, manual LED count,
capabilities = {manual_led_count, health_check}.
- DDPConfig joins the typed config union; Device storage gains
ddp_port / ddp_destination_id / ddp_color_order fields with safe
defaults (0/1/1 -> port 4048, destination 1=display, RGB byte order).
- URL scheme: ddp://host[:port] or bare host[:port] (default 4048).
- Health check resolves the host via async DNS; UDP has no reply
channel so reachability is best-effort by design.
- 29 new tests in test_ddp_led_client.py cover URL parsing, packet
hot path (brightness, list/numpy input shapes, fast vs async send),
provider validate/discover/capabilities, config round-trip via
Device.to_config() and to_dict/from_dict.
Frontend:
- 'ddp' in DEVICE_TYPE_KEYS (next to 'dmx'), paper-plane icon.
- isDdpDevice predicate + per-type field show/hide in the create &
settings modals.
- Color-order picker uses IconSelect (project rule bans plain select).
- Locale strings added in en/ru/zh.
Note: this commit also carries two pre-existing in-flight hunks that
were intermixed in the same files and could not be split out
non-interactively:
- api/routes/devices.py: URL-scheme inference for bare WLED hosts,
safer error messages, exception-isolated parallel discovery.
- storage/device_store.py: secret_box helpers + at-rest encryption of
Hue / BLE-Govee / MQTT credentials.
Both are independent of DDP and intentional per the user.
|
||
|
|
337984c618 |
feat(color-strips): in-editor live preview for all viable source types
Extend the editor's Preview button to render unsaved form values for every CSS source type that can be previewed without external calibration. New types now supported transiently: audio, math_wave, weather, game_event, api_input, mapped, composite, processed. Backend (preview WebSocket): - Dispatch in _create_stream by source_type, injecting the dependencies each stream needs (audio managers, weather manager, value stream manager, CSPT store via public get_cspt_store, color strip stream manager). - Roll back clock + stream resources if start() fails so failed previews don't leak refs. - On source_type change mid-preview, drop the rebuilt-stream reference if rebuild fails and close the WS rather than poll a stopped stream. Stream lifecycle fixes flushed out by the new preview paths: - MappedColorStripStream and ProcessedColorStripStream now stamp a per- instance UUID into the sub-stream consumer_id so concurrent consumers (multiple preview WS connections) don't collide in the CSM registry. - ProcessedColorStripStream.update_source now re-acquires the input stream when input_source_id changes (previously silently kept the old input). Frontend: - Expand _PREVIEW_TYPES; route non-quirky types through a new exported getCSSEditorPreviewPayload helper that reuses the existing per-type handler registry. - For picture / picture_advanced / key_colors (which depend on calibration or rectangles edited elsewhere), show a clearer "save the source first" message instead of the generic "unsupported" toast. |
||
|
|
530316c2c3 |
feat(mqtt): multi-broker MQTT + Zigbee2MQTT light target
- New Z2MLightOutputTarget storage, processor, editor and routes for Zigbee2MQTT light entities (shares the HA-Light editor UI via the new light-target-editor module) - Replace global MQTTService/MQTTConfig with per-source MQTTManager + MQTTRuntime; thread mqtt_source_id through Z2M targets, DIY MQTT devices, and the automation engine - Migrate legacy single-broker YAML/env config to a "Default Broker" MQTTSource on startup (core/mqtt/legacy_migration.py) and drop the obsolete core/mqtt/mqtt_service.py - Refresh /api/v1/system integration status to surface every MQTT source - Extract shared light-target editor and refactor OutputTargetStore + output_targets routes around typed factories / auto-registry - Modal CSS polish, locale strings, and storage/bindable test coverage |
||
|
|
6e4c1b6642 |
perf(wled): cache per-frame max-pixel for brightness threshold
Cache the np.max(frame) result keyed on frame identity. The min_brightness_threshold check ran a full reduction over the LED array on every loop iteration even when the frame reference was unchanged — at 60 fps with multiple targets this added up to hundreds of redundant reductions per second. |
||
|
|
ee4fa81376 |
perf(processing): event-driven frame hand-off and scheduling fixes
- LiveStream: add frame_id counter + Condition with wait_for_new_frame() helper. Producers (ScreenCaptureLiveStream, ProcessedLiveStream, StaticImageLiveStream, VideoCaptureLiveStream) now signal_new_frame() on each new frame; consumers (PictureColorStripStream, ProcessedLive Stream) wait on the event with frame_time as a safety timeout instead of polling + sleeping. Cuts glass-to-LED latency at matched FPS by up to one frame_time. - ProcessedLiveStream ring buffer: 3 -> 5 slots. The previous "max 2 frames in flight" assumption ignored the multi-consumer case where several PictureColorStripStream/HA-target threads can hold the same _latest_frame reference while we wrap. 5 slots gives ~83 ms of consumer-read margin at 60 FPS. - PictureColorStripStream advanced mode: reuse the already-fetched primary frame instead of re-acquiring its lock from _live_streams. - _blend_u16: use cv2.addWeighted (single SIMD-fused pass) when cv2 is available; numpy fallback unchanged. Output verified bit-equal to the existing 6-pass implementation. - FrameLimiter.wait: drop the 1 ms minimum-sleep floor. Over-budget loops no longer add an extra ms per iteration; the cap on achievable rate (~750 fps) is removed. |
||
|
|
f184ef0afb |
perf(capture): vectorize hot paths and fix engine bugs
- WGC: replace per-frame ~30 MB BGRA->RGB fancy-index allocation with cv2.cvtColor into a 3-slot pre-allocated RGB pool. Use gc.collect(0) on cleanup instead of full GC to avoid multi-hundred-ms stalls. - MSS: switch from screenshot.rgb (pure-Python BGRA->RGB rebuild) to screenshot.raw + cv2.cvtColor into a pooled buffer. Add cheap 256-byte hash-based change detection so idle frames return None — matches DXcam/BetterCam semantics. - DXcam/BetterCam: fix silent factory leak — Python name-mangling rewrote self._dxcam.__factory to _DXcamCaptureStream__factory inside the class body, so cleanup never reached the real attribute. Use getattr with string literal to bypass mangling. - calculate_dominant_color: replace np.random.choice(replace=False) (full sort) with np.random.randint, and np.unique(axis=0) (lexsort) with packed-RGB np.bincount. ~10x faster on dominant mode. - calibration._map_edge_average: switch cached scratch buffers from float64 to float32. Halves memory bandwidth on the dominant reduction path; range-safe up to 8K screens. - All engines: per-frame DEBUG logs use structlog kwarg style instead of f-strings to avoid per-frame string allocation. |
||
|
|
ad84b60ae4 |
fix(ha-light): apply brightness_scale once and respect boost multipliers
`_send_entity_color` was multiplying the per-mapping `brightness_scale` into the brightness payload twice when the effective scale was below 1, yielding a quartered output for a configured half-scale. Conversely, when the value-stream multiplier exceeded 1.0 with a default scale, the entire scaling step was skipped and the boost was lost. Compute brightness as `clamp(max(r,g,b) * bs * vs, 0, 255)` once and ship it directly, with regression tests pinning the half-scale, boost, and 255-clamp cases. |
||
|
|
cdf7d94652 |
feat(ui): expand card icon picker (44 -> 120 icons, +5 categories)
Add 76 new icons to the custom card-icon picker and introduce five new categories: weather, nature, controls, status, office. Existing icon ids are unchanged so persisted card icons keep resolving. - icon-paths.ts: +36 Lucide path constants (weather, nature, room, office, media, hardware, lighting variants) - device-icons.ts: extend IconCategory union and CATEGORIES; add registry entries with labels + search aliases - en/ru/zh locales: 5 new category labels + 76 per-icon labels each (126 device.icon keys per locale, fully aligned) Tabs scroll horizontally via existing overflow-x; no migration needed (picker reads/writes ids by value, missing ids fall back to inheritance). |
||
|
|
09792a9a05 |
chore: release v0.6.1
Build Release / create-release (push) Successful in 4s
Build Android APK / build-android (push) Failing after 9s
Build Release / build-linux (push) Successful in 2m13s
Build Release / build-docker (push) Successful in 3m9s
Build Release / build-windows (push) Successful in 4m6s
|
||
|
|
75ca487be1 |
feat(ui): per-surface card presentation modes (C/M/D/R)
Adds a comfortable/compact/dense/row toggle to every card grid in the
app. Each surface (LED devices, targets, automations, scenes, sources,
streams, dashboard subsections, etc.) remembers its mode independently.
Persistence mirrors dashboard-layout: localStorage cache for first paint,
debounced PUT to /api/v1/preferences/card-modes (new endpoint) for
cross-browser sync. Surface registry is open — any non-empty key
accepted server-side; modes validated against {comfortable, compact,
dense, row}.
CSS is token-driven: grid min-width and gap come from --card-grid-min /
--card-grid-gap / --card-grid-min-narrow / --card-grid-gap-narrow /
--templates-grid-min / --templates-grid-gap defined on :root, overridden
per [data-card-mode]. Dense/row also hide .mod-leds, collapse secondary
button labels, and tighten .mod-metrics; row collapses the grid to one
full-width column. Coexists with the existing per-section [data-density]
on the dashboard tab — different attribute, additive concern.
Toggle UI auto-mounts into every CardSection header (18+ surfaces) plus
the six dashboard subsections via post-render mount; teardown tracking
keeps the listener Set bounded across re-renders.
i18n: card_mode.{tooltip,comfortable,compact,dense,row} in en/ru/zh.
Tests: 9 new cases in tests/test_preferences_card_modes_api.py covering
defaults, round-trip, validation, open-registry keys, row mode, delete.
|
||
|
|
e65dcb41f4 |
chore: clean up cfg abbreviation and stale TODO link
Rename `cfg` parameter/local in resolve_mqtt_password to `config` for PEP 8 compliance. Drop the broken reference to the long-removed docs/plans/device-typed-configs.md from TODO.md. |
||
|
|
6a07a6b1a2 |
fix(shutdown): apply target stop actions before tearing down HA/MQTT
Reorder the lifespan shutdown so processor_manager.stop_all() runs before ha_manager.shutdown(), mqtt_manager.shutdown(), and mqtt_service.stop(). HA-light targets check `_ha_runtime.is_connected` before applying their `stop_action` (turn_off / restore) and silently skip when HA is already disconnected; MQTT-output devices need the broker connection alive to send restore frames. The previous order tore those down first, turning "stop_targets" into a no-op for those targets — most visible when closing via the tray Shutdown button. Also moves automation_engine.stop(), discovery_watcher.stop(), and the OS notification listener stop ahead of processor stop so they can no longer fire events into a shutting-down processor manager. Independent services (weather, update checker, auto-backup) now run last, where their order does not matter. Bonus: if the daemon-thread join times out (10 s) and the rest of shutdown is cut short, the user-visible part — targets stopping — has already run. |
||
|
|
0f5850ef80 |
feat(ui): customisable card icon for all entity types
Extends the icon-plate work from devices and output targets to every
remaining card type — 18 new entities, 20 in total. Users can now pick
a curated icon (with optional colour override) for any card on any tab,
and the picker reuses the same modal, recent-strip, search, and
category tabs introduced for the device picker.
Foundation:
- icon-picker.ts — replace the hardcoded 2-entry adapter record with a
Map<EntityType, EntityTypeAdapter> and expose
registerIconEntityType() + makeSimpleIconAdapter() so each feature
module owns its own adapter (~6 lines per type).
- bodyExtras hook on adapters, keyed off id, lets discriminated routes
(output-targets target_type, picture-sources stream_type, audio /
value / color-strip-sources source_type) accept icon-only PUTs.
- core/card-icon.ts — new makeCardIconFields(type, id, entity) helper
spreads iconHtml / iconColor / iconAttrs into a mod-card head in one
line.
- _onDocumentClick now accepts any registered type instead of a
hardcoded device/target check.
Backend (purely additive — no migrations needed thanks to JSON-blob
storage):
- 18 dataclasses gained icon: str = "" + icon_color: str = "" with
emit-when-truthy serialisation and "" defaults on load.
- All matching Create / Update / Response Pydantic schemas gained the
fields with the standard Optional[str] + max_length=64/32 +
description set.
- All routes' response builders use
getattr(entity, "icon", "") or "" so existing rows render unchanged.
- ValueSource and CSS handle icon/icon_color on the base class so all
source-type subclasses inherit them automatically.
Frontend wiring (12 modules):
- streams.ts — picture sources, capture templates, PP templates,
CSPT, audio sources, audio templates, gradients (built-in
gradients keep no plate).
- automations, scene-presets, sync-clocks, weather-sources,
value-sources, mqtt-sources, home-assistant-sources,
game-integration, audio-processing-templates, assets,
color-strips/cards.
- pattern-templates skipped — uses the legacy wrapCard({content,
actions}) string API, separate migration.
Dashboard cards now also display the chosen icon:
- Targets already had it (with device inheritance for LED targets).
- Sync clocks, automations, and scene presets gained the same plate
via a shared _dashboardIconPlate helper that mirrors the mod-card
layout (mod-head--with-icon class flips on when present).
i18n: 20 new device.icon.entity.<type> labels in en/ru/zh.
Verification:
- ruff check src/ tests/ — clean.
- npx tsc --noEmit — clean.
- npm run build — 2.6 MB bundle.
- pytest tests/ --no-cov — 949 passed (no regressions).
Pending: manual smoke test on each card type — open picker, save, and
confirm the channel-color preview matches the live card.
|
||
|
|
a79f4bf73c |
feat(ha-light): broadcast a single Color Value Source to all entities
HALightOutputTarget gains a `source_kind` field with two modes: - `css` (existing): per-mapping LED segments averaged from a ColorStripSource. - `color_vs` (new): one colour from a colour-returning ValueSource pushed to every mapped entity (mapping LED ranges are ignored in this mode). Backend wiring: - Schema/route: add `source_kind` + `color_value_source_id` to create/update/ response payloads, with VS existence + return_type=color validation. - Storage: persist new fields, with defensive `or ""` coalesce so legacy rows written via resolve_ref with None survive the str-typed response schema. - Processor: ha_light_target_processor reworked to drive both source kinds (incl. update_target_settings hot-swap of source mode). New unit tests in tests/core/test_ha_light_target_processor.py and extended store tests. Frontend: - ha-light editor modal: collapsed Color Strip + Color VS into one "Color Source" picker with grouped headers; mappings list shows a mode-aware hint when broadcasting a single colour. - EntityPalette: support non-selectable header rows (with keyboard / filter handling) for grouped source pickers. Bundled UI polish (icon inheritance + cleanup): - Custom card icons now flow into more surfaces: command palette, dashboard target cards, scene-preset target picker, calibration test-device picker, and the LED-target device picker. LED targets inherit their device's icon when none is set on the target itself. - Empty mod-card icon plates render as a dashed "+" placeholder when an icon-picker hook is wired, so the action stays discoverable. - Icon picker: distinct "HA light target" eyebrow label and supports HA-light cards (data-ha-target-id) for channel-colour resolution. - Update banner: "View release" now opens the in-app Update settings tab instead of an external link; uses the sparkles icon. - Color-strip delete: cleaner toast on 409 conflict. |
||
|
|
ced72fc864 |
feat(targets): customisable card icon + HA-light stop action
Extends the icon-plate work from the device cards to LED and HA-light output targets, and adds finalization behaviour for HA-light targets. Targets: - Add `icon` and `icon_color` fields to OutputTarget (LED + HA-light). - LED target cards inherit the icon from their referenced device when no override is set; the icon picker shows an "inherited" indicator. - Promote the device link from the meta line to a chip with the device's custom icon, leaving the head row free for the icon plate. HA-light: - New `stop_action` field with three modes: `none` / `turn_off` / `restore`. Processor snapshots mapped-entity states at start and applies the chosen action on stop (rgb / hs / color_temp / brightness restored where present). - Editor modal exposes the choice via an IconSelect of three modes. Adjacent fixes: - Fader slider hit-zone now overlays the visible track exactly, regardless of label/value column widths. - Dashboard customise drag-drop indicator splits into before/after rather than highlighting the whole row. - Picture-source EntitySelect resyncs its visible value on load. |
||
|
|
49ddabbc36 |
feat(ui): customisable card icon plate for devices
A user-chosen icon ("mouse", "motherboard", "keyboard"…) renders as a
44x44 instrument-panel face plate at the leading edge of .mod-head on
device cards. Optional per-card; null hides the plate and reverts to
the existing badge-only head.
- Storage/schema: new icon, icon_color fields on Device + DeviceUpdate /
DeviceResponse. SQLite stores entities as a JSON blob, so no migration
is needed; from_dict defaults handle existing rows.
- Curated 47-icon library across six categories (Hardware / Lighting /
Rooms / Media / Signal / Ambience), reusing the existing Lucide path
module; adds circuit-board, bed, armchair, leaf paths.
- mod-card.ts: ModHeadOpts gains iconHtml / iconColor / iconAttrs;
ModMenuItemOpts gains optional dataAttrs. The plate is rendered when
iconHtml is supplied; otherwise no layout change.
- Picker modal (icon-picker.html + features/icon-picker.ts): live
preview, search, six category tabs, recent strip, channel-color
override toggle. Wired through document-level click delegation on
[data-icon-picker-trigger="<deviceId>"] — no window globals, no
inline onclick string. Sets the precedent for migrating other card
actions off window in a follow-up.
- en/ru/zh locales for picker UI + categories.
Includes a docs/ mockup that's the source-of-truth for the design.
|
||
|
|
a026f0b349 |
ci(android): fail-fast on missing release keystore before SDK setup
Move the keystore guard from after the Decode step (step 9) to right after Resolve build label (step 3). A release tag pushed without ANDROID_KEYSTORE_BASE64 configured now fails in seconds instead of after JDK + Python + Android SDK + NDK install (~3-5 min of wasted runner time). Switched the condition from steps.keystore.outputs.present to env.ANDROID_KEYSTORE_BASE64 since the env var is set at job level and the keystore decode step has not yet run at the new position. |
||
|
|
5ef6ac1317 |
chore: release v0.6.0
Build Release / create-release (push) Successful in 3s
Build Android APK / build-android (push) Failing after 3m52s
Build Release / build-linux (push) Successful in 5m20s
Build Release / build-docker (push) Successful in 6m20s
Build Release / build-windows (push) Successful in 7m7s
|
||
|
|
0980cf4dde |
fix(ui): audio-source modal — preserve device on refresh, relocate refresh action
- Move the device refresh button into the label row next to "Audio Device:" so it can no longer overflow the Source panel edge; introduces a small .label-row-action style alongside .hint-toggle. - Restore device selection after refresh by matching on (index, loopback) value first, with a trimmed name fallback for OS-side reindexing. - _selectAudioDevice now syncs the EntitySelect trigger so the visible label matches the underlying <select> when the modal opens in edit mode. - Drop unused min-width/overflow on .transport-status. |
||
|
|
fdac26b9d9 |
feat: daylight tz, camera engine, value stream + modal/UI polish
- daylight: new daylight_settings module + daylight-tz frontend helper; expanded daylight_stream behavior - camera engine: capture path additions plus new test_camera_engine suite - value stream: schema + processing updates (~178 lines) - color strip: drop cycle effect (cycle.py / color-cycle.ts removed), tighten static path - modal CSS: large refactor (+883), components.css polish (+110) - templates: settings, css-editor, value-source-editor, test-template, display-picker, image-lightbox - frontend core: state, modal, icons, graph-nodes, app - frontend features: displays, streams, streams-capture-templates, value-sources, settings, color-strips/cards - locales: en/ru/zh - storage: color_strip, picture_source, value_source loaders touched - preferences/sync_clocks/picture_sources routes; home_assistant + templates schemas |
||
|
|
816a27db73 |
refactor(ui): drop app footer, move author info to About panel
The "Created by Alexei Dolgolyov..." line lived in a global app footer that took up vertical space on every page. Move the author + contact details into the About tab of the global settings modal (rendered by renderAboutPanel), where they sit next to the version pill and license. Adds a localized "donation.about_author" key (en/ru/zh) and matching .about-hero .about-author styles. Removes the now-unused .app-footer / .footer-content rules. |
||
|
|
797b806972 |
feat: LED hot-path perf, tutorials expansion, modal markup polish
Performance (LED hot path, allocation-free per-frame): - Adalight: dedicated single-worker tx executor (avoids asyncio.to_thread overhead), pre-allocated wire buffer + uint8 scratch, header struct precomputed. New tests cover header format, buffer reuse, non-contiguous input, and brightness scaling. - DDP: pre-built struct.Struct for the 10-byte header, allocation-free send buffer + memoryview emit path. New tests cover RGB/RGBW packets, sequence/PUSH semantics, and multi-packet fragmentation. - Calibration: precomputed Phase 3 skip-LED resampling (floor/ceil indices, fractional weights, take/blend scratch buffers) — per-frame work is now np.take + in-place blend, no allocations. - WLED target processor: matching hot-path tightening. Tutorials: - Sub-tab switching, breadcrumb header, and prepare/switchSubTab hooks so a tour can open/close the dashboard customize panel and resolve targets behind sub-tabs. - New steps for integrations tab, dashboard customize panel (presets, global, sections, perf cells), targets, scenes, sync-clocks. - en/ru/zh locales updated with the new tour strings. Dashboard layout: - Structural deep-equal so the "modified" indicator reflects truth after a user edits then reverts, instead of a stale flag. UI polish: - Mod-card / modal markup pass across ~33 modal templates and the tutorial overlay partial. - appearance.css, modal.css, tutorials.css refresh. Tooling: - Add .mcp.json with code-review-graph MCP server config so the graph tools are available to the team out of the box. |
||
|
|
9d4a534ec6 |
feat(ui): release notes overlay v2 + settings/streams/dashboard polish
Release Notes overlay redesign (scoped via .release-notes-shell)
- Backend exposes release.assets (name/size/download_url) through
UpdateReleaseInfo so the frontend can render real download links.
- New masthead: eyebrow + display-font title + tag/published/pre-release
chip strip + close/external action buttons; opts out of layout.css's
global `header { height: 60px }` and `header::before` accent bar that
were leaking into the overlay's <header>.
- Markdown body: <code> filenames are wrapped in clickable <a> via fuzzy
asset match (exact basename, then same-extension token-overlap), with
per-asset description tooltip and a small download glyph.
- Per-asset description derived from filename pattern (Windows installer
/portable/msi, Linux tarball/AppImage/deb/rpm, macOS dmg/pkg, Android
apk/aab, iOS ipa, generic archives) with i18n keys in en/ru/zh.
- Hide checksum / signature side-files (.sha256/.sha512/.sig/.asc/...).
Settings modal & dashboard polish
- ds-section refresh, rail-channel routing, notif matrix updates.
- Dashboard customize panel + per-account layout updates.
- New docs/settings-modal-redesign.html design reference.
Streams / targets / color-strip
- Stream cards rewrite (cards.css, streams.css, streams.ts).
- Composite stream + metrics history adjustments.
- WLED target processor + color-strip pipeline refinements.
- Color-strip WS source streamer touch-ups.
Misc
- Perf charts overhaul; tabular game-integration / HA / MQTT / weather
source cards; donation/sync-clocks/scene-presets minor polish.
- New i18n keys across en/ru/zh.
Test infrastructure
- conftest pre-creates the test DB so main.py's legacy-data migration
doesn't shovel the user's production DB into the test temp dir.
- test_preferences_notifications wipes its own setting at the start of
the defaults test (was relying on isolation it never enforced).
Pre-commit gates: ruff clean, tsc clean, npm run build clean,
pytest 899/899 passing.
|
||
|
|
51eebf21d5 |
feat(ui): redesign target pipeline as compact strip + chip row
Drops the legacy "Pipeline details" collapsible block on running LED target cards. Instead: - Always-visible 4px segmented timing bar (extract / map / smooth / send for video, read / fft / render / send for audio) — same stage colors as before, scaled by per-segment ms cost - One chip row beneath it: total ms / frames count / keepalive count, using a new .chip--inline variant (display-weighted number + tiny mono-caps unit) - _patchTargetMetrics now writes only the bar's segments and the data-tm spans — bar wrapper survives across polls so the flex-transition animates smoothly between samples - _buildLedTimingHTML replaced by _buildLedTimingSegments (no more header / total / legend wrappers — those live in the chip row) Cleanup - Drop .target-metrics-collapse / -toggle / -animate / -expanded CSS — no callers remain - Drop targets.metrics.pipeline from en/ru/zh locales — toggle label is gone |
||
|
|
9067db2639 |
feat(ui): align Targets metric cells with dashboard pattern
mod-card.ts - ModMetricOpts.extra: raw HTML appended after the .v cell — used to embed a sparkline canvas inside the FPS metric block - ModMetricOpts.valueDataAttrs: data-attrs on the .v element so live-update patchers can target the value directly LED target card - FPS sparkline (mod-metric-spark-canvas) is embedded INSIDE the FPS cell as a sibling of .v — was a separate target-fps-row block before, which floated under the metrics grid - Label hardcoded to "FPS" (the i18n value "Target FPS:" was meant for the editor field, not the readout) - Uptime cell gets ICON_CLOCK; Errors cell gets ICON_OK / ICON_WARNING based on count — matches dashboard cell decorations - Drops the leading FPS icon (display-font number is the focal element; no icon needed) - _patchTargetMetrics now emits the dashboard FPS shape: current<span.dashboard-fps-target>/target</span> <span.dashboard-fps-avg>avg N.N</span> — picks up dashboard.css styling for free HA Light target card - Same icon treatment (Uptime → clock; HA → ok/warning by ha_connected); FPS icon dropped Grid sizing - .devices-grid bumped from minmax(300px, 1fr) / gap 20px to minmax(min(380px, 100%), 1fr) / gap 14px — matches the dashboard's section grid so metric values like "1m 43s" stop truncating at the typical desktop width |
||
|
|
233b463ac3 |
feat(ui): migrate Targets cards to mod-card system
LED targets and HA Light targets adopt the dashboard's instrument- readout vocabulary (mod-head, mod-leds, mod-metrics, mod-foot, mod-patch, mod-btn, kebab menu) — same classes and tokens already used by Dashboard and device cards. mod-card.ts - ModBtnOpts.dataAttrs for arbitrary data-attrs (used by the LED preview toggle's data-led-preview-btn binding) - ModBodyOpts.extraHtml escape-hatch for live-update widgets that don't fit the predefined slots (FPS sparkline canvas, entity swatch grid, collapsible pipeline metrics) LED target card (targets.ts) - Badge "LED · TGT" pairs with device "WLED · OUT"-style badges - Meta row: device link → protocol badge → fps → pixel count - LED bezel: 1-3 dots reflecting checking / streaming / online / offline / unreachable - Headline metrics on running cards (FPS / ERR / UPTIME) preserve data-tm selectors so _patchTargetMetrics still patches in place - Chips for CSS source link, brightness/value-source, threshold - Patch indicator: STREAMING / UNREACHABLE / STANDBY / OFFLINE / CHECKING - Foot: START/STOP go/stop variant + LED preview + Edit - Kebab menu: Duplicate / Hide / Delete (replaces top-right trash) - FPS sparkline + collapsible pipeline preserved via extraHtml - Tag chips and LED preview panel appended after wrap (mirrors devices.ts pattern) HA Light target card (ha-light-targets.ts) - Badge "HA · LIGHT" - Meta: HA source link → light count → update rate - LEDs: blink running, fault when ha_connected === false, off idle - Running metrics: RATE / UPTIME / HA status - Patch: STREAMING / DISCONNECTED / STANDBY / NOT CONFIGURED - Buttons keep [data-action] for initHALightTargetDelegation - Live entity color swatches preserved via extraHtml Misc - Chip border-radius dropped from 999px (pill) to var(--lux-r-sm, 3px) — sharp corners match badges/metrics/buttons elsewhere - _patchTargetMetrics FPS readout uses <small> for the target fraction instead of the legacy target-fps-target span |
||
|
|
de13f44f24 |
feat(autostart): suppress browser auto-open on Windows login
When the user enables "Start with Windows" in the installer, the app launches on every PC login. Previously each login popped a fresh WebUI tab, which is noisy for a tray-resident background service. The autostart shortcut now passes --autostart to start-hidden.vbs, which sets LEDGRAB_AUTOSTART=1 in the child env. __main__ checks this flag alongside LEDGRAB_RESTART when deciding whether to open the browser. Manual launches (desktop/start-menu shortcuts) and the installer's post-install "Launch LedGrab" finish-page action are unchanged — they don't pass the arg, so they still open the WebUI tab. |
||
|
|
1c9acc5afb |
feat(api-input): make SegmentPayload start/length optional
start defaults to 0, length defaults to led_count - start (the rest of the strip from start). A single segment with only mode + color now fills the entire strip — no more length: 9999 magic value clients have to pass. Buffer auto-grow only fires for segments with an explicit length past the current end; implicit "to the end" segments adapt to the current strip size. |
||
|
|
a56569b02f |
feat(ui): cards redesign + settings, modal, toolbar polish
Dashboard cards (mod-card system) - New mod-card / mod-menu modules backing dashboard cards - Reworked card colors, sections, dashboard layout, perf charts - Channel-stripe styling, hairline borders, signal-flow animation on running cards, refined metric grid Multiselect bulk toolbar - Replaced tri-state checkbox with explicit Select-all / Deselect-all icon buttons; both disable when not applicable - Dim + slight blur on non-selected siblings during selection mode so the active picks pop; selected card gains a subtle lift + primary-color glow halo - Bulk tick uses ICON_CHECK from the icon registry (was U+2713) and scale-pops in via a cubic-bezier overshoot keyframe - Toolbar restyled with luxury gradient bg, top accent stripe, glass blur, neon hover glows on each button group Settings modal - Tab bar converted to icon-only (cog / hard-drive / bell / palette / refresh / help) so labels never overflow at any locale; title and aria-label preserve translated names. Tabs distribute evenly via flex: 1 1 0 + space-around — no overflow possible - IconSelect auto-populates <option> elements when the underlying select is empty, fixing the blank notification triggers (root cause: setting .value on an empty select is a no-op) - Tab activation calls scrollIntoView on the active button as a safety net for narrow viewports Modal exit animation - Added symmetric fadeOut + slideDown keyframes; .modal.closing applies them with animation-fill-mode: forwards - Modal.forceClose() defers display:none until animationend (with timer fallback). State cleanup (focus, body lock, stack) runs immediately so callers querying state get correct values - isOpen returns false during the close animation; open() cancels any in-flight close so re-open works during the animation - prefers-reduced-motion disables all modal animations Locale picker - Dropped redundant English/Русский/中文 long-form labels — picker now shows only EN / RU / ZH - IconSelect trigger/cell hides empty icon/label slots via :empty so the layout collapses cleanly for minimal items Filter input (cards section) - Embedded magnifier icon via data URI (no HTML change); monospace uppercase placeholder, lux-bg-0 background, neon focus ring with inset shadow + outer glow - Reset button only shows when the input has content (CSS-only via :placeholder-shown sibling selector — JS-resilient) Snack toast - Glass background (gradient + backdrop-blur) with top channel-color accent stripe matching the modal/toolbar language - Per-type --toast-ch drives border/glow/timer color (success → primary, error → danger, info → info) - Undo button gets a tinted hover with channel-color halo Top header toolbar - Removed hairline border from .header-btn for a flatter look; hover keeps the subtle background tint and primary-color glow Device URL hyperlink - Styled .mod-meta__link to pick up the card's --ch accent (instead of inheriting browser-blue underline). Dotted underline at rest solidifies on hover; soft text-shadow glow; web icon dims at rest, brightens on hover Misc - ICON_CHECK and ICON_HARD_DRIVE added to the icon registry - Existing card-redesign demos checked in under docs/ - Removed obsolete docs/plans/device-typed-configs.md |
||
|
|
ccf4406349 |
Merge branch 'feat/device-event-notifications'
Configurable device-event notifications: snackbar + Web Notifications for online/offline (configured targets) and discovery (new WLED/serial on the LAN/USB) events, with per-event channel matrix and background discovery toggle. |
||
|
|
8aa3a323d6 |
feat(notifications): device event notifications (snack + Web Notifications)
Surface device connection state changes (configured target online/offline) and discovery events (new WLED on LAN, new serial port, devices that disappear) through a configurable per-event channel matrix: none / snack / OS / both. - Backend: long-running mDNS browser + 10 s serial poller in core/devices/discovery_watcher.py, gated by user pref. Reuses the existing device_health_changed event for online/offline transitions. New GET/PUT /api/v1/preferences/notifications endpoint with Pydantic v2 schema (channel matrix + background-discovery flag + grace/debounce). 13 new tests, full suite still 899 passing. - Frontend: features/notifications-watcher.ts with startup-grace + flap-debounce + bulk-coalesce pipeline. Web Notifications API for the OS channel (no platform-specific code, works in PWA shell). New "Notifications" tab in Settings with 4 IconSelect rows + bg toggle + permission row + test button. en/ru/zh translations. Defaults: device_offline=both (urgent), online/discovered=snack, lost=none, background discovery on. Already-configured devices are filtered from discovery events to avoid double-notifications. |
||
|
|
8e109f32b9 |
fix(pwa): add mobile-web-app-capable meta tag
Chrome deprecated apple-mobile-web-app-capable in favor of the standard mobile-web-app-capable. Add the new tag while keeping the Apple variant for iOS Safari compatibility. |
||
|
|
033c1f6a92 |
ci: add workflow_dispatch and skip lint/test on release commits
Release-bump commits don't change code that affects lint/tests, and release.yml already runs in parallel. Manual dispatch lets us re-run on demand if needed. |
||
|
|
0804f54537 |
chore: release v0.5.0
Build Release / create-release (push) Successful in 3s
Build Android APK / build-android (push) Failing after 2m16s
Build Release / build-linux (push) Successful in 3m54s
Build Release / build-docker (push) Successful in 7m30s
Build Release / build-windows (push) Successful in 8m37s
Lint & Test / test (push) Successful in 8m45s
|
||
|
|
66f921c07f |
Merge branch 'feat/lumenworks-ui-redesign'
Lint & Test / test (push) Successful in 2m36s
Lumenworks studio-console redesign + per-account dashboard customization + Inputs/Integrations/Graph treatment + transport-bar uptime + server shutdown action. Sub-features (in order on the branch): - feat(ui): Lumenworks tokens, fonts, transport bar, channel-strip sidebar - feat(ui): dashboard polish, perf strip, transport-bar controls - feat(dashboard): per-account customizable dashboard with slide-in panel - feat(ui): item-card restyle, perf hover tooltips, FPS ceiling - feat(ui): Lumenworks treatment for Inputs / Integrations / Graph tabs - fix(ui): cards on pure black/white, decoupled from bg-anim - fix(ui): single-row header + readable sidebar labels at narrow widths - feat: server shutdown action with public cancel_task lifecycle method - feat(ui): live card-color picker, monotonic uptime ticker, default preset uses base palette - fix(ui): channel stripe paints only on custom-color or running cards - chore: harden test isolation, gitignore stale src/data, mark TODO done Pre-merge audit: - 886/886 pytest passed twice in a row - ruff + tsc clean - frontend bundle rebuilt at static/dist - python package reinstalled in editable mode (dev WebUI now reports 0.4.2 instead of stale 0.3.0 dist-info) |
||
|
|
80f01d4813 |
chore: harden test isolation, gitignore stale src/data, mark shutdown action done
- ``tests/test_preferences_api.py`` no longer captures the auth API key at module-import time. The new ``client`` fixture resolves it inside its body and bakes the Bearer header into ``TestClient.headers``, so the e2e conftest swapping the global config singleton during collection cannot leave the test holding a stale 401-bound header. Same proven pattern as ``test_audio_processing_templates_api.py``. - ``.gitignore`` now anchors ``/server/src/data/`` defensively. If the server is launched from ``server/src/`` (uncommon but possible during ad-hoc debugging), its relative ``data/`` resolves there. Templates now live in SQLite (``capture_templates`` / ``pattern_templates`` / ``postprocessing_templates`` tables); any stale ``*.json`` that lands in that directory is a runtime export and must not be committed. - Three such stale exports were untracked at the start of the pre-merge audit and have been deleted from the working tree. - ``TODO.md`` flips the shutdown-action checklist to done and notes that real-hardware verification (WLED + serial after Ctrl+C) is still pending. |
||
|
|
b1ee3c3942 |
fix(ui): channel stripe paints only on custom-color or running cards
Reported during pre-merge review of the Lumenworks redesign: non-dashboard cards (Inputs, Integrations, Targets) all showed aggressive cyan / green left stripes "regardless of custom color set or not", and even the ``+`` Add card carried a stripe. Root cause: ``.template-card`` defaulted to ``--ch: cyan`` and ``::before`` painted it unconditionally; ``.add-template-card`` inherits ``.template-card`` so it picked the stripe up too. Fix: gate the ``::before`` channel stripe behind opt-in selectors. It now paints only when the card carries ``data-has-color="1"`` (the user picked a personal colour via the picker, set by ``wrapCard``) or has the ``card-running`` class (the "patched and live" indicator). Dashboard module rows are unchanged — their ``::before`` already runs at ``opacity: 0.6`` and was approved as the visual benchmark. ``add-template-card::before`` is hidden unconditionally with ``!important`` since the Add card is not an entity and should never carry a channel hue. |
||
|
|
e0ff40f4f5 |
feat(ui): live card-color picker, monotonic uptime ticker tweaks, default preset uses base palette
Three adjacent UI fixes that surfaced while soaking the Lumenworks redesign: - ``card-colors.ts`` now writes the user's picked color through to *every* card representing the same entity (e.g. the targets-tab card AND its dashboard mirror), not just the one that owns the picker. Sets the ``--ch`` custom property on each match instead of a literal ``border-left``, which avoided the double-stripe (custom border + Lumenworks ``::before`` channel stripe) the old approach produced and reaches mirrors the picker callback's ``.closest()`` lookup couldn't. - ``appearance.ts`` "default" preset now *clears* its colour overrides instead of stamping the historic muted greys (#1a1a1a / #2d2d2d / #f5f5f5 / #ffffff). With the redesign's pure-black / pure-white base palette in ``base.css``, "default" should mean "use the base" — the preset swatch in Appearance now matches what ships out of the box. Existing users with "default" selected will see a one-time visual shift to the new neutrals on next reload; this is intentional. - ``dashboard.css`` mod-metric label row gets explicit sizing for the small status glyphs (clock / check / warning) so they sit beside the mono-caps label without competing with the big value. Errors cell picks up the coral channel tint when the count is non-zero. |
||
|
|
3f80ef2101 |
feat: server shutdown action with public cancel_task lifecycle method
Lets users choose what happens to LED targets when the server shuts
down. Default ("stop_targets") runs the existing per-device stop
sequence, so devices with auto-restore replay their prior state.
"Nothing" cancels the capture tasks without sending restore frames,
so the LEDs keep displaying their last frame on shutdown.
Backend:
- New setting ``shutdown_action`` persisted in db.settings
(``stop_targets`` default | ``nothing``) with GET/PUT
``/api/v1/system/shutdown-action`` endpoints
- ``ProcessorManager.stop_all(restore_devices: bool = True)`` now
picks the path based on the flag — ``proc.stop()`` for the normal
branch, public ``proc.cancel_task()`` for the "nothing" branch.
- ``TargetProcessor.cancel_task()`` (new, on the abstract base) cancels
the loop task and *awaits* its termination so no half-written frame
is in flight when the process exits. Replaces an earlier draft that
reached into the private ``_task`` attribute via ``getattr``.
- Lifespan in ``main.py`` reads the setting at shutdown and forwards
the flag; falls back to ``stop_targets`` on any read error.
- ``/health`` exposes ``uptime_seconds`` (process-wide monotonic clock
captured at first import of ``api.routes.system``) so the WebUI can
show the *server's* uptime instead of the browser session's.
Browser launch:
- ``__main__._open_browser`` now polls ``/health`` for up to 30 s
instead of sleeping a flat 2 s, so the tab opens once the server
actually accepts requests.
Frontend:
- New "Shutdown action" picker in Settings → General, rendered via
IconSelect with ICON_SQUARE / ICON_CIRCLE (added to ``core/icons.ts``
+ ``circle`` path to ``icon-paths.ts``).
- Transport-bar uptime ticker reads ``window.__serverUptime`` (typed
in ``global.d.ts``); shows "—" until the first /health response
lands so refresh doesn't briefly flash 00:00:00. After 99 h the
format widens to "Dd HH:MM:SS".
- New i18n keys for the action picker (label, hint, opt.stop /
opt.nothing + descriptions, saved / save_error toasts) in en/ru/zh.
No data migration needed — the setting is additive and defaults to
the existing behavior.
|
||
|
|
2bae304107 |
fix(ui): single-row header + readable sidebar labels at narrow widths
At ≤1100px the header grid only declared 3 tracks for 4 children, so the toolbar wrapped to a second row, doubling the header height. Add a 4th track, tighten the meta cluster, and hide non-essential toolbar items (API link, tour-restart) so everything fits in one row. At ≤900px drop CPU/Mem cells (Uptime + Poll remain) so the toolbar still fits beside the meta cluster. Sidebar tab captions on the 56 px icon rail were ellipsis-truncated to "DASHBO…" / "AUTOMA…" / "INTEGR…". Switch to a 2-line clamp with tighter font/tracking so each label renders in full. |
||
|
|
dd415e2813 |
fix(ui): cards on pure black/white, decoupled from bg-anim
Three related fixes after the Phase-4 migration landed:
- `--card-bg` flipped from `#101216` / `#f5f6f8` to pure `#000000` /
`#ffffff` in base.css. Off-pure greys read as muddy when sitting on a
pure-black/white page background; pure values keep card surfaces flush
with the rest of the chrome and let the channel stripe + corner
bracket carry all the visual differentiation.
- Removed the `[data-bg-anim="on"] .card { background: rgba(...) }`
block that turned every entity card translucent whenever the WebGL
background was enabled. Card backgrounds are now stable across the
toggle — the shader bleeds through `body { background: transparent }`
only, not through cards. The same card now reads identically with the
shader on or off.
- WebGL shader base colour (`_bgColor` in bg-anim.ts and bg-shaders.ts)
was using the legacy mid-grey `#1a1a1a` / `#f5f5f5`. That added a
constant grey haze under the additive accent glow that didn't exist
on the surrounding pure-black/white page. Switched to `[0,0,0]` /
`[1,1,1]` so the shader composes against the same base as the page.
- Reverted two leftovers from the Phase-4 commit where I had migrated
`.template-card` and `.graph-node-body` away from `var(--card-bg)`
toward `var(--lux-bg-1, …)`. Those backgrounds now live on
`var(--card-bg)` again, matching every other migrated card.
|
||
|
|
b43e1cf375 |
feat(ui): Lumenworks treatment for Inputs / Integrations / Graph tabs
Brings the remaining tabs in line with the Channels-tab visual language: - .template-card now mirrors .card and .dashboard-target — channel stripe on the left edge with glow, silkscreened corner bracket top-right, hairline border on --lux-bg-1, hover lift + stripe widen-and-glow. Covers streams, capture / pp / cspt / pattern / audio templates and every Integrations card (HA / MQTT / weather / value / sync clocks / game integrations). - Channel mapping extended in cards.css. Direct attribute hooks for the per-domain ids; section-scoped hooks via [data-card-section="…"] for the cards that share a generic data-id (HA / MQTT / weather / value → cyan, game-integrations → amber, sync-clocks → violet, HA-light-targets → signal). No JS changes — uses the section markup CardSection.render already emits. - Graph editor nodes pick up the studio-console palette: --lux-bg-1 fill with hairline stroke, hover bold-line, selected/running stroke --ch-signal with drop-shadow glow. Title font moved off Big Shoulders Display (which read as "stretched" at 12 px) onto --font-body (Manrope); subtitle keeps the mono-uppercase caption treatment with a conservative letter-spacing. Running gradient now rides the channel palette (signal → cyan → signal) rather than the legacy primary / success colours. Port labels and grid dots adopt --lux-line tokens. - Graph node titles get real text-overflow:ellipsis behaviour. SVG <text> can't do that natively, so renderNodes runs a post-mount fit pass that binary-searches the longest character prefix that fits inside the clip rect (with 2 px slack), suffixed with "…". Trailing whitespace is stripped before the ellipsis so we never get "Foo …". Full text is stashed on data-full-text so the fit can be re-run on re-renders. Also bundles two perf-charts fixes from the same session: - Hover regression — listener was bound to .perf-charts-grid, which rerenderPerfGrid() replaces. Moved to document.body with a guard, and the cursor → sample math now uses the same sliceN as the spark rendering so the tooltip stays accurate when the user changes the window setting. - Color picker on every perf cell. Patches / Total FPS / Devices now expose the same color picker as the spark cells; defaults added to METRIC_CSS_VARS. Each card gets an inline --perf-accent on render so saved colours apply immediately, including across rerenderPerfGrid. |
||
|
|
56853b7123 |
feat(dashboard): per-account customizable dashboard with slide-in panel
Open-registry section/perf-cell schema persisted server-side under
db.get_setting('dashboard_layout'); localStorage cache for instant
first-paint, server sync after auth. 5 built-in presets
(Studio/Operator/Showrunner/Diagnostics/TV); JSON export/import.
Slide-in Customize panel toggles section + perf-cell visibility,
reorders via hand-rolled HTML5 drag (with up/down buttons for
keyboard/TV-remote use), changes density per section, and exposes
global Width / Animations / Perf-mode / Window with per-cell Inherit
overrides.
Window setting now drives the actual sparkline slice (30s/1m/2m/5m at
configurable poll interval) instead of always rendering 120 fixed
samples. Perf-grid edits re-render in place — sparklines repaint from
persistent module-level history, value labels replay from cached
last-fetch payload, so there is no flicker frame and no zero-data
window between layout change and next poll. initPerfCharts now fires
an immediate fetch on init so reload no longer shows "—" until the
first interval tick.
Reset confirmation uses the project's themed showConfirm modal
instead of the browser dialog. Reserved registry keys (audio-meters,
alerts, led-preview, source-thumbs, pinned, flow) are forward-
compatible so v1.1 cards slot in without a schema bump.
Backend exposes GET/PUT/DELETE /api/v1/preferences/dashboard-layout
treating the body as opaque JSON with a numeric version gate; covered
by 6 round-trip / validation / unknown-field tests.
|
||
|
|
70c95d1c09 |
feat(ui): item-card restyle, perf hover tooltips, FPS ceiling
Item cards (Automations, Channels, Inputs, Integrations):
- `.card-title` — bumped to weight 700, -0.01em tracking, solid --lux-ink
for better presence against the flat card bg.
- `.card-subtitle` / `.card-meta` — mono font, 0.04em tracking, tighter
gap so rule chips pack in a readable row.
- `.stream-card-prop` rule chips — rectangular 2px radius + hairline
border + flat dark bg (was rounded 10px grey pill). Channel-signal
icon tint; hover fades in a channel-green wash with matching border.
- `.badge` generic — rectangular 2px radius, mono 0.62rem, 0.12em
tracking, hairline border slot for variants.
- `.badge-automation-active` — channel-signal tinted bg + border +
soft outer glow so the "ACTIVE" state reads at a glance.
- `.badge-automation-inactive` / `-disabled` — transparent with a
hairline outline so they sit quietly alongside the active variant.
- `.device-url-badge` — switched from rounded pill to rectangular
hairline mono chip; hover shifts to filled bg + bolder border +
brighter ink.
- `.card-actions` — 1px hairline top divider, 6px gap.
- `.btn-icon` — 7/10px padding, 1rem icon, hairline border, channel-
signal glow on hover (replaces the old scale(1.1) jiggle).
- `.btn-icon.btn-warning` — amber ink + hairline + amber hover glow
(drives the "disable" action in the automation card).
- `.btn-icon.btn-success` — signal-green ink + hairline + green hover
glow ("enable" action).
Cross-link navigation highlight:
- `cardHighlight` keyframes were using an undefined `--primary-rgb` var,
so the outer glow fell back to 59/130/246 (the Tailwind blue default).
Rewritten with `var(--ch-signal)` + color-mix so the highlight tracks
the accent picker and reads as signal-green. Added double-layer
box-shadow (ring + 32px/10px bloom) so the highlight is obvious on
the flat dark/light card surfaces. Added .dashboard-target to the
selector + `isolation: isolate` so the glow isn't clipped inside
overflow: hidden containers (perf strip cells, tree-nav panels).
Perf strip (follow-up polish):
- Total FPS cell shows `/<N>` ceiling suffix next to the live value —
sum of fps_target across running targets, styled like the Patches
"/12". A dashed horizontal reference line at that ceiling is rendered
on the sparkline so the live value reads as "percentage of max
achievable throughput." Y-axis ceiling grows to targetSum * 1.1 so
the dashed line never clips.
- Removed the empty `.perf-chart-app` pill in the FPS cell (no app
variant). Added `:empty { display: none }` as a safety so any other
unpopulated cell doesn't render a ghost pill.
- Hover tooltips on all sparks — single floating `.perf-chart-tooltip`
in <body> with fixed positioning; event-delegated from the perf
grid so re-renders don't need rebinding. Shows metric label + sys
value + app value (in both-mode) + "−Ns ago" age line derived from
the poll interval. Vertical marker line follows the cursor over the
spark; `cursor: crosshair` on the spark container signals interact-
ability. `pointer-events: none` shifted from the spark container
down to the inner SVG so hover events land on the container.
Grid:
- Perf strip capped at 4 cols even on widescreen; wraps to 2 rows ×
4 when the full 7 cells are present. Responsive breakpoints at
1100 / 760 / 480 px.
- Big value font uses `clamp(1.8rem, 2.8vw, 2.8rem)` so readouts
like "18.9/31.8 GB" fit a 1fr cell at desktop while still scaling
down on narrow viewports. `white-space: nowrap; flex-wrap: nowrap;
overflow: hidden; text-overflow: clip` prevents mid-text wrapping.
- `.perf-chart-spark` uses `margin-top: auto` so sparkline baselines
align across cells regardless of whether a subtitle is present
(CPU/GPU model name, FPS min/max).
Dashboard target meta:
- Integrations card stripe reverted to the default signal color so it
matches the overall accent picker; the health-dot inside the card
carries the connection state. Removed the per-integration channel
override in both cards.css and dashboard.css.
Section headers:
- `.dashboard-section-header` / `.subtab-section-header` underline
switched from dashed to solid; channel-green 40px accent rule on
the left remains.
- Section count badge (`.dashboard-section-count`) restyled to match
the rest of the badge family (mono tabular-nums, 2px radius, hairline
border, --lux-bg-3 fill).
Build: tsc --noEmit clean; CSS bundle stable at ~216 KB.
|
||
|
|
e5a2af9821 |
feat(ui): dashboard polish, richer perf strip, transport-bar controls
Dashboard perf strip:
- Unified rack-module shell with hairline-divided cells (mockup parity)
replacing 3 separate perf cards. Cells auto-wrap to 2 rows of 4 on
widescreen; responsive breakpoints at 1100 / 760 / 480 px.
- Active Patches cell (first) shows running/total channel count plus up
to 4 live FPS readouts with channel-colored stripes; bottom-right
radial glow anchors the "live channel bank" corner.
- Total FPS cell — aggregate throughput across running targets, mono
"fps" unit suffix, session-peak-scaled sparkline with a 60 FPS floor.
- Devices cell — online/total count + per-device dot strip (green when
online with signal-glow, coral when offline, tooltip with name +
latency), fed from /devices/batch/states (added to the dashboard
batch poll).
- Value font uses clamp(1.8rem, 2.8vw, 2.8rem) + white-space: nowrap so
long readouts (RAM "18.9/31.8 GB", GPU "50% · 37°C") scale down
instead of wrapping.
- Sparklines anchor to the cell bottom via margin-top: auto so baselines
align across cells regardless of subtitle presence.
- App-load tag ("APP 3.1%") moved to a pinned top-right position per
card, accent-colored pill; replaces the subdued inline badge.
- Perf mode toggle (System / App / Both) triggers an immediate poll so
positioning updates without waiting for the next tick.
- Chart.js removed from perf-charts — inline SVG sparklines with
drop-shadow filter for the "lit instrument" feel. Chart.js still used
for per-target FPS charts via chart-utils (now owns the registration).
- Fixed history seed bug: app_ram is MB in the server history payload,
not percent — convert to percent using sample's ram_total before
pushing into _appHistory.ram. Skip seeding app_gpu_mem since the
history schema has no gpu_memory_total.
- Temperature card reveals with an explanatory hint when the backend
reports cpu_temp_hint_key (e.g. Windows without LibreHardwareMonitor)
instead of silently hiding; .perf-chart-card-hint neutralizes the big
display font so the message reads as plain body copy.
Transport bar:
- LED brand mark — 28 px, double-layer signal glow (0 22px + 0 8px),
brandPulse animation. Brand-stack wraps the title + version so
"LED GRAB" sits above "V0.3.0" on a single line each.
- Transport status chip — bigger (9/18 padding), mono uppercase,
inner+outer signal glow when .is-armed.
- Transport meta cells — Uptime (JS-local session ticker), CPU (app
CPU share), Mem (app RAM, G/M format) as stacked KEY/VALUE mono
readouts with hairline separators.
- New interactive Poll cell cycles through 1/2/5/10s presets on click;
replaces the range slider that used to live in the Dashboard toolbar
(it controlled the whole app, not just the Dashboard).
- Header icon buttons — hairline-bordered 30 px squares with channel-
glow on hover, replacing the pill container.
- Perf poll moved to global bootstrap so transport CPU / Mem stay live
across all tabs (was paused when leaving the Dashboard).
- Connection pip (#server-status) hidden; the brand mark itself turns
coral when offline via :has() selector on .header-title.
Dashboard cards:
- renderDashboardTarget now emits full rack-module markup with CH badge,
name, meta, LED cluster, 3-cell metric grid (FPS / Uptime / Errors),
and patch-label + stop button. Running cards get the signal-flow
strip at the bottom. data-fps-text / data-uptime-text / data-errors-
text hooks preserved so _updateRunningMetrics updates in place.
- LED count surfaced in the target card meta line (e.g. "LED · WLED ·
144 LED · GRADIENT") when the linked device reports led_count > 0.
- Integrations (HA + MQTT) picked up .mod-head markup — compact module
layout with online/offline patch indicator. Integration card stripe
uses the default signal color (not cyan or amber).
- Scene presets, sync clocks, automations gain the same compact module
treatment. Automations/scenes dropped into a dashboard-autostart-grid
so they share the visual language.
- Perf mode toggle, stream sub-tabs, cs-count / tree-count /
tab-badge / dashboard-section-count badges all use the mono
rectangular style with tabular-nums.
Command palette:
- Flat background (no gradient), channel-accent rule across the top,
mono placeholder / group headers / footer, active result gets a
channel-green left stripe.
Modals:
- Popover + backdrop get a stronger radial dim + 6 px blur.
- Per-modal-ID channel lanes (target→green, source→cyan, audio→magenta,
automation/scene→violet, settings→amber, confirm→coral) via --modal-ch
override.
- Modal header picks up a vertical channel stripe + hairline divider;
footer gets hairline top + subtle wash.
Components:
- Inputs use hairline borders + tabular-nums mono for number fields;
focus state has channel-green ring + soft glow.
- Buttons switch to mono-uppercase with signal-glow on primary,
coral-glow on danger, hairline border on secondary.
- Card background flattened — removed gradient wash in favor of solid
--lux-bg-1 for both dark (#0e1014) and light (#f6f8fb).
- Page background: pure black for dark, pure white for light.
Color-picker:
- Always detaches to <body> with fixed positioning when its swatch sits
inside an overflow: hidden / auto / clip ancestor (perf strip, modal
bodies, tree-dd panels). Prevents the popover getting clipped.
Settings modal:
- Remembers the last-opened tab via localStorage key
settings_active_tab; falls back to 'general' if the tab id no longer
exists. Explicit overrides (donation → about, update badge →
updates) still work because callers invoke switchSettingsTab after
openSettingsModal.
Microcopy:
- Sidebar / transport localization for en/ru/zh:
sidebar.workspaces · transport.meta.{uptime,cpu,mem,poll,poll_hint}
· transport.status.{ready,armed} · dashboard.perf.{active_patches,
total_fps,devices}
Backend (coordinated with frontend):
- /system/performance now returns cpu_temp_hint_key when no live CPU
temperature is available, so the Temperature card can render an
actionable explainer instead of being hidden. Frontend respects the
key via t() lookup.
Section headers:
- Underline switched from dashed to solid; channel-green accent rule
(40 px) on the left remains.
Build / tests:
- ruff clean on touched Python files.
- tsc --noEmit clean.
- Python metrics-provider tests: 18 passed.
- CSS bundle ~214 KB.
|