Data inspector: input/resolved rows below the chart, with a resizable divider

A collapsible Data panel under the Live Preview and Chart Builder charts shows the rows the chart actually uses, switching between Input (parsed source) and Resolved (post-transform) views read from the live Vega view. Collapsed by default; the open-state and a draggable height divider persist.

Rows come through a new RenderHandle.inspectData() accessor, so no component touches the view: core/result-data picks the most-upstream source and most-downstream result from the compiled dataflow, read lazily. The divider reuses the window-splitter pattern (horizontal variant).

Consolidations: a shared DataTable primitive replaces the inspector's and the builder's duplicate read-only tables; useResizeDrag merges the col/row drag-gesture twins.

Docs: spec 04/06 and arch 05/10 updated; the now-shipped exploration memo removed.
This commit is contained in:
2026-06-18 02:22:03 +03:00
parent 223646398e
commit efb5a9bbe0
35 changed files with 1210 additions and 184 deletions
+3 -2
View File
@@ -520,8 +520,9 @@ is in the maintained plan, not the archive.
a starter-example front door. Distinct from the shipped onboarding gallery
(`core/examples.ts``Onboarding.tsx`), which seeds a snippet straight into the editor, not
the builder — there is no spec→builder-config path yet.
- **Transform-aware data inspector** — show resolved post-transform rows
(`exploration/data-inspector-exploration.md`).
- ~~**Transform-aware data inspector** — show resolved post-transform rows~~ ✅ shipped: an
Input | Resolved data inspector below the Live Preview and Chart Builder charts, with a
draggable height divider (spec §04; arch 05 → "data inspector rides the boundary").
- **Field-chip drag-and-drop** — click/keyboard-first shipped; drag deferred.
- **Calculated-field autocomplete popup** — Monaco-style completion for expressions.
@@ -40,12 +40,28 @@ above it is data; everything below it is a Vega `View` we own and must tear down
`subscribe` listener), not as reactive state itself.
- **Don't** scatter `vegaEmbed(...)` calls across components.
> **Forward note — data inspector.** A planned vega-editor-style inspector (show the
> _resolved, post-transform_ data tables, in the builder and as a togglable panel below
> the main Live Preview) reads runtime rows via `view.data(name)`. It rides this same
> boundary: a `RenderHandle.data()` accessor wraps the view (like `toImageURL`), so no
> component touches the raw view. Parked, not built — see
> [`docs/exploration/data-inspector-exploration.md`](../exploration/data-inspector-exploration.md).
### The data inspector rides the boundary too
The data inspector (the Live Preview and Chart Builder panel showing the chart's input
vs. resolved rows — spec §04) reads runtime rows through the handle, never the raw view:
`RenderHandle.inspectData()` returns the input + resolved tables (`{ input, resolved }`,
or `null` when no chart is up), wrapping the view exactly like `toImageURL`. It works in
two layers:
- **Enumerate + pick (`view.getState` + `core/result-data`).** A compiled Vega dataflow
holds many named datasets; Vega-Lite names them by convention — `source_<n>` per parsed
source, `data_<n>` per transform stage. The pure `pickSourceDataset` / `pickResultDataset`
choose the **most-upstream source** (the input) and **most-downstream output** (what the
marks draw), skipping dataflow internals (`marks`, `root`, layout, selection `*_store`s).
A spec with no transforms resolves both to the same table. The picking is pure (in `core`,
unit-tested); only the enumeration touches the view.
- **Read lazily.** `getState` serializes the datasets it lists, so it is **only called while
the panel is open** — a collapsed inspector costs nothing, which is why the panel reads on
demand rather than on every render.
Limitation: one name per direction can't represent a multi-view spec (layer/concat/facet
produce several `data_<n>`); the most-downstream/upstream ones are returned, and a full
dataset selector is left as a future option.
---
@@ -210,6 +210,14 @@ each above its min. **valuenow trade-off:** it reports the library's 0100 pos
rendered position after a window enlargement (worst near the extremes). Drag/keyboard read live
`clientWidth`, so resizing itself stays accurate; only the announced value drifts.
**Horizontal variant — the data-inspector divider.** The chart ↔ data-inspector divider in
the Live Preview (`InspectorSplitHandle`, spec §04) is the same window-splitter contract with
the axis flipped: `aria-orientation="horizontal"`, ↑/↓ + Home/End, sizing a stacked region's
**height** instead of a pane's width. It reads its flanking siblings (chart above, inspector
below) like `PaneSplitHandle`, with the gesture in `useRowResizeDrag` (the row twin of
`useColResizeDrag`) and the clamp/value math pure in `AppStore` (`clampInspectorHeight` /
`inspectorHeightValue`). Rendered only while the inspector is open.
_(Consulted via `/council` → WAI-ARIA APG `windowsplitter`. This bullet is the contract;
cite it, not the APG file.)_
-1
View File
@@ -15,4 +15,3 @@ snapshots, not live numbers.
- `chart-builder-enhancement-scope.md` — consolidated Tier-B → Tier-C forward plan for the Chart Builder.
- `chart-theming-scope.md` — chart theming plan and slice breakdown.
- `monetization-and-sync-exploration.md` — monetization + BYO-cloud-sync direction memo.
- `data-inspector-exploration.md` — transform-aware data-inspector direction.
@@ -223,10 +223,10 @@ the quick "where are we" — read it first.
smoke tests) + `eslint` + `build` (PWA, 45 precache entries). **Owed:** a manual/visual
pass against the live builder (filter shelf, calc → channel, expr errors, preview
table) — tests don't cover what the surface looks/feels like.
- **Surfaced direction (now parked):** the 1D preview shows **raw source** rows; a
transform-aware **data inspector** (resolved rows, à la vega-editor, in the builder
_and_ below the main Live Preview) is the wanted evolution — documented in
**[`docs/data-inspector-exploration.md`](./data-inspector-exploration.md)**, deferred.
- **Surfaced direction (since shipped):** the 1D preview shows **raw source** rows; a
transform-aware **data inspector** (input vs. resolved rows, à la vega-editor, in the
builder _and_ below the main Live Preview) was the wanted evolution — now shipped
(spec §04 → _Data Inspector_).
- **2026-06-10** — **Up next: 1C (filter + calculate transforms), paired with 1D (data preview).**
- **1B · Per-chart export** shipped: an **Export** disclosure in the Live Preview header
@@ -580,12 +580,10 @@ guardrail: _promote a control only when it is **both common AND awkward in JSON*
is short-lived; the smart default already gives a sane start).
- **Lookup / join a second dataset** — Lyra §3.12; data-model change (multi-dataset
snippets). Larger, separate effort.
- **Transform-aware data inspector** — evolve 1D from raw-source rows to the _resolved,
post-transform_ data (filtered rows + calculated columns), and generalize it to a
togglable data panel below the **main** Live Preview (vega-editor's "Data Viewer", a
debugging aid for any snippet, not only builder output). Reads runtime rows via
`view.data(name)` through the `RenderHandle`. Cross-cutting (editor + builder), so it
has its own home: **[`docs/data-inspector-exploration.md`](./data-inspector-exploration.md)**.
- ~~**Transform-aware data inspector**~~ ✅ shipped — an Input | Resolved data panel below
the **main** Live Preview and the builder preview (vega-editor's "Data Viewer", a
debugging aid for any snippet), reading runtime rows through the `RenderHandle`. Spec §04
→ _Data Inspector_; arch 05 → "the data inspector rides the boundary".
---
@@ -1,64 +0,0 @@
# Data Inspector — exploration (future direction)
> **Status:** parked idea (2026-06-11). A deliberate divergence from the Phase-1
> chart-builder scope, recorded here so it isn't lost. **Not built.** The shipped
> seed is the builder's 1D data preview (raw source rows); this is its transform-aware
> evolution, generalized to the main editor.
## What
A vega-editor-style **data inspector**: show the data tables the chart actually uses at
runtime — _after_ the spec's transforms run — not just the raw source rows. Two homes:
1. **Chart Builder** — make the existing data preview (1D) transform-aware: show the
filtered rows and any calculated columns, so the preview reflects the `transform`
block the builder emits. (Today 1D shows the **raw dataset rows**, pre-transform — an
"inspect the source" view. This adds the "inspect the result" view alongside it.)
2. **Main window** — a **togglable data panel below the Live Preview** in the editor,
for any snippet (not only builder-made ones): inspect each named dataset / the
post-transform rows while authoring a spec by hand.
## Why
vega-editor surfaces every internal data table below the chart (its "Data Viewer"), and
it is the primary way to debug "why is my chart empty / wrong" — you look at what the
transforms produced. Astrolabe has no equivalent today: the only data views are of the
**raw source** (the Datasets manager, and builder 1D). Seeing the **resolved** data
closes that debugging gap and is useful well beyond the builder.
## Technique
Vega exposes runtime datasets via `view.data(name)` — the rows of any named dataset in
the compiled dataflow, including the root data _after_ transforms. The renderer already
owns the only `view` reference behind `RenderHandle` (`src/app/services/chart-renderer.ts`);
add a `RenderHandle.listData()` / `data(name)` accessor that wraps `view.data(...)`,
mirroring how `toImageURL` wraps `view.toImageURL` — so no component touches the raw
view and the embedding boundary holds (architecture/05 §1–§2).
Vega-Lite compiles user transforms into named datasets (e.g. `source_0`, `data_0`);
mapping those to friendly labels and choosing which to expose is the main design work.
## Scope notes / open questions
- **Read-only**, like 1D — it inspects, never mutates (the "moderately capable" ceiling:
a debugging aid, not a data editor).
- **Big datasets** — cap the rows shown (reuse the 50-row preview cap). The view holds
every row; the table must not.
- **Which tables to list** — just the post-transform root/result, or every internal
dataset (the vega-editor dropdown)? Start with the result; expose the rest behind a
selector if it earns its place.
- **Main-window placement** — a third stacked region inside the preview pane (toggle +
persisted visibility, like the existing pane toggles) vs. a tab over the chart. Wants a
small interaction pass (architecture/10) before building.
- **Refresh timing** — the data is only available once a view has rendered; the panel
reads from the live `RenderHandle` and updates when a new render resolves (it must not
read a finalized view — same generation discipline as the preview).
## Relationship to shipped work
- Seed: builder **1D** data preview — see
[`chart-builder-enhancement-scope.md`](./chart-builder-enhancement-scope.md) (§3 1D).
- Renderer/preview home: [`architecture/05`](../architecture/05-rendering-theming-preview.md)
(the `RenderHandle` boundary the `view.data` accessor extends).
- Behaviour, once built, is specified in `docs/spec/04` (Live Preview) and `docs/spec/06`
(the builder preview), per "spec follows code".
+12
View File
@@ -71,6 +71,18 @@ Layout: a saved-theme list on the left; the open theme's editor on the right.
The preview pane header also carries a per-chart **Export** control — a disclosure for copying or downloading the current chart's spec, or downloading its rendered image (PNG/SVG). It exports what the preview shows. The behavior is specified in _Import & Export → Per-chart export_; it lives in this header because the image formats are produced from the live rendered view.
## Data Inspector
Below the chart, a collapsible **Data** panel shows the rows behind the chart — the answer to "why is my chart empty or wrong" is to look at the data the spec produced. It reads the live rendered view, so it reflects exactly what was drawn.
- A **disclosure** at the bottom of the pane, collapsed by default (a debugging aid, not the default view); the open state persists across sessions, stored in _Settings_ as `ui.dataInspectorOpen`.
- An **Input | Resolved** switch chooses which rows to show:
- **Resolved** (default) — the rows the chart draws, _after_ the spec's transforms (filters, calculated fields, aggregation).
- **Input** — the parsed source rows, _before_ those transforms. A spec with no transforms shows the same rows for both.
- The table is read-only and capped (the first rows, with a "first N of M" note), like the dataset and builder previews, and updates whenever the chart re-renders.
- States: when nothing has rendered, the panel guides the user to render a chart; when the chosen view's table is empty, it names which side is empty and why (the resolved side's transforms produced nothing to draw; the input side's source has no rows).
- A **draggable divider** between the chart and the panel sizes the panel's height (the chart above absorbs the change) — the same window-splitter interaction as the layout panes (see _Application Shell & Navigation_). The height persists, stored in _Settings_ as `ui.dataInspectorHeight`.
## Rendering Contract
Before the chart is drawn, the spec shown in the editor is transformed into the spec actually rendered. Two deterministic transforms are applied in order. They are specified here because reproducing them faithfully is what makes references and fit modes behave correctly; the result is observable as the rendered chart.
+2 -1
View File
@@ -52,7 +52,7 @@ The section is ordered **input → shaping** so the distinction reads at a glanc
### Data preview
- The first item in the section: a collapsible, **read-only** sample of the dataset's first rows (capped), with a per-column **type chip** in each header. It lets the user sanity-check inferred types against the actual values _before_ building — exactly when type inference is most likely to surprise. Editing the data is out of scope. A non-tabular payload (a single JSON object, TopoJSON, an unfetched URL) has no rows to show.
- The preview shows the dataset's **raw source rows**_before_ the filters and calculated fields below are applied; it does not show derived columns. Its position above those controls makes that explicit: it is the input, not the result. (The transformed result is what the right-hand chart renders. A transform-aware "data inspector" that shows the resolved rows is a planned future direction — see [`docs/exploration/data-inspector-exploration.md`](../exploration/data-inspector-exploration.md).)
- The preview shows the dataset's **raw source rows**_before_ the filters and calculated fields below are applied; it does not show derived columns. Its position above those controls makes that explicit: it is the input, not the result. The transformed result is shown by the **data inspector** under the chart on the preview side (below), which switches between input and resolved rows read from the rendered view.
### Filters
@@ -147,6 +147,7 @@ A slim strip pinned **under the live preview** — these describe the chart itse
- Updates are debounced: changes to mark, encodings, or dimensions trigger a re-render after a short pause rather than on every keystroke.
- While no encoding is mapped, the pane shows a placeholder instructing the user to configure at least one encoding.
- If the spec fails to render, the pane shows an inline error message describing the problem instead of a chart.
- Under the chart, the same **data inspector** as _Live Preview_ (Input | Resolved rows, collapsed by default) lets the user compare the source rows against what the builder's filters and calculated fields produced. It appears only with a live chart, so it never doubles the placeholder/error.
## Validation