Editor: spec transforms (wrap/simplify/add-view) and dataset-aware hints

This commit is contained in:
2026-06-28 16:56:20 +03:00
parent 20e70bee0e
commit 31458114fb
20 changed files with 2498 additions and 7 deletions
@@ -275,6 +275,60 @@ defaults-spread" discipline is worth keeping.
---
## 5 · Editor augmentation (our layer over the borrowed base)
Beyond schema validation/completion (§1), the spec editor adds structural refactors and
data-aware hints — the edits that are awkward in raw JSON and out of reach of the
single-view visual builder. All transform logic is pure `src/core/`; the Monaco glue is
thin app-layer services.
**Core (pure, portable):**
- `spec-transforms` — wrap a view in `layer`/`hconcat`/`vconcat`/`facet`/`repeat`; collapse
a single-child composition (`unwrapSingleton`). Object-in/object-out.
- `spec-cursor` — `findViewRange` (cursor offset → the enclosing view's byte range) and
`valueKeyAtOffset`/`stringValueAtOffset` (the JSON context at the cursor), over `jsonc-parser`.
- `spec-fields` — field names a spec's own transforms introduce (their `as`).
- `spec-inline-data` — the inline rows a spec carries (`data.values`, a `datasets` entry).
- `spec-insert` — composition arrays + appending a view to one.
**Services (app, store-aware via `getState`):** `spec-transform-actions` (the
wrap/simplify/add-view operations and their surfaces), `spec-dataset-hints` (completion,
hover, inlay providers), `active-dataset` (`dataInfo()` — the columns/types/stats + derived
fields the draft sees). `SpecEditor` does the wiring.
Decision rules:
- **Provider lifetime — global-once vs per-editor.** Language providers that need no editor
handle (code actions, completion, hover, inlay) register **once** for `json`, like the
schema and formatter; per-editor registration would duplicate them on remount. Pieces that
need the editor handle — the `addAction` context/F1 commands, and the CodeLens whose command
runs `executeEdits` — are installed **per editor** and disposed with it.
- **Transform scope.** A transform targets, in order: an explicit selection → the view the
cursor sits in (`findViewRange`) → the whole document. A "view" is a composition-array
element or a facet/repeat `spec` child; a flat unit spec has no inner view, so it scopes to
the whole document. `jsonc-parser` is error-tolerant, so scoping holds mid-edit; the path
logic stays in core and only the Monaco `Range` is built in the service.
- **One edit path.** The lightbulb returns a `WorkspaceEdit` (no editor handle); the toolbar
and palette use `executeEdits`. Both build the replacement through the same
serialize-and-reindent step, bracketed by `pushUndoStop`, so ⌘Z restores the prior text.
- **Field source for hints.** `dataInfo()` reads columns from the named library dataset the
draft references, else profiles the spec's **inline data on the fly** (`spec-inline-data` +
`core/profile`) — a "ghost dataset" with nothing stored — and adds the spec's derived
fields. Memoized by draft text, since providers fire per keystroke and per scroll. Out of
scope: data-dependent derived columns (`pivot`/`lookup` output) and `url`/CSV-string inline
data, which need the pipeline run or format-aware parsing.
- **No unknown-field diagnostic.** Hints are additive and forgiving, so over- or
under-listing costs nothing; a "field not in data" squiggle would false-positive on every
derived or data-dependent field, so there is deliberately none.
- **Code-action menu icons are kind-derived** (a wrench for the `refactor.*` kinds) — Monaco's
`CodeAction` carries no icon field. Custom iconography lives only where it is supported:
CodeLens titles (`$(codicon)`), completion-item kinds, and glyph-margin decorations.
`jsonc-parser` is a direct dependency (Monaco bundles its own copy internally but does not
re-export it). A standalone `editor-augmentation-demo.html` loads Monaco from a CDN to
exercise these provider surfaces in isolation.
## Borrow list (where each lands)
| Technique | Lands in | Milestone |