@dfosco/storyboard 0.7.1 → 0.9.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 (323) hide show
  1. package/dist/storyboard-ui.css +1 -1
  2. package/dist/storyboard-ui.js +14128 -10953
  3. package/dist/storyboard-ui.js.map +1 -1
  4. package/dist/tailwind.css +1 -1
  5. package/dist/tiny-canvas.css +1 -1
  6. package/package.json +138 -128
  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 +41 -0
  22. package/scaffold/src/library/_app.module.css +2 -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 +51 -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/canvas/style.css +16 -16
  32. package/src/core/artifact/operations.js +54 -15
  33. package/src/core/artifact/schemas/canvas.schema.json +7 -1
  34. package/src/core/autosync/server.test.js +1 -1
  35. package/src/core/canvas/hot-pool.js +21 -33
  36. package/src/core/canvas/materializer.js +39 -13
  37. package/src/core/canvas/selectedWidgets.js +1 -1
  38. package/src/core/canvas/server.js +376 -100
  39. package/src/core/canvas/server.test.js +472 -2
  40. package/src/core/canvas/terminal-server.js +144 -57
  41. package/src/core/canvas/undoRedo.js +174 -0
  42. package/src/core/canvas/undoRedo.test.js +250 -0
  43. package/src/core/cli/artifact.js +2 -0
  44. package/src/core/cli/buildManifest.js +34 -0
  45. package/src/core/cli/canvasRedo.js +44 -0
  46. package/src/core/cli/canvasUndo.js +40 -0
  47. package/src/core/cli/create.js +151 -23
  48. package/src/core/cli/dev.js +23 -35
  49. package/src/core/cli/index.js +21 -0
  50. package/src/core/cli/schemas.js +8 -4
  51. package/src/core/cli/serverUrl.js +24 -0
  52. package/src/core/cli/setup.js +14 -25
  53. package/src/core/cli/terminal-welcome.js +12 -6
  54. package/src/core/cli/updateVersion.js +42 -2
  55. package/src/core/cli/workleaf.js +140 -0
  56. package/src/core/comments/api.js +1 -1
  57. package/src/core/comments/queries.js +3 -0
  58. package/src/core/comments/ui/CommentWindow.jsx +3 -2
  59. package/src/core/comments/ui/comment-layout.css +14 -14
  60. package/src/core/data/artifactIndex.js +235 -0
  61. package/src/core/data/artifactIndex.test.js +203 -0
  62. package/src/core/data/artifactManifest.js +290 -0
  63. package/src/core/data/artifactManifest.test.js +207 -0
  64. package/src/core/data/viewfinder.js +47 -7
  65. package/src/core/index.js +24 -1
  66. package/src/core/inspector/highlighter.js +1 -1
  67. package/src/core/inspector/mouseMode.js +2 -2
  68. package/src/core/lib/components/ui/avatar/avatar-fallback.jsx +2 -2
  69. package/src/core/lib/components/ui/avatar/avatar-image.jsx +2 -2
  70. package/src/core/lib/components/ui/avatar/avatar.jsx +2 -2
  71. package/src/core/lib/components/ui/checkbox/checkbox.jsx +12 -9
  72. package/src/core/lib/components/ui/dialog/dialog-content.jsx +45 -31
  73. package/src/core/lib/components/ui/dialog/dialog-trigger.jsx +9 -5
  74. package/src/core/lib/components/ui/dialog/dialog.jsx +12 -3
  75. package/src/core/lib/components/ui/dialog/dialog.module.css +118 -0
  76. package/src/core/lib/components/ui/dialog/index.js +4 -34
  77. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.jsx +3 -3
  78. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.jsx +19 -9
  79. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-content.jsx +8 -8
  80. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.jsx +2 -2
  81. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-group.jsx +2 -2
  82. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-item.jsx +15 -4
  83. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-portal.jsx +2 -2
  84. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.jsx +2 -2
  85. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.jsx +14 -7
  86. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-separator.jsx +2 -2
  87. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.jsx +8 -4
  88. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.jsx +4 -4
  89. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub.jsx +2 -2
  90. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-trigger.jsx +8 -2
  91. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu.jsx +2 -2
  92. package/src/core/lib/components/ui/label/label.jsx +1 -2
  93. package/src/core/lib/components/ui/panel/panel-close.jsx +7 -4
  94. package/src/core/lib/components/ui/panel/panel-content.jsx +7 -9
  95. package/src/core/lib/components/ui/panel/panel-title.jsx +3 -3
  96. package/src/core/lib/components/ui/panel/panel.jsx +11 -2
  97. package/src/core/lib/components/ui/separator/separator.jsx +6 -4
  98. package/src/core/lib/components/ui/tooltip/tooltip-content.jsx +7 -6
  99. package/src/core/lib/components/ui/tooltip/tooltip-trigger.jsx +13 -6
  100. package/src/core/lib/components/ui/tooltip/tooltip.jsx +5 -5
  101. package/src/core/modes/modes.css +4 -4
  102. package/src/core/mountStoryboardCore.js +287 -83
  103. package/src/core/mountStoryboardCore.test.js +198 -0
  104. package/src/core/rename-watcher/config.json +1 -1
  105. package/src/core/rename-watcher/watcher.js +330 -37
  106. package/src/core/rename-watcher/watcher.test.js +253 -0
  107. package/src/core/scaffold/fragments.js +287 -0
  108. package/src/core/scaffold/fragments.test.js +401 -0
  109. package/src/core/scaffold/index.js +291 -0
  110. package/src/core/scaffold/migrateLegacyGitignore.js +157 -0
  111. package/src/core/scaffold/scaffold-integration.test.js +247 -0
  112. package/src/core/scaffold.js +4 -97
  113. package/src/core/stores/canvasInteractionStore.js +120 -0
  114. package/src/core/stores/canvasInteractionStore.test.js +126 -0
  115. package/src/core/stores/configSchema.js +64 -8
  116. package/src/core/stores/configSchema.test.js +3 -0
  117. package/src/core/stores/customerModeConfig.js +24 -3
  118. package/src/core/stores/customerModeConfig.test.js +46 -0
  119. package/src/core/stores/presentationStore.js +118 -0
  120. package/src/core/stores/presentationStore.test.js +114 -0
  121. package/src/core/stores/recentArtifacts.js +49 -2
  122. package/src/core/stores/recentArtifacts.test.js +59 -6
  123. package/src/core/stores/themeBootstrap.js +264 -0
  124. package/src/core/stores/themeBootstrap.test.js +211 -0
  125. package/src/core/stores/themeStore.test.js +154 -0
  126. package/src/core/stores/themeStore.ts +235 -220
  127. package/src/core/stores/uiConfig.js +10 -2
  128. package/src/core/styles/generateThemesCss.js +63 -0
  129. package/src/core/styles/tailwind.css +150 -94
  130. package/src/core/styles/workspace-chrome.css +13 -0
  131. package/src/core/tools/handlers/paletteTheme.js +11 -8
  132. package/src/core/ui/ActionMenuButton.jsx +2 -1
  133. package/src/core/ui/AutosyncMenuButton.css +7 -7
  134. package/src/core/ui/BranchSelect.module.css +5 -5
  135. package/src/core/ui/CanvasCreateMenu.jsx +1 -19
  136. package/src/core/ui/ChromeSlot.jsx +92 -0
  137. package/src/core/ui/ChromeSlot.test.jsx +140 -0
  138. package/src/core/ui/CoreUIBar.css +5 -5
  139. package/src/core/ui/CoreUIBar.jsx +170 -142
  140. package/src/core/ui/InspectorPanel.css +5 -5
  141. package/src/core/ui/InspectorPanel.jsx +9 -9
  142. package/src/core/ui/PwaInstallBanner.css +5 -5
  143. package/src/core/ui/PwaInstallBanner.jsx +5 -5
  144. package/src/core/ui/ThemeMenuButton.jsx +45 -30
  145. package/src/core/ui/core-ui-colors.css +28 -66
  146. package/src/core/ui/sidepanel.css +12 -12
  147. package/src/core/vite/server-plugin.js +13 -0
  148. package/src/core/workleaf/cleanup.js +102 -0
  149. package/src/core/workleaf/create.js +199 -0
  150. package/src/core/workleaf/delete.js +101 -0
  151. package/src/core/workleaf/list.js +57 -0
  152. package/src/core/workleaf/merge.js +238 -0
  153. package/src/core/workleaf/paths.js +146 -0
  154. package/src/core/workleaf/paths.test.js +230 -0
  155. package/src/core/workshop/features/createCanvas/CreateCanvasForm.jsx +130 -34
  156. package/src/core/workshop/features/createFlow/server.js +5 -1
  157. package/src/core/workshop/features/createPrototype/server.js +5 -1
  158. package/src/core/workshop/features/templateIndex.js +28 -1
  159. package/src/core/worktree/port.js +48 -5
  160. package/src/core/worktree/port.test.js +42 -0
  161. package/src/internals/ArtifactForm/ArtifactForm.jsx +173 -112
  162. package/src/internals/ArtifactForm/ArtifactForm.module.css +51 -69
  163. package/src/internals/ArtifactForm/artifactSchemas.js +40 -5
  164. package/src/internals/AuthModal/AuthModal.module.css +34 -34
  165. package/src/internals/BaseUiForm/BaseUiForm.jsx +293 -0
  166. package/src/internals/BaseUiForm/BaseUiForm.module.css +542 -0
  167. package/src/internals/BranchBar/BranchBar.module.css +4 -4
  168. package/src/internals/BranchesDropdown/BranchesDropdown.jsx +69 -0
  169. package/src/internals/BranchesDropdown/BranchesDropdown.module.css +84 -0
  170. package/src/internals/CommandPalette/CommandPalette.jsx +41 -29
  171. package/src/internals/CommandPalette/CreateArtifactForm.jsx +150 -0
  172. package/src/internals/CommandPalette/CreateDialog.jsx +20 -110
  173. package/src/internals/CommandPalette/WidgetArtifactDialog.css +16 -16
  174. package/src/internals/CommandPalette/WidgetArtifactDialog.jsx +1 -1
  175. package/src/internals/CommandPalette/command-palette.css +14 -14
  176. package/src/internals/CommandPalette/createHelpers.js +30 -0
  177. package/src/internals/CreatePage/CreatePage.jsx +57 -0
  178. package/src/internals/CreatePage/CreatePage.module.css +34 -0
  179. package/src/internals/Dialog.jsx +51 -0
  180. package/src/internals/FlowError.module.css +5 -5
  181. package/src/internals/PrototypeErrorBoundary.module.css +15 -15
  182. package/src/internals/SimpleWorkspace/SimpleWorkspace.jsx +534 -0
  183. package/src/internals/SimpleWorkspace/SimpleWorkspace.module.css +323 -0
  184. package/src/internals/Tooltip.jsx +39 -0
  185. package/src/internals/Viewfinder.jsx +229 -64
  186. package/src/internals/Viewfinder.module.css +253 -182
  187. package/src/internals/canvas/CanvasControls.module.css +11 -11
  188. package/src/internals/canvas/CanvasPage.bridge.test.jsx +11 -37
  189. package/src/internals/canvas/CanvasPage.jsx +358 -217
  190. package/src/internals/canvas/CanvasPage.module.css +20 -19
  191. package/src/internals/canvas/CanvasPage.multiselect.test.jsx +39 -26
  192. package/src/internals/canvas/CanvasToolbar.module.css +11 -11
  193. package/src/internals/canvas/ConnectorLayer.jsx +39 -2
  194. package/src/internals/canvas/ConnectorLayer.module.css +10 -10
  195. package/src/internals/canvas/PageSelector.jsx +15 -1
  196. package/src/internals/canvas/PageSelector.module.css +41 -27
  197. package/src/internals/canvas/canvasApi.js +40 -5
  198. package/src/internals/canvas/canvasTheme.js +22 -111
  199. package/src/internals/canvas/isolation/createIsolationMiddleware.js +68 -0
  200. package/src/internals/canvas/isolation/mountIsolationApp.jsx +88 -0
  201. package/src/internals/canvas/isolation/prototypeRoutes.jsx +140 -0
  202. package/src/internals/canvas/isolation/prototypesEntry.jsx +28 -0
  203. package/src/internals/canvas/isolation/storiesEntry.jsx +20 -0
  204. package/src/internals/canvas/isolation/storyRoutes.jsx +55 -0
  205. package/src/internals/canvas/useUndoRedo.js +75 -60
  206. package/src/internals/canvas/useUndoRedo.test.js +46 -178
  207. package/src/internals/canvas/widgets/CodePenEmbed.jsx +16 -36
  208. package/src/internals/canvas/widgets/CodePenEmbed.module.css +12 -12
  209. package/src/internals/canvas/widgets/CropOverlay.module.css +23 -23
  210. package/src/internals/canvas/widgets/ExpandedPane.module.css +8 -8
  211. package/src/internals/canvas/widgets/ExpandedPaneTopBar.jsx +1 -1
  212. package/src/internals/canvas/widgets/FigmaEmbed.jsx +15 -36
  213. package/src/internals/canvas/widgets/FigmaEmbed.module.css +15 -15
  214. package/src/internals/canvas/widgets/FrozenTerminalOverlay.jsx +30 -21
  215. package/src/internals/canvas/widgets/FrozenTerminalOverlay.module.css +5 -5
  216. package/src/internals/canvas/widgets/ImageWidget.jsx +7 -2
  217. package/src/internals/canvas/widgets/ImageWidget.module.css +3 -3
  218. package/src/internals/canvas/widgets/LinkPreview.module.css +79 -80
  219. package/src/internals/canvas/widgets/MarkdownBlock.module.css +31 -31
  220. package/src/internals/canvas/widgets/PromptWidget.jsx +2 -16
  221. package/src/internals/canvas/widgets/PromptWidget.module.css +33 -33
  222. package/src/internals/canvas/widgets/PrototypeEmbed.jsx +92 -17
  223. package/src/internals/canvas/widgets/PrototypeEmbed.module.css +58 -56
  224. package/src/internals/canvas/widgets/PrototypeEmbed.test.jsx +118 -4
  225. package/src/internals/canvas/widgets/ResizeHandle.module.css +4 -4
  226. package/src/internals/canvas/widgets/StorySetWidget.jsx +53 -25
  227. package/src/internals/canvas/widgets/StoryWidget.jsx +71 -56
  228. package/src/internals/canvas/widgets/StoryWidget.module.css +22 -20
  229. package/src/internals/canvas/widgets/TerminalReadWidget.jsx +13 -15
  230. package/src/internals/canvas/widgets/TerminalReadWidget.module.css +4 -4
  231. package/src/internals/canvas/widgets/TerminalWidget.jsx +56 -45
  232. package/src/internals/canvas/widgets/TerminalWidget.module.css +10 -10
  233. package/src/internals/canvas/widgets/TilesWidget.module.css +17 -17
  234. package/src/internals/canvas/widgets/WidgetChrome.jsx +1 -1
  235. package/src/internals/canvas/widgets/WidgetChrome.module.css +76 -61
  236. package/src/internals/canvas/widgets/WidgetWrapper.module.css +2 -2
  237. package/src/internals/canvas/widgets/embedOverlay.module.css +14 -4
  238. package/src/internals/canvas/widgets/embedTheme.js +12 -104
  239. package/src/internals/canvas/widgets/expandUtils.js +19 -19
  240. package/src/internals/canvas/widgets/normalizeLegacyEmbedSrc.js +74 -0
  241. package/src/internals/canvas/widgets/widgetConfig.js +5 -5
  242. package/src/internals/context.jsx +22 -1
  243. package/src/internals/context.test.jsx +47 -0
  244. package/src/internals/hooks/useArtifactIndex.js +31 -0
  245. package/src/internals/hooks/useArtifactIndex.test.js +45 -0
  246. package/src/internals/hooks/usePrototypeReloadGuard.js +14 -8
  247. package/src/internals/index.js +6 -0
  248. package/src/internals/loadConsumerWrapper.js +60 -0
  249. package/src/internals/story/ComponentSetPage.jsx +75 -34
  250. package/src/internals/story/ComponentSetPage.module.css +14 -14
  251. package/src/internals/story/StoryPage.jsx +16 -25
  252. package/src/internals/story/StoryPage.module.css +2 -2
  253. package/src/internals/vite/data-plugin.js +317 -149
  254. package/src/internals/vite/data-plugin.test.js +11 -11
  255. package/{scaffold/storyboard.config.json → storyboard.config.json} +25 -0
  256. package/terminal.config.json +1 -1
  257. package/src/core/lib/components/ui/card/card-action.jsx +0 -11
  258. package/src/core/lib/components/ui/card/card-content.jsx +0 -11
  259. package/src/core/lib/components/ui/card/card-description.jsx +0 -11
  260. package/src/core/lib/components/ui/card/card-footer.jsx +0 -11
  261. package/src/core/lib/components/ui/card/card-header.jsx +0 -19
  262. package/src/core/lib/components/ui/card/card-title.jsx +0 -11
  263. package/src/core/lib/components/ui/card/card.jsx +0 -17
  264. package/src/core/lib/components/ui/card/index.js +0 -25
  265. package/src/core/lib/components/ui/collapsible/collapsible-content.jsx +0 -7
  266. package/src/core/lib/components/ui/collapsible/collapsible-trigger.jsx +0 -7
  267. package/src/core/lib/components/ui/collapsible/collapsible.jsx +0 -7
  268. package/src/core/lib/components/ui/collapsible/index.js +0 -13
  269. package/src/core/lib/components/ui/dialog/dialog-close.jsx +0 -7
  270. package/src/core/lib/components/ui/dialog/dialog-description.jsx +0 -15
  271. package/src/core/lib/components/ui/dialog/dialog-footer.jsx +0 -23
  272. package/src/core/lib/components/ui/dialog/dialog-header.jsx +0 -11
  273. package/src/core/lib/components/ui/dialog/dialog-overlay.jsx +0 -15
  274. package/src/core/lib/components/ui/dialog/dialog-portal.jsx +0 -4
  275. package/src/core/lib/components/ui/dialog/dialog-title.jsx +0 -15
  276. package/src/core/lib/components/ui/popover/index.js +0 -28
  277. package/src/core/lib/components/ui/popover/popover-close.jsx +0 -7
  278. package/src/core/lib/components/ui/popover/popover-content.jsx +0 -22
  279. package/src/core/lib/components/ui/popover/popover-description.jsx +0 -11
  280. package/src/core/lib/components/ui/popover/popover-header.jsx +0 -11
  281. package/src/core/lib/components/ui/popover/popover-portal.jsx +0 -4
  282. package/src/core/lib/components/ui/popover/popover-title.jsx +0 -11
  283. package/src/core/lib/components/ui/popover/popover-trigger.jsx +0 -8
  284. package/src/core/lib/components/ui/popover/popover.jsx +0 -4
  285. package/src/core/lib/components/ui/select/index.js +0 -37
  286. package/src/core/lib/components/ui/select/select-content.jsx +0 -30
  287. package/src/core/lib/components/ui/select/select-group-heading.jsx +0 -17
  288. package/src/core/lib/components/ui/select/select-group.jsx +0 -15
  289. package/src/core/lib/components/ui/select/select-item.jsx +0 -26
  290. package/src/core/lib/components/ui/select/select-label.jsx +0 -11
  291. package/src/core/lib/components/ui/select/select-portal.jsx +0 -4
  292. package/src/core/lib/components/ui/select/select-scroll-down-button.jsx +0 -18
  293. package/src/core/lib/components/ui/select/select-scroll-up-button.jsx +0 -18
  294. package/src/core/lib/components/ui/select/select-separator.jsx +0 -15
  295. package/src/core/lib/components/ui/select/select-trigger.jsx +0 -25
  296. package/src/core/lib/components/ui/select/select.jsx +0 -4
  297. package/src/core/lib/components/ui/sheet/index.js +0 -34
  298. package/src/core/lib/components/ui/sheet/sheet-close.jsx +0 -7
  299. package/src/core/lib/components/ui/sheet/sheet-content.jsx +0 -35
  300. package/src/core/lib/components/ui/sheet/sheet-description.jsx +0 -15
  301. package/src/core/lib/components/ui/sheet/sheet-footer.jsx +0 -11
  302. package/src/core/lib/components/ui/sheet/sheet-header.jsx +0 -11
  303. package/src/core/lib/components/ui/sheet/sheet-overlay.jsx +0 -15
  304. package/src/core/lib/components/ui/sheet/sheet-portal.jsx +0 -4
  305. package/src/core/lib/components/ui/sheet/sheet-title.jsx +0 -15
  306. package/src/core/lib/components/ui/sheet/sheet-trigger.jsx +0 -7
  307. package/src/core/lib/components/ui/sheet/sheet.jsx +0 -4
  308. package/src/core/lib/components/ui/toggle/index.js +0 -8
  309. package/src/core/lib/components/ui/toggle/toggle.jsx +0 -36
  310. package/src/core/lib/components/ui/toggle-group/index.js +0 -10
  311. package/src/core/lib/components/ui/toggle-group/toggle-group-item.jsx +0 -29
  312. package/src/core/lib/components/ui/toggle-group/toggle-group.jsx +0 -43
  313. package/src/internals/canvas/StoryErrorBoundary.jsx +0 -47
  314. package/src/internals/canvas/WebGLContextPool.jsx +0 -291
  315. package/src/internals/canvas/WebGLContextPool.test.jsx +0 -165
  316. package/src/internals/canvas/componentIsolate.jsx +0 -144
  317. package/src/internals/canvas/componentSetIsolate.jsx +0 -300
  318. package/src/internals/canvas/widgets/ComponentSetWidget.module.css +0 -89
  319. package/src/internals/canvas/widgets/InlineStoryRenderer.jsx +0 -129
  320. package/src/internals/canvas/widgets/InlineStoryRenderer.test.jsx +0 -68
  321. package/src/internals/canvas/widgets/StorySetWidget.module.css +0 -89
  322. package/src/primer/ThemeSync.jsx +0 -73
  323. package/src/primer/index.js +0 -1
@@ -0,0 +1,41 @@
1
+ import { Suspense } from 'react'
2
+ import { Outlet } from 'react-router-dom'
3
+ // Direct imports bypass the barrel re-export in @dfosco/storyboard/index.js.
4
+ // The barrel eagerly re-exports CanvasPage (3K lines), CommandPalette (1.3K lines),
5
+ // and Viewfinder (1.5K lines) — all resolved by Vite dev even though _app only
6
+ // needs StoryboardProvider and AppErrorBoundary.
7
+ import StoryboardProvider from '@dfosco/storyboard/context'
8
+ import { AppErrorBoundary } from '@dfosco/storyboard/error-boundary'
9
+ import './_app.module.css'
10
+ import '@dfosco/storyboard/canvas/style.css'
11
+
12
+ function PageLoading() {
13
+ return (
14
+ <div style={{
15
+ display: 'flex',
16
+ alignItems: 'center',
17
+ justifyContent: 'center',
18
+ minHeight: '100vh',
19
+ backgroundColor: 'var(--bgColor-default, #0d1117)',
20
+ }}>
21
+ <svg width="48" height="48" viewBox="0 0 24 24" fill="none" style={{ animation: 'spin 0.8s linear infinite' }}>
22
+ <circle cx="12" cy="12" r="10" stroke="var(--fgColor-muted, #484f58)" strokeWidth="2.5" opacity="0.25" />
23
+ <path d="M12 2a10 10 0 0 1 10 10" stroke="var(--fgColor-default, #e6edf3)" strokeWidth="2.5" strokeLinecap="round" />
24
+ </svg>
25
+ <style>{`@keyframes spin { to { transform: rotate(360deg) } }`}</style>
26
+ </div>
27
+ )
28
+ }
29
+
30
+ export default function App() {
31
+ return (
32
+ <StoryboardProvider>
33
+ <Suspense fallback={<PageLoading />}>
34
+ <Outlet />
35
+ </Suspense>
36
+ </StoryboardProvider>
37
+ )
38
+ }
39
+
40
+ // Last-resort error boundary — generouted wires this via _app?.Catch
41
+ export const Catch = AppErrorBoundary
@@ -0,0 +1,2 @@
1
+ /* Placeholder — _app.jsx imports this for side effects. Add scoped
2
+ * classes here as needed. */
@@ -0,0 +1,10 @@
1
+ import { CreatePage } from '@dfosco/storyboard'
2
+
3
+ /**
4
+ * `/create` route — standalone schema-driven artifact creator.
5
+ * Opened by `storyboard create --ui` to give a focused creation surface
6
+ * without the workspace chrome.
7
+ */
8
+ export default function CreateRoute() {
9
+ return <CreatePage basePath={import.meta.env.BASE_URL} />
10
+ }
@@ -0,0 +1,14 @@
1
+ import { SimpleWorkspace } from '@dfosco/storyboard'
2
+
3
+ const pageModules = import.meta.glob('/src/prototypes/*/*.jsx')
4
+
5
+ export default function IndexPage() {
6
+ return (
7
+ <SimpleWorkspace
8
+ title="Storyboard"
9
+ subtitle="Collaborative workspace for design & code"
10
+ pageModules={pageModules}
11
+ basePath={import.meta.env.BASE_URL}
12
+ />
13
+ )
14
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * DO NOT EDIT — storyboard-owned library file.
3
+ *
4
+ * Synced from @dfosco/storyboard. Customize via storyboard.config.json,
5
+ * src/_prototype.jsx, src/_story.jsx, or src/library/_app.jsx.
6
+ * See src/library/README.md for the full customization surface.
7
+ *
8
+ * Workspace SPA entry. Mounts the React Router with the file-based prototype
9
+ * routes (see ./routes.jsx) and bootstraps storyboard core (devtools, comments,
10
+ * theming, etc.) via mountStoryboardCore.
11
+ */
12
+ import { StrictMode } from 'react'
13
+ import { createRoot } from 'react-dom/client'
14
+ import { RouterProvider, createBrowserRouter } from 'react-router-dom'
15
+ import { routes } from './routes'
16
+
17
+ import { installHashPreserver } from '@dfosco/storyboard/hash-preserver'
18
+ import { mountStoryboardCore } from '@dfosco/storyboard/core'
19
+ import '@dfosco/storyboard/comments/ui/comment-layout.css'
20
+ import storyboardConfig from '../../storyboard.config.json'
21
+ import { installPrototypeOverlayIsolation } from '../prototypeOverlayIsolation.js'
22
+
23
+ // Suppress Vite error overlays that originate in /src/prototypes/ so a
24
+ // broken prototype can't take down the canvas surface — see
25
+ // .agents/plans/vite-isolation.md and src/prototypeOverlayIsolation.js.
26
+ installPrototypeOverlayIsolation()
27
+
28
+ // Redirect after canvas creation — Vite full-reloads when a new
29
+ // .canvas.jsonl is created. The form sets ?redirect=/canvas/name
30
+ // which survives the reload. We navigate once the route is registered.
31
+ const redirectParam = new URLSearchParams(window.location.search).get('redirect')
32
+ if (redirectParam) {
33
+ const base = (import.meta.env.BASE_URL || '/').replace(/\/$/, '')
34
+ window.location.replace(base + redirectParam)
35
+ }
36
+
37
+ const router = createBrowserRouter(routes, {
38
+ basename: import.meta.env.BASE_URL,
39
+ })
40
+
41
+ installHashPreserver(router, import.meta.env.BASE_URL)
42
+ mountStoryboardCore(storyboardConfig, { basePath: import.meta.env.BASE_URL })
43
+
44
+ const rootElement = document.getElementById('root')
45
+ const root = createRoot(rootElement)
46
+
47
+ root.render(
48
+ <StrictMode>
49
+ <RouterProvider router={router} />
50
+ </StrictMode>
51
+ )
@@ -0,0 +1,57 @@
1
+ /**
2
+ * DO NOT EDIT — storyboard-owned library file.
3
+ *
4
+ * Synced from @dfosco/storyboard. Customize via src/_prototype.jsx.
5
+ * See src/library/README.md for the full customization surface.
6
+ *
7
+ * Isolated prototypes entry point (see .agents/plans/vite-isolation.md).
8
+ *
9
+ * This is the second Vite HTML entry — prototypes.html loads this file.
10
+ * Goal: physically separate the prototypes module graph + Rollup chunk graph
11
+ * from the canvas app so a broken prototype cannot poison canvas surfaces.
12
+ *
13
+ * Uses createHashRouter so prototype routes live in the URL hash of
14
+ * prototypes.html (e.g. prototypes.html#/MyProto/SignupForm). This avoids
15
+ * needing any server rewrite — every iframe load resolves to the same
16
+ * prototypes.html document, then the router renders the matched prototype.
17
+ *
18
+ * Does NOT install the workspace-level hash preserver — that hook is the
19
+ * canvas/workshop SPA concern, not relevant inside an isolated prototype.
20
+ *
21
+ * The prototype provider chain (Primer / Tailwind / fonts / design tokens)
22
+ * comes from the consumer's `src/_prototype.jsx` — wired by the route layout
23
+ * inside ./routes.jsx. When absent, prototypes render with no shared providers.
24
+ */
25
+ import { StrictMode } from 'react'
26
+ import { createRoot } from 'react-dom/client'
27
+ import { RouterProvider, createHashRouter } from 'react-router-dom'
28
+ import { routes, getRoutesForProto } from './routes'
29
+ import { mountStoryboardCore } from '@dfosco/storyboard/core'
30
+ import '@dfosco/storyboard/comments/ui/comment-layout.css'
31
+ import storyboardConfig from '../../storyboard.config.json'
32
+
33
+ // Mount storyboard core. In embed mode (_sb_embed in the URL — always true
34
+ // for canvas iframes) this short-circuits past UI mounting and only:
35
+ // - honors the canvas-aware theme (_sb_theme_target, _sb_canvas_theme)
36
+ // - sets up the iframe→parent postMessage navigation bridge
37
+ mountStoryboardCore(storyboardConfig, { basePath: import.meta.env.BASE_URL })
38
+
39
+ // Per-prototype filtering: when PrototypeEmbed loads us with ?proto=Name in
40
+ // dev, narrow the router to that prototype's subtree. Other prototypes'
41
+ // lazy() entries never appear in the route table, so a broken sibling
42
+ // prototype cannot leak into this iframe's module graph or HMR scope. Falls
43
+ // back to the full route tree when no proto param is set (production builds
44
+ // + direct-load flows).
45
+ const protoParam = new URLSearchParams(window.location.search).get('proto')
46
+ const activeRoutes = protoParam ? getRoutesForProto(protoParam) : routes
47
+
48
+ const router = createHashRouter(activeRoutes)
49
+
50
+ const rootElement = document.getElementById('root')
51
+ const root = createRoot(rootElement)
52
+
53
+ root.render(
54
+ <StrictMode>
55
+ <RouterProvider router={router} />
56
+ </StrictMode>
57
+ )
@@ -0,0 +1,205 @@
1
+ /**
2
+ * DO NOT EDIT — storyboard-owned library file.
3
+ *
4
+ * Synced from @dfosco/storyboard. Customize via src/_prototype.jsx (wraps
5
+ * every prototype route) or src/library/_app.jsx (workspace SPA shell).
6
+ * See src/library/README.md for the full customization surface.
7
+ *
8
+ * Custom generouted routes entry for storyboard.
9
+ *
10
+ * generouted hardcodes /src/pages/ in both its import.meta.glob patterns and
11
+ * its route-path regex. This module mirrors the generouted runtime but scans
12
+ * two directories:
13
+ * - /src/library/ — storyboard's workspace pages (workspace, viewfinder,
14
+ * create, index) plus _app.jsx (SPA shell wrapper).
15
+ * - /src/prototypes/ — the consumer's actual prototypes.
16
+ *
17
+ * Both directories have their prefix segment stripped from the URL — so
18
+ * src/library/workspace.jsx mounts at /workspace, and
19
+ * src/prototypes/MyProto/index.jsx mounts at /MyProto.
20
+ *
21
+ * Every PROTOTYPE route (anything originating from /src/prototypes/) is
22
+ * wrapped in the consumer's src/_prototype.jsx (resolved via
23
+ * `getPrototypeWrapper`). LIBRARY routes (workspace, viewfinder, create,
24
+ * index) are NOT wrapped — they own their own provider chain inherited
25
+ * from _app.jsx.
26
+ */
27
+ import { Fragment, Suspense } from 'react'
28
+ import { Outlet, useLocation } from 'react-router-dom'
29
+ import PrototypeErrorBoundary, { ImportErrorFallback } from '@dfosco/storyboard/error-boundary'
30
+ import { getPrototypeWrapper } from '@dfosco/storyboard/wrappers'
31
+ import {
32
+ generatePreservedRoutes,
33
+ generateRegularRoutes,
34
+ generateModalRoutes,
35
+ patterns,
36
+ } from '@generouted/react-router/core'
37
+
38
+ // Patch the route regex to strip src/library/, src/prototypes/, .folder/,
39
+ // and `drafts/` segments. A `drafts/` dir is a gitignored scratch root —
40
+ // prototypes inside route at the same URL as a non-prefixed sibling (so
41
+ // `prototypes/drafts/Foo/` and `prototypes/Foo/` both resolve at `/Foo`).
42
+ patterns.route = [/^.*\/src\/(library|prototypes)\/|^\/(library|prototypes)\/|[^/]*\.folder\/|(?<=^|\/)drafts\/|\.(jsx|tsx|mdx)$/g, '']
43
+
44
+ // `_app.jsx` lives in src/library/ — it's storyboard's SPA shell
45
+ // (StoryboardProvider + suspense + error boundary). Consumers can copy
46
+ // it to src/library/_app.jsx and customize freely. `404` is currently a
47
+ // no-op convention, kept for parity with generouted preserved routes.
48
+ const PRESERVED = import.meta.glob('/src/library/(_app|404).{jsx,tsx}', { eager: true })
49
+ const MODALS = import.meta.glob('/src/prototypes/**/[+]*.{jsx,tsx}', { eager: true })
50
+
51
+ const PrototypeWrapper = getPrototypeWrapper()
52
+
53
+ // Library workspace pages — non-prototype routes (Workspace, Viewfinder,
54
+ // Create, landing index). Bypasses the PrototypeWrapper.
55
+ const LIBRARY_ROUTES = import.meta.glob(
56
+ ['/src/library/*.{jsx,tsx}', '!/src/library/(_app|404|mount|prototypes-entry|routes).{jsx,tsx}'],
57
+ )
58
+
59
+ // Lazy-load prototype routes — only the matched route's module is fetched.
60
+ // This prevents story/canvas URLs from paying for every prototype page's
61
+ // imports (Primer, Reshaped, user components, etc.) on first load.
62
+ const PROTOTYPE_ROUTES = import.meta.glob(
63
+ ['/src/prototypes/**/[\\w[-]*.{jsx,tsx,mdx}', '!/src/prototypes/**/(_!(layout)*(/*)?|_app|404)*'],
64
+ )
65
+
66
+ const preservedRoutes = generatePreservedRoutes(PRESERVED)
67
+ const modalRoutes = generateModalRoutes(MODALS)
68
+
69
+ function makeLazyRoute(importFn, key) {
70
+ const index = /index\.(jsx|tsx|mdx)$/.test(key) && !key.includes('prototypes/index') && !key.includes('library/index')
71
+ ? { index: true }
72
+ : {}
73
+ return {
74
+ ...index,
75
+ lazy: async () => {
76
+ try {
77
+ const module = await importFn()
78
+ const Default = module?.default || Fragment
79
+ const Page = () =>
80
+ module?.Pending ? (
81
+ <Suspense fallback={<module.Pending />}>
82
+ <Default />
83
+ </Suspense>
84
+ ) : (
85
+ <Default />
86
+ )
87
+ return {
88
+ Component: Page,
89
+ ErrorBoundary: module?.Catch || PrototypeErrorBoundary,
90
+ loader: module?.Loader,
91
+ action: module?.Action,
92
+ }
93
+ } catch (err) {
94
+ // Import itself failed (syntax error, broken dependency, etc.)
95
+ return {
96
+ Component: () => <ImportErrorFallback error={err} route={key} />,
97
+ }
98
+ }
99
+ },
100
+ }
101
+ }
102
+
103
+ const libraryRoutes = generateRegularRoutes(LIBRARY_ROUTES, makeLazyRoute)
104
+ const prototypeRoutes = generateRegularRoutes(PROTOTYPE_ROUTES, makeLazyRoute)
105
+
106
+ const _app = preservedRoutes?.['_app']
107
+ const _404 = preservedRoutes?.['404']
108
+ const Default = _app?.default || Outlet
109
+
110
+ // eslint-disable-next-line react-refresh/only-export-components
111
+ const Modals_ = () => {
112
+ const Modal = modalRoutes[useLocation().state?.modal] || Fragment
113
+ return <Modal />
114
+ }
115
+
116
+ // eslint-disable-next-line react-refresh/only-export-components
117
+ const Layout = () => (
118
+ <>
119
+ <Default /> <Modals_ />
120
+ </>
121
+ )
122
+
123
+ // eslint-disable-next-line react-refresh/only-export-components
124
+ const App = () =>
125
+ _app?.Pending ? (
126
+ <Suspense fallback={<_app.Pending />}>
127
+ <Layout />
128
+ </Suspense>
129
+ ) : (
130
+ <Layout />
131
+ )
132
+
133
+ const app = { Component: _app?.default ? App : Layout, ErrorBoundary: _app?.Catch, loader: _app?.Loader }
134
+ const fallback = { path: '*', Component: _404?.default || Fragment }
135
+
136
+ // Wrap every PROTOTYPE route in <PrototypeWrapper> via a layout route.
137
+ // The wrapper resolves to consumer's src/_prototype.jsx — or a Fragment
138
+ // pass-through when absent (truly blank slate).
139
+ //
140
+ // The wrapping <div data-storyboard-prototype> uses `display: contents` so
141
+ // it never affects layout — it only exposes a CSS-selector hook
142
+ // (`[data-storyboard-prototype] selector`) that consumers can use to scope
143
+ // prototype-side styles.
144
+ //
145
+ // eslint-disable-next-line react-refresh/only-export-components
146
+ function PrototypeLayout() {
147
+ return (
148
+ <Suspense fallback={null}>
149
+ <PrototypeWrapper>
150
+ <div data-storyboard-prototype style={{ display: 'contents' }}>
151
+ <Outlet />
152
+ </div>
153
+ </PrototypeWrapper>
154
+ </Suspense>
155
+ )
156
+ }
157
+
158
+ const prototypeLayoutRoute = {
159
+ Component: PrototypeLayout,
160
+ children: prototypeRoutes,
161
+ }
162
+
163
+ export const routes = [{
164
+ ...app,
165
+ children: [...libraryRoutes, prototypeLayoutRoute, fallback],
166
+ }]
167
+
168
+ // Return a routes tree filtered to a single prototype subtree. Used by the
169
+ // per-prototype iframe entry (see prototypes-entry.jsx + PrototypeEmbed
170
+ // dev URL builder). Filtering at this layer keeps a broken sibling
171
+ // prototype from ever appearing in the matched route's lazy() chain — only
172
+ // the requested prototype's modules are reachable from the router.
173
+ //
174
+ // Library routes (workspace, viewfinder, create) are NEVER in the iframe
175
+ // path because the iframe entry uses a different routes module
176
+ // (prototypeRoutes.jsx inside the library). So filtering here only needs
177
+ // to walk the prototype subtree.
178
+ export function getRoutesForProto(proto) {
179
+ if (!proto) return routes
180
+ const matchesProto = (route) => {
181
+ const seg = (route.path || '').split('/').filter(Boolean)[0]
182
+ return seg === proto
183
+ }
184
+ const filter = (children) => children
185
+ .map((r) => {
186
+ if (r.path === '*') return r
187
+ if (!r.path) {
188
+ if (r.children) return { ...r, children: filter(r.children) }
189
+ return r
190
+ }
191
+ if (matchesProto(r)) {
192
+ return r.children ? { ...r, children: filter(r.children) } : r
193
+ }
194
+ return null
195
+ })
196
+ .filter(Boolean)
197
+ return [{
198
+ ...routes[0],
199
+ children: routes[0].children.map((child) => {
200
+ // Only the prototype-layout child has nested children we want to filter.
201
+ if (child === prototypeLayoutRoute) return { ...child, children: filter(child.children || []) }
202
+ return child
203
+ }),
204
+ }]
205
+ }
@@ -0,0 +1,13 @@
1
+ import { useEffect } from 'react'
2
+
3
+ /**
4
+ * Backward-compat redirect: /viewfinder → /workspace
5
+ * Kept for one release cycle so existing bookmarks and links still work.
6
+ */
7
+ export default function ViewfinderRedirect() {
8
+ useEffect(() => {
9
+ const base = (import.meta.env.BASE_URL || '/').replace(/\/+$/, '')
10
+ window.location.replace(`${base}/workspace${window.location.hash}`)
11
+ }, [])
12
+ return null
13
+ }
@@ -0,0 +1,17 @@
1
+ import { flows } from 'virtual:storyboard-data-index'
2
+ import { Workspace } from '@dfosco/storyboard'
3
+
4
+ const pageModules = import.meta.glob('/src/prototypes/*.jsx')
5
+
6
+ export default function WorkspacePage() {
7
+ return (
8
+ <Workspace
9
+ title="Storyboard"
10
+ subtitle="Where design work goes"
11
+ flows={flows}
12
+ hideDefaultFlow={true}
13
+ pageModules={pageModules}
14
+ basePath={import.meta.env.BASE_URL}
15
+ />
16
+ )
17
+ }
@@ -0,0 +1,257 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'fs'
4
+ import path from 'path'
5
+ import process from 'process'
6
+
7
+ const INDEX_FILENAME = 'artifacts.index.json'
8
+ const TMP_FILENAME = '.artifacts.index.json.tmp'
9
+ const SCHEMA_VERSION = 1
10
+
11
+ function compareBranchNames(a, b) {
12
+ if (a.branch === 'main' && b.branch !== 'main') return -1
13
+ if (b.branch === 'main' && a.branch !== 'main') return 1
14
+ return String(a.branch || '').localeCompare(String(b.branch || ''))
15
+ }
16
+
17
+ function withTrailingSlash(folder) {
18
+ if (!folder) return ''
19
+ return folder.endsWith('/') ? folder : `${folder}/`
20
+ }
21
+
22
+ function generatedAtMs(manifest) {
23
+ const value = Date.parse(manifest?.generatedAt || '')
24
+ return Number.isNaN(value) ? 0 : value
25
+ }
26
+
27
+ function deriveFolder(manifest, dir, ghPagesDir) {
28
+ if (dir === ghPagesDir) return ''
29
+ if (typeof manifest.folder === 'string') return manifest.folder
30
+ return path.basename(dir)
31
+ }
32
+
33
+ function deriveBranch(manifest, folder) {
34
+ if (!folder) return 'main'
35
+ if (typeof manifest.branch === 'string' && manifest.branch) return manifest.branch
36
+ return manifest.deployBranch || folder.replace(/^branch--/, '')
37
+ }
38
+
39
+ function deriveDeployBranch(manifest, branch, folder) {
40
+ if (!folder || branch === 'main') return 'main'
41
+ if (typeof manifest.deployBranch === 'string' && manifest.deployBranch) return manifest.deployBranch
42
+ return folder.replace(/^branch--/, '')
43
+ }
44
+
45
+ function readManifest(dir) {
46
+ const manifestPath = path.join(dir, 'artifacts.json')
47
+ const label = path.basename(dir) || dir
48
+
49
+ let raw
50
+ try {
51
+ raw = fs.readFileSync(manifestPath, 'utf8')
52
+ } catch (err) {
53
+ if (err?.code === 'ENOENT') {
54
+ console.info(`[aggregate-artifacts] Skipping ${label}: artifacts.json not found`)
55
+ return { manifest: null, readError: false }
56
+ }
57
+ console.warn(`[aggregate-artifacts] Skipping ${label}: could not read artifacts.json (${err.message})`)
58
+ return { manifest: null, readError: true }
59
+ }
60
+
61
+ let manifest
62
+ try {
63
+ manifest = JSON.parse(raw)
64
+ } catch (err) {
65
+ console.warn(`[aggregate-artifacts] Skipping ${label}: malformed artifacts.json (${err.message})`)
66
+ return { manifest: null, readError: false }
67
+ }
68
+
69
+ if (manifest?.schemaVersion !== SCHEMA_VERSION) {
70
+ console.warn(`[aggregate-artifacts] Skipping ${label}: unsupported schemaVersion ${manifest?.schemaVersion}`)
71
+ return { manifest: null, readError: false }
72
+ }
73
+
74
+ return { manifest, readError: false }
75
+ }
76
+
77
+ function listManifestDirs(ghPagesDir) {
78
+ const entries = fs.readdirSync(ghPagesDir, { withFileTypes: true })
79
+ const branchDirs = entries
80
+ .filter(entry => entry.isDirectory() && entry.name.startsWith('branch--'))
81
+ .map(entry => path.join(ghPagesDir, entry.name))
82
+ .sort((a, b) => path.basename(a).localeCompare(path.basename(b)))
83
+
84
+ return [ghPagesDir, ...branchDirs]
85
+ }
86
+
87
+ function collectManifests(ghPagesDir) {
88
+ const manifestsByFolder = new Map()
89
+ let readErrorCount = 0
90
+
91
+ for (const dir of listManifestDirs(ghPagesDir)) {
92
+ const { manifest, readError } = readManifest(dir)
93
+ if (readError) readErrorCount += 1
94
+ if (!manifest) continue
95
+
96
+ const folder = deriveFolder(manifest, dir, ghPagesDir)
97
+ const current = { manifest, dir, folder }
98
+ const previous = manifestsByFolder.get(folder)
99
+
100
+ if (!previous) {
101
+ manifestsByFolder.set(folder, current)
102
+ continue
103
+ }
104
+
105
+ const previousBranch = deriveBranch(previous.manifest, previous.folder)
106
+ const currentBranch = deriveBranch(manifest, folder)
107
+ const keepCurrent = generatedAtMs(manifest) > generatedAtMs(previous.manifest)
108
+ const keptBranch = keepCurrent ? currentBranch : previousBranch
109
+
110
+ console.warn(
111
+ `[aggregate-artifacts] Branch folder collision for "${folder || '(root)'}" between "${previousBranch}" and "${currentBranch}"; keeping "${keptBranch}"`,
112
+ )
113
+
114
+ if (keepCurrent) manifestsByFolder.set(folder, current)
115
+ }
116
+
117
+ if (readErrorCount > 1) {
118
+ throw new Error(`Failed to read ${readErrorCount} artifacts manifests; preserving existing ${INDEX_FILENAME}`)
119
+ }
120
+
121
+ const manifests = [...manifestsByFolder.values()]
122
+ if (manifests.length === 0) {
123
+ throw new Error(`No readable artifacts.json manifests found; preserving existing ${INDEX_FILENAME}`)
124
+ }
125
+
126
+ return manifests
127
+ }
128
+
129
+ function addPrototype(index, manifestInfo, item) {
130
+ const id = item?.artifactId || item?.id
131
+ if (!id) return
132
+
133
+ const { manifest, folder } = manifestInfo
134
+ const branch = deriveBranch(manifest, folder)
135
+ const key = String(id)
136
+
137
+ if (!index.prototypes[key]) {
138
+ index.prototypes[key] = { id: key, branches: [] }
139
+ }
140
+
141
+ index.prototypes[key].branches.push({
142
+ branch,
143
+ folder: withTrailingSlash(folder),
144
+ title: item.title ?? item.id ?? key,
145
+ route: item.route ?? null,
146
+ isExternal: Boolean(item.isExternal),
147
+ externalUrl: item.externalUrl ?? null,
148
+ folderName: item.folder ?? null,
149
+ sha: manifest.sha ?? null,
150
+ lastModified: item.lastModified ?? null,
151
+ })
152
+ }
153
+
154
+ function addCanvas(index, manifestInfo, item) {
155
+ const id = item?.artifactId || item?.id
156
+ if (!id) return
157
+
158
+ const { manifest, folder } = manifestInfo
159
+ const branch = deriveBranch(manifest, folder)
160
+ const key = String(id)
161
+
162
+ if (!index.canvases[key]) {
163
+ index.canvases[key] = { id: key, branches: [] }
164
+ }
165
+
166
+ index.canvases[key].branches.push({
167
+ branch,
168
+ folder: withTrailingSlash(folder),
169
+ name: item.name ?? item.id ?? key,
170
+ route: item.route ?? null,
171
+ folderName: item.folder ?? null,
172
+ sha: manifest.sha ?? null,
173
+ lastModified: item.lastModified ?? null,
174
+ })
175
+ }
176
+
177
+ function sortArtifactMap(map) {
178
+ const sorted = {}
179
+
180
+ for (const key of Object.keys(map).sort((a, b) => a.localeCompare(b))) {
181
+ sorted[key] = {
182
+ ...map[key],
183
+ branches: [...map[key].branches].sort(compareBranchNames),
184
+ }
185
+ }
186
+
187
+ return sorted
188
+ }
189
+
190
+ function buildIndex(manifests) {
191
+ const index = {
192
+ schemaVersion: SCHEMA_VERSION,
193
+ generatedAt: new Date().toISOString(),
194
+ branches: [],
195
+ prototypes: {},
196
+ canvases: {},
197
+ }
198
+
199
+ for (const manifestInfo of manifests) {
200
+ const { manifest, folder } = manifestInfo
201
+ const branch = deriveBranch(manifest, folder)
202
+ const deployBranch = deriveDeployBranch(manifest, branch, folder)
203
+
204
+ index.branches.push({ branch, deployBranch, folder })
205
+
206
+ for (const item of Array.isArray(manifest.prototypes) ? manifest.prototypes : []) {
207
+ addPrototype(index, manifestInfo, item)
208
+ }
209
+
210
+ for (const item of Array.isArray(manifest.canvases) ? manifest.canvases : []) {
211
+ addCanvas(index, manifestInfo, item)
212
+ }
213
+ }
214
+
215
+ index.branches = [...index.branches].sort(compareBranchNames)
216
+ index.prototypes = sortArtifactMap(index.prototypes)
217
+ index.canvases = sortArtifactMap(index.canvases)
218
+
219
+ return index
220
+ }
221
+
222
+ function writeIndex(ghPagesDir, index) {
223
+ const targetPath = path.join(ghPagesDir, INDEX_FILENAME)
224
+ const tmpPath = path.join(ghPagesDir, TMP_FILENAME)
225
+ const contents = `${JSON.stringify(index, null, 2)}\n`
226
+
227
+ fs.writeFileSync(tmpPath, contents, 'utf8')
228
+ fs.renameSync(tmpPath, targetPath)
229
+
230
+ return targetPath
231
+ }
232
+
233
+ function aggregateArtifacts(ghPagesDir) {
234
+ const resolvedDir = path.resolve(ghPagesDir)
235
+ const manifests = collectManifests(resolvedDir)
236
+ const index = buildIndex(manifests)
237
+ return writeIndex(resolvedDir, index)
238
+ }
239
+
240
+ function main() {
241
+ const ghPagesDir = process.argv[2]
242
+
243
+ if (!ghPagesDir) {
244
+ console.error('Usage: node aggregate-artifacts.mjs <gh-pages-dir>')
245
+ process.exit(1)
246
+ }
247
+
248
+ try {
249
+ const targetPath = aggregateArtifacts(ghPagesDir)
250
+ console.info(`[aggregate-artifacts] Wrote ${targetPath}`)
251
+ } catch (err) {
252
+ console.error(`[aggregate-artifacts] ${err.message}`)
253
+ process.exit(1)
254
+ }
255
+ }
256
+
257
+ main()