@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,31 @@
1
+ import { useCallback, useSyncExternalStore } from 'react'
2
+ import { isHideMode, activateHideMode, deactivateHideMode } from '../../core/index.js'
3
+ import { subscribeToStorage, getStorageSnapshot } from '../../core/index.js'
4
+
5
+ /**
6
+ * Read/control hide mode.
7
+ *
8
+ * Hide mode moves all URL hash overrides into localStorage so the URL
9
+ * stays clean — useful when sharing storyboards with customers.
10
+ *
11
+ * @returns {{ isHidden: boolean, hide: function, show: function }}
12
+ * - isHidden – true when hide mode is active
13
+ * - hide() – activate hide mode (copies hash → localStorage, cleans URL)
14
+ * - show() – deactivate hide mode (restores localStorage → hash)
15
+ */
16
+ export function useHideMode() {
17
+ // Re-render when localStorage changes (hide flag lives there)
18
+ useSyncExternalStore(subscribeToStorage, getStorageSnapshot)
19
+
20
+ const isHidden = isHideMode()
21
+
22
+ const hide = useCallback(() => {
23
+ activateHideMode()
24
+ }, [])
25
+
26
+ const show = useCallback(() => {
27
+ deactivateHideMode()
28
+ }, [])
29
+
30
+ return { isHidden, hide, show }
31
+ }
@@ -0,0 +1,43 @@
1
+ import { renderHook, act } from '@testing-library/react'
2
+ import { seedTestData } from '../../test-utils.js'
3
+ import { useHideMode } from './useHideMode.js'
4
+
5
+ beforeEach(() => {
6
+ seedTestData()
7
+ })
8
+
9
+ describe('useHideMode', () => {
10
+ it('returns { isHidden, hide, show }', () => {
11
+ const { result } = renderHook(() => useHideMode())
12
+ expect(result.current).toHaveProperty('isHidden')
13
+ expect(typeof result.current.hide).toBe('function')
14
+ expect(typeof result.current.show).toBe('function')
15
+ })
16
+
17
+ it('isHidden is false initially', () => {
18
+ const { result } = renderHook(() => useHideMode())
19
+ expect(result.current.isHidden).toBe(false)
20
+ })
21
+
22
+ it('after calling hide(), isHidden becomes true', () => {
23
+ const { result } = renderHook(() => useHideMode())
24
+
25
+ act(() => {
26
+ result.current.hide()
27
+ })
28
+
29
+ expect(result.current.isHidden).toBe(true)
30
+ })
31
+
32
+ it('after calling show(), isHidden becomes false', () => {
33
+ // Activate hide mode directly via localStorage to set known state
34
+ localStorage.setItem('storyboard:__hide__', '1')
35
+ const { result } = renderHook(() => useHideMode())
36
+ expect(result.current.isHidden).toBe(true)
37
+
38
+ act(() => {
39
+ result.current.show()
40
+ })
41
+ expect(result.current.isHidden).toBe(false)
42
+ })
43
+ })
@@ -0,0 +1,57 @@
1
+ import { useCallback, useContext, useSyncExternalStore } from 'react'
2
+ import { StoryboardContext } from '../StoryboardContext.js'
3
+ import { getByPath } from '../../core/index.js'
4
+ import { getParam } from '../../core/index.js'
5
+ import { getLocal, setLocal, removeLocal, subscribeToStorage, getStorageSnapshot } from '../../core/index.js'
6
+ import { subscribeToHash, getHashSnapshot } from '../../core/index.js'
7
+
8
+ /**
9
+ * Persistent localStorage override on top of scene data.
10
+ *
11
+ * Read priority: URL hash param → localStorage → Scene JSON value → undefined
12
+ * Write target: localStorage (not the URL hash)
13
+ *
14
+ * Use this hook for values that should survive page refreshes (e.g. theme).
15
+ * For ephemeral URL-based overrides, use `useOverride()`.
16
+ *
17
+ * @param {string} path - Dot-notation key (e.g. 'settings.theme')
18
+ * @returns {[any, function, function]}
19
+ * [0] current value (hash ?? localStorage ?? scene default)
20
+ * [1] setValue(newValue) – write to localStorage
21
+ * [2] clearValue() – remove from localStorage
22
+ */
23
+ export function useLocalStorage(path) {
24
+ const context = useContext(StoryboardContext)
25
+ if (context === null) {
26
+ throw new Error('useLocalStorage must be used within a <StoryboardProvider>')
27
+ }
28
+
29
+ const { data } = context
30
+
31
+ // Scene default for this path
32
+ const sceneDefault = data != null ? getByPath(data, path) : undefined
33
+
34
+ // Subscribe to both hash and localStorage changes for reactivity
35
+ useSyncExternalStore(subscribeToHash, getHashSnapshot)
36
+ useSyncExternalStore(subscribeToStorage, getStorageSnapshot)
37
+
38
+ // Read priority: hash → localStorage → scene default
39
+ const hashValue = getParam(path)
40
+ const localValue = getLocal(path)
41
+ const value = hashValue !== null ? hashValue : (localValue !== null ? localValue : sceneDefault)
42
+
43
+ /** Write a value to localStorage */
44
+ const setValue = useCallback(
45
+ (newValue) => {
46
+ setLocal(path, newValue)
47
+ },
48
+ [path],
49
+ )
50
+
51
+ /** Remove the localStorage value, reverting to scene default */
52
+ const clearValue = useCallback(() => {
53
+ removeLocal(path)
54
+ }, [path])
55
+
56
+ return [value, setValue, clearValue]
57
+ }
@@ -0,0 +1,75 @@
1
+ import { renderHook, act } from '@testing-library/react'
2
+ import { seedTestData, createWrapper, TEST_SCENES } from '../../test-utils.js'
3
+ import { useLocalStorage } from './useLocalStorage.js'
4
+
5
+ beforeEach(() => {
6
+ seedTestData()
7
+ })
8
+
9
+ const wrapper = createWrapper(TEST_SCENES.default)
10
+
11
+ describe('useLocalStorage', () => {
12
+ it('returns [value, setValue, clearValue]', () => {
13
+ const { result } = renderHook(() => useLocalStorage('settings.theme'), {
14
+ wrapper,
15
+ })
16
+ expect(result.current).toHaveLength(3)
17
+ expect(typeof result.current[1]).toBe('function')
18
+ expect(typeof result.current[2]).toBe('function')
19
+ })
20
+
21
+ it('falls back to scene default when no override exists', () => {
22
+ const { result } = renderHook(() => useLocalStorage('settings.theme'), {
23
+ wrapper,
24
+ })
25
+ expect(result.current[0]).toBe('dark_dimmed')
26
+ })
27
+
28
+ it('reads from localStorage when present', () => {
29
+ localStorage.setItem('storyboard:settings.theme', 'light')
30
+ const { result } = renderHook(() => useLocalStorage('settings.theme'), {
31
+ wrapper,
32
+ })
33
+ expect(result.current[0]).toBe('light')
34
+ })
35
+
36
+ it('hash override takes priority over localStorage', () => {
37
+ localStorage.setItem('storyboard:settings.theme', 'light')
38
+ window.location.hash = 'settings.theme=high-contrast'
39
+ const { result } = renderHook(() => useLocalStorage('settings.theme'), {
40
+ wrapper,
41
+ })
42
+ expect(result.current[0]).toBe('high-contrast')
43
+ })
44
+
45
+ it('setValue writes to localStorage', () => {
46
+ const { result } = renderHook(() => useLocalStorage('settings.theme'), {
47
+ wrapper,
48
+ })
49
+
50
+ act(() => {
51
+ result.current[1]('light')
52
+ })
53
+
54
+ expect(localStorage.getItem('storyboard:settings.theme')).toBe('light')
55
+ })
56
+
57
+ it('clearValue removes from localStorage', () => {
58
+ localStorage.setItem('storyboard:settings.theme', 'light')
59
+ const { result } = renderHook(() => useLocalStorage('settings.theme'), {
60
+ wrapper,
61
+ })
62
+
63
+ act(() => {
64
+ result.current[2]()
65
+ })
66
+
67
+ expect(localStorage.getItem('storyboard:settings.theme')).toBeNull()
68
+ })
69
+
70
+ it('throws when used outside StoryboardProvider', () => {
71
+ expect(() => {
72
+ renderHook(() => useLocalStorage('settings.theme'))
73
+ }).toThrow('useLocalStorage must be used within a <StoryboardProvider>')
74
+ })
75
+ })
@@ -0,0 +1,43 @@
1
+ import { useCallback, useSyncExternalStore } from 'react'
2
+ import {
3
+ getCurrentMode,
4
+ getRegisteredModes,
5
+ activateMode,
6
+ subscribeToMode,
7
+ getModeSnapshot,
8
+ } from '../../core/index.js'
9
+
10
+ /**
11
+ * React hook for the design-mode system.
12
+ *
13
+ * Uses useSyncExternalStore so the component re-renders whenever
14
+ * the active mode or the set of registered modes changes.
15
+ *
16
+ * @returns {{
17
+ * mode: string,
18
+ * modes: Array<{ name: string, label: string, icon?: string }>,
19
+ * switchMode: (name: string, options?: object) => void,
20
+ * currentModeConfig: object | undefined,
21
+ * }}
22
+ */
23
+ export function useMode() {
24
+ const snapshot = useSyncExternalStore(subscribeToMode, getModeSnapshot)
25
+
26
+ // snapshot is "modeName|registeredNames" — we only use it to trigger re-renders
27
+ void snapshot
28
+
29
+ const mode = getCurrentMode()
30
+ const modes = getRegisteredModes()
31
+ const currentModeConfig = modes.find((m) => m.name === mode)
32
+
33
+ const switchMode = useCallback((name, options) => {
34
+ activateMode(name, options)
35
+ }, [])
36
+
37
+ return {
38
+ mode,
39
+ modes,
40
+ switchMode,
41
+ currentModeConfig,
42
+ }
43
+ }
@@ -0,0 +1,101 @@
1
+ import { useContext, useMemo, useSyncExternalStore } from 'react'
2
+ import { loadObject, resolveObjectName } from '../../core/index.js'
3
+ import { getByPath, deepClone, setByPath } from '../../core/index.js'
4
+ import { getParam, getAllParams } from '../../core/index.js'
5
+ import { isHideMode, getShadow, getAllShadows } from '../../core/index.js'
6
+ import { subscribeToHash, getHashSnapshot } from '../../core/index.js'
7
+ import { subscribeToStorage, getStorageSnapshot } from '../../core/index.js'
8
+ import { StoryboardContext } from '../StoryboardContext.js'
9
+
10
+ /**
11
+ * Load an object data file directly by name, without going through a scene.
12
+ * Supports dot-notation path access and URL hash overrides.
13
+ * Objects inside prototypes are automatically resolved with prototype scope.
14
+ *
15
+ * Hash override convention: object.{objectName}.{field}=value
16
+ *
17
+ * @param {string} objectName - Name of the object file (e.g., "jane-doe")
18
+ * @param {string} [path] - Optional dot-notation path (e.g., "profile.name")
19
+ * @returns {*} The resolved value, or undefined if loading fails
20
+ *
21
+ * @example
22
+ * const user = useObject('jane-doe')
23
+ * const name = useObject('jane-doe', 'profile.name')
24
+ *
25
+ * // Override via URL hash: #object.jane-doe.name=Alice
26
+ */
27
+ export function useObject(objectName, path) {
28
+ const context = useContext(StoryboardContext)
29
+ const prototypeName = context?.prototypeName ?? null
30
+ const hashString = useSyncExternalStore(subscribeToHash, getHashSnapshot)
31
+ const storageString = useSyncExternalStore(subscribeToStorage, getStorageSnapshot)
32
+
33
+ return useMemo(() => {
34
+ const resolvedName = resolveObjectName(prototypeName, objectName)
35
+ let data
36
+ try {
37
+ data = loadObject(resolvedName)
38
+ } catch (err) {
39
+ console.error(`[useObject] ${err.message}`)
40
+ return undefined
41
+ }
42
+
43
+ const hidden = isHideMode()
44
+ const readParam = hidden ? getShadow : getParam
45
+ const readAllParams = hidden ? getAllShadows : getAllParams
46
+
47
+ // Apply overrides scoped to this object.
48
+ // Check both the resolved (scoped) prefix and the plain (unscoped) prefix
49
+ // so overrides work whether written with the bare or scoped name.
50
+ const resolvedPrefix = `object.${resolvedName}.`
51
+ const plainPrefix = objectName !== resolvedName ? `object.${objectName}.` : null
52
+ const allParams = readAllParams()
53
+ const overrideKeys = Object.keys(allParams).filter(k =>
54
+ k.startsWith(resolvedPrefix) || (plainPrefix && k.startsWith(plainPrefix))
55
+ )
56
+
57
+ if (overrideKeys.length > 0) {
58
+ data = deepClone(data)
59
+ for (const key of overrideKeys) {
60
+ const fieldPath = key.startsWith(resolvedPrefix)
61
+ ? key.slice(resolvedPrefix.length)
62
+ : key.slice(plainPrefix.length)
63
+ setByPath(data, fieldPath, allParams[key])
64
+ }
65
+ }
66
+
67
+ if (!path) return data
68
+
69
+ // Exact match for this sub-path override (check both prefixes)
70
+ const exactResolved = `${resolvedPrefix}${path}`
71
+ const exactPlain = plainPrefix ? `${plainPrefix}${path}` : null
72
+ const exact = readParam(exactResolved) ?? (exactPlain ? readParam(exactPlain) : null)
73
+ if (exact !== null) return exact
74
+
75
+ // Child overrides under the sub-path
76
+ const subResolved = exactResolved + '.'
77
+ const subPlain = exactPlain ? exactPlain + '.' : null
78
+ const childKeys = overrideKeys.filter(k =>
79
+ k.startsWith(subResolved) || (subPlain && k.startsWith(subPlain))
80
+ )
81
+ const baseValue = getByPath(data, path)
82
+
83
+ if (childKeys.length > 0 && baseValue !== undefined) {
84
+ const merged = deepClone(baseValue)
85
+ for (const key of childKeys) {
86
+ const relativePath = key.startsWith(subResolved)
87
+ ? key.slice(subResolved.length)
88
+ : key.slice(subPlain.length)
89
+ setByPath(merged, relativePath, allParams[key])
90
+ }
91
+ return merged
92
+ }
93
+
94
+ if (baseValue === undefined) {
95
+ console.warn(`[useObject] Path "${path}" not found in object "${objectName}".`)
96
+ return undefined
97
+ }
98
+
99
+ return baseValue
100
+ }, [objectName, prototypeName, path, hashString, storageString]) // eslint-disable-line react-hooks/exhaustive-deps
101
+ }
@@ -0,0 +1,74 @@
1
+ import { renderHook, act } from '@testing-library/react'
2
+ import { seedTestData, TEST_OBJECTS } from '../../test-utils.js'
3
+ import { activateHideMode, setShadow } from '../../core/index.js'
4
+ import { useObject } from './useObject.js'
5
+
6
+ beforeEach(() => {
7
+ seedTestData()
8
+ window.location.hash = ''
9
+ })
10
+
11
+ describe('useObject', () => {
12
+ it('loads an object by name', () => {
13
+ const { result } = renderHook(() => useObject('jane-doe'))
14
+ expect(result.current).toEqual(TEST_OBJECTS['jane-doe'])
15
+ })
16
+
17
+ it('returns undefined for missing object', () => {
18
+ vi.spyOn(console, 'error').mockImplementation(() => {})
19
+ const { result } = renderHook(() => useObject('nonexistent'))
20
+ expect(result.current).toBeUndefined()
21
+ console.error.mockRestore()
22
+ })
23
+
24
+ it('resolves dot-notation path', () => {
25
+ const { result } = renderHook(() => useObject('jane-doe', 'name'))
26
+ expect(result.current).toBe('Jane Doe')
27
+ })
28
+
29
+ it('returns undefined for missing path', () => {
30
+ vi.spyOn(console, 'warn').mockImplementation(() => {})
31
+ const { result } = renderHook(() => useObject('jane-doe', 'missing.path'))
32
+ expect(result.current).toBeUndefined()
33
+ console.warn.mockRestore()
34
+ })
35
+
36
+ it('applies hash overrides to full object', () => {
37
+ window.location.hash = 'object.jane-doe.name=Alice'
38
+ const { result } = renderHook(() => useObject('jane-doe'))
39
+ expect(result.current.name).toBe('Alice')
40
+ expect(result.current.role).toBe('admin')
41
+ })
42
+
43
+ it('applies hash overrides when accessing by path', () => {
44
+ window.location.hash = 'object.jane-doe.name=Alice'
45
+ const { result } = renderHook(() => useObject('jane-doe', 'name'))
46
+ expect(result.current).toBe('Alice')
47
+ })
48
+
49
+ it('returns deep clone (mutations do not affect source data)', () => {
50
+ const { result: r1 } = renderHook(() => useObject('jane-doe'))
51
+ r1.current.name = 'Mutated'
52
+ // A fresh hook call should return original data, not the mutated reference
53
+ const { result: r2 } = renderHook(() => useObject('jane-doe'))
54
+ expect(r2.current.name).toBe('Jane Doe')
55
+ })
56
+ })
57
+
58
+ describe('useObject (hide mode)', () => {
59
+ beforeEach(() => {
60
+ act(() => { activateHideMode() })
61
+ })
62
+
63
+ it('reads overrides from localStorage shadow in hide mode', () => {
64
+ act(() => { setShadow('object.jane-doe.name', 'Shadow Jane') })
65
+ const { result } = renderHook(() => useObject('jane-doe'))
66
+ expect(result.current.name).toBe('Shadow Jane')
67
+ })
68
+
69
+ it('reads path-specific overrides from shadow in hide mode', () => {
70
+ act(() => { setShadow('object.jane-doe.role', 'superadmin') })
71
+ const { result } = renderHook(() => useObject('jane-doe', 'role'))
72
+ expect(result.current).toBe('superadmin')
73
+ })
74
+ })
@@ -0,0 +1,84 @@
1
+ import { useCallback, useContext, useSyncExternalStore } from 'react'
2
+ import { StoryboardContext } from '../StoryboardContext.js'
3
+ import { getByPath } from '../../core/index.js'
4
+ import { getParam, setParam, removeParam } from '../../core/index.js'
5
+ import { subscribeToHash } from '../../core/index.js'
6
+ import { isHideMode, getShadow, setShadow, removeShadow } from '../../core/index.js'
7
+ import { subscribeToStorage, getStorageSnapshot } from '../../core/index.js'
8
+
9
+ /**
10
+ * Read/write overrides on top of scene data or object data.
11
+ *
12
+ * **Normal mode:**
13
+ * Read priority: URL hash param → Scene JSON value → undefined
14
+ * Write target: URL hash + shadow copy to localStorage
15
+ *
16
+ * **Hide mode** (activated by `?hide`):
17
+ * Read priority: shadow localStorage → Scene JSON value → undefined
18
+ * Write target: shadow localStorage only (URL stays clean)
19
+ *
20
+ * Every write also mirrors to localStorage shadow keys, so hide mode
21
+ * can hot-swap without data loss.
22
+ *
23
+ * Works with any override namespace — scene paths (e.g. 'settings.theme'),
24
+ * object paths (e.g. 'object.jane-doe.name'), or record paths
25
+ * (e.g. 'record.posts.post-1.title').
26
+ *
27
+ * When used outside a StoryboardProvider (e.g. for object overrides),
28
+ * the scene fallback is skipped and value resolves to override ?? undefined.
29
+ *
30
+ * @param {string} path - Dot-notation key (e.g. 'settings.theme')
31
+ * @returns {[any, function, function]}
32
+ * [0] current value (override ?? scene default ?? undefined)
33
+ * [1] setValue(newValue) – write an override
34
+ * [2] clearValue() – remove the override, reverting to scene default
35
+ */
36
+ export function useOverride(path) {
37
+ const context = useContext(StoryboardContext)
38
+
39
+ const data = context?.data
40
+ const hidden = isHideMode()
41
+
42
+ // Scene default for this path (fallback when no override exists)
43
+ const sceneDefault = data != null ? getByPath(data, path) : undefined
44
+
45
+ // Subscribe to both sources for reactivity
46
+ const getHashSnap = useCallback(() => getParam(path), [path])
47
+ const hashValue = useSyncExternalStore(subscribeToHash, getHashSnap)
48
+ useSyncExternalStore(subscribeToStorage, getStorageSnapshot)
49
+
50
+ // Resolved value depends on mode
51
+ let value
52
+ if (hidden) {
53
+ const shadowValue = getShadow(path)
54
+ value = shadowValue !== null ? shadowValue : sceneDefault
55
+ } else {
56
+ value = hashValue !== null ? hashValue : sceneDefault
57
+ }
58
+
59
+ /** Write a value — targets hash or shadow depending on mode */
60
+ const setValue = useCallback(
61
+ (newValue) => {
62
+ if (isHideMode()) {
63
+ setShadow(path, newValue)
64
+ } else {
65
+ setParam(path, newValue)
66
+ // Always mirror to shadow so hide mode can hot-swap
67
+ setShadow(path, newValue)
68
+ }
69
+ },
70
+ [path],
71
+ )
72
+
73
+ /** Remove the override, reverting to scene default */
74
+ const clearValue = useCallback(() => {
75
+ if (isHideMode()) {
76
+ removeShadow(path)
77
+ } else {
78
+ removeParam(path)
79
+ removeShadow(path)
80
+ }
81
+ }, [path])
82
+
83
+ return [value, setValue, clearValue]
84
+ }
@@ -0,0 +1,71 @@
1
+ import { renderHook, act } from '@testing-library/react'
2
+ import { useOverride } from './useOverride.js'
3
+ import { seedTestData, createWrapper, TEST_SCENES } from '../test-utils.js'
4
+
5
+ const sceneData = TEST_SCENES.default
6
+
7
+ beforeEach(() => {
8
+ seedTestData()
9
+ })
10
+
11
+ describe('useOverride', () => {
12
+ it('returns [value, setValue, clearValue] tuple', () => {
13
+ const { result } = renderHook(() => useOverride('settings.theme'), {
14
+ wrapper: createWrapper(sceneData),
15
+ })
16
+ expect(result.current).toHaveLength(3)
17
+ expect(typeof result.current[1]).toBe('function')
18
+ expect(typeof result.current[2]).toBe('function')
19
+ })
20
+
21
+ it('value falls back to scene default when no hash override', () => {
22
+ const { result } = renderHook(() => useOverride('settings.theme'), {
23
+ wrapper: createWrapper(sceneData),
24
+ })
25
+ expect(result.current[0]).toBe('dark')
26
+ })
27
+
28
+ it('value reads from hash override when present', () => {
29
+ window.location.hash = '#settings.theme=light'
30
+ const { result } = renderHook(() => useOverride('settings.theme'), {
31
+ wrapper: createWrapper(sceneData),
32
+ })
33
+ expect(result.current[0]).toBe('light')
34
+ })
35
+
36
+ it('setValue writes to hash', () => {
37
+ const { result } = renderHook(() => useOverride('settings.theme'), {
38
+ wrapper: createWrapper(sceneData),
39
+ })
40
+
41
+ act(() => {
42
+ result.current[1]('blue')
43
+ })
44
+
45
+ expect(window.location.hash).toContain('settings.theme=blue')
46
+ })
47
+
48
+ it('clearValue removes hash param', () => {
49
+ window.location.hash = '#settings.theme=red'
50
+ const { result } = renderHook(() => useOverride('settings.theme'), {
51
+ wrapper: createWrapper(sceneData),
52
+ })
53
+
54
+ act(() => {
55
+ result.current[2]()
56
+ })
57
+
58
+ expect(window.location.hash).not.toContain('settings.theme')
59
+ })
60
+
61
+ it('works without StoryboardProvider for object overrides', () => {
62
+ window.location.hash = '#object.jane-doe.name=Alice'
63
+ const { result } = renderHook(() => useOverride('object.jane-doe.name'))
64
+ expect(result.current[0]).toBe('Alice')
65
+ })
66
+
67
+ it('returns undefined without provider when no override exists', () => {
68
+ const { result } = renderHook(() => useOverride('object.jane-doe.name'))
69
+ expect(result.current[0]).toBeUndefined()
70
+ })
71
+ })
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Prototype reload guard — suppresses Vite HMR full-reloads for non-canvas pages.
3
+ *
4
+ * Controlled by the "prototype-auto-reload" feature flag (default: true).
5
+ * When the flag is false (user opted out), sends heartbeat messages to the
6
+ * Vite dev server which suppresses full-reload and update payloads for this
7
+ * client. Custom storyboard events (canvas file changes, story changes, etc.)
8
+ * always pass through.
9
+ *
10
+ * Heartbeats are sent every 3s and auto-expire server-side after 5s, so
11
+ * closed tabs never leave the guard stuck.
12
+ */
13
+ import { useEffect } from 'react'
14
+ import { getFlag, subscribeToStorage } from '../../core/index.js'
15
+
16
+ const FLAG_KEY = 'prototype-auto-reload'
17
+ const HEARTBEAT_MS = 3000
18
+
19
+ export default function usePrototypeReloadGuard() {
20
+ useEffect(() => {
21
+ if (!import.meta.hot) return
22
+
23
+ let interval = null
24
+
25
+ function start() {
26
+ if (interval) return
27
+ const msg = { active: true }
28
+ import.meta.hot.send('storyboard:prototype-reload-guard', msg)
29
+ interval = setInterval(() => {
30
+ import.meta.hot.send('storyboard:prototype-reload-guard', msg)
31
+ }, HEARTBEAT_MS)
32
+ }
33
+
34
+ function stop() {
35
+ if (interval) {
36
+ clearInterval(interval)
37
+ interval = null
38
+ }
39
+ import.meta.hot.send('storyboard:prototype-reload-guard', { active: false })
40
+ }
41
+
42
+ function sync() {
43
+ const autoReload = getFlag(FLAG_KEY)
44
+ if (autoReload) {
45
+ stop()
46
+ } else {
47
+ start()
48
+ }
49
+ }
50
+
51
+ // Initial sync
52
+ sync()
53
+
54
+ // Re-sync when the flag changes in localStorage (e.g. toggled from devtools)
55
+ const unsub = subscribeToStorage((key) => {
56
+ if (key === 'flag.' + FLAG_KEY) sync()
57
+ })
58
+
59
+ return () => {
60
+ stop()
61
+ unsub()
62
+ }
63
+ }, [])
64
+ }