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,217 @@
1
+ ---
2
+ name: substrate-canvas
3
+ description: "Spatial hooks and utilities for Substrate canvas surfaces: viewport (pan/zoom), selection, drag reorder, snapping, bounding box, coordinate maths, and hit testing. Use when building or extending a 2D/3D spatial surface. Triggers on: viewport, pan, zoom, snap to grid, bounding box, hit test, coordinate transform, canvas interaction, spatial, drag."
4
+ user-invocable: true
5
+ ---
6
+
7
+ # Substrate canvas
8
+
9
+ Canvas utilities handle everything spatial – viewport transforms, coordinate systems, snapping, bounding box computation, and hit testing. These are the building blocks for any surface where users interact with positioned objects.
10
+
11
+ ## When to use this skill
12
+
13
+ Use the canvas skill when you need to:
14
+
15
+ - Add pan/zoom to a surface
16
+ - Implement snap-to-grid or smart edge snapping
17
+ - Compute bounding boxes for selection or zoom-to-fit
18
+ - Convert between screen and world coordinates
19
+ - Build or extend hit testing
20
+ - Add spatial selection (click, shift-click, marquee)
21
+
22
+ For the app shell around the surface, see **substrate-scaffold**.
23
+ For non-spatial interaction (undo, clipboard, shortcuts), see **substrate-interaction**.
24
+
25
+ ---
26
+
27
+ ## Hooks
28
+
29
+ ### useViewport
30
+
31
+ `registry/substrate/hooks/use-viewport.ts`
32
+
33
+ Generic pan/zoom state. Surface-agnostic – works for 2D canvas, node editors, or any pannable area.
34
+
35
+ **Returns**: `viewport` (x, y, zoom), `toWorld`, `toScreen`, `pan`, `zoomToPoint`, `zoomIn`, `zoomOut`, `resetZoom`, `zoomToFit`, `handleWheel`
36
+
37
+ **When to use**: Any surface that needs panning and zooming. Wire `handleWheel` to the container's `onWheel`, use `toWorld`/`toScreen` for coordinate transforms.
38
+
39
+ **Connects to**: `ZoomControls` (substrate-scaffold) reads `viewport.zoom` and calls `zoomIn`/`zoomOut`/`resetZoom`.
40
+
41
+ ```tsx
42
+ const { viewport, handleWheel, zoomToFit } = useViewport({
43
+ minZoom: 0.1,
44
+ maxZoom: 10,
45
+ })
46
+ ```
47
+
48
+ ---
49
+
50
+ ### useSelection
51
+
52
+ `registry/substrate/hooks/use-selection.ts`
53
+
54
+ Multi-select with modifier key support: plain click, ⌘-click toggle, shift-click range.
55
+
56
+ **Returns**: `selectedIds`, `select`, `setSelection`, `selectAll`, `deselectAll`, `isSelected`
57
+
58
+ **When to use**: Layer lists, node selections, element picking – any context where the user selects from a list or spatial layout.
59
+
60
+ **Connects to**: `LayerList` and `TreeList` (substrate-interaction) for list-based selection. Canvas hit-testing for spatial selection.
61
+
62
+ ---
63
+
64
+ ### useDragReorder
65
+
66
+ `registry/substrate/hooks/use-drag-reorder.ts`
67
+
68
+ Pointer-based list reordering using pointer capture. Calculates drop index from drag distance.
69
+
70
+ **Returns**: `getDragItemProps`, `dragIndex`, `dropIndex`, `isDragging`
71
+
72
+ **When to use**: Reorderable layer lists, timeline tracks, node port ordering.
73
+
74
+ **Connects to**: `LayerList` (substrate-interaction) for visual layer reordering.
75
+
76
+ ---
77
+
78
+ ### Canvas-specific hooks
79
+
80
+ These hooks are tightly coupled to the 2D canvas surface and its stores:
81
+
82
+ | Hook | File | Purpose |
83
+ | --- | --- | --- |
84
+ | `useCanvasTransform` | `hooks/use-canvas-transform.ts` | Screen ↔ world coordinate conversion using `document-store` camera |
85
+ | `useCanvasRenderer` | `hooks/use-canvas-renderer.ts` | Canvas 2D rendering loop with dirty flag and rAF |
86
+ | `useCanvasDrag` | `hooks/use-canvas-drag.ts` | Pointer-based drag tracking (start/move/end) |
87
+ | `useCanvasInteraction` | `hooks/use-canvas-interaction.ts` | Orchestrates tool-specific behaviour (select, create, pan) |
88
+ | `useHitTest` | `hooks/use-hit-test.ts` | Point-in-element and handle hit detection |
89
+ | `useKeyboardShortcuts` | `hooks/use-keyboard-shortcuts.ts` | Canvas-specific keyboard bindings |
90
+
91
+ These are typically used together inside a `DesignCanvas` or similar surface component. See **substrate-surfaces** for how they compose.
92
+
93
+ ---
94
+
95
+ ## Utilities
96
+
97
+ ### snapToGrid / snapToGuides
98
+
99
+ `registry/substrate/lib/snap.ts`
100
+
101
+ Grid snapping and smart edge snapping.
102
+
103
+ | Function | Purpose |
104
+ | --- | --- |
105
+ | `snapToGrid(point, gridSize, options?)` | Snap a point to nearest grid intersection |
106
+ | `snapValueToGrid(value, gridSize, origin?)` | Snap a single number to grid |
107
+ | `snapToGuides(point, guides, options?)` | Snap to nearby guide lines (edges, centres) within a threshold |
108
+ | `guidesFromBounds(boundsList, exclude?)` | Generate alignment guides from sibling element bounds |
109
+
110
+ **When to use**: Object placement and resizing. Call `snapToGrid` during move/resize interactions. Use `guidesFromBounds` + `snapToGuides` for Figma-style smart alignment lines.
111
+
112
+ ```ts
113
+ // During element drag
114
+ const guides = guidesFromBounds(siblingBounds, draggedBounds)
115
+ const { point, snappedGuides } = snapToGuides(newPosition, guides)
116
+ // Render snappedGuides as alignment lines
117
+ ```
118
+
119
+ ---
120
+
121
+ ### boundingBox
122
+
123
+ `registry/substrate/lib/bounding-box.ts`
124
+
125
+ Bounding box computation and spatial queries.
126
+
127
+ | Function | Purpose |
128
+ | --- | --- |
129
+ | `boundingBox(items)` | Union bounds of multiple Bounds |
130
+ | `boundingBoxFromPoints(points)` | Bounds from an array of Points |
131
+ | `expandBounds(bounds, padding)` | Expand uniformly |
132
+ | `expandBoundsBy(bounds, { top, right, bottom, left })` | Expand per-side |
133
+ | `containsPoint(bounds, point)` | Point-in-bounds test |
134
+ | `containsBounds(a, b)` | Does A fully contain B? |
135
+ | `boundsCenter(bounds)` | Centre point |
136
+ | `intersectBounds(a, b)` | Intersection or null |
137
+
138
+ **When to use**: Zoom-to-fit (compute union bounds → pass to `useViewport.zoomToFit`), marquee selection (test which elements intersect the selection rectangle), alignment distribution.
139
+
140
+ ---
141
+
142
+ ### math
143
+
144
+ `registry/substrate/lib/math.ts`
145
+
146
+ Core geometry functions.
147
+
148
+ | Function | Purpose |
149
+ | --- | --- |
150
+ | `rotatePoint(point, center, angleDeg)` | Rotate a point around a centre |
151
+ | `boundsCenter(bounds)` | Centre of a bounding box |
152
+ | `pointInBounds(point, bounds)` | Hit test |
153
+ | `pointInEllipseBounds(point, bounds)` | Ellipse hit test |
154
+ | `boundsOverlap(a, b)` | Overlap test |
155
+ | `normalizeBounds(start, end)` | Normalise a drag rectangle |
156
+ | `screenToWorld(point, camera)` / `worldToScreen(point, camera)` | Coordinate transforms |
157
+ | `clamp(value, min, max)` | Numeric clamp |
158
+ | `distance(a, b)` | Euclidean distance |
159
+
160
+ ---
161
+
162
+ ## Stores
163
+
164
+ ### document-store
165
+
166
+ `registry/substrate/stores/document-store.ts`
167
+
168
+ Central store for the 2D canvas surface – elements, selection, camera, history.
169
+
170
+ ### tool-store
171
+
172
+ `registry/substrate/stores/tool-store.ts`
173
+
174
+ Active tool state. Read by both the surface (to change interaction mode) and the toolbar (to highlight the active tool).
175
+
176
+ ---
177
+
178
+ ## Recommended patterns
179
+
180
+ ### Adding snap-to-grid to an existing surface
181
+
182
+ 1. Import `snapToGrid` from `lib/snap`
183
+ 2. In your drag handler, snap the position before applying it:
184
+ ```ts
185
+ const snapped = snapToGrid(worldPosition, gridSize)
186
+ updateElement(id, { x: snapped.x, y: snapped.y })
187
+ ```
188
+ 3. Optionally add a `Toggle` in the toolbar for enabling/disabling snap
189
+
190
+ ### Zoom-to-fit selection
191
+
192
+ ```ts
193
+ const selected = elements.filter((el) => selectedIds.has(el.id))
194
+ const bounds = boundingBox(selected)
195
+ if (bounds) zoomToFit(expandBounds(bounds, 40))
196
+ ```
197
+
198
+ ### Smart alignment snapping
199
+
200
+ ```ts
201
+ const siblings = elements.filter((el) => !selectedIds.has(el.id))
202
+ const guides = guidesFromBounds(siblings.map(toBounds))
203
+ const { point, snappedGuides } = snapToGuides(dragPos, guides, { threshold: 5 })
204
+ // Render snappedGuides as thin blue lines
205
+ ```
206
+
207
+ ---
208
+
209
+ ## Cross-references
210
+
211
+ | Need | Skill |
212
+ | --- | --- |
213
+ | Zoom controls and status bar | **substrate-scaffold** |
214
+ | Layer list, tree list for structure panels | **substrate-interaction** |
215
+ | Controls inside property panes | **substrate-controls** |
216
+ | Surface architecture and rendering | **substrate-surfaces** |
217
+ | Node editor specifics | **substrate-nodes** |
@@ -0,0 +1,242 @@
1
+ ---
2
+ name: substrate-controls
3
+ description: "Substrate input and selection primitives: number input, slider, number slider, select, toggle, text area, colour picker, easing curve editor, swatch palette, and action controls. Use when building property editors, parameter forms, or any interactive control surface. Triggers on: input, slider, select, toggle, colour picker, swatch, easing, action controls, property editor, form control."
4
+ user-invocable: true
5
+ ---
6
+
7
+ # Substrate controls
8
+
9
+ Controls are the interactive inputs and selectors that live inside panes, toolbars, and action groups. They follow the `data-slot` composition pattern – parent containers (ActionControls, Toolbar, PaneHeader) style children automatically via CSS selectors.
10
+
11
+ ## When to use this skill
12
+
13
+ Use the controls skill when you need to:
14
+
15
+ - Add a property input to a pane (e.g. width, height, rotation, opacity)
16
+ - Build a colour selection UI
17
+ - Create a parameter group with labels and inline controls
18
+ - Wire an easing curve editor into an animation panel
19
+ - Understand how ActionControls composes child controls
20
+
21
+ For the pane containers that hold controls, see **substrate-scaffold**.
22
+ For selection, undo, and clipboard behaviour, see **substrate-interaction**.
23
+
24
+ ---
25
+
26
+ ## The data-slot composition pattern
27
+
28
+ This is the core design pattern of Substrate controls. Parent components style children by matching `data-slot` attributes – no prop drilling required.
29
+
30
+ ### ActionControls (the key container)
31
+
32
+ `registry/substrate/components/action.tsx`
33
+
34
+ A horizontal row that auto-styles its children:
35
+
36
+ | Child slot | Styling applied |
37
+ | ---------------- | ------------------------------------ |
38
+ | `number-input` | Strips border, sets `bg-transparent` |
39
+ | `select-trigger` | Strips border, sets `bg-transparent` |
40
+ | `colour-swatch` | `size-4`, removes border |
41
+ | `slider` | Removes border |
42
+
43
+ **Exports**: `ActionControls`, `ActionLabel`, `ActionSeparator`
44
+
45
+ **Typical use**: Group a label + one or more controls on a single row inside a pane.
46
+
47
+ ```tsx
48
+ <ActionControls>
49
+ <ActionLabel>Width</ActionLabel>
50
+ <NumberInput value={width} onChange={setWidth} min={0} />
51
+ </ActionControls>
52
+ ```
53
+
54
+ ### How it works
55
+
56
+ ActionControls applies Tailwind `**:data-[slot=...]` selectors in its className. When you drop a `NumberInput` (which renders `data-slot="number-input"`) inside, it automatically loses its border and gains transparent background – creating a seamless inline look.
57
+
58
+ The same pattern is used by **Toolbar** (compaction) and **PaneHeader** (auto-positioning HistoryControls).
59
+
60
+ ---
61
+
62
+ ## Components in this domain
63
+
64
+ ### NumberInput
65
+
66
+ `registry/substrate/components/number-input.tsx`
67
+
68
+ Scrub-to-adjust number field. Supports label, suffix, min/max, step/shiftStep, precision, and pointer-lock scrubbing.
69
+
70
+ **Props**: `value`, `onChange`, `onStart`, `label`, `suffix`, `min`, `max`, `step`, `shiftStep`, `precision`, `disabled`
71
+
72
+ **When to use**: Any numeric property – dimensions, position, rotation, opacity, font size. Use `onStart` to push an undo snapshot before the value changes.
73
+
74
+ ---
75
+
76
+ ### Slider
77
+
78
+ `registry/substrate/components/slider.tsx`
79
+
80
+ Range slider built on Radix Slider. Single value or range (two thumbs).
81
+
82
+ **When to use**: Bounded continuous values where a visual track helps – opacity, volume, zoom level.
83
+
84
+ ---
85
+
86
+ ### NumberSlider
87
+
88
+ `registry/substrate/components/number-slider.tsx`
89
+
90
+ Compound of Slider + NumberInput on a single row. The slider fills available space, the input is a fixed `w-18`.
91
+
92
+ **Props**: Same as NumberInput plus all Slider range props.
93
+
94
+ **When to use**: When you want both a slider for quick scrubbing and a precise numeric input – e.g. opacity, blur radius.
95
+
96
+ ---
97
+
98
+ ### Select
99
+
100
+ `registry/substrate/components/select.tsx`
101
+
102
+ Dropdown built on Radix Select. Compact `h-7` trigger.
103
+
104
+ **Exports**: `Select`, `SelectTrigger`, `SelectContent`, `SelectItem`, `SelectGroup`, `SelectLabel`, `SelectSeparator`, `SelectValue`
105
+
106
+ **When to use**: Enum-style choices – blend mode, font family, alignment, export format. Renders `data-slot="select-trigger"` for ActionControls awareness.
107
+
108
+ ---
109
+
110
+ ### Toggle
111
+
112
+ `registry/substrate/components/toggle.tsx`
113
+
114
+ Binary toggle button built on Radix Toggle. `h-7 min-w-7 px-2`. Accent styling when pressed.
115
+
116
+ **When to use**: On/off features – snap to grid, show guides, lock aspect ratio, bold/italic.
117
+
118
+ ---
119
+
120
+ ### TextArea
121
+
122
+ `registry/substrate/components/text-area.tsx`
123
+
124
+ Auto-resizing textarea with `minRows` / `maxRows`. Shares border/bg styling with NumberInput.
125
+
126
+ **When to use**: Multi-line text – element content, descriptions, notes, code snippets.
127
+
128
+ ---
129
+
130
+ ### ColourPicker
131
+
132
+ `registry/substrate/components/colour-picker.tsx`
133
+
134
+ HSV colour picker with hex/RGB inputs and opacity slider. The swatch trigger renders `data-slot="colour-swatch"` for slot-aware composition.
135
+
136
+ **When to use**: Fill, stroke, background, text colour – any colour property.
137
+
138
+ ---
139
+
140
+ ### SwatchPalette
141
+
142
+ `registry/substrate/components/swatch-palette.tsx`
143
+
144
+ Grid of colour swatches. Exports `MATERIAL_COLOURS` (24) and `TAILWIND_COLOURS` (16) presets.
145
+
146
+ **Props**: `colours`, `columns` (default 8), `selected`, `onSelect`
147
+
148
+ **When to use**: Quick colour selection from a predefined palette. Often paired with ColourPicker.
149
+
150
+ ---
151
+
152
+ ### EasingCurveEditor
153
+
154
+ `registry/substrate/components/easing-curve-editor.tsx`
155
+
156
+ Cubic bezier editor with draggable control points and real-time preview.
157
+
158
+ **When to use**: Animation easing configuration. Typically inside a CollapsiblePane in an animation surface's right panel.
159
+
160
+ ---
161
+
162
+ ## Recommended combinations
163
+
164
+ ### Property pane for a shape
165
+
166
+ ```tsx
167
+ <CollapsiblePane>
168
+ <CollapsiblePaneHeader>
169
+ <CollapsiblePaneLabel>Transform</CollapsiblePaneLabel>
170
+ <HistoryControls />
171
+ </CollapsiblePaneHeader>
172
+ <CollapsiblePaneContent className="space-y-1">
173
+ <ActionControls>
174
+ <ActionLabel>X</ActionLabel>
175
+ <NumberInput value={x} onChange={setX} onStart={pushSnapshot} />
176
+ <ActionSeparator />
177
+ <ActionLabel>Y</ActionLabel>
178
+ <NumberInput value={y} onChange={setY} onStart={pushSnapshot} />
179
+ </ActionControls>
180
+ <ActionControls>
181
+ <ActionLabel>W</ActionLabel>
182
+ <NumberInput value={w} onChange={setW} min={0} onStart={pushSnapshot} />
183
+ <ActionSeparator />
184
+ <ActionLabel>H</ActionLabel>
185
+ <NumberInput value={h} onChange={setH} min={0} onStart={pushSnapshot} />
186
+ </ActionControls>
187
+ <ActionControls>
188
+ <ActionLabel>Rotation</ActionLabel>
189
+ <NumberInput
190
+ value={r}
191
+ onChange={setR}
192
+ suffix="°"
193
+ onStart={pushSnapshot}
194
+ />
195
+ </ActionControls>
196
+ </CollapsiblePaneContent>
197
+ </CollapsiblePane>
198
+ ```
199
+
200
+ ### Opacity control
201
+
202
+ ```tsx
203
+ <ActionControls>
204
+ <ActionLabel>Opacity</ActionLabel>
205
+ <NumberSlider
206
+ value={opacity}
207
+ onChange={setOpacity}
208
+ min={0}
209
+ max={100}
210
+ suffix="%"
211
+ />
212
+ </ActionControls>
213
+ ```
214
+
215
+ ### Colour + blend mode row
216
+
217
+ ```tsx
218
+ <ActionControls>
219
+ <ColourPicker value={fill} onChange={setFill} />
220
+ <Select value={blendMode} onValueChange={setBlendMode}>
221
+ <SelectTrigger>
222
+ <SelectValue />
223
+ </SelectTrigger>
224
+ <SelectContent>
225
+ <SelectItem value="normal">Normal</SelectItem>
226
+ <SelectItem value="multiply">Multiply</SelectItem>
227
+ <SelectItem value="screen">Screen</SelectItem>
228
+ </SelectContent>
229
+ </Select>
230
+ </ActionControls>
231
+ ```
232
+
233
+ ---
234
+
235
+ ## Cross-references
236
+
237
+ | Need | Skill |
238
+ | ------------------------------------- | ------------------------- |
239
+ | Pane and panel containers | **substrate-scaffold** |
240
+ | Undo support (onStart → pushSnapshot) | **substrate-interaction** |
241
+ | Animation easing in timeline surfaces | **substrate-surfaces** |
242
+ | Node property panels | **substrate-nodes** |
@@ -0,0 +1,219 @@
1
+ ---
2
+ name: substrate-feedback
3
+ description: "Substrate status, communication, and discovery components: toast, progress bar, badge, empty state, shortcut overlay, command palette, context menu, and popover. Use when showing feedback, status indicators, contextual menus, or discoverability aids. Triggers on: toast, notification, progress, loading, empty state, badge, command palette, context menu, popover, shortcut help, keyboard shortcuts display."
4
+ user-invocable: true
5
+ ---
6
+
7
+ # Substrate feedback
8
+
9
+ Feedback components handle communication with the user – notifications, status indicators, contextual actions, and discoverability features.
10
+
11
+ ## When to use this skill
12
+
13
+ Use the feedback skill when you need to:
14
+
15
+ - Show success/error/undo notifications
16
+ - Display loading or progress
17
+ - Show empty states when there's no content
18
+ - Add a command palette (⌘K) for quick actions
19
+ - Build right-click context menus
20
+ - Show a keyboard shortcuts overlay
21
+ - Use popovers for inline detail views
22
+ - Add status badges to elements
23
+
24
+ For structural layout (toolbar, panels), see **substrate-scaffold**.
25
+ For interactive controls (inputs, sliders), see **substrate-controls**.
26
+
27
+ ---
28
+
29
+ ## Components
30
+
31
+ ### Toast
32
+
33
+ `registry/substrate/components/toast.tsx`
34
+
35
+ Thin wrapper around Sonner. Exports a `toast` function with convenience methods.
36
+
37
+ | Method | Duration | Use case |
38
+ | --- | --- | --- |
39
+ | `toast(message)` | 4s | General notification |
40
+ | `toast.success(message)` | 3s | Confirmation (saved, exported) |
41
+ | `toast.error(message)` | 6s | Error with longer reading time |
42
+ | `toast.warning(message)` | 5s | Caution |
43
+ | `toast.info(message)` | 4s | Informational |
44
+ | `toast.undo(message, onUndo)` | 6s | Action with undo option |
45
+ | `toast.dismiss(id?)` | — | Programmatic dismiss |
46
+
47
+ **When to use**: After async operations (save, export, publish), destructive actions (delete with undo), errors. Use `toast.undo` for delete operations to give users a recovery path.
48
+
49
+ ```ts
50
+ toast.undo("Layer deleted", () => restoreLayer(id))
51
+ ```
52
+
53
+ ---
54
+
55
+ ### ProgressBar
56
+
57
+ `registry/substrate/components/progress-bar.tsx`
58
+
59
+ Thin bar (h-1.5) with determinate and indeterminate modes.
60
+
61
+ **Props**: `value`, `max` (default 100), `indeterminate`
62
+
63
+ **When to use**: File uploads, export progress, batch operations. Use `indeterminate` when duration is unknown. Place at the top of a panel or inside a pane.
64
+
65
+ ---
66
+
67
+ ### Badge
68
+
69
+ `registry/substrate/components/badge.tsx`
70
+
71
+ Small label with 4 variants: `default`, `secondary`, `outline`, `muted`.
72
+
73
+ **When to use**: Element type indicators (in layer lists), node categories, status labels (draft/published), keyboard shortcut hints.
74
+
75
+ ---
76
+
77
+ ### EmptyState
78
+
79
+ `registry/substrate/components/empty-state.tsx`
80
+
81
+ Centred placeholder for empty panels or lists.
82
+
83
+ **Exports**: `EmptyState`, `EmptyStateIcon`, `EmptyStateTitle`, `EmptyStateDescription`, `EmptyStateAction`
84
+
85
+ **When to use**: Empty layer lists, empty canvas, no search results, first-run experience.
86
+
87
+ ```tsx
88
+ <EmptyState>
89
+ <EmptyStateIcon><RiStackLine /></EmptyStateIcon>
90
+ <EmptyStateTitle>No layers yet</EmptyStateTitle>
91
+ <EmptyStateDescription>
92
+ Draw a shape on the canvas to get started
93
+ </EmptyStateDescription>
94
+ </EmptyState>
95
+ ```
96
+
97
+ ---
98
+
99
+ ### CommandPalette
100
+
101
+ `registry/substrate/components/command-palette.tsx`
102
+
103
+ ⌘K-triggered command launcher using cmdk + Radix Dialog.
104
+
105
+ **Exports**: `CommandPalette`, `CommandInput`, `CommandList`, `CommandEmpty`, `CommandGroup`, `CommandItem`, `CommandShortcut`, `CommandSeparator`
106
+
107
+ **When to use**: Quick access to any action – tool switching, file operations, element creation, settings. Especially useful in node editors ("Add Node → Blur") and complex apps with many features.
108
+
109
+ **Recommended groups**: Tools, Actions, Create, Recent, Settings.
110
+
111
+ ---
112
+
113
+ ### ContextMenu
114
+
115
+ `registry/substrate/components/context-menu.tsx`
116
+
117
+ Right-click menu built on Radix ContextMenu. Supports submenus.
118
+
119
+ **Exports**: `ContextMenu`, `ContextMenuTrigger`, `ContextMenuContent`, `ContextMenuItem`, `ContextMenuSeparator`, `ContextMenuLabel`, `ContextMenuShortcut`, `ContextMenuSub`, `ContextMenuSubTrigger`, `ContextMenuSubContent`
120
+
121
+ **When to use**: Right-click on canvas elements, layer items, node cards, tabs. Show contextual actions (copy, paste, delete, group, bring to front) with keyboard shortcut hints.
122
+
123
+ **Connects to**: `createKeyboardShortcuts` (substrate-interaction) – use `formatShortcut` to render shortcut hints in menu items.
124
+
125
+ ---
126
+
127
+ ### ShortcutOverlay
128
+
129
+ `registry/substrate/components/shortcut-overlay.tsx`
130
+
131
+ Full-screen overlay listing all keyboard shortcuts, triggered by `?` key.
132
+
133
+ **Props**: `groups` – array of `{ label, shortcuts: { label, keys }[] }`
134
+
135
+ **When to use**: Discoverability aid. One per app. Pass the same shortcut groups registered with `createKeyboardShortcuts`.
136
+
137
+ ---
138
+
139
+ ### Popover
140
+
141
+ `registry/substrate/components/popover.tsx`
142
+
143
+ Floating panel built on Radix Popover. Optional arrow.
144
+
145
+ **Exports**: `Popover`, `PopoverTrigger`, `PopoverContent`, `PopoverClose`
146
+
147
+ **When to use**: Inline details, settings, colour pickers, property editors that don't warrant a full panel.
148
+
149
+ ---
150
+
151
+ ## Recommended patterns
152
+
153
+ ### Delete with undo
154
+
155
+ ```ts
156
+ function handleDelete(ids: string[]) {
157
+ const snapshot = getElements(ids)
158
+ pushSnapshot()
159
+ removeElements(ids)
160
+ toast.undo(`${ids.length} element${ids.length > 1 ? "s" : ""} deleted`, () => {
161
+ restoreElements(snapshot)
162
+ })
163
+ }
164
+ ```
165
+
166
+ ### Context menu with shortcuts
167
+
168
+ ```tsx
169
+ <ContextMenu>
170
+ <ContextMenuTrigger>{children}</ContextMenuTrigger>
171
+ <ContextMenuContent>
172
+ <ContextMenuItem onSelect={copy}>
173
+ Copy <ContextMenuShortcut>⌘C</ContextMenuShortcut>
174
+ </ContextMenuItem>
175
+ <ContextMenuItem onSelect={paste}>
176
+ Paste <ContextMenuShortcut>⌘V</ContextMenuShortcut>
177
+ </ContextMenuItem>
178
+ <ContextMenuSeparator />
179
+ <ContextMenuSub>
180
+ <ContextMenuSubTrigger>Arrange</ContextMenuSubTrigger>
181
+ <ContextMenuSubContent>
182
+ <ContextMenuItem onSelect={bringToFront}>Bring to front</ContextMenuItem>
183
+ <ContextMenuItem onSelect={sendToBack}>Send to back</ContextMenuItem>
184
+ </ContextMenuSubContent>
185
+ </ContextMenuSub>
186
+ </ContextMenuContent>
187
+ </ContextMenu>
188
+ ```
189
+
190
+ ### Command palette with tool switching
191
+
192
+ ```tsx
193
+ <CommandPalette>
194
+ <CommandInput placeholder="Search actions…" />
195
+ <CommandList>
196
+ <CommandGroup heading="Tools">
197
+ <CommandItem onSelect={() => setTool("select")}>
198
+ <RiCursorLine /> Select
199
+ <CommandShortcut>V</CommandShortcut>
200
+ </CommandItem>
201
+ <CommandItem onSelect={() => setTool("rectangle")}>
202
+ <RiRectangleLine /> Rectangle
203
+ <CommandShortcut>R</CommandShortcut>
204
+ </CommandItem>
205
+ </CommandGroup>
206
+ </CommandList>
207
+ </CommandPalette>
208
+ ```
209
+
210
+ ---
211
+
212
+ ## Cross-references
213
+
214
+ | Need | Skill |
215
+ | --- | --- |
216
+ | Keyboard shortcut registration | **substrate-interaction** |
217
+ | Toolbar and status bar for badges | **substrate-scaffold** |
218
+ | Node editor command palette groups | **substrate-nodes** |
219
+ | Empty state in layer/tree lists | **substrate-interaction** |