create-substrate 0.1.6 → 0.2.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.
- package/LICENSE +21 -0
- package/README.md +11 -4
- package/dist/index.js +25 -7
- package/dist/index.js.map +1 -1
- package/dist/prompts.d.ts +0 -2
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +6 -50
- package/dist/prompts.js.map +1 -1
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +82 -80
- package/dist/scaffold.js.map +1 -1
- package/dist/surfaces/3d-scene.d.ts.map +1 -1
- package/dist/surfaces/3d-scene.js +43 -74
- package/dist/surfaces/3d-scene.js.map +1 -1
- package/dist/surfaces/canvas-2d.d.ts.map +1 -1
- package/dist/surfaces/canvas-2d.js +7 -10
- package/dist/surfaces/canvas-2d.js.map +1 -1
- package/dist/surfaces/data-vis.d.ts.map +1 -1
- package/dist/surfaces/data-vis.js +37 -143
- package/dist/surfaces/data-vis.js.map +1 -1
- package/dist/surfaces/image-gen.d.ts.map +1 -1
- package/dist/surfaces/image-gen.js +15 -35
- package/dist/surfaces/image-gen.js.map +1 -1
- package/dist/surfaces/node-editor.js +1 -1
- package/dist/utils/detect-pm.d.ts +2 -0
- package/dist/utils/detect-pm.d.ts.map +1 -1
- package/dist/utils/detect-pm.js +9 -0
- package/dist/utils/detect-pm.js.map +1 -1
- package/dist/utils/package-info.d.ts +5 -0
- package/dist/utils/package-info.d.ts.map +1 -0
- package/dist/utils/package-info.js +17 -0
- package/dist/utils/package-info.js.map +1 -0
- package/package.json +4 -3
- package/skills/3d-scene/SKILL.md +2 -1
- package/skills/animation/SKILL.md +2 -1
- package/skills/canvas-2d/SKILL.md +67 -5
- package/skills/composing-panels/SKILL.md +1 -4
- package/skills/data-visualisation/SKILL.md +2 -1
- package/skills/image-generation/SKILL.md +2 -1
- package/skills/install-components/SKILL.md +22 -16
- package/skills/scaffold-playground/SKILL.md +39 -19
- package/skills/substrate-canvas/SKILL.md +5 -0
- package/skills/substrate-controls/SKILL.md +57 -2
- package/skills/substrate-interaction/SKILL.md +19 -10
- package/skills/substrate-scaffold/SKILL.md +4 -4
- package/skills/theming/SKILL.md +8 -2
- package/skills/wire-interactions/SKILL.md +6 -3
- package/templates/agents-section.md +41 -0
|
@@ -45,7 +45,7 @@ function handleMove(id: string, x: number, y: number) {
|
|
|
45
45
|
}
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
**Connects to**: `
|
|
48
|
+
**Connects to**: `NumberInput.onStart` (push snapshot before scrub), `createKeyboardShortcuts` (⌘Z / ⌘⇧Z). The 2D canvas does not use this hook: `useDocumentStore` has its own history (`pushSnapshot`, `undo`, `redo`) that covers elements and their order, not the camera, so pan and zoom survive an undo. Call `pushSnapshot()` before every mutation you want to be undoable, including property edits.
|
|
49
49
|
|
|
50
50
|
---
|
|
51
51
|
|
|
@@ -139,7 +139,7 @@ formatShortcut({ key: "z", meta: true, shift: true }) // → "⌘+Shift+Z"
|
|
|
139
139
|
|
|
140
140
|
Undo/redo button pair. Renders `data-slot="history-controls"` for auto-positioning in PaneHeader and Toolbar.
|
|
141
141
|
|
|
142
|
-
**Props**: `
|
|
142
|
+
**Props**: `size` (`"sm"` or `"md"`). It takes no undo callbacks: it reads `past`/`future` and calls `undo`/`redo` on `useDocumentStore`, so it works with the canvas store out of the box. For any other undo stack (`useUndoable`), build your own pair of buttons from `canUndo`/`undo`/`canRedo`/`redo`.
|
|
143
143
|
|
|
144
144
|
**Typical placement**:
|
|
145
145
|
- Inside `PaneHeader` – auto-positioned to the right via `**:data-[slot=history-controls]:ml-auto`
|
|
@@ -157,7 +157,9 @@ Flat layer panel with selection, visibility, lock, rename, and reorder.
|
|
|
157
157
|
|
|
158
158
|
**Features**:
|
|
159
159
|
- `LayerItem` supports `selected`, `hovered`, `depth` props
|
|
160
|
-
- `LayerName` enables double-click inline rename
|
|
160
|
+
- `LayerName` takes the name as `children` and enables double-click inline rename via `onRename`
|
|
161
|
+
- `LayerVisibilityToggle` and `LayerLockToggle` are controlled: `visible`/`onVisibleChange` and `locked`/`onLockedChange`
|
|
162
|
+
- Sorting is not built in: spread `useDragReorder`'s `getDragItemProps` on each `LayerItem`
|
|
161
163
|
- `LayerActions` are hidden by default, visible on row hover
|
|
162
164
|
- `LayerVisibilityToggle` and `LayerLockToggle` use eye/lock icons
|
|
163
165
|
|
|
@@ -199,8 +201,8 @@ Hierarchical tree with expand/collapse, depth indentation, and icons.
|
|
|
199
201
|
│ → apply to store │
|
|
200
202
|
├─────────────────────────────────────────────────┤
|
|
201
203
|
│ HistoryControls (in PaneHeader or Toolbar) │
|
|
202
|
-
│ → reads
|
|
203
|
-
│ → calls
|
|
204
|
+
│ → reads past/future from useDocumentStore │
|
|
205
|
+
│ → calls the store's undo/redo │
|
|
204
206
|
├─────────────────────────────────────────────────┤
|
|
205
207
|
│ NumberInput.onStart │
|
|
206
208
|
│ → pushSnapshot before scrub begins │
|
|
@@ -231,7 +233,14 @@ Hierarchical tree with expand/collapse, depth indentation, and icons.
|
|
|
231
233
|
```tsx
|
|
232
234
|
function LayerPanel() {
|
|
233
235
|
const { selectedIds, select, isSelected } = useSelection({ items: elementIds })
|
|
234
|
-
const
|
|
236
|
+
const setElementOrder = useDocumentStore((s) => s.setElementOrder)
|
|
237
|
+
const { getDragItemProps } = useDragReorder({
|
|
238
|
+
items: elements,
|
|
239
|
+
onReorder: (next) => {
|
|
240
|
+
pushSnapshot()
|
|
241
|
+
setElementOrder(next.map((el) => el.id))
|
|
242
|
+
},
|
|
243
|
+
})
|
|
235
244
|
|
|
236
245
|
return (
|
|
237
246
|
<LayerList>
|
|
@@ -244,21 +253,21 @@ function LayerPanel() {
|
|
|
244
253
|
{...getDragItemProps(i)}
|
|
245
254
|
>
|
|
246
255
|
<LayerIcon colour={el.fill?.color} />
|
|
247
|
-
<LayerName
|
|
256
|
+
<LayerName onRename={(name) => updateElement(el.id, { name })}>{el.name}</LayerName>
|
|
248
257
|
<LayerActions>
|
|
249
258
|
<LayerVisibilityToggle
|
|
250
259
|
visible={el.visible}
|
|
251
|
-
|
|
260
|
+
onVisibleChange={(visible) => updateElement(el.id, { visible })}
|
|
252
261
|
/>
|
|
253
262
|
<LayerLockToggle
|
|
254
263
|
locked={el.locked}
|
|
255
|
-
|
|
264
|
+
onLockedChange={(locked) => updateElement(el.id, { locked })}
|
|
256
265
|
/>
|
|
257
266
|
</LayerActions>
|
|
258
267
|
</LayerItem>
|
|
259
268
|
</ContextMenuTrigger>
|
|
260
269
|
<ContextMenuContent>
|
|
261
|
-
<ContextMenuItem onSelect={() =>
|
|
270
|
+
<ContextMenuItem onSelect={() => duplicateElements([el.id])}>
|
|
262
271
|
Duplicate <ContextMenuShortcut>⌘D</ContextMenuShortcut>
|
|
263
272
|
</ContextMenuItem>
|
|
264
273
|
<ContextMenuItem onSelect={() => deleteElement(el.id)}>
|
|
@@ -39,9 +39,9 @@ Floating bottom bar. Renders tool buttons, separators, and groups. Uses `data-sl
|
|
|
39
39
|
| `slider` / `slider-track` | h-6 |
|
|
40
40
|
| `history-controls` | gap-0 |
|
|
41
41
|
|
|
42
|
-
**Exports**: `Toolbar`, `
|
|
42
|
+
**Exports**: `Toolbar`, `ToolbarToggleGroup`, `ToolbarToggle`, `ToolbarSeparator`, `ToolbarDropdown`
|
|
43
43
|
|
|
44
|
-
**Typical use**: One per playground, positioned at the bottom.
|
|
44
|
+
**Typical use**: One per playground, positioned at the bottom. Put the tool buttons in a `ToolbarToggleGroup` of `ToolbarToggle`s and connect it to `useToolStore` for active-tool state.
|
|
45
45
|
|
|
46
46
|
**Related**: substrate-controls (for inputs inside the toolbar), substrate-interaction (for keyboard shortcuts bound to tools)
|
|
47
47
|
|
|
@@ -181,11 +181,11 @@ Breadcrumbs show the path into nested node groups. Right panel shows properties
|
|
|
181
181
|
<div className="flex h-screen flex-col">
|
|
182
182
|
<DocumentTabList>{/* tabs */}</DocumentTabList>
|
|
183
183
|
<div className="flex flex-1 overflow-hidden">
|
|
184
|
-
<Panel
|
|
184
|
+
<Panel position="left">{/* layers/tree */}</Panel>
|
|
185
185
|
<div className="relative flex-1">
|
|
186
186
|
<Surface />{/* central area */}
|
|
187
187
|
</div>
|
|
188
|
-
<Panel
|
|
188
|
+
<Panel position="right">{/* property panes */}</Panel>
|
|
189
189
|
</div>
|
|
190
190
|
<StatusBar>{/* metadata */}</StatusBar>
|
|
191
191
|
<Toolbar>{/* tools */}</Toolbar>
|
package/skills/theming/SKILL.md
CHANGED
|
@@ -113,9 +113,15 @@ const CURSOR_MAP: Record<string, string> = {
|
|
|
113
113
|
}
|
|
114
114
|
```
|
|
115
115
|
|
|
116
|
-
## 5.
|
|
116
|
+
## 5. Light and dark mode
|
|
117
117
|
|
|
118
|
-
Substrate
|
|
118
|
+
Every Substrate component works in both themes. Colours come from tokens, and the theme is chosen by a `.dark` class on `<html>`: light values live in `:root`, dark values in `.dark`.
|
|
119
|
+
|
|
120
|
+
Projects made with `create-substrate` follow the system theme through a small script in `index.html`. To force a theme, remove that script and either set `class="dark"` on `<html>` or leave it off. To add a switch, toggle that class yourself (or use `next-themes` in a Next.js app).
|
|
121
|
+
|
|
122
|
+
Canvas-drawn parts follow the theme too: the design canvas reads `--canvas` and `--canvas-foreground`, the easing curve editor draws in its text colour, and both repaint when the theme changes. In your own canvas code, read colours with `getComputedStyle` at draw time instead of hardcoding them, and use mid-tones for defaults that must read on both backgrounds.
|
|
123
|
+
|
|
124
|
+
When writing a component, use tokens (`bg-panel`, `text-muted-foreground`, `bg-foreground/10`) and check it in both themes. Avoid `bg-background` for a control's track or well: it matches the page in light mode and disappears.
|
|
119
125
|
|
|
120
126
|
## Related reference skills
|
|
121
127
|
|
|
@@ -39,6 +39,7 @@ Keyboard events (DOM)
|
|
|
39
39
|
| `creating` | Drag with shape tool on empty canvas | Preview element dimensions | Commit new element to store |
|
|
40
40
|
| `moving` | Drag on selected element | Update element positions | Commit positions |
|
|
41
41
|
| `resizing` | Drag on resize handle | Update element bounds | Commit bounds |
|
|
42
|
+
| `rotating` | Drag on the rotate handle above a single selection | Turn the element about its centre (hold Shift to snap to 15°) | Commit rotation |
|
|
42
43
|
| `marquee_selecting` | Drag on empty canvas with select tool | Draw selection rectangle | Select contained elements |
|
|
43
44
|
|
|
44
45
|
### Pointer down logic
|
|
@@ -66,7 +67,8 @@ Keyboard events (DOM)
|
|
|
66
67
|
- "pan": update camera
|
|
67
68
|
- "create": preview element (stored on drag ref, rendered by renderer)
|
|
68
69
|
- "move": update element positions (delta from start)
|
|
69
|
-
- "resize": update element bounds
|
|
70
|
+
- "resize": update element bounds with `resizeBounds`, which respects the handle and the element's rotation
|
|
71
|
+
- "rotate": set `rotation` from the pointer's angle around the element centre
|
|
70
72
|
- "marquee": update selection rectangle
|
|
71
73
|
5. Call markDirty() to trigger repaint
|
|
72
74
|
```
|
|
@@ -77,7 +79,7 @@ Keyboard events (DOM)
|
|
|
77
79
|
1. End drag (useCanvasDrag.endDrag)
|
|
78
80
|
2. Based on drag type:
|
|
79
81
|
- "create": push snapshot, add element to store, select it, reset tool to select
|
|
80
|
-
- "move"/"resize": push snapshot
|
|
82
|
+
- "move"/"resize"/"rotate": nothing to push; the snapshot was taken when the drag first moved, so a click without a drag leaves no undo step
|
|
81
83
|
- "marquee": select all elements within the marquee bounds
|
|
82
84
|
- "pan": nothing to commit
|
|
83
85
|
```
|
|
@@ -104,12 +106,13 @@ Always convert pointer event coordinates to world space before doing hit-testing
|
|
|
104
106
|
|
|
105
107
|
```ts
|
|
106
108
|
interface DragInfo {
|
|
107
|
-
type: DragType // "create" | "move" | "resize" | "marquee" | "pan"
|
|
109
|
+
type: DragType // "create" | "move" | "resize" | "rotate" | "marquee" | "pan"
|
|
108
110
|
startWorld: Point // Where the drag began (world space)
|
|
109
111
|
startScreen: Point // Where the drag began (screen space)
|
|
110
112
|
currentWorld: Point // Current position (updated on every move)
|
|
111
113
|
currentScreen: Point
|
|
112
114
|
handle?: HandlePosition // Which resize handle, if resizing
|
|
115
|
+
rotate?: { id, center, startAngle, startRotation } // Set for a rotate drag
|
|
113
116
|
elementStartPositions?: Record<string, { x, y, width, height }> // Snapshot for move/resize
|
|
114
117
|
}
|
|
115
118
|
```
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Substrate
|
|
2
|
+
|
|
3
|
+
This project uses [Substrate](https://substrate.georgedrury.co.uk), a set of primitives for building creative tools: scaffold (toolbar, panels, panes, shortcuts), canvas and node helpers, and config-driven controls.
|
|
4
|
+
|
|
5
|
+
## Use Substrate components first
|
|
6
|
+
|
|
7
|
+
Components are installed into the project with the Substrate CLI, which wraps shadcn. Never run `npx shadcn@latest add` for them directly.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx substrate-kit add panel slider toolbar # install by name, in one call
|
|
11
|
+
npx substrate-kit add # interactive picker
|
|
12
|
+
npx substrate-kit docs <component> # props, CSS variables, usage
|
|
13
|
+
npx substrate-kit docs <component> --open # open its docs page in the browser
|
|
14
|
+
npx substrate-kit docs --list # every component
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Before writing a custom slider, input, panel, timeline or colour picker, check `docs --list`. If the Substrate MCP server is connected, use `search_components`, `get_component` and `get_docs` instead of the commands above, and `install_components` to install.
|
|
18
|
+
|
|
19
|
+
## Tune values with Controls
|
|
20
|
+
|
|
21
|
+
For parameters you want to adjust live (sizes, colours, springs, toggles), describe them as an object and render them with `Controls` instead of building a form:
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
const card = useControls("Card", {
|
|
25
|
+
radius: [16, 0, 48], // [default, min, max, step?] slider
|
|
26
|
+
colour: "#a78bfa", // colour picker
|
|
27
|
+
visible: true, // toggle
|
|
28
|
+
motion: { type: "spring", visualDuration: 0.3, bounce: 0.2 },
|
|
29
|
+
})
|
|
30
|
+
// <Panel><Controls /></Panel> renders every registered panel
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The returned values keep the shape of the config. Panels have versions, reset, keyboard shortcuts (`shortcuts` option) and a copy button that gives an agent the tuned values to paste back in as the new defaults. Install with `npx substrate-kit add controls`, and read `docs controls` for every control type and option. Controls and the timeline are built on [DialKit](https://github.com/joshpuckett/dialkit) (MIT, by Josh Puckett), which installs as the `dialkit` dependency.
|
|
34
|
+
|
|
35
|
+
## Tune animation with the timeline
|
|
36
|
+
|
|
37
|
+
For motion, define clips with `useControlsTimeline` and bind `clip.current` to your UI. `ControlsTimeline` is the editor: scrub, move and resize clips, edit curves, and copy the tuned timing back as defaults. Install with `npx substrate-kit add controls-timeline`, and read `docs controls-timeline` for clip types, looping and zoom.
|
|
38
|
+
|
|
39
|
+
## Skills
|
|
40
|
+
|
|
41
|
+
Substrate skills are in `.claude/skills/` and `.agents/skills/`. Surface guides explain how to build each kind of tool (`scaffold-playground`, `canvas-2d`, `3d-scene`, `animation`, `image-generation`, `data-visualisation`) and workflows (`composing-panels`, `wire-interactions`, `create-custom-tool`, `theming`). Component references (`substrate-scaffold`, `substrate-controls`, `substrate-canvas`, `substrate-nodes`, `substrate-feedback`, `substrate-interaction`) say what to use. `install-components` lists everything available.
|