4.8 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/rendering → prepareSpecForRender); 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-leveldatablock. - Data inspector (
core/result-data,DataInspector) surfaces one input + one resolved table; a composition produces severalsource_<n>/data_<n>. deriveSnippetName(core/snippet) reads top-levelmark/encodingonly, 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
nullfor composed specs, so they stay Monaco-only (correct).
Vega-Lite fidelity clashes
- 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 (spuriousDatasetNotFoundError, or clobbered inline values). Resolved by thecore/spec-dataclassifier (M1). - Shadowing — a library dataset whose name equals a self-defined
datasetskey is silently ignored (self-defined wins). Documented precedence; candidate for a user-facing note, no code change required. - 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. - 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-dataclassifier mirroringisNamedData;spec-refs+renderingrouted through it. Closes clash 1. - M2 — view-scoped editor context — pure
dataContextAtPath(spec, path): climb the cursor's JSON path to the nearest enclosingdata(honoring Vega-Lite's parent→child data inheritance), classify it, and collect ancestor-chain derived fields. Reworkactive-datasetto be cursor-scoped and thread the offset through the three Monaco providers and the facet/repeat defaults. Resolve columns for every form: library ref, inlinevalues, named-inline, self-defineddatasets, url (no static rows), generator (none). - 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 toDataInspector(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)
lands once the shape is final.