@jxsuite/studio 4.1.0 → 5.1.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 (612) hide show
  1. package/README.md +1 -1
  2. package/data/elements-meta.json +5 -15
  3. package/data/html-meta.json +51 -6
  4. package/dist/chunks/{2020-n6tfwnda.js → 2020-g79n01v9.js} +14 -14
  5. package/dist/chunks/action-group-sp6tna8v.js +118 -0
  6. package/dist/chunks/adapter-nj7tnjfn.js +13 -0
  7. package/dist/chunks/cf-account-picker-qwkm9zrf.js +495 -0
  8. package/dist/chunks/cf-settings-71bv5zn9.js +20 -0
  9. package/dist/chunks/color-area-v17df7b8.js +154 -0
  10. package/dist/chunks/color-field-anwybnkn.js +208 -0
  11. package/dist/chunks/color-slider-44ez96fc.js +79 -0
  12. package/dist/chunks/color-xgg3kfw7.js +35 -0
  13. package/dist/chunks/combobox-5e7jev23.js +236 -0
  14. package/dist/chunks/components-vj72gqtq.js +20 -0
  15. package/dist/chunks/content-source-5mex4x1h.js +46 -0
  16. package/dist/chunks/convert-to-repeater-n64h8znc.js +639 -0
  17. package/dist/chunks/dialog-s890b918.js +85 -0
  18. package/dist/chunks/editor-3qhbg51x.js +35 -0
  19. package/dist/chunks/entry-commands-234hjnqw.js +42 -0
  20. package/dist/chunks/extension-sections-3hpcambp.js +651 -0
  21. package/dist/chunks/file-ops-8yhd83xr.js +47 -0
  22. package/dist/chunks/files-hnxqxtsg.js +72 -0
  23. package/dist/chunks/format-host-779sfp2n.js +40 -0
  24. package/dist/chunks/fs-events-b88z7g4h.js +18 -0
  25. package/dist/chunks/git-panel-xwj8fpab.js +49 -0
  26. package/dist/chunks/icons-yy10wk9r.js +402 -0
  27. package/dist/chunks/iframe-entry-15g3n6xv.js +114 -0
  28. package/dist/chunks/iframe-entry-b27vq81n.js +203 -0
  29. package/dist/chunks/iframe-entry-nbyxee1y.js +154 -0
  30. package/dist/chunks/iframe-host-syr0f9sb.js +115 -0
  31. package/dist/chunks/{index-wcnt3r9g.js → index-mdmyx6s2.js} +11 -11
  32. package/dist/chunks/index-n4j7qxyr.js +109 -0
  33. package/dist/chunks/{javascript-q8kpmfah.js → javascript-xvrfrpkb.js} +8 -9
  34. package/dist/chunks/{jsonMode-dkr040ga.js → jsonMode-tyx2kj6p.js} +526 -527
  35. package/dist/chunks/jx-validate-j0wxk72w.js +15 -0
  36. package/dist/chunks/layers-ff81wthn.js +30 -0
  37. package/dist/chunks/layers-panel-pnnghw12.js +40 -0
  38. package/dist/chunks/listbox-6v2ac08j.js +44 -0
  39. package/dist/chunks/lit-html-m244w9nh.js +17 -0
  40. package/dist/chunks/media-picker-cqhhxhhe.js +33 -0
  41. package/dist/chunks/menu-9p2w46gj.js +15 -0
  42. package/dist/chunks/menu-q99sy9rf.js +285 -0
  43. package/dist/chunks/{monaco-binding-rj6xpx9y.js → monaco-binding-sw6en4dw.js} +12 -15
  44. package/dist/chunks/monaco-buffer-bhy3hb6m.js +24 -0
  45. package/dist/chunks/{monaco-setup-vx9bhp1d.js → monaco-setup-8r62fj6y.js} +27 -37
  46. package/dist/chunks/notify-628t0anh.js +21 -0
  47. package/dist/chunks/number-field-5cndyft8.js +72 -0
  48. package/dist/chunks/parse-0zx14y15.js +10 -0
  49. package/dist/chunks/platform-ts9cg1a4.js +15 -0
  50. package/dist/chunks/popover-47z3ytva.js +25 -0
  51. package/dist/chunks/publish-panel-5zt840sy.js +27 -0
  52. package/dist/chunks/quick-search-3748564r.js +45 -0
  53. package/dist/chunks/references-qvvxej7h.js +26 -0
  54. package/dist/chunks/right-panel-eccz92qt.js +45 -0
  55. package/dist/chunks/section-registry-5pq32t0q.js +25 -0
  56. package/dist/chunks/select-2r16ez41.js +49 -0
  57. package/dist/chunks/split-g7nm1rgb.js +191 -0
  58. package/dist/chunks/studio-0hqbpbd0.js +62 -0
  59. package/dist/chunks/{studio-kryvsw6j.js → studio-1k325pwf.js} +48 -48
  60. package/dist/chunks/{studio-b55y6ewx.js → studio-1knfqgy7.js} +58 -58
  61. package/dist/chunks/{studio-2b946z26.js → studio-1z1kcjv4.js} +3628 -6095
  62. package/dist/chunks/studio-3773086z.js +111 -0
  63. package/dist/chunks/{studio-4r1pv6zf.js → studio-38emantv.js} +22 -5
  64. package/dist/chunks/{studio-x813zp48.js → studio-3rz5md06.js} +30 -30
  65. package/dist/chunks/{studio-ynxwkrzd.js → studio-4wbb9ct0.js} +1953 -1785
  66. package/dist/chunks/studio-5te5tqng.js +81 -0
  67. package/dist/chunks/studio-5wt9nvhd.js +4763 -0
  68. package/dist/chunks/{studio-p4ngc046.js → studio-6ewqv390.js} +9 -9
  69. package/dist/chunks/{studio-9ab8gqsm.js → studio-6nzaktvy.js} +7 -7
  70. package/dist/chunks/studio-6xa21ej9.js +28 -0
  71. package/dist/chunks/{studio-5ehhackm.js → studio-727xh0e3.js} +19 -19
  72. package/dist/chunks/{studio-gcye7qka.js → studio-7e11jpa9.js} +21 -21
  73. package/dist/chunks/{studio-q7v10cg2.js → studio-7r42c6ca.js} +17 -7
  74. package/dist/chunks/studio-7xnmgv66.js +13615 -0
  75. package/dist/chunks/studio-8scdew0e.js +258 -0
  76. package/dist/chunks/{studio-fbf92ckf.js → studio-93zafjcm.js} +275 -163
  77. package/dist/chunks/{studio-dg7zyt5x.js → studio-98b3z07n.js} +1 -1
  78. package/dist/chunks/studio-a79k90n9.js +1162 -0
  79. package/dist/chunks/studio-aj37eer3.js +422 -0
  80. package/dist/chunks/studio-b0k5tqgp.js +2432 -0
  81. package/dist/chunks/studio-b4s9eykd.js +2811 -0
  82. package/dist/chunks/{studio-f3wqjtk5.js → studio-d0300636.js} +5076 -18
  83. package/dist/chunks/{studio-b9b6t1dg.js → studio-e1f24eff.js} +5664 -5666
  84. package/dist/chunks/studio-e1pgga3k.js +839 -0
  85. package/dist/chunks/{studio-g1wdche0.js → studio-esrxgwh6.js} +80 -80
  86. package/dist/chunks/{studio-q43a5y9h.js → studio-g3jevyjt.js} +16 -16
  87. package/dist/chunks/studio-gb13095a.js +2806 -0
  88. package/dist/chunks/{studio-3n4ndrzh.js → studio-h4nvnjvz.js} +303 -303
  89. package/dist/chunks/{studio-dm19q43r.js → studio-hhvs4sdg.js} +10 -10
  90. package/dist/chunks/{studio-x4qkdat5.js → studio-js8nv53n.js} +1749 -2481
  91. package/dist/chunks/{studio-qjy2z9bv.js → studio-jxp12rnj.js} +32 -137
  92. package/dist/chunks/{studio-trxrabqf.js → studio-k75n7vhp.js} +527 -283
  93. package/dist/chunks/{studio-q9jd1a60.js → studio-kcgff4r0.js} +15 -18
  94. package/dist/chunks/studio-kkqkcp3d.js +17668 -0
  95. package/dist/chunks/studio-mhhyamxf.js +763 -0
  96. package/dist/chunks/{studio-cft9r4r7.js → studio-nq5yf5mp.js} +22 -22
  97. package/dist/chunks/{studio-685s0ytq.js → studio-nqgawfks.js} +572 -572
  98. package/dist/chunks/studio-nzzpvzx9.js +185 -0
  99. package/dist/chunks/{studio-xrapxwwc.js → studio-p1dw8wfw.js} +55 -29
  100. package/dist/chunks/{studio-3bqpr0zh.js → studio-pxbgypmg.js} +299 -110
  101. package/dist/chunks/studio-q0a3yk4n.js +33 -0
  102. package/dist/chunks/{studio-wcy8trfx.js → studio-q3x06vmj.js} +23 -22
  103. package/dist/chunks/{studio-6yw4y3my.js → studio-qhhm6hbt.js} +8 -8
  104. package/dist/chunks/studio-qk4bfsq2.js +362 -0
  105. package/dist/chunks/{studio-msd847zn.js → studio-qk90fgqm.js} +361 -90
  106. package/dist/chunks/{studio-jkftxyvz.js → studio-r6nvacqs.js} +5861 -2154
  107. package/dist/chunks/{studio-53n8bp04.js → studio-r6xmmtfh.js} +1 -1
  108. package/dist/chunks/{studio-tvgyw9xw.js → studio-rnyf3f8e.js} +83 -250
  109. package/dist/chunks/studio-rs76k67r.js +12 -0
  110. package/dist/chunks/studio-sbjyc8bt.js +954 -0
  111. package/dist/chunks/{studio-1wagb550.js → studio-syg2tadj.js} +11 -11
  112. package/dist/chunks/{studio-t5wqwzdh.js → studio-tjjk7jvk.js} +1 -1
  113. package/dist/chunks/studio-tqgryxwg.js +1171 -0
  114. package/dist/chunks/studio-try13h8c.js +8448 -0
  115. package/dist/chunks/studio-vfq8qpjt.js +1219 -0
  116. package/dist/chunks/studio-vhv5tgxm.js +51 -0
  117. package/dist/chunks/{studio-ahn3pdgx.js → studio-vw7mzp6z.js} +52 -38
  118. package/dist/chunks/studio-wqpngpsk.js +174 -0
  119. package/dist/chunks/studio-wqz6dggm.js +1356 -0
  120. package/dist/chunks/{studio-92ab2jqe.js → studio-y9zedyjn.js} +14 -14
  121. package/dist/chunks/studio-yxa81f45.js +12 -0
  122. package/dist/chunks/studio-zdn2g6dt.js +148 -0
  123. package/dist/chunks/swatch-group-t0n6h2rn.js +139 -0
  124. package/dist/chunks/tabs-wbp1cdws.js +156 -0
  125. package/dist/chunks/textfield-abk6qyee.js +73 -0
  126. package/dist/chunks/toast-780pvfbv.js +27 -0
  127. package/dist/chunks/toast-host-3sqv46sn.js +116 -0
  128. package/dist/chunks/toolbar-xbh5bhtr.js +194 -0
  129. package/dist/chunks/tooltip-eptb614w.js +369 -0
  130. package/dist/chunks/tree-a73p0pqm.js +367 -0
  131. package/dist/chunks/{tsMode-76kr53q0.js → tsMode-jy4m86rv.js} +83 -85
  132. package/dist/chunks/workspace-t55jdjbx.js +69 -0
  133. package/dist/chunks/{ws-client-6shc5rze.js → ws-client-ny4zc51r.js} +20 -22
  134. package/dist/iframe-entry.js +1873 -673
  135. package/dist/studio.js +52757 -36535
  136. package/dist/workers/editor.worker.js +1040 -1085
  137. package/dist/workers/json.worker.js +1946 -1991
  138. package/dist/workers/ts.worker.js +4960 -5005
  139. package/index.html +1 -0
  140. package/package.json +18 -52
  141. package/src/about/about-modal.ts +72 -123
  142. package/src/browse/library-commands.ts +20 -49
  143. package/src/browse/library-layouts.ts +87 -218
  144. package/src/browse/library-pane.ts +406 -438
  145. package/src/canvas/canvas-helpers.ts +0 -49
  146. package/src/canvas/canvas-live-render.ts +25 -2
  147. package/src/canvas/canvas-render.ts +666 -231
  148. package/src/canvas/canvas-surface.ts +0 -1
  149. package/src/canvas/canvas-utils.ts +333 -113
  150. package/src/canvas/dialog-path.ts +76 -0
  151. package/src/canvas/dialog-state.ts +67 -0
  152. package/src/canvas/diff-marks.ts +252 -0
  153. package/src/canvas/diff-toolbar.ts +349 -0
  154. package/src/canvas/diff-view.ts +123 -0
  155. package/src/canvas/edit-width-drag.ts +187 -0
  156. package/src/canvas/edit-width.ts +150 -0
  157. package/src/canvas/iframe-entry.ts +125 -8
  158. package/src/canvas/iframe-host.ts +271 -15
  159. package/src/canvas/iframe-interaction.ts +133 -13
  160. package/src/canvas/iframe-patch.ts +7 -1
  161. package/src/canvas/iframe-position.ts +5 -6
  162. package/src/canvas/iframe-protocol.ts +106 -5
  163. package/src/canvas/iframe-render.ts +373 -5
  164. package/src/canvas/iframe-slash.ts +2 -2
  165. package/src/canvas/iframe-subtree.ts +5 -8
  166. package/src/canvas/path-mapping.ts +25 -3
  167. package/src/canvas/popover-path.ts +106 -0
  168. package/src/canvas/popover-state.ts +155 -0
  169. package/src/canvas/surface-registry.ts +21 -10
  170. package/src/collab/presence-chips.ts +51 -73
  171. package/src/commands/app-commands.ts +10 -0
  172. package/src/commands/budget.ts +12 -0
  173. package/src/commands/command-args.ts +404 -6
  174. package/src/commands/defaults.ts +44 -14
  175. package/src/commands/live-context.ts +11 -5
  176. package/src/commands/reference.ts +12 -2
  177. package/src/commands/registry.ts +177 -10
  178. package/src/commands/run-reported.ts +120 -0
  179. package/src/content/entry-commands.ts +16 -23
  180. package/src/content/entry-editor.ts +144 -101
  181. package/src/editor/canvas-slash-bridge.ts +2 -2
  182. package/src/editor/context-menu.ts +49 -200
  183. package/src/editor/convert-to-component.ts +42 -82
  184. package/src/editor/convert-to-repeater.ts +132 -173
  185. package/src/editor/inline-edit.ts +11 -0
  186. package/src/editor/shortcuts.ts +76 -49
  187. package/src/editor/slash-menu.ts +150 -157
  188. package/src/files/components.ts +39 -0
  189. package/src/files/file-ops.ts +117 -11
  190. package/src/files/files.ts +944 -660
  191. package/src/files/json-layout.ts +474 -0
  192. package/src/files/serialize-document.ts +8 -1
  193. package/src/format/convert-file.ts +16 -10
  194. package/src/format/format-host.ts +50 -1
  195. package/src/github/github-auth.ts +71 -29
  196. package/src/github/github-publish.ts +57 -65
  197. package/src/grid/cell-editors.ts +46 -4
  198. package/src/grid/cell-popovers.ts +65 -62
  199. package/src/grid/grid-open.ts +86 -64
  200. package/src/grid/grid-panel.ts +390 -469
  201. package/src/grid/redirects-grid.ts +40 -44
  202. package/src/hosting/layout.ts +2 -0
  203. package/src/i18n/i18n-commands.ts +137 -62
  204. package/src/media/media-pane.ts +83 -133
  205. package/src/new-project/add-repo-modal.ts +125 -176
  206. package/src/new-project/import-tab.ts +181 -252
  207. package/src/new-project/location-fields.ts +93 -160
  208. package/src/new-project/new-project-modal.ts +376 -367
  209. package/src/packages/jxsuite-update.ts +76 -18
  210. package/src/packages/pull-package-sync.ts +5 -1
  211. package/src/panels/activity-panel.ts +137 -104
  212. package/src/panels/ai-chat/chat-markdown.ts +15 -13
  213. package/src/panels/ai-chat/chat-view.ts +307 -472
  214. package/src/panels/ai-chat/composer.ts +175 -163
  215. package/src/panels/ai-chat/sessions-view.ts +31 -62
  216. package/src/panels/ai-panel.ts +213 -185
  217. package/src/panels/block-action-bar.ts +444 -645
  218. package/src/panels/bottom-dock.ts +112 -47
  219. package/src/panels/chat-panel.ts +19 -14
  220. package/src/panels/data-explorer.ts +141 -102
  221. package/src/panels/data-grid.ts +183 -150
  222. package/src/panels/dnd.ts +75 -20
  223. package/src/panels/editors.ts +26 -19
  224. package/src/panels/elements-panel.ts +243 -186
  225. package/src/panels/empty-state.ts +26 -42
  226. package/src/panels/events-panel.ts +950 -729
  227. package/src/panels/formula-workspace.ts +434 -207
  228. package/src/panels/frontmatter-fields.ts +185 -153
  229. package/src/panels/frontmatter-panel.ts +411 -187
  230. package/src/panels/git-diff-open.ts +148 -0
  231. package/src/panels/git-panel.ts +679 -605
  232. package/src/panels/head-panel.ts +617 -435
  233. package/src/panels/i18n-panel.ts +148 -135
  234. package/src/panels/imports-panel.ts +388 -414
  235. package/src/panels/jump-bar.ts +175 -147
  236. package/src/panels/layers-panel.ts +785 -671
  237. package/src/panels/left-panel.ts +166 -94
  238. package/src/panels/navigator-panels.ts +1 -1
  239. package/src/panels/pane-context.ts +529 -677
  240. package/src/panels/pane-grid.ts +265 -241
  241. package/src/panels/panel-registry.ts +15 -4
  242. package/src/panels/problems-panel.ts +181 -99
  243. package/src/panels/properties-panel.ts +1525 -966
  244. package/src/panels/provenance.ts +31 -113
  245. package/src/panels/quick-search.ts +223 -184
  246. package/src/panels/right-panel.ts +135 -169
  247. package/src/panels/seo-modal.ts +346 -167
  248. package/src/panels/settings-menu.ts +114 -661
  249. package/src/panels/settings-pane.ts +102 -86
  250. package/src/panels/signals-panel.ts +1318 -1172
  251. package/src/panels/statement-editor.ts +556 -428
  252. package/src/panels/style-panel.ts +1367 -630
  253. package/src/panels/style-utils.ts +36 -32
  254. package/src/panels/stylebook-doc.ts +30 -73
  255. package/src/panels/stylebook-layers-panel.ts +202 -109
  256. package/src/panels/stylebook-panel.ts +124 -88
  257. package/src/panels/tab-strip.ts +386 -288
  258. package/src/platforms/cloud.ts +106 -16
  259. package/src/platforms/devserver.ts +13 -0
  260. package/src/publish/deploy-checklist.ts +124 -78
  261. package/src/publish/publish-commands.ts +4 -12
  262. package/src/publish/publish-panel.ts +134 -328
  263. package/src/services/a11y-report.ts +100 -43
  264. package/src/services/ai-command-tools.ts +538 -0
  265. package/src/services/ai-project-tools.ts +8 -2
  266. package/src/services/ai-session-store.ts +6 -0
  267. package/src/services/ai-system-prompt.ts +112 -24
  268. package/src/services/ai-tools.ts +36 -212
  269. package/src/services/ai-write-report.ts +174 -0
  270. package/src/services/document-assistant.ts +59 -8
  271. package/src/services/idle.ts +16 -14
  272. package/src/services/live-surfaces.ts +237 -0
  273. package/src/services/model-uri.ts +75 -0
  274. package/src/services/notify.ts +63 -6
  275. package/src/services/popover-report.ts +325 -0
  276. package/src/services/surface-registry.ts +56 -0
  277. package/src/services/tool-executor.ts +6 -0
  278. package/src/settings/contexts-section.ts +301 -299
  279. package/src/settings/contributed-section.ts +361 -222
  280. package/src/settings/css-vars-editor.ts +320 -325
  281. package/src/settings/defs-editor.ts +572 -535
  282. package/src/settings/dependencies-editor.ts +79 -107
  283. package/src/settings/extension-commands.ts +379 -0
  284. package/src/settings/extension-rows.ts +113 -0
  285. package/src/settings/extensions-section.ts +287 -0
  286. package/src/settings/general-settings.ts +130 -181
  287. package/src/settings/head-editor.ts +10 -305
  288. package/src/settings/locales-section.ts +146 -196
  289. package/src/settings/preferences-accounts.ts +1 -1
  290. package/src/settings/preferences-dialog.ts +350 -330
  291. package/src/settings/project-sections.ts +108 -181
  292. package/src/settings/schema-field-ui.ts +11 -399
  293. package/src/settings/section-registry.ts +8 -1
  294. package/src/settings/settings-document.ts +31 -29
  295. package/src/shell/tree.ts +42 -87
  296. package/src/shell.ts +48 -36
  297. package/src/studio.ts +104 -69
  298. package/src/style/token-ref.ts +0 -39
  299. package/src/surfaces/about.json +263 -0
  300. package/src/surfaces/about.ts +163 -0
  301. package/src/surfaces/add-repo.json +486 -0
  302. package/src/surfaces/add-repo.ts +236 -0
  303. package/src/surfaces/ai-chat.json +2185 -0
  304. package/src/surfaces/ai-chat.ts +549 -0
  305. package/src/surfaces/ai-credentials-form.json +332 -0
  306. package/src/surfaces/ai-credentials-form.ts +151 -0
  307. package/src/surfaces/ai-managed-connect.json +108 -0
  308. package/src/surfaces/ai-managed-connect.ts +123 -0
  309. package/src/surfaces/ai-model-picker.json +114 -0
  310. package/src/surfaces/ai-model-picker.ts +139 -0
  311. package/src/surfaces/block-action-bar.json +640 -0
  312. package/src/surfaces/block-action-bar.ts +368 -0
  313. package/src/surfaces/bottom-dock.json +154 -0
  314. package/src/surfaces/bottom-dock.ts +125 -0
  315. package/src/surfaces/canvas-stage.json +444 -0
  316. package/src/surfaces/canvas-stage.ts +253 -0
  317. package/src/surfaces/cf-account-picker.json +300 -0
  318. package/src/surfaces/cf-account-picker.ts +214 -0
  319. package/src/surfaces/commandbar.json +985 -0
  320. package/src/{panels/toolbar.ts → surfaces/commandbar.ts} +417 -391
  321. package/src/surfaces/convert-repeater.json +309 -0
  322. package/src/surfaces/convert-repeater.ts +276 -0
  323. package/src/surfaces/data-actions.json +157 -0
  324. package/src/surfaces/data-actions.ts +115 -0
  325. package/src/surfaces/dialog.json +289 -0
  326. package/src/surfaces/dialog.ts +343 -0
  327. package/src/surfaces/diff-toolbar.json +266 -0
  328. package/src/surfaces/diff-toolbar.ts +139 -0
  329. package/src/surfaces/doc-header.json +739 -0
  330. package/src/surfaces/doc-header.ts +223 -0
  331. package/src/surfaces/empty-state.json +152 -0
  332. package/src/surfaces/empty-state.ts +173 -0
  333. package/src/surfaces/entry-editor.json +281 -0
  334. package/src/surfaces/entry-editor.ts +191 -0
  335. package/src/surfaces/expression-editor.json +977 -0
  336. package/src/surfaces/expression-editor.ts +266 -0
  337. package/src/surfaces/files-panel.json +592 -0
  338. package/src/surfaces/files-panel.ts +301 -0
  339. package/src/surfaces/formula-palette.json +364 -0
  340. package/src/surfaces/formula-palette.ts +340 -0
  341. package/src/surfaces/git-panel.json +1822 -0
  342. package/src/surfaces/git-panel.ts +272 -0
  343. package/src/surfaces/github-auth.json +112 -0
  344. package/src/surfaces/github-auth.ts +139 -0
  345. package/src/surfaces/github-publish.json +206 -0
  346. package/src/surfaces/github-publish.ts +169 -0
  347. package/src/surfaces/grid-cell.json +218 -0
  348. package/src/surfaces/grid-cell.ts +120 -0
  349. package/src/surfaces/grid-open.json +147 -0
  350. package/src/surfaces/grid-open.ts +87 -0
  351. package/src/surfaces/grid-panel.json +706 -0
  352. package/src/surfaces/grid-panel.ts +206 -0
  353. package/src/surfaces/grid-replace.json +179 -0
  354. package/src/surfaces/grid-replace.ts +89 -0
  355. package/src/surfaces/grid-views.json +446 -0
  356. package/src/surfaces/grid-views.ts +172 -0
  357. package/src/surfaces/inspector-dock.json +187 -0
  358. package/src/surfaces/inspector-dock.ts +161 -0
  359. package/src/surfaces/jump-bar.json +243 -0
  360. package/src/surfaces/jump-bar.ts +129 -0
  361. package/src/surfaces/jxsuite-update.json +146 -0
  362. package/src/surfaces/jxsuite-update.ts +149 -0
  363. package/src/surfaces/library-pane.json +1509 -0
  364. package/src/surfaces/library-pane.ts +363 -0
  365. package/src/surfaces/logic-panel.json +1949 -0
  366. package/src/surfaces/logic-panel.ts +369 -0
  367. package/src/surfaces/logic-workspace.json +685 -0
  368. package/src/surfaces/logic-workspace.ts +238 -0
  369. package/src/surfaces/media-browser.json +303 -0
  370. package/src/surfaces/media-browser.ts +145 -0
  371. package/src/surfaces/media-field.json +163 -0
  372. package/src/surfaces/media-field.ts +116 -0
  373. package/src/surfaces/media-pane.json +513 -0
  374. package/src/surfaces/media-pane.ts +197 -0
  375. package/src/surfaces/menu.json +232 -0
  376. package/src/surfaces/menu.ts +375 -0
  377. package/src/surfaces/navigator-dock.json +113 -0
  378. package/src/surfaces/navigator-dock.ts +130 -0
  379. package/src/surfaces/new-project.json +1654 -0
  380. package/src/surfaces/new-project.ts +411 -0
  381. package/src/surfaces/palette.json +535 -0
  382. package/src/surfaces/pane-context.json +1497 -0
  383. package/src/surfaces/pane-context.ts +513 -0
  384. package/src/surfaces/pane-grid.json +188 -0
  385. package/src/surfaces/pane-grid.ts +250 -0
  386. package/src/surfaces/panel-activity.json +563 -0
  387. package/src/surfaces/panel-activity.ts +187 -0
  388. package/src/surfaces/panel-data.json +141 -0
  389. package/src/surfaces/panel-data.ts +156 -0
  390. package/src/surfaces/panel-deploy-checklist.json +283 -0
  391. package/src/surfaces/panel-deploy-checklist.ts +175 -0
  392. package/src/surfaces/panel-elements.json +367 -0
  393. package/src/surfaces/panel-elements.ts +175 -0
  394. package/src/surfaces/panel-i18n.json +569 -0
  395. package/src/surfaces/panel-i18n.ts +212 -0
  396. package/src/surfaces/panel-imports.json +780 -0
  397. package/src/surfaces/panel-imports.ts +209 -0
  398. package/src/surfaces/panel-outline.json +734 -0
  399. package/src/surfaces/panel-outline.ts +326 -0
  400. package/src/surfaces/panel-page.json +1017 -0
  401. package/src/surfaces/panel-page.ts +240 -0
  402. package/src/surfaces/panel-problems.json +497 -0
  403. package/src/surfaces/panel-problems.ts +157 -0
  404. package/src/surfaces/panel-signals.json +1973 -0
  405. package/src/surfaces/panel-signals.ts +436 -0
  406. package/src/surfaces/panel-stylebook-layers.json +317 -0
  407. package/src/surfaces/panel-stylebook-layers.ts +155 -0
  408. package/src/surfaces/preferences.json +850 -0
  409. package/src/surfaces/preferences.ts +332 -0
  410. package/src/surfaces/progress-modal.json +110 -0
  411. package/src/surfaces/progress-modal.ts +150 -0
  412. package/src/surfaces/properties-panel.json +1578 -0
  413. package/src/surfaces/properties-panel.ts +337 -0
  414. package/src/surfaces/publish.json +945 -0
  415. package/src/surfaces/publish.ts +392 -0
  416. package/src/surfaces/push-plan.json +110 -0
  417. package/src/surfaces/push-plan.ts +87 -0
  418. package/src/surfaces/rail.json +255 -0
  419. package/src/surfaces/rail.ts +206 -0
  420. package/src/surfaces/reference-field.json +191 -0
  421. package/src/surfaces/reference-field.ts +168 -0
  422. package/src/surfaces/schema-builder.json +1011 -0
  423. package/src/surfaces/schema-builder.ts +181 -0
  424. package/src/surfaces/schema-form.json +1013 -0
  425. package/src/surfaces/schema-form.ts +287 -0
  426. package/src/surfaces/secret-field.json +60 -0
  427. package/src/surfaces/secret-field.ts +146 -0
  428. package/src/surfaces/seo.json +932 -0
  429. package/src/surfaces/seo.ts +334 -0
  430. package/src/surfaces/settings-contexts.json +538 -0
  431. package/src/surfaces/settings-contexts.ts +196 -0
  432. package/src/surfaces/settings-contributed.json +489 -0
  433. package/src/surfaces/settings-contributed.ts +199 -0
  434. package/src/surfaces/settings-css-vars.json +820 -0
  435. package/src/surfaces/settings-css-vars.ts +199 -0
  436. package/src/surfaces/settings-defs.json +1363 -0
  437. package/src/surfaces/settings-defs.ts +232 -0
  438. package/src/surfaces/settings-deploy.json +108 -0
  439. package/src/surfaces/settings-deploy.ts +135 -0
  440. package/src/surfaces/settings-extensions.json +486 -0
  441. package/src/surfaces/settings-extensions.ts +201 -0
  442. package/src/surfaces/settings-head.json +779 -0
  443. package/src/surfaces/settings-head.ts +397 -0
  444. package/src/surfaces/settings-locales.json +420 -0
  445. package/src/surfaces/settings-locales.ts +137 -0
  446. package/src/surfaces/settings-overview.json +423 -0
  447. package/src/surfaces/settings-overview.ts +218 -0
  448. package/src/surfaces/settings-packages.json +549 -0
  449. package/src/surfaces/settings-packages.ts +158 -0
  450. package/src/surfaces/settings-pane.json +147 -0
  451. package/src/surfaces/settings-pane.ts +150 -0
  452. package/src/surfaces/settings-rawjson.json +104 -0
  453. package/src/surfaces/settings-rawjson.ts +107 -0
  454. package/src/surfaces/shell.json +99 -0
  455. package/src/surfaces/shell.ts +30 -0
  456. package/src/surfaces/slash-menu.json +180 -0
  457. package/src/surfaces/slash-menu.ts +272 -0
  458. package/src/surfaces/statements.json +607 -0
  459. package/src/surfaces/statements.ts +238 -0
  460. package/src/surfaces/statusbar.json +94 -0
  461. package/src/{panels → surfaces}/statusbar.ts +164 -90
  462. package/src/surfaces/style-panel.json +1643 -0
  463. package/src/surfaces/style-panel.ts +338 -0
  464. package/src/surfaces/stylebook-chrome.json +122 -0
  465. package/src/surfaces/stylebook-chrome.ts +122 -0
  466. package/src/surfaces/tab-strip.json +448 -0
  467. package/src/surfaces/tab-strip.ts +200 -0
  468. package/src/surfaces/target-line.json +419 -0
  469. package/src/surfaces/target-line.ts +349 -0
  470. package/src/surfaces/toasts.json +189 -0
  471. package/src/surfaces/welcome.json +571 -0
  472. package/src/surfaces/welcome.ts +338 -0
  473. package/src/tabs/project-config.ts +111 -21
  474. package/src/tabs/tab.ts +52 -1
  475. package/src/types.ts +22 -0
  476. package/src/ui/ai-credentials-form.ts +104 -100
  477. package/src/ui/ai-managed-connect.ts +54 -36
  478. package/src/ui/ai-model-picker.ts +75 -47
  479. package/src/ui/cf-account-picker.ts +76 -141
  480. package/src/ui/color-selector.ts +80 -301
  481. package/src/ui/dynamic-slot.ts +43 -283
  482. package/src/ui/expression-editor.ts +797 -576
  483. package/src/ui/field-input.ts +8 -181
  484. package/src/ui/form-controls.ts +838 -366
  485. package/src/ui/formula-chips.ts +80 -85
  486. package/src/ui/kit.ts +33 -0
  487. package/src/ui/layers.ts +296 -593
  488. package/src/ui/media-picker.ts +203 -284
  489. package/src/ui/panel-resize.ts +251 -64
  490. package/src/ui/popover-surface.ts +153 -0
  491. package/src/ui/progress-modal.ts +22 -60
  492. package/src/ui/regions.ts +13 -7
  493. package/src/ui/schema-form.ts +906 -440
  494. package/src/ui/surface.ts +122 -0
  495. package/src/ui/unit-selector.ts +15 -131
  496. package/src/ui/value-source.ts +14 -0
  497. package/src/ui/virtual-window.ts +88 -9
  498. package/src/utils/canvas-media.ts +72 -0
  499. package/src/utils/edit-display.ts +7 -2
  500. package/src/utils/element-selectors.ts +69 -0
  501. package/src/utils/link-target.ts +2 -2
  502. package/src/utils/studio-utils.ts +0 -10
  503. package/src/view.ts +15 -0
  504. package/src/workspace/pane-derive.ts +36 -14
  505. package/src/workspace/workspace.ts +75 -6
  506. package/styles/canvas.css +72 -467
  507. package/styles/forced-colors.css +48 -82
  508. package/styles/forced-colors.json +41 -0
  509. package/styles/inspector.css +2 -1301
  510. package/styles/overlays.css +96 -2823
  511. package/styles/panels.css +6 -1423
  512. package/styles/shell-frame.css +272 -0
  513. package/styles/shell-frame.json +227 -0
  514. package/styles/shell.css +0 -1568
  515. package/styles/tokens.css +65 -136
  516. package/styles/tokens.json +84 -0
  517. package/dist/chunks/Overlay.dev-etqjwyf9.js +0 -23
  518. package/dist/chunks/adapter-jkcyhgtr.js +0 -14
  519. package/dist/chunks/cf-account-picker-en86v7x7.js +0 -163
  520. package/dist/chunks/cf-settings-45fgyx8y.js +0 -21
  521. package/dist/chunks/components-zmx10t62.js +0 -21
  522. package/dist/chunks/content-source-48xg86wj.js +0 -49
  523. package/dist/chunks/convert-to-repeater-43tfn7cs.js +0 -268
  524. package/dist/chunks/editor-cptq5jp1.js +0 -37
  525. package/dist/chunks/entry-commands-e63f1xc0.js +0 -46
  526. package/dist/chunks/extension-sections-txv08xxg.js +0 -311
  527. package/dist/chunks/file-ops-wbh4hhsx.js +0 -48
  528. package/dist/chunks/files-3kfkwhk4.js +0 -93
  529. package/dist/chunks/flow-qbfgrpeh.js +0 -634
  530. package/dist/chunks/focus-trap.esm-9t2drp0g.js +0 -1025
  531. package/dist/chunks/focus-visible-6eg70z18.js +0 -167
  532. package/dist/chunks/format-host-w16q81tf.js +0 -43
  533. package/dist/chunks/fs-events-hrrg328g.js +0 -21
  534. package/dist/chunks/git-panel-532whr1y.js +0 -59
  535. package/dist/chunks/index-zqxmqxmh.js +0 -109
  536. package/dist/chunks/jx-validate-8er6gbxy.js +0 -19
  537. package/dist/chunks/layers-panel-07hm7g5j.js +0 -67
  538. package/dist/chunks/layers-q75tj2mf.js +0 -45
  539. package/dist/chunks/lit-html-j055mkh8.js +0 -22
  540. package/dist/chunks/monaco-buffer-dqw0xxpx.js +0 -25
  541. package/dist/chunks/notify-yjm79n4s.js +0 -30
  542. package/dist/chunks/parse-24d0gay3.js +0 -19
  543. package/dist/chunks/platform-ba1j1gbh.js +0 -16
  544. package/dist/chunks/publish-panel-v9st96ja.js +0 -29
  545. package/dist/chunks/quick-search-zccjp6gv.js +0 -66
  546. package/dist/chunks/references-3qx8fkyp.js +0 -29
  547. package/dist/chunks/right-panel-vm3b6kwy.js +0 -46
  548. package/dist/chunks/section-registry-sx1ykvkw.js +0 -30
  549. package/dist/chunks/sp-overlay.dev-qgvhnxf4.js +0 -18
  550. package/dist/chunks/sp-popover.dev-n2etmqdp.js +0 -9
  551. package/dist/chunks/sp-progress-circle.dev-hryhj0es.js +0 -15
  552. package/dist/chunks/sp-tooltip.dev-4sastdnw.js +0 -15
  553. package/dist/chunks/sp-tray.dev-tkbscm06.js +0 -199
  554. package/dist/chunks/studio-0a5qrh01.js +0 -3
  555. package/dist/chunks/studio-0bq6eh7v.js +0 -12
  556. package/dist/chunks/studio-18j3bmcz.js +0 -14
  557. package/dist/chunks/studio-1ngbejft.js +0 -181
  558. package/dist/chunks/studio-4b5z1x43.js +0 -424
  559. package/dist/chunks/studio-4n2b5gzb.js +0 -357
  560. package/dist/chunks/studio-4rzyrtjz.js +0 -426
  561. package/dist/chunks/studio-5fx3g9jg.js +0 -4325
  562. package/dist/chunks/studio-5myknm0a.js +0 -1903
  563. package/dist/chunks/studio-7afd21nw.js +0 -67
  564. package/dist/chunks/studio-7nte2hj4.js +0 -613
  565. package/dist/chunks/studio-8jpzqk5s.js +0 -128
  566. package/dist/chunks/studio-8q517dfr.js +0 -24
  567. package/dist/chunks/studio-950k0dx6.js +0 -6939
  568. package/dist/chunks/studio-9n4mrmnb.js +0 -184
  569. package/dist/chunks/studio-a0tswy0w.js +0 -58
  570. package/dist/chunks/studio-ahnzd9rg.js +0 -23
  571. package/dist/chunks/studio-bgxt93ga.js +0 -62
  572. package/dist/chunks/studio-cmb03bg5.js +0 -13890
  573. package/dist/chunks/studio-d1ks410h.js +0 -175
  574. package/dist/chunks/studio-ef49pnzx.js +0 -387
  575. package/dist/chunks/studio-eqq1w638.js +0 -113
  576. package/dist/chunks/studio-ew0djmhg.js +0 -341
  577. package/dist/chunks/studio-gfgb8pe6.js +0 -1652
  578. package/dist/chunks/studio-h4rxscfc.js +0 -10
  579. package/dist/chunks/studio-jc994dnr.js +0 -1066
  580. package/dist/chunks/studio-kyh9avvv.js +0 -28
  581. package/dist/chunks/studio-m3hwn7y1.js +0 -120
  582. package/dist/chunks/studio-mtfnaqs9.js +0 -659
  583. package/dist/chunks/studio-n6j9t10q.js +0 -318
  584. package/dist/chunks/studio-n8b9wqwy.js +0 -291
  585. package/dist/chunks/studio-qcy05dp8.js +0 -1743
  586. package/dist/chunks/studio-qq6jym04.js +0 -53
  587. package/dist/chunks/studio-rdx8jcxy.js +0 -83
  588. package/dist/chunks/studio-rn9ta2mz.js +0 -111
  589. package/dist/chunks/studio-t7qb9db8.js +0 -665
  590. package/dist/chunks/studio-tmc5emrw.js +0 -8
  591. package/dist/chunks/studio-z6nw2ek2.js +0 -18
  592. package/dist/chunks/studio-zczps70q.js +0 -378
  593. package/dist/chunks/studio-zqazsct2.js +0 -1823
  594. package/dist/chunks/workspace-fjx52ryv.js +0 -89
  595. package/src/panels/activity-bar.ts +0 -251
  596. package/src/panels/panel-scheduler.ts +0 -219
  597. package/src/panels/style-inputs.ts +0 -237
  598. package/src/panels/target-line.ts +0 -274
  599. package/src/panels/welcome-screen.ts +0 -346
  600. package/src/ui/button-group.ts +0 -102
  601. package/src/ui/choice-field.ts +0 -72
  602. package/src/ui/field-row.ts +0 -249
  603. package/src/ui/formula-palette.ts +0 -213
  604. package/src/ui/icons.ts +0 -52
  605. package/src/ui/jx-theme.ts +0 -130
  606. package/src/ui/spectrum.ts +0 -340
  607. package/src/ui/value-selector.ts +0 -174
  608. package/src/ui/widgets.ts +0 -172
  609. /package/dist/chunks/{javascript-s56a8dpk.css → javascript-4abzjcy9.css} +0 -0
  610. /package/dist/chunks/{jsonMode-zr9sw0c7.css → jsonMode-vvmpmk34.css} +0 -0
  611. /package/dist/chunks/{monaco-setup-pav68602.css → monaco-setup-8vbyqnhr.css} +0 -0
  612. /package/dist/chunks/{tsMode-ef187txx.css → tsMode-hnxxb73h.css} +0 -0
@@ -1,43 +1,41 @@
1
1
  /// <reference lib="dom" />
2
2
  /**
3
- * The Command Bar — region ① of UX-REDESIGN-PLAN §3.2, rendered FROM the registry.
3
+ * The Command Bar — region ① of studio-ui-guidelines §6, rendered FROM the registry, as a document.
4
4
  *
5
- * Every control in this band used to be hand-authored twice: once in `toolbarTemplate` and once in
6
- * `minimalToolbarTemplate`, a retyped copy of the same buttons with `disabled` hard-coded on each
7
- * one for the no-project case. That second copy is the canonical example of the defect §2 principle
8
- * 1 names — a hand-maintained list of actions beside the definition site — and §2 principle 4 says
9
- * enablement is a PREDICATE with a sentence, never a duplicated template. It is deleted. With no
10
- * project open the same bar renders; the records' own `when` clauses empty it.
5
+ * `surfaces/commandbar.json` is the band; this module is its adapter. Every control in the band is
6
+ * a command, so what the document is handed is a PROJECTION of the registry rather than a list of
7
+ * buttons: `primary` is `forPlacement("commandbar/primary")` (capped at five by
8
+ * `scripts/check-chrome-budget.ts`), `docks` is the three dock records with the shell's state
9
+ * beside them, `layouts` is the project's own layout record, and the ⬢ Studio menu is
10
+ * `forPlacement("commandbar/overflow")` opened on the `menu` surface. With no project open the same
11
+ * document renders; the records' own `when` clauses empty it (§2 principle 4: enablement is a
12
+ * predicate with a sentence, never a second template).
11
13
  *
12
- * What is left is four things, none of which decides what an action is called:
14
+ * What is decided here, and nowhere in the document:
13
15
  *
14
- * - {@link tbCmd} renders one command: title, icon, `title="Save (⌘S)"` with the chord formatted for
15
- * THIS platform by `keymap.format` (the predecessor hardcoded `⌘P` and showed it to Windows and
16
- * Linux users), disabled state from `enablement`, and the `requires` sentence in the tooltip when
17
- * it is off — so no control is ever permanently dead with no explanation.
18
- * - The primary verb cluster is `forPlacement("commandbar/primary")`, capped at five by
19
- * `scripts/check-chrome-budget.ts`; the ⬢ menu is `forPlacement("commandbar/overflow")`.
16
+ * - {@link commandTooltip}: the title, plus its chord formatted for THIS platform by `keymap.format`,
17
+ * or plus the `requires` sentence when the record is off — so no control is ever permanently dead
18
+ * with no explanation.
20
19
  * - The **Command Center pill** (①a) is the app's address bar: `◈ project › document › selection`,
21
- * right-aligned ⌘K, each segment opening the palette pre-scoped. It replaces the Open Project
22
- * split button, the recents dropdown and the `Search files… ⌘P` trigger, and it gives Studio a
23
- * persistent project name for the first time — today it renders only in the Files panel header,
24
- * and the desktop titlebar is `titleBarStyle:"hidden"`.
25
- * - The window controls, which are the one thing in here that is not an action.
20
+ * right-aligned ⌘K, each segment opening the palette pre-scoped. It gives Studio a persistent
21
+ * project name — the desktop titlebar is `titleBarStyle:"hidden"`.
22
+ * - The window controls, which are the one thing in the band that is not an action, and whose ORDER
23
+ * is the platform's.
26
24
  *
27
- * **Retired here, with a name, a chord and a residue** (§2 principle 9): Open Project + New Project
28
- * + recents → the pill and `Project: Open Recent…`; `Manage` → `File: Browse Library`; `Publish` →
29
- * the `Publish:` family; `Sync Project` → Source Control. The five-mode switcher leaves for the
30
- * pane context bar (region ⑦) and is reachable meanwhile as `View: Set Canvas Mode` in the palette,
31
- * which prompts for the mode from the record's own `args` enum.
25
+ * **Retired, with a name, a chord and a residue** (§2 principle 9): Open Project + New Project +
26
+ * recents → the pill and `Project: Open Recent…`; `Manage` → `File: Browse Library`; `Publish` →
27
+ * the `Publish:` family; `Sync Project` → Source Control. The five-mode switcher lives in the pane
28
+ * context bar and is reachable as `View: Set Canvas Mode` in the palette.
29
+ *
30
+ * @docs studio/interface
32
31
  */
33
32
 
34
- import { html, render as litRender, nothing } from "lit-html";
35
- import { presenceChipsTemplate } from "../collab/presence-chips";
36
- import { effect, effectScope } from "../reactivity";
33
+ import { presenceProjection } from "../collab/presence-chips";
34
+ import { effect, effectScope, reactive } from "../reactivity";
37
35
  import { activeTab } from "../workspace/workspace";
38
36
  import { primarySelection } from "../tabs/selection";
39
37
  import { shell } from "../shell";
40
- import { openQuickSearch } from "./quick-search";
38
+ import { openQuickSearch } from "../panels/quick-search";
41
39
  import { showPromptDialog } from "../ui/layers";
42
40
  import { getPlatform, hasPlatform } from "../platform";
43
41
  import { getPreviewNavigateHandler } from "../canvas/preview-navigate";
@@ -45,20 +43,31 @@ import { armPreviewOverlay, flushPreviewOverlay } from "../preview/preview-overl
45
43
  import { documentUrlPattern, dynamicRouteParams } from "../page-params";
46
44
  import { getNodeAtPath, nodeLabel, projectState } from "../store";
47
45
  import { activeRegistry } from "../commands/active-registry";
46
+ import { runActiveReported, runReported } from "../commands/run-reported";
48
47
  import { notify } from "../services/notify";
48
+ import { mountSurface, registerSurface } from "../ui/surface";
49
+ import { rectOf } from "../utils/geometry";
50
+ import { openMenu } from "./menu";
51
+ import commandbarDoc from "./commandbar.json";
52
+ import type { PresenceProjection } from "../collab/presence-chips";
53
+ import type { MenuHandle, MenuRowProjection } from "./menu";
54
+ import type { SurfaceHandle } from "../ui/surface";
49
55
  import type { Tab } from "../tabs/tab";
50
56
  import type { SiteBuildResult, SitePreviewResult, StudioPlatform } from "../types";
51
57
  import type { CommandRegistry } from "../commands/registry";
52
58
  import type { EffectScope } from "@vue/reactivity";
53
- import type { TemplateResult } from "lit-html";
59
+ import type { JxDocument } from "@jxsuite/schema/types";
60
+
61
+ registerSurface("commandbar", commandbarDoc as unknown as JxDocument);
54
62
 
55
63
  /**
56
64
  * What the Command Bar is handed at mount.
57
65
  *
58
66
  * HANDOFF: **nothing here is read any more** — every control in the band is a command, so the bar
59
67
  * asks the registry rather than the bootstrap. The fields stay declared, and optional, so
60
- * `studio.ts`'s `toolbarPanel.mount(toolbarEl, { … })` object literal keeps type-checking; deleting
61
- * them is one edit in that file, which is another workstream's this wave.
68
+ * `studio.ts`'s `toolbarPanel.mount(toolbarEl, { … })` object literal keeps type-checking, and the
69
+ * shell tests keep driving the canvas-mode seam through it; deleting them is one edit in that
70
+ * file.
62
71
  */
63
72
  export interface ToolbarCtx {
64
73
  openProject?: () => void;
@@ -72,8 +81,6 @@ export interface ToolbarCtx {
72
81
  closeFunctionEditor?: () => void;
73
82
  }
74
83
 
75
- let _rootEl: HTMLElement | null = null;
76
-
77
84
  /** Test override for the mac CSD layout — happy-dom forbids redefining navigator.platform. */
78
85
  let _isMacOverride: boolean | null = null;
79
86
 
@@ -87,34 +94,114 @@ function isMacPlatform(): boolean {
87
94
  return _isMacOverride ?? navigator.platform.startsWith("Mac");
88
95
  }
89
96
 
90
- let _scope: EffectScope | null = null;
97
+ // ─── The projections ──────────────────────────────────────────────────────────
98
+
99
+ /** One command of the primary cluster, as the document draws it. */
100
+ export interface PrimaryProjection {
101
+ id: string;
102
+ title: string;
103
+ /** {@link commandTooltip}: the name with its chord, or with why it is off. */
104
+ tooltip: string;
105
+ /** The record's glyph, by its name in the kit's manifest; empty for a labelled button. */
106
+ icon: string;
107
+ hasIcon: boolean;
108
+ disabled: boolean;
109
+ }
110
+
111
+ /** One dock toggle: the record, with the dock's state beside it. */
112
+ export interface DockProjection {
113
+ id: string;
114
+ title: string;
115
+ tooltip: string;
116
+ icon: string;
117
+ /** The right-hand dock draws the left-hand glyph mirrored: one shipped shape, two sides. */
118
+ mirror: boolean;
119
+ /** Pressed while the dock is open. */
120
+ selected: boolean;
121
+ disabled: boolean;
122
+ }
123
+
124
+ /** One layout tab. */
125
+ export interface LayoutTabProjection {
126
+ id: string;
127
+ name: string;
128
+ active: boolean;
129
+ title: string;
130
+ }
131
+
132
+ /** One segment of the address bar. */
133
+ export interface SegmentProjection {
134
+ key: "project" | "document" | "selection";
135
+ label: string;
136
+ title: string;
137
+ /** Every segment but the first is preceded by `›`. */
138
+ sepBefore: boolean;
139
+ }
91
140
 
92
141
  /**
93
- * Icon key → Spectrum icon, for the `icon` a command record declares.
94
- *
95
- * The RECORD names the icon; this map only knows how to draw one. Keys are the record's vocabulary
96
- * ("save", "undo"), not Spectrum tag names, so swapping the icon set is a change here and nowhere
97
- * else — and a record that names an icon this bar cannot draw renders as a labelled button rather
98
- * than as an empty one.
142
+ * Which window-control layout the band draws: none in a browser, mac leading, everything else
143
+ * trailing.
99
144
  */
100
- const COMMAND_ICONS: Readonly<Record<string, TemplateResult>> = {
101
- browser: html`<sp-icon-export slot="icon"></sp-icon-export>`,
102
- redo: html`<sp-icon-redo slot="icon"></sp-icon-redo>`,
103
- save: html`<sp-icon-save-floppy slot="icon"></sp-icon-save-floppy>`,
104
- undo: html`<sp-icon-undo slot="icon"></sp-icon-undo>`,
145
+ export type WindowControlLayout = "none" | "mac" | "other";
146
+
147
+ /** The document's scope: the projections, and the host functions its handlers `call`. */
148
+ interface CommandBarScope extends Record<string, unknown> {
149
+ csd: WindowControlLayout;
150
+ menuVisible: boolean;
151
+ menuOpen: boolean;
152
+ layoutsVisible: boolean;
153
+ layouts: LayoutTabProjection[];
154
+ segments: SegmentProjection[];
155
+ chord: string;
156
+ hasChord: boolean;
157
+ primary: PrimaryProjection[];
158
+ presenceVisible: boolean;
159
+ presence: PresenceProjection;
160
+ docks: DockProjection[];
161
+ run: (id: string) => void;
162
+ openSegment: (key: SegmentProjection["key"]) => void;
163
+ openPicker: () => void;
164
+ setLayout: (id: string) => void;
165
+ renameLayout: (id: string) => void;
166
+ saveLayout: () => void;
167
+ openStudioMenu: (opener: HTMLElement) => void;
168
+ windowControl: (action: keyof WindowControls) => void;
169
+ }
170
+
171
+ /** What the presence cluster holds while there is nothing to say — the `$switch` hides it. */
172
+ const NO_PRESENCE: PresenceProjection = {
173
+ frozen: false,
174
+ label: "",
175
+ peers: [],
176
+ readOnly: false,
177
+ status: "unavailable",
178
+ title: "",
105
179
  };
106
180
 
107
- // ─── One command, one control ─────────────────────────────────────────────────
181
+ /* The window, with THIS dock's rail marked — one shipped glyph per side.
108
182
 
109
- /** How {@link tbCmd} draws a record. Presentation only — never what the record means. */
110
- export interface TbCmdOptions {
111
- /** Icon-only, with the title as the accessible name. Used by the dock toggles. */
112
- compact?: boolean;
113
- /** Rendered pressed. The dock toggles are the only controls with an on-state. */
114
- selected?: boolean;
115
- /** Override the record's icon — a dock toggle's glyph depends on which way the dock is. */
116
- icon?: TemplateResult;
117
- }
183
+ The pair these replace was `rail-right-open`/`close` under `scaleX(-1)`, because the left-hand
184
+ pair looked absent. Spectrum's two right-hand glyphs were exact mirror images OF EACH OTHER, so
185
+ flipping the wrong member of the pair landed on the other member's appearance — a real, crisp
186
+ arrow pointing the wrong way, looking entirely deliberate. It shipped crossed and nothing could
187
+ see it. Here the glyph is one shape and `mirror` is one boolean, asserted by name.
188
+
189
+ State stays where it already was and where it is already asserted: `selected` on the button. */
190
+ const DOCKS = [
191
+ { dock: "left", icon: "sidebar-simple", id: "view.toggleNavigator", mirror: false },
192
+ { dock: "right", icon: "sidebar-simple", id: "view.toggleInspector", mirror: true },
193
+ { dock: "bottom", icon: "rows", id: "view.toggleBottomDock", mirror: false },
194
+ ] as const;
195
+
196
+ /** The palette mode each segment of the address opens. */
197
+ const SEGMENT_MODES = { document: "files", project: "projects", selection: "nodes" } as const;
198
+
199
+ let _rootEl: HTMLElement | null = null;
200
+ let _scope: EffectScope | null = null;
201
+ let _state: CommandBarScope | null = null;
202
+ let _mount: Promise<SurfaceHandle> | null = null;
203
+ let _handle: SurfaceHandle | null = null;
204
+ let _menu: MenuHandle | null = null;
118
205
 
119
206
  /**
120
207
  * The tooltip a control shows: the action's name, plus its chord, or plus WHY it is off.
@@ -136,38 +223,6 @@ export function commandTooltip(registry: CommandRegistry, id: string): string {
136
223
  return chord ? `${command.title} (${chord})` : command.title;
137
224
  }
138
225
 
139
- /**
140
- * Render one command as a Command Bar button, or `nothing` when its `when` hides it.
141
- *
142
- * This is the whole of §5.5's first row: label, icon, tooltip, chord, disabled state and disabled
143
- * reason all come off the record, so the bar cannot disagree with the palette, the keymap or the
144
- * agent about any of them.
145
- */
146
- export function tbCmd(registry: CommandRegistry, id: string, options: TbCmdOptions = {}) {
147
- const command = registry.get(id);
148
- if (!command || !registry.isVisible(id)) {
149
- return nothing;
150
- }
151
- const enabled = registry.isEnabled(id);
152
- const icon = options.icon ?? (command.icon ? COMMAND_ICONS[command.icon] : undefined);
153
- return html`
154
- <sp-action-button
155
- size="s"
156
- ?quiet=${options.compact === true}
157
- ?selected=${options.selected === true}
158
- title=${commandTooltip(registry, id)}
159
- aria-label=${command.title}
160
- ?disabled=${!enabled}
161
- @click=${() => {
162
- void registry.run(id);
163
- }}
164
- >
165
- ${icon ?? nothing}
166
- ${options.compact ? nothing : html`<span class="tb-label">${command.title}</span>`}
167
- </sp-action-button>
168
- `;
169
- }
170
-
171
226
  // ─── View: Open in Browser ───────────────────────────────────────────────────
172
227
 
173
228
  /**
@@ -430,16 +485,6 @@ function errorText(error: unknown): string {
430
485
  return error instanceof Error ? error.message : String(error);
431
486
  }
432
487
 
433
- // ─── ①a The Command Center pill ──────────────────────────────────────────────
434
-
435
- /** One segment of the address, and the palette mode it opens. */
436
- interface PillSegment {
437
- key: string;
438
- label: string;
439
- title: string;
440
- onClick: () => void;
441
- }
442
-
443
488
  /** The document's label: its path without the project root, which is already segment one. */
444
489
  export function documentSegmentLabel(tab: Tab | null): string {
445
490
  const path = tab?.documentPath;
@@ -469,124 +514,6 @@ export function selectionSegmentLabel(tab: Tab | null): string {
469
514
  return nodeLabel(getNodeAtPath(tab.doc.document, selection));
470
515
  }
471
516
 
472
- /**
473
- * `◈ project › document › selection`, right-aligned ⌘K.
474
- *
475
- * Four facts about where you are, in non-collapsible chrome (§4.4), each one click-through to the
476
- * surface that owns it. The empty space between the segments opens the mode picker, which is what
477
- * makes the pill an address bar rather than three buttons.
478
- */
479
- function commandCenterTpl(registry: CommandRegistry | null) {
480
- const tab = activeTab.value ?? null;
481
- const chord = registry?.keymap.formatBinding("palette.open") ?? "";
482
- const selection = selectionSegmentLabel(tab);
483
- const segments: PillSegment[] = [
484
- {
485
- key: "project",
486
- label: projectState?.name ?? "No project",
487
- title: "Switch project — opens Project: Open Recent…",
488
- onClick: () => openQuickSearch("projects"),
489
- },
490
- {
491
- key: "document",
492
- label: documentSegmentLabel(tab),
493
- title: "Go to a file",
494
- onClick: () => openQuickSearch("files"),
495
- },
496
- ];
497
- if (selection) {
498
- segments.push({
499
- key: "selection",
500
- label: selection,
501
- title: "Go to an element in this document",
502
- onClick: () => openQuickSearch("nodes"),
503
- });
504
- }
505
- return html`
506
- <div
507
- class="tb-center"
508
- role="group"
509
- aria-label="Command Center"
510
- @click=${() => openQuickSearch("picker")}
511
- >
512
- <span class="tb-center-mark" aria-hidden="true">◈</span>
513
- ${segments.map(
514
- (segment, index) => html`
515
- ${index > 0 ? html`<span class="tb-center-sep" aria-hidden="true">›</span>` : nothing}
516
- <button
517
- class="tb-center-seg"
518
- type="button"
519
- title=${segment.title}
520
- @click=${(e: Event) => {
521
- e.stopPropagation();
522
- segment.onClick();
523
- }}
524
- >
525
- ${segment.label}
526
- </button>
527
- `,
528
- )}
529
- ${chord ? html`<kbd class="tb-center-chord">${chord}</kbd>` : nothing}
530
- </div>
531
- `;
532
- }
533
-
534
- // ─── ①b Layout tabs ──────────────────────────────────────────────────────────
535
-
536
- /**
537
- * `Write · Design · Build · Ship · +` — named arrangements, as plain-text tabs.
538
- *
539
- * The 80% of a workspace switcher that costs a segmented control and none of the re-architecture
540
- * (§3.2 ①b). Each tab RUNS `view.setLayout`, so the click, the palette row and an agent all take
541
- * the same path; double-clicking one renames it, and `+` saves whatever is on screen now.
542
- *
543
- * **A layout reconfigures; it never removes.** Nothing here hides a panel: every one stays on the
544
- * rail, on its chord and in the palette after any layout is applied — §13 is explicit that
545
- * workspaces which gate features hand the non-technical user the affordance they are least likely
546
- * to reach for.
547
- */
548
- function layoutTabsTpl(registry: CommandRegistry) {
549
- // `get` first, like {@link tbCmd}: a registry that has not been handed the shell's records yet
550
- // (the skeleton the bar paints before the bootstrap composes them) has no verb to ask about.
551
- if (!registry.get("view.setLayout") || !registry.isEnabled("view.setLayout")) {
552
- return nothing;
553
- }
554
- return html`
555
- <div class="tb-layouts" role="tablist" aria-label="Layouts">
556
- ${shell.layouts.map(
557
- (preset) => html`
558
- <button
559
- class=${preset.id === shell.layout ? "tb-layout active" : "tb-layout"}
560
- type="button"
561
- role="tab"
562
- aria-selected=${preset.id === shell.layout ? "true" : "false"}
563
- title=${`${preset.name} layout — double-click to rename`}
564
- @click=${() => {
565
- void registry.run("view.setLayout", { layout: preset.id });
566
- }}
567
- @dblclick=${() => {
568
- void renameLayoutPrompt(registry, preset.id, preset.name);
569
- }}
570
- >
571
- ${preset.name}
572
- </button>
573
- `,
574
- )}
575
- <button
576
- class="tb-layout-add"
577
- type="button"
578
- title="Save the current arrangement as a layout"
579
- aria-label="Save layout"
580
- @click=${() => {
581
- void saveLayoutPrompt(registry);
582
- }}
583
- >
584
- +
585
- </button>
586
- </div>
587
- `;
588
- }
589
-
590
517
  /** Ask for a name, then run the command. Exported for the same reason `runOpenInBrowser` is. */
591
518
  export async function saveLayoutPrompt(registry: CommandRegistry): Promise<void> {
592
519
  const name = await showPromptDialog("Save layout", {
@@ -595,7 +522,7 @@ export async function saveLayoutPrompt(registry: CommandRegistry): Promise<void>
595
522
  placeholder: "Layout name",
596
523
  });
597
524
  if (name) {
598
- await registry.run("view.saveLayout", { name });
525
+ await runReported(registry, "view.saveLayout", { name }, "Command Bar");
599
526
  }
600
527
  }
601
528
 
@@ -607,54 +534,10 @@ export async function renameLayoutPrompt(
607
534
  ): Promise<void> {
608
535
  const name = await showPromptDialog("Rename layout", { confirmLabel: "Rename", value: current });
609
536
  if (name) {
610
- await registry.run("view.renameLayout", { layout, name });
537
+ await runReported(registry, "view.renameLayout", { layout, name }, "Command Bar");
611
538
  }
612
539
  }
613
540
 
614
- // ─── The ⬢ app menu (commandbar/overflow) ────────────────────────────────────
615
-
616
- /**
617
- * Everything that declared `commandbar/overflow` — the chrome's residue for retired controls.
618
- *
619
- * Rendered as a menu of the records themselves, so a command that moves from the primary cluster to
620
- * the overflow keeps its name, its chord and its gate, and the move is one edit to its `menus`.
621
- */
622
- function appMenuTpl(registry: CommandRegistry) {
623
- const commands = registry.forPlacement("commandbar/overflow");
624
- return html`
625
- <overlay-trigger placement="bottom-start" triggered-by="click">
626
- <sp-action-button size="s" quiet slot="trigger" title="Studio menu" aria-label="Studio menu">
627
- <sp-icon-show-menu slot="icon"></sp-icon-show-menu>
628
- </sp-action-button>
629
- <sp-popover slot="click-content" tip>
630
- <sp-menu
631
- @change=${(e: Event) => {
632
- const id = (e.target as unknown as HTMLInputElement).value;
633
- if (registry.isEnabled(id)) {
634
- void registry.run(id);
635
- }
636
- }}
637
- >
638
- ${commands.map(
639
- (command) => html`
640
- <sp-menu-item
641
- value=${command.id}
642
- ?disabled=${!registry.isEnabled(command.id)}
643
- title=${commandTooltip(registry, command.id)}
644
- >
645
- ${command.title}
646
- <span slot="value">${registry.keymap.formatBinding(command.id) ?? ""}</span>
647
- </sp-menu-item>
648
- `,
649
- )}
650
- </sp-menu>
651
- </sp-popover>
652
- </overlay-trigger>
653
- `;
654
- }
655
-
656
- // ─── Window controls ──────────────────────────────────────────────────────────
657
-
658
541
  interface WindowControls {
659
542
  minimize: () => void;
660
543
  maximize: () => void;
@@ -666,97 +549,254 @@ function windowControls(): WindowControls | undefined {
666
549
  .__jxPlatform?.windowControls;
667
550
  }
668
551
 
669
- /** Client-side decorations. Mac puts them leading and close-first; everything else trailing. */
670
- function csdTpl(controls: WindowControls, mac: boolean) {
671
- const minimize = html`
672
- <sp-action-button
673
- quiet
674
- size="s"
675
- title="Minimize"
676
- class="csd-minimize"
677
- @click=${() => controls.minimize()}
678
- >
679
- <sp-icon-remove slot="icon"></sp-icon-remove>
680
- </sp-action-button>
681
- `;
682
- const maximize = html`
683
- <sp-action-button
684
- quiet
685
- size="s"
686
- title="Maximize"
687
- class="csd-maximize"
688
- @click=${() => controls.maximize()}
689
- >
690
- <sp-icon-rectangle slot="icon"></sp-icon-rectangle>
691
- </sp-action-button>
692
- `;
693
- const close = html`
694
- <sp-action-button
695
- quiet
696
- size="s"
697
- title="Close"
698
- class="csd-close"
699
- @click=${() => controls.close()}
700
- >
701
- <sp-icon-close slot="icon"></sp-icon-close>
702
- </sp-action-button>
703
- `;
704
- return mac
705
- ? html`<sp-action-group class="window-controls mac" size="s">
706
- ${close}${minimize}${maximize}
707
- </sp-action-group>`
708
- : html`<sp-action-group class="window-controls" size="s">
709
- ${minimize}${maximize}${close}
710
- </sp-action-group>`;
711
- }
712
-
713
- // ─── The three dock toggles ───────────────────────────────────────────────────
714
-
715
- /* The window, with THIS dock's rail marked — one shipped glyph per region.
552
+ // ─── The Command Center pill, the layout tabs and the docks, projected ───────
716
553
 
717
- The pair these replace was `rail-right-open`/`close` under `scaleX(-1)`, because the left-hand
718
- pair looked absent. It is not: `rail-left`, `rail-right` and `rail-bottom` are all in the
719
- workflow set, and they are a better vocabulary than an arrow besides. An arrow says which way
720
- something will move and nothing about what or where, which is why the Bottom dock could never
721
- join the set and carried a static `align-bottom` instead.
722
-
723
- The mirror was also worth deleting on its own terms. Spectrum's two right-hand glyphs are exact
724
- mirror images OF EACH OTHER, so flipping the wrong member of the pair lands on the other
725
- member's appearance — a real, crisp arrow pointing the wrong way, looking entirely deliberate.
726
- It shipped crossed and nothing could see it. Three distinct glyphs cannot fail that way.
727
-
728
- State stays where it already was and where it is already asserted: `?selected` on the button. */
729
- const DOCK_ICONS = {
730
- bottom: html`<sp-icon-rail-bottom slot="icon"></sp-icon-rail-bottom>`,
731
- left: html`<sp-icon-rail-left slot="icon"></sp-icon-rail-left>`,
732
- right: html`<sp-icon-rail-right slot="icon"></sp-icon-rail-right>`,
733
- } as const;
554
+ /**
555
+ * `◈ project › document › selection`: four facts about where you are, in non-collapsible chrome
556
+ * (§4.4).
557
+ */
558
+ function projectSegments(tab: Tab | null): SegmentProjection[] {
559
+ const segments: SegmentProjection[] = [
560
+ {
561
+ key: "project",
562
+ label: projectState?.name ?? "No project",
563
+ sepBefore: false,
564
+ title: "Switch project — opens Project: Open Recent…",
565
+ },
566
+ { key: "document", label: documentSegmentLabel(tab), sepBefore: true, title: "Go to a file" },
567
+ ];
568
+ const selection = selectionSegmentLabel(tab);
569
+ if (selection) {
570
+ segments.push({
571
+ key: "selection",
572
+ label: selection,
573
+ sepBefore: true,
574
+ title: "Go to an element in this document",
575
+ });
576
+ }
577
+ return segments;
578
+ }
734
579
 
735
580
  /**
736
- * ▤▥▦ — the three docks, each rendered from its own record.
581
+ * `Write · Design · Build · Ship · +` — named arrangements, as plain-text tabs.
582
+ *
583
+ * Each tab RUNS `view.setLayout`, so the click, the palette row and an agent all take the same
584
+ * path; double-clicking one renames it, and `+` saves whatever is on screen now. **A layout
585
+ * reconfigures; it never removes**: every panel stays on the rail, on its chord and in the palette
586
+ * after any layout is applied.
587
+ */
588
+ function projectLayouts(registry: CommandRegistry | null): LayoutTabProjection[] {
589
+ // `get` first: a registry that has not been handed the shell's records yet (the skeleton the bar
590
+ // Paints before the bootstrap composes them) has no verb to ask about.
591
+ if (!registry?.get("view.setLayout") || !registry.isEnabled("view.setLayout")) {
592
+ return [];
593
+ }
594
+ return shell.layouts.map((preset) => ({
595
+ active: preset.id === shell.layout,
596
+ id: preset.id,
597
+ name: preset.name,
598
+ title: `${preset.name} layout — double-click to rename`,
599
+ }));
600
+ }
601
+
602
+ /** The primary verb cluster: exactly what declared `commandbar/primary`, in the registry's order. */
603
+ function projectPrimary(registry: CommandRegistry | null): PrimaryProjection[] {
604
+ if (!registry) {
605
+ return [];
606
+ }
607
+ return registry
608
+ .forPlacement("commandbar/primary")
609
+ .filter((command) => registry.isVisible(command.id))
610
+ .map((command) => ({
611
+ disabled: !registry.isEnabled(command.id),
612
+ hasIcon: Boolean(command.icon),
613
+ icon: command.icon ?? "",
614
+ id: command.id,
615
+ title: command.title,
616
+ tooltip: commandTooltip(registry, command.id),
617
+ }));
618
+ }
619
+
620
+ /**
621
+ * ▤▥▦ — the three docks, each projected from its own record.
737
622
  *
738
- * The glyph flips with the dock's state and `?selected` reports it, so the control says which way
739
- * it will go; the NAME and the chord still come from the record, which is why ⌘B and this button
623
+ * The glyph names the region and `selected` reports its state, so the control says which way it
624
+ * will go; the NAME and the chord still come from the record, which is why ⌘B and this button
740
625
  * cannot drift apart the way ⌘W and the tab strip's × did.
741
626
  */
742
- function dockTogglesTpl(registry: CommandRegistry) {
743
- return html`
744
- ${tbCmd(registry, "view.toggleNavigator", {
745
- compact: true,
746
- icon: DOCK_ICONS.left,
747
- selected: !shell.docks.left.collapsed,
748
- })}
749
- ${tbCmd(registry, "view.toggleInspector", {
750
- compact: true,
751
- icon: DOCK_ICONS.right,
752
- selected: !shell.docks.right.collapsed,
753
- })}
754
- ${tbCmd(registry, "view.toggleBottomDock", {
755
- compact: true,
756
- icon: DOCK_ICONS.bottom,
757
- selected: !shell.docks.bottom.collapsed,
758
- })}
759
- `;
627
+ function projectDocks(registry: CommandRegistry | null): DockProjection[] {
628
+ if (!registry) {
629
+ return [];
630
+ }
631
+ return DOCKS.flatMap((dock) => {
632
+ const command = registry.get(dock.id);
633
+ if (!command || !registry.isVisible(dock.id)) {
634
+ return [];
635
+ }
636
+ return [
637
+ {
638
+ disabled: !registry.isEnabled(dock.id),
639
+ icon: dock.icon,
640
+ id: dock.id,
641
+ mirror: dock.mirror,
642
+ selected: !shell.docks[dock.dock].collapsed,
643
+ title: command.title,
644
+ tooltip: commandTooltip(registry, dock.id),
645
+ },
646
+ ];
647
+ });
648
+ }
649
+
650
+ // ─── The ⬢ Studio menu (commandbar/overflow) ─────────────────────────────────
651
+
652
+ /**
653
+ * Everything that declared `commandbar/overflow` — the chrome's residue for retired controls —
654
+ * projected as rows for the `menu` surface, so a command that moves from the primary cluster to the
655
+ * overflow keeps its name, its chord and its gate, and the move is one edit to its `menus`.
656
+ */
657
+ function overflowRows(registry: CommandRegistry): MenuRowProjection[] {
658
+ let group: string | undefined;
659
+ return registry.forPlacement("commandbar/overflow").map((command, index) => {
660
+ const dividerAbove = index > 0 && command.group !== group;
661
+ ({ group } = command);
662
+ const enabled = registry.isEnabled(command.id);
663
+ const chord = registry.keymap.formatBinding(command.id);
664
+ const reason = enabled ? undefined : registry.disabledReason(command.id);
665
+ const row: MenuRowProjection = {
666
+ destructive: command.destructive === true,
667
+ disabled: !enabled,
668
+ dividerAbove,
669
+ id: command.id,
670
+ title: command.title,
671
+ };
672
+ if (chord) {
673
+ row.chord = chord;
674
+ }
675
+ if (reason) {
676
+ row.requires = reason;
677
+ }
678
+ return row;
679
+ });
680
+ }
681
+
682
+ /** Open the Studio menu under its button, or close the one that is up: the button is a toggle. */
683
+ function openStudioMenu(opener: HTMLElement): void {
684
+ if (_menu) {
685
+ _menu.close();
686
+ return;
687
+ }
688
+ const registry = activeRegistry();
689
+ if (!registry) {
690
+ return;
691
+ }
692
+ const scope = state();
693
+ const handle = openMenu({
694
+ label: "Studio menu",
695
+ onClosed: (closed) => {
696
+ if (_menu !== closed) {
697
+ return;
698
+ }
699
+ _menu = null;
700
+ scope.menuOpen = false;
701
+ },
702
+ opener,
703
+ place: () => {
704
+ const box = rectOf(opener);
705
+ return { x: box.left, y: box.bottom + 4 };
706
+ },
707
+ region: "studio",
708
+ rows: overflowRows(registry),
709
+ run: (id) => {
710
+ if (registry.isEnabled(id)) {
711
+ void runReported(registry, id, undefined, "Command Bar");
712
+ }
713
+ },
714
+ });
715
+ _menu = handle;
716
+ scope.menuOpen = true;
717
+ }
718
+
719
+ // ─── The scope ────────────────────────────────────────────────────────────────
720
+
721
+ /**
722
+ * The one reactive scope the document is mounted over: rebuilt on mount, re-projected by the
723
+ * effect.
724
+ */
725
+ function state(): CommandBarScope {
726
+ _state ??= reactive<CommandBarScope>({
727
+ chord: "",
728
+ csd: "none",
729
+ docks: [],
730
+ hasChord: false,
731
+ layouts: [],
732
+ layoutsVisible: false,
733
+ menuOpen: false,
734
+ menuVisible: false,
735
+ openPicker: () => {
736
+ openQuickSearch("picker");
737
+ },
738
+ openSegment: (key) => {
739
+ openQuickSearch(SEGMENT_MODES[key]);
740
+ },
741
+ openStudioMenu,
742
+ presence: NO_PRESENCE,
743
+ presenceVisible: false,
744
+ primary: [],
745
+ renameLayout: (id) => {
746
+ const registry = activeRegistry();
747
+ const preset = shell.layouts.find((layout) => layout.id === id);
748
+ if (registry && preset) {
749
+ void renameLayoutPrompt(registry, id, preset.name);
750
+ }
751
+ },
752
+ run: (id) => {
753
+ const registry = activeRegistry();
754
+ if (registry?.isEnabled(id)) {
755
+ void runReported(registry, id, undefined, "Command Bar");
756
+ }
757
+ },
758
+ saveLayout: () => {
759
+ const registry = activeRegistry();
760
+ if (registry) {
761
+ void saveLayoutPrompt(registry);
762
+ }
763
+ },
764
+ segments: [],
765
+ setLayout: (id) => {
766
+ void runActiveReported("view.setLayout", { layout: id }, "Command Bar");
767
+ },
768
+ windowControl: (action) => {
769
+ windowControls()?.[action]();
770
+ },
771
+ }) as CommandBarScope;
772
+ return _state;
773
+ }
774
+
775
+ /**
776
+ * Re-project the registry, the shell and the tab onto the scope. Every write is keyed, so the DOM
777
+ * reconciles in place.
778
+ */
779
+ function project(): void {
780
+ const scope = state();
781
+ const registry = activeRegistry();
782
+ const tab = activeTab.value ?? null;
783
+ const controls = windowControls();
784
+ scope.csd = controls ? (isMacPlatform() ? "mac" : "other") : "none";
785
+ scope.menuVisible = registry !== null;
786
+ scope.layouts = projectLayouts(registry);
787
+ scope.layoutsVisible = scope.layouts.length > 0;
788
+ scope.segments = projectSegments(tab);
789
+ scope.chord = registry?.keymap.formatBinding("palette.open") ?? "";
790
+ scope.hasChord = scope.chord !== "";
791
+ scope.primary = projectPrimary(registry);
792
+ const presence = presenceProjection(tab);
793
+ // The object before the flag, so the case that reads it never renders against the empty one.
794
+ scope.presence = presence ?? NO_PRESENCE;
795
+ scope.presenceVisible = presence !== null;
796
+ scope.docks = projectDocks(registry);
797
+ if (_menu && registry) {
798
+ _menu.setRows(overflowRows(registry));
799
+ }
760
800
  }
761
801
 
762
802
  // ─── Mount ────────────────────────────────────────────────────────────────────
@@ -767,7 +807,8 @@ function dockTogglesTpl(registry: CommandRegistry) {
767
807
  * @param rootEl The `#toolbar` host, stamped `commandbar` by the frame (`shell/tree.ts`).
768
808
  * @param _ctx Ignored — see {@link ToolbarCtx}.
769
809
  */
770
- export function mount(rootEl: HTMLElement, _ctx: ToolbarCtx = {}) {
810
+ export function mount(rootEl: HTMLElement, _ctx: ToolbarCtx = {}): void {
811
+ unmount();
771
812
  _rootEl = rootEl;
772
813
  if (windowControls()) {
773
814
  rootEl.classList.add("electrobun-webkit-app-region-drag");
@@ -777,14 +818,14 @@ export function mount(rootEl: HTMLElement, _ctx: ToolbarCtx = {}) {
777
818
  effect(() => {
778
819
  // Dock visibility, source control and the project are shell state, tracked here so the band
779
820
  // Follows a flip made from anywhere — the automation runner, the New Project agent hand-off,
780
- // The boot-time restore — not just this module's click handlers.
821
+ // The boot-time restore — not just this document's handlers.
781
822
  void shell.docks.left.collapsed;
782
823
  void shell.docks.right.collapsed;
783
824
  void shell.docks.bottom.collapsed;
784
825
  void shell.git.status;
785
826
  void shell.layoutSelection;
786
- // The layout tabs are a rendering of the project's own record (§3.2 ①b), so the band
787
- // Repaints when a layout is saved, renamed or deleted from anywhere.
827
+ // The layout tabs are a rendering of the project's own record, so the band repaints when a
828
+ // Layout is saved, renamed or deleted from anywhere.
788
829
  void shell.layout;
789
830
  void shell.layouts;
790
831
  // The registry itself is reactive state: it is composed AFTER this mount runs, and reading it
@@ -804,56 +845,41 @@ export function mount(rootEl: HTMLElement, _ctx: ToolbarCtx = {}) {
804
845
  void tab.history.index;
805
846
  void tab.history.snapshots.length;
806
847
  }
807
- render();
848
+ try {
849
+ project();
850
+ } catch (error) {
851
+ console.error("command bar projection error:", error);
852
+ }
808
853
  });
809
854
  });
855
+ _mount = mountSurface("commandbar", state(), rootEl);
856
+ void _mount.then((handle) => {
857
+ _handle = handle;
858
+ });
810
859
  }
811
860
 
812
- export function unmount() {
861
+ export function unmount(): void {
862
+ _menu?.close();
813
863
  _scope?.stop();
814
864
  _scope = null;
865
+ const pending = _mount;
866
+ _mount = null;
867
+ if (_handle) {
868
+ _handle.dispose();
869
+ _handle = null;
870
+ } else if (pending) {
871
+ void pending.then((handle) => handle.dispose());
872
+ }
873
+ _state = null;
815
874
  _rootEl = null;
816
875
  }
817
876
 
818
- export function render() {
819
- if (!_rootEl) {
820
- return;
821
- }
822
- try {
823
- litRender(toolbarTemplate(), _rootEl);
824
- } catch (error) {
825
- console.error("toolbar render error:", error);
826
- }
827
- }
828
-
829
877
  /**
830
- * The band.
831
- *
832
- * ONE template for every state. With no project open the records' `when` clauses empty the primary
833
- * cluster and the pill reads "No project"; there is no second variant to keep in step.
878
+ * Re-project now. The effect does this on its own; the bootstrap calls it after a store it does not
879
+ * track hydrates.
834
880
  */
835
- function toolbarTemplate() {
836
- const registry = activeRegistry();
837
- const controls = windowControls();
838
- const mac = isMacPlatform();
839
- const csd = controls ? csdTpl(controls, mac) : nothing;
840
- const tab = activeTab.value ?? null;
841
- return html`
842
- ${mac ? csd : nothing} ${registry ? appMenuTpl(registry) : nothing}
843
- ${registry ? layoutTabsTpl(registry) : nothing}
844
- <div class="tb-spacer"></div>
845
- ${commandCenterTpl(registry)}
846
- <div class="tb-spacer"></div>
847
- ${
848
- registry
849
- ? html`<sp-action-group compact size="s">
850
- ${registry
851
- .forPlacement("commandbar/primary")
852
- .map((command) => tbCmd(registry, command.id))}
853
- </sp-action-group>`
854
- : nothing
855
- }
856
- ${tab ? presenceChipsTemplate(tab) : nothing} ${registry ? dockTogglesTpl(registry) : nothing}
857
- ${mac ? nothing : csd}
858
- `;
881
+ export function render(): void {
882
+ if (_rootEl) {
883
+ project();
884
+ }
859
885
  }