@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
@@ -20,6 +20,23 @@ async function request(path, method, body) {
20
20
  return res.json()
21
21
  }
22
22
 
23
+ /**
24
+ * Like `request` but also returns the HTTP status code so callers can
25
+ * distinguish between "endpoint succeeded but logical no-op" and "the
26
+ * target resource is gone" (which undo/redo handle differently after
27
+ * compaction wipes event ids).
28
+ */
29
+ async function requestWithStatus(path, method, body) {
30
+ const url = getApiBase() + path
31
+ const res = await fetch(url, {
32
+ method,
33
+ headers: body ? { 'Content-Type': 'application/json' } : undefined,
34
+ body: body ? JSON.stringify(body) : undefined,
35
+ })
36
+ const data = await res.json().catch(() => ({}))
37
+ return { status: res.status, ok: res.ok, data }
38
+ }
39
+
23
40
  export function listCanvases() {
24
41
  return request('/list', 'GET')
25
42
  }
@@ -32,6 +49,18 @@ export function updateCanvas(canvasId, { widgets, sources, settings, connectors
32
49
  return request('/update', 'PUT', { name: canvasId, widgets, sources, settings, connectors })
33
50
  }
34
51
 
52
+ export function patchWidget(canvasId, widgetId, { props, position } = {}) {
53
+ return request('/widget', 'PATCH', { name: canvasId, widgetId, props, position })
54
+ }
55
+
56
+ export function undoEvent(canvasId, eventId) {
57
+ return requestWithStatus('/undo', 'POST', { name: canvasId, eventId })
58
+ }
59
+
60
+ export function redoEvent(canvasId, eventId) {
61
+ return requestWithStatus('/redo', 'POST', { name: canvasId, eventId })
62
+ }
63
+
35
64
  export function addWidget(canvasId, { type, props, position }) {
36
65
  return request('/widget', 'POST', { name: canvasId, type, props, position })
37
66
  }
@@ -56,9 +85,10 @@ export function duplicateImage(filename) {
56
85
 
57
86
  /**
58
87
  * Crop an image client-side and upload the result.
59
- * @param {string} imageSrc — current image filename (e.g. "canvas--2026-01-01--12-00-00.png")
88
+ * @param {string} imageSrc — current image filename, possibly prefixed with `drafts/` and/or `~`
89
+ * (e.g. "drafts/~canvas--2026-01-01--12-00-00.png")
60
90
  * @param {{ x: number, y: number, width: number, height: number }} cropRect — crop region in natural image pixels
61
- * @param {string} canvasId — canvas name for directory resolution
91
+ * @param {string} canvasId — canvas name for directory resolution (server uses this to decide drafts/ vs public)
62
92
  * @returns {Promise<{ success: boolean, filename: string }>}
63
93
  */
64
94
  export async function cropAndUpload(imageSrc, cropRect, canvasId) {
@@ -89,15 +119,20 @@ export async function cropAndUpload(imageSrc, cropRect, canvasId) {
89
119
  canvas.width, canvas.height,
90
120
  )
91
121
 
122
+ // Strip optional `drafts/` directory prefix — the server re-adds it based
123
+ // on canvasId. We only pass the basename through the cropped-filename
124
+ // pipeline (the server validates `--cropped--` filenames as basenames).
125
+ const srcBase = imageSrc.includes('/') ? imageSrc.slice(imageSrc.lastIndexOf('/') + 1) : imageSrc
126
+
92
127
  // Determine output format from original filename
93
- const ext = imageSrc.split('.').pop()?.toLowerCase() || 'png'
128
+ const ext = srcBase.split('.').pop()?.toLowerCase() || 'png'
94
129
  const mimeMap = { png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', webp: 'image/webp', gif: 'image/gif' }
95
130
  const mime = mimeMap[ext] || 'image/png'
96
131
  const dataUrl = canvas.toDataURL(mime, 0.92)
97
132
 
98
133
  // Build cropped filename: strip any previous --cropped-- suffix, append new one
99
- const privacyPrefix = imageSrc.startsWith('~') ? '~' : ''
100
- const baseName = imageSrc.replace(/^~/, '')
134
+ const privacyPrefix = srcBase.startsWith('~') ? '~' : ''
135
+ const baseName = srcBase.replace(/^~/, '')
101
136
  const withoutCrop = baseName.replace(/--cropped--\d{4}-\d{2}-\d{2}--\d{2}-\d{2}-\d{2}/, '')
102
137
  const nameWithoutExt = withoutCrop.replace(/\.\w+$/, '')
103
138
  const now = new Date()
@@ -1,118 +1,29 @@
1
+ /**
2
+ * canvasTheme.js — DEPRECATED COMPATIBILITY SHIM.
3
+ *
4
+ * Color variables previously written here as inline styles now live in
5
+ * `core/styles/tailwind.css` and cascade automatically from
6
+ * `[data-sb-canvas-theme^="dark"]` (set by CanvasPage on the canvas root).
7
+ *
8
+ * `getCanvasPrimerAttrs` is preserved as a no-op to keep external consumers
9
+ * (Primer React inside prototypes) functional via `data-color-mode` — but
10
+ * storyboard core no longer depends on this attribute internally.
11
+ *
12
+ * Both exports return empty objects so existing call sites continue to
13
+ * destructure/spread without errors. Remove the call sites in a follow-up
14
+ * sweep, then delete this file.
15
+ */
16
+
1
17
  export function getCanvasPrimerAttrs(theme) {
2
18
  const value = String(theme || 'light')
3
- if (value.startsWith('dark')) {
4
- return {
5
- 'data-color-mode': 'dark',
6
- 'data-dark-theme': value,
7
- 'data-light-theme': 'light',
8
- }
9
- }
19
+ const isDark = value.startsWith('dark')
10
20
  return {
11
- 'data-color-mode': 'light',
12
- 'data-dark-theme': 'dark',
13
- 'data-light-theme': value.startsWith('light') ? value : 'light',
21
+ 'data-color-mode': isDark ? 'dark' : 'light',
22
+ 'data-light-theme': isDark ? 'light' : (value.startsWith('light') ? value : 'light'),
23
+ 'data-dark-theme': isDark ? value : 'dark',
14
24
  }
15
25
  }
16
26
 
17
- /**
18
- * Per-theme canvas CSS custom properties sourced from @primer/primitives.
19
- * Each theme gets its own entry so high-contrast, colorblind, and dimmed
20
- * variants all render with the correct background, dot, and text colors.
21
- */
22
- const THEME_VARS = {
23
- light: {
24
- '--sb--canvas-bg': '#f6f8fa',
25
- '--bgColor-default': '#ffffff',
26
- '--bgColor-muted': '#f6f8fa',
27
- '--bgColor-neutral-muted': '#818b981f',
28
- '--bgColor-accent-emphasis': '#0969da',
29
- '--tc-bg-muted': '#f6f8fa',
30
- '--tc-dot-color': 'rgba(0, 0, 0, 0.08)',
31
- '--overlay-backdrop-bgColor': 'rgba(0, 0, 0, 0.08)',
32
- '--fgColor-muted': '#59636e',
33
- '--fgColor-default': '#1f2328',
34
- '--fgColor-onEmphasis': '#ffffff',
35
- '--borderColor-default': '#d1d9e0',
36
- '--borderColor-muted': '#d1d9e0b3',
37
- },
38
- light_colorblind: {
39
- '--sb--canvas-bg': '#f6f8fa',
40
- '--bgColor-default': '#ffffff',
41
- '--bgColor-muted': '#f6f8fa',
42
- '--bgColor-neutral-muted': '#818b981f',
43
- '--bgColor-accent-emphasis': '#0969da',
44
- '--tc-bg-muted': '#f6f8fa',
45
- '--tc-dot-color': 'rgba(0, 0, 0, 0.08)',
46
- '--overlay-backdrop-bgColor': 'rgba(0, 0, 0, 0.08)',
47
- '--fgColor-muted': '#59636e',
48
- '--fgColor-default': '#1f2328',
49
- '--fgColor-onEmphasis': '#ffffff',
50
- '--borderColor-default': '#d1d9e0',
51
- '--borderColor-muted': '#d1d9e0b3',
52
- },
53
- dark: {
54
- '--sb--canvas-bg': '#151b23',
55
- '--bgColor-default': '#0d1117',
56
- '--bgColor-muted': '#151b23',
57
- '--bgColor-neutral-muted': '#656c7633',
58
- '--bgColor-accent-emphasis': '#1f6feb',
59
- '--tc-bg-muted': '#151b23',
60
- '--tc-dot-color': 'rgba(255, 255, 255, 0.1)',
61
- '--overlay-backdrop-bgColor': 'rgba(255, 255, 255, 0.1)',
62
- '--fgColor-muted': '#9198a1',
63
- '--fgColor-default': '#f0f6fc',
64
- '--fgColor-onEmphasis': '#ffffff',
65
- '--borderColor-default': '#3d444d',
66
- '--borderColor-muted': '#3d444db3',
67
- },
68
- dark_dimmed: {
69
- '--sb--canvas-bg': '#262c36',
70
- '--bgColor-default': '#212830',
71
- '--bgColor-muted': '#262c36',
72
- '--bgColor-neutral-muted': '#656c7633',
73
- '--bgColor-accent-emphasis': '#316dca',
74
- '--tc-bg-muted': '#262c36',
75
- '--tc-dot-color': 'rgba(209, 215, 224, 0.18)',
76
- '--overlay-backdrop-bgColor': 'rgba(209, 215, 224, 0.18)',
77
- '--fgColor-muted': '#9198a1',
78
- '--fgColor-default': '#d1d7e0',
79
- '--fgColor-onEmphasis': '#f0f6fc',
80
- '--borderColor-default': '#3d444d',
81
- '--borderColor-muted': '#3d444db3',
82
- },
83
- dark_colorblind: {
84
- '--sb--canvas-bg': '#151b23',
85
- '--bgColor-default': '#0d1117',
86
- '--bgColor-muted': '#151b23',
87
- '--bgColor-neutral-muted': '#656c7633',
88
- '--bgColor-accent-emphasis': '#1f6feb',
89
- '--tc-bg-muted': '#151b23',
90
- '--tc-dot-color': 'rgba(255, 255, 255, 0.1)',
91
- '--overlay-backdrop-bgColor': 'rgba(255, 255, 255, 0.1)',
92
- '--fgColor-muted': '#9198a1',
93
- '--fgColor-default': '#f0f6fc',
94
- '--fgColor-onEmphasis': '#ffffff',
95
- '--borderColor-default': '#3d444d',
96
- '--borderColor-muted': '#3d444db3',
97
- },
98
- dark_high_contrast: {
99
- '--sb--canvas-bg': '#151b23',
100
- '--bgColor-default': '#010409',
101
- '--bgColor-muted': '#151b23',
102
- '--bgColor-neutral-muted': '#212830',
103
- '--bgColor-accent-emphasis': '#194fb1',
104
- '--tc-bg-muted': '#151b23',
105
- '--tc-dot-color': 'rgba(183, 189, 200, 0.25)',
106
- '--overlay-backdrop-bgColor': 'rgba(183, 189, 200, 0.25)',
107
- '--fgColor-muted': '#b7bdc8',
108
- '--fgColor-default': '#ffffff',
109
- '--fgColor-onEmphasis': '#ffffff',
110
- '--borderColor-default': '#b7bdc8',
111
- '--borderColor-muted': '#b7bdc8',
112
- },
113
- }
114
-
115
- export function getCanvasThemeVars(theme) {
116
- const value = String(theme || 'light')
117
- return THEME_VARS[value] || THEME_VARS.light
27
+ export function getCanvasThemeVars() {
28
+ return {}
118
29
  }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Vite middleware factory for iframe-isolation entries.
3
+ *
4
+ * Both `prototypes.html` (prototype embeds) and `stories.html` (story /
5
+ * component-set widgets) follow the same recipe: serve a minimal HTML shell
6
+ * that boots a library-side React entry, with a theme bootstrap inlined so
7
+ * first paint matches the active theme. This factory captures that recipe
8
+ * so the two surfaces can't drift apart.
9
+ *
10
+ * Returns a Connect middleware that matches `/${name}` and `/${name}/*`
11
+ * (after the configured Vite base path is stripped). Non-matching URLs are
12
+ * passed through to `next()`.
13
+ *
14
+ * @param {object} opts
15
+ * @param {string} opts.name — entry name, including extension, e.g. 'prototypes.html'
16
+ * @param {string} opts.entryPath — absolute on-disk path to the JS entry to /@fs-load
17
+ * @param {string} opts.surface — value for `data-sb-surface` attribute on <body>
18
+ * @param {string} opts.title — document <title>
19
+ * @param {() => object|undefined} opts.getThemes — read live themes config; called per request
20
+ * @returns {(req, res, next) => void}
21
+ */
22
+ export function createIsolationMiddleware({ name, entryPath, surface, title, getThemes }, { renderInlineBootstrapScript, server }) {
23
+ const matchPath = `/${name}`
24
+ return async function isolationMiddleware(req, res, next) {
25
+ if (!req.url) return next()
26
+ let url = req.url
27
+ const baseNoTrail = (server.config.base || '/').replace(/\/$/, '')
28
+ if (baseNoTrail && url.startsWith(baseNoTrail)) {
29
+ url = url.slice(baseNoTrail.length) || '/'
30
+ }
31
+ const cleanUrl = url.split('?')[0].split('#')[0]
32
+ if (cleanUrl !== matchPath && !cleanUrl.startsWith(`${matchPath}/`)) return next()
33
+
34
+ const themes = getThemes?.()
35
+ const themeBootstrap = themes ? renderInlineBootstrapScript(themes) : ''
36
+
37
+ const rawHtml = [
38
+ '<!DOCTYPE html>',
39
+ '<html lang="en"><head>',
40
+ '<meta charset="UTF-8" />',
41
+ '<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />',
42
+ `<title>${escapeHtml(title)}</title>`,
43
+ themeBootstrap,
44
+ `</head><body data-sb-surface="${escapeAttr(surface)}">`,
45
+ '<div id="root"></div>',
46
+ `<script type="module" src="/@fs${entryPath}"></script>`,
47
+ '</body></html>',
48
+ ].join('\n')
49
+
50
+ try {
51
+ const html = await server.transformIndexHtml(req.url, rawHtml)
52
+ res.writeHead(200, { 'Content-Type': 'text/html' })
53
+ res.end(html)
54
+ } catch (err) {
55
+ console.error(`[storyboard] ${name} transform failed:`, err)
56
+ res.writeHead(500, { 'Content-Type': 'text/plain' })
57
+ res.end(`${name} failed`)
58
+ }
59
+ }
60
+ }
61
+
62
+ function escapeHtml(s) {
63
+ return String(s).replace(/[&<>]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;' }[c]))
64
+ }
65
+
66
+ function escapeAttr(s) {
67
+ return String(s).replace(/[&<>"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]))
68
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Shared mount helper for isolated iframe entries.
3
+ *
4
+ * Used by `prototypesEntry.jsx` and `storiesEntry.jsx` — they both:
5
+ * 1. Read `storyboard.config.json` via import.meta.glob
6
+ * 2. Call `mountStoryboardCore` so the embed gets ui-runtime CSS, theme
7
+ * sync, and the iframe→parent postMessage bridge
8
+ * 3. Mount a React tree with `createBrowserRouter` scoped to the entry
9
+ * page (basename = `${BASE_URL}/${name}`)
10
+ *
11
+ * Centralizing this here means bug fixes / iterations land in one place
12
+ * for both surfaces. Each surface entry shrinks to a few lines:
13
+ *
14
+ * import { routes, getRoutesForProto } from './prototypeRoutes.jsx'
15
+ * import { mountIsolationApp } from './mountIsolationApp.jsx'
16
+ * mountIsolationApp({
17
+ * name: 'prototypes.html',
18
+ * wrapperKind: 'prototype',
19
+ * resolveRoutes: ({ firstSegment, search }) => {
20
+ * const proto = firstSegment || search.get('proto') || ''
21
+ * return proto ? getRoutesForProto(proto) : routes
22
+ * },
23
+ * })
24
+ *
25
+ * Provider chain: the consumer's `src/_prototype.jsx` (or `src/_story.jsx`)
26
+ * wraps the routes. When absent, both wrappers degrade to a Fragment
27
+ * pass-through — prototypes render with no shared providers (blank slate).
28
+ *
29
+ * @param {object} opts
30
+ * @param {string} opts.name — entry name, e.g. 'prototypes.html'
31
+ * @param {'prototype' | 'story'} [opts.wrapperKind='prototype']
32
+ * — which consumer wrapper to mount around the router. Prototype
33
+ * uses `src/_prototype.jsx`, story uses `src/_story.jsx`.
34
+ * @param {RouteObject[]} [opts.routes] — fixed route table (used when resolveRoutes is omitted)
35
+ * @param {(ctx) => RouteObject[]} [opts.resolveRoutes]
36
+ * — callback that returns the route table to mount, given
37
+ * `{ basename, pathname, search, firstSegment }`. Use this when a
38
+ * surface needs to narrow routes per-request (e.g. prototypes
39
+ * isolate a broken sibling proto from the matched lazy() chain).
40
+ */
41
+ import { StrictMode, Suspense } from 'react'
42
+ import { createRoot } from 'react-dom/client'
43
+ import { RouterProvider, createBrowserRouter } from 'react-router-dom'
44
+ import { mountStoryboardCore } from '../../../core/index.js'
45
+ import { getPrototypeWrapper, getStoryWrapper } from '../../loadConsumerWrapper.js'
46
+ import '../../../core/comments/ui/comment-layout.css'
47
+
48
+ const configModules = import.meta.glob('/storyboard.config.json', { eager: true })
49
+
50
+ export function mountIsolationApp({ name, wrapperKind = 'prototype', routes, resolveRoutes }) {
51
+ const storyboardConfig = Object.values(configModules)[0]?.default || {}
52
+
53
+ // Mount storyboard core. In embed mode (`_sb_embed` in the URL — always true
54
+ // for canvas iframes) this short-circuits past UI mounting and only:
55
+ // - applies the canvas-aware theme (_sb_theme_target, _sb_canvas_theme)
56
+ // - sets up the iframe→parent postMessage navigation/wheel bridge
57
+ mountStoryboardCore(storyboardConfig, { basePath: import.meta.env.BASE_URL })
58
+
59
+ const basePathNoTrail = (import.meta.env.BASE_URL || '/').replace(/\/$/, '')
60
+ const basename = `${basePathNoTrail}/${name}`
61
+
62
+ const pathname = window.location.pathname
63
+ const search = new URLSearchParams(window.location.search)
64
+ const afterBasename = pathname.startsWith(basename) ? pathname.slice(basename.length) : ''
65
+ const firstSegment = (afterBasename.match(/^\/([^/?#]+)/) || [])[1] || ''
66
+
67
+ const activeRoutes = resolveRoutes
68
+ ? resolveRoutes({ basename, pathname, search, firstSegment })
69
+ : routes
70
+
71
+ const router = createBrowserRouter(activeRoutes, { basename })
72
+
73
+ const Wrapper = wrapperKind === 'story' ? getStoryWrapper() : getPrototypeWrapper()
74
+
75
+ const rootElement = document.getElementById('root')
76
+ const root = createRoot(rootElement)
77
+
78
+ root.render(
79
+ <StrictMode>
80
+ <Suspense fallback={null}>
81
+ <Wrapper>
82
+ <RouterProvider router={router} />
83
+ </Wrapper>
84
+ </Suspense>
85
+ </StrictMode>,
86
+ )
87
+ }
88
+
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Library-side prototype routes builder for the isolated prototypes iframe.
3
+ *
4
+ * Mirrors the consumer-scaffold convention (src/routes.jsx) but lives inside
5
+ * the library so consumers no longer need to maintain their own
6
+ * prototypes-entry plumbing. Uses absolute globs (`/src/prototypes/...`),
7
+ * which Vite resolves against the *consumer's* project root regardless of
8
+ * where this file physically lives.
9
+ *
10
+ * Used by `prototypesEntry.jsx`, which is the iframe entry served by the
11
+ * `prototypes.html` middleware in `data-plugin.js`.
12
+ */
13
+ import { Fragment, Suspense } from 'react'
14
+ import { Outlet, useLocation } from 'react-router-dom'
15
+ import {
16
+ generatePreservedRoutes,
17
+ generateRegularRoutes,
18
+ generateModalRoutes,
19
+ patterns,
20
+ } from '@generouted/react-router/core'
21
+ import PrototypeErrorBoundary, { ImportErrorFallback } from '../../PrototypeErrorBoundary.jsx'
22
+
23
+ // Strip src/library/, src/prototypes/, .folder/, and `drafts/` segments
24
+ // from route paths. `_app.jsx` lives in src/library/ (since
25
+ // b144dace5); user-authored prototype routes live in src/prototypes/.
26
+ // Both prefixes are stripped so the generated route paths (and the
27
+ // preservedRoutes _app key) come out clean.
28
+ //
29
+ // A `drafts/` dir is a gitignored scratch root — prototypes inside route at
30
+ // the same URL as a non-prefixed sibling. The lookbehind ensures we only
31
+ // match `drafts/` as a directory segment, not as part of a filename.
32
+ patterns.route = [/^.*\/src\/(library|prototypes)\/|^\/(library|prototypes)\/|[^/]*\.folder\/|(?<=^|\/)drafts\/|\.(jsx|tsx|mdx)$/g, '']
33
+
34
+ // `_app.jsx` lives in src/library/ — it's the consumer's SPA shell that
35
+ // wraps every route in <StoryboardProvider>. Without this glob hitting
36
+ // the right path, the iframe entry would render prototypes with no
37
+ // provider and any useFlowData / useObject / useRecord call would crash.
38
+ const PRESERVED = import.meta.glob('/src/library/(_app|404).{jsx,tsx}', { eager: true })
39
+ const MODALS = import.meta.glob('/src/prototypes/**/[+]*.{jsx,tsx}', { eager: true })
40
+
41
+ // Lazy-load prototype routes — only the matched route's module is fetched.
42
+ // This prevents story/canvas URLs from paying for every prototype page's
43
+ // imports (Primer, Reshaped, user components, etc.) on first load.
44
+ const ROUTES = import.meta.glob(
45
+ ['/src/prototypes/**/[\\w[-]*.{jsx,tsx,mdx}', '!/src/prototypes/**/(_!(layout)*(/*)?|_app|404)*'],
46
+ )
47
+
48
+ const preservedRoutes = generatePreservedRoutes(PRESERVED)
49
+ const modalRoutes = generateModalRoutes(MODALS)
50
+
51
+ const regularRoutes = generateRegularRoutes(ROUTES, (importFn, key) => {
52
+ const index = /index\.(jsx|tsx|mdx)$/.test(key) && !key.includes('prototypes/index') ? { index: true } : {}
53
+ return {
54
+ ...index,
55
+ lazy: async () => {
56
+ try {
57
+ const module = await importFn()
58
+ const Default = module?.default || Fragment
59
+ const Page = () =>
60
+ module?.Pending ? (
61
+ <Suspense fallback={<module.Pending />}>
62
+ <Default />
63
+ </Suspense>
64
+ ) : (
65
+ <Default />
66
+ )
67
+ return {
68
+ Component: Page,
69
+ ErrorBoundary: module?.Catch || PrototypeErrorBoundary,
70
+ loader: module?.Loader,
71
+ action: module?.Action,
72
+ }
73
+ } catch (err) {
74
+ return {
75
+ Component: () => <ImportErrorFallback error={err} route={key} />,
76
+ }
77
+ }
78
+ },
79
+ }
80
+ })
81
+
82
+ const _app = preservedRoutes?.['_app']
83
+ const _404 = preservedRoutes?.['404']
84
+ const Default = _app?.default || Outlet
85
+
86
+ // eslint-disable-next-line react-refresh/only-export-components
87
+ const Modals_ = () => {
88
+ const Modal = modalRoutes[useLocation().state?.modal] || Fragment
89
+ return <Modal />
90
+ }
91
+
92
+ // eslint-disable-next-line react-refresh/only-export-components
93
+ const Layout = () => (
94
+ <>
95
+ <Default /> <Modals_ />
96
+ </>
97
+ )
98
+
99
+ // eslint-disable-next-line react-refresh/only-export-components
100
+ const App = () =>
101
+ _app?.Pending ? (
102
+ <Suspense fallback={<_app.Pending />}>
103
+ <Layout />
104
+ </Suspense>
105
+ ) : (
106
+ <Layout />
107
+ )
108
+
109
+ const app = { Component: _app?.default ? App : Layout, ErrorBoundary: _app?.Catch, loader: _app?.Loader }
110
+ const fallback = { path: '*', Component: _404?.default || Fragment }
111
+
112
+ export const routes = [{ ...app, children: [...regularRoutes, fallback] }]
113
+
114
+ /**
115
+ * Return a routes tree filtered to a single prototype subtree. Used by the
116
+ * per-prototype iframe entry — filtering at this layer keeps a broken
117
+ * sibling prototype from ever appearing in the matched route's lazy() chain
118
+ * (only the requested prototype's modules are reachable from the router).
119
+ */
120
+ export function getRoutesForProto(proto) {
121
+ if (!proto) return routes
122
+ const matchesProto = (route) => {
123
+ const seg = (route.path || '').split('/').filter(Boolean)[0]
124
+ return seg === proto
125
+ }
126
+ const filter = (children) => children
127
+ .map((r) => {
128
+ if (r.path === '*') return r
129
+ if (!r.path) {
130
+ if (r.children) return { ...r, children: filter(r.children) }
131
+ return r
132
+ }
133
+ if (matchesProto(r)) {
134
+ return r.children ? { ...r, children: filter(r.children) } : r
135
+ }
136
+ return null
137
+ })
138
+ .filter(Boolean)
139
+ return [{ ...routes[0], children: filter(routes[0].children || []) }]
140
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Library-provided entry for the isolated prototypes iframe.
3
+ *
4
+ * Loaded by the prototypes.html middleware in `data-plugin.js`. All the
5
+ * mount plumbing (consumer stylesheet pull-in, storyboard.config.json read,
6
+ * mountStoryboardCore, basename derivation, React tree) lives in the shared
7
+ * `mountIsolationApp` helper so this file and `storiesEntry.jsx` stay in
8
+ * sync. See `.agents/plans/vite-isolation.md` for the isolation pattern.
9
+ *
10
+ * The `resolveRoutes` callback narrows the route table to a single
11
+ * prototype subtree based on the first path segment after the basename
12
+ * (e.g. `/prototypes.html/MyProto/SignupForm` → narrow to MyProto). This
13
+ * keeps a broken sibling prototype from ever appearing in the matched
14
+ * route's lazy() chain — module-graph isolation.
15
+ *
16
+ * The legacy `?proto=` query param is preserved as a fallback for older
17
+ * canvases / older builds.
18
+ */
19
+ import { routes, getRoutesForProto } from './prototypeRoutes.jsx'
20
+ import { mountIsolationApp } from './mountIsolationApp.jsx'
21
+
22
+ mountIsolationApp({
23
+ name: 'prototypes.html',
24
+ resolveRoutes: ({ firstSegment, search }) => {
25
+ const proto = firstSegment || search.get('proto') || ''
26
+ return proto ? getRoutesForProto(proto) : routes
27
+ },
28
+ })
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Library-provided entry for the isolated stories iframe.
3
+ *
4
+ * Loaded by the stories.html middleware in `data-plugin.js`. All the mount
5
+ * plumbing lives in the shared `mountIsolationApp` helper so this file
6
+ * and `prototypesEntry.jsx` stay in sync. See `.agents/plans/vite-isolation.md`
7
+ * for the isolation pattern.
8
+ *
9
+ * No `resolveRoutes` callback — stories don't need per-segment narrowing
10
+ * because each `.story.jsx` is dynamic-imported inside StoryPage, so the
11
+ * route module graph never eagerly pulls in sibling stories.
12
+ */
13
+ import { storyRoutes } from './storyRoutes.jsx'
14
+ import { mountIsolationApp } from './mountIsolationApp.jsx'
15
+
16
+ mountIsolationApp({
17
+ name: 'stories.html',
18
+ wrapperKind: 'story',
19
+ routes: storyRoutes,
20
+ })
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Route table for the isolated stories iframe (`/stories.html/components/...`).
3
+ *
4
+ * Mirrors the canvas SPA's story-route matching but built as a real react-router
5
+ * route table so the route lives in the URL (deep-linkable, "open in new tab"
6
+ * works) and a broken `.story.jsx` only crashes the matched story — not the
7
+ * canvas chrome (module-graph isolation: each .story.jsx is dynamic-imported
8
+ * by StoryPage._storyImport, not eagerly globbed).
9
+ *
10
+ * Uses a single splat route — the story name is derived at render time by
11
+ * matching the full route path against the stories index (`_route` field).
12
+ * Falls back to a friendly "story not found" message when no match.
13
+ */
14
+ import { lazy, Suspense } from 'react'
15
+ import { useLocation } from 'react-router-dom'
16
+ import { stories } from 'virtual:storyboard-data-index'
17
+
18
+ const StoryPageLazy = lazy(() => import('../../story/StoryPage.jsx'))
19
+
20
+ function resolveStoryName(pathname) {
21
+ const normalized = pathname.replace(/\/+$/, '') || '/'
22
+ for (const [name, data] of Object.entries(stories || {})) {
23
+ if (data?._route && data._route.replace(/\/+$/, '') === normalized) {
24
+ return name
25
+ }
26
+ }
27
+ return null
28
+ }
29
+
30
+ function StoryRouteComponent() {
31
+ const location = useLocation()
32
+ const storyName = resolveStoryName(location.pathname)
33
+
34
+ if (!storyName) {
35
+ return (
36
+ <div style={{ padding: 24, fontFamily: 'system-ui, sans-serif', color: 'var(--color-foreground-muted, #57606a)' }}>
37
+ <strong>Story not found</strong>
38
+ <br />
39
+ <code>{location.pathname}</code>
40
+ </div>
41
+ )
42
+ }
43
+
44
+ return (
45
+ <Suspense fallback={null}>
46
+ <StoryPageLazy name={storyName} />
47
+ </Suspense>
48
+ )
49
+ }
50
+
51
+ export const storyRoutes = [
52
+ { path: '/components', element: <StoryRouteComponent /> },
53
+ { path: '/components/*', element: <StoryRouteComponent /> },
54
+ { path: '*', element: <StoryRouteComponent /> },
55
+ ]