Skip to content

Commit 9f4a4ae

Browse files
mikewolfdclaude
andcommitted
docs: add Editor/Layout workspace split design spec
Defines the architectural split of the Studio Editor tab (Tier 1 definition tree editor) and Layout tab (Tier 2+3 visual form builder), aligned with the Formspec three-tier specification model. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 3a92307 commit 9f4a4ae

1 file changed

Lines changed: 319 additions & 0 deletions

File tree

Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
# Editor / Layout Workspace Split
2+
3+
**Date:** 2026-03-27
4+
**Status:** Draft
5+
**Scope:** `packages/formspec-studio/` only — no external package changes
6+
7+
## Problem
8+
9+
The Studio Editor tab mixes Tier 1 (Definition) and Tier 3 (Component) concerns. It renders items by walking the component tree, shows layout containers (Card, Grid, Page) as LayoutBlocks, includes page navigation tabs for wizard/tabs mode, and supports wrap-in-Card/Grid operations. This creates bugs (Page nodes appearing as layout blocks on the Editor canvas) and confuses two distinct authoring intents: shaping the data structure vs arranging the visual layout.
10+
11+
The Layout tab (currently `PagesTab`) already manages page structure — mode selection, page cards, grid region editing, field assignment — but lacks the visual canvas and drag-and-drop that the Editor provides.
12+
13+
## Design Principles
14+
15+
The split follows the Formspec specification's three-tier architecture (Core S2.3):
16+
17+
- **Editor tab = Tier 1 Definition** — Structure Layer (what data is collected) + Behavior Layer (how data behaves). Answers: "what items exist, what types are they, how do they validate?"
18+
- **Layout tab = Tier 2 Theme + Tier 3 Component** — Presentation Layer (how data is displayed). Answers: "where do items go, what do they look like, how is the form paginated?"
19+
20+
The spec mandates this separation as an "architectural invariant" (Core S2.3, line 627). Definition item array order is data-structural. Component tree children order is visual. Binding is by key, not position (Component S4.1). These are independent by design.
21+
22+
## The New Editor Tab — Definition Tree Editor
23+
24+
### Purpose
25+
26+
Pure Tier 1 data-structure editor. Shows what data the form collects and how it behaves. Derives its view entirely from `definition.items` — never reads the component tree.
27+
28+
### What It Shows
29+
30+
A tree view of `definition.items`:
31+
32+
- **Fields** — compact rows showing key, label, dataType badge
33+
- **Groups** — collapsible tree nodes with indented children
34+
- **Display items** — rows showing key, label, widgetHint badge (advisory)
35+
- **Bind indicators** — small pills/icons for required, calculated, conditional, readonly, constrained
36+
- No pages, no layout containers, no component tree structure
37+
38+
### Interactions
39+
40+
- Click item → select, open definition properties panel
41+
- `+ Add Item` → palette (fields, groups, display — no layout category)
42+
- Drag-to-reorder within tree → reorders `definition.items` array (data structure order)
43+
- Right-click → delete, duplicate, move into/out of group
44+
- Multi-select with Cmd+click, Shift+click (same patterns as current Editor)
45+
46+
### Properties Panel (Right Sidebar)
47+
48+
Definition and behavior properties only:
49+
50+
| Section | Properties |
51+
|---------|-----------|
52+
| Identity | key, label, description, hint, type, dataType |
53+
| Field config | initialValue, prePopulate, semanticType, currency, precision, prefix, suffix |
54+
| Options | options (inline), optionSet, choicesFrom |
55+
| Group config | repeatable, minRepeat, maxRepeat |
56+
| Display content | label (body text), advisory `presentation.widgetHint` |
57+
| Binds | required, calculate, relevant, readonly, constraint, constraintMessage, default, whitespace, excludedValue |
58+
| Advisory hints | `presentation.layout.page` (shown as annotation — "assigned to page X") |
59+
60+
Properties NOT shown (moved to Layout): widget override, style, CSS classes, grid span/start, responsive breakpoints, appearance/label-position overrides, component `when`.
61+
62+
### What Gets Deleted from Editor
63+
64+
- `render-tree-nodes.tsx` — component tree rendering
65+
- `LayoutBlock.tsx` — layout container blocks
66+
- `GroupTabs.tsx` — page navigation tabs
67+
- `FieldBlock.tsx`, `GroupBlock.tsx`, `DisplayBlock.tsx` — visual card blocks (replaced with compact tree rows)
68+
- `dnd/` directory — component-tree-order DnD (replaced with definition-order DnD)
69+
- `DragHandle.tsx` — extracted to `components/ui/` first (shared with Layout)
70+
- `canvas-operations.ts`, `block-utils.ts` — canvas utilities
71+
- `flattenStructural` and page-aware filtering — no longer needed
72+
- `properties/AppearanceSection.tsx` — moves to Layout
73+
- `properties/LayoutProperties.tsx` — moves to Layout
74+
- `properties/WidgetHintSection.tsx` — moves to Layout (queries component tree)
75+
76+
## The New Layout Tab — Visual Form Builder
77+
78+
### Purpose
79+
80+
Tier 2+3 visual builder. Shows how the form looks and is organized. Built from scratch — not migrated from the current Editor canvas. Absorbs all functionality from the current `PagesTab`.
81+
82+
### What It Shows
83+
84+
The component tree rendered as a visual canvas:
85+
86+
- **Page nodes** — first-class visible sections with titles, rendered as large section blocks
87+
- **Layout containers** — Card, Grid, Panel, Columns as dashed-border wrappers with children
88+
- **Field blocks** — bound items showing label and widget type
89+
- **Display blocks** — display items showing label and component type (Heading, Text, Divider)
90+
- **Page navigation** — wizard step bar or tab strip depending on `pageMode`
91+
- **Mode selector** — single / wizard / tabs (absorbed from current PagesTab)
92+
- **Unassigned items tray** — definition items not yet placed in the component tree
93+
94+
### Unassigned Items Tray
95+
96+
The tray shows definition items that exist but are not bound in the component tree. Per Component S4.5, required unbound items MUST be rendered at runtime; non-required MAY be omitted. The tray distinguishes between these:
97+
98+
- **Required unassigned** — highlighted, since the rendered form will auto-append them as fallback inputs
99+
- **Non-required unassigned** — dimmed, since they may not appear in the rendered form
100+
101+
Dragging an item from the tray into the canvas creates a component node with `bind` set to the item's key.
102+
103+
### Interactions
104+
105+
- Drag-and-drop reorder within component tree (visual order, not data order)
106+
- Drag from unassigned tray onto pages/containers
107+
- Right-click → wrap in Card/Grid/Panel, unwrap, move to page
108+
- Add Page button, page reordering, page deletion
109+
- Grid region editing (span/start within pages)
110+
111+
### Properties Panel (Right Sidebar)
112+
113+
Presentation properties only:
114+
115+
| Section | Properties |
116+
|---------|-----------|
117+
| Widget | component type selector (actual override, not advisory hint) |
118+
| Appearance | style overrides, CSS classes, labelPosition override |
119+
| Layout | grid span, start, responsive breakpoint overrides |
120+
| Container | direction, gap, columns (for Grid/Stack/Columns containers) |
121+
| Conditional | component `when` expression (visual-only — does NOT clear data) |
122+
| Accessibility | component-level a11y overrides |
123+
124+
Properties NOT shown (stay in Editor): key, label, dataType, options, binds, repeat config.
125+
126+
### Cross-Tier Write: pageMode
127+
128+
The Layout tab's mode selector writes `definition.formPresentation.pageMode` to the Definition document. This is a cross-tier write — a presentation control modifying a Tier 1 property. This is architecturally defensible because `pageMode` is explicitly advisory (Core S4.1.1) and exists solely to control pagination behavior. The Layout tab also writes `formPresentation.showProgress`, `allowSkip`, `defaultTab`, `tabPosition` — all mode-dependent behavioral settings that are only meaningful when `pageMode` is non-`single`.
129+
130+
### Component `when` vs Bind `relevant`
131+
132+
The Layout tab exposes component `when` expressions. The Editor tab exposes bind `relevant` expressions. Per Component S8.2, these have fundamentally different data-model semantics:
133+
134+
- **`when` (Layout)** — visual only. Hidden component's bound data is preserved in the instance.
135+
- **`relevant` (Editor)** — affects data model. Non-relevant items MAY be removed from the instance per `nonRelevantBehavior`.
136+
137+
The Layout properties panel should clearly label `when` as "Visual Condition (data preserved)" to prevent confusion with `relevant`.
138+
139+
## Shell & Routing Changes
140+
141+
### Tab Bar
142+
143+
No tab additions or removals. Same seven tabs: Editor, Logic, Data, Layout, Theme, Mapping, Preview.
144+
145+
### WORKSPACES Map
146+
147+
```typescript
148+
const WORKSPACES: Record<string, React.FC> = {
149+
Editor: DefinitionTreeEditor, // NEW — replaces EditorCanvas
150+
Logic: LogicTab,
151+
Data: DataTab,
152+
Layout: LayoutCanvas, // NEW — replaces PagesTab
153+
Theme: ThemeTab,
154+
Mapping: MappingTab,
155+
Preview: PreviewTab,
156+
};
157+
```
158+
159+
### Shared State
160+
161+
| Hook | Editor | Layout | Notes |
162+
|------|--------|--------|-------|
163+
| `useDefinition` | Yes | Yes | Shared — both read definition |
164+
| `useComponent` | No | Yes | Layout only — Editor never reads component tree |
165+
| `useSelection` | Yes | Yes | Shared SelectionProvider; both tabs select by path |
166+
| `useActiveGroup` | No | Yes | Layout only — page navigation is a Layout concern |
167+
| `useProject` | Yes | Yes | Shared — both dispatch commands |
168+
| `useCanvasTargets` | Yes | Yes | Shared — scroll-to-target in both |
169+
| `useTheme` | No | Yes | Layout only — theme cascade resolution |
170+
| `usePageStructure` | No | Yes | Layout only — from current PagesTab |
171+
172+
### Properties Panel in Shell
173+
174+
The right sidebar currently renders `ItemProperties` for all workspaces. In the new design:
175+
176+
- When **Editor** is active → render `DefinitionProperties` (Tier 1 props)
177+
- When **Layout** is active → render `ComponentProperties` (Tier 2/3 props)
178+
- When other tabs are active → keep current behavior (or hide panel)
179+
180+
The Shell routes based on `activeTab`, not item selection.
181+
182+
### Item Addition from Both Tabs
183+
184+
Items can be created from either tab:
185+
186+
- **Editor** `+ Add Item` → creates a definition item. Appears as unassigned in Layout until placed.
187+
- **Layout** add-from-palette → creates a definition item AND places it in the component tree in one operation (dispatches `definition.addItem` + `pages.assignItem` atomically, same as `project.addPage` does today).
188+
189+
## File Structure
190+
191+
```
192+
workspaces/
193+
editor/ # REWRITTEN — definition tree editor
194+
DefinitionTreeEditor.tsx # Main — tree view of definition.items
195+
ItemRow.tsx # Compact tree row for field/group/display
196+
GroupNode.tsx # Collapsible group with children
197+
AddItemPalette.tsx # Reuse existing, minus layout category
198+
EditorContextMenu.tsx # Simplified — no wrap/unwrap, no layout actions
199+
dnd/ # Definition-order DnD (reorders items array)
200+
properties/
201+
DefinitionProperties.tsx # Properties panel root
202+
FieldConfigSection.tsx # Stays — strip any presentation props
203+
OptionsSection.tsx # Stays as-is
204+
GroupConfigSection.tsx # Stays as-is
205+
ContentSection.tsx # Stays — strip widget override
206+
BindsInlineSection.tsx # New — inline bind editing for selected item
207+
MultiSelectSummary.tsx # Stays — batch delete/duplicate
208+
shared.tsx # Stays — PropInput, AddPlaceholder
209+
210+
layout/ # NEW — replaces pages/
211+
LayoutCanvas.tsx # Main — component tree visual builder
212+
CanvasBlock.tsx # Renders any component tree node
213+
PageSection.tsx # Page node as a titled section
214+
LayoutContainer.tsx # Card/Grid/Panel/Columns wrapper
215+
FieldBlock.tsx # Bound field as a visual card
216+
DisplayBlock.tsx # Display item visual block
217+
UnassignedTray.tsx # Items not placed in component tree
218+
ModeSelector.tsx # Single/wizard/tabs (from PagesTab)
219+
PageNav.tsx # Wizard step bar or tab bar
220+
dnd/ # Component-tree-order DnD
221+
properties/
222+
ComponentProperties.tsx # Properties panel root
223+
WidgetSection.tsx # Component type selector
224+
AppearanceSection.tsx # Style, CSS classes (migrated from editor)
225+
LayoutSection.tsx # Grid span, responsive
226+
ContainerSection.tsx # Direction, gap, columns
227+
```
228+
229+
### Deleted Entirely
230+
231+
- `workspaces/editor/FieldBlock.tsx`
232+
- `workspaces/editor/GroupBlock.tsx`
233+
- `workspaces/editor/DisplayBlock.tsx`
234+
- `workspaces/editor/LayoutBlock.tsx`
235+
- `workspaces/editor/render-tree-nodes.tsx`
236+
- `workspaces/editor/GroupTabs.tsx`
237+
- `workspaces/editor/DragHandle.tsx` (extracted to `components/ui/`)
238+
- `workspaces/editor/canvas-operations.ts`
239+
- `workspaces/editor/block-utils.ts`
240+
- `workspaces/editor/dnd/` (old component-tree DnD)
241+
- `workspaces/editor/properties/AppearanceSection.tsx` (moves to layout)
242+
- `workspaces/editor/properties/LayoutProperties.tsx` (moves to layout)
243+
- `workspaces/editor/properties/WidgetHintSection.tsx` (moves to layout)
244+
- `workspaces/pages/` (entire directory)
245+
246+
### Extracted to `components/ui/`
247+
248+
- `DragHandle.tsx` — shared between Editor (definition reorder) and Layout (component tree reorder)
249+
250+
## Concerns Not Affected by This Split
251+
252+
These Tier 1 behavioral constructs already live in dedicated tabs and are not part of the Editor/Layout split:
253+
254+
- **Variables** — Logic tab (`VariablesSection`)
255+
- **Shapes** — Logic tab (`ShapesSection`)
256+
- **Instances / Data Sources** — Data tab (`DataSources`)
257+
- **Option Sets** — Data tab (`OptionSets`)
258+
- **Screener** — Blueprint sidebar (`ScreenerSection`)
259+
- **Mappings** — Mapping tab
260+
- **Migrations** — not yet implemented; separate concern
261+
262+
## Testing Strategy
263+
264+
### Preserve Test IDs
265+
266+
The new Editor tree rows MUST emit the same `data-testid` patterns as the current blocks:
267+
268+
- `data-testid="field-{key}"` on field rows
269+
- `data-testid="group-{key}"` on group nodes
270+
- `data-testid="display-{key}"` on display rows
271+
- `data-testid="add-item"` on the Add Item button
272+
273+
This minimizes E2E test breakage. Layout-specific test IDs (`layout-{nodeId}`, wrap/unwrap actions) move to Layout tab tests.
274+
275+
### Unit Tests
276+
277+
- `DefinitionTreeEditor` — renders items tree from definition, add/delete/reorder changes definition array order, no component tree awareness
278+
- `LayoutCanvas` — renders component tree, Page nodes visible as sections, DnD reorders component tree children, unassigned tray shows unbound items
279+
- `DefinitionProperties` — shows only Tier 1 properties for selected item
280+
- `ComponentProperties` — shows only Tier 2/3 properties for selected component node
281+
282+
### Integration Tests
283+
284+
- Adding item in Editor → appears as unassigned in Layout
285+
- Placing unassigned item on a page in Layout → does not change definition order
286+
- Reordering in Editor → changes `definition.items` order, does not affect component tree
287+
- Reordering in Layout → changes component tree order, does not affect definition items
288+
- Properties split: selecting item in Editor shows definition props; selecting in Layout shows component props
289+
290+
### E2E Tests
291+
292+
- Full authoring workflow: create form in Editor → switch to Layout → arrange on pages → Preview renders correctly
293+
- Existing E2E tests updated: layout-specific tests (`layout-components.spec.ts`, wrap/unwrap) navigate to Layout tab instead of Editor
294+
- Wizard/tabs mode tests navigate to Layout tab for page management
295+
296+
### Test Files Affected
297+
298+
**Editor unit tests (rewrite):** 13 files in `tests/workspaces/editor/`
299+
**Pages unit tests (rewrite):** 7 files in `tests/workspaces/pages/`
300+
**Integration tests (update):** `editor-workflow`, `import-export`, `undo-redo`
301+
**E2E tests (update selectively):** ~13 files — primarily `editor-authoring`, `layout-components`, `interaction-patterns`, `wizard-mode`
302+
303+
## Migration
304+
305+
No data migration required. The definition and component documents are unchanged — only the UI surface that edits each part is relocated. Existing projects load identically.
306+
307+
## Spec References
308+
309+
- Core S2.3 — Three-layer separation of concerns (architectural invariant)
310+
- Core S4.1.1 — `formPresentation` (advisory, does not participate in processing model)
311+
- Core S4.2.5 — `presentation` hints (advisory, MUST NOT affect data semantics)
312+
- Core S4.3 — Binds (behavioral layer)
313+
- Component S3.3 — Children array ordering (renderers MUST preserve)
314+
- Component S4.1 — Slot binding by key (not position)
315+
- Component S4.5 — Unbound required items (MUST render as fallback)
316+
- Component S8.2 — `when` vs `relevant` (visual-only vs data-affecting)
317+
- Component S11.3 — Cross-tier cascade precedence
318+
- Theme S5.1 — Cascade levels (Tier 1 < Tier 2 < Tier 3)
319+
- Theme S6.3 — Unassigned items rendering policy

0 commit comments

Comments
 (0)