@dfosco/storyboard 0.7.0 → 0.8.1

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 (258) hide show
  1. package/assets/favicon.svg +12 -0
  2. package/dist/storyboard-ui.css +1 -1
  3. package/dist/storyboard-ui.js +13907 -10816
  4. package/dist/storyboard-ui.js.map +1 -1
  5. package/dist/tailwind.css +1 -1
  6. package/package.json +138 -127
  7. package/scaffold/AGENTS.md +9 -7
  8. package/scaffold/README.md +18 -0
  9. package/scaffold/deploy.yml +21 -0
  10. package/scaffold/gitignore +8 -14
  11. package/scaffold/preview.yml +40 -0
  12. package/scaffold/{manifest.json → scaffold.config.json} +73 -3
  13. package/scaffold/scripts/aggregate-artifacts.mjs +257 -0
  14. package/scaffold/skills/canvas/SKILL.md +1 -1
  15. package/scaffold/skills/migrate-0.5.0/SKILL.md +1 -1
  16. package/scaffold/skills/ship/SKILL.md +2 -2
  17. package/scaffold/skills/worktree/SKILL.md +1 -1
  18. package/scaffold/src/_prototype.jsx +17 -0
  19. package/scaffold/src/_story.jsx +13 -0
  20. package/scaffold/src/library/README.md +34 -0
  21. package/scaffold/src/library/_app.jsx +53 -0
  22. package/scaffold/src/library/_app.module.css +8 -0
  23. package/scaffold/src/library/create.jsx +10 -0
  24. package/scaffold/src/library/index.jsx +14 -0
  25. package/scaffold/src/library/mount.jsx +52 -0
  26. package/scaffold/src/library/prototypes-entry.jsx +57 -0
  27. package/scaffold/src/library/routes.jsx +205 -0
  28. package/scaffold/src/library/viewfinder.jsx +13 -0
  29. package/scaffold/src/library/workspace.jsx +17 -0
  30. package/scripts/aggregate-artifacts.mjs +257 -0
  31. package/src/core/artifact/operations.js +19 -13
  32. package/src/core/autosync/server.test.js +1 -1
  33. package/src/core/canvas/githubEmbeds.js +12 -0
  34. package/src/core/canvas/githubEmbeds.test.js +71 -2
  35. package/src/core/canvas/hot-pool.js +10 -11
  36. package/src/core/canvas/materializer.js +39 -13
  37. package/src/core/canvas/server.js +324 -69
  38. package/src/core/canvas/server.test.js +365 -2
  39. package/src/core/canvas/terminal-server.js +144 -57
  40. package/src/core/canvas/undoRedo.js +174 -0
  41. package/src/core/canvas/undoRedo.test.js +250 -0
  42. package/src/core/cli/buildManifest.js +34 -0
  43. package/src/core/cli/canvasRedo.js +44 -0
  44. package/src/core/cli/canvasUndo.js +40 -0
  45. package/src/core/cli/create.js +116 -18
  46. package/src/core/cli/dev.js +23 -35
  47. package/src/core/cli/index.js +21 -0
  48. package/src/core/cli/schemas.js +2 -3
  49. package/src/core/cli/serverUrl.js +24 -0
  50. package/src/core/cli/setup.js +14 -25
  51. package/src/core/cli/terminal-welcome.js +12 -6
  52. package/src/core/cli/updateVersion.js +42 -2
  53. package/src/core/cli/workleaf.js +140 -0
  54. package/src/core/comments/api.js +1 -1
  55. package/src/core/comments/queries.js +3 -0
  56. package/src/core/comments/ui/CommentWindow.jsx +3 -2
  57. package/src/core/data/artifactIndex.js +235 -0
  58. package/src/core/data/artifactIndex.test.js +203 -0
  59. package/src/core/data/artifactManifest.js +290 -0
  60. package/src/core/data/artifactManifest.test.js +207 -0
  61. package/src/core/data/viewfinder.js +47 -7
  62. package/src/core/index.js +6 -1
  63. package/src/core/lib/components/ui/avatar/avatar-fallback.jsx +2 -2
  64. package/src/core/lib/components/ui/avatar/avatar-image.jsx +2 -2
  65. package/src/core/lib/components/ui/avatar/avatar.jsx +2 -2
  66. package/src/core/lib/components/ui/checkbox/checkbox.jsx +12 -9
  67. package/src/core/lib/components/ui/dialog/dialog-content.jsx +45 -31
  68. package/src/core/lib/components/ui/dialog/dialog-trigger.jsx +9 -5
  69. package/src/core/lib/components/ui/dialog/dialog.jsx +12 -3
  70. package/src/core/lib/components/ui/dialog/dialog.module.css +118 -0
  71. package/src/core/lib/components/ui/dialog/index.js +4 -34
  72. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.jsx +3 -3
  73. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.jsx +19 -9
  74. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-content.jsx +8 -8
  75. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.jsx +2 -2
  76. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-group.jsx +2 -2
  77. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-item.jsx +15 -4
  78. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-portal.jsx +2 -2
  79. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.jsx +2 -2
  80. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.jsx +14 -7
  81. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-separator.jsx +2 -2
  82. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.jsx +8 -4
  83. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.jsx +4 -4
  84. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub.jsx +2 -2
  85. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-trigger.jsx +8 -2
  86. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu.jsx +2 -2
  87. package/src/core/lib/components/ui/label/label.jsx +1 -2
  88. package/src/core/lib/components/ui/panel/panel-close.jsx +7 -4
  89. package/src/core/lib/components/ui/panel/panel-content.jsx +7 -9
  90. package/src/core/lib/components/ui/panel/panel-title.jsx +3 -3
  91. package/src/core/lib/components/ui/panel/panel.jsx +11 -2
  92. package/src/core/lib/components/ui/separator/separator.jsx +6 -4
  93. package/src/core/lib/components/ui/tooltip/tooltip-content.jsx +7 -6
  94. package/src/core/lib/components/ui/tooltip/tooltip-trigger.jsx +13 -6
  95. package/src/core/lib/components/ui/tooltip/tooltip.jsx +5 -5
  96. package/src/core/mountStoryboardCore.js +234 -74
  97. package/src/core/mountStoryboardCore.test.js +174 -0
  98. package/src/core/rename-watcher/watcher.js +329 -36
  99. package/src/core/rename-watcher/watcher.test.js +253 -0
  100. package/src/core/scaffold/fragments.js +287 -0
  101. package/src/core/scaffold/fragments.test.js +401 -0
  102. package/src/core/scaffold/index.js +291 -0
  103. package/src/core/scaffold/migrateLegacyGitignore.js +157 -0
  104. package/src/core/scaffold/scaffold-integration.test.js +247 -0
  105. package/src/core/scaffold.js +4 -97
  106. package/src/core/stores/configSchema.js +41 -2
  107. package/src/core/stores/recentArtifacts.js +49 -2
  108. package/src/core/stores/recentArtifacts.test.js +59 -6
  109. package/src/core/stores/themeBootstrap.js +264 -0
  110. package/src/core/stores/themeBootstrap.test.js +211 -0
  111. package/src/core/stores/themeStore.test.js +154 -0
  112. package/src/core/stores/themeStore.ts +235 -220
  113. package/src/core/styles/generateThemesCss.js +63 -0
  114. package/src/core/styles/tailwind.css +12 -4
  115. package/src/core/styles/workspace-chrome.css +30 -0
  116. package/src/core/tools/handlers/paletteTheme.js +11 -8
  117. package/src/core/ui/ActionMenuButton.jsx +2 -1
  118. package/src/core/ui/CanvasCreateMenu.jsx +1 -19
  119. package/src/core/ui/CoreUIBar.jsx +10 -7
  120. package/src/core/ui/ThemeMenuButton.jsx +45 -30
  121. package/src/core/vite/server-plugin.js +36 -0
  122. package/src/core/workleaf/cleanup.js +102 -0
  123. package/src/core/workleaf/create.js +199 -0
  124. package/src/core/workleaf/delete.js +101 -0
  125. package/src/core/workleaf/list.js +57 -0
  126. package/src/core/workleaf/merge.js +238 -0
  127. package/src/core/workleaf/paths.js +146 -0
  128. package/src/core/workleaf/paths.test.js +230 -0
  129. package/src/core/workshop/features/createCanvas/CreateCanvasForm.jsx +28 -21
  130. package/src/core/workshop/features/createFlow/server.js +5 -1
  131. package/src/core/workshop/features/createPrototype/server.js +5 -1
  132. package/src/core/workshop/features/templateIndex.js +28 -1
  133. package/src/core/worktree/port.js +48 -5
  134. package/src/core/worktree/port.test.js +42 -0
  135. package/src/internals/ArtifactForm/ArtifactForm.jsx +173 -112
  136. package/src/internals/ArtifactForm/ArtifactForm.module.css +48 -66
  137. package/src/internals/ArtifactForm/artifactSchemas.js +40 -5
  138. package/src/internals/BaseUiForm/BaseUiForm.jsx +293 -0
  139. package/src/internals/BaseUiForm/BaseUiForm.module.css +542 -0
  140. package/src/internals/BranchesDropdown/BranchesDropdown.jsx +69 -0
  141. package/src/internals/BranchesDropdown/BranchesDropdown.module.css +84 -0
  142. package/src/internals/CommandPalette/CommandPalette.jsx +25 -19
  143. package/src/internals/CommandPalette/CreateArtifactForm.jsx +150 -0
  144. package/src/internals/CommandPalette/CreateDialog.jsx +20 -110
  145. package/src/internals/CommandPalette/WidgetArtifactDialog.jsx +1 -1
  146. package/src/internals/CommandPalette/createHelpers.js +30 -0
  147. package/src/internals/CreatePage/CreatePage.jsx +57 -0
  148. package/src/internals/CreatePage/CreatePage.module.css +34 -0
  149. package/src/internals/Dialog.jsx +51 -0
  150. package/src/internals/SimpleWorkspace/SimpleWorkspace.jsx +533 -0
  151. package/src/internals/SimpleWorkspace/SimpleWorkspace.module.css +323 -0
  152. package/src/internals/Tooltip.jsx +39 -0
  153. package/src/internals/Viewfinder.jsx +196 -48
  154. package/src/internals/Viewfinder.module.css +72 -1
  155. package/src/internals/canvas/CanvasPage.jsx +267 -124
  156. package/src/internals/canvas/CanvasPage.multiselect.test.jsx +39 -26
  157. package/src/internals/canvas/PageSelector.jsx +15 -1
  158. package/src/internals/canvas/PageSelector.module.css +14 -0
  159. package/src/internals/canvas/canvasApi.js +40 -5
  160. package/src/internals/canvas/isolation/createIsolationMiddleware.js +68 -0
  161. package/src/internals/canvas/isolation/mountIsolationApp.jsx +88 -0
  162. package/src/internals/canvas/isolation/prototypeRoutes.jsx +131 -0
  163. package/src/internals/canvas/isolation/prototypesEntry.jsx +28 -0
  164. package/src/internals/canvas/isolation/storiesEntry.jsx +20 -0
  165. package/src/internals/canvas/isolation/storyRoutes.jsx +55 -0
  166. package/src/internals/canvas/useUndoRedo.js +75 -60
  167. package/src/internals/canvas/useUndoRedo.test.js +46 -178
  168. package/src/internals/canvas/widgets/ExpandedPaneTopBar.jsx +1 -1
  169. package/src/internals/canvas/widgets/FrozenTerminalOverlay.jsx +30 -21
  170. package/src/internals/canvas/widgets/ImageWidget.jsx +7 -2
  171. package/src/internals/canvas/widgets/LinkPreview.jsx +45 -1
  172. package/src/internals/canvas/widgets/LinkPreview.module.css +71 -0
  173. package/src/internals/canvas/widgets/PrototypeEmbed.jsx +69 -10
  174. package/src/internals/canvas/widgets/PrototypeEmbed.test.jsx +114 -0
  175. package/src/internals/canvas/widgets/StorySetWidget.jsx +8 -4
  176. package/src/internals/canvas/widgets/StoryWidget.jsx +21 -38
  177. package/src/internals/canvas/widgets/TerminalReadWidget.jsx +13 -15
  178. package/src/internals/canvas/widgets/WidgetChrome.jsx +1 -1
  179. package/src/internals/canvas/widgets/WidgetChrome.module.css +15 -0
  180. package/src/internals/canvas/widgets/embedTheme.js +2 -2
  181. package/src/internals/canvas/widgets/expandUtils.js +19 -19
  182. package/src/internals/canvas/widgets/normalizeLegacyEmbedSrc.js +74 -0
  183. package/src/internals/context.jsx +22 -1
  184. package/src/internals/context.test.jsx +47 -0
  185. package/src/internals/hooks/useArtifactIndex.js +31 -0
  186. package/src/internals/hooks/useArtifactIndex.test.js +45 -0
  187. package/src/internals/hooks/usePrototypeReloadGuard.js +14 -8
  188. package/src/internals/index.js +6 -0
  189. package/src/internals/loadConsumerWrapper.js +60 -0
  190. package/src/internals/story/ComponentSetPage.jsx +75 -34
  191. package/src/internals/story/StoryPage.jsx +16 -25
  192. package/src/internals/vite/data-plugin.js +313 -148
  193. package/src/internals/vite/data-plugin.test.js +11 -11
  194. package/{scaffold/storyboard.config.json → storyboard.config.json} +25 -0
  195. package/terminal.config.json +1 -1
  196. package/src/core/lib/components/ui/card/card-action.jsx +0 -11
  197. package/src/core/lib/components/ui/card/card-content.jsx +0 -11
  198. package/src/core/lib/components/ui/card/card-description.jsx +0 -11
  199. package/src/core/lib/components/ui/card/card-footer.jsx +0 -11
  200. package/src/core/lib/components/ui/card/card-header.jsx +0 -19
  201. package/src/core/lib/components/ui/card/card-title.jsx +0 -11
  202. package/src/core/lib/components/ui/card/card.jsx +0 -17
  203. package/src/core/lib/components/ui/card/index.js +0 -25
  204. package/src/core/lib/components/ui/collapsible/collapsible-content.jsx +0 -7
  205. package/src/core/lib/components/ui/collapsible/collapsible-trigger.jsx +0 -7
  206. package/src/core/lib/components/ui/collapsible/collapsible.jsx +0 -7
  207. package/src/core/lib/components/ui/collapsible/index.js +0 -13
  208. package/src/core/lib/components/ui/dialog/dialog-close.jsx +0 -7
  209. package/src/core/lib/components/ui/dialog/dialog-description.jsx +0 -15
  210. package/src/core/lib/components/ui/dialog/dialog-footer.jsx +0 -23
  211. package/src/core/lib/components/ui/dialog/dialog-header.jsx +0 -11
  212. package/src/core/lib/components/ui/dialog/dialog-overlay.jsx +0 -15
  213. package/src/core/lib/components/ui/dialog/dialog-portal.jsx +0 -4
  214. package/src/core/lib/components/ui/dialog/dialog-title.jsx +0 -15
  215. package/src/core/lib/components/ui/popover/index.js +0 -28
  216. package/src/core/lib/components/ui/popover/popover-close.jsx +0 -7
  217. package/src/core/lib/components/ui/popover/popover-content.jsx +0 -22
  218. package/src/core/lib/components/ui/popover/popover-description.jsx +0 -11
  219. package/src/core/lib/components/ui/popover/popover-header.jsx +0 -11
  220. package/src/core/lib/components/ui/popover/popover-portal.jsx +0 -4
  221. package/src/core/lib/components/ui/popover/popover-title.jsx +0 -11
  222. package/src/core/lib/components/ui/popover/popover-trigger.jsx +0 -8
  223. package/src/core/lib/components/ui/popover/popover.jsx +0 -4
  224. package/src/core/lib/components/ui/select/index.js +0 -37
  225. package/src/core/lib/components/ui/select/select-content.jsx +0 -30
  226. package/src/core/lib/components/ui/select/select-group-heading.jsx +0 -17
  227. package/src/core/lib/components/ui/select/select-group.jsx +0 -15
  228. package/src/core/lib/components/ui/select/select-item.jsx +0 -26
  229. package/src/core/lib/components/ui/select/select-label.jsx +0 -11
  230. package/src/core/lib/components/ui/select/select-portal.jsx +0 -4
  231. package/src/core/lib/components/ui/select/select-scroll-down-button.jsx +0 -18
  232. package/src/core/lib/components/ui/select/select-scroll-up-button.jsx +0 -18
  233. package/src/core/lib/components/ui/select/select-separator.jsx +0 -15
  234. package/src/core/lib/components/ui/select/select-trigger.jsx +0 -25
  235. package/src/core/lib/components/ui/select/select.jsx +0 -4
  236. package/src/core/lib/components/ui/sheet/index.js +0 -34
  237. package/src/core/lib/components/ui/sheet/sheet-close.jsx +0 -7
  238. package/src/core/lib/components/ui/sheet/sheet-content.jsx +0 -35
  239. package/src/core/lib/components/ui/sheet/sheet-description.jsx +0 -15
  240. package/src/core/lib/components/ui/sheet/sheet-footer.jsx +0 -11
  241. package/src/core/lib/components/ui/sheet/sheet-header.jsx +0 -11
  242. package/src/core/lib/components/ui/sheet/sheet-overlay.jsx +0 -15
  243. package/src/core/lib/components/ui/sheet/sheet-portal.jsx +0 -4
  244. package/src/core/lib/components/ui/sheet/sheet-title.jsx +0 -15
  245. package/src/core/lib/components/ui/sheet/sheet-trigger.jsx +0 -7
  246. package/src/core/lib/components/ui/sheet/sheet.jsx +0 -4
  247. package/src/core/lib/components/ui/toggle/index.js +0 -8
  248. package/src/core/lib/components/ui/toggle/toggle.jsx +0 -36
  249. package/src/core/lib/components/ui/toggle-group/index.js +0 -10
  250. package/src/core/lib/components/ui/toggle-group/toggle-group-item.jsx +0 -29
  251. package/src/core/lib/components/ui/toggle-group/toggle-group.jsx +0 -43
  252. package/src/internals/canvas/StoryErrorBoundary.jsx +0 -47
  253. package/src/internals/canvas/componentIsolate.jsx +0 -144
  254. package/src/internals/canvas/componentSetIsolate.jsx +0 -300
  255. package/src/internals/canvas/widgets/InlineStoryRenderer.jsx +0 -129
  256. package/src/internals/canvas/widgets/InlineStoryRenderer.test.jsx +0 -68
  257. package/src/primer/ThemeSync.jsx +0 -73
  258. package/src/primer/index.js +0 -1
@@ -447,25 +447,31 @@ function buildDynamicSection(section, prefix, onNavigateToPage, onCreateAction)
447
447
  if (!isLocalDev) return null
448
448
  const isCanvasRoute = typeof window !== 'undefined' && window.location.pathname.includes('/canvas/')
449
449
  if (!isCanvasRoute) return null
450
- // Note: 'story' is intentionally NOT hidden here it now has a proper
451
- // artifact picker dialog (see WIDGET_TYPES_WITH_PICKER below). The
452
- // deprecated 'component' widget type is hidden in favor of 'story'.
453
- const hiddenTypes = new Set(['link-preview', 'image', 'figma-embed', 'codepen-embed', 'component', 'terminal-read', 'agent'])
454
- const items = Object.entries(widgetTypes).filter(([type]) => !hiddenTypes.has(type)).map(([type, def]) => ({
455
- id: `create-widget:${type}`,
456
- children: def.label,
457
- index_tags: [`Add ${def.label}`],
458
- keywords: ['add', 'widget', 'create', type, def.label.toLowerCase()],
459
- itemType: type,
460
- onClick: () => {
461
- if (WIDGET_TYPES_WITH_PICKER.has(type)) {
462
- // Open the artifact picker dialog instead of creating an empty widget.
463
- document.dispatchEvent(new CustomEvent('storyboard:open-widget-artifact-dialog', { detail: { type } }))
464
- } else {
465
- document.dispatchEvent(new CustomEvent('storyboard:canvas:add-widget', { detail: { type } }))
466
- }
467
- },
468
- }))
450
+ // 'story' is shown as "Component"the StoryPicker auto-upgrades
451
+ // multi-export stories to a component-set widget, so we expose a single
452
+ // unified "Component" entry instead of separate Story / Component Set items.
453
+ // The deprecated 'component' widget type and the raw 'component-set' entry
454
+ // are both hidden in favor of this consolidated flow.
455
+ const hiddenTypes = new Set(['link-preview', 'image', 'figma-embed', 'codepen-embed', 'component', 'component-set', 'terminal-read', 'agent'])
456
+ const labelOverrides = { story: 'Component' }
457
+ const items = Object.entries(widgetTypes).filter(([type]) => !hiddenTypes.has(type)).map(([type, def]) => {
458
+ const label = labelOverrides[type] || def.label
459
+ return {
460
+ id: `create-widget:${type}`,
461
+ children: label,
462
+ index_tags: [`Add ${label}`],
463
+ keywords: ['add', 'widget', 'create', type, label.toLowerCase(), def.label.toLowerCase()],
464
+ itemType: type === 'story' ? 'component' : type,
465
+ onClick: () => {
466
+ if (WIDGET_TYPES_WITH_PICKER.has(type)) {
467
+ // Open the artifact picker dialog instead of creating an empty widget.
468
+ document.dispatchEvent(new CustomEvent('storyboard:open-widget-artifact-dialog', { detail: { type } }))
469
+ } else {
470
+ document.dispatchEvent(new CustomEvent('storyboard:canvas:add-widget', { detail: { type } }))
471
+ }
472
+ },
473
+ }
474
+ })
469
475
 
470
476
  // Build agent submenu from canvas.agents config
471
477
  const subPages = []
@@ -0,0 +1,150 @@
1
+ /**
2
+ * CreateArtifactForm — shared form body for the artifact create UI.
3
+ *
4
+ * Wraps `ArtifactForm` with the data-loading, gh-author prefill, payload
5
+ * shaping, and post-submit navigation logic. Used by both the in-app
6
+ * modal (`CreateDialog`) and the standalone full-page route
7
+ * (`CreatePage`). Picker mode (no `type`) is supported — ArtifactForm
8
+ * renders its TypeSelector and the submit handler resolves the schema
9
+ * from the form callback.
10
+ */
11
+ import { useState, useEffect, useMemo } from 'react'
12
+ import ArtifactForm, { ARTIFACT_SCHEMAS, isFieldVisible } from '../ArtifactForm/ArtifactForm.jsx'
13
+ import { resolveSchemaKey, withBase } from './createHelpers.js'
14
+
15
+ export default function CreateArtifactForm({
16
+ type,
17
+ basePath,
18
+ onClose,
19
+ hideHeader = true,
20
+ }) {
21
+ const schemaKey = resolveSchemaKey(type)
22
+ const isPicker = schemaKey === null
23
+ const fixedSchema = schemaKey ? ARTIFACT_SCHEMAS[schemaKey] : null
24
+
25
+ const [prototypes, setPrototypes] = useState([])
26
+ const [partials, setPartials] = useState([])
27
+ const [ghLogin, setGhLogin] = useState(null)
28
+
29
+ // In picker mode we don't yet know the schema, so load everything that
30
+ // any schema might need. For a fixed type we only load what's required.
31
+ const needsPrototypes = useMemo(() => {
32
+ if (isPicker) return true
33
+ return fixedSchema?.fields.some(f => f.dynamic === 'prototypes') || false
34
+ }, [isPicker, fixedSchema])
35
+
36
+ const needsPartials = useMemo(() => {
37
+ if (isPicker) return true
38
+ return fixedSchema?.fields.some(f => f.dynamic === 'partials') || false
39
+ }, [isPicker, fixedSchema])
40
+
41
+ const needsAuthor = useMemo(() => {
42
+ if (isPicker) return true
43
+ return fixedSchema?.fields.some(f => f.name === 'author') || false
44
+ }, [isPicker, fixedSchema])
45
+
46
+ useEffect(() => {
47
+ if (!needsPrototypes) return
48
+ const apiBase = (basePath || '/').replace(/\/+$/, '')
49
+ fetch(`${apiBase}/_storyboard/artifact/list?type=prototype`)
50
+ .then(r => (r.ok ? r.json() : null))
51
+ .then(data => { if (data?.items) setPrototypes(data.items.map(p => p.name)) })
52
+ .catch(() => {})
53
+ }, [needsPrototypes, basePath])
54
+
55
+ useEffect(() => {
56
+ if (!needsPartials) return
57
+ const apiBase = (basePath || '/').replace(/\/+$/, '')
58
+ fetch(`${apiBase}/_storyboard/workshop/prototypes`)
59
+ .then(r => (r.ok ? r.json() : null))
60
+ .then(data => {
61
+ if (!data?.partials) return
62
+ // Group by scope+kind: "Templates" / "Recipes" for global, "<proto> / Templates" etc. for local.
63
+ const opts = data.partials.map(p => {
64
+ const kindLabel = p.kind === 'recipe' ? 'Recipes' : 'Templates'
65
+ const group = p.scope === 'global'
66
+ ? kindLabel
67
+ : `${p.folder ? p.folder + ' / ' : ''}${p.prototype || 'local'} / ${kindLabel}`
68
+ return { value: p.id, label: p.name, group }
69
+ })
70
+ setPartials(opts)
71
+ })
72
+ .catch(() => {})
73
+ }, [needsPartials, basePath])
74
+
75
+ useEffect(() => {
76
+ if (!needsAuthor) return
77
+ const apiBase = (basePath || '/').replace(/\/+$/, '')
78
+ fetch(`${apiBase}/_storyboard/git-user`)
79
+ .then(r => (r.ok ? r.json() : null))
80
+ .then(data => { if (data?.login) setGhLogin(data.login) })
81
+ .catch(() => {})
82
+ }, [needsAuthor, basePath])
83
+
84
+ // Late-arriving gh login is merged into empty fields inside ArtifactForm.
85
+ const initialValues = useMemo(() => {
86
+ if (!needsAuthor || !ghLogin) return undefined
87
+ return { author: ghLogin }
88
+ }, [needsAuthor, ghLogin])
89
+
90
+ if (!isPicker && !fixedSchema) return null
91
+
92
+ async function handleSubmit({ type: t, values }) {
93
+ const apiBase = (basePath || '/').replace(/\/+$/, '')
94
+ const endpoint = `${apiBase}/_storyboard/artifact/`
95
+
96
+ // In picker mode the schema is decided by the form; resolve it now.
97
+ const submittedSchema = ARTIFACT_SCHEMAS[t] || fixedSchema
98
+ if (!submittedSchema) throw new Error(`Unknown artifact type: ${t}`)
99
+
100
+ const payload = { type: t }
101
+ for (const field of submittedSchema.fields) {
102
+ // Skip UI-only fields (e.g. the `external` toggle on prototype —
103
+ // the server infers external mode from the presence of `url`).
104
+ if (field.uiOnly) continue
105
+ // Skip fields hidden by `showIf` so we never send a stale value
106
+ // for a field the user can't see (e.g. `url` when external is off).
107
+ if (!isFieldVisible(field, values)) continue
108
+ const val = values[field.name]
109
+ if (field.type === 'checkbox') {
110
+ if (val) payload[field.name] = true
111
+ } else if (typeof val === 'string') {
112
+ const trimmed = val.trim()
113
+ if (trimmed) payload[field.name] = trimmed
114
+ } else if (val != null) {
115
+ payload[field.name] = val
116
+ }
117
+ }
118
+
119
+ const res = await fetch(endpoint, {
120
+ method: 'POST',
121
+ headers: { 'Content-Type': 'application/json' },
122
+ body: JSON.stringify(payload),
123
+ })
124
+ if (!res.ok) {
125
+ const data = await res.json().catch(() => null)
126
+ throw new Error(data?.error || `Request failed (${res.status})`)
127
+ }
128
+ const data = await res.json().catch(() => ({}))
129
+ // Navigate only if the server gave us an explicit route. Types without
130
+ // a route (object/record/component) just close the form.
131
+ if (data.route) {
132
+ window.location.href = withBase(basePath, data.route)
133
+ } else {
134
+ onClose?.()
135
+ }
136
+ }
137
+
138
+ return (
139
+ <ArtifactForm
140
+ type={isPicker ? undefined : schemaKey}
141
+ onSubmit={handleSubmit}
142
+ onCancel={onClose}
143
+ dynamicOptions={{ prototypes, partials }}
144
+ initialValues={initialValues}
145
+ hideHeader={hideHeader}
146
+ />
147
+ )
148
+ }
149
+
150
+ export { ARTIFACT_SCHEMAS }
@@ -1,125 +1,35 @@
1
1
  /**
2
- * CreateDialog — Primer Dialog wrapper around the unified ArtifactForm.
2
+ * CreateDialog — Primer Dialog wrapper around CreateArtifactForm.
3
3
  *
4
- * The form, fields, and validation are entirely schema-driven via
5
- * ARTIFACT_SCHEMAS. This component supplies modal chrome (Primer Dialog),
6
- * fetches dynamic select options (e.g. prototype list), POSTs to the
7
- * artifact API, and navigates to the resulting route on success.
4
+ * The form, fields, validation, data-loading, and submission live in
5
+ * `CreateArtifactForm`. This component just supplies the modal chrome.
8
6
  */
9
- import { useState, useEffect, useMemo } from 'react'
10
- import { Dialog } from '@primer/react/experimental'
11
- import ArtifactForm, { ARTIFACT_SCHEMAS } from '../ArtifactForm/ArtifactForm.jsx'
12
-
13
- // Map UI command-palette type ids (PascalCase) to schema keys (lowercase)
14
- const TYPE_KEY_MAP = {
15
- Prototype: 'prototype', Canvas: 'canvas', Component: 'component',
16
- Flow: 'flow', Object: 'object', Record: 'record', Page: 'page',
17
- }
18
-
19
- function withBase(base, route) {
20
- const b = (base || '/').replace(/\/+$/, '')
21
- const r = route?.startsWith('/') ? route : `/${route || ''}`
22
- return b === '' || b === '/' ? r : `${b}${r}`
23
- }
7
+ import Dialog from '../Dialog.jsx'
8
+ import CreateArtifactForm, { ARTIFACT_SCHEMAS } from './CreateArtifactForm.jsx'
9
+ import { resolveSchemaKey, TYPE_KEY_MAP } from './createHelpers.js'
24
10
 
25
11
  export default function CreateDialog({ type, basePath, onClose }) {
26
- const schemaKey = type ? (TYPE_KEY_MAP[type] || String(type).toLowerCase()) : null
27
- const schema = schemaKey ? ARTIFACT_SCHEMAS[schemaKey] : null
28
- const [prototypes, setPrototypes] = useState([])
29
- const [partials, setPartials] = useState([])
30
-
31
- const needsPrototypes = useMemo(() => {
32
- if (!schema) return false
33
- return schema.fields.some(f => f.dynamic === 'prototypes')
34
- }, [schema])
12
+ // `type === null`/`undefined` means "no dialog requested" render nothing.
13
+ // Explicit `'Any'` (or empty string) means picker mode.
14
+ if (type == null) return null
35
15
 
36
- const needsPartials = useMemo(() => {
37
- if (!schema) return false
38
- return schema.fields.some(f => f.dynamic === 'partials')
39
- }, [schema])
40
-
41
- useEffect(() => {
42
- if (!needsPrototypes || !schemaKey) return
43
- const apiBase = (basePath || '/').replace(/\/+$/, '')
44
- fetch(`${apiBase}/_storyboard/artifact/list?type=prototype`)
45
- .then(r => (r.ok ? r.json() : null))
46
- .then(data => { if (data?.items) setPrototypes(data.items.map(p => p.name)) })
47
- .catch(() => {})
48
- }, [needsPrototypes, schemaKey, basePath])
49
-
50
- useEffect(() => {
51
- if (!needsPartials || !schemaKey) return
52
- const apiBase = (basePath || '/').replace(/\/+$/, '')
53
- fetch(`${apiBase}/_storyboard/workshop/prototypes`)
54
- .then(r => (r.ok ? r.json() : null))
55
- .then(data => {
56
- if (!data?.partials) return
57
- // Map workshop format → grouped select options.
58
- // Group by scope+kind: "Templates" / "Recipes" for global, "<proto> / Templates" etc. for local.
59
- const opts = data.partials.map(p => {
60
- const kindLabel = p.kind === 'recipe' ? 'Recipes' : 'Templates'
61
- const group = p.scope === 'global'
62
- ? kindLabel
63
- : `${p.folder ? p.folder + ' / ' : ''}${p.prototype || 'local'} / ${kindLabel}`
64
- return { value: p.id, label: p.name, group }
65
- })
66
- setPartials(opts)
67
- })
68
- .catch(() => {})
69
- }, [needsPartials, schemaKey, basePath])
70
-
71
- if (!schemaKey || !schema) return null
72
-
73
- async function handleSubmit({ type: t, values }) {
74
- const apiBase = (basePath || '/').replace(/\/+$/, '')
75
- const endpoint = `${apiBase}/_storyboard/artifact/`
76
-
77
- // Build payload — include checkboxes that are true, trimmed strings, raw selects
78
- const payload = { type: t }
79
- for (const field of schema.fields) {
80
- const val = values[field.name]
81
- if (field.type === 'checkbox') {
82
- if (val) payload[field.name] = true
83
- } else if (typeof val === 'string') {
84
- const trimmed = val.trim()
85
- if (trimmed) payload[field.name] = trimmed
86
- } else if (val != null) {
87
- payload[field.name] = val
88
- }
89
- }
16
+ const schemaKey = resolveSchemaKey(type)
17
+ const isPicker = schemaKey === null
18
+ const schema = schemaKey ? ARTIFACT_SCHEMAS[schemaKey] : null
90
19
 
91
- const res = await fetch(endpoint, {
92
- method: 'POST',
93
- headers: { 'Content-Type': 'application/json' },
94
- body: JSON.stringify(payload),
95
- })
96
- if (!res.ok) {
97
- const data = await res.json().catch(() => null)
98
- throw new Error(data?.error || `Request failed (${res.status})`)
99
- }
100
- const data = await res.json().catch(() => ({}))
101
- // Only navigate when the server gives us an explicit route. Falling back
102
- // to data.path here is wrong — that's a filesystem path (e.g. "src/canvas"),
103
- // not a URL route. Types without a route (object/record/component) just close.
104
- if (data.route) {
105
- window.location.href = withBase(basePath, data.route)
106
- } else {
107
- onClose?.()
108
- }
109
- }
20
+ if (!isPicker && !schema) return null
110
21
 
111
22
  return (
112
23
  <Dialog
113
- title={`New ${schema.label}`}
114
- subtitle={schema.description}
24
+ title={isPicker ? 'New artifact' : `New ${schema.label}`}
25
+ subtitle={isPicker ? 'Pick an artifact type to create.' : schema.description}
115
26
  onClose={onClose}
116
27
  width="large"
117
28
  >
118
- <ArtifactForm
119
- type={schemaKey}
120
- onSubmit={handleSubmit}
121
- onCancel={onClose}
122
- dynamicOptions={{ prototypes, partials }}
29
+ <CreateArtifactForm
30
+ type={type}
31
+ basePath={basePath}
32
+ onClose={onClose}
123
33
  hideHeader
124
34
  />
125
35
  </Dialog>
@@ -127,4 +37,4 @@ export default function CreateDialog({ type, basePath, onClose }) {
127
37
  }
128
38
 
129
39
  // Re-export for canvas/workspace integrations
130
- export { ARTIFACT_SCHEMAS as CREATE_SCHEMAS }
40
+ export { ARTIFACT_SCHEMAS as CREATE_SCHEMAS, TYPE_KEY_MAP }
@@ -8,7 +8,7 @@
8
8
  * `storyboard:canvas:add-widget` with full props.
9
9
  */
10
10
  import { useState, useMemo, useEffect, useRef } from 'react'
11
- import { Dialog } from '@primer/react/experimental'
11
+ import Dialog from '../Dialog.jsx'
12
12
  import { buildPrototypeIndex, listStories, getStoryData } from '../../core/index.js'
13
13
  import './WidgetArtifactDialog.css'
14
14
 
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Map UI command-palette type ids (PascalCase) to schema keys (lowercase),
3
+ * and normalise consumer-supplied `type` values to a schema key.
4
+ *
5
+ * Kept in its own file so React Fast Refresh stays happy in
6
+ * `CreateArtifactForm.jsx` and `CreateDialog.jsx` (component-only modules).
7
+ */
8
+ export const TYPE_KEY_MAP = {
9
+ Prototype: 'prototype', Canvas: 'canvas', Component: 'component',
10
+ Flow: 'flow', Object: 'object', Record: 'record', Page: 'page',
11
+ }
12
+
13
+ /**
14
+ * Returns the schema key (lowercase) for a consumer-supplied type, or
15
+ * `null` when no fixed type was requested (picker mode).
16
+ */
17
+ export function resolveSchemaKey(type) {
18
+ if (!type || type === 'Any' || String(type).toLowerCase() === 'any') return null
19
+ return TYPE_KEY_MAP[type] || String(type).toLowerCase()
20
+ }
21
+
22
+ /**
23
+ * Prepend `base` (e.g. `/storyboard/`) to an artifact route. Used by the
24
+ * post-create navigation in `CreateArtifactForm`.
25
+ */
26
+ export function withBase(base, route) {
27
+ const b = (base || '/').replace(/\/+$/, '')
28
+ const r = route?.startsWith('/') ? route : `/${route || ''}`
29
+ return b === '' || b === '/' ? r : `${b}${r}`
30
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * CreatePage — full-page route that renders only the schema-driven
3
+ * artifact creation form, centered on a clean background. Used by
4
+ * `storyboard create --ui` to give the user a focused creation surface
5
+ * without the workspace/viewfinder chrome.
6
+ *
7
+ * URL contract:
8
+ * /create → picker (TypeSelector visible)
9
+ * /create?type=canvas → fixed type
10
+ *
11
+ * The component renders as a fixed full-viewport overlay so it covers
12
+ * the chrome (BranchBar, FeatureFlagBanner, CoreUIBar toolbar) that
13
+ * StoryboardProvider mounts globally — even on this route. No close
14
+ * button: this surface is opened by the CLI specifically as the create
15
+ * flow; the user closes it by completing/cancelling the form.
16
+ *
17
+ * After successful create:
18
+ * - If the artifact has a route (prototype, canvas, page) → navigate to it.
19
+ * - Otherwise → navigate back to the workspace (basePath).
20
+ */
21
+ import { useMemo } from 'react'
22
+ import CreateArtifactForm from '../CommandPalette/CreateArtifactForm.jsx'
23
+ import styles from './CreatePage.module.css'
24
+
25
+ export default function CreatePage({ basePath }) {
26
+ const resolvedBase = basePath || (typeof window !== 'undefined' ? '/' : '/')
27
+
28
+ // Parse `?type=<type>` once. We don't react to changes — switching the
29
+ // type is handled in-form via the TypeSelector when in picker mode.
30
+ const type = useMemo(() => {
31
+ if (typeof window === 'undefined') return null
32
+ const params = new URLSearchParams(window.location.search)
33
+ return params.get('type') || null
34
+ }, [])
35
+
36
+ function handleClose() {
37
+ // Cancel = navigate back to the workspace. Hard navigation so we
38
+ // drop the `?type=…` query and any in-flight form state.
39
+ if (typeof window !== 'undefined') {
40
+ const target = (resolvedBase || '/').replace(/\/+$/, '/') || '/'
41
+ window.location.href = target
42
+ }
43
+ }
44
+
45
+ return (
46
+ <div className={styles.page}>
47
+ <div className={styles.card}>
48
+ <CreateArtifactForm
49
+ type={type}
50
+ basePath={resolvedBase}
51
+ onClose={handleClose}
52
+ hideHeader={false}
53
+ />
54
+ </div>
55
+ </div>
56
+ )
57
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Standalone create-only page styling.
3
+ *
4
+ * The page is a fixed full-viewport overlay so it covers the chrome
5
+ * (BranchBar, FeatureFlagBanner, CoreUIBar toolbar) that
6
+ * StoryboardProvider mounts globally on every prototype route. A high
7
+ * z-index keeps it above the toolbar specifically.
8
+ *
9
+ * The `.card` wrapper deliberately mirrors `.form` in ArtifactForm.module.css
10
+ * so the card looks identical to the in-app dialog body.
11
+ */
12
+
13
+ .page {
14
+ position: fixed;
15
+ inset: 0;
16
+ /* Sits above the BranchBar (10001), CoreUIBar toolbar (9999), and any
17
+ * other dev-overlay chrome StoryboardProvider mounts. The /create
18
+ * route is a focused creation surface — chrome would be a distraction. */
19
+ z-index: 2147483646;
20
+ display: flex;
21
+ align-items: flex-start;
22
+ justify-content: center;
23
+ padding: 2rem 1.5rem 4rem;
24
+ background: var(--bgColor-muted, #f6f8fa);
25
+ overflow: auto;
26
+ }
27
+
28
+ .card {
29
+ width: 100%;
30
+ max-width: 480px;
31
+ /* Center horizontally; vertical alignment is flex-start so tall forms
32
+ * scroll naturally instead of clipping the top. */
33
+ margin: auto;
34
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Dialog — internal adapter that exposes a Primer-style
3
+ * `<Dialog title subtitle onClose width height>` API on top of the
4
+ * framework-neutral core Dialog primitive (`core/lib/components/ui/dialog`).
5
+ *
6
+ * Lets the command palette mount its existing dialogs without pulling in
7
+ * `@primer/react/experimental`. See plan `.github/plans/style-separation.md`
8
+ * (Layer A).
9
+ *
10
+ * Semantics:
11
+ * - The dialog is open while mounted (caller controls visibility by
12
+ * conditionally rendering this component, matching Primer's behaviour).
13
+ * - Backdrop click + ESC fire `onClose`.
14
+ */
15
+ import * as Dialog from '../core/lib/components/ui/dialog/index.js'
16
+ import css from '../core/lib/components/ui/dialog/dialog.module.css'
17
+
18
+ export default function DialogAdapter({
19
+ title,
20
+ subtitle,
21
+ onClose,
22
+ width = 'medium',
23
+ height = 'auto',
24
+ children,
25
+ }) {
26
+ return (
27
+ <Dialog.Root open onOpenChange={(open) => { if (!open) onClose?.() }}>
28
+ <Dialog.Content width={width} height={height} aria-label={typeof title === 'string' ? title : undefined}>
29
+ {(title || subtitle) && (
30
+ <div className={css.header}>
31
+ <div className={css.titleGroup}>
32
+ {title && <h2 className={css.title}>{title}</h2>}
33
+ {subtitle && <p className={css.subtitle}>{subtitle}</p>}
34
+ </div>
35
+ <button
36
+ type="button"
37
+ className={css.closeBtn}
38
+ onClick={() => onClose?.()}
39
+ aria-label="Close"
40
+ >
41
+ <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
42
+ <path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.75.75 0 1 1 1.06 1.06L9.06 8l3.22 3.22a.75.75 0 1 1-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 0 1-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z" />
43
+ </svg>
44
+ </button>
45
+ </div>
46
+ )}
47
+ <div className={css.body}>{children}</div>
48
+ </Dialog.Content>
49
+ </Dialog.Root>
50
+ )
51
+ }