@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,53 @@
1
+ /**
2
+ * Canvas write guard — coordinates the canvas server with Vite's file watcher
3
+ * to prevent duplicate HMR events.
4
+ *
5
+ * When the canvas server writes to a .canvas.jsonl file, it immediately pushes
6
+ * an HMR event via pushCanvasUpdate(). Vite's file watcher also detects the
7
+ * change ~100-500ms later and the data plugin sends a second HMR event. This
8
+ * duplicate event can cause visible rollbacks when the client has made new edits
9
+ * in the intervening window.
10
+ *
11
+ * The guard tracks files currently being written by the canvas server. The data
12
+ * plugin checks this before sending watcher-triggered HMR events and skips them
13
+ * if the server has already pushed.
14
+ *
15
+ * Uses globalThis with a Symbol key to guarantee the same Map instance is shared
16
+ * across all import paths (e.g., relative imports from server.js vs package
17
+ * imports from data-plugin.js).
18
+ */
19
+
20
+ const KEY = Symbol.for('sb:canvasWriteGuard')
21
+ if (!globalThis[KEY]) globalThis[KEY] = new Map()
22
+
23
+ /** @type {Map<string, number>} filePath → active write count */
24
+ const guard = globalThis[KEY]
25
+
26
+ /**
27
+ * Mark a file as being written by the canvas server.
28
+ * Call before appendEvent / writeFileSync.
29
+ */
30
+ export function markCanvasWrite(filePath) {
31
+ guard.set(filePath, (guard.get(filePath) || 0) + 1)
32
+ }
33
+
34
+ /**
35
+ * Unmark a file after the watcher has had time to fire.
36
+ * Call via setTimeout after the write + push completes.
37
+ */
38
+ export function unmarkCanvasWrite(filePath) {
39
+ const count = (guard.get(filePath) || 1) - 1
40
+ if (count <= 0) guard.delete(filePath)
41
+ else guard.set(filePath, count)
42
+ }
43
+
44
+ /**
45
+ * Check if a file is currently being written by the canvas server.
46
+ * The data plugin uses this to skip duplicate watcher-triggered HMR events.
47
+ */
48
+ export function isCanvasWriteInFlight(filePath) {
49
+ return guard.has(filePath)
50
+ }
51
+
52
+ // Legacy export for backward compatibility
53
+ export const canvasWritesInFlight = guard
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * storyboard agent signal — signal agent status to the canvas server.
4
+ *
5
+ * Usage:
6
+ * npx storyboard agent signal --widget <id> --canvas <canvasId> --status done|error|running [--message "..."]
7
+ *
8
+ * Environment variables (auto-set by terminal server):
9
+ * STORYBOARD_WIDGET_ID — widget ID (fallback if --widget not provided)
10
+ * STORYBOARD_CANVAS_ID — canvas ID (fallback if --canvas not provided)
11
+ * STORYBOARD_SERVER_URL — server base URL
12
+ * STORYBOARD_BRANCH — current branch
13
+ */
14
+
15
+ import { parseFlags } from './flags.js'
16
+ import { dim, bold, cyan, yellow } from './intro.js'
17
+
18
+ const subcommand = process.argv[3]
19
+
20
+ if (subcommand === 'signal') {
21
+ const flagSchema = {
22
+ widget: { type: 'string', description: 'Widget ID' },
23
+ canvas: { type: 'string', description: 'Canvas ID' },
24
+ status: { type: 'string', required: true, description: 'Status: done, error, or running' },
25
+ message: { type: 'string', description: 'Optional status message' },
26
+ }
27
+
28
+ const { flags } = parseFlags(process.argv.slice(4), flagSchema)
29
+
30
+ const widgetId = flags.widget || process.env.STORYBOARD_WIDGET_ID
31
+ const canvasId = flags.canvas || process.env.STORYBOARD_CANVAS_ID
32
+ const status = flags.status
33
+ const message = flags.message || null
34
+ const serverUrl = process.env.STORYBOARD_SERVER_URL || 'http://localhost:1234'
35
+ const branch = process.env.STORYBOARD_BRANCH || 'unknown'
36
+
37
+ if (!widgetId || !canvasId || !status) {
38
+ console.error(`${bold('Usage:')} npx storyboard agent signal --status done|error|running`)
39
+ console.error(`${dim('Widget and canvas IDs are read from environment if not provided.')}`)
40
+ process.exit(1)
41
+ }
42
+
43
+ const validStatuses = ['done', 'error', 'running']
44
+ if (!validStatuses.includes(status)) {
45
+ console.error(`${bold('Error:')} Status must be one of: ${validStatuses.join(', ')}`)
46
+ process.exit(1)
47
+ }
48
+
49
+ try {
50
+ const url = `${serverUrl}/_storyboard/canvas/agent/signal`
51
+ const res = await fetch(url, {
52
+ method: 'POST',
53
+ headers: { 'Content-Type': 'application/json' },
54
+ body: JSON.stringify({ widgetId, canvasId, branch, status, message }),
55
+ })
56
+
57
+ if (res.ok) {
58
+ console.log(`${cyan('✓')} Agent status: ${bold(status)}${message ? ` — ${message}` : ''}`)
59
+ } else {
60
+ const data = await res.json().catch(() => ({}))
61
+ console.error(`${yellow('⚠')} Server returned ${res.status}: ${data.error || 'unknown error'}`)
62
+ // Fallback: write directly to terminal config
63
+ await fallbackWrite({ branch, canvasId, widgetId, status, message })
64
+ }
65
+ } catch {
66
+ // Server not reachable — write directly to terminal config file
67
+ await fallbackWrite({ branch, canvasId, widgetId, status, message })
68
+ }
69
+ } else {
70
+ console.error(`${bold('Usage:')} npx storyboard agent <signal>`)
71
+ console.error(`${dim('Subcommands: signal')}`)
72
+ process.exit(1)
73
+ }
74
+
75
+ async function fallbackWrite({ branch, canvasId, widgetId, status, message }) {
76
+ try {
77
+ const { updateAgentStatus, initTerminalConfig } = await import('../canvas/terminal-config.js')
78
+ initTerminalConfig(process.cwd())
79
+ updateAgentStatus({ branch, canvasId, widgetId, status, message })
80
+ console.log(`${cyan('✓')} Agent status written to config file (server offline): ${bold(status)}`)
81
+ } catch (err) {
82
+ console.error(`Failed to write agent status: ${err.message}`)
83
+ process.exit(1)
84
+ }
85
+ }
@@ -0,0 +1,386 @@
1
+ /**
2
+ * storyboard branch — Interactive guide to switch to a branch worktree.
3
+ *
4
+ * Deterministic flow (no AI):
5
+ * 1. Ask which branch to work on (or accept --worktree flag)
6
+ * 2. If existing worktree:
7
+ * a. Stash uncommitted work in source (named stash)
8
+ * b. Ensure target is on the correct branch
9
+ * c. Apply source stash in target
10
+ * 3. If new worktree:
11
+ * a. Stash uncommitted work in source
12
+ * b. Create worktree (git worktree add + npm install)
13
+ * c. Pull --rebase from origin
14
+ * d. Apply source stash in target
15
+ * 4. Confirm to user
16
+ *
17
+ * Also available as post-setup prompt in setup.js.
18
+ *
19
+ * Usage:
20
+ * npx storyboard branch # interactive
21
+ * npx storyboard branch <name> # positional, skip prompt
22
+ * npx storyboard branch --worktree=<name> # non-interactive flag
23
+ */
24
+
25
+ import * as p from '@clack/prompts'
26
+ import { execFileSync } from 'child_process'
27
+ import { existsSync } from 'fs'
28
+ import { resolve } from 'path'
29
+ import { repoRoot, worktreeDir, listWorktrees, getPort, detectWorktreeName } from '../worktree/port.js'
30
+ import { hasUncommittedChanges, localBranchExists } from './dev-helpers.js'
31
+ import { parseFlags } from './flags.js'
32
+ import { dim, green, bold, cyan } from './intro.js'
33
+
34
+ const flagSchema = {
35
+ worktree: { type: 'string', description: 'Target worktree/branch name (non-interactive)' },
36
+ cd: { type: 'boolean', default: false, description: 'Output shell-evaluable cd command (for eval)' },
37
+ }
38
+
39
+ /** Check if a remote branch exists on origin. */
40
+ function remoteBranchExists(name, cwd) {
41
+ try {
42
+ const result = execFileSync('git', ['ls-remote', '--exit-code', '--heads', 'origin', name], { cwd, encoding: 'utf8' })
43
+ return result.trim().length > 0
44
+ } catch {
45
+ return false
46
+ }
47
+ }
48
+
49
+ /** Get the current branch name in a given directory. */
50
+ function currentBranch(cwd) {
51
+ try {
52
+ return execFileSync('git', ['branch', '--show-current'], { cwd, encoding: 'utf8' }).trim()
53
+ } catch {
54
+ return 'unknown'
55
+ }
56
+ }
57
+
58
+ /** Validate a branch name for git. */
59
+ function isValidBranchName(name) {
60
+ if (!name || name.trim().length === 0) return 'Branch name cannot be empty'
61
+ const n = name.trim()
62
+ if (/\s/.test(n)) return 'Branch name cannot contain spaces'
63
+ if (/\.\./.test(n)) return 'Branch name cannot contain ".."'
64
+ if (/[~^:\\]/.test(n)) return 'Branch name cannot contain ~, ^, :, or \\'
65
+ if (n.startsWith('-')) return 'Branch name cannot start with "-"'
66
+ if (n.endsWith('.lock')) return 'Branch name cannot end with ".lock"'
67
+ return undefined
68
+ }
69
+
70
+ /** Build a timestamped stash message. */
71
+ function stashMessage(from, to) {
72
+ const ts = new Date().toISOString().replace(/[:.]/g, '-')
73
+ return `from-${from}-to-${to}-${ts}`
74
+ }
75
+
76
+ /**
77
+ * Stash uncommitted changes (including untracked files) and return the stash ref SHA.
78
+ * Returns null if nothing was stashed.
79
+ */
80
+ function stashChanges(cwd, message) {
81
+ if (!hasUncommittedChanges(cwd)) return null
82
+
83
+ p.log.step('Stashing uncommitted work…')
84
+ try {
85
+ execFileSync('git', ['stash', 'push', '-u', '-m', message], { cwd, stdio: 'pipe' })
86
+ // Capture the exact stash SHA so we can apply it by ref later
87
+ const sha = execFileSync('git', ['stash', 'list', '--format=%H', '-1'], { cwd, encoding: 'utf8' }).trim()
88
+ p.log.success(`Work stashed: ${dim(message)}`)
89
+ return sha
90
+ } catch {
91
+ p.log.warning('Could not stash changes — proceeding anyway')
92
+ return null
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Apply a specific stash by SHA in the given directory.
98
+ * Uses apply (not pop) so the backup remains in the stash list.
99
+ */
100
+ function applyStash(cwd, stashSha) {
101
+ try {
102
+ execFileSync('git', ['stash', 'apply', stashSha], { cwd, stdio: 'pipe' })
103
+ p.log.success('Previous work applied to this branch')
104
+ return true
105
+ } catch {
106
+ p.log.warning('Stash apply had conflicts — resolve them manually')
107
+ p.log.info(` Your work is safe in ${dim('git stash list')}`)
108
+ return false
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Core logic for switching to an EXISTING worktree.
114
+ *
115
+ * 1. Stash source changes (if any)
116
+ * 2. Ensure target worktree is on the expected branch
117
+ * 3. Apply source stash in target
118
+ */
119
+ function switchToExistingWorktree(targetBranch, { sourceDir, fromBranch }) {
120
+ const targetDir = worktreeDir(targetBranch)
121
+
122
+ // 1. Stash source changes
123
+ const sourceStashSha = stashChanges(sourceDir, stashMessage(fromBranch, targetBranch))
124
+
125
+ // 2. Ensure target is on the correct branch
126
+ const targetCurrentBranch = currentBranch(targetDir)
127
+
128
+ if (targetCurrentBranch !== targetBranch) {
129
+ // Target worktree is on a different branch — check if it's clean
130
+ if (hasUncommittedChanges(targetDir)) {
131
+ p.log.error(
132
+ `Worktree ${bold(targetBranch)} is on branch ${bold(targetCurrentBranch)} with uncommitted changes.`
133
+ )
134
+ p.log.info(` Clean up the worktree first:`)
135
+ p.log.info(` ${green('cd')} ${dim(`worktrees/${targetBranch}`)}`)
136
+ p.log.info(` ${dim('git stash')} ${dim('or')} ${dim('git commit')}`)
137
+ p.log.info(` ${dim(`git checkout ${targetBranch}`)}`)
138
+ if (sourceStashSha) {
139
+ p.log.info(`\n Your source stash is safe — apply it later with:`)
140
+ p.log.info(` ${dim(`git stash apply ${sourceStashSha.slice(0, 8)}`)}`)
141
+ }
142
+ p.outro('')
143
+ process.exit(1)
144
+ }
145
+
146
+ // Clean worktree on wrong branch — switch it
147
+ p.log.step(`Switching worktree from ${bold(targetCurrentBranch)} to ${bold(targetBranch)}…`)
148
+ try {
149
+ execFileSync('git', ['checkout', targetBranch], { cwd: targetDir, stdio: 'pipe' })
150
+ p.log.success(`Now on branch ${bold(targetBranch)}`)
151
+ } catch (err) {
152
+ p.log.error(`Could not switch branch: ${err.message || 'git checkout failed'}`)
153
+ if (sourceStashSha) {
154
+ p.log.info(` Your source stash is safe: ${dim(`git stash apply ${sourceStashSha.slice(0, 8)}`)}`)
155
+ }
156
+ p.outro('')
157
+ process.exit(1)
158
+ }
159
+ } else {
160
+ p.log.success(`Worktree ${bold(targetBranch)} is on the correct branch`)
161
+ }
162
+
163
+ // 3. Apply source stash in target
164
+ if (sourceStashSha) {
165
+ if (hasUncommittedChanges(targetDir)) {
166
+ p.log.warning(`Target worktree has uncommitted changes — skipping stash apply`)
167
+ p.log.info(` Apply your stash manually: ${dim(`git stash apply ${sourceStashSha.slice(0, 8)}`)}`)
168
+ } else {
169
+ applyStash(targetDir, sourceStashSha)
170
+ }
171
+ }
172
+
173
+ // 4. Summary
174
+ const lines = [
175
+ ` Worktree ready: ${green(`worktrees/${targetBranch}`)}`,
176
+ ]
177
+ if (sourceStashSha) {
178
+ lines.push(` Your uncommitted work has been safely moved`)
179
+ }
180
+ lines.push('')
181
+ lines.push(` ${green('cd')} ${dim(`worktrees/${targetBranch}`)}`)
182
+ lines.push(` ${green('npx storyboard dev')} ${dim('to start developing')}`)
183
+ lines.push('')
184
+ lines.push(` ${dim('Tip: auto-cd with')} ${green('eval "$(npx sb branch --cd)"')}`)
185
+
186
+ p.note(lines.join('\n'), `Branch ${bold(targetBranch)} is ready`)
187
+ p.outro('')
188
+ return targetDir
189
+ }
190
+
191
+ /**
192
+ * Core logic for creating a NEW worktree.
193
+ *
194
+ * 1. Stash source changes
195
+ * 2. Resolve branch (local/remote/new)
196
+ * 3. Create worktree + npm install
197
+ * 4. Pull --rebase
198
+ * 5. Apply source stash
199
+ */
200
+ function createNewWorktree(targetBranch, { sourceDir, fromBranch, root }) {
201
+ // 1. Stash source changes
202
+ const sourceStashSha = stashChanges(sourceDir, stashMessage(fromBranch, targetBranch))
203
+
204
+ // 2. Resolve branch
205
+ const hasLocal = localBranchExists(targetBranch, root)
206
+ const hasRemote = !hasLocal && remoteBranchExists(targetBranch, root)
207
+ const isNew = !hasLocal && !hasRemote
208
+
209
+ if (isNew) {
210
+ p.log.step(`Creating new branch ${bold(targetBranch)} from ${bold(fromBranch)}`)
211
+ } else if (hasRemote) {
212
+ p.log.step(`Fetching ${bold(targetBranch)} from origin…`)
213
+ try {
214
+ execFileSync('git', ['fetch', 'origin', targetBranch], { cwd: root, stdio: 'pipe' })
215
+ try {
216
+ execFileSync('git', ['branch', targetBranch, `origin/${targetBranch}`], { cwd: root, stdio: 'pipe' })
217
+ } catch { /* may already exist after fetch */ }
218
+ } catch {
219
+ p.log.warning('Could not fetch from origin — using local state')
220
+ }
221
+ } else {
222
+ p.log.step(`Using existing branch ${bold(targetBranch)}`)
223
+ }
224
+
225
+ // 3. Create the worktree
226
+ const targetDir = resolve(root, 'worktrees', targetBranch)
227
+ const spin = p.spinner()
228
+
229
+ try {
230
+ // For new branches, use current branch as start-point (not main's HEAD)
231
+ const gitArgs = isNew
232
+ ? ['worktree', 'add', targetDir, '-b', targetBranch, fromBranch]
233
+ : ['worktree', 'add', targetDir, targetBranch]
234
+
235
+ spin.start(`Creating worktree worktrees/${targetBranch}`)
236
+ execFileSync('git', gitArgs, { cwd: root, stdio: 'pipe' })
237
+ spin.stop(`Worktree created: worktrees/${targetBranch}`)
238
+ } catch (err) {
239
+ spin.stop('Failed to create worktree')
240
+ p.log.error(err.message || 'git worktree add failed')
241
+ process.exit(1)
242
+ }
243
+
244
+ // Install dependencies
245
+ try {
246
+ spin.start('Installing dependencies…')
247
+ const npmBin = process.platform === 'win32' ? 'npm.cmd' : 'npm'
248
+ execFileSync(npmBin, ['install'], { cwd: targetDir, stdio: 'pipe' })
249
+ spin.stop('Dependencies installed')
250
+ } catch {
251
+ spin.stop('npm install failed — you may need to run it manually')
252
+ }
253
+
254
+ // Assign a dev server port
255
+ getPort(targetBranch)
256
+
257
+ // 4. Pull --rebase from origin
258
+ if (!isNew) {
259
+ try {
260
+ spin.start('Pulling latest changes…')
261
+ execFileSync('git', ['pull', '--rebase', 'origin', targetBranch], { cwd: targetDir, stdio: 'pipe' })
262
+ spin.stop('Up to date with origin')
263
+ } catch {
264
+ spin.stop(dim('No remote changes (or origin not available)'))
265
+ }
266
+ }
267
+
268
+ // 5. Apply source stash
269
+ if (sourceStashSha) {
270
+ applyStash(targetDir, sourceStashSha)
271
+ }
272
+
273
+ // 6. Summary
274
+ const lines = [
275
+ ` Your branch is set up as a worktree in ${green(`worktrees/${targetBranch}`)}`,
276
+ ]
277
+ if (sourceStashSha) {
278
+ lines.push(` Your uncommitted work has been safely moved`)
279
+ }
280
+ lines.push('')
281
+ lines.push(` ${green('cd')} ${dim(`worktrees/${targetBranch}`)}`)
282
+ lines.push(` ${green('npx storyboard dev')} ${dim('to start developing')}`)
283
+ lines.push('')
284
+ lines.push(` ${dim('Tip: auto-cd with')} ${green('eval "$(npx sb branch --cd)"')}`)
285
+
286
+ p.note(lines.join('\n'), `Branch ${bold(targetBranch)} is ready`)
287
+ p.outro('')
288
+ return targetDir
289
+ }
290
+
291
+ // ─── Main ───
292
+
293
+ export async function runBranchGuide(branchArg) {
294
+ const root = repoRoot()
295
+ const existing = listWorktrees()
296
+ const fromWorktree = detectWorktreeName()
297
+ const sourceDir = fromWorktree === 'main' ? root : worktreeDir(fromWorktree)
298
+ const fromBranch = currentBranch(sourceDir)
299
+
300
+ // 1. Get branch name — select from existing or type a new one
301
+ let targetBranch = branchArg?.trim()
302
+
303
+ if (!targetBranch) {
304
+ if (existing.length > 0) {
305
+ // Build select options from existing worktrees + "new branch" option
306
+ const NEW_BRANCH = Symbol('new')
307
+ const options = [
308
+ ...existing.map(name => ({ value: name, label: name })),
309
+ { value: NEW_BRANCH, label: dim('Create a new branch…') },
310
+ ]
311
+
312
+ const selected = await p.select({
313
+ message: 'Which branch do you want to work on?',
314
+ options,
315
+ })
316
+
317
+ if (p.isCancel(selected)) {
318
+ p.cancel('Cancelled')
319
+ process.exit(0)
320
+ }
321
+
322
+ if (selected === NEW_BRANCH) {
323
+ const newName = await p.text({
324
+ message: 'New branch name:',
325
+ placeholder: 'e.g. 4.3.0--my-feature',
326
+ validate: isValidBranchName,
327
+ })
328
+ if (p.isCancel(newName)) {
329
+ p.cancel('Cancelled')
330
+ process.exit(0)
331
+ }
332
+ targetBranch = newName.trim()
333
+ } else {
334
+ targetBranch = selected
335
+ }
336
+ } else {
337
+ // No existing worktrees — just ask for a name
338
+ const result = await p.text({
339
+ message: 'Branch name for new worktree:',
340
+ placeholder: 'e.g. 4.3.0--my-feature',
341
+ validate: isValidBranchName,
342
+ })
343
+
344
+ if (p.isCancel(result)) {
345
+ p.cancel('Cancelled')
346
+ process.exit(0)
347
+ }
348
+ targetBranch = result.trim()
349
+ }
350
+ }
351
+
352
+ // 2. Show equivalent non-interactive command (when user used TUI to select)
353
+ if (!branchArg) {
354
+ p.log.info(`${dim('Non-interactive:')} ${green(`npx sb branch --worktree=${targetBranch}`)}`)
355
+ }
356
+
357
+ // 3. Route to existing or new worktree flow
358
+ const wtDir = worktreeDir(targetBranch)
359
+ if (existsSync(resolve(wtDir, '.git'))) {
360
+ return switchToExistingWorktree(targetBranch, { sourceDir, fromBranch })
361
+ } else {
362
+ return createNewWorktree(targetBranch, { sourceDir, fromBranch, root })
363
+ }
364
+ }
365
+
366
+ // ─── Direct invocation ───
367
+
368
+ const { flags, positional } = parseFlags(process.argv.slice(3), flagSchema)
369
+ const branchArg = flags.worktree || positional[0] || undefined
370
+
371
+ // When --cd is set, redirect all TUI output (Clack) to stderr so that
372
+ // stdout contains only the shell-evaluable `cd <path>` command.
373
+ // Usage: eval "$(npx sb branch --worktree=<name> --cd)"
374
+ const realStdoutWrite = process.stdout.write.bind(process.stdout)
375
+ if (flags.cd) {
376
+ process.stdout.write = (chunk, encoding, callback) =>
377
+ process.stderr.write(chunk, encoding, callback)
378
+ }
379
+
380
+ p.intro('storyboard branch')
381
+ runBranchGuide(branchArg).then((targetDir) => {
382
+ if (flags.cd && targetDir) {
383
+ process.stdout.write = realStdoutWrite
384
+ realStdoutWrite(`cd ${JSON.stringify(targetDir)}\n`)
385
+ }
386
+ })