From d505388f0eb6d4705ac7a561b6248af3d8f664e3 Mon Sep 17 00:00:00 2001 From: "alexei.dolgolyov" Date: Thu, 28 May 2026 23:38:04 +0300 Subject: [PATCH] docs: graph-editor wiring-control roadmap (review findings A1-D6) --- TODO.md | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/TODO.md b/TODO.md index 1e2698f..18b293a 100644 --- a/TODO.md +++ b/TODO.md @@ -993,3 +993,87 @@ After phase 1 the codebase will have 3 fresh examples of "ping the LAN, listen f - LOW: Nanoleaf `.port` property added; pair-then-create E2E test added. - Tests: 1379 pass (+21 regression tests). + +## Graph editor — "full control of wiring via graph" (in progress) + +Goal: make the visual graph a first-class wiring control surface, not just a +viewer. Driven by the ULTRA-DEEP review (findings A1–A5, B1–B6, C1–C6, D1–D6). + +### Done (NOT yet committed — awaiting review/commit) + +- [x] **A1** Undo/redo wired to connect/detach/move (was dead code); inverse ops + throw on failure so the stack can't silently desync. +- [x] **A2** Manual node layout persists to `localStorage` (`graph_node_positions`), + cleared on relayout. +- [x] **A3** Scene-preset disambiguation — deactivation scene now reachable via a + field picker (was always picking the first match). +- [x] **B6** Edge field labels (revealed on zoom ≥ 0.9). +- [x] **C3** Health overlay — broken refs (referrer exists, target missing), + dependency cycles, orphans; node warning badges + an issues toolbar button. +- [x] **D1** `GET /api/v1/graph/schema` — authoritative connectable-field registry + (`api/graph_schema.py`, pure + unit-tested). +- [x] **D2** `GET /api/v1/graph` (nodes+edges+validation) and + `GET /api/v1/graph/dependents/{kind}/{id}`. +- [x] **D4** `POST /api/v1/graph/validate-connection` — existence + source-kind + + cycle pre-flight; frontend validates before every write (fails open if the + endpoint is unreachable). List/double-nested fields rejected. +- [x] **B2** Drop-on-node connect — empty top-level slots are now wireable (drop a + source onto any compatible node body, not just an existing port). +- [x] **C4** Overwrite-occupied-slot confirm + delete-with-dependents warning + (single delete only; bulk keeps the batch confirm). +- [x] **D5** Create-and-connect — drag a port onto empty canvas → pick a compatible + new entity kind → it's created and auto-wired (kind-scoped watcher). +- [x] **D6 (read-only half)** "Export graph (JSON)" toolbar action. +- [x] Custom per-entity `icon` + `icon_color` now render on graph nodes (parity + with custom node colours; fallback to kind/subtype glyph). +- [x] **B1** Edit single-level **BindableFloat** value slots from the graph + (`brightness`, `smoothing`, `intensity`, `scale`, `speed`, … on + color_strip_source; `brightness`/`transition` on output_target). Subtype-safe + (only offers slots the target entity actually has). Writes the partial + `{ : { source_id } }` payload → backend `Bindable*.apply_update` merges, + preserving the static value. Verified data-safe (no `from_raw`/value-reset path). +- 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, + 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`. +- [ ] **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 + CLAUDE.md). Scope as its own feature. +- [ ] **List-slot editing** (composite `layers[]`, mapped `zones[]`, scene preset + `targets[]`) — needs an element index in the write + validate paths + (`validate_connection` currently rejects list fields). Edit via entity modal + for now. + +### Notes / decisions + +- The backend `CONNECTION_SCHEMA` (`api/graph_schema.py`) is the authoritative + superset; it already declares the bindable + list + value_source-chain edges. The + frontend `CONNECTION_MAP` still owns write-routing (endpoint/cache) — that's the + only reason it survives (see B4). +- Bindable edges render dashed (`.graph-edge-nested`) but ARE editable — the dashed + style intentionally distinguishes value bindings from structural edges. +- `validate-connection` and `dependents` fail **open/safe** on the frontend so the + graph keeps working against an older server without these endpoints.