mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
Chart Builder: open a snippet in the builder to edit it in place
This commit is contained in:
@@ -515,11 +515,17 @@ is in the maintained plan, not the archive.
|
||||
|
||||
**Chart Builder** (`exploration/chart-builder-enhancement-scope.md`):
|
||||
|
||||
- **Open-in-builder + builder starter examples (3B/3C)** — the next builder slice in the
|
||||
scope doc: hydrate the **builder** from an existing spec (strict spec→config parse), behind
|
||||
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.
|
||||
- ~~**Open-in-builder (3C, strict hydration)**~~ ✅ shipped (2026-06-18): an editor-toolbar
|
||||
**Open in builder** action reopens a builder-representable snippet to **edit it in place**
|
||||
(`parseChartSpec` is the strict inverse of `buildChartSpec`, gated by re-assemble +
|
||||
deep-compare). Only dataset-referencing specs hydrate (the builder's data model). See the
|
||||
scope doc status log.
|
||||
- **Builder starter examples (3B)** — a small set of curated starters, one per covered FT
|
||||
intent, **authored in the builder dialect so they reopen via 3C**. Reshaped by 3C's
|
||||
data-model edge: a builder-openable starter must reference a dataset, so 3B ships paired
|
||||
sample datasets (or is reframed) — decide its shape now that the gate is live. Distinct from
|
||||
the shipped onboarding gallery (`core/examples.ts` → `Onboarding.tsx`), which seeds an
|
||||
inline-data snippet straight into the editor (Monaco-only, never the builder).
|
||||
- ~~**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").
|
||||
|
||||
@@ -284,6 +284,13 @@ export function toggleDatasets(): void {
|
||||
}
|
||||
```
|
||||
|
||||
> **Pre-hydrate variant — open without `init`.** When a modal must open onto state the
|
||||
> caller already loaded (the Chart Builder's _Open in builder_ edit flow — `openChartBuilderForEdit`
|
||||
> hydrates the builder from a snippet first), the opener sets the active modal, snapshots, and
|
||||
> syncs the URL **itself** and **skips the registry `init`** — running `init` would re-derive the
|
||||
> default state and clobber the hydration. Such an opener is the exception, not the rule: ordinary
|
||||
> opens go through `openModal` so `init` is the single place transient state is seeded.
|
||||
|
||||
### Change detection
|
||||
|
||||
```ts
|
||||
|
||||
@@ -495,6 +495,22 @@ call the same functions as the visible control — one code path, two doors.
|
||||
_(Consulted via /council → NN/g #6/#7, Carbon menu-buttons/overflow-menu. This bullet is
|
||||
the contract; cite it, not the source.)_
|
||||
|
||||
**Resolved — content-gated toolbar actions hide; state-gated actions disable.** Two ways a
|
||||
toolbar action can be inapplicable, with opposite affordances. **State-gated** — applicable
|
||||
to this object in principle, just inert right now (Revert with no draft changes, Config in
|
||||
the read-only Published view) → **disabled**: the user can act (edit / switch view) and it
|
||||
lights up. **Content-gated** — inapplicable to _this spec's shape_, and nothing the user can
|
||||
do in the moment changes that (_Extract to Dataset_ needs inline data; _Open in builder_
|
||||
needs a builder-representable spec referencing an existing dataset) → **hidden**. A
|
||||
permanently-disabled control the user cannot enable reads as broken or teasing, not as
|
||||
guidance (NN/g #6 — a disabled state must imply "do X and this becomes available"; Carbon
|
||||
button states). So _Open in builder_ (spec §06) sits in the editor toolbar beside _Extract
|
||||
to Dataset_ and follows its visibility — present only when the active snippet round-trips
|
||||
through the builder and its dataset exists. This refines "disabled is for temporarily
|
||||
unavailable actions" (below) from the unbuilt-feature case to the per-spec case.
|
||||
_(Consulted via /council → NN/g #4/#6, Carbon button usage/states. This bullet is the
|
||||
contract; cite it, not the source.)_
|
||||
|
||||
**Resolved — field→channel assignment: explicit choice, visible armed state.** Clicking a
|
||||
shelf field with no channel armed opens an explicit **channel chooser** (the channels that
|
||||
accept the field; an occupied one is labelled with what it replaces) — never a silent
|
||||
|
||||
@@ -25,6 +25,32 @@
|
||||
Newest first. The at-a-glance build-order tracker is §4; per-item detail is §3. This log is
|
||||
the quick "where are we" — read it first.
|
||||
|
||||
- **2026-06-18 (3C open-in-builder, edit-in-place)** — **the builder became create-and-revise.**
|
||||
Core: `parseChartSpec` / `parseChartSpecText` (`chart-builder.ts`) — the strict inverse of
|
||||
`buildChartSpec`, gated by **re-assemble + canonical deep-compare** (ignoring key order,
|
||||
`$schema`, the injected `mark.tooltip`), so the gate widens for free as the dialect grows and
|
||||
never lets the builder overwrite a richer spec. `unescapeVegaField` added beside its escape
|
||||
twin (`rendering.ts`). Save semantics chosen with the user = **edit in place** (not
|
||||
always-create): `SnippetStore.replaceSnippetSpec` republishes the built spec into both the
|
||||
snippet's versions (auto names re-derive like publish; user names kept), and
|
||||
`ChartBuilderStore` gained `editingSnippetId`/`editingSnippetName` + `openForEdit`/`saveEdits`.
|
||||
Entry point: an **editor-toolbar** _Open in builder_ action (`SpecEditor`), beside _Extract to
|
||||
Dataset_, shown only when the published spec round-trips _and_ its dataset exists — **hidden
|
||||
otherwise** (content-gated, like Extract). The modal's primary becomes **Save changes** with an
|
||||
"Editing _name_" banner; a dedicated `openChartBuilderForEdit` coordinator opener hydrates
|
||||
before showing the modal so the registry's default `init` can't clobber it. **Data-model edge
|
||||
(called out, not a bug):** the builder references a dataset by name, so only dataset-referencing
|
||||
specs hydrate — inline-data snippets (incl. the onboarding gallery) stay Monaco-only. This
|
||||
reshapes **3B**: builder-openable starters need a paired dataset, so 3B's shape is decided
|
||||
_after_ this lands (see §3). Spec §06 gained an _Open in builder_ section. **Placement settled
|
||||
by council:** an initial library metadata-panel placement (beside Duplicate/Delete) tested
|
||||
undiscoverable; `/council` (NN/g #6 recognition / #4 consistency; Carbon button usage) put it in
|
||||
the editor toolbar and settled the hidden-vs-disabled question via the toolbar's own precedent —
|
||||
**content-gated** actions hide (Extract, Open-in-builder), **state-gated** ones disable (Revert,
|
||||
Config). Recorded as a contract rule in `architecture/10` §5; the parked `ux-second-pass.md` row
|
||||
is closed. Verified: typecheck + full tests (1089) + eslint + prettier. **Owed:** a visual/manual
|
||||
pass on the live edit flow (the toolbar action, the "Editing" banner, Save changes).
|
||||
|
||||
- **2026-06-13 (guidance: reason over role, not raw type)** — closed a **false-positive class**
|
||||
in `builderWarnings` (eng-council + council consult). A histogram (bar, binned-Q X, count Y)
|
||||
tripped "two measures → scatter" because `isMeasureMapping`/`effectiveType` ignored `bin`.
|
||||
@@ -599,8 +625,8 @@ Phase 2 2A value-or-field channels (Property model) ✓ done
|
||||
2B field shelf + in-place type cycling ✓ done (field-first + on-chart shelves)
|
||||
Phase 3 3D entry points & discoverability ✓ done (2026-06-12)
|
||||
3A intent-first front door (Tier C) ✓ done (2026-06-13); persistent strip
|
||||
3B starter examples ← next; pairs with 3C
|
||||
3C open in builder (strict hydration) ← added 2026-06-11; pairs with 3B
|
||||
3C open in builder (strict hydration) ✓ done (2026-06-18); edit-in-place
|
||||
3B starter examples ← next; now hydrates via 3C
|
||||
Marks +rect (Heatmap) ✓ done (2026-06-13)
|
||||
Phase 4 (gated) theta/facets/styling-overrides/undo/lookup — decide after Phase 3
|
||||
|
||||
|
||||
@@ -164,6 +164,16 @@ Selecting "Create Snippet" produces the final artifact:
|
||||
- Links the snippet to the dataset by recording the dataset reference, so the bidirectional snippet↔dataset relationship is established (see _Datasets_).
|
||||
- Closes the builder; the newly created snippet becomes the active snippet in the library/editor. **No success toast** — the result is immediately visible (the new snippet opens in the editor), so a toast would be noise (architecture 10 §1, "toast only what the user can't already see"). This refines the earlier blanket "every action toasts" rule, consistent with the Extract-to-dataset / publish reconciliation.
|
||||
|
||||
## Open in builder (edit in place)
|
||||
|
||||
The builder is also the way to **revise** a chart it could have produced — not only create one. The **editor toolbar** (top of the Spec Editor, alongside the Draft/Published toggle, Config, Revert, and Publish) offers an **Open in builder** action that reopens the active snippet in the builder, populated from its spec — the visual counterpart to editing the same chart's JSON, placed where that editing happens.
|
||||
|
||||
- **When it is offered.** Only when the active snippet's **published spec** is **losslessly representable** in the builder's dialect _and_ the dataset it references still exists. Losslessness is judged by parsing the spec back to a builder configuration and **re-assembling it for an exact comparison** against the original (ignoring key order, the `$schema` stamp, and the builder's injected `tooltip`), not by enumerating supported features — so the gate stays correct automatically as the builder's dialect grows. A spec the builder cannot reproduce exactly (hand-authored richness, an unsupported channel/mark, inline `data.values`, a `url` source) stays **Monaco-only**, and the action is **hidden** for it — the same content-gated treatment as _Extract to Dataset_ (a permanently-disabled control the user can't enable in the moment would read as broken; see _Interaction & Feedback_ → action visibility). The builder references a dataset **by name** — its sole data model — so only a snippet that references a saved dataset can hydrate.
|
||||
- **What opening does.** Hydrates the builder from the snippet — mark, encodings, transforms, sort/stack, title/subtitle, size — and loads the referenced dataset's columns. The configuration pane names the snippet under edit ("Editing _name_"), and the dataset picker behaves as a **rebase** (built-on work) for any subsequent dataset switch — a loaded chart is never treated as a fresh default.
|
||||
- **An edit session is builder-local, not part of the URL.** Like the builder's in-progress configuration generally, the "editing _name_" context is transient: a reload, Back, or shared link reopens the builder as a fresh **create** flow on the same dataset rather than restoring the edit. No data is lost — the published snippet is untouched until _Save changes_ — and re-entering the edit is one click from the toolbar.
|
||||
- **Saving.** The primary action becomes **Save changes** (in place of "Create Snippet"): it **republishes** the built spec into the same snippet — overwriting **both** its published and draft versions, so there is no pending draft to reconcile — while keeping the snippet's identity, timestamps' `created`, and dataset links. A user-chosen name is preserved; an auto-named snippet re-derives its name from the new content (as _Publish_ does). The edited snippet becomes the active snippet. As with Create, there is no success toast (the result is immediately visible) and the action is gated on the same validation (at least one channel bound).
|
||||
- **The JSON stays the document.** Because Open-in-builder is strict, the builder never silently overwrites a richer spec it cannot represent — it is a view that emits the spec, never a competing source of truth.
|
||||
|
||||
## Closing
|
||||
|
||||
- The builder can be dismissed without creating anything (close control / modal dismissal).
|
||||
|
||||
Reference in New Issue
Block a user