Inspector: live data updates under interactive selections

This commit is contained in:
2026-06-29 09:58:32 +03:00
parent 6656811b8e
commit 8aa05e503d
13 changed files with 269 additions and 21 deletions
+29 -14
View File
@@ -82,19 +82,34 @@ collection (`spec-fields`), config baking (`spec-config`), standalone export
`columns · rows` cue. Enumerating by drawn table (not authored view) is forced by
Vega-Lite desugaring (a `point: true` line compiles to two layers). Replaced the
single-pair `core/result-data`.
- **M5 — live / interactive inspection** — make the inspector react to interactive
selections. A selection-as-**filter** (`filter: {param}`) recomputes a downstream
view's `data_N` live, so the inspector should re-read on selection change to show
the brushed result ("what am I visualizing _now_"); a selection-as-**highlight**
(a `condition` encoding) changes no data, so nothing to react to. Needs a refresh
model beyond the per-render `renderEpoch`: subscribe to the live view
(`view.addDataListener` / selection signals), debounced (a brush drag pulses
continuously — latency/interaction `/council` pass), and a default-on-vs-toggle
choice. Selection `*_store` tables are not drawn, so the M4 enumeration already
ignores them.
- **M3 — view-scoped extract** — seed Extract from the focused view's inline data
(reusing the cursor-scope machinery) and rewrite that view's `data`.
- **M5 — live / interactive inspection** — the inspector reacts to interactive
selections. `RenderHandle.onDataChange` attaches a debounced `view.addDataListener`
to each drawn table's resolved + input names; a selection-as-**filter**
(`filter: {param}`) recomputes a downstream view's `data_N`, so its listener fires
and `LivePreview` bumps a `liveEpoch` that re-reads the table ("what am I
visualizing _now_"); a selection-as-**highlight** (a `condition` encoding) changes
no data, so nothing fires. The watcher is gated on the inspector being open
(a collapsed one costs nothing) and is **always live, no toggle** — the table just
tracks the brush; the ~120ms debounce coalesces a drag's continuous pulses.
Selection `*_store` tables are not drawn, so the M4 enumeration already ignores them.
- **M3 — view-scoped extract** — Extract is scoped to the view at the cursor.
`services/extract-action` resolves the focused view's data binding
(`dataBindingAtPath`) and lifts whichever of two embedded-data shapes it carries:
a view's inline **`data.values`** (`inlineValuesOf` → rewrite that view's `data`
block at its anchor path), or a **`{ name }` reference to a self-defined
`datasets` entry** (`selfDefinedPayloadOf``promoteSelfDefinedDataset`: drop the
`datasets` entry, and the map when it empties, so the same reference resolves to
the new library dataset; rename refs when the name changes, pre-filled with the
existing name). The toolbar offers Extract whenever any view carries either shape
(`specHasExtractableData`); a cursor in a view with neither (a library ref, url,
generator) gets a guide toast. A single-view spec resolves to the root binding
from any cursor, so the common case is unchanged. Confirm re-serializes in the
app's house style. A `lookup` transform's inline `from.data` is covered incidentally
`dataBindingAtPath` finds it like any view binding (which also means the editor
_hints_ read the lookup table's columns when the cursor sits inside the transform;
acceptable for now, noted). The orphan case — a `datasets` entry no view references
— is out of scope (no view to scope the cursor to; it is dead data to delete).
Delivery is incremental, one milestone per commit, verified against real behavior.
The consolidated data-model contract write-up into `docs/architecture` (05/08)
lands once the shape is final.
The durable contract is recorded in `docs/architecture` 05 (live inspection) and 07
(reference detection + extraction, §3.13.2); this memo stays the point-in-time record.