@jxsuite/studio 4.0.0 → 5.0.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 (613) 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-xp8vnwma.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-fyf63pmy.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-2w1f35qs.js +18 -0
  15. package/dist/chunks/content-source-3wqeas45.js +46 -0
  16. package/dist/chunks/convert-to-repeater-d0cgnb6d.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-d6g50j7s.js +42 -0
  20. package/dist/chunks/extension-sections-fy7q4fe9.js +651 -0
  21. package/dist/chunks/file-ops-3btj6st0.js +47 -0
  22. package/dist/chunks/files-ef8fgs3w.js +72 -0
  23. package/dist/chunks/format-host-3c33nwr8.js +40 -0
  24. package/dist/chunks/fs-events-13tj6sex.js +18 -0
  25. package/dist/chunks/git-panel-4wp2rn9b.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-jgesd924.js +115 -0
  31. package/dist/chunks/{index-nj44jpzj.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-1nq5evsh.js +29 -0
  37. package/dist/chunks/layers-panel-ar2gh90g.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-2qbetq73.js +33 -0
  41. package/dist/chunks/menu-q99sy9rf.js +285 -0
  42. package/dist/chunks/menu-rf7n72mf.js +14 -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-rws7k4br.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-98f7sr3g.js +27 -0
  52. package/dist/chunks/quick-search-srzz83k2.js +45 -0
  53. package/dist/chunks/references-n553f0e1.js +26 -0
  54. package/dist/chunks/right-panel-rrgx2dn3.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-18avnhjp.js +2430 -0
  60. package/dist/chunks/{studio-kryvsw6j.js → studio-1k325pwf.js} +48 -48
  61. package/dist/chunks/{studio-hbtp0eh1.js → studio-25hkdrkw.js} +356 -85
  62. package/dist/chunks/studio-26d7myd1.js +258 -0
  63. package/dist/chunks/{studio-p8j86wag.js → studio-38emantv.js} +22 -5
  64. package/dist/chunks/studio-3gxv60tn.js +422 -0
  65. package/dist/chunks/{studio-8swrjbny.js → studio-3hxkd92t.js} +275 -163
  66. package/dist/chunks/studio-4m0haseg.js +185 -0
  67. package/dist/chunks/{studio-ynxwkrzd.js → studio-4wbb9ct0.js} +1953 -1785
  68. package/dist/chunks/{studio-gv8vvshc.js → studio-5523bpzt.js} +11 -11
  69. package/dist/chunks/{studio-5q69gfag.js → studio-57mf66w2.js} +1753 -2484
  70. package/dist/chunks/studio-5jy81t3s.js +17463 -0
  71. package/dist/chunks/studio-5rt7sqa0.js +1168 -0
  72. package/dist/chunks/studio-5te5tqng.js +81 -0
  73. package/dist/chunks/{studio-p4ngc046.js → studio-6ewqv390.js} +9 -9
  74. package/dist/chunks/{studio-9ab8gqsm.js → studio-6nzaktvy.js} +7 -7
  75. package/dist/chunks/studio-6xa21ej9.js +28 -0
  76. package/dist/chunks/{studio-5ehhackm.js → studio-727xh0e3.js} +19 -19
  77. package/dist/chunks/{studio-eenfkt4e.js → studio-79y5sz51.js} +52 -38
  78. package/dist/chunks/{studio-4q0wznzf.js → studio-7e11jpa9.js} +461 -145
  79. package/dist/chunks/{studio-q7v10cg2.js → studio-7r42c6ca.js} +17 -7
  80. package/dist/chunks/studio-7xptrg0j.js +954 -0
  81. package/dist/chunks/{studio-cft9r4r7.js → studio-81390n6f.js} +22 -22
  82. package/dist/chunks/{studio-q43a5y9h.js → studio-8jn5yhyn.js} +16 -16
  83. package/dist/chunks/studio-8qtrpzr3.js +839 -0
  84. package/dist/chunks/{studio-dg7zyt5x.js → studio-98b3z07n.js} +1 -1
  85. package/dist/chunks/studio-9xxz1q69.js +1356 -0
  86. package/dist/chunks/studio-akbww0j5.js +13615 -0
  87. package/dist/chunks/studio-arfpe389.js +195 -0
  88. package/dist/chunks/{studio-2zkz8evn.js → studio-az3zd49t.js} +473 -281
  89. package/dist/chunks/studio-cpvb2ed1.js +1171 -0
  90. package/dist/chunks/{studio-9r6j5ev1.js → studio-d0300636.js} +5078 -19
  91. package/dist/chunks/{studio-bgxt93ga.js → studio-d055c37e.js} +13 -13
  92. package/dist/chunks/studio-d1ja2x9r.js +362 -0
  93. package/dist/chunks/{studio-b9b6t1dg.js → studio-e1f24eff.js} +5664 -5666
  94. package/dist/chunks/{studio-4pqqxbrt.js → studio-em9nw8eh.js} +301 -112
  95. package/dist/chunks/studio-erdatzvp.js +1190 -0
  96. package/dist/chunks/{studio-g1wdche0.js → studio-esrxgwh6.js} +80 -80
  97. package/dist/chunks/studio-fabxk6r9.js +2811 -0
  98. package/dist/chunks/{studio-3n4ndrzh.js → studio-h4nvnjvz.js} +303 -303
  99. package/dist/chunks/studio-hhvs4sdg.js +195 -0
  100. package/dist/chunks/{studio-68n67094.js → studio-kcgff4r0.js} +15 -18
  101. package/dist/chunks/{studio-685s0ytq.js → studio-nqgawfks.js} +572 -572
  102. package/dist/chunks/{studio-xrapxwwc.js → studio-p1dw8wfw.js} +55 -29
  103. package/dist/chunks/studio-pm6hgy7r.js +4763 -0
  104. package/dist/chunks/studio-q0a3yk4n.js +33 -0
  105. package/dist/chunks/{studio-wcy8trfx.js → studio-q3x06vmj.js} +23 -22
  106. package/dist/chunks/studio-qahxvnan.js +111 -0
  107. package/dist/chunks/{studio-53n8bp04.js → studio-r6xmmtfh.js} +1 -1
  108. package/dist/chunks/{studio-kjjsjak4.js → studio-rkstz940.js} +3661 -6136
  109. package/dist/chunks/{studio-tvgyw9xw.js → studio-rnyf3f8e.js} +83 -250
  110. package/dist/chunks/studio-rpyah2ew.js +2805 -0
  111. package/dist/chunks/studio-rs76k67r.js +12 -0
  112. package/dist/chunks/{studio-yxrkyv8a.js → studio-s6z853aq.js} +12108 -11766
  113. package/dist/chunks/{studio-sf7zjgv1.js → studio-sgdc5pfc.js} +8 -8
  114. package/dist/chunks/studio-tb3ev01j.js +763 -0
  115. package/dist/chunks/{studio-t5wqwzdh.js → studio-tjjk7jvk.js} +1 -1
  116. package/dist/chunks/studio-vhv5tgxm.js +51 -0
  117. package/dist/chunks/{studio-htsf0w6m.js → studio-vz68yn9e.js} +31 -154
  118. package/dist/chunks/studio-wqpngpsk.js +174 -0
  119. package/dist/chunks/{studio-eesa1myv.js → studio-x3rty6tr.js} +58 -58
  120. package/dist/chunks/studio-xsykpvsh.js +8448 -0
  121. package/dist/chunks/{studio-92ab2jqe.js → studio-y9zedyjn.js} +14 -14
  122. package/dist/chunks/studio-yxa81f45.js +12 -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-wrew8zk4.js +69 -0
  133. package/dist/chunks/{ws-client-6shc5rze.js → ws-client-ny4zc51r.js} +20 -22
  134. package/dist/iframe-entry.js +1812 -698
  135. package/dist/manifest.json +7 -0
  136. package/dist/studio.js +52900 -36573
  137. package/dist/workers/editor.worker.js +1040 -1085
  138. package/dist/workers/json.worker.js +1946 -1991
  139. package/dist/workers/ts.worker.js +4960 -5005
  140. package/favicon.ico +0 -0
  141. package/index.html +2 -0
  142. package/package.json +22 -55
  143. package/src/about/about-modal.ts +73 -123
  144. package/src/browse/library-commands.ts +20 -49
  145. package/src/browse/library-layouts.ts +87 -218
  146. package/src/browse/library-pane.ts +406 -438
  147. package/src/canvas/canvas-helpers.ts +0 -49
  148. package/src/canvas/canvas-live-render.ts +25 -2
  149. package/src/canvas/canvas-render.ts +666 -231
  150. package/src/canvas/canvas-surface.ts +0 -1
  151. package/src/canvas/canvas-utils.ts +310 -117
  152. package/src/canvas/dialog-path.ts +76 -0
  153. package/src/canvas/dialog-state.ts +67 -0
  154. package/src/canvas/diff-marks.ts +252 -0
  155. package/src/canvas/diff-toolbar.ts +349 -0
  156. package/src/canvas/diff-view.ts +123 -0
  157. package/src/canvas/edit-width-drag.ts +187 -0
  158. package/src/canvas/edit-width.ts +150 -0
  159. package/src/canvas/iframe-entry.ts +125 -8
  160. package/src/canvas/iframe-host.ts +271 -15
  161. package/src/canvas/iframe-interaction.ts +133 -13
  162. package/src/canvas/iframe-patch.ts +7 -1
  163. package/src/canvas/iframe-position.ts +5 -6
  164. package/src/canvas/iframe-protocol.ts +106 -5
  165. package/src/canvas/iframe-render.ts +373 -5
  166. package/src/canvas/iframe-slash.ts +2 -2
  167. package/src/canvas/iframe-subtree.ts +5 -8
  168. package/src/canvas/path-mapping.ts +25 -3
  169. package/src/canvas/popover-path.ts +106 -0
  170. package/src/canvas/popover-state.ts +155 -0
  171. package/src/canvas/surface-registry.ts +21 -10
  172. package/src/collab/presence-chips.ts +51 -73
  173. package/src/commands/app-commands.ts +10 -0
  174. package/src/commands/budget.ts +12 -0
  175. package/src/commands/command-args.ts +369 -6
  176. package/src/commands/defaults.ts +44 -14
  177. package/src/commands/live-context.ts +11 -5
  178. package/src/commands/reference.ts +12 -2
  179. package/src/commands/registry.ts +165 -8
  180. package/src/content/entry-commands.ts +16 -23
  181. package/src/content/entry-editor.ts +144 -101
  182. package/src/editor/canvas-slash-bridge.ts +2 -2
  183. package/src/editor/context-menu.ts +49 -200
  184. package/src/editor/convert-to-component.ts +42 -82
  185. package/src/editor/convert-to-repeater.ts +132 -173
  186. package/src/editor/inline-edit.ts +11 -0
  187. package/src/editor/shortcuts.ts +65 -26
  188. package/src/editor/slash-menu.ts +150 -157
  189. package/src/files/file-ops.ts +117 -11
  190. package/src/files/files.ts +949 -664
  191. package/src/files/json-layout.ts +474 -0
  192. package/src/files/media-paths.ts +13 -42
  193. package/src/files/media-usage.ts +41 -131
  194. package/src/files/serialize-document.ts +8 -1
  195. package/src/format/convert-file.ts +16 -10
  196. package/src/format/format-host.ts +50 -1
  197. package/src/github/github-auth.ts +71 -29
  198. package/src/github/github-publish.ts +57 -65
  199. package/src/grid/cell-editors.ts +46 -4
  200. package/src/grid/cell-popovers.ts +65 -62
  201. package/src/grid/grid-open.ts +86 -64
  202. package/src/grid/grid-panel.ts +390 -469
  203. package/src/grid/redirects-grid.ts +40 -44
  204. package/src/hosting/document.ts +2 -0
  205. package/src/hosting/layout.ts +10 -0
  206. package/src/i18n/i18n-commands.ts +127 -62
  207. package/src/media/media-pane.ts +83 -133
  208. package/src/new-project/add-repo-modal.ts +125 -176
  209. package/src/new-project/import-tab.ts +201 -250
  210. package/src/new-project/location-fields.ts +101 -165
  211. package/src/new-project/new-project-modal.ts +376 -367
  212. package/src/packages/jxsuite-update.ts +76 -18
  213. package/src/panels/activity-panel.ts +137 -104
  214. package/src/panels/ai-chat/chat-markdown.ts +15 -13
  215. package/src/panels/ai-chat/chat-view.ts +307 -472
  216. package/src/panels/ai-chat/composer.ts +175 -153
  217. package/src/panels/ai-chat/sessions-view.ts +31 -62
  218. package/src/panels/ai-panel.ts +210 -185
  219. package/src/panels/block-action-bar.ts +437 -643
  220. package/src/panels/bottom-dock.ts +112 -47
  221. package/src/panels/chat-panel.ts +19 -14
  222. package/src/panels/data-explorer.ts +141 -102
  223. package/src/panels/data-grid.ts +183 -150
  224. package/src/panels/dnd.ts +75 -20
  225. package/src/panels/editors.ts +26 -19
  226. package/src/panels/elements-panel.ts +243 -186
  227. package/src/panels/empty-state.ts +26 -42
  228. package/src/panels/events-panel.ts +950 -729
  229. package/src/panels/formula-workspace.ts +434 -207
  230. package/src/panels/frontmatter-fields.ts +185 -153
  231. package/src/panels/frontmatter-panel.ts +411 -187
  232. package/src/panels/git-diff-open.ts +148 -0
  233. package/src/panels/git-panel.ts +679 -605
  234. package/src/panels/head-panel.ts +617 -435
  235. package/src/panels/i18n-panel.ts +153 -133
  236. package/src/panels/imports-panel.ts +388 -414
  237. package/src/panels/jump-bar.ts +174 -147
  238. package/src/panels/layers-panel.ts +785 -671
  239. package/src/panels/left-panel.ts +166 -94
  240. package/src/panels/navigator-panels.ts +1 -1
  241. package/src/panels/pane-context.ts +529 -677
  242. package/src/panels/pane-grid.ts +265 -241
  243. package/src/panels/panel-registry.ts +15 -4
  244. package/src/panels/problems-panel.ts +177 -99
  245. package/src/panels/properties-panel.ts +1525 -966
  246. package/src/panels/provenance.ts +31 -113
  247. package/src/panels/quick-search.ts +217 -172
  248. package/src/panels/right-panel.ts +135 -169
  249. package/src/panels/seo-modal.ts +346 -167
  250. package/src/panels/settings-menu.ts +109 -646
  251. package/src/panels/settings-pane.ts +102 -86
  252. package/src/panels/signals-panel.ts +1318 -1172
  253. package/src/panels/statement-editor.ts +556 -428
  254. package/src/panels/style-panel.ts +1367 -630
  255. package/src/panels/style-utils.ts +36 -32
  256. package/src/panels/stylebook-doc.ts +30 -73
  257. package/src/panels/stylebook-layers-panel.ts +202 -109
  258. package/src/panels/stylebook-panel.ts +124 -88
  259. package/src/panels/tab-strip.ts +385 -287
  260. package/src/platforms/cloud.ts +347 -86
  261. package/src/platforms/devserver.ts +13 -0
  262. package/src/publish/deploy-checklist.ts +124 -78
  263. package/src/publish/publish-commands.ts +4 -12
  264. package/src/publish/publish-panel.ts +165 -299
  265. package/src/services/a11y-report.ts +100 -43
  266. package/src/services/ai-command-tools.ts +468 -0
  267. package/src/services/ai-import-tools.ts +50 -12
  268. package/src/services/ai-models.ts +108 -1
  269. package/src/services/ai-session-store.ts +6 -0
  270. package/src/services/ai-system-prompt.ts +110 -18
  271. package/src/services/ai-tools.ts +28 -143
  272. package/src/services/ai-write-report.ts +174 -0
  273. package/src/services/context-manager.ts +12 -1
  274. package/src/services/document-assistant.ts +50 -2
  275. package/src/services/idle.ts +16 -14
  276. package/src/services/live-surfaces.ts +237 -0
  277. package/src/services/model-uri.ts +75 -0
  278. package/src/services/notify.ts +63 -6
  279. package/src/services/popover-report.ts +325 -0
  280. package/src/services/surface-registry.ts +56 -0
  281. package/src/services/tool-executor.ts +17 -0
  282. package/src/settings/contexts-section.ts +301 -299
  283. package/src/settings/contributed-section.ts +361 -222
  284. package/src/settings/css-vars-editor.ts +320 -325
  285. package/src/settings/defs-editor.ts +572 -535
  286. package/src/settings/dependencies-editor.ts +79 -107
  287. package/src/settings/extension-commands.ts +379 -0
  288. package/src/settings/extension-rows.ts +113 -0
  289. package/src/settings/extensions-section.ts +287 -0
  290. package/src/settings/general-settings.ts +130 -181
  291. package/src/settings/head-editor.ts +10 -305
  292. package/src/settings/locales-section.ts +146 -196
  293. package/src/settings/preferences-accounts.ts +193 -12
  294. package/src/settings/preferences-dialog.ts +384 -307
  295. package/src/settings/project-sections.ts +108 -181
  296. package/src/settings/schema-field-ui.ts +11 -399
  297. package/src/settings/section-registry.ts +8 -1
  298. package/src/settings/settings-document.ts +31 -29
  299. package/src/shell/tree.ts +42 -87
  300. package/src/shell.ts +48 -36
  301. package/src/studio.ts +90 -67
  302. package/src/style/token-ref.ts +0 -39
  303. package/src/surfaces/about.json +263 -0
  304. package/src/surfaces/about.ts +163 -0
  305. package/src/surfaces/add-repo.json +486 -0
  306. package/src/surfaces/add-repo.ts +236 -0
  307. package/src/surfaces/ai-chat.json +2185 -0
  308. package/src/surfaces/ai-chat.ts +549 -0
  309. package/src/surfaces/ai-credentials-form.json +332 -0
  310. package/src/surfaces/ai-credentials-form.ts +151 -0
  311. package/src/surfaces/ai-managed-connect.json +108 -0
  312. package/src/surfaces/ai-managed-connect.ts +123 -0
  313. package/src/surfaces/ai-model-picker.json +114 -0
  314. package/src/surfaces/ai-model-picker.ts +139 -0
  315. package/src/surfaces/block-action-bar.json +640 -0
  316. package/src/surfaces/block-action-bar.ts +368 -0
  317. package/src/surfaces/bottom-dock.json +154 -0
  318. package/src/surfaces/bottom-dock.ts +125 -0
  319. package/src/surfaces/canvas-stage.json +444 -0
  320. package/src/surfaces/canvas-stage.ts +253 -0
  321. package/src/surfaces/cf-account-picker.json +300 -0
  322. package/src/surfaces/cf-account-picker.ts +214 -0
  323. package/src/surfaces/commandbar.json +984 -0
  324. package/src/{panels/toolbar.ts → surfaces/commandbar.ts} +414 -389
  325. package/src/surfaces/convert-repeater.json +309 -0
  326. package/src/surfaces/convert-repeater.ts +276 -0
  327. package/src/surfaces/data-actions.json +157 -0
  328. package/src/surfaces/data-actions.ts +115 -0
  329. package/src/surfaces/dialog.json +289 -0
  330. package/src/surfaces/dialog.ts +343 -0
  331. package/src/surfaces/diff-toolbar.json +266 -0
  332. package/src/surfaces/diff-toolbar.ts +139 -0
  333. package/src/surfaces/doc-header.json +739 -0
  334. package/src/surfaces/doc-header.ts +223 -0
  335. package/src/surfaces/empty-state.json +152 -0
  336. package/src/surfaces/empty-state.ts +173 -0
  337. package/src/surfaces/entry-editor.json +281 -0
  338. package/src/surfaces/entry-editor.ts +191 -0
  339. package/src/surfaces/expression-editor.json +977 -0
  340. package/src/surfaces/expression-editor.ts +266 -0
  341. package/src/surfaces/files-panel.json +592 -0
  342. package/src/surfaces/files-panel.ts +301 -0
  343. package/src/surfaces/formula-palette.json +364 -0
  344. package/src/surfaces/formula-palette.ts +340 -0
  345. package/src/surfaces/git-panel.json +1822 -0
  346. package/src/surfaces/git-panel.ts +272 -0
  347. package/src/surfaces/github-auth.json +112 -0
  348. package/src/surfaces/github-auth.ts +139 -0
  349. package/src/surfaces/github-publish.json +206 -0
  350. package/src/surfaces/github-publish.ts +169 -0
  351. package/src/surfaces/grid-cell.json +218 -0
  352. package/src/surfaces/grid-cell.ts +120 -0
  353. package/src/surfaces/grid-open.json +147 -0
  354. package/src/surfaces/grid-open.ts +87 -0
  355. package/src/surfaces/grid-panel.json +706 -0
  356. package/src/surfaces/grid-panel.ts +206 -0
  357. package/src/surfaces/grid-replace.json +179 -0
  358. package/src/surfaces/grid-replace.ts +89 -0
  359. package/src/surfaces/grid-views.json +446 -0
  360. package/src/surfaces/grid-views.ts +172 -0
  361. package/src/surfaces/inspector-dock.json +187 -0
  362. package/src/surfaces/inspector-dock.ts +161 -0
  363. package/src/surfaces/jump-bar.json +243 -0
  364. package/src/surfaces/jump-bar.ts +129 -0
  365. package/src/surfaces/jxsuite-update.json +146 -0
  366. package/src/surfaces/jxsuite-update.ts +149 -0
  367. package/src/surfaces/library-pane.json +1509 -0
  368. package/src/surfaces/library-pane.ts +363 -0
  369. package/src/surfaces/logic-panel.json +1949 -0
  370. package/src/surfaces/logic-panel.ts +369 -0
  371. package/src/surfaces/logic-workspace.json +685 -0
  372. package/src/surfaces/logic-workspace.ts +238 -0
  373. package/src/surfaces/media-browser.json +303 -0
  374. package/src/surfaces/media-browser.ts +145 -0
  375. package/src/surfaces/media-field.json +163 -0
  376. package/src/surfaces/media-field.ts +116 -0
  377. package/src/surfaces/media-pane.json +513 -0
  378. package/src/surfaces/media-pane.ts +197 -0
  379. package/src/surfaces/menu.json +232 -0
  380. package/src/surfaces/menu.ts +375 -0
  381. package/src/surfaces/navigator-dock.json +113 -0
  382. package/src/surfaces/navigator-dock.ts +130 -0
  383. package/src/surfaces/new-project.json +1654 -0
  384. package/src/surfaces/new-project.ts +411 -0
  385. package/src/surfaces/palette.json +535 -0
  386. package/src/surfaces/pane-context.json +1497 -0
  387. package/src/surfaces/pane-context.ts +513 -0
  388. package/src/surfaces/pane-grid.json +188 -0
  389. package/src/surfaces/pane-grid.ts +250 -0
  390. package/src/surfaces/panel-activity.json +563 -0
  391. package/src/surfaces/panel-activity.ts +187 -0
  392. package/src/surfaces/panel-data.json +141 -0
  393. package/src/surfaces/panel-data.ts +156 -0
  394. package/src/surfaces/panel-deploy-checklist.json +283 -0
  395. package/src/surfaces/panel-deploy-checklist.ts +175 -0
  396. package/src/surfaces/panel-elements.json +367 -0
  397. package/src/surfaces/panel-elements.ts +175 -0
  398. package/src/surfaces/panel-i18n.json +569 -0
  399. package/src/surfaces/panel-i18n.ts +212 -0
  400. package/src/surfaces/panel-imports.json +780 -0
  401. package/src/surfaces/panel-imports.ts +209 -0
  402. package/src/surfaces/panel-outline.json +734 -0
  403. package/src/surfaces/panel-outline.ts +326 -0
  404. package/src/surfaces/panel-page.json +1017 -0
  405. package/src/surfaces/panel-page.ts +240 -0
  406. package/src/surfaces/panel-problems.json +497 -0
  407. package/src/surfaces/panel-problems.ts +157 -0
  408. package/src/surfaces/panel-signals.json +1973 -0
  409. package/src/surfaces/panel-signals.ts +436 -0
  410. package/src/surfaces/panel-stylebook-layers.json +317 -0
  411. package/src/surfaces/panel-stylebook-layers.ts +155 -0
  412. package/src/surfaces/preferences.json +850 -0
  413. package/src/surfaces/preferences.ts +332 -0
  414. package/src/surfaces/progress-modal.json +110 -0
  415. package/src/surfaces/progress-modal.ts +150 -0
  416. package/src/surfaces/properties-panel.json +1578 -0
  417. package/src/surfaces/properties-panel.ts +337 -0
  418. package/src/surfaces/publish.json +945 -0
  419. package/src/surfaces/publish.ts +392 -0
  420. package/src/surfaces/push-plan.json +110 -0
  421. package/src/surfaces/push-plan.ts +87 -0
  422. package/src/surfaces/rail.json +255 -0
  423. package/src/surfaces/rail.ts +206 -0
  424. package/src/surfaces/reference-field.json +191 -0
  425. package/src/surfaces/reference-field.ts +168 -0
  426. package/src/surfaces/schema-builder.json +1011 -0
  427. package/src/surfaces/schema-builder.ts +181 -0
  428. package/src/surfaces/schema-form.json +1013 -0
  429. package/src/surfaces/schema-form.ts +287 -0
  430. package/src/surfaces/secret-field.json +60 -0
  431. package/src/surfaces/secret-field.ts +146 -0
  432. package/src/surfaces/seo.json +932 -0
  433. package/src/surfaces/seo.ts +334 -0
  434. package/src/surfaces/settings-contexts.json +538 -0
  435. package/src/surfaces/settings-contexts.ts +196 -0
  436. package/src/surfaces/settings-contributed.json +489 -0
  437. package/src/surfaces/settings-contributed.ts +199 -0
  438. package/src/surfaces/settings-css-vars.json +820 -0
  439. package/src/surfaces/settings-css-vars.ts +199 -0
  440. package/src/surfaces/settings-defs.json +1363 -0
  441. package/src/surfaces/settings-defs.ts +232 -0
  442. package/src/surfaces/settings-deploy.json +108 -0
  443. package/src/surfaces/settings-deploy.ts +135 -0
  444. package/src/surfaces/settings-extensions.json +486 -0
  445. package/src/surfaces/settings-extensions.ts +201 -0
  446. package/src/surfaces/settings-head.json +779 -0
  447. package/src/surfaces/settings-head.ts +397 -0
  448. package/src/surfaces/settings-locales.json +420 -0
  449. package/src/surfaces/settings-locales.ts +137 -0
  450. package/src/surfaces/settings-overview.json +423 -0
  451. package/src/surfaces/settings-overview.ts +218 -0
  452. package/src/surfaces/settings-packages.json +549 -0
  453. package/src/surfaces/settings-packages.ts +158 -0
  454. package/src/surfaces/settings-pane.json +147 -0
  455. package/src/surfaces/settings-pane.ts +150 -0
  456. package/src/surfaces/settings-rawjson.json +104 -0
  457. package/src/surfaces/settings-rawjson.ts +107 -0
  458. package/src/surfaces/shell.json +99 -0
  459. package/src/surfaces/shell.ts +30 -0
  460. package/src/surfaces/slash-menu.json +180 -0
  461. package/src/surfaces/slash-menu.ts +272 -0
  462. package/src/surfaces/statements.json +607 -0
  463. package/src/surfaces/statements.ts +238 -0
  464. package/src/surfaces/statusbar.json +94 -0
  465. package/src/{panels → surfaces}/statusbar.ts +163 -90
  466. package/src/surfaces/style-panel.json +1643 -0
  467. package/src/surfaces/style-panel.ts +338 -0
  468. package/src/surfaces/stylebook-chrome.json +122 -0
  469. package/src/surfaces/stylebook-chrome.ts +122 -0
  470. package/src/surfaces/tab-strip.json +448 -0
  471. package/src/surfaces/tab-strip.ts +200 -0
  472. package/src/surfaces/target-line.json +419 -0
  473. package/src/surfaces/target-line.ts +349 -0
  474. package/src/surfaces/toasts.json +189 -0
  475. package/src/surfaces/welcome.json +571 -0
  476. package/src/surfaces/welcome.ts +338 -0
  477. package/src/tabs/project-config.ts +71 -15
  478. package/src/tabs/tab.ts +52 -1
  479. package/src/types.ts +49 -3
  480. package/src/ui/ai-credentials-form.ts +104 -100
  481. package/src/ui/ai-managed-connect.ts +116 -49
  482. package/src/ui/ai-model-picker.ts +102 -47
  483. package/src/ui/cf-account-picker.ts +135 -0
  484. package/src/ui/color-selector.ts +80 -301
  485. package/src/ui/dynamic-slot.ts +43 -283
  486. package/src/ui/expression-editor.ts +797 -576
  487. package/src/ui/field-input.ts +8 -181
  488. package/src/ui/form-controls.ts +838 -366
  489. package/src/ui/formula-chips.ts +80 -85
  490. package/src/ui/kit.ts +33 -0
  491. package/src/ui/layers.ts +293 -593
  492. package/src/ui/media-picker.ts +203 -284
  493. package/src/ui/panel-resize.ts +251 -64
  494. package/src/ui/popover-surface.ts +153 -0
  495. package/src/ui/progress-modal.ts +22 -60
  496. package/src/ui/regions.ts +13 -7
  497. package/src/ui/schema-form.ts +906 -440
  498. package/src/ui/surface.ts +122 -0
  499. package/src/ui/unit-selector.ts +15 -131
  500. package/src/ui/value-source.ts +14 -0
  501. package/src/ui/virtual-window.ts +88 -9
  502. package/src/utils/canvas-media.ts +72 -0
  503. package/src/utils/edit-display.ts +7 -2
  504. package/src/utils/element-selectors.ts +69 -0
  505. package/src/utils/link-target.ts +2 -2
  506. package/src/utils/studio-utils.ts +0 -10
  507. package/src/view.ts +15 -0
  508. package/src/workspace/pane-derive.ts +36 -14
  509. package/src/workspace/workspace.ts +15 -6
  510. package/styles/canvas.css +72 -467
  511. package/styles/forced-colors.css +48 -82
  512. package/styles/forced-colors.json +41 -0
  513. package/styles/inspector.css +2 -1301
  514. package/styles/overlays.css +96 -2823
  515. package/styles/panels.css +6 -1423
  516. package/styles/shell-frame.css +272 -0
  517. package/styles/shell-frame.json +227 -0
  518. package/styles/shell.css +0 -1532
  519. package/styles/tokens.css +65 -136
  520. package/styles/tokens.json +84 -0
  521. package/dist/chunks/Overlay.dev-723bv0df.js +0 -23
  522. package/dist/chunks/adapter-jkcyhgtr.js +0 -14
  523. package/dist/chunks/cf-settings-45fgyx8y.js +0 -21
  524. package/dist/chunks/components-zmx10t62.js +0 -21
  525. package/dist/chunks/content-source-t88vb2dr.js +0 -49
  526. package/dist/chunks/convert-to-repeater-dhw1nrz9.js +0 -268
  527. package/dist/chunks/editor-cptq5jp1.js +0 -37
  528. package/dist/chunks/entry-commands-6z6bhxg0.js +0 -46
  529. package/dist/chunks/extension-sections-3ydgjbdy.js +0 -311
  530. package/dist/chunks/file-ops-rskmhxyq.js +0 -48
  531. package/dist/chunks/files-0cratsr0.js +0 -92
  532. package/dist/chunks/flow-qbfgrpeh.js +0 -634
  533. package/dist/chunks/focus-trap.esm-ggawf1hy.js +0 -1024
  534. package/dist/chunks/focus-visible-6eg70z18.js +0 -167
  535. package/dist/chunks/format-host-tgcmfs4p.js +0 -43
  536. package/dist/chunks/fs-events-hrrg328g.js +0 -21
  537. package/dist/chunks/git-panel-0yywyk76.js +0 -58
  538. package/dist/chunks/index-zqxmqxmh.js +0 -109
  539. package/dist/chunks/jx-validate-8q2g05x1.js +0 -19
  540. package/dist/chunks/layers-panel-z3tqe5cd.js +0 -67
  541. package/dist/chunks/layers-q75tj2mf.js +0 -45
  542. package/dist/chunks/lit-html-j055mkh8.js +0 -22
  543. package/dist/chunks/monaco-buffer-dqw0xxpx.js +0 -25
  544. package/dist/chunks/notify-yjm79n4s.js +0 -30
  545. package/dist/chunks/parse-24d0gay3.js +0 -19
  546. package/dist/chunks/platform-ba1j1gbh.js +0 -16
  547. package/dist/chunks/publish-panel-54jvhskq.js +0 -28
  548. package/dist/chunks/quick-search-cgfmjwq5.js +0 -65
  549. package/dist/chunks/references-3qx8fkyp.js +0 -29
  550. package/dist/chunks/right-panel-cqp8bc4v.js +0 -46
  551. package/dist/chunks/section-registry-sx1ykvkw.js +0 -30
  552. package/dist/chunks/sp-overlay.dev-yztmtrs1.js +0 -18
  553. package/dist/chunks/sp-popover.dev-n2etmqdp.js +0 -9
  554. package/dist/chunks/sp-progress-circle.dev-hryhj0es.js +0 -15
  555. package/dist/chunks/sp-tooltip.dev-xcvpnqr0.js +0 -15
  556. package/dist/chunks/sp-tray.dev-tkbscm06.js +0 -199
  557. package/dist/chunks/studio-0a5qrh01.js +0 -3
  558. package/dist/chunks/studio-18j3bmcz.js +0 -14
  559. package/dist/chunks/studio-1hbqnt19.js +0 -1823
  560. package/dist/chunks/studio-4rzyrtjz.js +0 -426
  561. package/dist/chunks/studio-5rywrkxz.js +0 -1903
  562. package/dist/chunks/studio-7afd21nw.js +0 -67
  563. package/dist/chunks/studio-7nte2hj4.js +0 -613
  564. package/dist/chunks/studio-8jpzqk5s.js +0 -128
  565. package/dist/chunks/studio-8q517dfr.js +0 -24
  566. package/dist/chunks/studio-9c4k4vhv.js +0 -357
  567. package/dist/chunks/studio-9n4mrmnb.js +0 -184
  568. package/dist/chunks/studio-a0tswy0w.js +0 -58
  569. package/dist/chunks/studio-ahjfdy6e.js +0 -387
  570. package/dist/chunks/studio-ahnzd9rg.js +0 -23
  571. package/dist/chunks/studio-bw5dz6km.js +0 -608
  572. package/dist/chunks/studio-cb12yz9w.js +0 -15055
  573. package/dist/chunks/studio-d1ks410h.js +0 -175
  574. package/dist/chunks/studio-eqq1w638.js +0 -113
  575. package/dist/chunks/studio-ex8zyp98.js +0 -1813
  576. package/dist/chunks/studio-h4rxscfc.js +0 -10
  577. package/dist/chunks/studio-kyh9avvv.js +0 -28
  578. package/dist/chunks/studio-kyk2v92a.js +0 -659
  579. package/dist/chunks/studio-m3hwn7y1.js +0 -120
  580. package/dist/chunks/studio-mmv72a27.js +0 -4344
  581. package/dist/chunks/studio-n8b9wqwy.js +0 -291
  582. package/dist/chunks/studio-pdfjntbk.js +0 -6939
  583. package/dist/chunks/studio-qq6jym04.js +0 -53
  584. package/dist/chunks/studio-r1r7afxx.js +0 -1066
  585. package/dist/chunks/studio-rdx8jcxy.js +0 -83
  586. package/dist/chunks/studio-rn9ta2mz.js +0 -111
  587. package/dist/chunks/studio-t7qb9db8.js +0 -665
  588. package/dist/chunks/studio-tmc5emrw.js +0 -8
  589. package/dist/chunks/studio-txwkm6p9.js +0 -12
  590. package/dist/chunks/studio-v38hmvx1.js +0 -390
  591. package/dist/chunks/studio-vadzgkrz.js +0 -318
  592. package/dist/chunks/studio-wx5609xd.js +0 -341
  593. package/dist/chunks/studio-z6nw2ek2.js +0 -18
  594. package/dist/chunks/studio-zczps70q.js +0 -378
  595. package/dist/chunks/workspace-xpres40v.js +0 -89
  596. package/src/panels/activity-bar.ts +0 -251
  597. package/src/panels/panel-scheduler.ts +0 -219
  598. package/src/panels/style-inputs.ts +0 -237
  599. package/src/panels/target-line.ts +0 -274
  600. package/src/panels/welcome-screen.ts +0 -346
  601. package/src/ui/button-group.ts +0 -102
  602. package/src/ui/choice-field.ts +0 -72
  603. package/src/ui/field-row.ts +0 -249
  604. package/src/ui/formula-palette.ts +0 -213
  605. package/src/ui/icons.ts +0 -52
  606. package/src/ui/jx-theme.ts +0 -130
  607. package/src/ui/spectrum.ts +0 -340
  608. package/src/ui/value-selector.ts +0 -174
  609. package/src/ui/widgets.ts +0 -172
  610. /package/dist/chunks/{javascript-s56a8dpk.css → javascript-4abzjcy9.css} +0 -0
  611. /package/dist/chunks/{jsonMode-zr9sw0c7.css → jsonMode-vvmpmk34.css} +0 -0
  612. /package/dist/chunks/{monaco-setup-pav68602.css → monaco-setup-8vbyqnhr.css} +0 -0
  613. /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";
@@ -46,19 +44,29 @@ import { documentUrlPattern, dynamicRouteParams } from "../page-params";
46
44
  import { getNodeAtPath, nodeLabel, projectState } from "../store";
47
45
  import { activeRegistry } from "../commands/active-registry";
48
46
  import { notify } from "../services/notify";
47
+ import { mountSurface, registerSurface } from "../ui/surface";
48
+ import { rectOf } from "../utils/geometry";
49
+ import { openMenu } from "./menu";
50
+ import commandbarDoc from "./commandbar.json";
51
+ import type { PresenceProjection } from "../collab/presence-chips";
52
+ import type { MenuHandle, MenuRowProjection } from "./menu";
53
+ import type { SurfaceHandle } from "../ui/surface";
49
54
  import type { Tab } from "../tabs/tab";
50
55
  import type { SiteBuildResult, SitePreviewResult, StudioPlatform } from "../types";
51
56
  import type { CommandRegistry } from "../commands/registry";
52
57
  import type { EffectScope } from "@vue/reactivity";
53
- import type { TemplateResult } from "lit-html";
58
+ import type { JxDocument } from "@jxsuite/schema/types";
59
+
60
+ registerSurface("commandbar", commandbarDoc as unknown as JxDocument);
54
61
 
55
62
  /**
56
63
  * What the Command Bar is handed at mount.
57
64
  *
58
65
  * HANDOFF: **nothing here is read any more** — every control in the band is a command, so the bar
59
66
  * 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.
67
+ * `studio.ts`'s `toolbarPanel.mount(toolbarEl, { … })` object literal keeps type-checking, and the
68
+ * shell tests keep driving the canvas-mode seam through it; deleting them is one edit in that
69
+ * file.
62
70
  */
63
71
  export interface ToolbarCtx {
64
72
  openProject?: () => void;
@@ -72,8 +80,6 @@ export interface ToolbarCtx {
72
80
  closeFunctionEditor?: () => void;
73
81
  }
74
82
 
75
- let _rootEl: HTMLElement | null = null;
76
-
77
83
  /** Test override for the mac CSD layout — happy-dom forbids redefining navigator.platform. */
78
84
  let _isMacOverride: boolean | null = null;
79
85
 
@@ -87,34 +93,114 @@ function isMacPlatform(): boolean {
87
93
  return _isMacOverride ?? navigator.platform.startsWith("Mac");
88
94
  }
89
95
 
90
- let _scope: EffectScope | null = null;
96
+ // ─── The projections ──────────────────────────────────────────────────────────
97
+
98
+ /** One command of the primary cluster, as the document draws it. */
99
+ export interface PrimaryProjection {
100
+ id: string;
101
+ title: string;
102
+ /** {@link commandTooltip}: the name with its chord, or with why it is off. */
103
+ tooltip: string;
104
+ /** The record's glyph, by its name in the kit's manifest; empty for a labelled button. */
105
+ icon: string;
106
+ hasIcon: boolean;
107
+ disabled: boolean;
108
+ }
109
+
110
+ /** One dock toggle: the record, with the dock's state beside it. */
111
+ export interface DockProjection {
112
+ id: string;
113
+ title: string;
114
+ tooltip: string;
115
+ icon: string;
116
+ /** The right-hand dock draws the left-hand glyph mirrored: one shipped shape, two sides. */
117
+ mirror: boolean;
118
+ /** Pressed while the dock is open. */
119
+ selected: boolean;
120
+ disabled: boolean;
121
+ }
122
+
123
+ /** One layout tab. */
124
+ export interface LayoutTabProjection {
125
+ id: string;
126
+ name: string;
127
+ active: boolean;
128
+ title: string;
129
+ }
130
+
131
+ /** One segment of the address bar. */
132
+ export interface SegmentProjection {
133
+ key: "project" | "document" | "selection";
134
+ label: string;
135
+ title: string;
136
+ /** Every segment but the first is preceded by `›`. */
137
+ sepBefore: boolean;
138
+ }
91
139
 
92
140
  /**
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.
141
+ * Which window-control layout the band draws: none in a browser, mac leading, everything else
142
+ * trailing.
99
143
  */
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>`,
144
+ export type WindowControlLayout = "none" | "mac" | "other";
145
+
146
+ /** The document's scope: the projections, and the host functions its handlers `call`. */
147
+ interface CommandBarScope extends Record<string, unknown> {
148
+ csd: WindowControlLayout;
149
+ menuVisible: boolean;
150
+ menuOpen: boolean;
151
+ layoutsVisible: boolean;
152
+ layouts: LayoutTabProjection[];
153
+ segments: SegmentProjection[];
154
+ chord: string;
155
+ hasChord: boolean;
156
+ primary: PrimaryProjection[];
157
+ presenceVisible: boolean;
158
+ presence: PresenceProjection;
159
+ docks: DockProjection[];
160
+ run: (id: string) => void;
161
+ openSegment: (key: SegmentProjection["key"]) => void;
162
+ openPicker: () => void;
163
+ setLayout: (id: string) => void;
164
+ renameLayout: (id: string) => void;
165
+ saveLayout: () => void;
166
+ openStudioMenu: (opener: HTMLElement) => void;
167
+ windowControl: (action: keyof WindowControls) => void;
168
+ }
169
+
170
+ /** What the presence cluster holds while there is nothing to say — the `$switch` hides it. */
171
+ const NO_PRESENCE: PresenceProjection = {
172
+ frozen: false,
173
+ label: "",
174
+ peers: [],
175
+ readOnly: false,
176
+ status: "unavailable",
177
+ title: "",
105
178
  };
106
179
 
107
- // ─── One command, one control ─────────────────────────────────────────────────
180
+ /* The window, with THIS dock's rail marked — one shipped glyph per side.
108
181
 
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
- }
182
+ The pair these replace was `rail-right-open`/`close` under `scaleX(-1)`, because the left-hand
183
+ pair looked absent. Spectrum's two right-hand glyphs were exact mirror images OF EACH OTHER, so
184
+ flipping the wrong member of the pair landed on the other member's appearance — a real, crisp
185
+ arrow pointing the wrong way, looking entirely deliberate. It shipped crossed and nothing could
186
+ see it. Here the glyph is one shape and `mirror` is one boolean, asserted by name.
187
+
188
+ State stays where it already was and where it is already asserted: `selected` on the button. */
189
+ const DOCKS = [
190
+ { dock: "left", icon: "sidebar-simple", id: "view.toggleNavigator", mirror: false },
191
+ { dock: "right", icon: "sidebar-simple", id: "view.toggleInspector", mirror: true },
192
+ { dock: "bottom", icon: "rows", id: "view.toggleBottomDock", mirror: false },
193
+ ] as const;
194
+
195
+ /** The palette mode each segment of the address opens. */
196
+ const SEGMENT_MODES = { document: "files", project: "projects", selection: "nodes" } as const;
197
+
198
+ let _rootEl: HTMLElement | null = null;
199
+ let _scope: EffectScope | null = null;
200
+ let _state: CommandBarScope | null = null;
201
+ let _mount: Promise<SurfaceHandle> | null = null;
202
+ let _handle: SurfaceHandle | null = null;
203
+ let _menu: MenuHandle | null = null;
118
204
 
119
205
  /**
120
206
  * The tooltip a control shows: the action's name, plus its chord, or plus WHY it is off.
@@ -136,38 +222,6 @@ export function commandTooltip(registry: CommandRegistry, id: string): string {
136
222
  return chord ? `${command.title} (${chord})` : command.title;
137
223
  }
138
224
 
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
225
  // ─── View: Open in Browser ───────────────────────────────────────────────────
172
226
 
173
227
  /**
@@ -430,16 +484,6 @@ function errorText(error: unknown): string {
430
484
  return error instanceof Error ? error.message : String(error);
431
485
  }
432
486
 
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
487
  /** The document's label: its path without the project root, which is already segment one. */
444
488
  export function documentSegmentLabel(tab: Tab | null): string {
445
489
  const path = tab?.documentPath;
@@ -469,124 +513,6 @@ export function selectionSegmentLabel(tab: Tab | null): string {
469
513
  return nodeLabel(getNodeAtPath(tab.doc.document, selection));
470
514
  }
471
515
 
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
516
  /** Ask for a name, then run the command. Exported for the same reason `runOpenInBrowser` is. */
591
517
  export async function saveLayoutPrompt(registry: CommandRegistry): Promise<void> {
592
518
  const name = await showPromptDialog("Save layout", {
@@ -611,50 +537,6 @@ export async function renameLayoutPrompt(
611
537
  }
612
538
  }
613
539
 
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
540
  interface WindowControls {
659
541
  minimize: () => void;
660
542
  maximize: () => void;
@@ -666,97 +548,254 @@ function windowControls(): WindowControls | undefined {
666
548
  .__jxPlatform?.windowControls;
667
549
  }
668
550
 
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.
551
+ // ─── The Command Center pill, the layout tabs and the docks, projected ───────
716
552
 
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;
553
+ /**
554
+ * `◈ project › document › selection`: four facts about where you are, in non-collapsible chrome
555
+ * (§4.4).
556
+ */
557
+ function projectSegments(tab: Tab | null): SegmentProjection[] {
558
+ const segments: SegmentProjection[] = [
559
+ {
560
+ key: "project",
561
+ label: projectState?.name ?? "No project",
562
+ sepBefore: false,
563
+ title: "Switch project — opens Project: Open Recent…",
564
+ },
565
+ { key: "document", label: documentSegmentLabel(tab), sepBefore: true, title: "Go to a file" },
566
+ ];
567
+ const selection = selectionSegmentLabel(tab);
568
+ if (selection) {
569
+ segments.push({
570
+ key: "selection",
571
+ label: selection,
572
+ sepBefore: true,
573
+ title: "Go to an element in this document",
574
+ });
575
+ }
576
+ return segments;
577
+ }
734
578
 
735
579
  /**
736
- * ▤▥▦ — the three docks, each rendered from its own record.
580
+ * `Write · Design · Build · Ship · +` — named arrangements, as plain-text tabs.
737
581
  *
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
582
+ * Each tab RUNS `view.setLayout`, so the click, the palette row and an agent all take the same
583
+ * path; double-clicking one renames it, and `+` saves whatever is on screen now. **A layout
584
+ * reconfigures; it never removes**: every panel stays on the rail, on its chord and in the palette
585
+ * after any layout is applied.
586
+ */
587
+ function projectLayouts(registry: CommandRegistry | null): LayoutTabProjection[] {
588
+ // `get` first: a registry that has not been handed the shell's records yet (the skeleton the bar
589
+ // Paints before the bootstrap composes them) has no verb to ask about.
590
+ if (!registry?.get("view.setLayout") || !registry.isEnabled("view.setLayout")) {
591
+ return [];
592
+ }
593
+ return shell.layouts.map((preset) => ({
594
+ active: preset.id === shell.layout,
595
+ id: preset.id,
596
+ name: preset.name,
597
+ title: `${preset.name} layout — double-click to rename`,
598
+ }));
599
+ }
600
+
601
+ /** The primary verb cluster: exactly what declared `commandbar/primary`, in the registry's order. */
602
+ function projectPrimary(registry: CommandRegistry | null): PrimaryProjection[] {
603
+ if (!registry) {
604
+ return [];
605
+ }
606
+ return registry
607
+ .forPlacement("commandbar/primary")
608
+ .filter((command) => registry.isVisible(command.id))
609
+ .map((command) => ({
610
+ disabled: !registry.isEnabled(command.id),
611
+ hasIcon: Boolean(command.icon),
612
+ icon: command.icon ?? "",
613
+ id: command.id,
614
+ title: command.title,
615
+ tooltip: commandTooltip(registry, command.id),
616
+ }));
617
+ }
618
+
619
+ /**
620
+ * ▤▥▦ — the three docks, each projected from its own record.
621
+ *
622
+ * The glyph names the region and `selected` reports its state, so the control says which way it
623
+ * will go; the NAME and the chord still come from the record, which is why ⌘B and this button
740
624
  * cannot drift apart the way ⌘W and the tab strip's × did.
741
625
  */
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
- `;
626
+ function projectDocks(registry: CommandRegistry | null): DockProjection[] {
627
+ if (!registry) {
628
+ return [];
629
+ }
630
+ return DOCKS.flatMap((dock) => {
631
+ const command = registry.get(dock.id);
632
+ if (!command || !registry.isVisible(dock.id)) {
633
+ return [];
634
+ }
635
+ return [
636
+ {
637
+ disabled: !registry.isEnabled(dock.id),
638
+ icon: dock.icon,
639
+ id: dock.id,
640
+ mirror: dock.mirror,
641
+ selected: !shell.docks[dock.dock].collapsed,
642
+ title: command.title,
643
+ tooltip: commandTooltip(registry, dock.id),
644
+ },
645
+ ];
646
+ });
647
+ }
648
+
649
+ // ─── The ⬢ Studio menu (commandbar/overflow) ─────────────────────────────────
650
+
651
+ /**
652
+ * Everything that declared `commandbar/overflow` — the chrome's residue for retired controls —
653
+ * projected as rows for the `menu` surface, so a command that moves from the primary cluster to the
654
+ * overflow keeps its name, its chord and its gate, and the move is one edit to its `menus`.
655
+ */
656
+ function overflowRows(registry: CommandRegistry): MenuRowProjection[] {
657
+ let group: string | undefined;
658
+ return registry.forPlacement("commandbar/overflow").map((command, index) => {
659
+ const dividerAbove = index > 0 && command.group !== group;
660
+ ({ group } = command);
661
+ const enabled = registry.isEnabled(command.id);
662
+ const chord = registry.keymap.formatBinding(command.id);
663
+ const reason = enabled ? undefined : registry.disabledReason(command.id);
664
+ const row: MenuRowProjection = {
665
+ destructive: command.destructive === true,
666
+ disabled: !enabled,
667
+ dividerAbove,
668
+ id: command.id,
669
+ title: command.title,
670
+ };
671
+ if (chord) {
672
+ row.chord = chord;
673
+ }
674
+ if (reason) {
675
+ row.requires = reason;
676
+ }
677
+ return row;
678
+ });
679
+ }
680
+
681
+ /** Open the Studio menu under its button, or close the one that is up: the button is a toggle. */
682
+ function openStudioMenu(opener: HTMLElement): void {
683
+ if (_menu) {
684
+ _menu.close();
685
+ return;
686
+ }
687
+ const registry = activeRegistry();
688
+ if (!registry) {
689
+ return;
690
+ }
691
+ const scope = state();
692
+ const handle = openMenu({
693
+ label: "Studio menu",
694
+ onClosed: (closed) => {
695
+ if (_menu !== closed) {
696
+ return;
697
+ }
698
+ _menu = null;
699
+ scope.menuOpen = false;
700
+ },
701
+ opener,
702
+ place: () => {
703
+ const box = rectOf(opener);
704
+ return { x: box.left, y: box.bottom + 4 };
705
+ },
706
+ region: "studio",
707
+ rows: overflowRows(registry),
708
+ run: (id) => {
709
+ if (registry.isEnabled(id)) {
710
+ void registry.run(id);
711
+ }
712
+ },
713
+ });
714
+ _menu = handle;
715
+ scope.menuOpen = true;
716
+ }
717
+
718
+ // ─── The scope ────────────────────────────────────────────────────────────────
719
+
720
+ /**
721
+ * The one reactive scope the document is mounted over: rebuilt on mount, re-projected by the
722
+ * effect.
723
+ */
724
+ function state(): CommandBarScope {
725
+ _state ??= reactive<CommandBarScope>({
726
+ chord: "",
727
+ csd: "none",
728
+ docks: [],
729
+ hasChord: false,
730
+ layouts: [],
731
+ layoutsVisible: false,
732
+ menuOpen: false,
733
+ menuVisible: false,
734
+ openPicker: () => {
735
+ openQuickSearch("picker");
736
+ },
737
+ openSegment: (key) => {
738
+ openQuickSearch(SEGMENT_MODES[key]);
739
+ },
740
+ openStudioMenu,
741
+ presence: NO_PRESENCE,
742
+ presenceVisible: false,
743
+ primary: [],
744
+ renameLayout: (id) => {
745
+ const registry = activeRegistry();
746
+ const preset = shell.layouts.find((layout) => layout.id === id);
747
+ if (registry && preset) {
748
+ void renameLayoutPrompt(registry, id, preset.name);
749
+ }
750
+ },
751
+ run: (id) => {
752
+ const registry = activeRegistry();
753
+ if (registry?.isEnabled(id)) {
754
+ void registry.run(id);
755
+ }
756
+ },
757
+ saveLayout: () => {
758
+ const registry = activeRegistry();
759
+ if (registry) {
760
+ void saveLayoutPrompt(registry);
761
+ }
762
+ },
763
+ segments: [],
764
+ setLayout: (id) => {
765
+ void activeRegistry()?.run("view.setLayout", { layout: id });
766
+ },
767
+ windowControl: (action) => {
768
+ windowControls()?.[action]();
769
+ },
770
+ }) as CommandBarScope;
771
+ return _state;
772
+ }
773
+
774
+ /**
775
+ * Re-project the registry, the shell and the tab onto the scope. Every write is keyed, so the DOM
776
+ * reconciles in place.
777
+ */
778
+ function project(): void {
779
+ const scope = state();
780
+ const registry = activeRegistry();
781
+ const tab = activeTab.value ?? null;
782
+ const controls = windowControls();
783
+ scope.csd = controls ? (isMacPlatform() ? "mac" : "other") : "none";
784
+ scope.menuVisible = registry !== null;
785
+ scope.layouts = projectLayouts(registry);
786
+ scope.layoutsVisible = scope.layouts.length > 0;
787
+ scope.segments = projectSegments(tab);
788
+ scope.chord = registry?.keymap.formatBinding("palette.open") ?? "";
789
+ scope.hasChord = scope.chord !== "";
790
+ scope.primary = projectPrimary(registry);
791
+ const presence = presenceProjection(tab);
792
+ // The object before the flag, so the case that reads it never renders against the empty one.
793
+ scope.presence = presence ?? NO_PRESENCE;
794
+ scope.presenceVisible = presence !== null;
795
+ scope.docks = projectDocks(registry);
796
+ if (_menu && registry) {
797
+ _menu.setRows(overflowRows(registry));
798
+ }
760
799
  }
761
800
 
762
801
  // ─── Mount ────────────────────────────────────────────────────────────────────
@@ -767,7 +806,8 @@ function dockTogglesTpl(registry: CommandRegistry) {
767
806
  * @param rootEl The `#toolbar` host, stamped `commandbar` by the frame (`shell/tree.ts`).
768
807
  * @param _ctx Ignored — see {@link ToolbarCtx}.
769
808
  */
770
- export function mount(rootEl: HTMLElement, _ctx: ToolbarCtx = {}) {
809
+ export function mount(rootEl: HTMLElement, _ctx: ToolbarCtx = {}): void {
810
+ unmount();
771
811
  _rootEl = rootEl;
772
812
  if (windowControls()) {
773
813
  rootEl.classList.add("electrobun-webkit-app-region-drag");
@@ -777,14 +817,14 @@ export function mount(rootEl: HTMLElement, _ctx: ToolbarCtx = {}) {
777
817
  effect(() => {
778
818
  // Dock visibility, source control and the project are shell state, tracked here so the band
779
819
  // 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.
820
+ // The boot-time restore — not just this document's handlers.
781
821
  void shell.docks.left.collapsed;
782
822
  void shell.docks.right.collapsed;
783
823
  void shell.docks.bottom.collapsed;
784
824
  void shell.git.status;
785
825
  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.
826
+ // The layout tabs are a rendering of the project's own record, so the band repaints when a
827
+ // Layout is saved, renamed or deleted from anywhere.
788
828
  void shell.layout;
789
829
  void shell.layouts;
790
830
  // The registry itself is reactive state: it is composed AFTER this mount runs, and reading it
@@ -804,56 +844,41 @@ export function mount(rootEl: HTMLElement, _ctx: ToolbarCtx = {}) {
804
844
  void tab.history.index;
805
845
  void tab.history.snapshots.length;
806
846
  }
807
- render();
847
+ try {
848
+ project();
849
+ } catch (error) {
850
+ console.error("command bar projection error:", error);
851
+ }
808
852
  });
809
853
  });
854
+ _mount = mountSurface("commandbar", state(), rootEl);
855
+ void _mount.then((handle) => {
856
+ _handle = handle;
857
+ });
810
858
  }
811
859
 
812
- export function unmount() {
860
+ export function unmount(): void {
861
+ _menu?.close();
813
862
  _scope?.stop();
814
863
  _scope = null;
864
+ const pending = _mount;
865
+ _mount = null;
866
+ if (_handle) {
867
+ _handle.dispose();
868
+ _handle = null;
869
+ } else if (pending) {
870
+ void pending.then((handle) => handle.dispose());
871
+ }
872
+ _state = null;
815
873
  _rootEl = null;
816
874
  }
817
875
 
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
876
  /**
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.
877
+ * Re-project now. The effect does this on its own; the bootstrap calls it after a store it does not
878
+ * track hydrates.
834
879
  */
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
- `;
880
+ export function render(): void {
881
+ if (_rootEl) {
882
+ project();
883
+ }
859
884
  }