@substrate-kit/cli 0.1.1 → 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 -60
  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 -106
  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 +19 -30
  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 +12 -0
  51. package/dist/utils/shadcn.d.ts.map +1 -0
  52. package/dist/utils/shadcn.js +29 -0
  53. package/dist/utils/shadcn.js.map +1 -0
  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 -3
  108. package/registry.json +331 -16
  109. package/skills/3d-scene/SKILL.md +2 -1
  110. package/skills/animation/SKILL.md +2 -1
  111. package/skills/canvas-2d/SKILL.md +68 -6
  112. package/skills/composing-panels/SKILL.md +9 -12
  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 +42 -22
  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 -415
  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
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,12 +492,14 @@
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": {
496
501
  "color-canvas": "var(--canvas, var(--color-background))",
497
- "color-canvas-foreground": "var(--canvas-foreground, var(--color-foreground-muted))"
502
+ "color-canvas-foreground": "var(--canvas-foreground, var(--color-muted-foreground))"
498
503
  },
499
504
  "light": {
500
505
  "canvas": "oklch(.97 0.0 0)",
@@ -699,7 +704,7 @@
699
704
  "cssVars": {
700
705
  "theme": {
701
706
  "color-canvas": "var(--canvas, var(--color-background))",
702
- "color-canvas-foreground": "var(--canvas-foreground, var(--color-foreground-muted))"
707
+ "color-canvas-foreground": "var(--canvas-foreground, var(--color-muted-foreground))"
703
708
  },
704
709
  "light": {
705
710
  "canvas": "oklch(.97 0.0 0)",
@@ -878,6 +883,9 @@
878
883
  "type": "registry:component",
879
884
  "target": "components/substrate/history-controls.tsx"
880
885
  }
886
+ ],
887
+ "registryDependencies": [
888
+ "https://substrate.georgedrury.co.uk/r/document-store"
881
889
  ]
882
890
  },
883
891
  {
@@ -1316,7 +1324,35 @@
1316
1324
  "target": "components/substrate/collapsible-pane.tsx"
1317
1325
  }
1318
1326
  ],
1319
- "author": "George Drury <info@georgedrury.co.uk>"
1327
+ "author": "George Drury <info@georgedrury.co.uk>",
1328
+ "cssVars": {
1329
+ "theme": {
1330
+ "animate-collapsible-down": "collapsible-down 150ms ease-out",
1331
+ "animate-collapsible-up": "collapsible-up 150ms ease-out"
1332
+ }
1333
+ },
1334
+ "css": {
1335
+ "@keyframes collapsible-down": {
1336
+ "from": {
1337
+ "height": "0",
1338
+ "opacity": "0"
1339
+ },
1340
+ "to": {
1341
+ "height": "var(--radix-collapsible-content-height)",
1342
+ "opacity": "1"
1343
+ }
1344
+ },
1345
+ "@keyframes collapsible-up": {
1346
+ "from": {
1347
+ "height": "var(--radix-collapsible-content-height)",
1348
+ "opacity": "1"
1349
+ },
1350
+ "to": {
1351
+ "height": "0",
1352
+ "opacity": "0"
1353
+ }
1354
+ }
1355
+ }
1320
1356
  },
1321
1357
  {
1322
1358
  "name": "command-palette",
@@ -1449,7 +1485,7 @@
1449
1485
  "name": "layer-list",
1450
1486
  "type": "registry:ui",
1451
1487
  "title": "Layer List",
1452
- "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.",
1453
1489
  "categories": [
1454
1490
  "substrate",
1455
1491
  "ui",
@@ -1493,7 +1529,11 @@
1493
1529
  "color-node-port-geometry": "var(--node-port-geometry, oklch(0.7 0.15 200))",
1494
1530
  "color-node-port-text": "var(--node-port-text, oklch(0.7 0.15 150))",
1495
1531
  "color-node-port-boolean": "var(--node-port-boolean, oklch(0.7 0.15 30))",
1496
- "color-node-port-any": "var(--node-port-any, oklch(0.6 0 0))"
1532
+ "color-node-port-any": "var(--node-port-any, oklch(0.6 0 0))",
1533
+ "color-node-bg": "var(--node-bg, var(--card))",
1534
+ "color-node-border": "var(--node-border, var(--border))",
1535
+ "color-node-header": "var(--node-header, var(--muted))",
1536
+ "color-node-selected": "var(--node-selected, var(--ring))"
1497
1537
  }
1498
1538
  },
1499
1539
  "meta": {
@@ -1524,8 +1564,8 @@
1524
1564
  "form"
1525
1565
  ],
1526
1566
  "registryDependencies": [
1527
- "slider",
1528
- "number-input"
1567
+ "https://substrate.georgedrury.co.uk/r/slider",
1568
+ "https://substrate.georgedrury.co.uk/r/number-input"
1529
1569
  ],
1530
1570
  "meta": {
1531
1571
  "example": "<NumberSlider label=\"Opacity\" min={0} max={100} value={opacity} onChange={setOpacity} />",
@@ -1600,7 +1640,22 @@
1600
1640
  "target": "components/substrate/progress-bar.tsx"
1601
1641
  }
1602
1642
  ],
1603
- "author": "George Drury <info@georgedrury.co.uk>"
1643
+ "author": "George Drury <info@georgedrury.co.uk>",
1644
+ "cssVars": {
1645
+ "theme": {
1646
+ "animate-progress-indeterminate": "progress-indeterminate 1.5s ease-in-out infinite"
1647
+ }
1648
+ },
1649
+ "css": {
1650
+ "@keyframes progress-indeterminate": {
1651
+ "0%": {
1652
+ "transform": "translateX(-100%)"
1653
+ },
1654
+ "100%": {
1655
+ "transform": "translateX(400%)"
1656
+ }
1657
+ }
1658
+ }
1604
1659
  },
1605
1660
  {
1606
1661
  "name": "resizer",
@@ -1852,9 +1907,6 @@
1852
1907
  "dependencies": [
1853
1908
  "radix-ui"
1854
1909
  ],
1855
- "registryDependencies": [
1856
- "toggle"
1857
- ],
1858
1910
  "meta": {
1859
1911
  "example": "<ToggleGroup type=\"single\" value={align} onValueChange={setAlign}><ToggleGroupItem value=\"left\">Left</ToggleGroupItem><ToggleGroupItem value=\"center\">Center</ToggleGroupItem><ToggleGroupItem value=\"right\">Right</ToggleGroupItem></ToggleGroup>",
1860
1912
  "lineage": [
@@ -1934,6 +1986,269 @@
1934
1986
  }
1935
1987
  ],
1936
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
+ ]
1937
2252
  }
1938
2253
  ]
1939
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
@@ -19,7 +19,7 @@ This is Substrate's default surface. The `DesignCanvas` component provides a ful
19
19
  ### Install
20
20
 
21
21
  ```bash
22
- npx shadcn@latest add https://substrate.georgedrury.co.uk/r/design-canvas
22
+ npx substrate-kit add design-canvas
23
23
  ```
24
24
 
25
25
  This pulls in the canvas renderer, hit-test system, interaction hooks, transform utilities, and all required stores.
@@ -27,7 +27,7 @@ This pulls in the canvas renderer, hit-test system, interaction hooks, transform
27
27
  ### Mount
28
28
 
29
29
  ```tsx
30
- import { DesignCanvas } from "@/registry/substrate/canvas/design-canvas"
30
+ import { DesignCanvas } from "@/components/substrate/design-canvas"
31
31
 
32
32
  export default function PlaygroundPage() {
33
33
  return (
@@ -85,27 +85,88 @@ The default tool set for a 2D canvas:
85
85
 
86
86
  ### Panels
87
87
 
88
- See the [composing panels](./composing-panels.md) skill for full details. In summary:
88
+ See the `composing-panels` skill for full details. In summary:
89
89
 
90
90
  - **Left panel** — layers list (render order, visibility toggles, selection)
91
91
  - **Right panel** — properties inspector (transform, fill, stroke, opacity, type-specific properties)
92
92
 
93
93
  ### Interactions
94
94
 
95
- See the [wire interactions](./wire-interactions.md) skill for the full state machine. Key patterns:
95
+ See the `wire-interactions` skill for the full state machine. Key patterns:
96
96
 
97
97
  - Click to select, shift-click to multi-select
98
98
  - Drag on empty space with select tool → marquee selection
99
99
  - Drag on selected element → move
100
100
  - Drag on resize handle → resize
101
+ - Drag on the rotate handle → rotate (Shift snaps to 15°)
101
102
  - Drag with shape tool → create new element
102
103
  - Middle-click or hand tool → pan
103
104
 
105
+ ## State, undo and persistence
106
+
107
+ `useDocumentStore` holds the board: `elements`, `elementOrder` (back to front), `camera`, `showGrid`, the selection and the history.
108
+
109
+ - **Undo and redo cover elements and their order only.** Pan, zoom and the grid are view state, so undoing an edit never moves the viewport.
110
+ - **Call `pushSnapshot()` before every mutation that should be undoable,** including property edits. For a scrub or drag, snapshot once at the start (`onStart`), not on every change.
111
+ - **Composite actions** record their own undo step: `duplicateElements(ids?)` clones the selection (or the given ids), and `setElementOrder(ids)` replaces the stacking order. Call `pushSnapshot()` yourself before `setElementOrder`.
112
+ - **Zoom** with `useCanvasTransform`: `setZoom(zoom)` and `zoomIn`/`zoomOut` zoom around the middle of the canvas, and `zoomToFit()` frames every visible element. Wire `ZoomControls` to `setZoom` and `zoomToFit`; setting `camera.zoom` directly zooms around the world origin and drifts.
113
+
114
+ To remember the board across reloads, restore before the first render and save on change:
115
+
116
+ ```ts
117
+ import { useDocumentStore, type DocumentSnapshot } from "@/stores/substrate/document-store"
118
+
119
+ const KEY = "my-board"
120
+
121
+ // Call before the first render so the saved board is there on first paint
122
+ export function restoreBoard(): boolean {
123
+ const raw = localStorage.getItem(KEY)
124
+ if (!raw) return false
125
+ try {
126
+ useDocumentStore.getState().loadState(JSON.parse(raw) as DocumentSnapshot)
127
+ return true
128
+ } catch {
129
+ return false
130
+ }
131
+ }
132
+
133
+ // Returns an unsubscribe function
134
+ export function persistBoard(): () => void {
135
+ let timer: number | undefined
136
+ return useDocumentStore.subscribe((state, prev) => {
137
+ const changed =
138
+ state.elements !== prev.elements ||
139
+ state.elementOrder !== prev.elementOrder ||
140
+ state.camera !== prev.camera ||
141
+ state.showGrid !== prev.showGrid
142
+ if (!changed) return
143
+ window.clearTimeout(timer)
144
+ timer = window.setTimeout(() => {
145
+ const snapshot: DocumentSnapshot = {
146
+ elements: state.elements,
147
+ elementOrder: state.elementOrder,
148
+ camera: state.camera,
149
+ showGrid: state.showGrid,
150
+ }
151
+ localStorage.setItem(KEY, JSON.stringify(snapshot))
152
+ }, 300)
153
+ })
154
+ }
155
+ ```
156
+
157
+ Selection and history are not saved, which is usually what you want. Seed a starter board with `addElement` only when `restoreBoard()` returns `false`, and give the starter shapes theme-safe colours.
158
+
159
+ ## Current limits
160
+
161
+ - **Rotation** works on a single selection: drag the round handle above it (hold Shift to snap to 15°), and the box, handles and cursors turn with the shape. Resizing a rotated shape keeps the opposite corner fixed. Several selected elements can be moved and deleted but not rotated or resized together; there is no group transform.
162
+ - **Text:** a click with the text tool makes a 160×32 note. Text is edited through a properties panel (`textStyle.content`); there is no on-canvas editing.
163
+ - **Hidden and locked layers** (`visible: false`, `locked: true`) are skipped by hit-testing, so they can't be selected on the canvas.
164
+
104
165
  ## Extending the canvas
105
166
 
106
167
  ### Custom elements
107
168
 
108
- To add a new element type (e.g. star, arrow, image), follow the [create custom tool](./create-custom-tool.md) skill. The pipeline is: type definition → factory → renderer → hit-test → interaction → shortcut → cursor → toolbar.
169
+ To add a new element type (e.g. star, arrow, image), follow the `create-custom-tool` skill. The pipeline is: type definition → factory → renderer → hit-test → interaction → shortcut → cursor → toolbar.
109
170
 
110
171
  ### Custom rendering
111
172
 
@@ -117,7 +178,7 @@ For UI elements that sit on top of the canvas (selection indicators, context men
117
178
 
118
179
  ## Theming
119
180
 
120
- The canvas background is driven by the `--canvas` CSS variable, read at render time via `getComputedStyle`. Grid dots use `--canvas-foreground`. See the [theming](./theming.md) skill for the full token table.
181
+ The canvas background is driven by the `--canvas` CSS variable, read at render time via `getComputedStyle`. Grid dots use `--canvas-foreground`. See the `theming` skill for the full token table.
121
182
 
122
183
  ## What to build
123
184
 
@@ -132,6 +193,7 @@ Some examples of what this surface enables:
132
193
 
133
194
  ## Related reference skills
134
195
 
196
+ - **scaffold-playground** — the craft rules and the checklist to run before you say it's done
135
197
  - **substrate-canvas** — full catalogue of spatial hooks (useViewport, useSelection, useDragReorder) and utilities (snapToGrid, boundingBox, math)
136
198
  - **substrate-scaffold** — Toolbar, Panel, StatusBar, ZoomControls for the app shell
137
199
  - **substrate-controls** — NumberInput, Slider, ColourPicker, ActionControls for property panes
@@ -27,16 +27,13 @@ Panel (side panel shell — left or right)
27
27
  ## Install
28
28
 
29
29
  ```bash
30
- npx shadcn@latest add https://substrate.georgedrury.co.uk/r/panel
31
- npx shadcn@latest add https://substrate.georgedrury.co.uk/r/pane
32
- npx shadcn@latest add https://substrate.georgedrury.co.uk/r/action
33
- npx shadcn@latest add https://substrate.georgedrury.co.uk/r/slider
30
+ npx substrate-kit add panel pane action slider
34
31
  ```
35
32
 
36
33
  ## Panel positioning
37
34
 
38
35
  ```tsx
39
- import { Panel } from "@/registry/substrate/components/panel"
36
+ import { Panel } from "@/components/substrate/panel"
40
37
 
41
38
  {
42
39
  /* Left panel — layers, tool options */
@@ -58,19 +55,19 @@ A typical properties panel reads from `useDocumentStore` and writes back via `up
58
55
  ```tsx
59
56
  "use client"
60
57
 
61
- import { useDocumentStore } from "@/registry/substrate/stores/document-store"
58
+ import { useDocumentStore } from "@/stores/substrate/document-store"
62
59
  import {
63
60
  Pane,
64
61
  PaneHeader,
65
62
  PaneLabel,
66
63
  PaneAction,
67
- } from "@/registry/substrate/components/pane"
64
+ } from "@/components/substrate/pane"
68
65
  import {
69
66
  Action,
70
67
  ActionLabel,
71
68
  ActionControls,
72
- } from "@/registry/substrate/components/action"
73
- import { Slider } from "@/registry/substrate/components/slider"
69
+ } from "@/components/substrate/action"
70
+ import { Slider } from "@/components/substrate/slider"
74
71
 
75
72
  export function PropertiesPanel() {
76
73
  const selectedIds = useDocumentStore((s) => s.selectedIds)
@@ -170,12 +167,12 @@ A layers panel lists elements in render order (bottom to top) and lets users sel
170
167
  ```tsx
171
168
  "use client"
172
169
 
173
- import { useDocumentStore } from "@/registry/substrate/stores/document-store"
170
+ import { useDocumentStore } from "@/stores/substrate/document-store"
174
171
  import {
175
172
  Pane,
176
173
  PaneHeader,
177
174
  PaneLabel,
178
- } from "@/registry/substrate/components/pane"
175
+ } from "@/components/substrate/pane"
179
176
 
180
177
  export function LayersPanel() {
181
178
  const elements = useDocumentStore((s) => s.elements)
@@ -224,7 +221,7 @@ export function LayersPanel() {
224
221
  Use `usePanelStore` to control panel visibility:
225
222
 
226
223
  ```tsx
227
- import { usePanelStore } from "@/registry/substrate/stores/panel-store"
224
+ import { usePanelStore } from "@/stores/substrate/panel-store"
228
225
 
229
226
  const leftPanel = usePanelStore((s) => s.leftPanel)
230
227
  const rightPanel = usePanelStore((s) => s.rightPanel)
@@ -229,6 +229,7 @@ Some examples of what this surface enables:
229
229
 
230
230
  ## Related reference skills
231
231
 
232
+ - **scaffold-playground** — the craft rules and the checklist to run before you say it's done
232
233
  - **substrate-scaffold** — Toolbar, Panel, StatusBar, DocumentTabs for the app shell
233
- - **substrate-controls** — Select, NumberInput, Slider, ColourPicker for chart configuration
234
+ - **substrate-controls** — `useControls` for chart settings from one config object (sliders, toggles, colours, selects); Select, NumberInput, Slider, ColourPicker when composing rows by hand
234
235
  - **substrate-feedback** — Toast, EmptyState, CommandPalette for data loading feedback