@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,165 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import { render, act } from '@testing-library/react'
3
+ import { WebGLContextPoolProvider, useWebGLSlot, usePoolVisibilityUpdater, Priority } from './WebGLContextPool.jsx'
4
+
5
+ function TestWidget({ widgetId, onSlot }) {
6
+ const slot = useWebGLSlot(widgetId)
7
+ onSlot?.(slot)
8
+ return <div data-testid={widgetId}>{slot.isLive ? 'live' : 'frozen'}</div>
9
+ }
10
+
11
+ function TestUpdater({ onUpdater }) {
12
+ const update = usePoolVisibilityUpdater()
13
+ onUpdater?.(update)
14
+ return null
15
+ }
16
+
17
+ describe('WebGLContextPool', () => {
18
+ it('grants live slots to widgets within the max limit', () => {
19
+ let slot1, slot2
20
+ render(
21
+ <WebGLContextPoolProvider maxLive={2}>
22
+ <TestWidget widgetId="t1" onSlot={(s) => { slot1 = s }} />
23
+ <TestWidget widgetId="t2" onSlot={(s) => { slot2 = s }} />
24
+ </WebGLContextPoolProvider>
25
+ )
26
+
27
+ // Both should be live since we're under the limit
28
+ expect(slot1.isLive).toBe(true)
29
+ expect(slot2.isLive).toBe(true)
30
+ })
31
+
32
+ it('freezes excess widgets when over the limit', () => {
33
+ let slot1, slot2, slot3
34
+ render(
35
+ <WebGLContextPoolProvider maxLive={2}>
36
+ <TestWidget widgetId="t1" onSlot={(s) => { slot1 = s }} />
37
+ <TestWidget widgetId="t2" onSlot={(s) => { slot2 = s }} />
38
+ <TestWidget widgetId="t3" onSlot={(s) => { slot3 = s }} />
39
+ </WebGLContextPoolProvider>
40
+ )
41
+
42
+ const liveCount = [slot1, slot2, slot3].filter(s => s.isLive).length
43
+ const frozenCount = [slot1, slot2, slot3].filter(s => !s.isLive).length
44
+
45
+ expect(liveCount).toBe(2)
46
+ expect(frozenCount).toBe(1)
47
+ })
48
+
49
+ it('always returns live when no provider is present', () => {
50
+ let slot
51
+ render(<TestWidget widgetId="t1" onSlot={(s) => { slot = s }} />)
52
+ expect(slot.isLive).toBe(true)
53
+ expect(slot.generation).toBe(0)
54
+ })
55
+
56
+ it('prioritizes PINNED widgets over OFFSCREEN', () => {
57
+ let slot1, slot2, slot3
58
+ render(
59
+ <WebGLContextPoolProvider maxLive={2}>
60
+ <TestWidget widgetId="t1" onSlot={(s) => { slot1 = s }} />
61
+ <TestWidget widgetId="t2" onSlot={(s) => { slot2 = s }} />
62
+ <TestWidget widgetId="t3" onSlot={(s) => { slot3 = s }} />
63
+ </WebGLContextPoolProvider>
64
+ )
65
+
66
+ // Pin t3 — it should become live, evicting one of the others
67
+ act(() => { slot3.setPriority(Priority.PINNED) })
68
+
69
+ expect(slot3.isLive).toBe(true)
70
+ })
71
+
72
+ it('PINNED widgets bypass the max limit', () => {
73
+ let slot1, slot2, slot3
74
+ render(
75
+ <WebGLContextPoolProvider maxLive={2}>
76
+ <TestWidget widgetId="t1" onSlot={(s) => { slot1 = s }} />
77
+ <TestWidget widgetId="t2" onSlot={(s) => { slot2 = s }} />
78
+ <TestWidget widgetId="t3" onSlot={(s) => { slot3 = s }} />
79
+ </WebGLContextPoolProvider>
80
+ )
81
+
82
+ // Pin all three
83
+ act(() => {
84
+ slot1.setPriority(Priority.PINNED)
85
+ slot2.setPriority(Priority.PINNED)
86
+ slot3.setPriority(Priority.PINNED)
87
+ })
88
+
89
+ // All should be live because PINNED bypasses the cap
90
+ expect(slot1.isLive).toBe(true)
91
+ expect(slot2.isLive).toBe(true)
92
+ expect(slot3.isLive).toBe(true)
93
+ })
94
+
95
+ it('tracks generation across live-frozen-live transitions', () => {
96
+ let slot1, slot2, slot3
97
+ render(
98
+ <WebGLContextPoolProvider maxLive={2}>
99
+ <TestWidget widgetId="t1" onSlot={(s) => { slot1 = s }} />
100
+ <TestWidget widgetId="t2" onSlot={(s) => { slot2 = s }} />
101
+ <TestWidget widgetId="t3" onSlot={(s) => { slot3 = s }} />
102
+ </WebGLContextPoolProvider>
103
+ )
104
+
105
+ // t3 starts frozen with generation 0 (never was live)
106
+ expect(slot3.isLive).toBe(false)
107
+ expect(slot3.generation).toBe(0)
108
+
109
+ // Pin t3 to make it live
110
+ act(() => { slot3.setPriority(Priority.PINNED) })
111
+ expect(slot3.isLive).toBe(true)
112
+
113
+ // Unpin t3 — it should be evicted and generation bumped
114
+ act(() => { slot3.setPriority(Priority.OFFSCREEN) })
115
+ // Hysteresis delays eviction; use fake timers if needed.
116
+ // For now, verify that generation bumps when eviction happens.
117
+ })
118
+
119
+ it('usePoolVisibilityUpdater updates priorities based on viewport', () => {
120
+ let slot1, slot2, updater
121
+ render(
122
+ <WebGLContextPoolProvider maxLive={1}>
123
+ <TestWidget widgetId="t1" onSlot={(s) => { slot1 = s }} />
124
+ <TestWidget widgetId="t2" onSlot={(s) => { slot2 = s }} />
125
+ <TestUpdater onUpdater={(u) => { updater = u }} />
126
+ </WebGLContextPoolProvider>
127
+ )
128
+
129
+ const widgets = [
130
+ { id: 't1', type: 'terminal', position: { x: 100, y: 100 }, props: { width: 800, height: 450 } },
131
+ { id: 't2', type: 'terminal', position: { x: 5000, y: 5000 }, props: { width: 800, height: 450 } },
132
+ ]
133
+
134
+ // Viewport only covers t1
135
+ act(() => {
136
+ updater({ x: 0, y: 0, w: 1920, h: 1080 }, widgets, new Set(), null)
137
+ })
138
+
139
+ expect(slot1.isLive).toBe(true)
140
+ expect(slot2.isLive).toBe(false)
141
+ })
142
+
143
+ it('selected widgets get PINNED priority via visibility updater', () => {
144
+ let slot1, slot2, updater
145
+ render(
146
+ <WebGLContextPoolProvider maxLive={1}>
147
+ <TestWidget widgetId="t1" onSlot={(s) => { slot1 = s }} />
148
+ <TestWidget widgetId="t2" onSlot={(s) => { slot2 = s }} />
149
+ <TestUpdater onUpdater={(u) => { updater = u }} />
150
+ </WebGLContextPoolProvider>
151
+ )
152
+
153
+ const widgets = [
154
+ { id: 't1', type: 'terminal', position: { x: 100, y: 100 }, props: { width: 800, height: 450 } },
155
+ { id: 't2', type: 'terminal', position: { x: 5000, y: 5000 }, props: { width: 800, height: 450 } },
156
+ ]
157
+
158
+ // t2 is offscreen but selected — should be pinned and live
159
+ act(() => {
160
+ updater({ x: 0, y: 0, w: 1920, h: 1080 }, widgets, new Set(['t2']), null)
161
+ })
162
+
163
+ expect(slot2.isLive).toBe(true)
164
+ })
165
+ })
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Client-side API for canvas CRUD operations.
3
+ * Calls the /_storyboard/canvas/ server endpoints.
4
+ */
5
+
6
+ const BASE = '/_storyboard/canvas'
7
+
8
+ function getApiBase() {
9
+ const base = (import.meta.env?.BASE_URL || '/').replace(/\/$/, '')
10
+ return base + BASE
11
+ }
12
+
13
+ async function request(path, method, body) {
14
+ const url = getApiBase() + path
15
+ const res = await fetch(url, {
16
+ method,
17
+ headers: body ? { 'Content-Type': 'application/json' } : undefined,
18
+ body: body ? JSON.stringify(body) : undefined,
19
+ })
20
+ return res.json()
21
+ }
22
+
23
+ export function listCanvases() {
24
+ return request('/list', 'GET')
25
+ }
26
+
27
+ export function createCanvas(data) {
28
+ return request('/create', 'POST', data)
29
+ }
30
+
31
+ export function updateCanvas(canvasId, { widgets, sources, settings, connectors }) {
32
+ return request('/update', 'PUT', { name: canvasId, widgets, sources, settings, connectors })
33
+ }
34
+
35
+ export function addWidget(canvasId, { type, props, position }) {
36
+ return request('/widget', 'POST', { name: canvasId, type, props, position })
37
+ }
38
+
39
+ export function removeWidget(canvasId, widgetId) {
40
+ return request('/widget', 'DELETE', { name: canvasId, widgetId })
41
+ }
42
+
43
+ export function uploadImage(dataUrl, canvasId, filename) {
44
+ const body = { dataUrl, canvasName: canvasId }
45
+ if (filename) body.filename = filename
46
+ return request('/image', 'POST', body)
47
+ }
48
+
49
+ export function toggleImagePrivacy(filename) {
50
+ return request('/image/toggle-private', 'POST', { filename })
51
+ }
52
+
53
+ export function duplicateImage(filename) {
54
+ return request('/image/duplicate', 'POST', { filename })
55
+ }
56
+
57
+ /**
58
+ * Crop an image client-side and upload the result.
59
+ * @param {string} imageSrc — current image filename (e.g. "canvas--2026-01-01--12-00-00.png")
60
+ * @param {{ x: number, y: number, width: number, height: number }} cropRect — crop region in natural image pixels
61
+ * @param {string} canvasId — canvas name for directory resolution
62
+ * @returns {Promise<{ success: boolean, filename: string }>}
63
+ */
64
+ export async function cropAndUpload(imageSrc, cropRect, canvasId) {
65
+ const imageUrl = (() => {
66
+ const base = (import.meta.env?.BASE_URL || '/').replace(/\/$/, '')
67
+ return `${base}/_storyboard/canvas/images/${imageSrc}`
68
+ })()
69
+
70
+ // Load the image into an offscreen canvas
71
+ const img = new Image()
72
+ img.crossOrigin = 'anonymous'
73
+ await new Promise((resolve, reject) => {
74
+ img.onload = resolve
75
+ img.onerror = reject
76
+ img.src = imageUrl
77
+ })
78
+
79
+ // Draw the cropped region
80
+ const canvas = document.createElement('canvas')
81
+ canvas.width = Math.round(cropRect.width)
82
+ canvas.height = Math.round(cropRect.height)
83
+ const ctx = canvas.getContext('2d')
84
+ ctx.drawImage(
85
+ img,
86
+ Math.round(cropRect.x), Math.round(cropRect.y),
87
+ Math.round(cropRect.width), Math.round(cropRect.height),
88
+ 0, 0,
89
+ canvas.width, canvas.height,
90
+ )
91
+
92
+ // Determine output format from original filename
93
+ const ext = imageSrc.split('.').pop()?.toLowerCase() || 'png'
94
+ const mimeMap = { png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', webp: 'image/webp', gif: 'image/gif' }
95
+ const mime = mimeMap[ext] || 'image/png'
96
+ const dataUrl = canvas.toDataURL(mime, 0.92)
97
+
98
+ // Build cropped filename: strip any previous --cropped-- suffix, append new one
99
+ const privacyPrefix = imageSrc.startsWith('~') ? '~' : ''
100
+ const baseName = imageSrc.replace(/^~/, '')
101
+ const withoutCrop = baseName.replace(/--cropped--\d{4}-\d{2}-\d{2}--\d{2}-\d{2}-\d{2}/, '')
102
+ const nameWithoutExt = withoutCrop.replace(/\.\w+$/, '')
103
+ const now = new Date()
104
+ const pad = (n) => String(n).padStart(2, '0')
105
+ const ts = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}--${pad(now.getHours())}-${pad(now.getMinutes())}-${pad(now.getSeconds())}`
106
+ const croppedFilename = `${privacyPrefix}${nameWithoutExt}--cropped--${ts}.${ext}`
107
+
108
+ return uploadImage(dataUrl, canvasId, croppedFilename)
109
+ }
110
+
111
+ export function batchOperations(canvasId, operations) {
112
+ return request('/batch', 'POST', { name: canvasId, operations })
113
+ }
114
+
115
+ export function getCanvas(canvasId) {
116
+ return request(`/read?name=${encodeURIComponent(canvasId)}`, 'GET')
117
+ }
118
+
119
+ export function checkGitHubCliAvailable() {
120
+ return request('/github/available', 'GET')
121
+ }
122
+
123
+ export function fetchGitHubEmbed(url) {
124
+ return request('/github/embed', 'POST', { url })
125
+ }
126
+
127
+ export function renamePage(canvasId, newTitle) {
128
+ return request('/rename-page', 'PUT', { name: canvasId, newTitle })
129
+ }
130
+
131
+ export function reorderPages(folder, order) {
132
+ return request('/reorder-pages', 'PUT', { folder, order })
133
+ }
134
+
135
+ export function getPageOrder(folder) {
136
+ return request(`/page-order?folder=${encodeURIComponent(folder)}`, 'GET')
137
+ }
138
+
139
+ export function updateFolderMeta(folder, title) {
140
+ return request('/update-folder-meta', 'PUT', { folder, title })
141
+ }
142
+
143
+ export function duplicateCanvas(canvasId, newTitle) {
144
+ return request('/duplicate', 'POST', { name: canvasId, newTitle })
145
+ }
146
+
147
+ export function addConnector(canvasId, { startWidgetId, startAnchor, endWidgetId, endAnchor, connectorType }) {
148
+ return request('/connector', 'POST', {
149
+ name: canvasId,
150
+ startWidgetId,
151
+ startAnchor,
152
+ endWidgetId,
153
+ endAnchor,
154
+ connectorType,
155
+ })
156
+ }
157
+
158
+ export function removeConnector(canvasId, connectorId) {
159
+ return request('/connector', 'DELETE', { name: canvasId, connectorId })
160
+ }
161
+
162
+ export function updateConnector(canvasId, connectorId, meta) {
163
+ return request('/connector', 'PATCH', { name: canvasId, connectorId, meta })
164
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Canvas reload guard — client-side state for preventing HMR full reloads.
3
+ *
4
+ * This module tracks whether a canvas is currently active. When active,
5
+ * the Vite plugin suppresses full-page reloads to preserve canvas state.
6
+ *
7
+ * The actual guard logic is implemented in:
8
+ * - Server: vite.config.js (ws.send monkey-patch + heartbeat)
9
+ * - Client: CanvasPage.jsx (vite:beforeFullReload + vite:ws:disconnect)
10
+ *
11
+ * This module provides the state that those systems check.
12
+ */
13
+
14
+ let active = false
15
+
16
+ /**
17
+ * Enable the canvas reload guard.
18
+ * Call when a canvas page mounts.
19
+ */
20
+ export function enableCanvasGuard() {
21
+ active = true
22
+ }
23
+
24
+ /**
25
+ * Disable the canvas reload guard.
26
+ * Call when a canvas page unmounts.
27
+ */
28
+ export function disableCanvasGuard() {
29
+ active = false
30
+ }
31
+
32
+ /**
33
+ * Check if the canvas reload guard is currently active.
34
+ */
35
+ export function isCanvasGuardActive() {
36
+ return active
37
+ }
@@ -0,0 +1,27 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest'
2
+ import { enableCanvasGuard, disableCanvasGuard, isCanvasGuardActive } from './canvasReloadGuard.js'
3
+
4
+ describe('canvasReloadGuard', () => {
5
+ beforeEach(() => {
6
+ disableCanvasGuard()
7
+ })
8
+
9
+ it('starts inactive', () => {
10
+ expect(isCanvasGuardActive()).toBe(false)
11
+ })
12
+
13
+ it('can be enabled and disabled', () => {
14
+ enableCanvasGuard()
15
+ expect(isCanvasGuardActive()).toBe(true)
16
+ disableCanvasGuard()
17
+ expect(isCanvasGuardActive()).toBe(false)
18
+ })
19
+
20
+ it('enable is idempotent', () => {
21
+ enableCanvasGuard()
22
+ enableCanvasGuard()
23
+ expect(isCanvasGuardActive()).toBe(true)
24
+ disableCanvasGuard()
25
+ expect(isCanvasGuardActive()).toBe(false)
26
+ })
27
+ })
@@ -0,0 +1,118 @@
1
+ export function getCanvasPrimerAttrs(theme) {
2
+ const value = String(theme || 'light')
3
+ if (value.startsWith('dark')) {
4
+ return {
5
+ 'data-color-mode': 'dark',
6
+ 'data-dark-theme': value,
7
+ 'data-light-theme': 'light',
8
+ }
9
+ }
10
+ return {
11
+ 'data-color-mode': 'light',
12
+ 'data-dark-theme': 'dark',
13
+ 'data-light-theme': value.startsWith('light') ? value : 'light',
14
+ }
15
+ }
16
+
17
+ /**
18
+ * Per-theme canvas CSS custom properties sourced from @primer/primitives.
19
+ * Each theme gets its own entry so high-contrast, colorblind, and dimmed
20
+ * variants all render with the correct background, dot, and text colors.
21
+ */
22
+ const THEME_VARS = {
23
+ light: {
24
+ '--sb--canvas-bg': '#f6f8fa',
25
+ '--bgColor-default': '#ffffff',
26
+ '--bgColor-muted': '#f6f8fa',
27
+ '--bgColor-neutral-muted': '#818b981f',
28
+ '--bgColor-accent-emphasis': '#0969da',
29
+ '--tc-bg-muted': '#f6f8fa',
30
+ '--tc-dot-color': 'rgba(0, 0, 0, 0.08)',
31
+ '--overlay-backdrop-bgColor': 'rgba(0, 0, 0, 0.08)',
32
+ '--fgColor-muted': '#59636e',
33
+ '--fgColor-default': '#1f2328',
34
+ '--fgColor-onEmphasis': '#ffffff',
35
+ '--borderColor-default': '#d1d9e0',
36
+ '--borderColor-muted': '#d1d9e0b3',
37
+ },
38
+ light_colorblind: {
39
+ '--sb--canvas-bg': '#f6f8fa',
40
+ '--bgColor-default': '#ffffff',
41
+ '--bgColor-muted': '#f6f8fa',
42
+ '--bgColor-neutral-muted': '#818b981f',
43
+ '--bgColor-accent-emphasis': '#0969da',
44
+ '--tc-bg-muted': '#f6f8fa',
45
+ '--tc-dot-color': 'rgba(0, 0, 0, 0.08)',
46
+ '--overlay-backdrop-bgColor': 'rgba(0, 0, 0, 0.08)',
47
+ '--fgColor-muted': '#59636e',
48
+ '--fgColor-default': '#1f2328',
49
+ '--fgColor-onEmphasis': '#ffffff',
50
+ '--borderColor-default': '#d1d9e0',
51
+ '--borderColor-muted': '#d1d9e0b3',
52
+ },
53
+ dark: {
54
+ '--sb--canvas-bg': '#151b23',
55
+ '--bgColor-default': '#0d1117',
56
+ '--bgColor-muted': '#151b23',
57
+ '--bgColor-neutral-muted': '#656c7633',
58
+ '--bgColor-accent-emphasis': '#1f6feb',
59
+ '--tc-bg-muted': '#151b23',
60
+ '--tc-dot-color': 'rgba(255, 255, 255, 0.1)',
61
+ '--overlay-backdrop-bgColor': 'rgba(255, 255, 255, 0.1)',
62
+ '--fgColor-muted': '#9198a1',
63
+ '--fgColor-default': '#f0f6fc',
64
+ '--fgColor-onEmphasis': '#ffffff',
65
+ '--borderColor-default': '#3d444d',
66
+ '--borderColor-muted': '#3d444db3',
67
+ },
68
+ dark_dimmed: {
69
+ '--sb--canvas-bg': '#262c36',
70
+ '--bgColor-default': '#212830',
71
+ '--bgColor-muted': '#262c36',
72
+ '--bgColor-neutral-muted': '#656c7633',
73
+ '--bgColor-accent-emphasis': '#316dca',
74
+ '--tc-bg-muted': '#262c36',
75
+ '--tc-dot-color': 'rgba(209, 215, 224, 0.18)',
76
+ '--overlay-backdrop-bgColor': 'rgba(209, 215, 224, 0.18)',
77
+ '--fgColor-muted': '#9198a1',
78
+ '--fgColor-default': '#d1d7e0',
79
+ '--fgColor-onEmphasis': '#f0f6fc',
80
+ '--borderColor-default': '#3d444d',
81
+ '--borderColor-muted': '#3d444db3',
82
+ },
83
+ dark_colorblind: {
84
+ '--sb--canvas-bg': '#151b23',
85
+ '--bgColor-default': '#0d1117',
86
+ '--bgColor-muted': '#151b23',
87
+ '--bgColor-neutral-muted': '#656c7633',
88
+ '--bgColor-accent-emphasis': '#1f6feb',
89
+ '--tc-bg-muted': '#151b23',
90
+ '--tc-dot-color': 'rgba(255, 255, 255, 0.1)',
91
+ '--overlay-backdrop-bgColor': 'rgba(255, 255, 255, 0.1)',
92
+ '--fgColor-muted': '#9198a1',
93
+ '--fgColor-default': '#f0f6fc',
94
+ '--fgColor-onEmphasis': '#ffffff',
95
+ '--borderColor-default': '#3d444d',
96
+ '--borderColor-muted': '#3d444db3',
97
+ },
98
+ dark_high_contrast: {
99
+ '--sb--canvas-bg': '#151b23',
100
+ '--bgColor-default': '#010409',
101
+ '--bgColor-muted': '#151b23',
102
+ '--bgColor-neutral-muted': '#212830',
103
+ '--bgColor-accent-emphasis': '#194fb1',
104
+ '--tc-bg-muted': '#151b23',
105
+ '--tc-dot-color': 'rgba(183, 189, 200, 0.25)',
106
+ '--overlay-backdrop-bgColor': 'rgba(183, 189, 200, 0.25)',
107
+ '--fgColor-muted': '#b7bdc8',
108
+ '--fgColor-default': '#ffffff',
109
+ '--fgColor-onEmphasis': '#ffffff',
110
+ '--borderColor-default': '#b7bdc8',
111
+ '--borderColor-muted': '#b7bdc8',
112
+ },
113
+ }
114
+
115
+ export function getCanvasThemeVars(theme) {
116
+ const value = String(theme || 'light')
117
+ return THEME_VARS[value] || THEME_VARS.light
118
+ }
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Canvas Component Isolate — iframe entry point.
3
+ *
4
+ * Renders a single named export from a .story.jsx module inside an
5
+ * isolated document. The parent CanvasPage embeds this via an iframe
6
+ * so a broken component cannot crash the entire canvas.
7
+ *
8
+ * Query params:
9
+ * module — absolute or base-relative path to the .story.jsx file
10
+ * export — the named export to render
11
+ * theme — canvas theme (light / dark / dark_dimmed)
12
+ */
13
+ import { createElement, Component as ReactComponent } from 'react'
14
+ import { createRoot } from 'react-dom/client'
15
+ import { ThemeProvider, BaseStyles } from '@primer/react'
16
+
17
+ // ── Primer Primitives CSS (required for CSS variables) ──────────────
18
+ import '@primer/primitives/dist/css/base/size/size.css'
19
+ import '@primer/primitives/dist/css/base/typography/typography.css'
20
+ import '@primer/primitives/dist/css/base/motion/motion.css'
21
+ import '@primer/primitives/dist/css/functional/size/border.css'
22
+ import '@primer/primitives/dist/css/functional/size/breakpoints.css'
23
+ import '@primer/primitives/dist/css/functional/size/size-coarse.css'
24
+ import '@primer/primitives/dist/css/functional/size/size-fine.css'
25
+ import '@primer/primitives/dist/css/functional/size/size.css'
26
+ import '@primer/primitives/dist/css/functional/size/viewport.css'
27
+ import '@primer/primitives/dist/css/functional/typography/typography.css'
28
+ import '@primer/primitives/dist/css/functional/themes/light.css'
29
+ import '@primer/primitives/dist/css/functional/themes/light-colorblind.css'
30
+ import '@primer/primitives/dist/css/functional/themes/dark.css'
31
+ import '@primer/primitives/dist/css/functional/themes/dark-colorblind.css'
32
+ import '@primer/primitives/dist/css/functional/themes/dark-high-contrast.css'
33
+ import '@primer/primitives/dist/css/functional/themes/dark-dimmed.css'
34
+
35
+ // ── Error Boundary ──────────────────────────────────────────────────
36
+ class IsolateErrorBoundary extends ReactComponent {
37
+ constructor(props) {
38
+ super(props)
39
+ this.state = { error: null }
40
+ }
41
+ static getDerivedStateFromError(error) {
42
+ return { error }
43
+ }
44
+ render() {
45
+ if (this.state.error) {
46
+ return createElement('div', { style: errorStyle },
47
+ createElement('strong', null, this.props.name || 'Component'),
48
+ createElement('br'),
49
+ String(this.state.error.message || this.state.error),
50
+ )
51
+ }
52
+ return this.props.children
53
+ }
54
+ }
55
+
56
+ // ── Styles ──────────────────────────────────────────────────────────
57
+ const errorStyle = {
58
+ padding: '16px',
59
+ color: '#cf222e',
60
+ fontFamily: 'system-ui, -apple-system, sans-serif',
61
+ fontSize: '13px',
62
+ lineHeight: 1.5,
63
+ whiteSpace: 'pre-wrap',
64
+ wordBreak: 'break-word',
65
+ }
66
+
67
+ // ── Resolve module path (mirrors useCanvas.resolveCanvasModuleImport) ─
68
+ function resolveModulePath(raw) {
69
+ if (!raw) return raw
70
+ if (/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(raw)) return raw
71
+ if (!raw.startsWith('/')) return raw
72
+ const base = (import.meta.env?.BASE_URL || '/').replace(/\/$/, '')
73
+ if (!base) return raw
74
+ if (raw.startsWith(base)) return raw
75
+ return `${base}${raw}`
76
+ }
77
+
78
+ // ── Main ────────────────────────────────────────────────────────────
79
+ const params = new URLSearchParams(window.location.search)
80
+ const modulePath = params.get('module')
81
+ const exportName = params.get('export')
82
+ const theme = params.get('theme') || 'light'
83
+
84
+ // Map theme to Primer colorMode
85
+ const colorMode = theme.startsWith('dark') ? 'night' : 'day'
86
+
87
+ // Apply theme to document for Primer / CSS-var inheritance
88
+ document.documentElement.setAttribute('data-color-mode', theme.startsWith('dark') ? 'dark' : 'light')
89
+ document.documentElement.setAttribute('data-dark-theme', theme.startsWith('dark') ? theme : '')
90
+ document.documentElement.setAttribute('data-light-theme', theme.startsWith('dark') ? '' : theme || 'light')
91
+
92
+ // Suppress HMR full-reloads — this iframe is embedded inside a canvas page
93
+ // that manages its own reload lifecycle. Without this guard, every file change
94
+ // causes the iframe to flash/reload.
95
+ if (import.meta.hot) {
96
+ const msg = { active: true }
97
+ import.meta.hot.send('storyboard:canvas-hmr-guard', msg)
98
+ setInterval(() => import.meta.hot.send('storyboard:canvas-hmr-guard', msg), 3000)
99
+ }
100
+
101
+ const root = createRoot(document.getElementById('root'))
102
+
103
+ async function mount() {
104
+ if (!modulePath) {
105
+ root.render(createElement('div', { style: errorStyle }, 'Missing module param'))
106
+ return
107
+ }
108
+
109
+ // Validate: only allow .story.{jsx,tsx} modules
110
+ if (!modulePath.match(/\.story\.(jsx|tsx)$/)) {
111
+ root.render(createElement('div', { style: errorStyle }, 'Invalid module path — only .story.jsx/.tsx files are allowed'))
112
+ return
113
+ }
114
+
115
+ try {
116
+ const resolved = resolveModulePath(modulePath)
117
+ const mod = await import(/* @vite-ignore */ resolved)
118
+
119
+ if (exportName) {
120
+ // Single export mode
121
+ const Component = mod[exportName]
122
+ if (!Component || typeof Component !== 'function') {
123
+ throw new Error(`Export "${exportName}" not found or is not a component`)
124
+ }
125
+ root.render(
126
+ createElement(ThemeProvider, { colorMode },
127
+ createElement(BaseStyles, null,
128
+ createElement(IsolateErrorBoundary, { name: exportName },
129
+ createElement(Component),
130
+ ),
131
+ ),
132
+ ),
133
+ )
134
+ } else {
135
+ // All exports mode — render every named function export stacked
136
+ const entries = Object.entries(mod).filter(
137
+ ([key, value]) => key !== 'default' && typeof value === 'function',
138
+ )
139
+ if (entries.length === 0) {
140
+ throw new Error('No named exports found in story module')
141
+ }
142
+ root.render(
143
+ createElement(ThemeProvider, { colorMode },
144
+ createElement(BaseStyles, null,
145
+ ...entries.map(([name, Component]) =>
146
+ createElement(IsolateErrorBoundary, { key: name, name },
147
+ createElement(Component),
148
+ ),
149
+ ),
150
+ ),
151
+ ),
152
+ )
153
+ }
154
+ } catch (err) {
155
+ root.render(
156
+ createElement('div', { style: errorStyle },
157
+ createElement('strong', null, exportName || 'Component'),
158
+ createElement('br'),
159
+ String(err.message || err),
160
+ ),
161
+ )
162
+ }
163
+ }
164
+
165
+ mount()