feat(graph): make the visual editor a full wiring control surface
Lets users wire the system end-to-end from the graph, and fixes the core bug that made drag-to-wire silently fail. - Fix drag-to-wire 422s across 5 entity kinds: updateConnection() now echoes the target's discriminator (source_type/stream_type/target_type) into the partial PUT, so value/colour-strip/audio/picture sources and output targets all wire correctly. New contract test (54 cases) in test_graph_wiring_contract.py. - Re-wire composite layers / mapped zones from the graph (right-click a layer/zone source edge -> Re-wire). Whole-list write preserves every sibling layer/zone setting, with an optimistic-concurrency guard and undo. - Secret-safe /graph topology: project entities to id/name/subtype + reference roots so the endpoint cannot leak webhook tokens or other credentials. - Carry slot indices on list edges; node custom-icon + schema-drift refinements; rewire i18n keys (en/ru/zh); wiring-control roadmap (TODO.md).
This commit is contained in:
@@ -1032,32 +1032,68 @@ viewer. Driven by the ULTRA-DEEP review (findings A1–A5, B1–B6, C1–C6, D1
|
||||
(only offers slots the target entity actually has). Writes the partial
|
||||
`{ <slot>: { source_id } }` payload → backend `Bindable*.apply_update` merges,
|
||||
preserving the static value. Verified data-safe (no `from_raw`/value-reset path).
|
||||
- [x] Render the two functional value-source references `buildGraph` was missing —
|
||||
`value_source.value_source_id` (gradient_map → inner value source) and
|
||||
`value_source.color_strip_source_id` (css_extract → strip). Both are runtime-
|
||||
resolved and already drag-editable; now visible/detachable in the graph.
|
||||
- [x] **B4 foundation:** backend schema now authoritative about graph-editability
|
||||
(`is_editable()` + `editable` flag in `/graph/schema`); `validate-connection`
|
||||
hardened to reject non-editable fields (colour/list/double-nested), not just lists.
|
||||
- [x] **B4 drift guard + gap fixes:** `checkSchemaDrift()` (graph-connections.ts) warns
|
||||
once if the frontend `CONNECTION_MAP` editable set diverges from `/graph/schema`
|
||||
(the automated "10-step checklist"). Surfacing it found 3 real gaps; fixed 2:
|
||||
`color_strip_source.input_source_id` + `processing_template_id` are now drag-editable
|
||||
(processed-strip wiring; `apply_update` is partial-safe). The 3rd —
|
||||
`device.default_css_processing_template_id` — is intentionally NOT drag-editable
|
||||
(the device PUT route isn't partial-safe; a one-field PUT could null the URL) and is
|
||||
in the drift-check exclude set. Also broadened `_availableMatches` to hide any slot
|
||||
the target entity doesn't expose (subtype-accurate; refs are always-emitted so empty
|
||||
slots stay wireable). Review also caught a **dead `output_target.picture_source_id`
|
||||
slot** (no output target stores it — not a field/schema, never emitted) — removed
|
||||
from both registries + `buildGraph`.
|
||||
- [x] **Comprehensive review pass (4 subagents: backend/frontend-core/orchestrator/security).**
|
||||
Findings fixed:
|
||||
- **CRITICAL (security):** `GET /api/v1/graph` leaked plaintext **webhook tokens**
|
||||
(`asdict` recursed `Automation.rules[].token`, an auth-equivalent secret). Fixed with
|
||||
**field-projection** — `serialize_entity_for_graph()` / `graph_field_roots()` project
|
||||
each entity to only `{id, name, subtype, reference-roots}`; secrets can't survive.
|
||||
Added a structural regression test asserting no projection root is secret-bearing for
|
||||
any kind (drift-proof boundary) + a token-drop test.
|
||||
- MEDIUM: added missing `value_source.clock_id` (AnimatedColorValueSource → sync_clock)
|
||||
to the backend registry for topology/dependents completeness (drift-excluded on the
|
||||
frontend — value-source PUT needs a `source_type` discriminator, so it's editor-only).
|
||||
- MEDIUM/LOW: `CSS.escape` on the markIssues id selector; grouped/clarified
|
||||
`_DRIFT_EXCLUDE`; fixed the stale `_availableMatches` JSDoc; documented the
|
||||
`checkSchemaDrift` forward-reference. Orchestrator + frontend-core + security: APPROVE.
|
||||
- Verification: `npm --prefix server run typecheck` + `run build` clean; ruff clean;
|
||||
graph backend tests 24 pass; full backend suite 1614 pass. 6 code-review passes,
|
||||
graph backend tests 35 pass; full backend suite green. ~8 code-review passes,
|
||||
all CRITICAL/HIGH findings fixed.
|
||||
|
||||
### Left to do (deferred)
|
||||
|
||||
- [ ] **BindableColor slots** (`color`, `color_peak`, `fallback_color`,
|
||||
`default_color` on color_strip_source) — left non-editable in B1 because
|
||||
scalar-value-source → colour-slot semantics are unclear. **First check:** do
|
||||
colour-producing value sources exist? If a value_source can drive a colour,
|
||||
mark these 4 CONNECTION_MAP entries `bindable: true` (they already validate on
|
||||
the backend); the write path (`{ color: { source_id } }` → `BindableColor.apply_update`)
|
||||
is already value-preserving. If not, leave read-only.
|
||||
- [ ] **B4 — delete the frontend `CONNECTION_MAP` duplication.** Blocked on a backend
|
||||
write endpoint. Plan:
|
||||
1. Add `PUT /api/v1/graph/connection` (body: target_kind/id, field, source_id)
|
||||
that validates (reuse `validate_connection`) then APPLIES the write
|
||||
server-side — top-level via the owning store's update; single-level bindable
|
||||
via `apply_update`. Must reuse each entity's existing update path (validation,
|
||||
factory reconstruction, entity_changed event) — do NOT hand-roll per-store
|
||||
mutation. Highest regression risk; needs per-kind tests.
|
||||
2. Switch frontend `updateConnection`/`detachConnection` to call it.
|
||||
3. Have the frontend fetch `/graph/schema` and build ports/edges from it,
|
||||
then delete `CONNECTION_MAP` + the buildGraph edge duplication
|
||||
(graph-connections.ts / graph-layout.ts). Removes the 10-step sync checklist
|
||||
in `contexts/graph-editor.md`.
|
||||
- [x] **BindableColor slots** — CHECKED, decision: keep read-only (won't fix).
|
||||
Value sources are scalar-only (`ValueStream.get_value() -> float`) and every
|
||||
colour consumer (`color_strip/single.py`, `effect_stream.py`) reads the static
|
||||
RGB via `bcolor()`, ignoring `source_id`. So a value_source cannot drive a
|
||||
colour — wiring `color`/`color_peak`/… would be a dead binding. Documented in
|
||||
`api/graph_schema.py` next to the BindableColor entries. (Would only become
|
||||
viable if a colour-producing value-source type is added.)
|
||||
- [~] **B4 — delete the frontend `CONNECTION_MAP` duplication.**
|
||||
- [x] **Foundation done:** the backend schema now carries an authoritative
|
||||
`editable` flag per field (`is_editable()` in `api/graph_schema.py`, mirroring
|
||||
the frontend `_isEditable`: top-level refs + single-level BindableFloat slots;
|
||||
NOT colour/list/double-nested). `validate-connection` is hardened to reject any
|
||||
non-editable field (was list-only). `editable` is surfaced in `/graph/schema`.
|
||||
- [ ] **Remaining (the refactor):** frontend fetches `/graph/schema` on load and
|
||||
derives connection metadata + edges from it (port the `extract_refs` dot-path/list
|
||||
grammar to TS), keeping only a tiny `kind → {endpoint, cache}` write-routing table;
|
||||
then delete the field-level `CONNECTION_MAP` + the `buildGraph` edge loops
|
||||
(graph-connections.ts / graph-layout.ts). Removes the 10-step sync checklist in
|
||||
`contexts/graph-editor.md`. **A backend apply-write endpoint is NOT required** —
|
||||
keep the proven per-entity PUT. Risk: regressing drag-connect/bindable; keep a
|
||||
dev drift-check (frontend editable set vs `/graph/schema`) during the transition.
|
||||
Note: frontend `CONNECTION_MAP` also has inert `ha_source_id`/`gradient_id` entries
|
||||
(no graph node kind) — drop them, the backend schema already omits them.
|
||||
- [ ] **D6 — blueprint import/instantiate.** Export exists; the apply half (serialize
|
||||
a selected subgraph's topology + entities, re-import with id remapping, conflict
|
||||
handling) is large and data-integrity-sensitive (see Data Migration Policy in
|
||||
|
||||
Reference in New Issue
Block a user