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
+60
View File
@@ -33,6 +33,14 @@ import {
runExtractConfigToTheme,
runMergeChartTheme,
} from '../services/spec-config-actions';
import {
configureSpecTransformCodeActions,
installSpecTransformActions,
installSpecTransformCodeLens,
runUnwrap,
runWrap,
} from '../services/spec-transform-actions';
import { configureSpecDatasetHints } from '../services/spec-dataset-hints';
import { useAppStore } from '../stores/AppStore';
import { confirm } from '../stores/ConfirmStore';
import { useDatasetStore } from '../stores/DatasetStore';
@@ -149,6 +157,11 @@ function EditorSettings() {
configureVegaLiteJson();
// Register the compact JSON formatter once (Format Document + format-on-paste, §03A).
configureJsonFormatter();
// Register the structural-transform refactors (the lightbulb) once, globally for
// JSON — like the schema/formatter, not per editor (docs/architecture/08).
configureSpecTransformCodeActions();
// Register the dataset-aware completion/hover/inlay providers once (docs/architecture/08).
configureSpecDatasetHints();
/** The two spec↔config operations, surfaced as an overflow menu (council:
* Carbon menu-buttons — overflow for additional options under space
@@ -174,6 +187,25 @@ const CONFIG_ACTIONS = [
type ConfigActionId = (typeof CONFIG_ACTIONS)[number]['value'];
/** Structural transforms, surfaced as a sibling menu to Config (the discoverable
* home; the lightbulb and F1 palette are the accelerators — see
* services/spec-transform-actions). They act on the selection, else the whole
* spec. */
const TRANSFORM_ACTIONS = [
{ value: 'layer', label: 'Wrap in layer', detail: 'Overlay marks on shared scales' },
{ value: 'hconcat', label: 'Wrap in horizontal concat', detail: 'Place views side by side' },
{ value: 'vconcat', label: 'Wrap in vertical concat', detail: 'Stack views top to bottom' },
{ value: 'facet', label: 'Wrap in facet', detail: 'Small multiples across a field' },
{ value: 'repeat', label: 'Wrap in repeat', detail: 'Repeat the chart across fields' },
{
value: 'simplify',
label: 'Simplify composition',
detail: 'Collapse a single-child layer/concat back to a unit',
},
] as const;
type TransformActionId = (typeof TRANSFORM_ACTIONS)[number]['value'];
function EditorToolbar({
editorRef,
}: {
@@ -228,6 +260,13 @@ function EditorToolbar({
else runExtractConfigToTheme(editor);
};
const handleTransformAction = (action: TransformActionId) => {
const editor = editorRef.current;
if (!editor) return;
if (action === 'simplify') runUnwrap(editor);
else runWrap(editor, action);
};
const handleRevert = async () => {
const ok = await confirm({
title: 'Revert draft',
@@ -284,6 +323,16 @@ function EditorToolbar({
<span className={styles.actionLabel}>Extract to Dataset</span>
</Button>
)}
<SelectControl
id="editor-transform-actions"
label="Spec transform actions"
heading="Transform"
options={TRANSFORM_ACTIONS}
onSelect={handleTransformAction}
triggerContent="Transform"
triggerTitle="Structural transforms — wrap the focused view in a composition, or simplify one"
disabled={activeId === null || editorView === 'published'}
/>
<SelectControl
id="editor-config-actions"
label="Spec config actions"
@@ -376,6 +425,15 @@ export function SpecEditor() {
// above, so the draft buffer stays in sync like any other edit.
const configActionsSub = installSpecConfigActions(editor);
// Structural-transform actions in the F1 palette (the lightbulb is registered
// once, globally, above; the toolbar Transform menu is the home). Per editor,
// disposed below like the config actions.
const transformActionsSub = installSpecTransformActions(editor);
// " Add view" CodeLens over composition arrays — per editor, because its
// command needs this editor's handle to apply the edit.
const codeLensSub = installSpecTransformCodeLens(editor);
// Cmd/Ctrl+S is owned globally by the EventRouter (docs/architecture/04 →
// "bind listeners in exactly one place"), which publishes before the
// interactive-context gate so it works while the editor has focus. Monaco
@@ -385,6 +443,8 @@ export function SpecEditor() {
sub.dispose();
pasteSub.dispose();
configActionsSub.dispose();
transformActionsSub.dispose();
codeLensSub.dispose();
editor.dispose();
editorRef.current = null;
};