# 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-level `data` block. - **Data inspector** (`core/result-data`, `DataInspector`) surfaces one input + one resolved table; a composition produces several `source_`/`data_`. - **`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.