Editor: interactive composition wireframe — drag to reorder and restructure

Drag a view's box (or Alt+up/down) to reorder it within its container; drag onto another view's edge to pair the two in a new row/column, move across containers, or insert. Each leaf shows a glyph of its mark type.

Core: spec-restructure.wrapViews (wrap, with flatten-to-insert, collapse, and data-pin invariants) and spec-insert.moveViewTo; the editor applies every drag as one undoable edit via AppStore.composeRequest, keeping the editor the single text source.
This commit is contained in:
2026-06-29 14:25:31 +03:00
parent 57604a80a6
commit 216797ff9b
16 changed files with 1160 additions and 56 deletions
+20
View File
@@ -37,8 +37,10 @@ import {
configureSpecTransformCodeActions,
installSpecTransformActions,
installSpecTransformCodeLens,
runMoveViewTo,
runUnwrap,
runWrap,
runWrapViews,
} from '../services/spec-transform-actions';
import { configureSpecDatasetHints } from '../services/spec-dataset-hints';
import { runExtract } from '../services/extract-action';
@@ -385,6 +387,7 @@ export function SpecEditor() {
const bufferEpoch = useSnippetStore((s) => s.bufferEpoch);
const uiTheme = useAppStore((s) => s.uiTheme);
const revealTarget = useAppStore((s) => s.revealTarget);
const composeRequest = useAppStore((s) => s.composeRequest);
const error = usePreviewStore((s) => s.error);
// Editor preferences (spec §07 → Editor); applied live below as they change.
const editorPrefs = useUserSettingsStore((s) => s.saved.editor);
@@ -513,6 +516,23 @@ export function SpecEditor() {
// can keep browsing blocks while the editor scrolls/selects to follow.
}, [revealTarget]);
// Apply a composition restructure when the wireframe asks (wireframe → editor;
// the editor owns the undoable edit). The nonce makes a repeat request re-fire.
useEffect(() => {
const editor = editorRef.current;
if (!editor || !composeRequest) return;
if (composeRequest.kind === 'move')
runMoveViewTo(editor, composeRequest.arrayPath, composeRequest.from, composeRequest.to);
else
runWrapViews(
editor,
composeRequest.targetPath,
composeRequest.sourcePath,
composeRequest.axis,
composeRequest.side,
);
}, [composeRequest]);
return (
<div className={styles.editorPane}>
<EditorToolbar editorRef={editorRef} />