@dfosco/storyboard 0.5.0-alpha.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 (592) hide show
  1. package/commandpalette.config.json +152 -0
  2. package/dist/storyboard-ui.css +1 -0
  3. package/dist/storyboard-ui.js +21328 -0
  4. package/dist/storyboard-ui.js.map +1 -0
  5. package/dist/tailwind.css +2 -0
  6. package/dist/tiny-canvas.css +1 -0
  7. package/dist/tiny-canvas.js +389 -0
  8. package/package.json +121 -0
  9. package/paste.config.json +67 -0
  10. package/scaffold/AGENTS.md +432 -0
  11. package/scaffold/agents/prompt-agent.agent.md +181 -0
  12. package/scaffold/agents/terminal-agent.agent.md +351 -0
  13. package/scaffold/codex/config.toml +246 -0
  14. package/scaffold/deploy.yml +103 -0
  15. package/scaffold/githooks/pre-push +114 -0
  16. package/scaffold/gitignore +64 -0
  17. package/scaffold/manifest.json +56 -0
  18. package/scaffold/preview.yml +181 -0
  19. package/scaffold/scripts/link.sh +26 -0
  20. package/scaffold/scripts/unlink.sh +10 -0
  21. package/scaffold/skills/agent-browser/SKILL.md +260 -0
  22. package/scaffold/skills/canvas/SKILL.md +364 -0
  23. package/scaffold/skills/create/SKILL.md +501 -0
  24. package/scaffold/skills/ship/SKILL.md +237 -0
  25. package/scaffold/skills/storyboard/SKILL.md +360 -0
  26. package/scaffold/skills/update-storyboard/SKILL.md +16 -0
  27. package/scaffold/skills/update-storyboard/update-storyboard-packages.sh +26 -0
  28. package/scaffold/skills/vitest/GENERATION.md +5 -0
  29. package/scaffold/skills/vitest/SKILL.md +52 -0
  30. package/scaffold/skills/vitest/references/advanced-environments.md +264 -0
  31. package/scaffold/skills/vitest/references/advanced-projects.md +300 -0
  32. package/scaffold/skills/vitest/references/advanced-type-testing.md +237 -0
  33. package/scaffold/skills/vitest/references/advanced-vi.md +249 -0
  34. package/scaffold/skills/vitest/references/core-cli.md +166 -0
  35. package/scaffold/skills/vitest/references/core-config.md +174 -0
  36. package/scaffold/skills/vitest/references/core-describe.md +193 -0
  37. package/scaffold/skills/vitest/references/core-expect.md +219 -0
  38. package/scaffold/skills/vitest/references/core-hooks.md +244 -0
  39. package/scaffold/skills/vitest/references/core-test-api.md +233 -0
  40. package/scaffold/skills/vitest/references/features-concurrency.md +250 -0
  41. package/scaffold/skills/vitest/references/features-context.md +238 -0
  42. package/scaffold/skills/vitest/references/features-coverage.md +207 -0
  43. package/scaffold/skills/vitest/references/features-filtering.md +211 -0
  44. package/scaffold/skills/vitest/references/features-mocking.md +265 -0
  45. package/scaffold/skills/vitest/references/features-snapshots.md +207 -0
  46. package/scaffold/skills/worktree/SKILL.md +93 -0
  47. package/scaffold/storyboard.config.json +44 -0
  48. package/src/canvas/Canvas.jsx +78 -0
  49. package/src/canvas/Draggable.jsx +235 -0
  50. package/src/canvas/index.d.ts +41 -0
  51. package/src/canvas/index.js +6 -0
  52. package/src/canvas/style.css +118 -0
  53. package/src/canvas/useResetCanvas.js +17 -0
  54. package/src/canvas/utils.js +136 -0
  55. package/src/core/assets/fonts/IoskeleyMono-Bold.woff2 +0 -0
  56. package/src/core/assets/fonts/IoskeleyMono-Italic.woff2 +0 -0
  57. package/src/core/assets/fonts/IoskeleyMono-Medium.woff2 +0 -0
  58. package/src/core/assets/fonts/IoskeleyMono-Regular.woff2 +0 -0
  59. package/src/core/assets/fonts/IoskeleyMono-SemiBold.woff2 +0 -0
  60. package/src/core/autosync/server.js +714 -0
  61. package/src/core/autosync/server.test.js +158 -0
  62. package/src/core/canvas/__tests__/agent-integration.test.js +596 -0
  63. package/src/core/canvas/__tests__/helpers/browser.js +95 -0
  64. package/src/core/canvas/__tests__/helpers/canvas-api.js +129 -0
  65. package/src/core/canvas/__tests__/helpers/perf.js +118 -0
  66. package/src/core/canvas/__tests__/helpers/setup.js +176 -0
  67. package/src/core/canvas/__tests__/helpers/tmux.js +130 -0
  68. package/src/core/canvas/__tests__/helpers/transcript.js +132 -0
  69. package/src/core/canvas/__tests__/terminal-integration.test.js +177 -0
  70. package/src/core/canvas/collision.js +292 -0
  71. package/src/core/canvas/collision.test.js +371 -0
  72. package/src/core/canvas/compact.js +83 -0
  73. package/src/core/canvas/deriveCanvasId.test.js +40 -0
  74. package/src/core/canvas/githubEmbeds.js +527 -0
  75. package/src/core/canvas/githubEmbeds.test.js +302 -0
  76. package/src/core/canvas/hot-pool.js +766 -0
  77. package/src/core/canvas/identity.js +107 -0
  78. package/src/core/canvas/identity.test.js +100 -0
  79. package/src/core/canvas/materializer.js +259 -0
  80. package/src/core/canvas/materializer.test.js +356 -0
  81. package/src/core/canvas/selectedWidgets.js +270 -0
  82. package/src/core/canvas/selectedWidgets.test.js +321 -0
  83. package/src/core/canvas/server.js +3134 -0
  84. package/src/core/canvas/server.test.js +379 -0
  85. package/src/core/canvas/terminal-config.js +330 -0
  86. package/src/core/canvas/terminal-registry.js +465 -0
  87. package/src/core/canvas/terminal-server.js +1436 -0
  88. package/src/core/canvas/writeGuard.js +53 -0
  89. package/src/core/cli/agent.js +85 -0
  90. package/src/core/cli/branch.js +386 -0
  91. package/src/core/cli/canvasAdd.js +241 -0
  92. package/src/core/cli/canvasBatch.js +98 -0
  93. package/src/core/cli/canvasBounds.js +160 -0
  94. package/src/core/cli/canvasRead.js +236 -0
  95. package/src/core/cli/canvasUpdate.js +179 -0
  96. package/src/core/cli/code.js +67 -0
  97. package/src/core/cli/compact.js +62 -0
  98. package/src/core/cli/create.js +674 -0
  99. package/src/core/cli/dev-helpers.js +53 -0
  100. package/src/core/cli/dev-helpers.test.js +53 -0
  101. package/src/core/cli/dev.js +430 -0
  102. package/src/core/cli/exit.js +38 -0
  103. package/src/core/cli/flags.js +174 -0
  104. package/src/core/cli/flags.test.js +155 -0
  105. package/src/core/cli/index.js +233 -0
  106. package/src/core/cli/intro.js +37 -0
  107. package/src/core/cli/proxy.js +319 -0
  108. package/src/core/cli/proxy.test.js +63 -0
  109. package/src/core/cli/schemas.js +223 -0
  110. package/src/core/cli/server.js +192 -0
  111. package/src/core/cli/serverUrl.js +61 -0
  112. package/src/core/cli/sessions.js +459 -0
  113. package/src/core/cli/setup.js +404 -0
  114. package/src/core/cli/terminal-commands.js +287 -0
  115. package/src/core/cli/terminal-messaging.js +231 -0
  116. package/src/core/cli/terminal-welcome.js +515 -0
  117. package/src/core/cli/updateVersion.js +124 -0
  118. package/src/core/comments/api.js +284 -0
  119. package/src/core/comments/api.test.js +282 -0
  120. package/src/core/comments/auth.js +151 -0
  121. package/src/core/comments/auth.test.js +167 -0
  122. package/src/core/comments/commentCache.js +109 -0
  123. package/src/core/comments/commentCache.test.js +48 -0
  124. package/src/core/comments/commentDrafts.js +68 -0
  125. package/src/core/comments/commentMode.js +63 -0
  126. package/src/core/comments/commentMode.test.js +90 -0
  127. package/src/core/comments/config.js +47 -0
  128. package/src/core/comments/config.test.js +77 -0
  129. package/src/core/comments/graphql.js +65 -0
  130. package/src/core/comments/graphql.test.js +95 -0
  131. package/src/core/comments/index.js +42 -0
  132. package/src/core/comments/metadata.js +52 -0
  133. package/src/core/comments/metadata.test.js +110 -0
  134. package/src/core/comments/queries.js +245 -0
  135. package/src/core/comments/ui/AuthModal.jsx +114 -0
  136. package/src/core/comments/ui/CommentOverlay.js +52 -0
  137. package/src/core/comments/ui/CommentWindow.jsx +329 -0
  138. package/src/core/comments/ui/CommentsDrawer.jsx +102 -0
  139. package/src/core/comments/ui/Composer.jsx +64 -0
  140. package/src/core/comments/ui/authModal.js +66 -0
  141. package/src/core/comments/ui/authModal.test.js +76 -0
  142. package/src/core/comments/ui/comment-cursor-dark.svg +1 -0
  143. package/src/core/comments/ui/comment-cursor.svg +1 -0
  144. package/src/core/comments/ui/comment-layout.css +142 -0
  145. package/src/core/comments/ui/commentWindow.js +121 -0
  146. package/src/core/comments/ui/comments.css +242 -0
  147. package/src/core/comments/ui/commentsDrawer.js +84 -0
  148. package/src/core/comments/ui/composer.js +136 -0
  149. package/src/core/comments/ui/index.js +14 -0
  150. package/src/core/comments/ui/mount.js +687 -0
  151. package/src/core/comments/ui/mount.test.js +336 -0
  152. package/src/core/data/dotPath.js +53 -0
  153. package/src/core/data/dotPath.test.js +114 -0
  154. package/src/core/data/loader.js +409 -0
  155. package/src/core/data/loader.test.js +599 -0
  156. package/src/core/data/viewfinder.js +363 -0
  157. package/src/core/data/viewfinder.test.js +456 -0
  158. package/src/core/devtools/devtools-consumer.js +28 -0
  159. package/src/core/devtools/devtools.js +144 -0
  160. package/src/core/devtools/devtools.test.js +75 -0
  161. package/src/core/devtools/sceneDebug.js +112 -0
  162. package/src/core/devtools/sceneDebug.test.js +141 -0
  163. package/src/core/index.js +124 -0
  164. package/src/core/inspector/fiberWalker.js +239 -0
  165. package/src/core/inspector/highlighter.js +275 -0
  166. package/src/core/inspector/mouseMode.js +259 -0
  167. package/src/core/lib/components/ui/alert/alert-action.jsx +11 -0
  168. package/src/core/lib/components/ui/alert/alert-description.jsx +11 -0
  169. package/src/core/lib/components/ui/alert/alert-title.jsx +11 -0
  170. package/src/core/lib/components/ui/alert/alert.jsx +25 -0
  171. package/src/core/lib/components/ui/alert/index.js +17 -0
  172. package/src/core/lib/components/ui/avatar/avatar-badge.jsx +22 -0
  173. package/src/core/lib/components/ui/avatar/avatar-fallback.jsx +18 -0
  174. package/src/core/lib/components/ui/avatar/avatar-group-count.jsx +19 -0
  175. package/src/core/lib/components/ui/avatar/avatar-group.jsx +19 -0
  176. package/src/core/lib/components/ui/avatar/avatar-image.jsx +15 -0
  177. package/src/core/lib/components/ui/avatar/avatar.jsx +19 -0
  178. package/src/core/lib/components/ui/avatar/index.js +22 -0
  179. package/src/core/lib/components/ui/badge/badge.jsx +31 -0
  180. package/src/core/lib/components/ui/badge/index.js +2 -0
  181. package/src/core/lib/components/ui/button/button.jsx +100 -0
  182. package/src/core/lib/components/ui/button/index.js +12 -0
  183. package/src/core/lib/components/ui/card/card-action.jsx +11 -0
  184. package/src/core/lib/components/ui/card/card-content.jsx +11 -0
  185. package/src/core/lib/components/ui/card/card-description.jsx +11 -0
  186. package/src/core/lib/components/ui/card/card-footer.jsx +11 -0
  187. package/src/core/lib/components/ui/card/card-header.jsx +19 -0
  188. package/src/core/lib/components/ui/card/card-title.jsx +11 -0
  189. package/src/core/lib/components/ui/card/card.jsx +17 -0
  190. package/src/core/lib/components/ui/card/index.js +25 -0
  191. package/src/core/lib/components/ui/checkbox/checkbox.jsx +29 -0
  192. package/src/core/lib/components/ui/checkbox/index.js +6 -0
  193. package/src/core/lib/components/ui/collapsible/collapsible-content.jsx +7 -0
  194. package/src/core/lib/components/ui/collapsible/collapsible-trigger.jsx +7 -0
  195. package/src/core/lib/components/ui/collapsible/collapsible.jsx +7 -0
  196. package/src/core/lib/components/ui/collapsible/index.js +13 -0
  197. package/src/core/lib/components/ui/dialog/dialog-close.jsx +7 -0
  198. package/src/core/lib/components/ui/dialog/dialog-content.jsx +34 -0
  199. package/src/core/lib/components/ui/dialog/dialog-description.jsx +15 -0
  200. package/src/core/lib/components/ui/dialog/dialog-footer.jsx +23 -0
  201. package/src/core/lib/components/ui/dialog/dialog-header.jsx +11 -0
  202. package/src/core/lib/components/ui/dialog/dialog-overlay.jsx +15 -0
  203. package/src/core/lib/components/ui/dialog/dialog-portal.jsx +4 -0
  204. package/src/core/lib/components/ui/dialog/dialog-title.jsx +15 -0
  205. package/src/core/lib/components/ui/dialog/dialog-trigger.jsx +7 -0
  206. package/src/core/lib/components/ui/dialog/dialog.jsx +4 -0
  207. package/src/core/lib/components/ui/dialog/index.js +34 -0
  208. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.jsx +8 -0
  209. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.jsx +30 -0
  210. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-content.jsx +22 -0
  211. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.jsx +16 -0
  212. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-group.jsx +7 -0
  213. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-item.jsx +20 -0
  214. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-label.jsx +17 -0
  215. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-portal.jsx +4 -0
  216. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.jsx +7 -0
  217. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.jsx +29 -0
  218. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-separator.jsx +15 -0
  219. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.jsx +16 -0
  220. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.jsx +15 -0
  221. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.jsx +23 -0
  222. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub.jsx +4 -0
  223. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-trigger.jsx +7 -0
  224. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu.jsx +4 -0
  225. package/src/core/lib/components/ui/dropdown-menu/index.js +54 -0
  226. package/src/core/lib/components/ui/input/index.js +7 -0
  227. package/src/core/lib/components/ui/input/input.jsx +19 -0
  228. package/src/core/lib/components/ui/label/index.js +7 -0
  229. package/src/core/lib/components/ui/label/label.jsx +19 -0
  230. package/src/core/lib/components/ui/panel/index.js +24 -0
  231. package/src/core/lib/components/ui/panel/panel-body.jsx +11 -0
  232. package/src/core/lib/components/ui/panel/panel-close.jsx +16 -0
  233. package/src/core/lib/components/ui/panel/panel-content.jsx +29 -0
  234. package/src/core/lib/components/ui/panel/panel-footer.jsx +11 -0
  235. package/src/core/lib/components/ui/panel/panel-header.jsx +11 -0
  236. package/src/core/lib/components/ui/panel/panel-title.jsx +12 -0
  237. package/src/core/lib/components/ui/panel/panel.jsx +4 -0
  238. package/src/core/lib/components/ui/popover/index.js +28 -0
  239. package/src/core/lib/components/ui/popover/popover-close.jsx +7 -0
  240. package/src/core/lib/components/ui/popover/popover-content.jsx +22 -0
  241. package/src/core/lib/components/ui/popover/popover-description.jsx +11 -0
  242. package/src/core/lib/components/ui/popover/popover-header.jsx +11 -0
  243. package/src/core/lib/components/ui/popover/popover-portal.jsx +4 -0
  244. package/src/core/lib/components/ui/popover/popover-title.jsx +11 -0
  245. package/src/core/lib/components/ui/popover/popover-trigger.jsx +8 -0
  246. package/src/core/lib/components/ui/popover/popover.jsx +4 -0
  247. package/src/core/lib/components/ui/searchable-list.jsx +160 -0
  248. package/src/core/lib/components/ui/select/index.js +37 -0
  249. package/src/core/lib/components/ui/select/select-content.jsx +30 -0
  250. package/src/core/lib/components/ui/select/select-group-heading.jsx +17 -0
  251. package/src/core/lib/components/ui/select/select-group.jsx +15 -0
  252. package/src/core/lib/components/ui/select/select-item.jsx +26 -0
  253. package/src/core/lib/components/ui/select/select-label.jsx +11 -0
  254. package/src/core/lib/components/ui/select/select-portal.jsx +4 -0
  255. package/src/core/lib/components/ui/select/select-scroll-down-button.jsx +18 -0
  256. package/src/core/lib/components/ui/select/select-scroll-up-button.jsx +18 -0
  257. package/src/core/lib/components/ui/select/select-separator.jsx +15 -0
  258. package/src/core/lib/components/ui/select/select-trigger.jsx +25 -0
  259. package/src/core/lib/components/ui/select/select.jsx +4 -0
  260. package/src/core/lib/components/ui/separator/index.js +7 -0
  261. package/src/core/lib/components/ui/separator/separator.jsx +22 -0
  262. package/src/core/lib/components/ui/sheet/index.js +34 -0
  263. package/src/core/lib/components/ui/sheet/sheet-close.jsx +7 -0
  264. package/src/core/lib/components/ui/sheet/sheet-content.jsx +35 -0
  265. package/src/core/lib/components/ui/sheet/sheet-description.jsx +15 -0
  266. package/src/core/lib/components/ui/sheet/sheet-footer.jsx +11 -0
  267. package/src/core/lib/components/ui/sheet/sheet-header.jsx +11 -0
  268. package/src/core/lib/components/ui/sheet/sheet-overlay.jsx +15 -0
  269. package/src/core/lib/components/ui/sheet/sheet-portal.jsx +4 -0
  270. package/src/core/lib/components/ui/sheet/sheet-title.jsx +15 -0
  271. package/src/core/lib/components/ui/sheet/sheet-trigger.jsx +7 -0
  272. package/src/core/lib/components/ui/sheet/sheet.jsx +4 -0
  273. package/src/core/lib/components/ui/textarea/index.js +7 -0
  274. package/src/core/lib/components/ui/textarea/textarea.jsx +18 -0
  275. package/src/core/lib/components/ui/toggle/index.js +8 -0
  276. package/src/core/lib/components/ui/toggle/toggle.jsx +36 -0
  277. package/src/core/lib/components/ui/toggle-group/index.js +10 -0
  278. package/src/core/lib/components/ui/toggle-group/toggle-group-item.jsx +29 -0
  279. package/src/core/lib/components/ui/toggle-group/toggle-group.jsx +43 -0
  280. package/src/core/lib/components/ui/tooltip/index.js +3 -0
  281. package/src/core/lib/components/ui/tooltip/tooltip-content.jsx +21 -0
  282. package/src/core/lib/components/ui/tooltip/tooltip-trigger.jsx +23 -0
  283. package/src/core/lib/components/ui/tooltip/tooltip.jsx +11 -0
  284. package/src/core/lib/components/ui/trigger-button/index.js +6 -0
  285. package/src/core/lib/components/ui/trigger-button/trigger-button.css +38 -0
  286. package/src/core/lib/components/ui/trigger-button/trigger-button.jsx +63 -0
  287. package/src/core/lib/utils/index.js +6 -0
  288. package/src/core/logger/devLogger.js +238 -0
  289. package/src/core/logger/devLogger.test.js +193 -0
  290. package/src/core/modes/modes.css +98 -0
  291. package/src/core/modes/modes.js +492 -0
  292. package/src/core/modes/modes.test.js +562 -0
  293. package/src/core/mountStoryboardCore.js +478 -0
  294. package/src/core/rename-watcher/config.json +23 -0
  295. package/src/core/rename-watcher/watcher.js +531 -0
  296. package/src/core/scaffold.js +100 -0
  297. package/src/core/server/index.js +391 -0
  298. package/src/core/session/bodyClasses.js +128 -0
  299. package/src/core/session/bodyClasses.test.js +192 -0
  300. package/src/core/session/hashSubscribe.js +19 -0
  301. package/src/core/session/hashSubscribe.test.js +62 -0
  302. package/src/core/session/hideMode.js +424 -0
  303. package/src/core/session/hideMode.test.js +268 -0
  304. package/src/core/session/interceptHideParams.js +35 -0
  305. package/src/core/session/interceptHideParams.test.js +90 -0
  306. package/src/core/session/localStorage.js +134 -0
  307. package/src/core/session/localStorage.test.js +148 -0
  308. package/src/core/session/session.js +76 -0
  309. package/src/core/session/session.test.js +91 -0
  310. package/src/core/stores/canvasConfig.js +134 -0
  311. package/src/core/stores/canvasConfig.test.js +120 -0
  312. package/src/core/stores/commandActions.js +284 -0
  313. package/src/core/stores/commandPaletteConfig.js +31 -0
  314. package/src/core/stores/configSchema.js +232 -0
  315. package/src/core/stores/configSchema.test.js +72 -0
  316. package/src/core/stores/configStore.js +161 -0
  317. package/src/core/stores/customerModeConfig.js +30 -0
  318. package/src/core/stores/featureFlags.js +127 -0
  319. package/src/core/stores/paletteProviders.js +360 -0
  320. package/src/core/stores/paletteProviders.test.js +186 -0
  321. package/src/core/stores/plugins.js +40 -0
  322. package/src/core/stores/plugins.test.js +68 -0
  323. package/src/core/stores/recentArtifacts.js +68 -0
  324. package/src/core/stores/recentArtifacts.test.js +71 -0
  325. package/src/core/stores/sidePanelStore.ts +143 -0
  326. package/src/core/stores/themeStore.ts +291 -0
  327. package/src/core/stores/toolRegistry.js +227 -0
  328. package/src/core/stores/toolStateStore.js +183 -0
  329. package/src/core/stores/toolStateStore.test.js +220 -0
  330. package/src/core/stores/toolbarConfigStore.js +165 -0
  331. package/src/core/stores/uiConfig.js +64 -0
  332. package/src/core/stores/uiConfig.test.js +63 -0
  333. package/src/core/styles/tailwind.css +204 -0
  334. package/src/core/tools/handlers/autosync.js +12 -0
  335. package/src/core/tools/handlers/canvasAddWidget.js +11 -0
  336. package/src/core/tools/handlers/canvasAgents.js +20 -0
  337. package/src/core/tools/handlers/canvasToolbar.js +56 -0
  338. package/src/core/tools/handlers/commandPalette.js +9 -0
  339. package/src/core/tools/handlers/comments.js +16 -0
  340. package/src/core/tools/handlers/create.js +39 -0
  341. package/src/core/tools/handlers/devtools.js +122 -0
  342. package/src/core/tools/handlers/devtools.test.js +87 -0
  343. package/src/core/tools/handlers/featureFlags.js +21 -0
  344. package/src/core/tools/handlers/flows.js +68 -0
  345. package/src/core/tools/handlers/hideChrome.js +9 -0
  346. package/src/core/tools/handlers/hideToolbars.js +25 -0
  347. package/src/core/tools/handlers/inspector.js +19 -0
  348. package/src/core/tools/handlers/paletteTheme.js +35 -0
  349. package/src/core/tools/handlers/theme.js +9 -0
  350. package/src/core/tools/registry.js +26 -0
  351. package/src/core/tools/surfaces/canvasToolbar.js +10 -0
  352. package/src/core/tools/surfaces/commandList.js +10 -0
  353. package/src/core/tools/surfaces/mainToolbar.js +11 -0
  354. package/src/core/tools/surfaces/registry.js +19 -0
  355. package/src/core/ui/ActionMenuButton.jsx +114 -0
  356. package/src/core/ui/AutosyncMenuButton.css +67 -0
  357. package/src/core/ui/AutosyncMenuButton.jsx +242 -0
  358. package/src/core/ui/BranchSelect.jsx +29 -0
  359. package/src/core/ui/BranchSelect.module.css +30 -0
  360. package/src/core/ui/CanvasAgentsMenu.jsx +89 -0
  361. package/src/core/ui/CanvasCreateMenu.jsx +611 -0
  362. package/src/core/ui/CanvasSnap.css +27 -0
  363. package/src/core/ui/CanvasSnap.jsx +51 -0
  364. package/src/core/ui/CanvasUndoRedo.css +36 -0
  365. package/src/core/ui/CanvasUndoRedo.jsx +62 -0
  366. package/src/core/ui/CanvasZoomControl.css +53 -0
  367. package/src/core/ui/CanvasZoomControl.jsx +49 -0
  368. package/src/core/ui/CanvasZoomToFit.css +18 -0
  369. package/src/core/ui/CanvasZoomToFit.jsx +26 -0
  370. package/src/core/ui/CommandMenu.css +8 -0
  371. package/src/core/ui/CommandMenu.jsx +287 -0
  372. package/src/core/ui/CommandPalette.jsx +35 -0
  373. package/src/core/ui/CommandPaletteTrigger.jsx +25 -0
  374. package/src/core/ui/CommentsMenuButton.jsx +40 -0
  375. package/src/core/ui/CoreUIBar.css +47 -0
  376. package/src/core/ui/CoreUIBar.jsx +905 -0
  377. package/src/core/ui/CreateMenuButton.jsx +117 -0
  378. package/src/core/ui/HideChromeTrigger.jsx +48 -0
  379. package/src/core/ui/Icon.jsx +279 -0
  380. package/src/core/ui/InspectorPanel.css +109 -0
  381. package/src/core/ui/InspectorPanel.jsx +632 -0
  382. package/src/core/ui/PwaInstallBanner.css +42 -0
  383. package/src/core/ui/PwaInstallBanner.jsx +124 -0
  384. package/src/core/ui/SidePanel.jsx +261 -0
  385. package/src/core/ui/ThemeMenuButton.jsx +139 -0
  386. package/src/core/ui/core-ui-colors.css +129 -0
  387. package/src/core/ui/design-modes.ts +7 -0
  388. package/src/core/ui/sidepanel.css +301 -0
  389. package/src/core/ui/viewfinder.ts +7 -0
  390. package/src/core/ui-entry.js +30 -0
  391. package/src/core/utils/fuzzySearch.js +117 -0
  392. package/src/core/utils/fuzzySearch.test.js +119 -0
  393. package/src/core/utils/mobileViewport.js +57 -0
  394. package/src/core/utils/mobileViewport.test.js +68 -0
  395. package/src/core/utils/prodMode.js +38 -0
  396. package/src/core/utils/smoothCorners.js +20 -0
  397. package/src/core/vite/docs-handler.js +155 -0
  398. package/src/core/vite/server-plugin.js +797 -0
  399. package/src/core/workshop/features/createCanvas/CreateCanvasForm.jsx +260 -0
  400. package/src/core/workshop/features/createCanvas/index.js +14 -0
  401. package/src/core/workshop/features/createFlow/CreateFlowForm.jsx +334 -0
  402. package/src/core/workshop/features/createFlow/index.js +19 -0
  403. package/src/core/workshop/features/createFlow/server.js +663 -0
  404. package/src/core/workshop/features/createPage/CreatePageForm.jsx +304 -0
  405. package/src/core/workshop/features/createPage/index.js +11 -0
  406. package/src/core/workshop/features/createPrototype/CreatePrototypeForm.jsx +289 -0
  407. package/src/core/workshop/features/createPrototype/index.js +19 -0
  408. package/src/core/workshop/features/createPrototype/server.js +433 -0
  409. package/src/core/workshop/features/createStory/CreateStoryForm.jsx +208 -0
  410. package/src/core/workshop/features/createStory/index.js +14 -0
  411. package/src/core/workshop/features/registry-server.js +22 -0
  412. package/src/core/workshop/features/registry.js +28 -0
  413. package/src/core/workshop/features/templateIndex.js +155 -0
  414. package/src/core/workshop/ui/WorkshopPanel.jsx +98 -0
  415. package/src/core/workshop/ui/mount.ts +6 -0
  416. package/src/core/worktree/port.js +268 -0
  417. package/src/core/worktree/port.test.js +222 -0
  418. package/src/core/worktree/serverRegistry.js +120 -0
  419. package/src/internals/AuthModal/AuthModal.jsx +132 -0
  420. package/src/internals/AuthModal/AuthModal.module.css +221 -0
  421. package/src/internals/BranchBar/BranchBar.jsx +87 -0
  422. package/src/internals/BranchBar/BranchBar.module.css +247 -0
  423. package/src/internals/BranchBar/useBranches.js +93 -0
  424. package/src/internals/BranchBar/useBranches.test.js +68 -0
  425. package/src/internals/CommandPalette/CommandPalette.jsx +1361 -0
  426. package/src/internals/CommandPalette/CreateDialog.jsx +219 -0
  427. package/src/internals/CommandPalette/command-palette.css +180 -0
  428. package/src/internals/FlowError.module.css +30 -0
  429. package/src/internals/Icon.jsx +279 -0
  430. package/src/internals/StoryboardContext.js +3 -0
  431. package/src/internals/Viewfinder.jsx +1479 -0
  432. package/src/internals/Viewfinder.module.css +1540 -0
  433. package/src/internals/Workspace.jsx +7 -0
  434. package/src/internals/__mocks__/virtual-storyboard-data-index.js +4 -0
  435. package/src/internals/canvas/CanvasControls.jsx +112 -0
  436. package/src/internals/canvas/CanvasControls.module.css +135 -0
  437. package/src/internals/canvas/CanvasPage.bridge.test.jsx +387 -0
  438. package/src/internals/canvas/CanvasPage.dragdrop.test.jsx +350 -0
  439. package/src/internals/canvas/CanvasPage.jsx +3092 -0
  440. package/src/internals/canvas/CanvasPage.module.css +187 -0
  441. package/src/internals/canvas/CanvasPage.multiselect.test.jsx +358 -0
  442. package/src/internals/canvas/CanvasToolbar.jsx +73 -0
  443. package/src/internals/canvas/CanvasToolbar.module.css +92 -0
  444. package/src/internals/canvas/ComponentErrorBoundary.jsx +50 -0
  445. package/src/internals/canvas/ConnectorLayer.jsx +208 -0
  446. package/src/internals/canvas/ConnectorLayer.module.css +129 -0
  447. package/src/internals/canvas/MarqueeOverlay.jsx +20 -0
  448. package/src/internals/canvas/PageSelector.jsx +587 -0
  449. package/src/internals/canvas/PageSelector.module.css +261 -0
  450. package/src/internals/canvas/PageSelector.test.jsx +113 -0
  451. package/src/internals/canvas/WebGLContextPool.jsx +292 -0
  452. package/src/internals/canvas/WebGLContextPool.test.jsx +165 -0
  453. package/src/internals/canvas/canvasApi.js +164 -0
  454. package/src/internals/canvas/canvasReloadGuard.js +37 -0
  455. package/src/internals/canvas/canvasReloadGuard.test.js +27 -0
  456. package/src/internals/canvas/canvasTheme.js +118 -0
  457. package/src/internals/canvas/componentIsolate.jsx +165 -0
  458. package/src/internals/canvas/componentSetIsolate.jsx +257 -0
  459. package/src/internals/canvas/computeCanvasBounds.test.js +121 -0
  460. package/src/internals/canvas/connectorGeometry.js +132 -0
  461. package/src/internals/canvas/hotPoolDevLogs.js +25 -0
  462. package/src/internals/canvas/textSelection.js +10 -0
  463. package/src/internals/canvas/textSelection.test.js +26 -0
  464. package/src/internals/canvas/useCanvas.js +126 -0
  465. package/src/internals/canvas/useCanvas.test.js +26 -0
  466. package/src/internals/canvas/useMarqueeSelect.js +213 -0
  467. package/src/internals/canvas/useMarqueeSelect.test.js +78 -0
  468. package/src/internals/canvas/useUndoRedo.js +86 -0
  469. package/src/internals/canvas/useUndoRedo.test.js +231 -0
  470. package/src/internals/canvas/widgets/CodePenEmbed.jsx +293 -0
  471. package/src/internals/canvas/widgets/CodePenEmbed.module.css +161 -0
  472. package/src/internals/canvas/widgets/ComponentSetWidget.jsx +2 -0
  473. package/src/internals/canvas/widgets/ComponentSetWidget.module.css +89 -0
  474. package/src/internals/canvas/widgets/ComponentWidget.jsx +14 -0
  475. package/src/internals/canvas/widgets/ComponentWidget.module.css +0 -0
  476. package/src/internals/canvas/widgets/CropOverlay.jsx +179 -0
  477. package/src/internals/canvas/widgets/CropOverlay.module.css +154 -0
  478. package/src/internals/canvas/widgets/ExpandedPane.jsx +474 -0
  479. package/src/internals/canvas/widgets/ExpandedPane.module.css +179 -0
  480. package/src/internals/canvas/widgets/ExpandedPane.test.jsx +240 -0
  481. package/src/internals/canvas/widgets/ExpandedPaneTopBar.jsx +111 -0
  482. package/src/internals/canvas/widgets/ExpandedPaneTopBar.module.css +59 -0
  483. package/src/internals/canvas/widgets/ExpandedPaneTopBar.test.jsx +45 -0
  484. package/src/internals/canvas/widgets/FigmaEmbed.jsx +296 -0
  485. package/src/internals/canvas/widgets/FigmaEmbed.module.css +222 -0
  486. package/src/internals/canvas/widgets/FrozenTerminalOverlay.jsx +151 -0
  487. package/src/internals/canvas/widgets/FrozenTerminalOverlay.module.css +83 -0
  488. package/src/internals/canvas/widgets/ImageWidget.jsx +287 -0
  489. package/src/internals/canvas/widgets/ImageWidget.module.css +81 -0
  490. package/src/internals/canvas/widgets/LinkPreview.jsx +439 -0
  491. package/src/internals/canvas/widgets/LinkPreview.module.css +585 -0
  492. package/src/internals/canvas/widgets/LinkPreview.test.jsx +193 -0
  493. package/src/internals/canvas/widgets/MarkdownBlock.jsx +354 -0
  494. package/src/internals/canvas/widgets/MarkdownBlock.module.css +377 -0
  495. package/src/internals/canvas/widgets/MarkdownBlock.test.jsx +92 -0
  496. package/src/internals/canvas/widgets/PromptWidget.jsx +428 -0
  497. package/src/internals/canvas/widgets/PromptWidget.module.css +273 -0
  498. package/src/internals/canvas/widgets/PrototypeEmbed.jsx +463 -0
  499. package/src/internals/canvas/widgets/PrototypeEmbed.module.css +579 -0
  500. package/src/internals/canvas/widgets/PrototypeEmbed.test.jsx +10 -0
  501. package/src/internals/canvas/widgets/ResizeHandle.jsx +67 -0
  502. package/src/internals/canvas/widgets/ResizeHandle.module.css +29 -0
  503. package/src/internals/canvas/widgets/StickyNote.jsx +92 -0
  504. package/src/internals/canvas/widgets/StickyNote.module.css +70 -0
  505. package/src/internals/canvas/widgets/StickyNote.test.jsx +116 -0
  506. package/src/internals/canvas/widgets/StorySetWidget.jsx +208 -0
  507. package/src/internals/canvas/widgets/StorySetWidget.module.css +89 -0
  508. package/src/internals/canvas/widgets/StoryWidget.jsx +334 -0
  509. package/src/internals/canvas/widgets/StoryWidget.module.css +211 -0
  510. package/src/internals/canvas/widgets/TerminalReadWidget.jsx +146 -0
  511. package/src/internals/canvas/widgets/TerminalReadWidget.module.css +94 -0
  512. package/src/internals/canvas/widgets/TerminalWidget.jsx +704 -0
  513. package/src/internals/canvas/widgets/TerminalWidget.module.css +444 -0
  514. package/src/internals/canvas/widgets/TilesWidget.jsx +300 -0
  515. package/src/internals/canvas/widgets/TilesWidget.module.css +133 -0
  516. package/src/internals/canvas/widgets/WidgetChrome.jsx +580 -0
  517. package/src/internals/canvas/widgets/WidgetChrome.module.css +421 -0
  518. package/src/internals/canvas/widgets/WidgetWrapper.jsx +15 -0
  519. package/src/internals/canvas/widgets/WidgetWrapper.module.css +25 -0
  520. package/src/internals/canvas/widgets/codepenUrl.js +75 -0
  521. package/src/internals/canvas/widgets/codepenUrl.test.js +76 -0
  522. package/src/internals/canvas/widgets/embedInteraction.test.jsx +173 -0
  523. package/src/internals/canvas/widgets/embedOverlay.module.css +35 -0
  524. package/src/internals/canvas/widgets/embedTheme.js +148 -0
  525. package/src/internals/canvas/widgets/expandUtils.js +559 -0
  526. package/src/internals/canvas/widgets/expandUtils.test.js +155 -0
  527. package/src/internals/canvas/widgets/figmaUrl.js +118 -0
  528. package/src/internals/canvas/widgets/figmaUrl.test.js +139 -0
  529. package/src/internals/canvas/widgets/githubUrl.js +82 -0
  530. package/src/internals/canvas/widgets/githubUrl.test.js +74 -0
  531. package/src/internals/canvas/widgets/iframeDevLogs.js +49 -0
  532. package/src/internals/canvas/widgets/iframeDevLogs.test.jsx +81 -0
  533. package/src/internals/canvas/widgets/index.js +42 -0
  534. package/src/internals/canvas/widgets/pasteRules.js +295 -0
  535. package/src/internals/canvas/widgets/pasteRules.test.js +474 -0
  536. package/src/internals/canvas/widgets/snapshotDisplay.test.jsx +211 -0
  537. package/src/internals/canvas/widgets/tilePool.js +23 -0
  538. package/src/internals/canvas/widgets/tiles/diagonal-bl.png +0 -0
  539. package/src/internals/canvas/widgets/tiles/diagonal-br.png +0 -0
  540. package/src/internals/canvas/widgets/tiles/diagonal-tl.png +0 -0
  541. package/src/internals/canvas/widgets/tiles/leaf.png +0 -0
  542. package/src/internals/canvas/widgets/tiles/quarter-tl.png +0 -0
  543. package/src/internals/canvas/widgets/tiles/quarter-tr.png +0 -0
  544. package/src/internals/canvas/widgets/tiles/solid-a.png +0 -0
  545. package/src/internals/canvas/widgets/tiles/solid-b.png +0 -0
  546. package/src/internals/canvas/widgets/widgetConfig.js +291 -0
  547. package/src/internals/canvas/widgets/widgetConfig.test.js +68 -0
  548. package/src/internals/canvas/widgets/widgetIcons.jsx +190 -0
  549. package/src/internals/canvas/widgets/widgetProps.js +133 -0
  550. package/src/internals/context/FormContext.js +13 -0
  551. package/src/internals/context/FormContext.test.js +48 -0
  552. package/src/internals/context.jsx +481 -0
  553. package/src/internals/context.test.jsx +296 -0
  554. package/src/internals/hashPreserver.js +73 -0
  555. package/src/internals/hashPreserver.test.js +107 -0
  556. package/src/internals/hooks/useConfig.js +14 -0
  557. package/src/internals/hooks/useFeatureFlag.js +14 -0
  558. package/src/internals/hooks/useFlows.js +50 -0
  559. package/src/internals/hooks/useFlows.test.js +134 -0
  560. package/src/internals/hooks/useHideMode.js +31 -0
  561. package/src/internals/hooks/useHideMode.test.js +43 -0
  562. package/src/internals/hooks/useLocalStorage.js +57 -0
  563. package/src/internals/hooks/useLocalStorage.test.js +75 -0
  564. package/src/internals/hooks/useMode.js +43 -0
  565. package/src/internals/hooks/useObject.js +101 -0
  566. package/src/internals/hooks/useObject.test.js +74 -0
  567. package/src/internals/hooks/useOverride.js +84 -0
  568. package/src/internals/hooks/useOverride.test.js +71 -0
  569. package/src/internals/hooks/usePrototypeReloadGuard.js +64 -0
  570. package/src/internals/hooks/useRecord.js +158 -0
  571. package/src/internals/hooks/useRecord.test.js +221 -0
  572. package/src/internals/hooks/useScene.js +38 -0
  573. package/src/internals/hooks/useScene.test.js +66 -0
  574. package/src/internals/hooks/useSceneData.js +108 -0
  575. package/src/internals/hooks/useSceneData.test.js +136 -0
  576. package/src/internals/hooks/useSession.js +4 -0
  577. package/src/internals/hooks/useSession.test.js +8 -0
  578. package/src/internals/hooks/useThemeState.js +61 -0
  579. package/src/internals/hooks/useThemeState.test.js +66 -0
  580. package/src/internals/hooks/useUndoRedo.js +28 -0
  581. package/src/internals/hooks/useUndoRedo.test.js +64 -0
  582. package/src/internals/index.js +58 -0
  583. package/src/internals/story/ComponentSetPage.jsx +198 -0
  584. package/src/internals/story/ComponentSetPage.module.css +129 -0
  585. package/src/internals/story/StoryPage.jsx +147 -0
  586. package/src/internals/story/StoryPage.module.css +18 -0
  587. package/src/internals/test-utils.js +45 -0
  588. package/src/internals/vite/data-plugin.js +1508 -0
  589. package/src/internals/vite/data-plugin.test.js +1223 -0
  590. package/src/test-utils.js +44 -0
  591. package/toolbar.config.json +271 -0
  592. package/widgets.config.json +1537 -0
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Canvas Widget Props API
3
+ *
4
+ * Every canvas widget receives its data through a structured `props` object
5
+ * stored in .canvas.json. This module defines the prop schema system that
6
+ * widgets use to declare, read, and update their editable properties.
7
+ *
8
+ * ## Prop Categories
9
+ *
10
+ * Widget props are grouped into three categories:
11
+ *
12
+ * ### `content` — User-editable content
13
+ * Text, markdown, URLs — the stuff users type or paste.
14
+ * Updated frequently (every keystroke when editing).
15
+ * Examples: sticky note text, markdown content, embed URL.
16
+ *
17
+ * ### `settings` — Widget configuration
18
+ * One-off choices that affect appearance or behavior.
19
+ * Updated infrequently (user picks from a menu).
20
+ * Examples: sticky note color, markdown width, embed layout.
21
+ *
22
+ * ### `size` — Dimensions
23
+ * Width and height of the widget.
24
+ * Updated via resize handles or explicit input.
25
+ * Examples: markdown block width, prototype embed width/height.
26
+ *
27
+ * ## Storage Format (.canvas.json)
28
+ *
29
+ * Props are stored flat in the widget's `props` object:
30
+ *
31
+ * ```json
32
+ * {
33
+ * "id": "sticky-1",
34
+ * "type": "sticky-note",
35
+ * "position": { "x": 100, "y": 200 },
36
+ * "props": {
37
+ * "text": "Hello world",
38
+ * "color": "yellow"
39
+ * }
40
+ * }
41
+ * ```
42
+ *
43
+ * ## Widget Contract
44
+ *
45
+ * Every widget component receives:
46
+ * - `id` — stable widget identifier
47
+ * - `props` — the flat props object (may be null/undefined)
48
+ * - `onUpdate` — callback to persist prop changes: onUpdate({ key: value })
49
+ * - `onRemove` — callback to delete the widget
50
+ *
51
+ * `onUpdate` accepts a partial object that is shallow-merged into `props`.
52
+ * Multiple keys can be updated in one call:
53
+ * onUpdate({ text: 'new text', color: 'blue' })
54
+ *
55
+ * ## Declaring Widget Props (Schema)
56
+ *
57
+ * Widget prop schemas are defined in widgets.config.json (packages/core)
58
+ * and loaded via widgetConfig.js. This module re-exports the generated
59
+ * schemas and provides utility functions for reading props with defaults.
60
+ */
61
+
62
+ /**
63
+ * @typedef {'text' | 'select' | 'number' | 'url' | 'boolean'} PropType
64
+ *
65
+ * @typedef {Object} PropDef
66
+ * @property {PropType} type — input type for editing
67
+ * @property {string} label — human-readable label
68
+ * @property {string} category — 'content' | 'settings' | 'size'
69
+ * @property {*} defaultValue — fallback when prop is missing
70
+ * @property {Array} [options] — choices for 'select' type
71
+ * @property {number} [min] — minimum for 'number' type
72
+ * @property {number} [max] — maximum for 'number' type
73
+ */
74
+
75
+ import { schemas as configSchemas } from './widgetConfig.js'
76
+
77
+ /**
78
+ * Read a prop value with fallback to schema default.
79
+ * @param {object} props — widget props object (may be null)
80
+ * @param {string} key — prop name
81
+ * @param {object} schema — widget schema
82
+ * @returns {*}
83
+ */
84
+ export function readProp(props, key, schema) {
85
+ const value = props?.[key]
86
+ if (value !== undefined && value !== null) return value
87
+ return schema[key]?.defaultValue ?? null
88
+ }
89
+
90
+ /**
91
+ * Read all props with defaults applied from schema.
92
+ * @param {object} props — widget props object (may be null)
93
+ * @param {object} schema — widget schema
94
+ * @returns {object}
95
+ */
96
+ export function readAllProps(props, schema) {
97
+ const result = {}
98
+ for (const key of Object.keys(schema)) {
99
+ result[key] = readProp(props, key, schema)
100
+ }
101
+ return result
102
+ }
103
+
104
+ /**
105
+ * Get default props for a widget type from its schema.
106
+ * Used when creating new widgets.
107
+ * @param {object} schema — widget schema
108
+ * @returns {object}
109
+ */
110
+ export function getDefaults(schema) {
111
+ const result = {}
112
+ for (const [key, def] of Object.entries(schema)) {
113
+ if (def.defaultValue !== undefined) {
114
+ result[key] = def.defaultValue
115
+ }
116
+ }
117
+ return result
118
+ }
119
+
120
+ // ── Config-driven schemas ───────────────────────────────────────────
121
+
122
+ /** Schema registry — maps widget type strings to their schemas. */
123
+ export const schemas = configSchemas
124
+
125
+ // Named exports for backward compatibility with widget imports
126
+ export const stickyNoteSchema = schemas['sticky-note']
127
+ export const markdownSchema = schemas['markdown']
128
+ export const prototypeEmbedSchema = schemas['prototype']
129
+ export const linkPreviewSchema = schemas['link-preview']
130
+ export const imageSchema = schemas['image']
131
+ export const figmaEmbedSchema = schemas['figma-embed']
132
+ export const terminalSchema = schemas['terminal']
133
+ export const promptSchema = schemas['prompt']
@@ -0,0 +1,13 @@
1
+ import { createContext } from 'react'
2
+
3
+ /**
4
+ * Provides the form context from <StoryboardForm> to child inputs.
5
+ *
6
+ * Value shape:
7
+ * {
8
+ * prefix: string, // data path prefix (e.g. "checkout")
9
+ * getDraft: (name) => any, // read local draft value for a field
10
+ * setDraft: (name, value) => void, // write local draft value
11
+ * }
12
+ */
13
+ export const FormContext = createContext(null)
@@ -0,0 +1,48 @@
1
+ import { createElement } from 'react'
2
+ import { render, screen } from '@testing-library/react'
3
+ import { FormContext } from './FormContext.js'
4
+
5
+ describe('FormContext', () => {
6
+ it('is a React context object', () => {
7
+ expect(FormContext).toBeDefined()
8
+ expect(FormContext.Provider).toBeDefined()
9
+ expect(FormContext.Consumer).toBeDefined()
10
+ })
11
+
12
+ it('has a default value of null', () => {
13
+ function Reader() {
14
+ return createElement(FormContext.Consumer, null, (value) =>
15
+ createElement('span', { 'data-testid': 'val' }, String(value)),
16
+ )
17
+ }
18
+ render(createElement(Reader))
19
+ expect(screen.getByTestId('val')).toHaveTextContent('null')
20
+ })
21
+
22
+ it('can provide and consume a value with prefix, getDraft, setDraft', () => {
23
+ const getDraft = vi.fn((name) => `draft-${name}`)
24
+ const setDraft = vi.fn()
25
+ const contextValue = { prefix: 'checkout', getDraft, setDraft }
26
+
27
+ function Reader() {
28
+ return createElement(FormContext.Consumer, null, (value) =>
29
+ createElement(
30
+ 'span',
31
+ { 'data-testid': 'val' },
32
+ `${value.prefix}:${value.getDraft('email')}`,
33
+ ),
34
+ )
35
+ }
36
+
37
+ render(
38
+ createElement(
39
+ FormContext.Provider,
40
+ { value: contextValue },
41
+ createElement(Reader),
42
+ ),
43
+ )
44
+
45
+ expect(screen.getByTestId('val')).toHaveTextContent('checkout:draft-email')
46
+ expect(getDraft).toHaveBeenCalledWith('email')
47
+ })
48
+ })
@@ -0,0 +1,481 @@
1
+ import { useState, useEffect, useMemo, useRef, Suspense, lazy } from 'react'
2
+ import { useParams, useLocation } from 'react-router-dom'
3
+ // Named import seeds the core data index via init() AND provides canvas/story route data
4
+ import { canvases, stories } from 'virtual:storyboard-data-index'
5
+ import { loadFlow, flowExists, findRecord, deepMerge, setFlowClass, installBodyClassSync, resolveFlowName, resolveRecordName, isModesEnabled, getPrototypeMetadata } from '../core/index.js'
6
+ import { StoryboardContext } from './StoryboardContext.js'
7
+ import usePrototypeReloadGuard from './hooks/usePrototypeReloadGuard.js'
8
+ import styles from './FlowError.module.css'
9
+
10
+ export { StoryboardContext }
11
+
12
+ const CanvasPageLazy = lazy(() => import('./canvas/CanvasPage.jsx'))
13
+ const StoryPageLazy = lazy(() => import('./story/StoryPage.jsx'))
14
+ const CommandPaletteLazy = lazy(() => import('./CommandPalette/CommandPalette.jsx'))
15
+
16
+ // Build a map from canvas route paths → canvas names at module load time
17
+ const canvasRouteMap = new Map()
18
+ // Build a map from group name → array of { name, route, title } for page selector
19
+ const canvasGroupMap = new Map()
20
+ for (const [name, data] of Object.entries(canvases || {})) {
21
+ const route = (data?._route || `/canvas/${name}`).replace(/\/+$/, '')
22
+ canvasRouteMap.set(route, name)
23
+ const group = data?._group
24
+ if (group) {
25
+ if (!canvasGroupMap.has(group)) canvasGroupMap.set(group, [])
26
+ canvasGroupMap.get(group).push({
27
+ name,
28
+ route,
29
+ title: data?.title || name.split('/').pop(),
30
+ _canvasMeta: data?._canvasMeta || null,
31
+ })
32
+ }
33
+ }
34
+ // Sort each group's pages by pageOrder from .meta.json (if available)
35
+ for (const [, pages] of canvasGroupMap) {
36
+ const pageOrder = pages[0]?._canvasMeta?.pageOrder
37
+ if (Array.isArray(pageOrder)) {
38
+ const orderMap = new Map()
39
+ pageOrder.forEach((entry, idx) => {
40
+ if (typeof entry === 'string' && !entry.startsWith('sep-')) orderMap.set(entry, idx)
41
+ })
42
+ pages.sort((a, b) => {
43
+ const ai = orderMap.has(a.name) ? orderMap.get(a.name) : Infinity
44
+ const bi = orderMap.has(b.name) ? orderMap.get(b.name) : Infinity
45
+ return ai - bi
46
+ })
47
+ }
48
+ }
49
+
50
+ function matchCanvasRoute(pathname) {
51
+ const normalized = stripBasePath(pathname)
52
+ return canvasRouteMap.get(normalized) || null
53
+ }
54
+
55
+ /**
56
+ * Live-lookup a story route against the current `stories` object.
57
+ *
58
+ * Unlike the canvas route map (built once at module scope), this iterates
59
+ * the `stories` object on every call so it always reflects HMR mutations
60
+ * (the virtual-module HMR handler mutates `stories` in place).
61
+ *
62
+ * Also strips encoded query strings (%3F / %3f) that can leak into the
63
+ * pathname when an iframe src is percent-encoded incorrectly.
64
+ */
65
+ function matchStoryRoute(pathname) {
66
+ let normalized = stripBasePath(pathname)
67
+ // Strip encoded query strings that leaked into the path (%3F / %3f = ?)
68
+ const encodedIdx = normalized.search(/%3f/i)
69
+ if (encodedIdx !== -1) normalized = normalized.substring(0, encodedIdx)
70
+ const literalIdx = normalized.indexOf('?')
71
+ if (literalIdx !== -1) normalized = normalized.substring(0, literalIdx)
72
+
73
+ for (const [name, data] of Object.entries(stories || {})) {
74
+ if (data?._route && data._route.replace(/\/+$/, '') === normalized) {
75
+ return name
76
+ }
77
+ }
78
+ return null
79
+ }
80
+
81
+ /**
82
+ * Strip the app's sub-path prefix (e.g. /storyboard) from the pathname.
83
+ * React Router's basename strips the branch prefix but not the app name prefix
84
+ * when the app runs under a nested base path.
85
+ */
86
+ function stripBasePath(pathname) {
87
+ let p = pathname.replace(/\/+$/, '') || '/'
88
+ // BASE_URL includes branch prefix + app path (e.g. /branch--name/storyboard/)
89
+ // React Router strips the branch prefix but may leave the app sub-path
90
+ const base = (import.meta.env?.BASE_URL || '/').replace(/\/+$/, '')
91
+ if (base && base !== '/') {
92
+ // Extract just the last segment(s) after the branch prefix
93
+ const withoutBranch = base.replace(/^\/branch--[^/]+/, '')
94
+ const subPath = withoutBranch.replace(/\/+$/, '')
95
+ if (subPath && p.startsWith(subPath)) {
96
+ p = p.slice(subPath.length) || '/'
97
+ }
98
+ }
99
+ return p
100
+ }
101
+
102
+ function isCanvasPath(pathname) {
103
+ const normalized = stripBasePath(pathname)
104
+ return normalized === '/canvas' || normalized.startsWith('/canvas/')
105
+ }
106
+
107
+ function isStoryPath(pathname) {
108
+ const normalized = stripBasePath(pathname)
109
+ return normalized === '/components' || normalized.startsWith('/components/')
110
+ }
111
+
112
+ /**
113
+ * Derives the top-level prototype name from a pathname.
114
+ * "/Dashboard" → "Dashboard", "/Dashboard/sub" → "Dashboard"
115
+ * "/posts/123" → "posts", "/" → null
116
+ */
117
+ function getPrototypeName(pathname) {
118
+ const path = pathname.replace(/\/+$/, '') || '/'
119
+ if (path === '/') return null
120
+ const segments = path.split('/').filter(Boolean)
121
+ return segments[0] || null
122
+ }
123
+
124
+ /**
125
+ * Derives a flow name from a pathname.
126
+ * "/Overview" → "Overview", "/" → "index", "/nested/Page" → "Page"
127
+ */
128
+ function getPageFlowName(pathname) {
129
+ const path = pathname.replace(/\/+$/, '') || '/'
130
+ if (path === '/') return 'index'
131
+ const last = path.split('/').pop()
132
+ return last || 'index'
133
+ }
134
+
135
+ /**
136
+ * Provides loaded flow data to the component tree.
137
+ * Reads the flow name from the ?flow= URL param (with ?scene= as alias),
138
+ * a matching flow file for the current page, or defaults to "default".
139
+ *
140
+ * Derives the prototype scope from the route and uses it to resolve
141
+ * scoped flow and record names (e.g. "Dashboard/default" for /Dashboard).
142
+ *
143
+ * Optionally merges record data when `recordName` and `recordParam` are provided.
144
+ * The matched record entry is injected under the "record" key in flow data.
145
+ */
146
+ export default function StoryboardProvider({ flowName, sceneName, recordName, recordParam, children }) {
147
+ const basePath = import.meta.env?.BASE_URL || '/'
148
+
149
+ // Suppress HMR full-reloads when prototype-auto-reload flag is off
150
+ usePrototypeReloadGuard()
151
+
152
+ return (
153
+ <>
154
+ <StoryboardProviderInner
155
+ flowName={flowName}
156
+ sceneName={sceneName}
157
+ recordName={recordName}
158
+ recordParam={recordParam}
159
+ >
160
+ {children}
161
+ </StoryboardProviderInner>
162
+ <Suspense fallback={null}>
163
+ <CommandPaletteLazy basePath={basePath} />
164
+ </Suspense>
165
+ </>
166
+ )
167
+ }
168
+
169
+ function StoryboardProviderInner({ flowName, sceneName, recordName, recordParam, children }) {
170
+ const location = useLocation()
171
+ const params = useParams()
172
+
173
+ // Re-evaluate story route detection when the data index changes via HMR.
174
+ // The virtual-module HMR handler mutates `stories` in place and dispatches
175
+ // this event; bumping the key forces useMemo deps to re-fire.
176
+ const [storyIndexKey, setStoryIndexKey] = useState(0)
177
+ useEffect(() => {
178
+ const handler = () => setStoryIndexKey((k) => k + 1)
179
+ document.addEventListener('storyboard:story-index-changed', handler)
180
+ return () => document.removeEventListener('storyboard:story-index-changed', handler)
181
+ }, [])
182
+
183
+ // Story route detection — matches current URL against registered story routes
184
+ // storyIndexKey forces re-evaluation when HMR mutates the stories object in place
185
+ // eslint-disable-next-line react-hooks/exhaustive-deps
186
+ const storyName = useMemo(() => matchStoryRoute(location.pathname), [location.pathname, storyIndexKey])
187
+ const isMissingStoryRoute = useMemo(
188
+ () => isStoryPath(location.pathname) && !storyName,
189
+ [location.pathname, storyName],
190
+ )
191
+
192
+ // Canvas route detection — matches current URL against registered canvas routes
193
+ const canvasId = useMemo(() => matchCanvasRoute(location.pathname), [location.pathname])
194
+ const isMissingCanvasRoute = useMemo(
195
+ () => isCanvasPath(location.pathname) && !canvasId && !storyName,
196
+ [location.pathname, canvasId, storyName],
197
+ )
198
+
199
+ const searchParams = new URLSearchParams(location.search)
200
+ const sceneParam = searchParams.get('flow') || searchParams.get('scene')
201
+ const prototypeName = getPrototypeName(location.pathname)
202
+ const pageFlow = getPageFlowName(location.pathname)
203
+
204
+ // Resolve flow name with prototype scoping (skip for canvas/story pages)
205
+ const activeFlowName = useMemo(() => {
206
+ if (canvasId || isMissingCanvasRoute || storyName || isMissingStoryRoute) return null
207
+ const requested = sceneParam || flowName || sceneName
208
+ if (requested) {
209
+ // Allow fully-scoped flow names from URLs/widgets without re-prefixing
210
+ // (e.g. "Proto/flow" should not become "Proto/Proto/flow").
211
+ if (requested.includes('/')) return requested
212
+ return resolveFlowName(prototypeName, requested)
213
+ }
214
+ // 1. Page-specific flow (e.g., Example/Forms)
215
+ const scopedPageFlow = resolveFlowName(prototypeName, pageFlow)
216
+ if (flowExists(scopedPageFlow)) return scopedPageFlow
217
+ // 2. Prototype flow — named after the prototype folder (e.g., Example/example)
218
+ if (prototypeName) {
219
+ const protoFlow = resolveFlowName(prototypeName, prototypeName)
220
+ if (flowExists(protoFlow)) return protoFlow
221
+ }
222
+ // 3. Prototype-scoped default (e.g. Example/default)
223
+ if (prototypeName) {
224
+ const scopedDefault = resolveFlowName(prototypeName, 'default')
225
+ if (flowExists(scopedDefault)) return scopedDefault
226
+ }
227
+ // 4. Global default — or null if no flow exists at all
228
+ if (flowExists('default')) return 'default'
229
+ return null
230
+ }, [canvasId, isMissingCanvasRoute, storyName, isMissingStoryRoute, sceneParam, flowName, sceneName, prototypeName, pageFlow])
231
+
232
+ // Auto-install body class sync (sb-key--value classes on <body>)
233
+ useEffect(() => installBodyClassSync(), [])
234
+
235
+ // Update document.title to reflect the current artifact
236
+ useEffect(() => {
237
+ const base = import.meta.env?.BASE_URL || '/'
238
+ const branchMatch = base.match(/\/branch--([^/]+)/)
239
+ const branchSuffix = branchMatch ? ` (${branchMatch[1]})` : ''
240
+
241
+ let title
242
+ if (canvasId) {
243
+ const canvasData = canvases?.[canvasId]
244
+ const meta = canvasData?._canvasMeta
245
+ const pageTitle = canvasData?.title || canvasId.split('/').pop()
246
+ title = (meta?.title || pageTitle) + ' · Storyboard'
247
+ } else if (prototypeName) {
248
+ title = prototypeName + ' · Storyboard'
249
+ } else {
250
+ title = 'Storyboard'
251
+ }
252
+
253
+ document.title = title + branchSuffix
254
+ }, [canvasId, prototypeName])
255
+
256
+ // Mount design modes UI when enabled in storyboard.config.json
257
+ useEffect(() => {
258
+ if (!isModesEnabled()) return
259
+
260
+ let cleanup
261
+ import('@dfosco/storyboard/ui-runtime')
262
+ .then(({ mountDesignModes }) => {
263
+ cleanup = mountDesignModes()
264
+ })
265
+ .catch(() => {
266
+ // UI not available — degrade gracefully
267
+ })
268
+
269
+ return () => cleanup?.()
270
+ }, [])
271
+
272
+ // Skip flow loading for canvas/story pages and flow-less pages
273
+ const { data, error, flowTokens } = useMemo(() => {
274
+ if (canvasId || isMissingCanvasRoute || storyName || isMissingStoryRoute) return { data: null, error: null, flowTokens: null }
275
+ if (!activeFlowName) return { data: {}, error: null, flowTokens: null }
276
+ try {
277
+ let flowData = loadFlow(activeFlowName)
278
+
279
+ // Extract tokens before passing data to consumers (reserved metadata key)
280
+ const extractedTokens = flowData?.tokens || null
281
+ if (flowData?.tokens) {
282
+ flowData = { ...flowData }
283
+ delete flowData.tokens
284
+ }
285
+
286
+ // Merge record data if configured (with scoped resolution)
287
+ if (recordName && recordParam && params[recordParam]) {
288
+ const resolvedRecord = resolveRecordName(prototypeName, recordName)
289
+ const entry = findRecord(resolvedRecord, params[recordParam])
290
+ if (entry) {
291
+ flowData = deepMerge(flowData, { record: entry })
292
+ }
293
+ }
294
+
295
+ setFlowClass(activeFlowName)
296
+ return { data: flowData, error: null, flowTokens: extractedTokens }
297
+ } catch (err) {
298
+ return { data: null, error: err.message, flowTokens: null }
299
+ }
300
+ }, [canvasId, isMissingCanvasRoute, storyName, isMissingStoryRoute, activeFlowName, recordName, recordParam, params, prototypeName])
301
+
302
+ // Resolve prototype-level tokens from .prototype.json metadata
303
+ const protoTokens = useMemo(() => {
304
+ if (!prototypeName) return null
305
+ const meta = getPrototypeMetadata(prototypeName)
306
+ return meta?.tokens || null
307
+ }, [prototypeName])
308
+
309
+ // Merge prototype + flow tokens (flow wins). Stable reference when tokens don't change.
310
+ const mergedTokens = useMemo(() => {
311
+ if (!protoTokens && !flowTokens) return null
312
+ return { ...(protoTokens || {}), ...(flowTokens || {}) }
313
+ }, [protoTokens, flowTokens])
314
+
315
+ // Track which URL params were set by tokens (vs. user-explicit params)
316
+ const managedParamsRef = useRef({})
317
+
318
+ // Apply merged tokens to URL search params via replaceState.
319
+ // Only sets params not already present (user-explicit wins on first load).
320
+ // Cleans up stale managed params when flow/prototype tokens change.
321
+ useEffect(() => {
322
+ const url = new URL(window.location.href)
323
+ const managed = managedParamsRef.current
324
+ const nextManaged = {}
325
+ let changed = false
326
+
327
+ // Remove stale managed params no longer in merged tokens
328
+ for (const key of Object.keys(managed)) {
329
+ if (!mergedTokens || !(key in mergedTokens)) {
330
+ url.searchParams.delete(key)
331
+ changed = true
332
+ }
333
+ }
334
+
335
+ // Apply current tokens
336
+ if (mergedTokens) {
337
+ const reserved = new Set(['flow', 'scene'])
338
+ for (const [key, value] of Object.entries(mergedTokens)) {
339
+ if (value == null || typeof value === 'object' || reserved.has(key)) continue
340
+ const strValue = String(value)
341
+ if (!url.searchParams.has(key) || (key in managed && managed[key] !== strValue)) {
342
+ url.searchParams.set(key, strValue)
343
+ nextManaged[key] = strValue
344
+ changed = true
345
+ } else if (key in managed) {
346
+ nextManaged[key] = strValue
347
+ }
348
+ }
349
+ }
350
+
351
+ managedParamsRef.current = nextManaged
352
+
353
+ if (changed) {
354
+ window.history.replaceState(window.history.state, '', url.toString())
355
+ }
356
+ }, [mergedTokens])
357
+
358
+ // Canvas pages get their own rendering path — no flow data needed
359
+ if (canvasId) {
360
+ const canvasData = canvases?.[canvasId]
361
+ const group = canvasData?._group
362
+ // Include the current canvas as a sibling even if it's the only page in its group,
363
+ // so the PageSelector can render and allow adding new pages.
364
+ const siblingPages = group
365
+ ? canvasGroupMap.get(group) || []
366
+ : [{ name: canvasId, route: canvasData?._route || `/canvas/${canvasId}`, title: canvasData?.title || canvasId.split('/').pop() }]
367
+ const canvasMeta = canvasData?._canvasMeta || null
368
+ const canvasValue = {
369
+ data: null,
370
+ error: null,
371
+ loading: false,
372
+ flowName: null,
373
+ sceneName: null,
374
+ prototypeName: null,
375
+ }
376
+ return (
377
+ <StoryboardContext.Provider value={canvasValue}>
378
+ <Suspense fallback={null}>
379
+ <CanvasPageLazy canvasId={canvasId} siblingPages={siblingPages} canvasMeta={canvasMeta} />
380
+ </Suspense>
381
+ </StoryboardContext.Provider>
382
+ )
383
+ }
384
+
385
+ // Story pages get their own rendering path — no flow data needed
386
+ if (storyName) {
387
+ const storyValue = {
388
+ data: null,
389
+ error: null,
390
+ loading: false,
391
+ flowName: null,
392
+ sceneName: null,
393
+ prototypeName: null,
394
+ }
395
+ return (
396
+ <StoryboardContext.Provider value={storyValue}>
397
+ <Suspense fallback={null}>
398
+ <StoryPageLazy name={storyName} />
399
+ </Suspense>
400
+ </StoryboardContext.Provider>
401
+ )
402
+ }
403
+
404
+ if (isMissingCanvasRoute) {
405
+ const currentUrl = `${location.pathname}${location.search}`
406
+ const truncatedUrl = currentUrl.length > 60
407
+ ? currentUrl.slice(0, 60) + '…'
408
+ : currentUrl
409
+
410
+ return (
411
+ <main className={styles.container}>
412
+ <div className={styles.banner}>
413
+ <strong>Canvas not found</strong>
414
+ No canvas matches this route.
415
+ </div>
416
+ <p className={styles.meta}>
417
+ Tried to open{' '}
418
+ <a href={currentUrl} title={currentUrl}>{truncatedUrl}</a>
419
+ </p>
420
+ <a className={styles.homeLink} href="/">← Go to index page</a>
421
+ </main>
422
+ )
423
+ }
424
+
425
+ if (isMissingStoryRoute) {
426
+ const currentUrl = `${location.pathname}${location.search}`
427
+ const truncatedUrl = currentUrl.length > 60
428
+ ? currentUrl.slice(0, 60) + '…'
429
+ : currentUrl
430
+
431
+ return (
432
+ <main className={styles.container}>
433
+ <div className={styles.banner}>
434
+ <strong>Story not found</strong>
435
+ No story matches this route.
436
+ </div>
437
+ <p className={styles.meta}>
438
+ Tried to open{' '}
439
+ <a href={currentUrl} title={currentUrl}>{truncatedUrl}</a>
440
+ </p>
441
+ <a className={styles.homeLink} href="/">← Go to index page</a>
442
+ </main>
443
+ )
444
+ }
445
+
446
+ const value = {
447
+ data,
448
+ error,
449
+ loading: false,
450
+ flowName: activeFlowName,
451
+ sceneName: activeFlowName, // backward compat
452
+ prototypeName,
453
+ }
454
+
455
+ if (error) {
456
+ const currentUrl = `${location.pathname}${location.search}`
457
+ const truncatedUrl = currentUrl.length > 60
458
+ ? currentUrl.slice(0, 60) + '…'
459
+ : currentUrl
460
+
461
+ return (
462
+ <div className={styles.container}>
463
+ <div className={styles.banner}>
464
+ <strong>Error loading flow</strong>
465
+ {error}
466
+ </div>
467
+ <p className={styles.meta}>
468
+ Tried to load{' '}
469
+ <a href={currentUrl} title={currentUrl}>{truncatedUrl}</a>
470
+ </p>
471
+ <a className={styles.homeLink} href="/">← Go to homepage</a>
472
+ </div>
473
+ )
474
+ }
475
+
476
+ return (
477
+ <StoryboardContext.Provider value={value}>
478
+ {children}
479
+ </StoryboardContext.Provider>
480
+ )
481
+ }