@substrate-kit/cli 0.1.2 → 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.
Files changed (146) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/LICENSE +21 -0
  3. package/README.md +25 -8
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +10 -41
  6. package/dist/cli.js.map +1 -1
  7. package/dist/commands/add.d.ts +1 -0
  8. package/dist/commands/add.d.ts.map +1 -1
  9. package/dist/commands/add.js +51 -109
  10. package/dist/commands/add.js.map +1 -1
  11. package/dist/commands/docs.d.ts +4 -2
  12. package/dist/commands/docs.d.ts.map +1 -1
  13. package/dist/commands/docs.js +33 -54
  14. package/dist/commands/docs.js.map +1 -1
  15. package/dist/commands/init.d.ts.map +1 -1
  16. package/dist/commands/init.js +158 -122
  17. package/dist/commands/init.js.map +1 -1
  18. package/dist/index.js +0 -0
  19. package/dist/mcp/server.d.ts.map +1 -1
  20. package/dist/mcp/server.js +8 -7
  21. package/dist/mcp/server.js.map +1 -1
  22. package/dist/mcp/tools/get-component.d.ts +1 -1
  23. package/dist/mcp/tools/get-component.d.ts.map +1 -1
  24. package/dist/mcp/tools/get-component.js +10 -6
  25. package/dist/mcp/tools/get-component.js.map +1 -1
  26. package/dist/mcp/tools/get-docs.d.ts +17 -0
  27. package/dist/mcp/tools/get-docs.d.ts.map +1 -0
  28. package/dist/mcp/tools/get-docs.js +23 -0
  29. package/dist/mcp/tools/get-docs.js.map +1 -0
  30. package/dist/mcp/tools/install.d.ts +2 -1
  31. package/dist/mcp/tools/install.d.ts.map +1 -1
  32. package/dist/mcp/tools/install.js +18 -27
  33. package/dist/mcp/tools/install.js.map +1 -1
  34. package/dist/mcp/tools/search.d.ts +1 -1
  35. package/dist/mcp/tools/search.d.ts.map +1 -1
  36. package/dist/mcp/tools/search.js +2 -4
  37. package/dist/mcp/tools/search.js.map +1 -1
  38. package/dist/{mcp/types.d.ts → registry.d.ts} +6 -1
  39. package/dist/registry.d.ts.map +1 -0
  40. package/dist/registry.js +48 -0
  41. package/dist/registry.js.map +1 -0
  42. package/dist/utils/agents-file.d.ts +4 -0
  43. package/dist/utils/agents-file.d.ts.map +1 -0
  44. package/dist/utils/agents-file.js +36 -0
  45. package/dist/utils/agents-file.js.map +1 -0
  46. package/dist/utils/docs.d.ts +7 -0
  47. package/dist/utils/docs.d.ts.map +1 -0
  48. package/dist/utils/docs.js +28 -0
  49. package/dist/utils/docs.js.map +1 -0
  50. package/dist/utils/shadcn.d.ts +6 -1
  51. package/dist/utils/shadcn.d.ts.map +1 -1
  52. package/dist/utils/shadcn.js +25 -12
  53. package/dist/utils/shadcn.js.map +1 -1
  54. package/dist/utils/version.d.ts +3 -0
  55. package/dist/utils/version.d.ts.map +1 -0
  56. package/dist/utils/version.js +8 -0
  57. package/dist/utils/version.js.map +1 -0
  58. package/docs/components/action.mdx +30 -0
  59. package/docs/components/alignment-controls.mdx +30 -0
  60. package/docs/components/badge.mdx +22 -0
  61. package/docs/components/breadcrumbs.mdx +25 -0
  62. package/docs/components/collapsible-pane.mdx +35 -0
  63. package/docs/components/colour-picker.mdx +66 -0
  64. package/docs/components/command-palette.mdx +36 -0
  65. package/docs/components/context-menu.mdx +31 -0
  66. package/docs/components/controls-timeline.mdx +107 -0
  67. package/docs/components/controls.mdx +168 -0
  68. package/docs/components/design-canvas.mdx +51 -0
  69. package/docs/components/document-tabs.mdx +25 -0
  70. package/docs/components/easing-curve-editor.mdx +85 -0
  71. package/docs/components/empty-state.mdx +26 -0
  72. package/docs/components/gallery.mdx +60 -0
  73. package/docs/components/history-controls.mdx +47 -0
  74. package/docs/components/image-picker.mdx +50 -0
  75. package/docs/components/layer-list.mdx +36 -0
  76. package/docs/components/node.mdx +85 -0
  77. package/docs/components/number-input.mdx +44 -0
  78. package/docs/components/number-slider.mdx +32 -0
  79. package/docs/components/pane.mdx +39 -0
  80. package/docs/components/panel.mdx +28 -0
  81. package/docs/components/popover.mdx +32 -0
  82. package/docs/components/progress-bar.mdx +24 -0
  83. package/docs/components/resizer.mdx +24 -0
  84. package/docs/components/select.mdx +31 -0
  85. package/docs/components/shortcut-overlay.mdx +43 -0
  86. package/docs/components/slider.mdx +20 -0
  87. package/docs/components/spring-editor.mdx +85 -0
  88. package/docs/components/status-bar.mdx +42 -0
  89. package/docs/components/surface.mdx +47 -0
  90. package/docs/components/swatch-palette.mdx +36 -0
  91. package/docs/components/text-area.mdx +26 -0
  92. package/docs/components/timeline.mdx +96 -0
  93. package/docs/components/toast.mdx +42 -0
  94. package/docs/components/toggle-group.mdx +40 -0
  95. package/docs/components/toggle.mdx +27 -0
  96. package/docs/components/toolbar.mdx +24 -0
  97. package/docs/components/tree-list.mdx +46 -0
  98. package/docs/components/xy-pad.mdx +49 -0
  99. package/docs/hooks/use-clipboard.mdx +70 -0
  100. package/docs/hooks/use-drag-reorder.mdx +60 -0
  101. package/docs/hooks/use-selection.mdx +68 -0
  102. package/docs/hooks/use-undoable.mdx +83 -0
  103. package/docs/hooks/use-viewport.mdx +75 -0
  104. package/docs/utilities/bounding-box.mdx +76 -0
  105. package/docs/utilities/keyboard-shortcuts.mdx +64 -0
  106. package/docs/utilities/snap.mdx +73 -0
  107. package/package.json +6 -4
  108. package/registry.json +274 -6
  109. package/skills/3d-scene/SKILL.md +2 -1
  110. package/skills/animation/SKILL.md +2 -1
  111. package/skills/canvas-2d/SKILL.md +67 -5
  112. package/skills/composing-panels/SKILL.md +1 -4
  113. package/skills/data-visualisation/SKILL.md +2 -1
  114. package/skills/image-generation/SKILL.md +2 -1
  115. package/skills/install-components/SKILL.md +22 -16
  116. package/skills/scaffold-playground/SKILL.md +39 -19
  117. package/skills/substrate-canvas/SKILL.md +5 -0
  118. package/skills/substrate-controls/SKILL.md +57 -2
  119. package/skills/substrate-interaction/SKILL.md +19 -10
  120. package/skills/substrate-scaffold/SKILL.md +4 -4
  121. package/skills/theming/SKILL.md +8 -2
  122. package/skills/wire-interactions/SKILL.md +6 -3
  123. package/templates/agents-section.md +41 -0
  124. package/dist/commands/browse.d.ts +0 -2
  125. package/dist/commands/browse.d.ts.map +0 -1
  126. package/dist/commands/browse.js +0 -25
  127. package/dist/commands/browse.js.map +0 -1
  128. package/dist/commands/create.d.ts +0 -9
  129. package/dist/commands/create.d.ts.map +0 -1
  130. package/dist/commands/create.js +0 -27
  131. package/dist/commands/create.js.map +0 -1
  132. package/dist/commands/variables.d.ts +0 -4
  133. package/dist/commands/variables.d.ts.map +0 -1
  134. package/dist/commands/variables.js +0 -111
  135. package/dist/commands/variables.js.map +0 -1
  136. package/dist/mcp/registry.d.ts +0 -3
  137. package/dist/mcp/registry.d.ts.map +0 -1
  138. package/dist/mcp/registry.js +0 -43
  139. package/dist/mcp/registry.js.map +0 -1
  140. package/dist/mcp/tools/theme.d.ts +0 -10
  141. package/dist/mcp/tools/theme.d.ts.map +0 -1
  142. package/dist/mcp/tools/theme.js +0 -37
  143. package/dist/mcp/tools/theme.js.map +0 -1
  144. package/dist/mcp/types.d.ts.map +0 -1
  145. package/dist/mcp/types.js +0 -2
  146. package/dist/mcp/types.js.map +0 -1
@@ -0,0 +1,76 @@
1
+ ---
2
+ title: Bounding box
3
+ description: Compute union bounds, intersections, and containment checks.
4
+ ---
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ npx shadcn@latest add https://substrate.georgedrury.co.uk/r/bounding-box
10
+ ```
11
+
12
+ ## Usage
13
+
14
+ ```ts
15
+ import {
16
+ boundingBox,
17
+ expandBounds,
18
+ containsPoint,
19
+ intersectBounds,
20
+ } from "@/lib/substrate/bounding-box"
21
+
22
+ // Union of multiple element bounds
23
+ const union = boundingBox([
24
+ { x: 0, y: 0, width: 100, height: 50 },
25
+ { x: 80, y: 30, width: 60, height: 40 },
26
+ ])
27
+ // → { x: 0, y: 0, width: 140, height: 70 }
28
+
29
+ // Add padding
30
+ const padded = expandBounds(union!, 20)
31
+ // → { x: -20, y: -20, width: 180, height: 110 }
32
+
33
+ // Hit testing
34
+ containsPoint(union!, { x: 50, y: 25 }) // → true
35
+
36
+ // Intersection
37
+ intersectBounds(
38
+ { x: 0, y: 0, width: 100, height: 100 },
39
+ { x: 50, y: 50, width: 100, height: 100 },
40
+ )
41
+ // → { x: 50, y: 50, width: 50, height: 50 }
42
+ ```
43
+
44
+ ## Functions
45
+
46
+ ### `boundingBox(items)`
47
+
48
+ Computes the union bounding box of an array of `Bounds`. Returns `null` if the array is empty.
49
+
50
+ ### `boundingBoxFromPoints(points)`
51
+
52
+ Computes the bounding box from an array of `Point` values. Returns `null` if the array is empty.
53
+
54
+ ### `expandBounds(bounds, padding)`
55
+
56
+ Expands a bounding box by `padding` pixels on every side. Negative values contract.
57
+
58
+ ### `expandBoundsBy(bounds, { top?, right?, bottom?, left? })`
59
+
60
+ Expands with independent padding per side.
61
+
62
+ ### `containsPoint(bounds, point)`
63
+
64
+ Returns `true` if the point lies inside the bounds.
65
+
66
+ ### `containsBounds(a, b)`
67
+
68
+ Returns `true` if bounds `a` fully contains bounds `b`.
69
+
70
+ ### `boundsCenter(bounds)`
71
+
72
+ Returns the centre `{ x, y }` of a bounding box.
73
+
74
+ ### `intersectBounds(a, b)`
75
+
76
+ Returns the intersection of two bounds, or `null` if they don't overlap.
@@ -0,0 +1,64 @@
1
+ ---
2
+ title: createKeyboardShortcuts
3
+ description: Declarative keyboard shortcut registration replacing switch statements.
4
+ ---
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ npx shadcn@latest add https://substrate.georgedrury.co.uk/r/keyboard-shortcuts
10
+ ```
11
+
12
+ ## Usage
13
+
14
+ ```tsx
15
+ import {
16
+ createKeyboardShortcuts,
17
+ formatShortcut,
18
+ } from "@/lib/substrate/keyboard-shortcuts"
19
+
20
+ // Register shortcuts – returns a dispose function
21
+ const dispose = createKeyboardShortcuts([
22
+ { key: "z", meta: true, handler: undo, label: "Undo", group: "Edit" },
23
+ { key: "z", meta: true, shift: true, handler: redo, label: "Redo", group: "Edit" },
24
+ { key: "Delete", handler: deleteSelected, label: "Delete", group: "Edit" },
25
+ { key: "a", meta: true, handler: selectAll, label: "Select all", group: "Selection" },
26
+ { key: "0", meta: true, handler: resetZoom, label: "Reset zoom", group: "View" },
27
+ ])
28
+
29
+ // Later, when unmounting:
30
+ dispose()
31
+ ```
32
+
33
+ ## Features
34
+
35
+ Each shortcut entry declares the key and modifiers it responds to. The first matching shortcut wins – so more specific shortcuts (e.g. ⌘+Shift+Z) should be listed before less specific ones (⌘+Z).
36
+
37
+ By default, shortcuts are ignored when focus is inside an `<input>`, `<textarea>`, or `contentEditable` element. Set `ignoreInputs: false` on a specific entry to override this.
38
+
39
+ The `formatShortcut` helper converts an entry into a human-readable string for display in toolbars and overlays – e.g. `{ key: "z", meta: true, shift: true }` becomes `"⌘+Shift+Z"`.
40
+
41
+ ## API
42
+
43
+ ### `createKeyboardShortcuts(shortcuts, options?)`
44
+
45
+ Returns a dispose function that removes the event listener.
46
+
47
+ ### ShortcutEntry
48
+
49
+ | Property | Type | Default | Description |
50
+ | --- | --- | --- | --- |
51
+ | `key` | `string` | — | The key value (e.g. `"z"`, `"Delete"`, `"ArrowUp"`) |
52
+ | `meta` | `boolean` | `false` | Require ⌘/Ctrl |
53
+ | `ctrl` | `boolean` | `false` | Require Ctrl specifically |
54
+ | `shift` | `boolean` | `false` | Require Shift |
55
+ | `alt` | `boolean` | `false` | Require Alt/Option |
56
+ | `handler` | `(e: KeyboardEvent) => void` | — | Callback |
57
+ | `preventDefault` | `boolean` | `true` | Call `e.preventDefault()` |
58
+ | `ignoreInputs` | `boolean` | `true` | Skip when focus is in an input |
59
+ | `label` | `string` | — | Human-readable label for UI |
60
+ | `group` | `string` | — | Group name for UI |
61
+
62
+ ### `formatShortcut(shortcut)`
63
+
64
+ Returns a formatted string like `"⌘+Shift+Z"` or `"Alt+↑"`.
@@ -0,0 +1,73 @@
1
+ ---
2
+ title: Snap
3
+ description: Grid snapping and smart edge snapping for canvas surfaces.
4
+ ---
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ npx shadcn@latest add https://substrate.georgedrury.co.uk/r/snap
10
+ ```
11
+
12
+ ## Usage
13
+
14
+ ### Grid snapping
15
+
16
+ ```ts
17
+ import { snapToGrid, snapValueToGrid } from "@/lib/substrate/snap"
18
+
19
+ snapToGrid({ x: 37, y: 63 }, 10)
20
+ // → { x: 40, y: 60 }
21
+
22
+ snapToGrid({ x: 37, y: 63 }, 10, { origin: { x: 5, y: 5 } })
23
+ // → { x: 35, y: 65 }
24
+
25
+ snapValueToGrid(37, 10) // → 40
26
+ ```
27
+
28
+ ### Guide snapping
29
+
30
+ ```ts
31
+ import { snapToGuides, guidesFromBounds } from "@/lib/substrate/snap"
32
+
33
+ // Generate guides from sibling elements
34
+ const guides = guidesFromBounds(otherElements.map((el) => el.bounds))
35
+
36
+ // Snap the dragged element's position
37
+ const result = snapToGuides(dragPosition, guides, { threshold: 5 })
38
+
39
+ result.point // → snapped { x, y }
40
+ result.snappedGuides // → which guides were hit (for rendering snap lines)
41
+ ```
42
+
43
+ ## Functions
44
+
45
+ ### `snapToGrid(point, gridSize, options?)`
46
+
47
+ Rounds a point to the nearest grid intersection. An optional `origin` shifts the grid.
48
+
49
+ ### `snapValueToGrid(value, gridSize, origin?)`
50
+
51
+ Snaps a single number to the nearest grid line.
52
+
53
+ ### `snapToGuides(point, guides, options?)`
54
+
55
+ Snaps a point to nearby guide lines. Each axis snaps independently – only if a guide is within the `threshold` distance (default 5px). Returns the snapped point and an array of matched guides, which is useful for rendering alignment lines.
56
+
57
+ ### `guidesFromBounds(boundsList, exclude?)`
58
+
59
+ Generates alignment guides from an array of bounding boxes. Creates six guides per box – left edge, horizontal centre, right edge, top edge, vertical centre, bottom edge. Pass the element being dragged as `exclude` to avoid self-snapping.
60
+
61
+ ## Types
62
+
63
+ ```ts
64
+ type Guide = {
65
+ axis: "x" | "y"
66
+ value: number
67
+ }
68
+
69
+ type SnapResult = {
70
+ point: Point
71
+ snappedGuides: Guide[]
72
+ }
73
+ ```
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@substrate-kit/cli",
3
- "version": "0.1.2",
4
- "description": "Scaffold interactive canvas playgrounds with Substrate primitives",
3
+ "version": "0.2.0",
4
+ "description": "Add Substrate components, skills and docs to a project, with an MCP server for agents",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "substrate-kit": "./dist/index.js"
@@ -9,8 +9,8 @@
9
9
  "scripts": {
10
10
  "build": "tsc",
11
11
  "dev": "tsx src/index.ts",
12
- "prepack": "cp ../../registry.json registry.json && npm run build",
13
- "prepublishOnly": "npm --prefix ../.. run release:check"
12
+ "prepack": "node ../../scripts/bundle-cli-assets.mjs && npm run build",
13
+ "prepublishOnly": "node ../../scripts/release-stamp.mjs verify"
14
14
  },
15
15
  "dependencies": {
16
16
  "@modelcontextprotocol/sdk": "^1.28.0",
@@ -26,6 +26,8 @@
26
26
  "files": [
27
27
  "dist",
28
28
  "skills",
29
+ "templates",
30
+ "docs",
29
31
  "registry.json",
30
32
  ".claude-plugin"
31
33
  ],
package/registry.json CHANGED
@@ -32,7 +32,7 @@
32
32
  "name": "math",
33
33
  "type": "registry:lib",
34
34
  "title": "Math",
35
- "description": "Geometry helpers — rotate point, bounds checking, ellipse hit test, screen/world coordinate conversion, normalize bounds, distance, and clamping.",
35
+ "description": "Geometry helpers — rotate point, bounds checking, ellipse hit test, screen/world coordinate conversion, normalize bounds, distance, clamping, plus rotation and handle maths: rotated bounds, snap angle, and resizing a rotated box.",
36
36
  "author": "George Drury <info@georgedrury.co.uk>",
37
37
  "categories": [
38
38
  "substrate",
@@ -120,7 +120,8 @@
120
120
  "zustand"
121
121
  ],
122
122
  "registryDependencies": [
123
- "https://substrate.georgedrury.co.uk/r/types"
123
+ "https://substrate.georgedrury.co.uk/r/types",
124
+ "https://substrate.georgedrury.co.uk/r/id"
124
125
  ],
125
126
  "meta": {
126
127
  "lineage": [
@@ -239,7 +240,8 @@
239
240
  "canvas"
240
241
  ],
241
242
  "registryDependencies": [
242
- "https://substrate.georgedrury.co.uk/r/types"
243
+ "https://substrate.georgedrury.co.uk/r/types",
244
+ "https://substrate.georgedrury.co.uk/r/math"
243
245
  ],
244
246
  "meta": {
245
247
  "lineage": [
@@ -326,7 +328,8 @@
326
328
  "registryDependencies": [
327
329
  "https://substrate.georgedrury.co.uk/r/types",
328
330
  "https://substrate.georgedrury.co.uk/r/math",
329
- "https://substrate.georgedrury.co.uk/r/document-store"
331
+ "https://substrate.georgedrury.co.uk/r/document-store",
332
+ "https://substrate.georgedrury.co.uk/r/bounding-box"
330
333
  ],
331
334
  "meta": {
332
335
  "lineage": [
@@ -489,7 +492,9 @@
489
492
  "https://substrate.georgedrury.co.uk/r/tool-store",
490
493
  "https://substrate.georgedrury.co.uk/r/use-canvas-renderer",
491
494
  "https://substrate.georgedrury.co.uk/r/use-canvas-interaction",
492
- "https://substrate.georgedrury.co.uk/r/use-keyboard-shortcuts"
495
+ "https://substrate.georgedrury.co.uk/r/use-keyboard-shortcuts",
496
+ "https://substrate.georgedrury.co.uk/r/document-store",
497
+ "https://substrate.georgedrury.co.uk/r/types"
493
498
  ],
494
499
  "cssVars": {
495
500
  "theme": {
@@ -1480,7 +1485,7 @@
1480
1485
  "name": "layer-list",
1481
1486
  "type": "registry:ui",
1482
1487
  "title": "Layer List",
1483
- "description": "Sortable layer panel with visibility and lock toggles, icons, and context menu support. Compound component pattern.",
1488
+ "description": "Layer panel with selection, visibility and lock toggles, icons and inline rename. Compound component pattern; pair with use-drag-reorder to sort and context-menu for right-click actions.",
1484
1489
  "categories": [
1485
1490
  "substrate",
1486
1491
  "ui",
@@ -1981,6 +1986,269 @@
1981
1986
  }
1982
1987
  ],
1983
1988
  "author": "George Drury <info@georgedrury.co.uk>"
1989
+ },
1990
+ {
1991
+ "name": "spring",
1992
+ "type": "registry:lib",
1993
+ "title": "Spring",
1994
+ "description": "Spring physics utilities: convert time-based springs (duration, bounce) to stiffness, damping and mass, and sample their progress over time.",
1995
+ "author": "George Drury <info@georgedrury.co.uk>",
1996
+ "categories": [
1997
+ "substrate",
1998
+ "lib",
1999
+ "animation"
2000
+ ],
2001
+ "meta": {
2002
+ "lineage": [
2003
+ {
2004
+ "name": "Substrate"
2005
+ }
2006
+ ]
2007
+ },
2008
+ "files": [
2009
+ {
2010
+ "path": "registry/substrate/lib/spring.ts",
2011
+ "type": "registry:lib",
2012
+ "target": "lib/substrate/spring.ts"
2013
+ }
2014
+ ]
2015
+ },
2016
+ {
2017
+ "name": "spring-editor",
2018
+ "type": "registry:ui",
2019
+ "title": "Spring Editor",
2020
+ "description": "Spring curve preview with duration and bounce, or stiffness, damping and mass sliders. Used for spring transitions in animation tools.",
2021
+ "author": "George Drury <info@georgedrury.co.uk>",
2022
+ "categories": [
2023
+ "substrate",
2024
+ "ui",
2025
+ "animation"
2026
+ ],
2027
+ "cssVars": {
2028
+ "theme": {
2029
+ "color-spring-curve": "var(--spring-curve, var(--color-primary))"
2030
+ },
2031
+ "light": {},
2032
+ "dark": {}
2033
+ },
2034
+ "meta": {
2035
+ "example": "<SpringEditor value={{ type: \"spring\", visualDuration: 0.3, bounce: 0.2 }} onChange={setSpring} />",
2036
+ "lineage": [
2037
+ {
2038
+ "name": "DialKit",
2039
+ "href": "https://github.com/joshpuckett/dialkit"
2040
+ },
2041
+ {
2042
+ "name": "Substrate"
2043
+ }
2044
+ ]
2045
+ },
2046
+ "files": [
2047
+ {
2048
+ "path": "registry/substrate/components/spring-editor.tsx",
2049
+ "type": "registry:ui",
2050
+ "target": "components/substrate/spring-editor.tsx"
2051
+ }
2052
+ ],
2053
+ "registryDependencies": [
2054
+ "https://substrate.georgedrury.co.uk/r/action",
2055
+ "https://substrate.georgedrury.co.uk/r/number-slider",
2056
+ "https://substrate.georgedrury.co.uk/r/toggle-group",
2057
+ "https://substrate.georgedrury.co.uk/r/spring"
2058
+ ]
2059
+ },
2060
+ {
2061
+ "name": "xy-pad",
2062
+ "type": "registry:ui",
2063
+ "title": "XY Pad",
2064
+ "description": "Two-dimensional pad for setting two values at once by dragging or with the arrow keys. Used for offsets, positions and paired parameters.",
2065
+ "author": "George Drury <info@georgedrury.co.uk>",
2066
+ "categories": [
2067
+ "substrate",
2068
+ "ui",
2069
+ "form"
2070
+ ],
2071
+ "cssVars": {
2072
+ "theme": {
2073
+ "color-xy-pad-handle": "var(--xy-pad-handle, var(--color-primary))"
2074
+ },
2075
+ "light": {},
2076
+ "dark": {}
2077
+ },
2078
+ "meta": {
2079
+ "example": "<XYPad value={offset} onChange={setOffset} x={{ min: -50, max: 50, step: 1 }} y={{ min: -50, max: 50, step: 1 }} />",
2080
+ "lineage": [
2081
+ {
2082
+ "name": "DialKit",
2083
+ "href": "https://github.com/joshpuckett/dialkit"
2084
+ },
2085
+ {
2086
+ "name": "Substrate"
2087
+ }
2088
+ ]
2089
+ },
2090
+ "files": [
2091
+ {
2092
+ "path": "registry/substrate/components/xy-pad.tsx",
2093
+ "type": "registry:ui",
2094
+ "target": "components/substrate/xy-pad.tsx"
2095
+ }
2096
+ ]
2097
+ },
2098
+ {
2099
+ "name": "image-picker",
2100
+ "type": "registry:ui",
2101
+ "title": "Image Picker",
2102
+ "description": "Thumbnail grid for choosing an image from a list, with upload and drag-and-drop. Uploads become data URLs.",
2103
+ "author": "George Drury <info@georgedrury.co.uk>",
2104
+ "categories": [
2105
+ "substrate",
2106
+ "ui",
2107
+ "form"
2108
+ ],
2109
+ "cssVars": {
2110
+ "theme": {
2111
+ "color-image-picker-selection": "var(--image-picker-selection, var(--color-primary))"
2112
+ },
2113
+ "light": {},
2114
+ "dark": {}
2115
+ },
2116
+ "meta": {
2117
+ "example": "<ImagePicker value={image} onChange={setImage} options={[\"/a.jpg\", \"/b.jpg\"]} />",
2118
+ "lineage": [
2119
+ {
2120
+ "name": "DialKit",
2121
+ "href": "https://github.com/joshpuckett/dialkit"
2122
+ },
2123
+ {
2124
+ "name": "Substrate"
2125
+ }
2126
+ ]
2127
+ },
2128
+ "files": [
2129
+ {
2130
+ "path": "registry/substrate/components/image-picker.tsx",
2131
+ "type": "registry:ui",
2132
+ "target": "components/substrate/image-picker.tsx"
2133
+ }
2134
+ ],
2135
+ "dependencies": [
2136
+ "@remixicon/react"
2137
+ ]
2138
+ },
2139
+ {
2140
+ "name": "controls",
2141
+ "type": "registry:ui",
2142
+ "title": "Controls",
2143
+ "description": "Config-driven control panels — describe parameters as an object with useControls and render them with Controls, built on DialKit's store.",
2144
+ "author": "George Drury <info@georgedrury.co.uk>",
2145
+ "categories": [
2146
+ "substrate",
2147
+ "ui",
2148
+ "form"
2149
+ ],
2150
+ "dependencies": [
2151
+ "dialkit",
2152
+ "@remixicon/react"
2153
+ ],
2154
+ "meta": {
2155
+ "example": "const values = useControls(\"Card\", { radius: [24, 0, 64], visible: true })\n<Panel><Controls /></Panel>",
2156
+ "lineage": [
2157
+ {
2158
+ "name": "DialKit",
2159
+ "href": "https://github.com/joshpuckett/dialkit"
2160
+ },
2161
+ {
2162
+ "name": "Substrate"
2163
+ }
2164
+ ]
2165
+ },
2166
+ "files": [
2167
+ {
2168
+ "path": "registry/substrate/components/controls.tsx",
2169
+ "type": "registry:ui",
2170
+ "target": "components/substrate/controls.tsx"
2171
+ },
2172
+ {
2173
+ "path": "registry/substrate/hooks/use-controls.ts",
2174
+ "type": "registry:hook",
2175
+ "target": "hooks/substrate/use-controls.ts"
2176
+ },
2177
+ {
2178
+ "path": "registry/substrate/lib/controls-shortcuts.ts",
2179
+ "type": "registry:lib",
2180
+ "target": "lib/substrate/controls-shortcuts.ts"
2181
+ }
2182
+ ],
2183
+ "registryDependencies": [
2184
+ "https://substrate.georgedrury.co.uk/r/pane",
2185
+ "https://substrate.georgedrury.co.uk/r/collapsible-pane",
2186
+ "https://substrate.georgedrury.co.uk/r/action",
2187
+ "https://substrate.georgedrury.co.uk/r/number-slider",
2188
+ "https://substrate.georgedrury.co.uk/r/number-input",
2189
+ "https://substrate.georgedrury.co.uk/r/toggle",
2190
+ "https://substrate.georgedrury.co.uk/r/select",
2191
+ "https://substrate.georgedrury.co.uk/r/colour-picker",
2192
+ "https://substrate.georgedrury.co.uk/r/text-area",
2193
+ "https://substrate.georgedrury.co.uk/r/easing-curve-editor",
2194
+ "https://substrate.georgedrury.co.uk/r/toggle-group",
2195
+ "https://substrate.georgedrury.co.uk/r/spring-editor",
2196
+ "https://substrate.georgedrury.co.uk/r/xy-pad",
2197
+ "https://substrate.georgedrury.co.uk/r/image-picker",
2198
+ "https://substrate.georgedrury.co.uk/r/spring",
2199
+ "https://substrate.georgedrury.co.uk/r/popover"
2200
+ ]
2201
+ },
2202
+ {
2203
+ "name": "controls-timeline",
2204
+ "type": "registry:ui",
2205
+ "title": "Controls Timeline",
2206
+ "description": "Animation clips defined in code with useControlsTimeline, tuned in a timeline editor: scrub, move and resize clips, and edit their values. Built on DialKit's headless timeline.",
2207
+ "author": "George Drury <info@georgedrury.co.uk>",
2208
+ "categories": [
2209
+ "substrate",
2210
+ "ui",
2211
+ "animation"
2212
+ ],
2213
+ "dependencies": [
2214
+ "dialkit",
2215
+ "@remixicon/react"
2216
+ ],
2217
+ "cssVars": {
2218
+ "theme": {
2219
+ "color-timeline-clip": "var(--timeline-clip, var(--color-primary))"
2220
+ },
2221
+ "light": {},
2222
+ "dark": {}
2223
+ },
2224
+ "meta": {
2225
+ "example": "const timeline = useControlsTimeline(\"Card\", { enter: { at: 0, duration: 0.6, from: { y: 24 }, to: { y: 0 } } })\n<ControlsTimeline />",
2226
+ "lineage": [
2227
+ {
2228
+ "name": "DialKit",
2229
+ "href": "https://github.com/joshpuckett/dialkit"
2230
+ },
2231
+ {
2232
+ "name": "Substrate"
2233
+ }
2234
+ ]
2235
+ },
2236
+ "files": [
2237
+ {
2238
+ "path": "registry/substrate/components/controls-timeline.tsx",
2239
+ "type": "registry:ui",
2240
+ "target": "components/substrate/controls-timeline.tsx"
2241
+ },
2242
+ {
2243
+ "path": "registry/substrate/hooks/use-controls-timeline.ts",
2244
+ "type": "registry:hook",
2245
+ "target": "hooks/substrate/use-controls-timeline.ts"
2246
+ }
2247
+ ],
2248
+ "registryDependencies": [
2249
+ "https://substrate.georgedrury.co.uk/r/controls",
2250
+ "https://substrate.georgedrury.co.uk/r/popover"
2251
+ ]
1984
2252
  }
1985
2253
  ]
1986
2254
  }
@@ -173,6 +173,7 @@ Some examples of what this surface enables:
173
173
 
174
174
  ## Related reference skills
175
175
 
176
+ - **scaffold-playground** — the craft rules and the checklist to run before you say it's done
176
177
  - **substrate-scaffold** — Toolbar, Panel, StatusBar, ZoomControls for the app shell
177
- - **substrate-controls** — NumberInput, Slider, ColourPicker, ActionControls for property panes
178
+ - **substrate-controls** — NumberInput, Slider, ColourPicker, ActionControls for property panes; `useControls` for tuning material, light and camera values from one config object
178
179
  - **substrate-interaction** — useUndoable, useClipboard, createKeyboardShortcuts, TreeList for scene hierarchy
@@ -195,6 +195,7 @@ Some examples of what this surface enables:
195
195
 
196
196
  ## Related reference skills
197
197
 
198
+ - **scaffold-playground** — the craft rules and the checklist to run before you say it's done
198
199
  - **substrate-scaffold** — Toolbar, Panel, StatusBar for the app shell
199
- - **substrate-controls** — NumberInput, Slider, EasingCurveEditor, ActionControls for keyframe properties
200
+ - **substrate-controls** — NumberInput, Slider, EasingCurveEditor, ActionControls for keyframe properties. For tuning UI motion by eye (entrances, springs, choreography), use `useControlsTimeline` with `<ControlsTimeline />` instead of building a timeline: define clips in code, bind `clip.current` to style, then copy the tuned values back. The `timeline` component is for editing user-authored keyframes.
200
201
  - **substrate-interaction** — useUndoable, createKeyboardShortcuts, LayerList for animation layers