create-substrate 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/index.d.ts +3 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +27 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/prompts.d.ts +6 -0
  6. package/dist/prompts.d.ts.map +1 -0
  7. package/dist/prompts.js +127 -0
  8. package/dist/prompts.js.map +1 -0
  9. package/dist/scaffold.d.ts +10 -0
  10. package/dist/scaffold.d.ts.map +1 -0
  11. package/dist/scaffold.js +395 -0
  12. package/dist/scaffold.js.map +1 -0
  13. package/dist/surfaces/3d-scene.d.ts +3 -0
  14. package/dist/surfaces/3d-scene.d.ts.map +1 -0
  15. package/dist/surfaces/3d-scene.js +184 -0
  16. package/dist/surfaces/3d-scene.js.map +1 -0
  17. package/dist/surfaces/animation.d.ts +3 -0
  18. package/dist/surfaces/animation.d.ts.map +1 -0
  19. package/dist/surfaces/animation.js +211 -0
  20. package/dist/surfaces/animation.js.map +1 -0
  21. package/dist/surfaces/blank.d.ts +3 -0
  22. package/dist/surfaces/blank.d.ts.map +1 -0
  23. package/dist/surfaces/blank.js +72 -0
  24. package/dist/surfaces/blank.js.map +1 -0
  25. package/dist/surfaces/canvas-2d.d.ts +3 -0
  26. package/dist/surfaces/canvas-2d.d.ts.map +1 -0
  27. package/dist/surfaces/canvas-2d.js +139 -0
  28. package/dist/surfaces/canvas-2d.js.map +1 -0
  29. package/dist/surfaces/data-vis.d.ts +3 -0
  30. package/dist/surfaces/data-vis.d.ts.map +1 -0
  31. package/dist/surfaces/data-vis.js +175 -0
  32. package/dist/surfaces/data-vis.js.map +1 -0
  33. package/dist/surfaces/image-gen.d.ts +3 -0
  34. package/dist/surfaces/image-gen.d.ts.map +1 -0
  35. package/dist/surfaces/image-gen.js +193 -0
  36. package/dist/surfaces/image-gen.js.map +1 -0
  37. package/dist/surfaces/index.d.ts +4 -0
  38. package/dist/surfaces/index.d.ts.map +1 -0
  39. package/dist/surfaces/index.js +17 -0
  40. package/dist/surfaces/index.js.map +1 -0
  41. package/dist/surfaces/node-editor.d.ts +3 -0
  42. package/dist/surfaces/node-editor.d.ts.map +1 -0
  43. package/dist/surfaces/node-editor.js +211 -0
  44. package/dist/surfaces/node-editor.js.map +1 -0
  45. package/dist/surfaces/types.d.ts +22 -0
  46. package/dist/surfaces/types.d.ts.map +1 -0
  47. package/dist/surfaces/types.js +10 -0
  48. package/dist/surfaces/types.js.map +1 -0
  49. package/dist/utils/detect-pm.d.ts +5 -0
  50. package/dist/utils/detect-pm.d.ts.map +1 -0
  51. package/dist/utils/detect-pm.js +20 -0
  52. package/dist/utils/detect-pm.js.map +1 -0
  53. package/dist/utils/fs.d.ts +7 -0
  54. package/dist/utils/fs.d.ts.map +1 -0
  55. package/dist/utils/fs.js +52 -0
  56. package/dist/utils/fs.js.map +1 -0
  57. package/dist/utils/logger.d.ts +10 -0
  58. package/dist/utils/logger.d.ts.map +1 -0
  59. package/dist/utils/logger.js +15 -0
  60. package/dist/utils/logger.js.map +1 -0
  61. package/dist/utils/shell.d.ts +7 -0
  62. package/dist/utils/shell.d.ts.map +1 -0
  63. package/dist/utils/shell.js +28 -0
  64. package/dist/utils/shell.js.map +1 -0
  65. package/package.json +35 -0
  66. package/skills/3d-scene/SKILL.md +172 -0
  67. package/skills/animation/SKILL.md +194 -0
  68. package/skills/canvas-2d/SKILL.md +132 -0
  69. package/skills/composing-panels/SKILL.md +309 -0
  70. package/skills/create-custom-tool/SKILL.md +157 -0
  71. package/skills/data-visualisation/SKILL.md +228 -0
  72. package/skills/image-generation/SKILL.md +211 -0
  73. package/skills/scaffold-playground/SKILL.md +141 -0
  74. package/skills/substrate-canvas/SKILL.md +217 -0
  75. package/skills/substrate-controls/SKILL.md +242 -0
  76. package/skills/substrate-feedback/SKILL.md +219 -0
  77. package/skills/substrate-interaction/SKILL.md +286 -0
  78. package/skills/substrate-nodes/SKILL.md +208 -0
  79. package/skills/substrate-scaffold/SKILL.md +206 -0
  80. package/skills/theming/SKILL.md +117 -0
  81. package/skills/wire-interactions/SKILL.md +155 -0
@@ -0,0 +1,286 @@
1
+ ---
2
+ name: substrate-interaction
3
+ description: "Cross-cutting interaction patterns for Substrate: selection, undo/redo, clipboard, keyboard shortcuts, history controls, layer lists, and tree lists. Use when wiring up user interaction flows that span multiple components. Triggers on: selection, undo, redo, clipboard, copy, paste, keyboard shortcut, history, layer list, tree list, drag reorder, undo stack."
4
+ user-invocable: true
5
+ ---
6
+
7
+ # Substrate interaction
8
+
9
+ Interaction patterns wire together hooks, utilities, and components to create cohesive editing flows – selection, undo/redo, clipboard, keyboard shortcuts, and structural navigation (layers/trees).
10
+
11
+ ## When to use this skill
12
+
13
+ Use the interaction skill when you need to:
14
+
15
+ - Wire up undo/redo across a surface
16
+ - Add clipboard (copy/cut/paste) support
17
+ - Register and manage keyboard shortcuts
18
+ - Build a layer list or tree list with selection and reordering
19
+ - Connect HistoryControls to an undo stack
20
+
21
+ For spatial interaction (pan, zoom, snap), see **substrate-canvas**.
22
+ For the controls that display properties, see **substrate-controls**.
23
+ For notifications and command palette, see **substrate-feedback**.
24
+
25
+ ---
26
+
27
+ ## Hooks
28
+
29
+ ### useUndoable
30
+
31
+ `registry/substrate/hooks/use-undoable.ts`
32
+
33
+ Lightweight undo/redo stack using refs for performance. Stores snapshots, not diffs.
34
+
35
+ **Returns**: `pushSnapshot`, `undo`, `redo`, `canUndo`, `canRedo`, `undoCount`, `redoCount`, `clear`
36
+
37
+ **Pattern**: Call `pushSnapshot(currentState)` *before* mutating. Call `undo()` / `redo()` to get the previous/next state, then apply it.
38
+
39
+ ```ts
40
+ const { pushSnapshot, undo, redo, canUndo, canRedo } = useUndoable<State>({ maxHistory: 100 })
41
+
42
+ function handleMove(id: string, x: number, y: number) {
43
+ pushSnapshot(state)
44
+ setState(prev => updateElement(prev, id, { x, y }))
45
+ }
46
+ ```
47
+
48
+ **Connects to**: `HistoryControls` component, `NumberInput.onStart` (push snapshot before scrub), `createKeyboardShortcuts` (⌘Z / ⌘⇧Z).
49
+
50
+ ---
51
+
52
+ ### useClipboard
53
+
54
+ `registry/substrate/hooks/use-clipboard.ts`
55
+
56
+ Copy/cut/paste with ⌘C/⌘X/⌘V keyboard listeners. Writes to system clipboard where available, falls back to internal ref.
57
+
58
+ **Returns**: `copy`, `cut`, `paste`
59
+
60
+ **Pattern**: Provide `onCopy` (returns items to copy), `onPaste` (receives items), and optionally `onCut`.
61
+
62
+ ```ts
63
+ useClipboard({
64
+ onCopy: () => selectedElements.map(el => ({ ...el })),
65
+ onPaste: (items) => {
66
+ pushSnapshot(state)
67
+ addElements(items.map(offset))
68
+ },
69
+ onCut: () => {
70
+ const items = selectedElements.map(el => ({ ...el }))
71
+ pushSnapshot(state)
72
+ removeSelected()
73
+ return items
74
+ },
75
+ })
76
+ ```
77
+
78
+ **Auto-skips**: Input, textarea, and contentEditable elements.
79
+
80
+ ---
81
+
82
+ ### useSelection
83
+
84
+ `registry/substrate/hooks/use-selection.ts`
85
+
86
+ Multi-select with modifier keys. See **substrate-canvas** for spatial selection use.
87
+
88
+ **Also useful for**: Layer lists and tree lists where items are selected by clicking rows.
89
+
90
+ ---
91
+
92
+ ### useDragReorder
93
+
94
+ `registry/substrate/hooks/use-drag-reorder.ts`
95
+
96
+ Pointer-based list reordering. See **substrate-canvas** for details.
97
+
98
+ ---
99
+
100
+ ## Utilities
101
+
102
+ ### createKeyboardShortcuts
103
+
104
+ `registry/substrate/lib/keyboard-shortcuts.ts`
105
+
106
+ Declarative shortcut registration. Replaces imperative switch statements.
107
+
108
+ ```ts
109
+ const dispose = createKeyboardShortcuts([
110
+ { key: "z", meta: true, handler: handleUndo, label: "Undo", group: "Edit" },
111
+ { key: "z", meta: true, shift: true, handler: handleRedo, label: "Redo", group: "Edit" },
112
+ { key: "c", meta: true, handler: copy, label: "Copy", group: "Edit" },
113
+ { key: "v", meta: true, handler: paste, label: "Paste", group: "Edit" },
114
+ { key: "Delete", handler: deleteSelected, label: "Delete", group: "Edit" },
115
+ { key: "a", meta: true, handler: selectAll, label: "Select all", group: "Selection" },
116
+ { key: "v", handler: () => setTool("select"), label: "Select tool", group: "Tools" },
117
+ { key: "r", handler: () => setTool("rectangle"), label: "Rectangle tool", group: "Tools" },
118
+ ])
119
+
120
+ // Clean up on unmount
121
+ useEffect(() => dispose, [dispose])
122
+ ```
123
+
124
+ **formatShortcut**: Converts entries to display strings for context menus and shortcut overlay.
125
+
126
+ ```ts
127
+ formatShortcut({ key: "z", meta: true, shift: true }) // → "⌘+Shift+Z"
128
+ ```
129
+
130
+ **Connects to**: `ShortcutOverlay` (substrate-feedback) – pass the same groups. `ContextMenuShortcut` – use `formatShortcut` for display.
131
+
132
+ ---
133
+
134
+ ## Components
135
+
136
+ ### HistoryControls
137
+
138
+ `registry/substrate/components/history-controls.tsx`
139
+
140
+ Undo/redo button pair. Renders `data-slot="history-controls"` for auto-positioning in PaneHeader and Toolbar.
141
+
142
+ **Props**: `canUndo`, `canRedo`, `onUndo`, `onRedo`
143
+
144
+ **Typical placement**:
145
+ - Inside `PaneHeader` – auto-positioned to the right via `**:data-[slot=history-controls]:ml-auto`
146
+ - Inside `Toolbar` – auto-compacted via `**:data-[slot=history-controls]:gap-0`
147
+
148
+ ---
149
+
150
+ ### LayerList
151
+
152
+ `registry/substrate/components/layer-list.tsx`
153
+
154
+ Flat layer panel with selection, visibility, lock, rename, and reorder.
155
+
156
+ **Exports**: `LayerList`, `LayerItem`, `LayerIcon`, `LayerName`, `LayerActions`, `LayerToggle`, `LayerVisibilityToggle`, `LayerLockToggle`
157
+
158
+ **Features**:
159
+ - `LayerItem` supports `selected`, `hovered`, `depth` props
160
+ - `LayerName` enables double-click inline rename
161
+ - `LayerActions` are hidden by default, visible on row hover
162
+ - `LayerVisibilityToggle` and `LayerLockToggle` use eye/lock icons
163
+
164
+ **When to use**: 2D canvas surfaces, image editors, animation layers – any flat list of elements. For hierarchical structures, use TreeList instead.
165
+
166
+ **Connects to**: `useSelection` for multi-select, `useDragReorder` for reordering, `ContextMenu` (substrate-feedback) for right-click actions.
167
+
168
+ ---
169
+
170
+ ### TreeList
171
+
172
+ `registry/substrate/components/tree-list.tsx`
173
+
174
+ Hierarchical tree with expand/collapse, depth indentation, and icons.
175
+
176
+ **Exports**: `TreeList`, `TreeItem`, `TreeExpandButton`, `TreeItemContent`, `TreeItemIcon`, `TreeItemActions` + `useTreeExpansion` hook
177
+
178
+ **Features**:
179
+ - 16px depth indentation per level
180
+ - `TreeExpandButton` rotates chevron on expand
181
+ - `useTreeExpansion` manages expanded state: `isExpanded`, `toggle`, `expandAll`, `collapseAll`
182
+
183
+ **When to use**: Scene graph (3D), frame hierarchy (2D), file trees, component trees, node groups. For flat lists, use LayerList instead.
184
+
185
+ ---
186
+
187
+ ## Wiring it all together
188
+
189
+ ### Complete undo/redo flow
190
+
191
+ ```
192
+ ┌─────────────────────────────────────────────────┐
193
+ │ User action (move, resize, delete, etc.) │
194
+ │ → pushSnapshot(currentState) │
195
+ │ → mutate state │
196
+ ├─────────────────────────────────────────────────┤
197
+ │ ⌘Z pressed (via createKeyboardShortcuts) │
198
+ │ → undo() returns previous state │
199
+ │ → apply to store │
200
+ ├─────────────────────────────────────────────────┤
201
+ │ HistoryControls (in PaneHeader or Toolbar) │
202
+ │ → reads canUndo/canRedo │
203
+ │ → calls onUndo/onRedo │
204
+ ├─────────────────────────────────────────────────┤
205
+ │ NumberInput.onStart │
206
+ │ → pushSnapshot before scrub begins │
207
+ └─────────────────────────────────────────────────┘
208
+ ```
209
+
210
+ ### Complete shortcut flow
211
+
212
+ ```
213
+ ┌─────────────────────────────────────────────────┐
214
+ │ createKeyboardShortcuts(entries) │
215
+ │ → registers keydown listener │
216
+ │ → entries include label + group metadata │
217
+ ├─────────────────────────────────────────────────┤
218
+ │ ShortcutOverlay (substrate-feedback) │
219
+ │ → reads same groups for display │
220
+ ├─────────────────────────────────────────────────┤
221
+ │ ContextMenuShortcut (substrate-feedback) │
222
+ │ → formatShortcut(entry) for inline display │
223
+ ├─────────────────────────────────────────────────┤
224
+ │ CommandPalette (substrate-feedback) │
225
+ │ → CommandShortcut shows the key binding │
226
+ └─────────────────────────────────────────────────┘
227
+ ```
228
+
229
+ ### Layer list with full interaction
230
+
231
+ ```tsx
232
+ function LayerPanel() {
233
+ const { selectedIds, select, isSelected } = useSelection({ items: elementIds })
234
+ const { getDragItemProps } = useDragReorder({ items: elements, onReorder: reorderElements })
235
+
236
+ return (
237
+ <LayerList>
238
+ {elements.map((el, i) => (
239
+ <ContextMenu key={el.id}>
240
+ <ContextMenuTrigger asChild>
241
+ <LayerItem
242
+ selected={isSelected(el.id)}
243
+ onClick={(e) => select(el.id, e)}
244
+ {...getDragItemProps(i)}
245
+ >
246
+ <LayerIcon colour={el.fill?.color} />
247
+ <LayerName value={el.name} onRename={(name) => updateElement(el.id, { name })} />
248
+ <LayerActions>
249
+ <LayerVisibilityToggle
250
+ visible={el.visible}
251
+ onToggle={() => updateElement(el.id, { visible: !el.visible })}
252
+ />
253
+ <LayerLockToggle
254
+ locked={el.locked}
255
+ onToggle={() => updateElement(el.id, { locked: !el.locked })}
256
+ />
257
+ </LayerActions>
258
+ </LayerItem>
259
+ </ContextMenuTrigger>
260
+ <ContextMenuContent>
261
+ <ContextMenuItem onSelect={() => duplicateElement(el.id)}>
262
+ Duplicate <ContextMenuShortcut>⌘D</ContextMenuShortcut>
263
+ </ContextMenuItem>
264
+ <ContextMenuItem onSelect={() => deleteElement(el.id)}>
265
+ Delete <ContextMenuShortcut>⌫</ContextMenuShortcut>
266
+ </ContextMenuItem>
267
+ </ContextMenuContent>
268
+ </ContextMenu>
269
+ ))}
270
+ </LayerList>
271
+ )
272
+ }
273
+ ```
274
+
275
+ ---
276
+
277
+ ## Cross-references
278
+
279
+ | Need | Skill |
280
+ | --- | --- |
281
+ | Panes and panels hosting layer/tree lists | **substrate-scaffold** |
282
+ | Property controls for selected items | **substrate-controls** |
283
+ | Viewport, snap, hit testing | **substrate-canvas** |
284
+ | Toast for delete-with-undo notifications | **substrate-feedback** |
285
+ | Node selection and graph undo | **substrate-nodes** |
286
+ | Surface-specific interaction models | **substrate-surfaces** |
@@ -0,0 +1,208 @@
1
+ ---
2
+ name: substrate-nodes
3
+ description: "Substrate node graph system: node cards, typed ports, port colour theming, and node editor patterns. Use when building node-based editors, visual programming tools, shader graphs, or data flow UIs. Triggers on: node, port, graph, visual programming, shader graph, data flow, pipeline, wire, connection."
4
+ user-invocable: true
5
+ ---
6
+
7
+ # Substrate nodes
8
+
9
+ The node system provides card-based nodes with typed ports, a CSS variable colour scheme for port types, and compound components for building node editor UIs.
10
+
11
+ ## When to use this skill
12
+
13
+ Use the nodes skill when you need to:
14
+
15
+ - Build a node-based editor (shader graph, data flow, automation pipeline)
16
+ - Add typed input/output ports to nodes
17
+ - Theme port types with custom colours
18
+ - Render node previews, badges, or dividers
19
+ - Plan a node graph architecture
20
+
21
+ For the canvas that hosts nodes (pan/zoom/selection), see **substrate-canvas**.
22
+ For the scaffold shell around the editor, see **substrate-scaffold**.
23
+
24
+ ---
25
+
26
+ ## Components
27
+
28
+ ### Node compound component
29
+
30
+ `registry/substrate/components/node.tsx`
31
+
32
+ 10 exports forming a complete node card:
33
+
34
+ | Component | Slot | Purpose |
35
+ | --- | --- | --- |
36
+ | `NodeCard` | `node-card` | Outer card. Selected state adds accent ring. |
37
+ | `NodeHeader` | `node-header` | Optional coloured header strip. Pass `colour` for a category tint. |
38
+ | `NodeTitle` | `node-title` | Node name inside the header. |
39
+ | `NodeBadge` | `node-badge` | Small label (e.g. "GPU", "Deprecated"). |
40
+ | `NodeBody` | `node-body` | Content area below the header. |
41
+ | `NodePorts` | `node-ports` | Container for a column of ports (left or right side). |
42
+ | `NodePort` | `node-port` | A single port row: dot + label. Props: `type`, `label`, `direction`, `connected`. |
43
+ | `NodePortDot` | `node-port-dot` | The coloured circle. Filled when connected, hollow when not. |
44
+ | `NodePreview` | `node-preview` | Thumbnail/preview area inside the node body. |
45
+ | `NodeDivider` | `node-divider` | Thin horizontal line between body sections. |
46
+
47
+ ### Port type system
48
+
49
+ 7 built-in port types, each with a themed CSS variable:
50
+
51
+ | Type | CSS variable | Default colour |
52
+ | --- | --- | --- |
53
+ | `number` | `--node-port-number` | `#4a9eff` (blue) |
54
+ | `colour` | `--node-port-colour` | `#e84393` (pink) |
55
+ | `image` | `--node-port-image` | `#00b894` (green) |
56
+ | `geometry` | `--node-port-geometry` | `#fdcb6e` (amber) |
57
+ | `text` | `--node-port-text` | `#a29bfe` (violet) |
58
+ | `boolean` | `--node-port-boolean` | `#fd79a8` (rose) |
59
+ | `any` | `--node-port-any` | `#636e72` (grey) |
60
+
61
+ These follow the standard Substrate CSS variable fallback chain in `globals.css`:
62
+
63
+ ```css
64
+ --color-node-port-number: var(--node-port-number, #4a9eff);
65
+ ```
66
+
67
+ Consumers override `--node-port-number` to change the colour across their entire app.
68
+
69
+ ### Node chrome variables
70
+
71
+ 4 additional CSS variables for the node card itself:
72
+
73
+ | Variable | Default | Purpose |
74
+ | --- | --- | --- |
75
+ | `--node-bg` | `var(--popover)` | Node card background |
76
+ | `--node-border` | `var(--border)` | Node card border |
77
+ | `--node-header-bg` | `var(--muted)` | Header strip background |
78
+ | `--node-selected-ring` | `var(--accent)` | Selected ring colour |
79
+
80
+ ---
81
+
82
+ ## Usage
83
+
84
+ ### Basic node
85
+
86
+ ```tsx
87
+ <NodeCard selected={isSelected}>
88
+ <NodeHeader colour="#4a9eff">
89
+ <NodeTitle>Add</NodeTitle>
90
+ <NodeBadge>Math</NodeBadge>
91
+ </NodeHeader>
92
+ <NodeBody>
93
+ <div className="flex justify-between">
94
+ <NodePorts>
95
+ <NodePort type="number" label="A" direction="input" connected />
96
+ <NodePort type="number" label="B" direction="input" />
97
+ </NodePorts>
98
+ <NodePorts>
99
+ <NodePort type="number" label="Result" direction="output" connected />
100
+ </NodePorts>
101
+ </div>
102
+ </NodeBody>
103
+ </NodeCard>
104
+ ```
105
+
106
+ ### Node with preview
107
+
108
+ ```tsx
109
+ <NodeCard>
110
+ <NodeHeader colour="#00b894">
111
+ <NodeTitle>Blur</NodeTitle>
112
+ </NodeHeader>
113
+ <NodeBody>
114
+ <NodePreview>
115
+ <img src={previewUrl} alt="Preview" />
116
+ </NodePreview>
117
+ <NodeDivider />
118
+ <div className="flex justify-between">
119
+ <NodePorts>
120
+ <NodePort type="image" label="Input" direction="input" connected />
121
+ <NodePort type="number" label="Radius" direction="input" />
122
+ </NodePorts>
123
+ <NodePorts>
124
+ <NodePort type="image" label="Output" direction="output" />
125
+ </NodePorts>
126
+ </div>
127
+ </NodeBody>
128
+ </NodeCard>
129
+ ```
130
+
131
+ ---
132
+
133
+ ## Architecture recommendations
134
+
135
+ ### Node graph store
136
+
137
+ A node editor typically needs a Zustand store with:
138
+
139
+ ```ts
140
+ interface NodeGraphState {
141
+ nodes: Record<string, NodeData>
142
+ connections: Connection[]
143
+ selectedNodeIds: Set<string>
144
+
145
+ // CRUD
146
+ addNode: (type: string, position: Point) => void
147
+ updateNode: (id: string, changes: Partial<NodeData>) => void
148
+ removeNodes: (ids: string[]) => void
149
+
150
+ // Connections
151
+ connect: (from: PortRef, to: PortRef) => void
152
+ disconnect: (connectionId: string) => void
153
+
154
+ // History
155
+ pushSnapshot: () => void
156
+ undo: () => void
157
+ redo: () => void
158
+ }
159
+
160
+ interface NodeData {
161
+ id: string
162
+ type: string
163
+ position: Point
164
+ inputs: PortDefinition[]
165
+ outputs: PortDefinition[]
166
+ properties: Record<string, unknown>
167
+ }
168
+
169
+ interface Connection {
170
+ id: string
171
+ from: PortRef
172
+ to: PortRef
173
+ }
174
+
175
+ interface PortRef {
176
+ nodeId: string
177
+ portId: string
178
+ }
179
+ ```
180
+
181
+ ### Rendering connections
182
+
183
+ Connection wires between ports are typically rendered as SVG `<path>` elements (cubic beziers) in a layer above the nodes. The path control points depend on port direction:
184
+
185
+ - Output ports: bezier exits rightward
186
+ - Input ports: bezier enters from the left
187
+
188
+ Use `useViewport` (substrate-canvas) for the pan/zoom transform that applies to both nodes and wires.
189
+
190
+ ### Port type validation
191
+
192
+ When the user drags a wire from an output port to an input port, validate type compatibility:
193
+
194
+ - Same type → allow
195
+ - `any` type → allow
196
+ - Different types → show visual feedback (red wire, shake animation) and reject
197
+
198
+ ---
199
+
200
+ ## Cross-references
201
+
202
+ | Need | Skill |
203
+ | --- | --- |
204
+ | Pan/zoom canvas hosting nodes | **substrate-canvas** |
205
+ | App shell, breadcrumbs for nested groups | **substrate-scaffold** |
206
+ | Property controls for selected node | **substrate-controls** |
207
+ | Selection, undo, keyboard shortcuts | **substrate-interaction** |
208
+ | Command palette for "Add Node" | **substrate-feedback** |
@@ -0,0 +1,206 @@
1
+ ---
2
+ name: substrate-scaffold
3
+ description: "Substrate app shell: toolbar, panels, panes, document tabs, status bar, zoom controls, breadcrumbs, and resizer. Use when building the frame around a surface – layout, navigation, and structural chrome. Triggers on: app shell, layout, toolbar, panel, sidebar, tabs, status bar, zoom, breadcrumbs, split pane, resizer."
4
+ user-invocable: true
5
+ ---
6
+
7
+ # Substrate scaffold
8
+
9
+ The scaffold is the structural shell around a Substrate surface – everything that isn't the central creative area itself. It provides toolbar, panels, panes, tabs, status bar, and layout primitives.
10
+
11
+ ## When to use this skill
12
+
13
+ Use the scaffold skill when you need to:
14
+
15
+ - Build or modify the app shell around a surface
16
+ - Add toolbar items or customise toolbar behaviour
17
+ - Create or restructure side panels
18
+ - Add property panes inside panels
19
+ - Wire up document tabs, breadcrumbs, or status bar
20
+ - Add resizable split regions
21
+
22
+ For the controls *inside* panes, see **substrate-controls**.
23
+ For the central surface area, see **substrate-surfaces** and **substrate-canvas**.
24
+
25
+ ---
26
+
27
+ ## Components in this domain
28
+
29
+ ### Toolbar
30
+
31
+ `registry/substrate/components/toolbar.tsx`
32
+
33
+ Floating bottom bar. Renders tool buttons, separators, and groups. Uses `data-slot="toolbar"` and provides **compaction styling** – children with `data-slot` attributes are automatically resized:
34
+
35
+ | Child slot | Compacted to |
36
+ | --- | --- |
37
+ | `number-input` | h-6, w-16 |
38
+ | `colour-swatch` | size-6 |
39
+ | `slider` / `slider-track` | h-6 |
40
+ | `history-controls` | gap-0 |
41
+
42
+ **Exports**: `Toolbar`, `ToolbarButton`, `ToolbarSeparator`, `ToolbarGroup`
43
+
44
+ **Typical use**: One per playground, positioned at the bottom. Connect each `ToolbarButton` to `useToolStore` for active-tool state.
45
+
46
+ **Related**: substrate-controls (for inputs inside the toolbar), substrate-interaction (for keyboard shortcuts bound to tools)
47
+
48
+ ---
49
+
50
+ ### Panel
51
+
52
+ `registry/substrate/components/panel.tsx`
53
+
54
+ Side panel (left or right). Controlled by `usePanelStore`. Renders as a fixed-width column with a header and scrollable body.
55
+
56
+ **Exports**: `Panel`, `PanelHeader`, `PanelBody`
57
+
58
+ **Typical use**: Left panel for structure (layers, tree), right panel for properties (panes with controls). Wire visibility to `usePanelStore`.
59
+
60
+ ---
61
+
62
+ ### Pane and CollapsiblePane
63
+
64
+ `registry/substrate/components/pane.tsx`
65
+ `registry/substrate/components/collapsible-pane.tsx`
66
+
67
+ Sections within a panel. A pane is a labelled group of controls. CollapsiblePane wraps Radix Collapsible for expand/collapse with animated content.
68
+
69
+ **PaneHeader** is slot-aware – it auto-positions `HistoryControls` via:
70
+ ```
71
+ **:data-[slot=history-controls]:ml-auto
72
+ **:data-[slot=history-button]:size-6
73
+ ```
74
+
75
+ **Pane exports**: `Pane`, `PaneHeader`, `PaneLabel`, `PaneAction`
76
+ **CollapsiblePane exports**: `CollapsiblePane`, `CollapsiblePaneHeader`, `CollapsiblePaneLabel`, `CollapsiblePaneContent`
77
+
78
+ **Typical use**: Group related controls (Transform, Fill, Stroke, Typography) inside the right panel.
79
+
80
+ **Related**: substrate-controls (for controls inside panes)
81
+
82
+ ---
83
+
84
+ ### DocumentTabs
85
+
86
+ `registry/substrate/components/document-tabs.tsx`
87
+
88
+ Tab bar for multiple open documents. Built on Radix Tabs. Close button uses `<span role="button">` to avoid nested-button HTML violation.
89
+
90
+ **Exports**: `DocumentTabs`, `DocumentTabList`, `DocumentTab`, `DocumentTabContent`
91
+
92
+ **Typical use**: Top of the layout, above the surface. Each tab maps to a document/file.
93
+
94
+ ---
95
+
96
+ ### StatusBar
97
+
98
+ `registry/substrate/components/status-bar.tsx`
99
+
100
+ Thin bottom bar (h-7) for metadata display. Sections align left/centre/right via flexbox auto-margins. Uses `text-[11px]` and `tabular-nums`.
101
+
102
+ **Exports**: `StatusBar`, `StatusBarSection`, `StatusBarItem`, `StatusBarSeparator`
103
+
104
+ **Typical use**: Below the surface. Show cursor position, zoom level, selection count, element type.
105
+
106
+ ---
107
+
108
+ ### ZoomControls
109
+
110
+ `registry/substrate/components/zoom-controls.tsx`
111
+
112
+ Zoom in/out/fit/reset buttons with percentage display. Click the percentage to reset to 100%.
113
+
114
+ **Exports**: `ZoomControls`, `ZoomButton`
115
+
116
+ **Props**: `zoom`, `onZoomChange`, `onZoomToFit`, `min`, `max`, `step`
117
+
118
+ **Typical use**: Inside the StatusBar or floating over the surface. Wire to `useViewport` from substrate-canvas.
119
+
120
+ **Related**: substrate-canvas (`useViewport` hook)
121
+
122
+ ---
123
+
124
+ ### Breadcrumbs
125
+
126
+ `registry/substrate/components/breadcrumbs.tsx`
127
+
128
+ Navigation trail for hierarchical contexts (e.g. Frame → Group → Element). Auto-truncates labels at `max-w-32`.
129
+
130
+ **Exports**: `Breadcrumbs`, `BreadcrumbItem`
131
+
132
+ **Typical use**: Above the surface or in a panel header when navigating into nested frames/groups.
133
+
134
+ ---
135
+
136
+ ### Resizer
137
+
138
+ `registry/substrate/components/resizer.tsx`
139
+
140
+ Draggable divider between regions. 1px visual line with a wider hit area. Three grab dots appear on hover.
141
+
142
+ **Exports**: `Resizer`
143
+
144
+ **Props**: `orientation` (`"vertical"` | `"horizontal"`)
145
+
146
+ **Typical use**: Between the panel and the surface, or between two panes in a split layout.
147
+
148
+ ---
149
+
150
+ ## Recommended combinations
151
+
152
+ ### Standard creative app shell
153
+
154
+ ```
155
+ Toolbar + Panel (left + right) + StatusBar + DocumentTabs
156
+ ```
157
+
158
+ Panels contain Panes/CollapsiblePanes. StatusBar contains ZoomControls. Toolbar is connected to useToolStore.
159
+
160
+ ### Minimal single-document tool
161
+
162
+ ```
163
+ Toolbar + Panel (right only) + StatusBar
164
+ ```
165
+
166
+ No tabs, no left panel. Good for focused single-surface tools like an image editor or easing curve designer.
167
+
168
+ ### Node editor shell
169
+
170
+ ```
171
+ Toolbar + Panel (right) + StatusBar + Breadcrumbs
172
+ ```
173
+
174
+ Breadcrumbs show the path into nested node groups. Right panel shows properties of the selected node. See **substrate-nodes** for node components.
175
+
176
+ ---
177
+
178
+ ## Layout pattern
179
+
180
+ ```tsx
181
+ <div className="flex h-screen flex-col">
182
+ <DocumentTabList>{/* tabs */}</DocumentTabList>
183
+ <div className="flex flex-1 overflow-hidden">
184
+ <Panel side="left">{/* layers/tree */}</Panel>
185
+ <div className="relative flex-1">
186
+ <Surface />{/* central area */}
187
+ </div>
188
+ <Panel side="right">{/* property panes */}</Panel>
189
+ </div>
190
+ <StatusBar>{/* metadata */}</StatusBar>
191
+ <Toolbar>{/* tools */}</Toolbar>
192
+ </div>
193
+ ```
194
+
195
+ ---
196
+
197
+ ## Cross-references
198
+
199
+ | Need | Skill |
200
+ | --- | --- |
201
+ | Controls inside panes (sliders, inputs, selects) | **substrate-controls** |
202
+ | The central rendering surface | **substrate-surfaces** |
203
+ | Spatial hooks (viewport, snap, bounds) | **substrate-canvas** |
204
+ | Selection, undo, clipboard, shortcuts | **substrate-interaction** |
205
+ | Toasts, progress, empty states | **substrate-feedback** |
206
+ | Node cards and port wiring | **substrate-nodes** |