Editor: composition wireframe — pull-out, stacking, and simplify

This commit is contained in:
2026-06-30 15:42:22 +03:00
parent 26b383ff33
commit c019660692
13 changed files with 966 additions and 88 deletions
+21 -8
View File
@@ -314,7 +314,13 @@ thin app-layer services.
as a plain _insert_, not redundant nesting), **collapse** the source's emptied container (unwrap a
one-child, drop a zero-child, recursing up the chain), and **data-pin** a source's inherited
`data` before it changes ancestor (`dataBindingAtPath`, so it never silently rebinds). Degenerate
drops — onto itself, its own ancestor/descendant, or the root — return null.
drops — onto itself, its own ancestor/descendant, or the root — return null. `wrapContainer` is the
complement for a frame-margin drop: it stacks the source against the _whole_ container rather than
beside one view — the root included, lifting spec-level metadata onto the wrapper via
`spec-transforms.concatRootBeside` — pulling a view out into a new full-span row/column. The same
flatten/collapse cleanup runs after. `simplifyStructure` collapses redundant single-child
compositions recursively (a `{hconcat:[v]}` is just `v`; `facet`/`repeat` hold one child by design
and are left alone) — the wireframe's Simplify, returning null when nothing is redundant.
**Services (app, store-aware via `getState`):** `spec-transform-actions` (the
wrap/simplify/add-view operations and their surfaces), `spec-dataset-hints` (completion,
@@ -336,13 +342,20 @@ Decision rules:
- **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.
- **Wireframe restructuring is one core op + the editor's undo.** Every drag resolves to either
`moveViewTo` (reorder within one container) or `wrapViews` (everything else — wrap, cross-container
move, insert); the wireframe only _requests_ it (`AppStore.requestComposeMove`/`requestComposeWrap`)
and `SpecEditor` applies it through the same `executeEdits` + `pushUndoStop` path as the other
transforms, so a drag is one ⌘Z and the editor stays the single text source. `wrapViews` covers
wrap and insert with one operation because it flattens bare same-orientation nesting afterward; the
drag's edge-zone interaction model lives in [arch 10](10-interaction-and-feedback.md).
- **Transform actions share an applier, never re-inline the skeleton.** Every `run*` action is
parse → `build(spec)` → `writeBack` (toast on null). That prologue lives in a shared applier per
family — `resolveTarget` (scoped), `applyArrayEdit` (one array), `applyWholeSpecEdit` (whole-spec
drag/simplify) — so a new action passes its core call and differs only in scope and feedback. A
fresh `run*` reuses the matching applier rather than copying the model/parse/writeBack lines.
- **Wireframe restructuring is a core op + the editor's undo.** Every drag resolves to `moveViewTo`
(reorder within one container), `wrapViews` (pair, cross-container move, insert), or `wrapContainer`
(pull a view out around a container); the Simplify prompt resolves to `simplifyStructure`. The
wireframe only _requests_ each (`AppStore.requestComposeMove`/`requestComposeWrap`/
`requestComposeWrapContainer`/`requestComposeSimplify`) and `SpecEditor` applies it through the same
`executeEdits` + `pushUndoStop` path as the other transforms, so a drag is one ⌘Z and the editor
stays the single text source. `wrapViews` covers wrap and insert with one operation because it
flattens bare same-orientation nesting afterward; the drag's zone interaction model lives in
[arch 10](10-interaction-and-feedback.md).
- **Composition CodeLens is cursor-scoped.** It follows the view the cursor sits in —
` Add view above/below` at the view's edges and `↑/↓ Move` to reorder among its siblings —
rather than one fixed button per composition array; an empty composition shows a single ` Add view`,