@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,158 @@
1
+ import { useContext, useMemo, useSyncExternalStore } from 'react'
2
+ import { useParams } from 'react-router-dom'
3
+ import { loadRecord, resolveRecordName } from '../../core/index.js'
4
+ import { deepClone, setByPath } from '../../core/index.js'
5
+ import { getAllParams } from '../../core/index.js'
6
+ import { isHideMode, getAllShadows } from '../../core/index.js'
7
+ import { subscribeToHash, getHashSnapshot } from '../../core/index.js'
8
+ import { subscribeToStorage, getStorageSnapshot } from '../../core/index.js'
9
+ import { StoryboardContext } from '../StoryboardContext.js'
10
+
11
+ /**
12
+ * Collect overrides for a record and merge them into the base array.
13
+ *
14
+ * In normal mode reads from URL hash params; in hide mode reads from
15
+ * localStorage shadow snapshots.
16
+ *
17
+ * Hash convention: record.{recordName}.{entryId}.{field}=value
18
+ *
19
+ * - Existing entries (matched by id) get fields merged on top.
20
+ * - Unknown ids create new entries appended to the array.
21
+ *
22
+ * @param {Array} baseRecords - The original record array (will be deep-cloned)
23
+ * @param {string} resolvedName - Resolved (possibly scoped) record name (e.g. "security/rules")
24
+ * @param {string} [plainName] - Original unscoped record name (e.g. "rules"). Falls back to resolvedName.
25
+ * @returns {Array} Merged array
26
+ */
27
+ function applyRecordOverrides(baseRecords, resolvedName, plainName) {
28
+ const allParams = isHideMode() ? getAllShadows() : getAllParams()
29
+
30
+ // Check both the resolved (scoped) prefix and the plain (unscoped) prefix.
31
+ // Callers write overrides with the plain name, but the data index resolves
32
+ // to the scoped name — we need to match both so overrides are not silently
33
+ // dropped for prototype-scoped records.
34
+ const resolvedPrefix = `record.${resolvedName}.`
35
+ const plainPrefix = plainName && plainName !== resolvedName
36
+ ? `record.${plainName}.`
37
+ : null
38
+
39
+ // Collect only the params that target this record
40
+ const overrideKeys = Object.keys(allParams).filter(k =>
41
+ k.startsWith(resolvedPrefix) || (plainPrefix && k.startsWith(plainPrefix))
42
+ )
43
+ if (overrideKeys.length === 0) return baseRecords
44
+
45
+ const records = deepClone(baseRecords)
46
+
47
+ // Group overrides by entry id
48
+ // key format: record.{name}.{entryId}.{field...}
49
+ const byEntryId = {}
50
+ for (const key of overrideKeys) {
51
+ // Determine which prefix matched to slice correctly
52
+ const prefix = key.startsWith(resolvedPrefix) ? resolvedPrefix : plainPrefix
53
+ const rest = key.slice(prefix.length) // "{entryId}.{field...}"
54
+ const dotIdx = rest.indexOf('.')
55
+ if (dotIdx === -1) continue // no field path — skip
56
+ const entryId = rest.slice(0, dotIdx)
57
+ const fieldPath = rest.slice(dotIdx + 1)
58
+ if (!byEntryId[entryId]) byEntryId[entryId] = {}
59
+ byEntryId[entryId][fieldPath] = allParams[key]
60
+ }
61
+
62
+ for (const [entryId, fields] of Object.entries(byEntryId)) {
63
+ const existing = records.find(e => e.id === entryId)
64
+ if (existing) {
65
+ // Merge fields into existing entry
66
+ for (const [fieldPath, value] of Object.entries(fields)) {
67
+ setByPath(existing, fieldPath, value)
68
+ }
69
+ } else {
70
+ // Create new entry and append
71
+ const newEntry = { id: entryId }
72
+ for (const [fieldPath, value] of Object.entries(fields)) {
73
+ setByPath(newEntry, fieldPath, value)
74
+ }
75
+ records.push(newEntry)
76
+ }
77
+ }
78
+
79
+ return records
80
+ }
81
+
82
+ /**
83
+ * Loads a single record entry from a record collection, matched by URL param.
84
+ * Hash overrides are applied before lookup — both field overrides on existing
85
+ * entries and entirely new entries added via the URL are supported.
86
+ *
87
+ * The `paramName` serves double duty: it's both the route param to read from
88
+ * the URL and the record field to match against. This maps naturally to the
89
+ * file-based routing convention — `[id].jsx` matches entry.id,
90
+ * `[permalink].jsx` would match entry.permalink, etc.
91
+ *
92
+ * @param {string} recordName - Name of the record file (e.g., "posts")
93
+ * @param {string} paramName - Route param name, also used as the entry field to match
94
+ * @returns {object|null} The matched record entry, or null if not found
95
+ *
96
+ * @example
97
+ * // In pages/issues/[id].jsx:
98
+ * const issue = useRecord('issues', 'id')
99
+ * // URL /issues/refactor-auth-sso → finds entry where entry.id === 'refactor-auth-sso'
100
+ *
101
+ * // In pages/posts/[permalink].jsx:
102
+ * const post = useRecord('posts', 'permalink')
103
+ * // URL /posts/hello-world → finds entry where entry.permalink === 'hello-world'
104
+ */
105
+ export function useRecord(recordName, paramName = 'id') {
106
+ const params = useParams()
107
+ const paramValue = params[paramName]
108
+ const context = useContext(StoryboardContext)
109
+ const prototypeName = context?.prototypeName ?? null
110
+
111
+ // Re-render on hash or localStorage changes so overrides are reactive
112
+ const hashString = useSyncExternalStore(subscribeToHash, getHashSnapshot)
113
+ const storageString = useSyncExternalStore(subscribeToStorage, getStorageSnapshot)
114
+
115
+ return useMemo(() => {
116
+ if (!paramValue) return null
117
+ try {
118
+ const resolvedName = resolveRecordName(prototypeName, recordName)
119
+ const base = loadRecord(resolvedName)
120
+ const merged = applyRecordOverrides(base, resolvedName, recordName)
121
+ return merged.find(e => e[paramName] === paramValue) ?? null
122
+ } catch (err) {
123
+ console.error(`[useRecord] ${err.message}`)
124
+ return null
125
+ }
126
+ }, [recordName, paramName, paramValue, prototypeName, hashString, storageString]) // eslint-disable-line react-hooks/exhaustive-deps
127
+ }
128
+
129
+ /**
130
+ * Loads all entries from a record collection.
131
+ * Hash overrides are applied — existing entries can be modified and
132
+ * new entries can be created entirely from URL hash params.
133
+ *
134
+ * @param {string} recordName - Name of the record file (e.g., "posts")
135
+ * @returns {Array} All record entries (with overrides applied)
136
+ *
137
+ * @example
138
+ * const allPosts = useRecords('posts')
139
+ */
140
+ export function useRecords(recordName) {
141
+ const context = useContext(StoryboardContext)
142
+ const prototypeName = context?.prototypeName ?? null
143
+
144
+ // Re-render on hash or localStorage changes so overrides are reactive
145
+ const hashString = useSyncExternalStore(subscribeToHash, getHashSnapshot)
146
+ const storageString = useSyncExternalStore(subscribeToStorage, getStorageSnapshot)
147
+
148
+ return useMemo(() => {
149
+ try {
150
+ const resolvedName = resolveRecordName(prototypeName, recordName)
151
+ const base = loadRecord(resolvedName)
152
+ return applyRecordOverrides(base, resolvedName, recordName)
153
+ } catch (err) {
154
+ console.error(`[useRecords] ${err.message}`)
155
+ return []
156
+ }
157
+ }, [recordName, prototypeName, hashString, storageString]) // eslint-disable-line react-hooks/exhaustive-deps
158
+ }
@@ -0,0 +1,221 @@
1
+ import React from 'react'
2
+ import { renderHook, act } from '@testing-library/react'
3
+ import { seedTestData, TEST_RECORDS } from '../../test-utils.js'
4
+ import { activateHideMode, setShadow, init } from '../../core/index.js'
5
+ import { StoryboardContext } from '../StoryboardContext.js'
6
+
7
+ vi.mock('react-router-dom', async () => {
8
+ const actual = await vi.importActual('react-router-dom')
9
+ return { ...actual, useParams: vi.fn(() => ({})) }
10
+ })
11
+ import { useParams } from 'react-router-dom'
12
+
13
+ import { useRecord, useRecords } from './useRecord.js'
14
+
15
+ beforeEach(() => {
16
+ seedTestData()
17
+ useParams.mockReturnValue({})
18
+ })
19
+
20
+ /**
21
+ * Create a wrapper that provides StoryboardContext with a prototypeName,
22
+ * used for testing scoped (prototype-level) records.
23
+ */
24
+ function createPrototypeWrapper(prototypeName) {
25
+ return function Wrapper({ children }) {
26
+ return React.createElement(
27
+ StoryboardContext.Provider,
28
+ { value: { data: {}, prototypeName } },
29
+ children,
30
+ )
31
+ }
32
+ }
33
+
34
+ // ── useRecord ──
35
+
36
+ describe('useRecord', () => {
37
+ it('returns null when no URL param matches', () => {
38
+ const { result } = renderHook(() => useRecord('posts'))
39
+ expect(result.current).toBeNull()
40
+ })
41
+
42
+ it('returns matching record entry when param is set', () => {
43
+ useParams.mockReturnValue({ id: 'post-1' })
44
+ const { result } = renderHook(() => useRecord('posts'))
45
+ expect(result.current).toEqual(TEST_RECORDS.posts[0])
46
+ })
47
+
48
+ it('returns null when param value does not match any entry', () => {
49
+ useParams.mockReturnValue({ id: 'nonexistent' })
50
+ const { result } = renderHook(() => useRecord('posts'))
51
+ expect(result.current).toBeNull()
52
+ })
53
+
54
+ it('defaults paramName to id', () => {
55
+ useParams.mockReturnValue({ id: 'post-2' })
56
+ const { result } = renderHook(() => useRecord('posts'))
57
+ expect(result.current).toEqual(TEST_RECORDS.posts[1])
58
+ })
59
+
60
+ it('returns null gracefully when record collection does not exist', () => {
61
+ useParams.mockReturnValue({ id: 'post-1' })
62
+ vi.spyOn(console, 'error').mockImplementation(() => {})
63
+ const { result } = renderHook(() => useRecord('nonexistent'))
64
+ expect(result.current).toBeNull()
65
+ console.error.mockRestore()
66
+ })
67
+ })
68
+
69
+ // ── useRecords ──
70
+
71
+ describe('useRecords', () => {
72
+ it('returns all entries from a record collection', () => {
73
+ const { result } = renderHook(() => useRecords('posts'))
74
+ expect(result.current).toEqual(TEST_RECORDS.posts)
75
+ })
76
+
77
+ it('returns empty array when record does not exist', () => {
78
+ vi.spyOn(console, 'error').mockImplementation(() => {})
79
+ const { result } = renderHook(() => useRecords('nonexistent'))
80
+ expect(result.current).toEqual([])
81
+ console.error.mockRestore()
82
+ })
83
+
84
+ it('applies hash overrides to existing entries', () => {
85
+ window.location.hash = 'record.posts.post-1.title=Updated'
86
+ const { result } = renderHook(() => useRecords('posts'))
87
+ const post1 = result.current.find(e => e.id === 'post-1')
88
+ expect(post1.title).toBe('Updated')
89
+ })
90
+
91
+ it('creates new entries from hash overrides', () => {
92
+ window.location.hash = 'record.posts.new-post.title=New'
93
+ const { result } = renderHook(() => useRecords('posts'))
94
+ const newPost = result.current.find(e => e.id === 'new-post')
95
+ expect(newPost).toBeTruthy()
96
+ expect(newPost.title).toBe('New')
97
+ })
98
+ })
99
+
100
+ // ── Hide mode ──
101
+
102
+ describe('useRecord (hide mode)', () => {
103
+ beforeEach(() => {
104
+ act(() => { activateHideMode() })
105
+ })
106
+
107
+ it('reads overrides from localStorage shadow in hide mode', () => {
108
+ useParams.mockReturnValue({ id: 'post-1' })
109
+ act(() => { setShadow('record.posts.post-1.title', 'Shadow Title') })
110
+
111
+ const { result } = renderHook(() => useRecord('posts'))
112
+ expect(result.current.title).toBe('Shadow Title')
113
+ })
114
+
115
+ it('reactively updates when shadow changes in hide mode', () => {
116
+ useParams.mockReturnValue({ id: 'post-1' })
117
+ const { result } = renderHook(() => useRecord('posts'))
118
+ expect(result.current.title).toBe('First Post')
119
+
120
+ act(() => { setShadow('record.posts.post-1.title', 'Updated via Shadow') })
121
+ expect(result.current.title).toBe('Updated via Shadow')
122
+ })
123
+ })
124
+
125
+ describe('useRecords (hide mode)', () => {
126
+ beforeEach(() => {
127
+ act(() => { activateHideMode() })
128
+ })
129
+
130
+ it('applies shadow overrides to existing entries', () => {
131
+ act(() => { setShadow('record.posts.post-1.title', 'Hidden Update') })
132
+
133
+ const { result } = renderHook(() => useRecords('posts'))
134
+ const post1 = result.current.find(e => e.id === 'post-1')
135
+ expect(post1.title).toBe('Hidden Update')
136
+ })
137
+
138
+ it('creates new entries from shadow overrides', () => {
139
+ act(() => { setShadow('record.posts.shadow-post.title', 'New Shadow') })
140
+
141
+ const { result } = renderHook(() => useRecords('posts'))
142
+ const newPost = result.current.find(e => e.id === 'shadow-post')
143
+ expect(newPost).toBeTruthy()
144
+ expect(newPost.title).toBe('New Shadow')
145
+ })
146
+ })
147
+
148
+ // ── Scoped (prototype) records ──
149
+
150
+ const SCOPED_RECORDS = {
151
+ 'security/rules': [
152
+ { id: 'constant-condition', title: 'Constant Condition', state: 'open' },
153
+ { id: 'unused-var', title: 'Unused Variable', state: 'open' },
154
+ ],
155
+ }
156
+
157
+ function seedScopedData() {
158
+ init({
159
+ flows: {},
160
+ objects: {},
161
+ records: SCOPED_RECORDS,
162
+ })
163
+ }
164
+
165
+ describe('useRecords (scoped records)', () => {
166
+ beforeEach(() => {
167
+ seedScopedData()
168
+ window.location.hash = ''
169
+ })
170
+
171
+ it('applies overrides written with the plain (unscoped) record name', () => {
172
+ // Callers write: record.rules.constant-condition.state=dismissed
173
+ // Reader resolves to "security/rules" — this was the bug
174
+ window.location.hash = 'record.rules.constant-condition.state=dismissed'
175
+
176
+ const wrapper = createPrototypeWrapper('security')
177
+ const { result } = renderHook(() => useRecords('rules'), { wrapper })
178
+
179
+ const rule = result.current.find(e => e.id === 'constant-condition')
180
+ expect(rule.state).toBe('dismissed')
181
+ })
182
+
183
+ it('applies overrides written with the resolved (scoped) record name', () => {
184
+ window.location.hash = 'record.security/rules.constant-condition.state=dismissed'
185
+
186
+ const wrapper = createPrototypeWrapper('security')
187
+ const { result } = renderHook(() => useRecords('rules'), { wrapper })
188
+
189
+ const rule = result.current.find(e => e.id === 'constant-condition')
190
+ expect(rule.state).toBe('dismissed')
191
+ })
192
+
193
+ it('merges overrides from both plain and scoped prefixes', () => {
194
+ window.location.hash =
195
+ 'record.rules.constant-condition.state=dismissed' +
196
+ '&record.security/rules.unused-var.state=resolved'
197
+
198
+ const wrapper = createPrototypeWrapper('security')
199
+ const { result } = renderHook(() => useRecords('rules'), { wrapper })
200
+
201
+ expect(result.current.find(e => e.id === 'constant-condition').state).toBe('dismissed')
202
+ expect(result.current.find(e => e.id === 'unused-var').state).toBe('resolved')
203
+ })
204
+ })
205
+
206
+ describe('useRecord (scoped records)', () => {
207
+ beforeEach(() => {
208
+ seedScopedData()
209
+ window.location.hash = ''
210
+ useParams.mockReturnValue({ id: 'constant-condition' })
211
+ })
212
+
213
+ it('applies overrides written with the plain (unscoped) record name', () => {
214
+ window.location.hash = 'record.rules.constant-condition.state=dismissed'
215
+
216
+ const wrapper = createPrototypeWrapper('security')
217
+ const { result } = renderHook(() => useRecord('rules'), { wrapper })
218
+
219
+ expect(result.current.state).toBe('dismissed')
220
+ })
221
+ })
@@ -0,0 +1,38 @@
1
+ import { useContext, useCallback } from 'react'
2
+ import { StoryboardContext } from '../StoryboardContext.js'
3
+
4
+ /**
5
+ * Read the current flow name and programmatically switch flows.
6
+ *
7
+ * @returns {{ flowName: string, switchFlow: (name: string) => void }}
8
+ * - flowName – current active flow (e.g. "default")
9
+ * - switchFlow – navigate to a different flow by updating ?flow= param
10
+ */
11
+ export function useFlow() {
12
+ const context = useContext(StoryboardContext)
13
+ if (context === null) {
14
+ throw new Error('useFlow must be used within a <StoryboardProvider>')
15
+ }
16
+
17
+ const switchFlow = useCallback((name) => {
18
+ const url = new URL(window.location.href)
19
+ url.searchParams.delete('scene')
20
+ url.searchParams.set('flow', name)
21
+ // Preserve hash params across flow switches
22
+ window.location.href = url.toString()
23
+ }, [])
24
+
25
+ return {
26
+ flowName: context.flowName,
27
+ switchFlow,
28
+ }
29
+ }
30
+
31
+ /** @deprecated Use useFlow() */
32
+ export function useScene() {
33
+ const { flowName, switchFlow } = useFlow()
34
+ return {
35
+ sceneName: flowName,
36
+ switchScene: switchFlow,
37
+ }
38
+ }
@@ -0,0 +1,66 @@
1
+ import { renderHook } from '@testing-library/react'
2
+ import { useFlow } from './useScene.js'
3
+ import { useScene } from './useScene.js'
4
+ import { seedTestData, createWrapper, TEST_FLOWS } from '../test-utils.js'
5
+
6
+ const flowData = TEST_FLOWS.default
7
+
8
+ beforeEach(() => {
9
+ seedTestData()
10
+ })
11
+
12
+ describe('useFlow', () => {
13
+ it('returns { flowName, switchFlow }', () => {
14
+ const { result } = renderHook(() => useFlow(), {
15
+ wrapper: createWrapper(flowData),
16
+ })
17
+ expect(result.current).toHaveProperty('flowName')
18
+ expect(result.current).toHaveProperty('switchFlow')
19
+ })
20
+
21
+ it('flowName matches the value from context', () => {
22
+ const { result } = renderHook(() => useFlow(), {
23
+ wrapper: createWrapper(flowData, 'other'),
24
+ })
25
+ expect(result.current.flowName).toBe('other')
26
+ })
27
+
28
+ it('switchFlow is a function', () => {
29
+ const { result } = renderHook(() => useFlow(), {
30
+ wrapper: createWrapper(flowData),
31
+ })
32
+ expect(typeof result.current.switchFlow).toBe('function')
33
+ })
34
+
35
+ it('throws when used outside StoryboardProvider', () => {
36
+ expect(() => {
37
+ renderHook(() => useFlow())
38
+ }).toThrow('useFlow must be used within a <StoryboardProvider>')
39
+ })
40
+ })
41
+
42
+ // ── useScene (deprecated alias) ──
43
+
44
+ describe('useScene (deprecated alias)', () => {
45
+ it('returns { sceneName, switchScene }', () => {
46
+ const { result } = renderHook(() => useScene(), {
47
+ wrapper: createWrapper(flowData),
48
+ })
49
+ expect(result.current).toHaveProperty('sceneName')
50
+ expect(result.current).toHaveProperty('switchScene')
51
+ })
52
+
53
+ it('sceneName matches the flow name from context', () => {
54
+ const { result } = renderHook(() => useScene(), {
55
+ wrapper: createWrapper(flowData, 'other'),
56
+ })
57
+ expect(result.current.sceneName).toBe('other')
58
+ })
59
+
60
+ it('switchScene is a function', () => {
61
+ const { result } = renderHook(() => useScene(), {
62
+ wrapper: createWrapper(flowData),
63
+ })
64
+ expect(typeof result.current.switchScene).toBe('function')
65
+ })
66
+ })
@@ -0,0 +1,108 @@
1
+ import { useContext, useMemo, useSyncExternalStore } from 'react'
2
+ import { StoryboardContext } from '../StoryboardContext.js'
3
+ import { getByPath, deepClone, setByPath } from '../../core/index.js'
4
+ import { getParam, getAllParams } from '../../core/index.js'
5
+ import { subscribeToHash, getHashSnapshot } from '../../core/index.js'
6
+ import { isHideMode, getShadow, getAllShadows } from '../../core/index.js'
7
+ import { subscribeToStorage, getStorageSnapshot } from '../../core/index.js'
8
+
9
+ /**
10
+ * Access flow data by dot-notation path.
11
+ * Hash params override flow data — both exact matches and nested paths.
12
+ *
13
+ * Examples:
14
+ * useFlowData('user.name') with #user.name=Alice → "Alice"
15
+ * useFlowData('repositories') with #repositories.0.name=Foo
16
+ * → deep clone of repositories array with [0].name overridden to "Foo"
17
+ *
18
+ * @param {string} [path] - Dot-notation path (e.g. 'user.profile.name').
19
+ * Omit to get the entire flow object.
20
+ * @param {{ optional?: boolean }} [opts] - Pass { optional: true } to suppress
21
+ * the "path not found" warning for optional data.
22
+ * @returns {*} The resolved value. Returns {} if path is missing after loading.
23
+ * @throws If used outside a StoryboardProvider.
24
+ */
25
+ export function useFlowData(path, opts) {
26
+ const context = useContext(StoryboardContext)
27
+
28
+ if (context === null) {
29
+ throw new Error('useFlowData must be used within a <StoryboardProvider>')
30
+ }
31
+
32
+ const { data, loading, error } = context
33
+
34
+ // Re-render on any hash or localStorage change
35
+ const hashString = useSyncExternalStore(subscribeToHash, getHashSnapshot)
36
+ const storageString = useSyncExternalStore(subscribeToStorage, getStorageSnapshot)
37
+
38
+ // Collect overrides relevant to this path
39
+ // eslint-disable-next-line react-hooks/preserve-manual-memoization
40
+ const result = useMemo(() => {
41
+ if (loading || error || data == null) return undefined
42
+
43
+ const hidden = isHideMode()
44
+ // In hide mode, read from shadow localStorage; otherwise from URL hash
45
+ const readParam = hidden ? getShadow : getParam
46
+ const readAllParams = hidden ? getAllShadows : getAllParams
47
+
48
+ if (!path) {
49
+ // No path → return full scene data with all overrides applied
50
+ const allParams = readAllParams()
51
+ const keys = Object.keys(allParams)
52
+ if (keys.length === 0) return data
53
+ const merged = deepClone(data)
54
+ for (const key of keys) setByPath(merged, key, allParams[key])
55
+ return merged
56
+ }
57
+
58
+ // Exact match: param directly for this path
59
+ const exact = readParam(path)
60
+ if (exact !== null) return exact
61
+
62
+ // Child overrides: params that are nested under this path
63
+ const prefix = path + '.'
64
+ const allParams = readAllParams()
65
+ const childKeys = Object.keys(allParams).filter(k => k.startsWith(prefix))
66
+
67
+ const sceneValue = getByPath(data, path)
68
+
69
+ if (childKeys.length > 0 && sceneValue !== undefined) {
70
+ const merged = deepClone(sceneValue)
71
+ for (const key of childKeys) {
72
+ const relativePath = key.slice(prefix.length)
73
+ setByPath(merged, relativePath, allParams[key])
74
+ }
75
+ return merged
76
+ }
77
+
78
+ if (sceneValue === undefined) {
79
+ if (!opts?.optional && data != null && Object.keys(data).length > 0) {
80
+ console.warn(`[useFlowData] Path "${path}" not found in flow data.`)
81
+ }
82
+ return {}
83
+ }
84
+
85
+ return sceneValue
86
+ }, [data, loading, error, path, hashString, storageString]) // eslint-disable-line react-hooks/exhaustive-deps
87
+
88
+ return result
89
+ }
90
+
91
+ /** @deprecated Use useFlowData() */
92
+ export const useSceneData = useFlowData
93
+
94
+ /**
95
+ * Returns true while flow data is still loading.
96
+ */
97
+ export function useFlowLoading() {
98
+ const context = useContext(StoryboardContext)
99
+
100
+ if (context === null) {
101
+ throw new Error('useFlowLoading must be used within a <StoryboardProvider>')
102
+ }
103
+
104
+ return context.loading
105
+ }
106
+
107
+ /** @deprecated Use useFlowLoading() */
108
+ export const useSceneLoading = useFlowLoading