@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,1361 @@
1
+ import { useState, useEffect, useCallback, useMemo, useRef } from 'react'
2
+ import { Command } from 'cmdk'
3
+ import * as VisuallyHidden from '@radix-ui/react-visually-hidden'
4
+ import * as DialogPrimitive from '@radix-ui/react-dialog'
5
+ import Icon from '../Icon.jsx'
6
+ import {
7
+ buildPrototypeIndex,
8
+ listStories,
9
+ getStoryData,
10
+ getActionsForMode,
11
+ executeAction,
12
+ getActionChildren,
13
+ getToolbarToolState,
14
+ getCurrentMode,
15
+ getRecent,
16
+ trackRecent,
17
+ getCommandPaletteConfig,
18
+ getToolbarConfig,
19
+ getConfig,
20
+ setTheme,
21
+ getTheme,
22
+ isExcludedByRoute,
23
+ scoreMatch,
24
+ } from '../../core/index.js'
25
+ import { widgetTypes } from '../canvas/widgets/widgetConfig.js'
26
+ import CreateDialog from './CreateDialog.jsx'
27
+ import BranchBar from '../BranchBar/BranchBar.jsx'
28
+ import AuthModal from '../AuthModal/AuthModal.jsx'
29
+ import './command-palette.css'
30
+
31
+ // Icon size for all palette items
32
+ const ICON_SIZE = 16
33
+
34
+ function getIconMap() {
35
+ const config = getCommandPaletteConfig()
36
+ return config?.icons || {}
37
+ }
38
+
39
+ function ItemIcon({ type, toolIcon, toolMeta }) {
40
+ const icons = getIconMap()
41
+ const entry = toolIcon || icons[type] || icons.fallback || 'feather/hexagon'
42
+ const iconName = typeof entry === 'object' ? entry.name : entry
43
+ const meta = toolMeta || (typeof entry === 'object' ? entry.meta : undefined)
44
+ return <Icon name={iconName} size={ICON_SIZE} color="var(--fgColor-muted, #656d76)" {...(meta || {})} />
45
+ }
46
+
47
+ function AvatarIcon({ username }) {
48
+ return (
49
+ <img
50
+ src={`https://github.com/${username}.png?size=32`}
51
+ alt={username}
52
+ width={ICON_SIZE}
53
+ height={ICON_SIZE}
54
+ style={{ flexShrink: 0, borderRadius: '50%' }}
55
+ />
56
+ )
57
+ }
58
+
59
+ /**
60
+ * Alt-reactive label for the "Hide toolbars" command palette item.
61
+ * Shows "Completely hide toolbars" when alt is held, "Hide toolbars" otherwise.
62
+ */
63
+ function HideToolbarsLabel({ isHidden }) {
64
+ const [altHeld, setAltHeld] = useState(false)
65
+ useEffect(() => {
66
+ const onKey = (e) => setAltHeld(e.altKey)
67
+ const onUp = () => setAltHeld(false)
68
+ document.addEventListener('keydown', onKey, true)
69
+ document.addEventListener('keyup', onUp, true)
70
+ return () => {
71
+ document.removeEventListener('keydown', onKey, true)
72
+ document.removeEventListener('keyup', onUp, true)
73
+ }
74
+ }, [])
75
+
76
+ return (
77
+ <span style={{ display: 'flex', width: '100%', justifyContent: 'space-between', alignItems: 'center' }}>
78
+ <span>{altHeld ? 'Completely hide toolbars' : 'Hide toolbars'}</span>
79
+ <span>{isHidden ? '✓' : ''}</span>
80
+ </span>
81
+ )
82
+ }
83
+
84
+ /**
85
+ * Check if a tool should be hidden from the command palette on the current route.
86
+ * Uses the same pattern-matching logic as excludeRoutes.
87
+ */
88
+ function isHiddenInPalette(tool, basePath) {
89
+ const val = tool.hideInCommandPalette
90
+ if (val === true) return true
91
+ if (!val || !Array.isArray(val) || val.length === 0) return false
92
+ if (typeof window === 'undefined') return false
93
+ let pathname = window.location.pathname
94
+ const base = (basePath || '/').replace(/\/+$/, '')
95
+ if (base && pathname.startsWith(base)) {
96
+ pathname = pathname.slice(base.length) || '/'
97
+ }
98
+ return val.some(pattern => new RegExp(pattern).test(pathname))
99
+ }
100
+
101
+ /**
102
+ * Build groups from commandPalette.sections config.
103
+ * Returns { groups, toolMenus } where toolMenus are entries with sub-pages.
104
+ *
105
+ * Section types:
106
+ * - Static items: { items: [...] }
107
+ * - Dynamic list: { source: "canvases"|"prototypes"|"stories"|"recent" }
108
+ * - Tool section: { source: "tools", toolIds: ["theme", "flows"] }
109
+ * - Tool-menu: { type: "tool-menu", options: [...] }
110
+ */
111
+ function buildConfigSections(prefix, onNavigateToPage, onCreateAction) {
112
+ const config = getCommandPaletteConfig()
113
+ const sections = config?.sections || []
114
+ const groups = []
115
+ const toolMenus = []
116
+ const usedToolIds = new Set()
117
+ const hiddenFromSearchIds = new Set()
118
+ const basePath = prefix || '/'
119
+
120
+ for (const section of sections) {
121
+ // Separator: id starts with "sep"
122
+ if (section.id?.startsWith('sep')) {
123
+ groups.push({ id: `cfg:${section.id}`, items: [{ id: `cfg:${section.id}:sep`, children: '', keywords: ['*'] }] })
124
+ continue
125
+ }
126
+
127
+ if (section.type === 'tool-menu') {
128
+ toolMenus.push(section)
129
+ continue
130
+ }
131
+
132
+ if (section.source) {
133
+ // Defer tool-subpages — needs usedToolIds from all other sections first
134
+ if (section.source === 'tool-subpages') continue
135
+ const result = buildDynamicSection(section, prefix, onNavigateToPage, onCreateAction)
136
+ if (result?.group) groups.push(result.group)
137
+ if (result?.subPages) toolMenus.push(...result.subPages)
138
+ if (result?.usedToolIds) result.usedToolIds.forEach(id => usedToolIds.add(id))
139
+ if (result?.hiddenFromSearchIds) result.hiddenFromSearchIds.forEach(id => hiddenFromSearchIds.add(id))
140
+ continue
141
+ }
142
+
143
+ if (section.items && section.items.length > 0) {
144
+ groups.push({
145
+ heading: section.title,
146
+ id: `cfg:${section.id}`,
147
+ items: section.items.map((item, i) => {
148
+ const id = `cfg:${section.id}:${i}`
149
+ if (item.type === 'link') {
150
+ const resolvedUrl = item.url?.startsWith('/') ? prefix + item.url : item.url
151
+ return {
152
+ id,
153
+ children: item.label,
154
+ keywords: item.keywords || [item.label],
155
+ url: resolvedUrl,
156
+ onClick: () => {
157
+ if (resolvedUrl) window.location.href = resolvedUrl
158
+ },
159
+ }
160
+ }
161
+ if (item.type === 'action') {
162
+ return {
163
+ id,
164
+ children: item.label,
165
+ keywords: item.keywords || [item.label],
166
+ onClick: () => { if (item.action) executeAction(item.action) },
167
+ }
168
+ }
169
+ return { id, children: item.label, keywords: [item.label] }
170
+ }),
171
+ })
172
+ }
173
+ }
174
+
175
+ // Resolve tool-subpages sections (deferred — needs complete usedToolIds)
176
+ for (const section of sections) {
177
+ if (section.source !== 'tool-subpages') continue
178
+
179
+ // Scan all toolbar tools for sub-page candidates not already listed
180
+ const toolbarConfig = getToolbarConfig()
181
+ const allTools = toolbarConfig?.tools || {}
182
+ const mode = getCurrentMode() || 'default'
183
+ const actions = getActionsForMode(mode)
184
+ const remainingItems = []
185
+
186
+ for (const [toolId, tool] of Object.entries(allTools)) {
187
+ if (usedToolIds.has(toolId)) continue
188
+ const state = getToolbarToolState(toolId)
189
+ if (state === 'disabled' || state === 'hidden') continue
190
+ if (tool.disabled) continue
191
+ if (isHiddenInPalette(tool, basePath)) continue
192
+
193
+ const label = tool.label || tool.ariaLabel || toolId
194
+ const excluded = isExcludedByRoute(tool)
195
+
196
+ // Route-excluded tools show as disabled with hint
197
+ if (excluded) {
198
+ remainingItems.push({
199
+ id: `cfg:${section.id}:${toolId}`,
200
+ children: <><span>{label}</span><span style={{ marginLeft: 'auto', fontSize: '12px', opacity: 0.5 }}>Not available on this page</span></>,
201
+ keywords: [label, toolId].filter(Boolean),
202
+ showType: false,
203
+ disabled: true,
204
+ })
205
+ continue
206
+ }
207
+
208
+ // Tools with submenu children
209
+ if (tool.render === 'submenu' || tool.render === 'menu') {
210
+ const action = actions.find(a => a.toolKey === toolId)
211
+ if (action?.type === 'submenu') {
212
+ const children = getActionChildren(action.id)
213
+ if (children.length > 0) {
214
+ const pageId = `tool:${toolId}`
215
+ toolMenus.push({
216
+ id: pageId, label, title: label,
217
+ keywords: [label, toolId].filter(Boolean),
218
+ options: children.map(child => ({ label: child.label, execute: child.execute })),
219
+ })
220
+ remainingItems.push({
221
+ id: `cfg:${section.id}:${toolId}`,
222
+ children: label,
223
+ keywords: [label, toolId].filter(Boolean),
224
+ showType: false,
225
+ onClick: () => onNavigateToPage?.(pageId),
226
+ closeOnSelect: false,
227
+ })
228
+ continue
229
+ }
230
+ }
231
+ // Declarative options
232
+ if (tool.options?.length > 0) {
233
+ const pageId = `tool:${toolId}`
234
+ toolMenus.push({
235
+ id: pageId, label, title: label,
236
+ keywords: [label, toolId].filter(Boolean),
237
+ options: tool.options.map(opt => ({ label: opt.label, toolHandler: tool.handler || `core:${toolId}`, value: opt.value })),
238
+ })
239
+ remainingItems.push({
240
+ id: `cfg:${section.id}:${toolId}`,
241
+ children: label,
242
+ keywords: [label, toolId].filter(Boolean),
243
+ showType: false,
244
+ onClick: () => onNavigateToPage?.(pageId),
245
+ closeOnSelect: false,
246
+ })
247
+ continue
248
+ }
249
+ }
250
+
251
+ // Inline actions (e.g. toggle-chrome for hide toolbars)
252
+ if (tool.inlineAction === 'toggle-chrome') {
253
+ remainingItems.push({
254
+ id: `cfg:${section.id}:${toolId}`,
255
+ children: label,
256
+ keywords: [label, toolId, 'hide', 'show', 'toolbar', 'completely'].filter(Boolean),
257
+ showType: false,
258
+ onClick: () => {
259
+ document.documentElement.classList.remove('storyboard-chrome-completely-hidden')
260
+ document.documentElement.classList.toggle('storyboard-chrome-hidden')
261
+ },
262
+ onAltClick: () => {
263
+ document.documentElement.classList.add('storyboard-chrome-hidden')
264
+ document.documentElement.classList.add('storyboard-chrome-completely-hidden')
265
+ },
266
+ })
267
+ continue
268
+ }
269
+
270
+ if (tool.inlineAction === 'open-palette') {
271
+ // Skip — no point opening the palette from within itself
272
+ continue
273
+ }
274
+
275
+ // Any remaining tools (all surfaces)
276
+ if (tool.render === 'link' && tool.url) {
277
+ const resolvedUrl = tool.url.startsWith('/') ? prefix + tool.url : tool.url
278
+ remainingItems.push({
279
+ id: `cfg:${section.id}:${toolId}`,
280
+ children: label,
281
+ keywords: [label, toolId].filter(Boolean),
282
+ showType: false,
283
+ url: resolvedUrl,
284
+ onClick: () => { window.location.href = resolvedUrl },
285
+ })
286
+ } else {
287
+ // Menu tools: close palette and dispatch event to open the toolbar menu
288
+ if (tool.render === 'menu') {
289
+ const handlerId = tool.handler || `core:${toolId}`
290
+ remainingItems.push({
291
+ id: `cfg:${section.id}:${toolId}`,
292
+ children: label,
293
+ keywords: [label, toolId].filter(Boolean),
294
+ showType: false,
295
+ onClick: () => {
296
+ setTimeout(() => {
297
+ window.dispatchEvent(new CustomEvent('storyboard:open-tool-menu', { detail: { action: handlerId } }))
298
+ }, 100)
299
+ },
300
+ })
301
+ } else {
302
+ // Fallback: click toolbar button or execute action
303
+ const action = actions.find(a => a.toolKey === toolId)
304
+ const ariaLabel = tool.ariaLabel || tool.label || toolId
305
+ remainingItems.push({
306
+ id: `cfg:${section.id}:${toolId}`,
307
+ children: label,
308
+ keywords: [label, toolId].filter(Boolean),
309
+ showType: false,
310
+ onClick: action
311
+ ? () => executeAction(action.id)
312
+ : () => {
313
+ setTimeout(() => {
314
+ const btn = document.querySelector(`[aria-label="${ariaLabel}"]`)
315
+ if (btn) btn.click()
316
+ }, 100)
317
+ },
318
+ })
319
+ }
320
+ }
321
+ }
322
+
323
+ // Also include any tool sub-pages not yet listed (skip non-tool sub-pages like create-widget)
324
+ for (const menu of toolMenus) {
325
+ if (!menu.id?.startsWith('tool:')) continue
326
+ const menuToolId = menu.id.replace('tool:', '')
327
+ if (usedToolIds.has(menuToolId)) continue
328
+ if (remainingItems.some(i => i.id === `cfg:${section.id}:${menuToolId}`)) continue
329
+ remainingItems.push({
330
+ id: `cfg:${section.id}:${menuToolId || menu.id}`,
331
+ children: menu.label || menu.id,
332
+ keywords: menu.keywords || [menu.label || menu.id],
333
+ showType: false,
334
+ onClick: () => onNavigateToPage?.(menu.id),
335
+ closeOnSelect: false,
336
+ })
337
+ }
338
+
339
+ if (remainingItems.length === 0) continue
340
+
341
+ // Stamp toolIcon from toolbar config onto remaining items
342
+ for (const item of remainingItems) {
343
+ if (!item.toolIcon) {
344
+ const match = item.id?.match(/cfg:[^:]+:(.+)/)
345
+ if (match) {
346
+ const t = allTools[match[1]]
347
+ if (t?.icon) item.toolIcon = t.icon
348
+ }
349
+ }
350
+ }
351
+
352
+ groups.push({
353
+ heading: section.title,
354
+ id: `cfg:${section.id}`,
355
+ items: remainingItems,
356
+ })
357
+ }
358
+
359
+ return { groups, toolMenus, hiddenFromSearchIds }
360
+ }
361
+
362
+ function buildDynamicSection(section, prefix, onNavigateToPage, onCreateAction) {
363
+ if (section.source === 'tools') {
364
+ return buildToolsSection(section, prefix, onNavigateToPage)
365
+ }
366
+
367
+ // --- Create source (dev-only workshop actions) ---
368
+ if (section.source === 'create') {
369
+ const isLocalDev = typeof window !== 'undefined' && window.__SB_LOCAL_DEV__ === true
370
+ if (!isLocalDev) return null
371
+ const createItems = [
372
+ { id: 'create:canvas', children: 'Canvas', keywords: ['create', 'canvas', 'new', 'board'], itemType: 'create', onClick: () => onCreateAction?.('Canvas') },
373
+ { id: 'create:prototype', children: 'Prototype', keywords: ['create', 'prototype', 'new', 'page'], itemType: 'create', onClick: () => onCreateAction?.('Prototype') },
374
+ { id: 'create:component', children: 'Component', keywords: ['create', 'component', 'new', 'story'], itemType: 'create', onClick: () => onCreateAction?.('Component') },
375
+ { id: 'create:flow', children: 'Prototype Flow', keywords: ['create', 'flow', 'new', 'data'], itemType: 'create', onClick: () => onCreateAction?.('Flow') },
376
+ { id: 'create:page', children: 'Prototype Page', keywords: ['create', 'page', 'new'], itemType: 'create', onClick: () => onCreateAction?.('Page') },
377
+ ]
378
+ return { group: { heading: section.title, id: `cfg:${section.id}`, items: createItems } }
379
+ }
380
+
381
+ // --- Create widget source (all canvas widget types) ---
382
+ if (section.source === 'create-widget') {
383
+ const isLocalDev = typeof window !== 'undefined' && window.__SB_LOCAL_DEV__ === true
384
+ if (!isLocalDev) return null
385
+ const isCanvasRoute = typeof window !== 'undefined' && window.location.pathname.includes('/canvas/')
386
+ if (!isCanvasRoute) return null
387
+ const hiddenTypes = new Set(['link-preview', 'image', 'figma-embed', 'codepen-embed', 'story', 'terminal-read', 'agent'])
388
+ const items = Object.entries(widgetTypes).filter(([type]) => !hiddenTypes.has(type)).map(([type, def]) => ({
389
+ id: `create-widget:${type}`,
390
+ children: def.label,
391
+ keywords: ['add', 'widget', 'create', type, def.label.toLowerCase()],
392
+ itemType: type,
393
+ onClick: () => {
394
+ document.dispatchEvent(new CustomEvent('storyboard:canvas:add-widget', { detail: { type } }))
395
+ },
396
+ }))
397
+
398
+ // Build agent submenu from canvas.agents config
399
+ const subPages = []
400
+ const canvasConfig = getConfig('canvas')
401
+ const agentsConfig = canvasConfig?.agents
402
+ if (agentsConfig && typeof agentsConfig === 'object') {
403
+ const agentEntries = Object.entries(agentsConfig)
404
+ if (agentEntries.length > 0) {
405
+ const pageId = 'create-widget:agents'
406
+ subPages.push({
407
+ id: pageId,
408
+ label: 'Add agent to canvas',
409
+ title: 'Add agent to canvas',
410
+ keywords: ['agent', 'add', 'widget', 'copilot', 'claude', 'codex'],
411
+ options: agentEntries.map(([id, cfg]) => ({
412
+ label: cfg.label || id,
413
+ icon: cfg.icon,
414
+ execute: () => {
415
+ document.dispatchEvent(new CustomEvent('storyboard:canvas:add-widget', {
416
+ detail: {
417
+ type: 'agent',
418
+ props: {
419
+ agentId: id,
420
+ startupCommand: cfg.startupCommand || id,
421
+ ...(cfg.defaultWidth ? { width: cfg.defaultWidth } : {}),
422
+ ...(cfg.defaultHeight ? { height: cfg.defaultHeight } : {}),
423
+ },
424
+ },
425
+ }))
426
+ },
427
+ })),
428
+ })
429
+ items.push({
430
+ id: 'create-widget:agent',
431
+ children: 'Agent',
432
+ keywords: ['add', 'widget', 'create', 'agent'],
433
+ itemType: 'agent',
434
+ hideFromSearch: true,
435
+ onClick: () => onNavigateToPage?.(pageId),
436
+ closeOnSelect: false,
437
+ })
438
+ }
439
+ }
440
+
441
+ return { group: { heading: section.title, id: `cfg:${section.id}`, items }, subPages }
442
+ }
443
+
444
+ // --- Starred source (reads from viewfinder localStorage) ---
445
+ if (section.source === 'starred') {
446
+ const STARRED_KEY = 'sb-workspace-starred'
447
+ let starredIds = []
448
+ try { starredIds = JSON.parse(localStorage.getItem(STARRED_KEY)) || [] } catch { /* empty */ }
449
+ if (starredIds.length === 0) return null
450
+
451
+ const index = buildPrototypeIndex()
452
+ // Build a lookup map of all artifacts
453
+ const artifactMap = new Map()
454
+ const allProtos = [...index.prototypes]
455
+ for (const folder of index.folders) {
456
+ allProtos.push(...folder.prototypes)
457
+ if (folder.canvases) folder.canvases.forEach(c => artifactMap.set(`canvas:${c.dirName}`, { ...c, _type: 'canvas' }))
458
+ }
459
+ for (const c of index.canvases) artifactMap.set(`canvas:${c.dirName}`, { ...c, _type: 'canvas' })
460
+ for (const p of allProtos) artifactMap.set(`proto:${p.dirName}`, { ...p, _type: 'prototype' })
461
+
462
+ const items = []
463
+ for (const id of starredIds) {
464
+ const artifact = artifactMap.get(id)
465
+ if (!artifact) continue
466
+ const route = artifact._type === 'canvas'
467
+ ? `${prefix}/canvas/${artifact.dirName}`
468
+ : artifact.isExternal
469
+ ? artifact.externalUrl
470
+ : `${prefix}/${artifact.dirName}`
471
+ items.push({
472
+ id: `starred:${id}`,
473
+ children: artifact.name,
474
+ keywords: ['starred', 'star', artifact.name.toLowerCase()],
475
+ itemType: artifact._type === 'canvas' ? 'canvas' : 'prototype',
476
+ url: route,
477
+ onClick: () => {
478
+ if (artifact.isExternal) {
479
+ window.open(route, '_blank')
480
+ } else {
481
+ window.location.href = route
482
+ }
483
+ },
484
+ })
485
+ }
486
+ if (items.length === 0) return null
487
+ return { group: { heading: section.title, id: `cfg:${section.id}`, items } }
488
+ }
489
+
490
+ // --- Commands source (all registered toolbar actions) ---
491
+ if (section.source === 'commands') {
492
+ const mode = getCurrentMode() || 'default'
493
+ const actions = getActionsForMode(mode)
494
+ const commandItems = []
495
+ for (const action of actions) {
496
+ if (action.type === 'header' || action.type === 'separator' || action.type === 'footer') continue
497
+ if (action.toolKey) {
498
+ const state = getToolbarToolState(action.toolKey)
499
+ if (state === 'disabled' || state === 'hidden') continue
500
+ }
501
+ if (action.type === 'submenu') {
502
+ const children = getActionChildren(action.id)
503
+ for (const child of children) {
504
+ commandItems.push({
505
+ id: `cmd:${action.id}/${child.id || child.label}`,
506
+ children: child.label,
507
+ keywords: [action.label, child.label],
508
+ itemType: 'command',
509
+ onClick: () => { if (child.execute) child.execute() },
510
+ })
511
+ }
512
+ } else if (action.type === 'link' && action.url) {
513
+ const resolvedUrl = action.url.startsWith('/') && !action.url.startsWith('//') ? prefix + action.url : action.url
514
+ commandItems.push({
515
+ id: `cmd:${action.id}`,
516
+ children: action.label,
517
+ keywords: [action.label],
518
+ itemType: 'link',
519
+ url: resolvedUrl,
520
+ onClick: () => {
521
+ window.location.href = resolvedUrl
522
+ },
523
+ })
524
+ } else {
525
+ commandItems.push({
526
+ id: `cmd:${action.id}`,
527
+ children: action.label,
528
+ keywords: [action.label],
529
+ itemType: 'command',
530
+ onClick: () => executeAction(action.id),
531
+ })
532
+ }
533
+ }
534
+ if (commandItems.length === 0) return null
535
+ return { group: { heading: section.title, id: `cfg:${section.id}`, items: commandItems } }
536
+ }
537
+
538
+ // --- Recent source: all artifact types from getRecent() ---
539
+ if (section.source === 'recent') {
540
+ const recent = getRecent()
541
+ if (recent.length === 0) return null
542
+ let items = recent
543
+ if (section.limit) items = items.slice(0, section.limit)
544
+ return {
545
+ group: {
546
+ heading: section.title,
547
+ id: `cfg:${section.id}`,
548
+ items: items.map(entry => {
549
+ const route = resolveRecentRoute(entry, prefix)
550
+ return {
551
+ id: `cfg:${section.id}:${entry.type}:${entry.key}`,
552
+ children: entry.label,
553
+ keywords: [entry.type, entry.key, entry.label],
554
+ itemType: entry.type,
555
+ url: route || undefined,
556
+ onClick: () => {
557
+ trackRecent(entry.type, entry.key, entry.label)
558
+ if (route) window.location.href = route
559
+ },
560
+ }
561
+ }),
562
+ },
563
+ }
564
+ }
565
+
566
+ // --- Artifact sources: canvases, prototypes, stories ---
567
+ const index = buildPrototypeIndex()
568
+ let sourceItems = []
569
+
570
+ if (section.source === 'canvases') {
571
+ for (const c of index.canvases) sourceItems.push({ name: c.name, route: `${prefix}${c.route}`, id: c.dirName, type: 'canvas' })
572
+ for (const f of index.folders) {
573
+ if (f.canvases) for (const c of f.canvases) sourceItems.push({ name: c.name, route: `${prefix}${c.route}`, id: c.dirName, type: 'canvas' })
574
+ }
575
+ } else if (section.source === 'prototypes') {
576
+ const formatFlowName = (name) => name.replace(/[-_]/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase())
577
+ const pushProtoFlows = (p) => {
578
+ if (p.isExternal) {
579
+ sourceItems.push({ name: p.name, route: p.externalUrl, id: p.dirName, type: 'prototype', isExternal: true })
580
+ } else if (p.flows.length <= 1) {
581
+ const route = p.flows.length === 1 ? `${prefix}${p.flows[0].route}` : `${prefix}/${p.dirName}`
582
+ sourceItems.push({ name: p.name, route, id: p.dirName, type: 'prototype' })
583
+ } else {
584
+ for (const flow of p.flows) {
585
+ const flowLabel = flow.meta?.title || formatFlowName(flow.name)
586
+ sourceItems.push({
587
+ name: `${p.name} – ${flowLabel}`,
588
+ route: `${prefix}${flow.route}`,
589
+ id: `${p.dirName}/${flow.name}`,
590
+ type: 'prototype',
591
+ })
592
+ }
593
+ }
594
+ }
595
+ for (const p of index.prototypes) pushProtoFlows(p)
596
+ for (const f of index.folders) {
597
+ for (const p of f.prototypes) pushProtoFlows(p)
598
+ }
599
+ } else if (section.source === 'stories') {
600
+ for (const name of listStories()) {
601
+ const data = getStoryData(name)
602
+ const route = data?._route || `/components/${name}`
603
+ sourceItems.push({ name, route: `${prefix}${route}`, id: name, type: 'story' })
604
+ }
605
+ }
606
+
607
+ if (sourceItems.length === 0) return null
608
+
609
+ if (section.order === 'recent') {
610
+ const recent = getRecent()
611
+ const recentKeys = recent.map(r => r.key)
612
+ sourceItems.sort((a, b) => {
613
+ const ai = recentKeys.indexOf(a.id)
614
+ const bi = recentKeys.indexOf(b.id)
615
+ if (ai === -1 && bi === -1) return 0
616
+ if (ai === -1) return 1
617
+ if (bi === -1) return -1
618
+ return ai - bi
619
+ })
620
+ } else if (section.order === 'alphabetical') {
621
+ sourceItems.sort((a, b) => a.name.localeCompare(b.name))
622
+ }
623
+
624
+ if (section.limit) sourceItems = sourceItems.slice(0, section.limit)
625
+
626
+ return {
627
+ group: {
628
+ heading: section.title,
629
+ id: `cfg:${section.id}`,
630
+ items: sourceItems.map(item => ({
631
+ id: `cfg:${section.id}:${item.id}`,
632
+ children: item.name,
633
+ keywords: [item.name, item.id, item.type],
634
+ itemType: item.type,
635
+ url: item.route,
636
+ onClick: () => {
637
+ trackRecent(item.type, item.id, item.name)
638
+ if (item.isExternal) {
639
+ window.open(item.route, '_blank')
640
+ } else {
641
+ window.location.href = item.route
642
+ }
643
+ },
644
+ })),
645
+ },
646
+ }
647
+ }
648
+
649
+ /**
650
+ * Build a section from toolbar.config.json tools.
651
+ * If toolIds is provided, only include those tools in that order (with optional custom labels).
652
+ * Otherwise include all command-palette tools.
653
+ *
654
+ * toolIds format: ["theme", "flows"] or [{ id: "theme", label: "Change theme" }]
655
+ */
656
+ function buildToolsSection(section, prefix, onNavigateToPage) {
657
+ const toolbarConfig = getToolbarConfig()
658
+ const tools = toolbarConfig?.tools || {}
659
+ const mode = getCurrentMode() || 'default'
660
+ const actions = getActionsForMode(mode)
661
+ const basePath = prefix || '/'
662
+
663
+ let entries = []
664
+
665
+ if (section.toolIds && section.toolIds.length > 0) {
666
+ for (const entry of section.toolIds) {
667
+ const toolId = typeof entry === 'string' ? entry : entry.id
668
+ const customLabel = typeof entry === 'object' ? entry.label : null
669
+ const closeOnSelect = typeof entry === 'object' ? entry.closeOnSelect : undefined
670
+ const iconMeta = typeof entry === 'object' ? entry.meta : undefined
671
+ const tool = tools[toolId]
672
+ if (!tool) continue
673
+ const state = getToolbarToolState(toolId)
674
+ if (state === 'disabled' || state === 'hidden') continue
675
+ if (isHiddenInPalette(tool, basePath)) continue
676
+ entries.push({ toolId, tool, label: customLabel || tool.label || toolId, toolIcon: tool.icon, toolMeta: iconMeta, closeOnSelect: closeOnSelect ?? tool.closeOnSelect })
677
+ }
678
+ } else {
679
+ for (const [toolId, tool] of Object.entries(tools)) {
680
+ if (tool.surface !== 'command-palette') continue
681
+ const state = getToolbarToolState(toolId)
682
+ if (state === 'disabled' || state === 'hidden') continue
683
+ if (isHiddenInPalette(tool, basePath)) continue
684
+ entries.push({ toolId, tool, label: tool.label || toolId, toolIcon: tool.icon, toolMeta: undefined, closeOnSelect: tool.closeOnSelect })
685
+ }
686
+ }
687
+
688
+ if (entries.length === 0) return null
689
+
690
+ const items = []
691
+ const subPages = []
692
+
693
+ for (const { toolId, tool, label, closeOnSelect: entryCloseOnSelect } of entries) {
694
+ // Inline actions
695
+ if (tool.inlineAction === 'toggle-chrome') {
696
+ const isHidden = document.documentElement.classList.contains('storyboard-chrome-hidden')
697
+ items.push({
698
+ id: `cfg:${section.id}:${toolId}`,
699
+ toolIcon: 'primer/light-bulb',
700
+ children: <HideToolbarsLabel isHidden={isHidden} />,
701
+ keywords: [label, toolId, 'hide', 'show', 'toolbar', 'completely'].filter(Boolean),
702
+ showType: false,
703
+ closeOnSelect: entryCloseOnSelect,
704
+ onClick: () => {
705
+ document.documentElement.classList.remove('storyboard-chrome-completely-hidden')
706
+ document.documentElement.classList.toggle('storyboard-chrome-hidden')
707
+ },
708
+ onAltClick: () => {
709
+ document.documentElement.classList.add('storyboard-chrome-hidden')
710
+ document.documentElement.classList.add('storyboard-chrome-completely-hidden')
711
+ },
712
+ })
713
+ continue
714
+ }
715
+
716
+ if (tool.inlineAction === 'open-palette') {
717
+ items.push({
718
+ id: `cfg:${section.id}:${toolId}`,
719
+ children: label,
720
+ keywords: [label, toolId, 'command', 'palette', 'search'].filter(Boolean),
721
+ showType: false,
722
+ onClick: () => {
723
+ document.dispatchEvent(new CustomEvent('storyboard:open-palette'))
724
+ },
725
+ })
726
+ continue
727
+ }
728
+
729
+ if (tool.render === 'link' && tool.url) {
730
+ const resolvedUrl = tool.url.startsWith('/') ? prefix + tool.url : tool.url
731
+ items.push({
732
+ id: `cfg:${section.id}:${toolId}`,
733
+ children: label,
734
+ keywords: [label, toolId].filter(Boolean),
735
+ url: resolvedUrl,
736
+ closeOnSelect: entryCloseOnSelect,
737
+ onClick: () => {
738
+ window.location.href = resolvedUrl
739
+ },
740
+ })
741
+ continue
742
+ }
743
+
744
+ if (tool.render === 'submenu' || tool.render === 'menu') {
745
+ const action = actions.find(a => a.toolKey === toolId)
746
+ if (action?.type === 'submenu') {
747
+ const children = getActionChildren(action.id)
748
+ if (children.length > 0) {
749
+ const pageId = `tool:${toolId}`
750
+ subPages.push({
751
+ id: pageId,
752
+ label,
753
+ title: label,
754
+ keywords: [label, toolId].filter(Boolean),
755
+ options: children.map(child => ({
756
+ label: child.label,
757
+ execute: child.execute,
758
+ type: child.type,
759
+ active: child.active,
760
+ })),
761
+ })
762
+ items.push({
763
+ id: `cfg:${section.id}:${toolId}`,
764
+ children: label,
765
+ keywords: [label, toolId].filter(Boolean),
766
+ onClick: () => onNavigateToPage?.(pageId),
767
+ closeOnSelect: false,
768
+ showType: false,
769
+ })
770
+ continue
771
+ }
772
+ }
773
+
774
+ // Declarative options from toolbar.config.json (e.g. theme options)
775
+ if (tool.options && tool.options.length > 0) {
776
+ const pageId = `tool:${toolId}`
777
+ const handlerId = tool.handler || `core:${toolId}`
778
+ subPages.push({
779
+ id: pageId,
780
+ label,
781
+ title: label,
782
+ keywords: [label, toolId].filter(Boolean),
783
+ options: tool.options.map(opt => ({
784
+ label: opt.label,
785
+ // Lazy-execute via the handler's action system
786
+ toolHandler: handlerId,
787
+ value: opt.value,
788
+ })),
789
+ })
790
+ items.push({
791
+ id: `cfg:${section.id}:${toolId}`,
792
+ children: label,
793
+ keywords: [label, toolId].filter(Boolean),
794
+ onClick: () => onNavigateToPage?.(pageId),
795
+ closeOnSelect: false,
796
+ showType: false,
797
+ })
798
+ continue
799
+ }
800
+
801
+ // Menu tool without sub-items or options — dispatch open event, fall back to clicking toolbar button
802
+ const ariaLabel = tool.ariaLabel || tool.label || toolId
803
+ items.push({
804
+ id: `cfg:${section.id}:${toolId}`,
805
+ children: label,
806
+ keywords: [label, toolId].filter(Boolean),
807
+ showType: false,
808
+ onClick: () => {
809
+ setTimeout(() => {
810
+ document.dispatchEvent(new CustomEvent(`storyboard:open-${toolId}`))
811
+ const btn = document.querySelector(`[aria-label="${ariaLabel}"]`)
812
+ if (btn) btn.click()
813
+ }, 100)
814
+ },
815
+ })
816
+ continue
817
+ }
818
+
819
+ if (tool.render === 'sidepanel' && tool.sidepanel) {
820
+ const action = actions.find(a => a.toolKey === toolId)
821
+ items.push({
822
+ id: `cfg:${section.id}:${toolId}`,
823
+ children: label,
824
+ keywords: [label, toolId].filter(Boolean),
825
+ closeOnSelect: entryCloseOnSelect,
826
+ onClick: () => { if (action) executeAction(action.id) },
827
+ })
828
+ continue
829
+ }
830
+
831
+ items.push({
832
+ id: `cfg:${section.id}:${toolId}`,
833
+ children: label,
834
+ keywords: [label, toolId].filter(Boolean),
835
+ closeOnSelect: entryCloseOnSelect,
836
+ onClick: () => executeAction(toolId),
837
+ })
838
+ }
839
+
840
+ // Add toolIcon and toolMeta to all items from their entry
841
+ const iconByToolId = new Map(entries.map(e => [e.toolId, { icon: e.toolIcon, meta: e.toolMeta }]))
842
+ for (const item of items) {
843
+ if (!item.toolIcon) {
844
+ const match = item.id?.match(/cfg:[^:]+:(.+)/)
845
+ if (match && iconByToolId.has(match[1])) {
846
+ const entry = iconByToolId.get(match[1])
847
+ item.toolIcon = entry.icon
848
+ if (!item.toolMeta && entry.meta) item.toolMeta = entry.meta
849
+ }
850
+ }
851
+ }
852
+
853
+ return {
854
+ group: {
855
+ heading: section.title,
856
+ id: `cfg:${section.id}`,
857
+ items,
858
+ },
859
+ subPages,
860
+ usedToolIds: entries.map(e => e.toolId),
861
+ }
862
+ }
863
+
864
+ function resolveRecentRoute(entry, prefix) {
865
+ switch (entry.type) {
866
+ case 'prototype':
867
+ return `${prefix}/${entry.key}`
868
+ case 'canvas':
869
+ return `${prefix}/canvas/${entry.key}`
870
+ case 'story': {
871
+ const data = getStoryData(entry.key)
872
+ const route = data?._route || `/components/${entry.key}`
873
+ return `${prefix}${route}`
874
+ }
875
+ default:
876
+ return null
877
+ }
878
+ }
879
+
880
+ /**
881
+ * Build a map of author → artifacts from the prototype index.
882
+ * Returns { authorIndex: Map<lowercase-author, { author, items[] }> }
883
+ */
884
+ function buildAuthorIndex(prefix) {
885
+ const index = buildPrototypeIndex()
886
+ const authorMap = new Map()
887
+
888
+ function addItem(author, item) {
889
+ const key = author.toLowerCase()
890
+ if (!authorMap.has(key)) authorMap.set(key, { author, items: [] })
891
+ authorMap.get(key).items.push(item)
892
+ }
893
+
894
+ function processAuthors(authors, item) {
895
+ if (!authors) return
896
+ const list = Array.isArray(authors) ? authors : [authors]
897
+ for (const a of list) if (a) addItem(a, item)
898
+ }
899
+
900
+ for (const p of index.prototypes) {
901
+ processAuthors(p.author, { name: p.name, route: `${prefix}/${p.dirName}`, id: p.dirName, type: 'Prototype' })
902
+ }
903
+ for (const f of index.folders) {
904
+ for (const p of f.prototypes) {
905
+ processAuthors(p.author, { name: p.name, route: `${prefix}/${p.dirName}`, id: p.dirName, type: 'Prototype' })
906
+ }
907
+ if (f.canvases) {
908
+ for (const c of f.canvases) {
909
+ processAuthors(c.author, { name: c.name, route: `${prefix}${c.route}`, id: c.dirName, type: 'Canvas' })
910
+ }
911
+ }
912
+ }
913
+ for (const c of index.canvases) {
914
+ processAuthors(c.author, { name: c.name, route: `${prefix}${c.route}`, id: c.dirName, type: 'Canvas' })
915
+ }
916
+
917
+ return authorMap
918
+ }
919
+
920
+ /**
921
+ * Build the JSON structure for react-cmdk from all data providers.
922
+ * Entirely config-driven — all sections come from commandPalette.sections.
923
+ */
924
+ function buildPaletteItems(basePath, onCreateAction, onNavigateToPage) {
925
+ const base = (basePath || '/').replace(/\/+$/, '')
926
+ const prefix = base === '/' ? '' : base
927
+
928
+ const { groups, toolMenus, hiddenFromSearchIds } = buildConfigSections(prefix, onNavigateToPage, onCreateAction)
929
+ const authorIndex = buildAuthorIndex(prefix)
930
+
931
+ return { groups, toolMenus, authorIndex, hiddenFromSearchIds }
932
+ }
933
+
934
+ /**
935
+ * StoryboardCommandPalette — React command palette using react-cmdk.
936
+ * Mounted at app root, listens for custom events from CoreUIBar.
937
+ */
938
+ export default function StoryboardCommandPalette({ basePath }) {
939
+ const [open, setOpen] = useState(false)
940
+ const [search, setSearch] = useState('')
941
+ const [items, setItems] = useState([])
942
+ const [toolMenus, setToolMenus] = useState([])
943
+ const [authorIndex, setAuthorIndex] = useState(new Map())
944
+ const [hiddenFromSearchIds, setHiddenFromSearchIds] = useState(new Set())
945
+ const [activePage, setActivePage] = useState('root')
946
+ const [createType, setCreateType] = useState(null)
947
+ const [currentTheme, setCurrentTheme] = useState(() => getTheme())
948
+ const [refreshKey, setRefreshKey] = useState(0)
949
+
950
+ // Track modifier keys for link items (cmd/ctrl → new tab, alt → copy link).
951
+ // Updated from the most recent keyboard/mouse event via a capturing listener
952
+ // on the document so it fires before cmdk's own handlers.
953
+ const modifierHeldRef = useRef(false)
954
+ const altHeldRef = useRef(false)
955
+ useEffect(() => {
956
+ const track = (e) => { modifierHeldRef.current = e.metaKey || e.ctrlKey; altHeldRef.current = e.altKey }
957
+ const reset = () => { modifierHeldRef.current = false; altHeldRef.current = false }
958
+ document.addEventListener('keydown', track, true)
959
+ document.addEventListener('keyup', reset, true)
960
+ document.addEventListener('mousedown', track, true)
961
+ return () => {
962
+ document.removeEventListener('keydown', track, true)
963
+ document.removeEventListener('keyup', reset, true)
964
+ document.removeEventListener('mousedown', track, true)
965
+ }
966
+ }, [])
967
+
968
+ // Keep currentTheme in sync when theme changes
969
+ useEffect(() => {
970
+ const handler = (e) => setCurrentTheme(e.detail.theme)
971
+ document.addEventListener('storyboard:theme:changed', handler)
972
+ return () => document.removeEventListener('storyboard:theme:changed', handler)
973
+ }, [])
974
+
975
+ function handleCreateAction(type) {
976
+ setOpen(false)
977
+ requestAnimationFrame(() => setCreateType(type))
978
+ }
979
+
980
+ function handleNavigateToPage(pageId) {
981
+ setSearch('')
982
+ setActivePage(pageId)
983
+ }
984
+
985
+ // Listen for Cmd+K directly to toggle the palette
986
+ useEffect(() => {
987
+ function handleKeyDown(e) {
988
+ if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
989
+ e.preventDefault()
990
+ const built = buildPaletteItems(basePath, handleCreateAction, handleNavigateToPage)
991
+ setItems(built.groups)
992
+ setToolMenus(built.toolMenus)
993
+ setAuthorIndex(built.authorIndex)
994
+ setHiddenFromSearchIds(built.hiddenFromSearchIds || new Set())
995
+ setSearch('')
996
+ setActivePage('root')
997
+ setOpen(prev => !prev)
998
+ }
999
+ }
1000
+
1001
+ document.addEventListener('keydown', handleKeyDown)
1002
+ return () => {
1003
+ document.removeEventListener('keydown', handleKeyDown)
1004
+ }
1005
+ }, [basePath])
1006
+
1007
+ // Listen for toggle/open events from toolbar buttons (e.g. CommandPaletteTrigger)
1008
+ useEffect(() => {
1009
+ function handleToggle() {
1010
+ setOpen(prev => {
1011
+ if (!prev) {
1012
+ // Use setTimeout to set items after open state is committed
1013
+ setTimeout(() => {
1014
+ const built = buildPaletteItems(basePath, handleCreateAction, handleNavigateToPage)
1015
+ setItems(built.groups)
1016
+ setToolMenus(built.toolMenus)
1017
+ setAuthorIndex(built.authorIndex)
1018
+ setHiddenFromSearchIds(built.hiddenFromSearchIds || new Set())
1019
+ setSearch('')
1020
+ setActivePage('root')
1021
+ }, 0)
1022
+ }
1023
+ return !prev
1024
+ })
1025
+ }
1026
+
1027
+ function handleOpen() {
1028
+ const built = buildPaletteItems(basePath, handleCreateAction, handleNavigateToPage)
1029
+ setItems(built.groups)
1030
+ setToolMenus(built.toolMenus)
1031
+ setAuthorIndex(built.authorIndex)
1032
+ setHiddenFromSearchIds(built.hiddenFromSearchIds || new Set())
1033
+ setSearch('')
1034
+ setActivePage('root')
1035
+ setOpen(true)
1036
+ }
1037
+
1038
+ document.addEventListener('storyboard:toggle-palette', handleToggle)
1039
+ document.addEventListener('storyboard:open-palette', handleOpen)
1040
+ return () => {
1041
+ document.removeEventListener('storyboard:toggle-palette', handleToggle)
1042
+ document.removeEventListener('storyboard:open-palette', handleOpen)
1043
+ }
1044
+ }, [basePath])
1045
+
1046
+ // Rebuild palette items when a toggle is clicked (refreshKey changes)
1047
+ useEffect(() => {
1048
+ if (refreshKey === 0) return
1049
+ const built = buildPaletteItems(basePath, handleCreateAction, handleNavigateToPage)
1050
+ // eslint-disable-next-line react-hooks/set-state-in-effect
1051
+ setItems(built.groups)
1052
+ setToolMenus(built.toolMenus)
1053
+ }, [refreshKey, basePath])
1054
+
1055
+ const handleChangeOpen = useCallback((value) => {
1056
+ if (!value) {
1057
+ // Escape from a sub-page goes back to root instead of closing
1058
+ if (activePage !== 'root') {
1059
+ setActivePage('root')
1060
+ setSearch('')
1061
+ return
1062
+ }
1063
+ setOpen(false)
1064
+ setActivePage('root')
1065
+ }
1066
+ }, [activePage])
1067
+
1068
+ // Flatten sub-page options into searchable groups so they appear in root search
1069
+ const subPageGroups = useMemo(() => {
1070
+ return toolMenus.map(menu => ({
1071
+ heading: menu.label || menu.title || menu.id,
1072
+ id: `subpage:${menu.id}`,
1073
+ items: (menu.options || []).map((opt, i) => ({
1074
+ id: `subpage:${menu.id}:${i}`,
1075
+ label: opt.label,
1076
+ icon: opt.icon,
1077
+ isToggle: opt.type === 'toggle',
1078
+ isActiveToggle: opt.type === 'toggle' && opt.active,
1079
+ isActiveTheme: opt.toolHandler === 'core:theme' && opt.value === currentTheme,
1080
+ keywords: [opt.label, menu.label || menu.id],
1081
+ onSelect: () => {
1082
+ if (opt.execute) {
1083
+ opt.execute()
1084
+ } else if (opt.toolHandler === 'core:theme' && opt.value) {
1085
+ setTheme(opt.value)
1086
+ } else if (opt.action) {
1087
+ executeAction(opt.action, opt.value)
1088
+ }
1089
+ if (opt.type === 'toggle') {
1090
+ setRefreshKey(k => k + 1)
1091
+ } else {
1092
+ setOpen(false)
1093
+ setActivePage('root')
1094
+ }
1095
+ },
1096
+ })),
1097
+ })).filter(g => g.items.length > 0)
1098
+ }, [toolMenus, currentTheme, refreshKey])
1099
+
1100
+ // Build author groups from the index
1101
+ const authorGroups = useMemo(() => {
1102
+ const groups = []
1103
+ for (const [, { author, items: authorItems }] of authorIndex) {
1104
+ groups.push({
1105
+ heading: `Artifacts by @${author}`,
1106
+ id: `author:${author.toLowerCase()}`,
1107
+ author,
1108
+ items: authorItems.map(item => ({
1109
+ id: `author:${item.id}`,
1110
+ label: item.name,
1111
+ type: item.type,
1112
+ url: item.route,
1113
+ keywords: [item.name, item.id, item.type, author, `@${author}`],
1114
+ onSelect: () => {
1115
+ trackRecent(item.type.toLowerCase(), item.id, item.name)
1116
+ setOpen(false)
1117
+ setActivePage('root')
1118
+ window.location.href = item.route
1119
+ },
1120
+ })),
1121
+ })
1122
+ }
1123
+ return groups
1124
+ }, [authorIndex])
1125
+
1126
+ // Remove consecutive separators and leading/trailing separators
1127
+ const cleanedItems = useMemo(() => {
1128
+ const result = []
1129
+ for (const item of items) {
1130
+ const isSep = item.id?.startsWith('cfg:sep')
1131
+ if (isSep && (result.length === 0 || result[result.length - 1].id?.startsWith('cfg:sep'))) continue
1132
+ result.push(item)
1133
+ }
1134
+ while (result.length > 0 && result[result.length - 1].id?.startsWith('cfg:sep')) result.pop()
1135
+ return result
1136
+ }, [items])
1137
+
1138
+ // Build search value string from keywords array
1139
+ function itemValue(item) {
1140
+ const parts = []
1141
+ if (typeof item.children === 'string') parts.push(item.children)
1142
+ if (item.label) parts.push(item.label)
1143
+ if (item.keywords) parts.push(...item.keywords)
1144
+ return parts.filter(Boolean).join(' ')
1145
+ }
1146
+
1147
+ // Custom filter using scoreMatch for better ranking.
1148
+ // scoreMatch tiers: prefix (100) > word-boundary (75) > substring (50) > fuzzy (5-25).
1149
+ // Normalizes to 0-1 for cmdk; weak fuzzy matches (score < 10) are hidden to
1150
+ // prevent garbage results from dominating above exact matches in other groups.
1151
+ const MAX_SCORE = 110
1152
+ const cmdkFilter = useCallback((value, search) => {
1153
+ if (!search) return 1
1154
+ const score = scoreMatch(value, search.toLowerCase().trim())
1155
+ if (score < 10) return 0
1156
+ return Math.min(1, score / MAX_SCORE)
1157
+ }, [])
1158
+
1159
+ function showCenterToast(message) {
1160
+ const el = document.createElement('div')
1161
+ Object.assign(el.style, {
1162
+ position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)',
1163
+ zIndex: '10000', padding: '0.625rem 1rem', borderRadius: '0.5rem',
1164
+ background: 'var(--bgColor-emphasis, #1f2328)', color: 'var(--fgColor-onEmphasis, #fff)',
1165
+ fontSize: '0.8125rem', fontFamily: 'var(--fontStack-sansSerif, system-ui)',
1166
+ opacity: '0', transition: 'opacity 0.15s ease',
1167
+ pointerEvents: 'none',
1168
+ })
1169
+ el.textContent = message
1170
+ document.body.appendChild(el)
1171
+ requestAnimationFrame(() => { el.style.opacity = '1' })
1172
+ setTimeout(() => { el.style.opacity = '0'; setTimeout(() => el.remove(), 200) }, 1800)
1173
+ }
1174
+
1175
+ function copyLinkToClipboard(url, itemType) {
1176
+ const fullUrl = url.startsWith('/') ? window.location.origin + url : url
1177
+ const isCanvasRoute = typeof window !== 'undefined' && window.location.pathname.includes('/canvas/')
1178
+ const isPasteable = itemType === 'prototype' || itemType === 'story'
1179
+ const shouldPaste = isCanvasRoute && isPasteable
1180
+
1181
+ navigator.clipboard.writeText(fullUrl).then(() => {
1182
+ showCenterToast(shouldPaste ? 'Link copied and pasted' : 'Link copied to clipboard')
1183
+ })
1184
+
1185
+ if (shouldPaste) {
1186
+ document.dispatchEvent(new CustomEvent('storyboard:canvas:paste-url', { detail: { url: fullUrl } }))
1187
+ }
1188
+ }
1189
+
1190
+ return (
1191
+ <>
1192
+ <Command.Dialog
1193
+ open={open}
1194
+ onOpenChange={handleChangeOpen}
1195
+ label="Command Menu"
1196
+ className="command-palette"
1197
+ shouldFilter={activePage === 'root'}
1198
+ filter={cmdkFilter}
1199
+ aria-describedby={undefined}
1200
+ onKeyDown={(e) => {
1201
+ if (e.key === 'Escape' && activePage !== 'root') {
1202
+ e.preventDefault()
1203
+ e.stopPropagation()
1204
+ setActivePage('root')
1205
+ setSearch('')
1206
+ }
1207
+ }}
1208
+ >
1209
+ <VisuallyHidden.Root asChild>
1210
+ <DialogPrimitive.Title>Command Menu</DialogPrimitive.Title>
1211
+ </VisuallyHidden.Root>
1212
+ <DialogPrimitive.Description className="sr-only" style={{ display: 'none' }} />
1213
+ <Command.Input
1214
+ placeholder={activePage === 'root'
1215
+ ? 'Search commands, prototypes, canvases, stories...'
1216
+ : `Search ${toolMenus.find(m => m.id === activePage)?.label || ''}...`
1217
+ }
1218
+ value={search}
1219
+ onValueChange={setSearch}
1220
+ />
1221
+ <Command.List>
1222
+ <Command.Empty>No results found.</Command.Empty>
1223
+
1224
+ {activePage === 'root' ? (
1225
+ <>
1226
+ {/* Sub-page options flattened for root search — rendered first
1227
+ so high-scoring items (e.g. "Copilot CLI" for query "cop")
1228
+ appear above weaker matches in later groups. */}
1229
+ {search && subPageGroups.map(group => (
1230
+ <Command.Group key={group.id} heading={group.heading}>
1231
+ {group.items.map(item => (
1232
+ <Command.Item
1233
+ key={item.id}
1234
+ value={itemValue(item)}
1235
+ onSelect={item.onSelect}
1236
+ >
1237
+ {item.icon && <Icon name={item.icon} size={ICON_SIZE} color="var(--fgColor-muted, #656d76)" />}
1238
+ <span style={{ display: 'flex', width: '100%', justifyContent: 'space-between', alignItems: 'center' }}>
1239
+ <span>{item.label}</span>
1240
+ {(item.isActiveToggle || item.isActiveTheme) && <span>✓</span>}
1241
+ </span>
1242
+ </Command.Item>
1243
+ ))}
1244
+ </Command.Group>
1245
+ ))}
1246
+
1247
+ {/* Main config-driven groups */}
1248
+ {cleanedItems.map((list) => (
1249
+ list.id?.startsWith('cfg:sep') ? (
1250
+ !search && <Command.Separator key={list.id} />
1251
+ ) : (
1252
+ <Command.Group key={list.id} heading={list.heading}>
1253
+ {list.items.map(({ id, children, keywords, onClick, onAltClick, itemType, toolIcon, toolMeta, closeOnSelect, hideFromSearch, url }) => {
1254
+ if (search && hideFromSearch) return null
1255
+ if (hiddenFromSearchIds.size > 0) {
1256
+ for (const toolId of hiddenFromSearchIds) {
1257
+ if (id?.includes(toolId)) return null
1258
+ }
1259
+ }
1260
+ return (
1261
+ <Command.Item
1262
+ key={id}
1263
+ value={itemValue({ children, keywords })}
1264
+ onSelect={() => {
1265
+ if (url && altHeldRef.current) {
1266
+ copyLinkToClipboard(url, itemType)
1267
+ } else if (url && modifierHeldRef.current) {
1268
+ window.open(url, '_blank')
1269
+ } else if (onAltClick && altHeldRef.current) {
1270
+ onAltClick()
1271
+ } else {
1272
+ onClick?.()
1273
+ }
1274
+ if (closeOnSelect !== false) {
1275
+ setOpen(false)
1276
+ setActivePage('root')
1277
+ }
1278
+ }}
1279
+ >
1280
+ <ItemIcon type={itemType} toolIcon={toolIcon} toolMeta={toolMeta} />
1281
+ {children}
1282
+ </Command.Item>
1283
+ )
1284
+ })}
1285
+ </Command.Group>
1286
+ )
1287
+ ))}
1288
+
1289
+ {/* Author groups */}
1290
+ {search && authorGroups.map(group => (
1291
+ <Command.Group key={group.id} heading={group.heading}>
1292
+ {group.items.map(item => (
1293
+ <Command.Item
1294
+ key={item.id}
1295
+ value={itemValue(item)}
1296
+ onSelect={() => {
1297
+ if (item.url && altHeldRef.current) {
1298
+ copyLinkToClipboard(item.url, item.type?.toLowerCase())
1299
+ setOpen(false)
1300
+ setActivePage('root')
1301
+ } else if (item.url && modifierHeldRef.current) {
1302
+ window.open(item.url, '_blank')
1303
+ setOpen(false)
1304
+ setActivePage('root')
1305
+ } else {
1306
+ item.onSelect()
1307
+ }
1308
+ }}
1309
+ >
1310
+ <AvatarIcon username={group.author} />
1311
+ <span style={{ display: 'flex', width: '100%', justifyContent: 'space-between', alignItems: 'center' }}>
1312
+ <span>{item.label}</span>
1313
+ <span style={{ fontSize: '12px', color: 'var(--fgColor-muted, #999)' }}>{item.type}</span>
1314
+ </span>
1315
+ </Command.Item>
1316
+ ))}
1317
+ </Command.Group>
1318
+ ))}
1319
+ </>
1320
+ ) : (
1321
+ /* Tool-menu sub-pages */
1322
+ toolMenus.filter(menu => menu.id === activePage).map(menu => (
1323
+ <Command.Group key={menu.id} heading={menu.title || menu.label || menu.id}>
1324
+ {(menu.options || []).map((opt, i) => (
1325
+ <Command.Item
1326
+ key={`${menu.id}:${i}`}
1327
+ value={opt.label}
1328
+ onSelect={() => {
1329
+ if (opt.execute) {
1330
+ opt.execute()
1331
+ } else if (opt.toolHandler === 'core:theme' && opt.value) {
1332
+ setTheme(opt.value)
1333
+ } else if (opt.action) {
1334
+ executeAction(opt.action, opt.value)
1335
+ }
1336
+ setOpen(false)
1337
+ setActivePage('root')
1338
+ }}
1339
+ >
1340
+ {opt.icon && <Icon name={opt.icon} size={ICON_SIZE} color="var(--fgColor-muted, #656d76)" />}
1341
+ {opt.toolHandler === 'core:theme' && opt.value === currentTheme
1342
+ ? <span style={{ display: 'flex', width: '100%', justifyContent: 'space-between', alignItems: 'center' }}><span>{opt.label}</span><span>✓</span></span>
1343
+ : opt.label}
1344
+ </Command.Item>
1345
+ ))}
1346
+ </Command.Group>
1347
+ ))
1348
+ )}
1349
+ </Command.List>
1350
+ </Command.Dialog>
1351
+
1352
+ <CreateDialog
1353
+ type={createType}
1354
+ basePath={basePath}
1355
+ onClose={() => setCreateType(null)}
1356
+ />
1357
+ <BranchBar basePath={basePath} />
1358
+ <AuthModal />
1359
+ </>
1360
+ )
1361
+ }