Two HIGH issues surfaced by review of 3b8f00e:
1. ``_build_game_event`` was newly succeeding where the old store
raised ``ValueError("Invalid source type: game_event")``. The
coverage-assertion-symmetry comment was honest about it being
a path that didn't exist before, but silent broadening of the
create contract is a real behaviour delta — any internal caller
that previously caught the error would now succeed.
Make ``_build_game_event`` raise NotImplementedError. The
coverage assertion still passes (the entry exists), but the
historical "you can't create game_event sources through the
store" contract is preserved. game_event instances continue to
be wired up by the game-integration setup path.
2. The new ``create_source`` ran ``_check_name_unique`` BEFORE
``build_source``. When both ``source_type`` is invalid AND
``name`` collides with an existing source, the old code raised
``"Invalid source type: …"`` first; the new code raised the
name-collision error. Swap the order: build first (which
validates source_type), then check name uniqueness, then
persist. Bonus: a uuid is no longer minted for a source we end
up rejecting on type.
New test pins the game_event NotImplementedError so a future
refactor doesn't accidentally re-open the create path.
38 value-source-store + factory tests stay green; ruff clean.
ValueSourceStore.create_source used to be a ~260-line if/elif chain
over 14 source_type strings; update_source did the same dance again
with 14 isinstance branches (audit finding C7 store-side). Each
branch duplicated the common-fields scaffold and the per-type
defaulting + validation logic.
Lift each per-type create / update body into a free function in a
new ``storage.value_source_factories`` module:
* ``CREATE_BUILDERS[source_type]`` — owns defaulting + per-type
validation (HA needs ha_source_id + entity_id; gradient_map
needs value_source_id; system_metrics validates against
VALID_SYSTEM_METRICS; http rejects interval_s < 1; the two
adaptive_* sub-modes route to the same AdaptiveValueSource
class with different source_type discriminators).
* ``UPDATE_APPLIERS[source_type]`` — mirrors the above on the
update side; ``resolve_ref`` is applied to cross-entity
references so empty-string clears keep working.
* ``build_source(...)`` / ``apply_update(source, **kwargs)`` are
the public entry points the store calls.
* ``_assert_factory_coverage()`` runs at module import and
requires BOTH registries to match storage's _VALUE_SOURCE_MAP
exactly.
The store's ``create_source`` shrinks from ~260 lines to ~25;
``update_source`` from ~200 lines to ~40.
Tests: 14 new tests cover registry coverage in both directions
plus drift assertions, representative builder paths (static /
adaptive_time / adaptive_scene / ha_entity / http / unknown),
the AdaptiveValueSource dual-source-type discriminator, and
several applier paths including ``**_`` swallowing unknown kwargs
and HTTP zero-interval rejection. 47 existing value-source store
tests stay green; 769 storage / core / api tests in aggregate.
Ruff clean.
Two CRITICAL data-safety bugs from the architecture audit and the two
worst parallel-change problems are fixed in one coherent pass.
Audit findings addressed:
- C2 silent CSS response fallback. The previous _RESPONSE_MAP fell
through to a fabricated PictureCSSResponse whenever a source
class lacked an entry; in particular game_event sources were
silently mis-shaped. Now: GameEventCSSResponse/Create/Update
schemas exist, _RESPONSE_MAP is re-keyed by source_type string,
an import-time _assert_response_map_coverage() requires symmetric
agreement with storage._SOURCE_TYPE_MAP, and the runtime path
raises instead of fabricating a response.
- C11 string-replace JSON migration. ColorStripStore used
blob.replace('"source_type": "static"', '"source_type":
"single_color"') which can corrupt unrelated substrings (e.g.
an animation type named "static_wave") and provides no audit,
no transaction, no idempotency. Replaced with
storage.data_migrations.MigrationRunner backed by a
data_migrations audit table. Each migration runs inside one
db.transaction() that covers the applied-check, the apply(),
and the audit-INSERT — partial failures roll back atomically.
StaticToSingleColorMigration parses each row with json.loads
and mutates only the source_type field. Frozen-write databases
skip with a warning.
- C3+C4 color-strip stream dispatch. The 7-branch elif in
ColorStripStreamManager.acquire() and the duplicate one in
ws_stream._create_stream() now share a single STREAM_BUILDERS
registry in core.processing.color_strip_kinds, keyed by
source.source_type. Both call sites populate a StreamDeps bag
and delegate to build_stream(). _assert_stream_kind_coverage()
asserts at import that STREAM_BUILDERS plus SHARABLE_KINDS
partitions storage._SOURCE_TYPE_MAP. ws_stream's preview path
wraps each FastAPI-DI getter in _safe() so non-audio previews
no longer crash when audio/CSPT stores are not wired.
- C6+C7 value stream dispatch. The 14-branch isinstance ladder in
ValueStreamManager._create_stream and its silent
StaticValueStream(value=1.0) fallback are replaced by
core.processing.value_kinds.STREAM_BUILDERS, keyed by
source_type string (so AdaptiveValueSource's adaptive_time and
adaptive_scene route to different builders correctly). The
manager retains only the SyncClockRuntime pre-acquisition step
for animated_color (kinds needing this are listed explicitly
in NEEDS_CLOCK_RUNTIME). Symmetric coverage assertion plus a
separate assertion that NEEDS_CLOCK_RUNTIME is a subset of the
registry.
Bundled in: the static->single_color rename plus the HTTPValueStream
/ http_endpoint introduction that were already in flight on this
branch share these files; the registry refactor naturally absorbs
both via the new "single_color" / "static" alias entries and the
_build_http builder.
Tests: 26 new tests cover response-map coverage drift, migration
runner audit-table mechanics + transactional rollback +
frozen-write skip, and the two stream-builder registries. 343
existing storage / API / e2e tests stay green. Ruff clean.
- 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
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.
End-to-end BLE streaming: provider + client + per-protocol wire encoders
with whole-strip averaging, desktop (bleak) and Android (Kotlin BleBridge
via Chaquopy) transports, discovery with protocol-family detection that
auto-fills the UI, throttled not-connected warning + 10 s reconnect
cooldown so a dropped link no longer stalls the pipeline at ~30 s/frame,
and an explicit asyncio.wait_for wrapper around bleak connect() since
the WinRT backend doesn't always honor the timeout kwarg.
Also rewrites server/restart.ps1 to be parameterized (-Port / -Module /
-PythonVersion / timeouts / -Quiet), pick the right interpreter via the
py launcher, pre-flight the target module, poll port readiness on both
shutdown and startup, redirect child stdout/stderr so Start-Process
doesn't hang on inherited Git-Bash handles, and return proper exit codes.
Rolls in concurrent work: Android BLE permissions + launcher icons + ru/zh
resources, Chaquopy-safe value_stream psutil fallback, setup-required
modal, asset-store test coverage, and misc system/config touch-ups.
Fix test_list_filters test (filter_id field name mismatch).
Add tests for audio filters, template store, and source store.
All 678 tests pass, ruff clean, tsc clean, esbuild clean.
No dead code remaining from old source types.
Receive real-time events from games (CS2, Dota 2, LoL, etc.) and drive
LED effects through the existing color strip and value source pipelines.
Core:
- GameEventBus (thread-safe pub/sub) with standardized 23-type event vocabulary
- GameAdapter ABC + AdapterRegistry + MappingAdapter (YAML-driven)
- Built-in adapters: CS2 GSI, Dota 2 GSI, LoL Live Client, Generic Webhook
- Community YAML adapters: Minecraft, Valorant, Rocket League
- GameEventColorStripStream with 5 effects (flash/pulse/sweep/color_shift/breathing)
- GameEventValueSource with EMA smoothing and timeout
- 4 built-in effect presets (FPS Combat, MOBA Health, Racing, Generic Alert)
- Auto-setup for Valve GSI games (Steam path detection, cfg file writing)
- Demo capture engine exposed to non-demo mode
Frontend:
- Game tab in Streams tree navigation with integration cards
- Game integration editor modal with adapter picker, config fields, event mappings
- game_event source type in CSS and ValueSource editors
- Setup instructions overlay (markdown rendered)
- Live event monitor and connection test
API:
- Full CRUD for game integrations
- Event ingestion endpoint (adapter-level auth)
- Adapter metadata, presets, auto-setup, status/diagnostics endpoints
Introduce BindableFloat abstraction that allows any numeric property to be
either a static value or dynamically driven by a ValueSource. Backward-compatible
serialization: plain float when unbound, {value, source_id} dict when bound.
Backend:
- storage/bindable.py — BindableFloat dataclass + bfloat() helper
- 25+ scalar properties converted across all entity types
- Runtime VS acquisition in ColorStripStreamManager for CSS bindings
- All stream hot loops use self.resolve() for live values
- KeyColorsColorStripStream now inherits ColorStripStream
Frontend:
- BindableScalarWidget (slider + VS picker toggle) for all editors
- TypeScript BindableFloat type + helpers
- Graph editor edges for all bindable properties
- Audio source channel IconSelect grid
Fixes: daylight longitude, candlelight wind_strength/candle_type from_dict
Key Colors refactor:
- New `key_colors` CSS source type with inline rectangles
- KeyColorsColorStripStream: extracts N colors from screen regions
- CSS editor: EntitySelect for picture source, IconSelect for color mode
- Configure Regions button on card opens pattern canvas editor
- Live WS preview at 5 FPS with rectangle overlay + color swatches
- Removed KC target type, pattern template entity, and related API routes
- Removed KC/pattern template sections from Targets tab
HA light target improvements:
- Update rate, transition, mappings, brightness VS now editable via PUT
- Card crosslinks for HA source, CSS source, brightness VS
- HA connection status icon, text metrics (Hz, uptime)
- Brightness value source selector in editor
All store tests were passing file paths instead of Database objects
after the JSON-to-SQLite migration. Updated fixtures to create temp
Database instances, rewrote backup e2e tests for binary .db format,
and fixed config tests for the simplified StorageConfig.