@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,391 @@
1
+ /**
2
+ * Storyboard Server — persistent Node.js process that manages dev sessions.
3
+ *
4
+ * Runs independently of Vite. Manages:
5
+ * - Vite child processes (one per active branch)
6
+ * - /_storyboard/ API (workshop, canvas, comments, worktrees, branch switching)
7
+ * - Caddy proxy route registration
8
+ *
9
+ * Architecture:
10
+ * Browser → Caddy → Vite (serves app + static)
11
+ * Browser → Caddy → Storyboard Server (serves /_storyboard/ API)
12
+ * Storyboard Server → spawns/manages Vite processes
13
+ */
14
+
15
+ import http from 'node:http'
16
+ import { spawn } from 'node:child_process'
17
+ import { existsSync } from 'node:fs'
18
+ import { resolve, join } from 'node:path'
19
+ import { getPort, releasePort, repoRoot } from '../worktree/port.js'
20
+ import { generateCaddyfile, generateRouteConfig, upsertCaddyRoute, isCaddyRunning, reloadCaddy, readDevDomain } from '../cli/proxy.js'
21
+ import { compactAll } from '../canvas/compact.js'
22
+ import { register, unregister, generateId, list, findByWorktree } from '../worktree/serverRegistry.js'
23
+ import { createDevLogger } from '../logger/devLogger.js'
24
+
25
+ const SERVER_PORT_BASE = 4100
26
+
27
+ /**
28
+ * Derive a deterministic server port from the devDomain.
29
+ * Each repo (identified by its devDomain) gets its own port.
30
+ */
31
+ function deriveServerPort(domain) {
32
+ let h = 0
33
+ for (let i = 0; i < domain.length; i++) {
34
+ h = ((h << 5) - h + domain.charCodeAt(i)) | 0
35
+ }
36
+ // Map to port range 4100-4199
37
+ return SERVER_PORT_BASE + (Math.abs(h) % 100)
38
+ }
39
+
40
+ const DEV_DOMAIN = readDevDomain()
41
+ export const SERVER_PORT = deriveServerPort(DEV_DOMAIN)
42
+ const HEALTH_TIMEOUT = 30_000
43
+ const HEALTH_INTERVAL = 300
44
+
45
+ /** Active Vite processes: branch name → { child, port, status, cwd } */
46
+ const processes = new Map()
47
+
48
+ /** Route handlers: prefix → handler function */
49
+ const routeHandlers = new Map()
50
+
51
+ // ─── Dev Logger ───
52
+
53
+ let _currentBranch = null
54
+ try { _currentBranch = (await import('node:child_process')).execSync('git branch --show-current', { encoding: 'utf8', cwd: repoRoot() }).trim() } catch { /* empty */ }
55
+ const devLogger = createDevLogger({ root: repoRoot(), devDomain: DEV_DOMAIN.replace('.localhost', ''), branch: _currentBranch })
56
+
57
+ // ─── JSON helpers ───
58
+
59
+ function sendJson(res, status, data) {
60
+ const body = JSON.stringify(data)
61
+ res.writeHead(status, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' })
62
+ res.end(body)
63
+ }
64
+
65
+ function sendJsonLogged(res, status, data) {
66
+ sendJson(res, status, data)
67
+ if (status >= 400) {
68
+ const ctx = res.__sbLogCtx || {}
69
+ devLogger.logResponse({
70
+ status,
71
+ method: ctx.method || 'UNKNOWN',
72
+ url: ctx.url || '',
73
+ route: ctx.route || null,
74
+ subRoute: ctx.subRoute || null,
75
+ error: data?.error || null,
76
+ })
77
+ }
78
+ }
79
+
80
+ async function parseBody(req) {
81
+ const chunks = []
82
+ for await (const chunk of req) chunks.push(chunk)
83
+ try { return JSON.parse(Buffer.concat(chunks).toString()) }
84
+ catch { return {} }
85
+ }
86
+
87
+ // ─── Vite Process Management ───
88
+
89
+ function resolveWorktreeCwd(branch) {
90
+ const root = repoRoot()
91
+ if (branch === 'main') return root
92
+ const wtDir = join(root, 'worktrees', branch)
93
+ if (existsSync(wtDir)) return wtDir
94
+ return null
95
+ }
96
+
97
+ function isPortReady(port) {
98
+ return new Promise((resolve) => {
99
+ const req = http.get(`http://localhost:${port}`, () => resolve(true))
100
+ req.on('error', () => resolve(false))
101
+ req.setTimeout(1000, () => { req.destroy(); resolve(false) })
102
+ })
103
+ }
104
+
105
+ async function waitForPort(port, timeout = HEALTH_TIMEOUT) {
106
+ const start = Date.now()
107
+ while (Date.now() - start < timeout) {
108
+ if (await isPortReady(port)) return true
109
+ await new Promise(r => setTimeout(r, HEALTH_INTERVAL))
110
+ }
111
+ return false
112
+ }
113
+
114
+ function buildBasePath(branch) {
115
+ return branch === 'main' ? '/' : `/branch--${branch}/`
116
+ }
117
+
118
+ function spawnVite(branch) {
119
+ const cwd = resolveWorktreeCwd(branch)
120
+ if (!cwd) throw new Error(`Worktree not found for branch: ${branch}`)
121
+
122
+ const port = getPort(branch)
123
+ const basePath = buildBasePath(branch)
124
+ const localVite = resolve(cwd, 'node_modules', '.bin', 'vite')
125
+ const useLocalVite = existsSync(localVite)
126
+
127
+ // Compact canvas files before starting
128
+ try { compactAll(cwd) } catch { /* non-critical */ }
129
+
130
+ const viteArgs = ['--port', String(port)]
131
+ const child = useLocalVite
132
+ ? spawn(localVite, viteArgs, {
133
+ cwd,
134
+ env: { ...process.env, VITE_BASE_PATH: basePath },
135
+ stdio: ['ignore', 'pipe', 'pipe'],
136
+ })
137
+ : spawn('npx', ['vite', ...viteArgs], {
138
+ cwd,
139
+ env: { ...process.env, VITE_BASE_PATH: basePath },
140
+ stdio: ['ignore', 'pipe', 'pipe'],
141
+ })
142
+
143
+ const entry = { child, port, status: 'starting', cwd, branch, serverId: generateId() }
144
+ processes.set(branch, entry)
145
+
146
+ // Register in persistent server registry
147
+ register({ id: entry.serverId, worktree: branch, pid: child.pid, port, background: true })
148
+
149
+ // Detect ready state from stdout
150
+ child.stdout.on('data', (data) => {
151
+ const text = data.toString()
152
+ const portMatch = text.match(/localhost:(\d+)/)
153
+ if (portMatch) {
154
+ const actualPort = Number(portMatch[1])
155
+ if (actualPort !== entry.port) {
156
+ entry.port = actualPort
157
+ // Re-register with the actual port Vite bound to
158
+ register({ id: entry.serverId, worktree: branch, pid: child.pid, port: actualPort, background: true })
159
+ }
160
+ }
161
+ if (text.includes('ready in')) {
162
+ entry.status = 'ready'
163
+ registerCaddyRoute(branch, entry.port)
164
+ }
165
+ })
166
+
167
+ child.stderr.on('data', () => { /* suppress */ })
168
+
169
+ child.on('exit', (_code) => {
170
+ void _code
171
+ entry.status = 'stopped'
172
+ processes.delete(branch)
173
+ unregister(entry.serverId)
174
+ releasePort(branch)
175
+ })
176
+
177
+ return entry
178
+ }
179
+
180
+ function stopVite(branch) {
181
+ const entry = processes.get(branch)
182
+ if (!entry) return
183
+ try { entry.child.kill('SIGTERM') } catch { /* */ }
184
+ processes.delete(branch)
185
+ }
186
+
187
+ function registerCaddyRoute(branch, port) {
188
+ try {
189
+ const routeConfig = generateRouteConfig({ [branch]: port })
190
+ if (isCaddyRunning() && upsertCaddyRoute(routeConfig)) {
191
+ generateCaddyfile({ [branch]: port })
192
+ } else {
193
+ const caddyfilePath = generateCaddyfile({ [branch]: port })
194
+ if (isCaddyRunning()) reloadCaddy(caddyfilePath)
195
+ }
196
+ } catch { /* Caddy not available */ }
197
+ }
198
+
199
+ // ─── API Routes ───
200
+
201
+ // POST /_storyboard/switch-branch
202
+ routeHandlers.set('switch-branch', async (req, res, ctx) => {
203
+ if (ctx.method !== 'POST') {
204
+ sendJsonLogged(res, 405, { error: 'POST required' })
205
+ return
206
+ }
207
+
208
+ const { branch } = ctx.body
209
+ if (!branch) {
210
+ sendJsonLogged(res, 400, { error: 'branch is required' })
211
+ return
212
+ }
213
+
214
+ const devDomain = readDevDomain()
215
+ const targetUrl = branch === 'main'
216
+ ? `http://${devDomain}/`
217
+ : `http://${devDomain}/branch--${branch}/`
218
+
219
+ try {
220
+ // Check if already running
221
+ const existing = processes.get(branch)
222
+ if (existing && existing.status === 'ready') {
223
+ const alive = await isPortReady(existing.port)
224
+ if (alive) {
225
+ sendJsonLogged(res, 200, { url: targetUrl, status: 'already_running' })
226
+ return
227
+ }
228
+ // Stale entry — remove it
229
+ processes.delete(branch)
230
+ }
231
+
232
+ // Check if Vite is already running in this server's process map
233
+ const _port = getPort(branch)
234
+ void _port
235
+ const existingInRegistry = findByWorktree(branch)
236
+ if (existingInRegistry.length > 0) {
237
+ const latest = existingInRegistry.reduce((a, b) =>
238
+ (a.startedAt || '') >= (b.startedAt || '') ? a : b
239
+ )
240
+ if (await isPortReady(latest.port)) {
241
+ registerCaddyRoute(branch, latest.port)
242
+ sendJsonLogged(res, 200, { url: targetUrl, status: 'already_running' })
243
+ return
244
+ }
245
+ }
246
+
247
+ // Check worktree exists
248
+ const cwd = resolveWorktreeCwd(branch)
249
+ if (!cwd) {
250
+ sendJsonLogged(res, 404, { error: `Worktree not found: ${branch}. Create it with: npx storyboard dev ${branch}` })
251
+ return
252
+ }
253
+
254
+ // Spawn Vite
255
+ const entry = spawnVite(branch)
256
+
257
+ // Wait for Vite to report ready via stdout (not TCP polling, which
258
+ // can false-positive on occupied ports from other processes)
259
+ const ready = await (async () => {
260
+ const start = Date.now()
261
+ while (Date.now() - start < HEALTH_TIMEOUT) {
262
+ if (entry.status === 'ready') return true
263
+ await new Promise(r => setTimeout(r, 300))
264
+ }
265
+ return false
266
+ })()
267
+ if (!ready) {
268
+ stopVite(branch)
269
+ sendJsonLogged(res, 504, { error: `Vite server for ${branch} did not become ready in time` })
270
+ return
271
+ }
272
+
273
+ sendJsonLogged(res, 200, { url: targetUrl, status: 'started' })
274
+ } catch (err) {
275
+ sendJsonLogged(res, 500, { error: err.message })
276
+ }
277
+ })
278
+
279
+ // GET /_storyboard/worktrees
280
+ routeHandlers.set('worktrees', async (req, res) => {
281
+ try {
282
+ // Use the server registry (live processes) instead of stale ports.json
283
+ const servers = list()
284
+ const branches = servers.map(srv => ({
285
+ branch: srv.worktree,
286
+ folder: srv.worktree === 'main' ? '' : `branch--${srv.worktree}/`,
287
+ port: srv.port,
288
+ running: processes.has(srv.worktree) ? processes.get(srv.worktree).status : 'background',
289
+ }))
290
+ // Always include main even if no server is registered for it
291
+ if (!branches.some(b => b.branch === 'main')) {
292
+ branches.unshift({ branch: 'main', folder: '', port: 1234, running: null })
293
+ }
294
+ sendJsonLogged(res, 200, branches)
295
+ } catch { sendJsonLogged(res, 200, []) }
296
+ })
297
+
298
+ // GET /_storyboard/server/status
299
+ routeHandlers.set('server', async (req, res, ctx) => {
300
+ if (ctx.path === '/status' || ctx.path === '/') {
301
+ const active = []
302
+ for (const [branch, entry] of processes) {
303
+ active.push({ branch, port: entry.port, status: entry.status })
304
+ }
305
+ sendJsonLogged(res, 200, { active, serverPort: SERVER_PORT })
306
+ return
307
+ }
308
+ sendJsonLogged(res, 404, { error: 'Unknown server route' })
309
+ })
310
+
311
+ // ─── HTTP Server ───
312
+
313
+ const server = http.createServer(async (req, res) => {
314
+ // CORS preflight
315
+ if (req.method === 'OPTIONS') {
316
+ res.writeHead(204, {
317
+ 'Access-Control-Allow-Origin': '*',
318
+ 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
319
+ 'Access-Control-Allow-Headers': 'Content-Type',
320
+ })
321
+ res.end()
322
+ return
323
+ }
324
+
325
+ const url = new URL(req.url, `http://localhost:${SERVER_PORT}`)
326
+ const pathname = url.pathname
327
+
328
+ // Route /_storyboard/{prefix}/{rest}
329
+ if (pathname.startsWith('/_storyboard/')) {
330
+ const after = pathname.slice('/_storyboard/'.length)
331
+ const slashIdx = after.indexOf('/')
332
+ const prefix = slashIdx === -1 ? after : after.slice(0, slashIdx)
333
+ const restPath = slashIdx === -1 ? '/' : after.slice(slashIdx)
334
+
335
+ // Attach route context for the logging sendJson wrapper
336
+ res.__sbLogCtx = { method: req.method, url: req.url, route: prefix, subRoute: restPath }
337
+
338
+ const handler = routeHandlers.get(prefix)
339
+ if (handler) {
340
+ let body = {}
341
+ if (req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH') {
342
+ body = await parseBody(req)
343
+ }
344
+ try {
345
+ await handler(req, res, { body, path: restPath, method: req.method })
346
+ } catch (err) {
347
+ sendJsonLogged(res, 500, { error: err.message })
348
+ }
349
+ return
350
+ }
351
+ }
352
+
353
+ // Health check
354
+ if (pathname === '/health') {
355
+ sendJsonLogged(res, 200, { ok: true, devDomain: DEV_DOMAIN })
356
+ return
357
+ }
358
+
359
+ // Set context for catch-all 404
360
+ res.__sbLogCtx = { method: req.method, url: req.url, route: null, subRoute: null }
361
+ sendJsonLogged(res, 404, { error: 'Not found' })
362
+ })
363
+
364
+ export function startServer(port = SERVER_PORT) {
365
+ return new Promise((resolve, reject) => {
366
+ server.on('error', (err) => {
367
+ if (err.code === 'EADDRINUSE') {
368
+ reject(Object.assign(new Error(`Port ${port} already in use`), { code: 'EADDRINUSE' }))
369
+ return
370
+ }
371
+ reject(err)
372
+ })
373
+ server.listen(port, () => {
374
+ resolve(server)
375
+ })
376
+ })
377
+ }
378
+
379
+ /** Public API for spawning Vite from CLI (with stdout piping) */
380
+ export function spawnViteForBranch(branch, { pipeOutput = false } = {}) {
381
+ const entry = spawnVite(branch)
382
+
383
+ if (pipeOutput) {
384
+ entry.child.stdout.pipe(process.stdout)
385
+ entry.child.stderr.pipe(process.stderr)
386
+ }
387
+
388
+ return entry
389
+ }
390
+
391
+ export { processes, routeHandlers, waitForPort, isPortReady }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Body class sync for overrides and scenes.
3
+ *
4
+ * Mirrors active overrides as `sb-{key}--{value}` classes on <body>
5
+ * and the active scene as `sb-scene--{name}`.
6
+ *
7
+ * Works in both normal mode (URL hash) and hide mode (localStorage shadows).
8
+ */
9
+
10
+ import { getAllParams } from './session.js'
11
+ import { isHideMode, getAllShadows } from './hideMode.js'
12
+ import { subscribeToHash } from './hashSubscribe.js'
13
+ import { subscribeToStorage } from './localStorage.js'
14
+ import { syncFlagBodyClasses } from '../stores/featureFlags.js'
15
+
16
+ const PREFIX = 'sb-'
17
+ const FLOW_PREFIX = 'sb-scene--'
18
+
19
+ /**
20
+ * Sanitize a string for use in a CSS class name.
21
+ * Dots and spaces become dashes, lowercased, non-alphanumeric stripped.
22
+ * @param {string} str
23
+ * @returns {string}
24
+ */
25
+ function sanitize(str) {
26
+ return String(str)
27
+ .toLowerCase()
28
+ .replace(/[.\s]+/g, '-')
29
+ .replace(/[^a-z0-9-]/g, '')
30
+ .replace(/-+/g, '-')
31
+ .replace(/^-|-$/g, '')
32
+ }
33
+
34
+ /**
35
+ * Build the class name for an override key/value pair.
36
+ * @param {string} key
37
+ * @param {string} value
38
+ * @returns {string}
39
+ */
40
+ function overrideClass(key, value) {
41
+ return `${PREFIX}${sanitize(key)}--${sanitize(value)}`
42
+ }
43
+
44
+ /**
45
+ * Get all current sb- override classes on body.
46
+ * Override classes follow the pattern `sb-{key}--{value}` (double-dash separator).
47
+ * Other sb-* classes (comment mode, feature flags, etc.) are excluded
48
+ * because they don't contain the `--` separator.
49
+ * Flow/scene classes (`sb-scene--*`) are also excluded since they use
50
+ * the same `--` separator but have their own lifecycle.
51
+ * @returns {Set<string>}
52
+ */
53
+ function getCurrentOverrideClasses() {
54
+ const classes = new Set()
55
+ for (const cls of document.body.classList) {
56
+ if (cls.startsWith(PREFIX) && cls.includes('--') && !cls.startsWith(FLOW_PREFIX)) {
57
+ classes.add(cls)
58
+ }
59
+ }
60
+ return classes
61
+ }
62
+
63
+ /**
64
+ * Sync override classes on <body> with current hash/shadow state.
65
+ * Diffs against existing classes to minimize DOM mutations.
66
+ */
67
+ export function syncOverrideClasses() {
68
+ const overrides = isHideMode() ? getAllShadows() : getAllParams()
69
+ const desired = new Set()
70
+ for (const [key, value] of Object.entries(overrides)) {
71
+ if (key && value != null && value !== '') {
72
+ desired.add(overrideClass(key, value))
73
+ }
74
+ }
75
+
76
+ const current = getCurrentOverrideClasses()
77
+
78
+ // Remove stale classes
79
+ for (const cls of current) {
80
+ if (!desired.has(cls)) {
81
+ document.body.classList.remove(cls)
82
+ }
83
+ }
84
+ // Add missing classes
85
+ for (const cls of desired) {
86
+ if (!current.has(cls)) {
87
+ document.body.classList.add(cls)
88
+ }
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Set the flow class on <body>. Removes any previous flow class.
94
+ * @param {string} name - Flow name (e.g. "Dashboard")
95
+ */
96
+ export function setFlowClass(name) {
97
+ // Remove any existing flow classes
98
+ for (const cls of [...document.body.classList]) {
99
+ if (cls.startsWith(FLOW_PREFIX)) {
100
+ document.body.classList.remove(cls)
101
+ }
102
+ }
103
+ if (name) {
104
+ document.body.classList.add(`${FLOW_PREFIX}${sanitize(name)}`)
105
+ }
106
+ }
107
+
108
+ /** @deprecated Use setFlowClass() */
109
+ export const setSceneClass = setFlowClass
110
+
111
+ /**
112
+ * Install listeners that keep body classes in sync with overrides.
113
+ * Subscribes to both hashchange (normal mode) and storage (hide mode).
114
+ * Runs an initial sync immediately.
115
+ *
116
+ * @returns {function} unsubscribe — removes all listeners
117
+ */
118
+ export function installBodyClassSync() {
119
+ syncOverrideClasses()
120
+ syncFlagBodyClasses()
121
+ const sync = () => { syncOverrideClasses(); syncFlagBodyClasses() }
122
+ const unsubHash = subscribeToHash(sync)
123
+ const unsubStorage = subscribeToStorage(sync)
124
+ return () => {
125
+ unsubHash()
126
+ unsubStorage()
127
+ }
128
+ }
@@ -0,0 +1,192 @@
1
+ import {
2
+ syncOverrideClasses,
3
+ setFlowClass,
4
+ setSceneClass,
5
+ installBodyClassSync,
6
+ } from './bodyClasses.js'
7
+ import { activateHideMode, deactivateHideMode, setShadow } from './hideMode.js'
8
+
9
+ /**
10
+ * Collect all sb- prefixed classes currently on document.body.
11
+ * @returns {string[]}
12
+ */
13
+ function getSbClasses() {
14
+ return [...document.body.classList].filter((c) => c.startsWith('sb-'))
15
+ }
16
+
17
+ beforeEach(() => {
18
+ // Clear all sb- classes and hash between tests
19
+ for (const cls of getSbClasses()) {
20
+ document.body.classList.remove(cls)
21
+ }
22
+ window.location.hash = ''
23
+ // Ensure hide mode is off
24
+ try {
25
+ deactivateHideMode()
26
+ } catch {
27
+ // ignore if not active
28
+ }
29
+ })
30
+
31
+ // ── Override Classes ──
32
+
33
+ describe('Override body classes', () => {
34
+ it('adds sb- classes for hash overrides', () => {
35
+ window.location.hash = '#theme=dark&sidebar=collapsed'
36
+ syncOverrideClasses()
37
+ expect(getSbClasses()).toContain('sb-theme--dark')
38
+ expect(getSbClasses()).toContain('sb-sidebar--collapsed')
39
+ })
40
+
41
+ it('removes stale classes when overrides are cleared', () => {
42
+ window.location.hash = '#theme=dark&sidebar=collapsed'
43
+ syncOverrideClasses()
44
+ expect(getSbClasses()).toContain('sb-theme--dark')
45
+
46
+ window.location.hash = '#sidebar=collapsed'
47
+ syncOverrideClasses()
48
+ expect(getSbClasses()).not.toContain('sb-theme--dark')
49
+ expect(getSbClasses()).toContain('sb-sidebar--collapsed')
50
+ })
51
+
52
+ it('removes all override classes when hash is empty', () => {
53
+ window.location.hash = '#theme=dark'
54
+ syncOverrideClasses()
55
+ expect(getSbClasses()).toContain('sb-theme--dark')
56
+
57
+ window.location.hash = ''
58
+ syncOverrideClasses()
59
+ const overrideClasses = getSbClasses().filter((c) => !c.startsWith('sb-scene--'))
60
+ expect(overrideClasses).toEqual([])
61
+ })
62
+
63
+ it('sanitizes dot-notation keys (dots become dashes)', () => {
64
+ window.location.hash = '#settings.theme=dark'
65
+ syncOverrideClasses()
66
+ expect(getSbClasses()).toContain('sb-settings-theme--dark')
67
+ })
68
+
69
+ it('sanitizes values with special characters', () => {
70
+ window.location.hash = '#mode=dark.dimmed'
71
+ syncOverrideClasses()
72
+ expect(getSbClasses()).toContain('sb-mode--dark-dimmed')
73
+ })
74
+
75
+ it('skips overrides with empty values', () => {
76
+ window.location.hash = '#theme='
77
+ syncOverrideClasses()
78
+ const overrideClasses = getSbClasses().filter((c) => !c.startsWith('sb-scene--'))
79
+ expect(overrideClasses).toEqual([])
80
+ })
81
+
82
+ it('updates classes when override value changes', () => {
83
+ window.location.hash = '#theme=dark'
84
+ syncOverrideClasses()
85
+ expect(getSbClasses()).toContain('sb-theme--dark')
86
+
87
+ window.location.hash = '#theme=light'
88
+ syncOverrideClasses()
89
+ expect(getSbClasses()).not.toContain('sb-theme--dark')
90
+ expect(getSbClasses()).toContain('sb-theme--light')
91
+ })
92
+ })
93
+
94
+ // ── Non-override sb-* classes preserved ──
95
+
96
+ describe('Non-override sb-* classes', () => {
97
+ it('does not strip sb-comment-mode during sync', () => {
98
+ document.body.classList.add('sb-comment-mode')
99
+ window.location.hash = '#theme=dark'
100
+ syncOverrideClasses()
101
+ expect(document.body.classList.contains('sb-comment-mode')).toBe(true)
102
+ expect(getSbClasses()).toContain('sb-theme--dark')
103
+ })
104
+
105
+ it('does not strip sb-ff-* feature flag classes during sync', () => {
106
+ document.body.classList.add('sb-ff-dark-mode')
107
+ syncOverrideClasses()
108
+ expect(document.body.classList.contains('sb-ff-dark-mode')).toBe(true)
109
+ })
110
+
111
+ it('still removes stale override classes', () => {
112
+ document.body.classList.add('sb-comment-mode')
113
+ window.location.hash = '#theme=dark'
114
+ syncOverrideClasses()
115
+ window.location.hash = ''
116
+ syncOverrideClasses()
117
+ expect(document.body.classList.contains('sb-comment-mode')).toBe(true)
118
+ expect(getSbClasses()).not.toContain('sb-theme--dark')
119
+ })
120
+ })
121
+
122
+ // ── Flow Classes ──
123
+
124
+ describe('Flow body classes', () => {
125
+ it('sets sb-scene-- class', () => {
126
+ setFlowClass('Dashboard')
127
+ expect(getSbClasses()).toContain('sb-scene--dashboard')
128
+ })
129
+
130
+ it('replaces previous flow class', () => {
131
+ setFlowClass('Dashboard')
132
+ setFlowClass('Settings')
133
+ expect(getSbClasses()).not.toContain('sb-scene--dashboard')
134
+ expect(getSbClasses()).toContain('sb-scene--settings')
135
+ })
136
+
137
+ it('removes flow class when called with empty string', () => {
138
+ setFlowClass('Dashboard')
139
+ setFlowClass('')
140
+ const flowClasses = getSbClasses().filter((c) => c.startsWith('sb-scene--'))
141
+ expect(flowClasses).toEqual([])
142
+ })
143
+
144
+ it('does not interfere with override classes', () => {
145
+ window.location.hash = '#theme=dark'
146
+ syncOverrideClasses()
147
+ setFlowClass('Dashboard')
148
+ expect(getSbClasses()).toContain('sb-theme--dark')
149
+ expect(getSbClasses()).toContain('sb-scene--dashboard')
150
+ })
151
+ })
152
+
153
+ // ── setSceneClass (deprecated alias) ──
154
+
155
+ describe('setSceneClass (deprecated alias)', () => {
156
+ it('is the same function as setFlowClass', () => {
157
+ expect(setSceneClass).toBe(setFlowClass)
158
+ })
159
+
160
+ it('sets sb-scene-- class', () => {
161
+ setSceneClass('Dashboard')
162
+ expect(getSbClasses()).toContain('sb-scene--dashboard')
163
+ })
164
+ })
165
+
166
+ // ── Hide Mode ──
167
+
168
+ describe('Hide mode body classes', () => {
169
+ it('reflects shadow overrides as body classes', () => {
170
+ activateHideMode()
171
+ setShadow('theme', 'dark')
172
+ syncOverrideClasses()
173
+ expect(getSbClasses()).toContain('sb-theme--dark')
174
+ })
175
+ })
176
+
177
+ // ── installBodyClassSync ──
178
+
179
+ describe('installBodyClassSync', () => {
180
+ it('runs initial sync on install', () => {
181
+ window.location.hash = '#layout=compact'
182
+ const unsub = installBodyClassSync()
183
+ expect(getSbClasses()).toContain('sb-layout--compact')
184
+ unsub()
185
+ })
186
+
187
+ it('returns an unsubscribe function', () => {
188
+ const unsub = installBodyClassSync()
189
+ expect(typeof unsub).toBe('function')
190
+ unsub()
191
+ })
192
+ })