Editor: view-scoped data context — per-view dataset columns in hints and transforms

This commit is contained in:
2026-06-28 20:54:46 +03:00
parent e69430834a
commit a75ea5b59e
14 changed files with 547 additions and 244 deletions
+3 -3
View File
@@ -75,9 +75,9 @@ This is the at-a-glance list; keep it in sync with them.
**Next (flagged for build):**
- **Multi-view data model** ([`multi-view-data-model-scope.md`](exploration/multi-view-data-model-scope.md)) —
durable composition support across the data-facing features. M1 (Vega-Lite-fidelity
reference classifier, `core/spec-data`) is done; M2M4 extend the editor data context,
Extract, and the data inspector to be view-scoped.
durable composition support across the data-facing features. Done: the Vega-Lite-fidelity
reference classifier (`core/spec-data`) and the view-scoped editor data context. Remaining:
per-view data inspection (`DataInspector` view selector), then view-scoped Extract.
- **Chart Builder · 3B starter examples** ([`chart-builder-enhancement-scope.md`](exploration/chart-builder-enhancement-scope.md) §3) —
a small set of curated starters, one per covered FT intent. Reshaped by 3C: a
builder-openable starter must reference a dataset, so it ships paired sample datasets (or is
+23 -12
View File
@@ -286,16 +286,22 @@ thin app-layer services.
- `spec-transforms` — wrap a view in `layer`/`hconcat`/`vconcat`/`facet`/`repeat`; collapse
a single-child composition (`unwrapSingleton`). Object-in/object-out.
- `spec-cursor` — `findViewRange` (cursor offset → the enclosing view's byte range) and
`valueKeyAtOffset`/`stringValueAtOffset` (the JSON context at the cursor), over `jsonc-parser`.
- `spec-fields` — field names a spec's own transforms introduce (their `as`).
- `spec-inline-data` — the inline rows a spec carries (`data.values`, a `datasets` entry).
- `spec-data` — the Vega-Lite data model: classify a `data` block (`classifyData`, mirroring
`isNamedData`), the library reference name (`libraryRefName`), and the data binding in scope
at a cursor path (`dataBindingAtPath`, honoring a view's data inheritance from its parent).
- `spec-cursor` — `findViewRange` (cursor offset → the enclosing view's byte range),
`pathAtOffset` (cursor → JSON path), and `valueKeyAtOffset`/`stringValueAtOffset` (the JSON
context at the cursor), over `jsonc-parser`.
- `spec-fields` — field names a spec's transforms introduce (their `as`), scoped to a
cursor's ancestor chain (`derivedFieldNamesAtPath`).
- `spec-inline-data` — the rows a specific `data` binding carries for profiling
(`rowsForDataBinding`: inline `values`, or a self-defined `datasets` entry).
- `spec-insert` — composition arrays + appending a view to one.
**Services (app, store-aware via `getState`):** `spec-transform-actions` (the
wrap/simplify/add-view operations and their surfaces), `spec-dataset-hints` (completion,
hover, inlay providers), `active-dataset` (`dataInfo()` — the columns/types/stats + derived
fields the draft sees). `SpecEditor` does the wiring.
hover, inlay providers), `active-dataset` (`dataInfoAt(text, offset)` — the columns/types/stats
plus derived fields the draft sees at the cursor). `SpecEditor` does the wiring.
Decision rules:
@@ -312,12 +318,17 @@ Decision rules:
- **One edit path.** The lightbulb returns a `WorkspaceEdit` (no editor handle); the toolbar
and palette use `executeEdits`. Both build the replacement through the same
serialize-and-reindent step, bracketed by `pushUndoStop`, so ⌘Z restores the prior text.
- **Field source for hints.** `dataInfo()` reads columns from the named library dataset the
draft references, else profiles the spec's **inline data on the fly** (`spec-inline-data` +
`core/profile`) — a "ghost dataset" with nothing stored — and adds the spec's derived
fields. Memoized by draft text, since providers fire per keystroke and per scroll. Out of
scope: data-dependent derived columns (`pivot`/`lookup` output) and `url`/CSV-string inline
data, which need the pipeline run or format-aware parsing.
- **Field source for hints (view-scoped).** `dataInfoAt(text, offset)` resolves the data
binding of the cursor's **nearest enclosing view** (`dataBindingAtPath` — a child inherits a
parent's data unless it declares its own), then its columns: a named **library dataset**
(matched case-insensitively, like the renderer), else the binding's **inline rows** profiled
on the fly (`rowsForDataBinding` + `core/profile`) — a "ghost dataset" with nothing stored.
Derived fields come from that view's and its ancestors' transforms only
(`derivedFieldNamesAtPath`), so a sibling view's `calculate` does not leak in. Profiling is
memoized per (draft text, enclosing view), so the inlay provider's many per-line queries
profile once. A composed spec whose views bind different datasets therefore gets the right
columns per view. Out of scope: data-dependent derived columns (`pivot`/`lookup` output) and
`url`/CSV-string inline data, which need the pipeline run or format-aware parsing.
- **No unknown-field diagnostic.** Hints are additive and forgiving, so over- or
under-listing costs nothing; a "field not in data" squiggle would false-positive on every
derived or data-dependent field, so there is deliberately none.
+13 -10
View File
@@ -66,18 +66,21 @@ collection (`spec-fields`), config baking (`spec-config`), standalone export
- **M1 — data-model foundation** ✅ — `core/spec-data` classifier mirroring
`isNamedData`; `spec-refs` + `rendering` routed through it. Closes clash 1.
- **M2 — view-scoped editor context** — pure `dataContextAtPath(spec, path)`:
climb the cursor's JSON path to the nearest enclosing `data` (honoring
Vega-Lite's parent→child data inheritance), classify it, and collect
ancestor-chain derived fields. Rework `active-dataset` to be cursor-scoped and
thread the offset through the three Monaco providers and the facet/repeat
defaults. Resolve columns for every form: library ref, inline `values`,
named-inline, self-defined `datasets`, url (no static rows), generator (none).
- **M2 — view-scoped editor context** `dataBindingAtPath` (climb the cursor's
JSON path to the nearest enclosing `data`, honoring Vega-Lite's parent→child
inheritance) + `derivedFieldNamesAtPath` (ancestor-chain `as` outputs).
`active-dataset` is cursor-scoped (`dataInfoAt(text, offset)`), resolving columns
for every form (library ref case-insensitive, inline, named-inline, self-defined
`datasets`, url/generator → none); the three Monaco providers and the
facet/repeat defaults pass the cursor offset.
- **M4 — multi-view inspection** (pulled ahead of M3) — group the dataflow's
datasets into per-view input/resolved pairs (`result-data`) and add a view
selector to `DataInspector` (new interactive widget → `/council`). The data
inspector executes the live pipeline, so it is the place to review post-transform
rows (melt/fold/pivot/aggregate); today it surfaces one heuristic input/resolved
pair across the whole composition, with no per-view choice.
- **M3 — view-scoped extract** — seed Extract from the focused view's inline data
(reusing the cursor-scope machinery) and rewrite that view's `data`.
- **M4 — multi-view inspection** — group the dataflow's datasets into per-view
input/resolved pairs (`result-data`) and add a view selector to `DataInspector`
(new interactive widget → `/council`).
Delivery is incremental, one milestone per commit, verified against real behavior.
The consolidated data-model contract write-up into `docs/architecture` (05/08)