Files
astrolabe/docs/exploration/multi-view-data-model-scope.md
T

5.1 KiB

Multi-view data model — scope & plan

Astrolabe authors arbitrary Vega-Lite, including composed specs (layer, hconcat/vconcat/concat, facet, repeat). Most of the spec-structure machinery already handles composition; the data-facing features carried single-view assumptions. This memo records the assessment, the data-model contract that anchors the work, and the milestone plan to make multi-view support durable. The guiding constraint: extend Vega-Lite, never break it — every native data form must keep working.

The data-model contract

A dataset reference is exactly Vega-Lite named data: a data block with a string name and no values/url/generator key, whose name the spec does not self-define via top-level datasets. This mirrors Vega-Lite's isNamedData (reference/vega-lite/src/data.ts). The classification is owned by core/spec-data (classifyData, libraryRefName); reference extraction (spec-refs), rename (spec-refs), and render-time resolution (rendering) all route through it. See docs/architecture/07 §3.1.

Library references resolve to inline data before embedding (core/renderingprepareSpecForRender); a self-defined datasets name is left for Vega-Lite to resolve natively.

Assessment: already multi-view vs. single-view assumptions

Already composition-aware (recurse all view operators): ref extraction/rename (spec-refs), reference resolution + fit-mode (rendering), structural wrap/ unwrap/add-view (spec-transforms, spec-cursor, spec-insert), derived-field collection (spec-fields), config baking (spec-config), standalone export (chart-export, reuses prepareSpecForRender).

Single-view assumptions (the work):

  • Editor data context (app/services/active-dataset) resolves one dataset for the whole draft (first ref, or first inline data), with no notion of which view the cursor sits in. Completion/hover/inlay (spec-dataset-hints) and the facet/repeat field defaults (spec-transform-actions) therefore offer the wrong view's columns in a composition whose views bind different datasets.
  • Extract inline data (app/stores/ExtractStore) lifts only the top-level data block.
  • Data inspector (core/result-data, DataInspector) surfaces one input + one resolved table; a composition produces several source_<n>/data_<n>.
  • deriveSnippetName (core/snippet) reads top-level mark/encoding only, so a composed spec falls back to the default name (graceful, not a bug).
  • Chart builder is single-view by design; its strict round-trip hydration returns null for composed specs, so they stay Monaco-only (correct).

Vega-Lite fidelity clashes

  1. Named inline/url data misread as a reference — classifying on "has a string name" alone caught named-inline ({ name, values }) and named-url, breaking valid specs (spurious DatasetNotFoundError, or clobbered inline values). Resolved by the core/spec-data classifier (M1).
  2. Shadowing — a library dataset whose name equals a self-defined datasets key is silently ignored (self-defined wins). Documented precedence; candidate for a user-facing note, no code change required.
  3. Case-rule split — library matching is case-insensitive (naming.ts); self-defined exclusion and Vega-Lite's own named-data lookup are case-sensitive. These are distinct namespaces, so the split is defensible; minor.
  4. Runtime-injected named data — Vega-Lite allows binding { name } at runtime; Astrolabe always pre-resolves, so an imported spec relying on runtime injection won't render. Out of scope.

Milestone plan

  • M1 — data-model foundation core/spec-data classifier mirroring isNamedData; spec-refs + rendering routed through it. Closes clash 1.
  • 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.

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.