@jxsuite/studio 4.1.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 (607) 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-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-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-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-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-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-msd847zn.js → studio-25hkdrkw.js} +357 -86
  62. package/dist/chunks/studio-26d7myd1.js +258 -0
  63. package/dist/chunks/{studio-4r1pv6zf.js → studio-38emantv.js} +22 -5
  64. package/dist/chunks/studio-3gxv60tn.js +422 -0
  65. package/dist/chunks/{studio-fbf92ckf.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-1wagb550.js → studio-5523bpzt.js} +11 -11
  69. package/dist/chunks/{studio-x4qkdat5.js → studio-57mf66w2.js} +1749 -2481
  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-ahn3pdgx.js → studio-79y5sz51.js} +52 -38
  78. package/dist/chunks/{studio-gcye7qka.js → studio-7e11jpa9.js} +21 -21
  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-x813zp48.js → studio-arfpe389.js} +30 -30
  88. package/dist/chunks/{studio-trxrabqf.js → studio-az3zd49t.js} +475 -283
  89. package/dist/chunks/studio-cpvb2ed1.js +1171 -0
  90. package/dist/chunks/{studio-f3wqjtk5.js → studio-d0300636.js} +5076 -18
  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-3bqpr0zh.js → studio-em9nw8eh.js} +299 -110
  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-dm19q43r.js → studio-hhvs4sdg.js} +10 -10
  100. package/dist/chunks/{studio-q9jd1a60.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-2b946z26.js → studio-rkstz940.js} +3658 -6134
  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-jkftxyvz.js → studio-s6z853aq.js} +5858 -2152
  113. package/dist/chunks/{studio-6yw4y3my.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-qjy2z9bv.js → studio-vz68yn9e.js} +32 -137
  118. package/dist/chunks/studio-wqpngpsk.js +174 -0
  119. package/dist/chunks/{studio-b55y6ewx.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 +1780 -667
  135. package/dist/studio.js +52819 -36480
  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 +310 -117
  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 +369 -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 +165 -8
  178. package/src/content/entry-commands.ts +16 -23
  179. package/src/content/entry-editor.ts +144 -101
  180. package/src/editor/canvas-slash-bridge.ts +2 -2
  181. package/src/editor/context-menu.ts +49 -200
  182. package/src/editor/convert-to-component.ts +42 -82
  183. package/src/editor/convert-to-repeater.ts +132 -173
  184. package/src/editor/inline-edit.ts +11 -0
  185. package/src/editor/shortcuts.ts +65 -26
  186. package/src/editor/slash-menu.ts +150 -157
  187. package/src/files/file-ops.ts +117 -11
  188. package/src/files/files.ts +943 -660
  189. package/src/files/json-layout.ts +474 -0
  190. package/src/files/serialize-document.ts +8 -1
  191. package/src/format/convert-file.ts +16 -10
  192. package/src/format/format-host.ts +50 -1
  193. package/src/github/github-auth.ts +71 -29
  194. package/src/github/github-publish.ts +57 -65
  195. package/src/grid/cell-editors.ts +46 -4
  196. package/src/grid/cell-popovers.ts +65 -62
  197. package/src/grid/grid-open.ts +86 -64
  198. package/src/grid/grid-panel.ts +390 -469
  199. package/src/grid/redirects-grid.ts +40 -44
  200. package/src/hosting/layout.ts +2 -0
  201. package/src/i18n/i18n-commands.ts +127 -62
  202. package/src/media/media-pane.ts +83 -133
  203. package/src/new-project/add-repo-modal.ts +125 -176
  204. package/src/new-project/import-tab.ts +181 -252
  205. package/src/new-project/location-fields.ts +93 -160
  206. package/src/new-project/new-project-modal.ts +376 -367
  207. package/src/packages/jxsuite-update.ts +76 -18
  208. package/src/panels/activity-panel.ts +137 -104
  209. package/src/panels/ai-chat/chat-markdown.ts +15 -13
  210. package/src/panels/ai-chat/chat-view.ts +307 -472
  211. package/src/panels/ai-chat/composer.ts +175 -163
  212. package/src/panels/ai-chat/sessions-view.ts +31 -62
  213. package/src/panels/ai-panel.ts +210 -185
  214. package/src/panels/block-action-bar.ts +437 -643
  215. package/src/panels/bottom-dock.ts +112 -47
  216. package/src/panels/chat-panel.ts +19 -14
  217. package/src/panels/data-explorer.ts +141 -102
  218. package/src/panels/data-grid.ts +183 -150
  219. package/src/panels/dnd.ts +75 -20
  220. package/src/panels/editors.ts +26 -19
  221. package/src/panels/elements-panel.ts +243 -186
  222. package/src/panels/empty-state.ts +26 -42
  223. package/src/panels/events-panel.ts +950 -729
  224. package/src/panels/formula-workspace.ts +434 -207
  225. package/src/panels/frontmatter-fields.ts +185 -153
  226. package/src/panels/frontmatter-panel.ts +411 -187
  227. package/src/panels/git-diff-open.ts +148 -0
  228. package/src/panels/git-panel.ts +679 -605
  229. package/src/panels/head-panel.ts +617 -435
  230. package/src/panels/i18n-panel.ts +153 -133
  231. package/src/panels/imports-panel.ts +388 -414
  232. package/src/panels/jump-bar.ts +174 -147
  233. package/src/panels/layers-panel.ts +785 -671
  234. package/src/panels/left-panel.ts +166 -94
  235. package/src/panels/navigator-panels.ts +1 -1
  236. package/src/panels/pane-context.ts +529 -677
  237. package/src/panels/pane-grid.ts +265 -241
  238. package/src/panels/panel-registry.ts +15 -4
  239. package/src/panels/problems-panel.ts +177 -99
  240. package/src/panels/properties-panel.ts +1525 -966
  241. package/src/panels/provenance.ts +31 -113
  242. package/src/panels/quick-search.ts +217 -172
  243. package/src/panels/right-panel.ts +135 -169
  244. package/src/panels/seo-modal.ts +346 -167
  245. package/src/panels/settings-menu.ts +109 -646
  246. package/src/panels/settings-pane.ts +102 -86
  247. package/src/panels/signals-panel.ts +1318 -1172
  248. package/src/panels/statement-editor.ts +556 -428
  249. package/src/panels/style-panel.ts +1367 -630
  250. package/src/panels/style-utils.ts +36 -32
  251. package/src/panels/stylebook-doc.ts +30 -73
  252. package/src/panels/stylebook-layers-panel.ts +202 -109
  253. package/src/panels/stylebook-panel.ts +124 -88
  254. package/src/panels/tab-strip.ts +385 -287
  255. package/src/platforms/cloud.ts +106 -16
  256. package/src/platforms/devserver.ts +13 -0
  257. package/src/publish/deploy-checklist.ts +124 -78
  258. package/src/publish/publish-commands.ts +4 -12
  259. package/src/publish/publish-panel.ts +134 -328
  260. package/src/services/a11y-report.ts +100 -43
  261. package/src/services/ai-command-tools.ts +468 -0
  262. package/src/services/ai-session-store.ts +6 -0
  263. package/src/services/ai-system-prompt.ts +110 -18
  264. package/src/services/ai-tools.ts +28 -143
  265. package/src/services/ai-write-report.ts +174 -0
  266. package/src/services/document-assistant.ts +50 -2
  267. package/src/services/idle.ts +16 -14
  268. package/src/services/live-surfaces.ts +237 -0
  269. package/src/services/model-uri.ts +75 -0
  270. package/src/services/notify.ts +63 -6
  271. package/src/services/popover-report.ts +325 -0
  272. package/src/services/surface-registry.ts +56 -0
  273. package/src/services/tool-executor.ts +6 -0
  274. package/src/settings/contexts-section.ts +301 -299
  275. package/src/settings/contributed-section.ts +361 -222
  276. package/src/settings/css-vars-editor.ts +320 -325
  277. package/src/settings/defs-editor.ts +572 -535
  278. package/src/settings/dependencies-editor.ts +79 -107
  279. package/src/settings/extension-commands.ts +379 -0
  280. package/src/settings/extension-rows.ts +113 -0
  281. package/src/settings/extensions-section.ts +287 -0
  282. package/src/settings/general-settings.ts +130 -181
  283. package/src/settings/head-editor.ts +10 -305
  284. package/src/settings/locales-section.ts +146 -196
  285. package/src/settings/preferences-accounts.ts +1 -1
  286. package/src/settings/preferences-dialog.ts +350 -330
  287. package/src/settings/project-sections.ts +108 -181
  288. package/src/settings/schema-field-ui.ts +11 -399
  289. package/src/settings/section-registry.ts +8 -1
  290. package/src/settings/settings-document.ts +31 -29
  291. package/src/shell/tree.ts +42 -87
  292. package/src/shell.ts +48 -36
  293. package/src/studio.ts +90 -67
  294. package/src/style/token-ref.ts +0 -39
  295. package/src/surfaces/about.json +263 -0
  296. package/src/surfaces/about.ts +163 -0
  297. package/src/surfaces/add-repo.json +486 -0
  298. package/src/surfaces/add-repo.ts +236 -0
  299. package/src/surfaces/ai-chat.json +2185 -0
  300. package/src/surfaces/ai-chat.ts +549 -0
  301. package/src/surfaces/ai-credentials-form.json +332 -0
  302. package/src/surfaces/ai-credentials-form.ts +151 -0
  303. package/src/surfaces/ai-managed-connect.json +108 -0
  304. package/src/surfaces/ai-managed-connect.ts +123 -0
  305. package/src/surfaces/ai-model-picker.json +114 -0
  306. package/src/surfaces/ai-model-picker.ts +139 -0
  307. package/src/surfaces/block-action-bar.json +640 -0
  308. package/src/surfaces/block-action-bar.ts +368 -0
  309. package/src/surfaces/bottom-dock.json +154 -0
  310. package/src/surfaces/bottom-dock.ts +125 -0
  311. package/src/surfaces/canvas-stage.json +444 -0
  312. package/src/surfaces/canvas-stage.ts +253 -0
  313. package/src/surfaces/cf-account-picker.json +300 -0
  314. package/src/surfaces/cf-account-picker.ts +214 -0
  315. package/src/surfaces/commandbar.json +984 -0
  316. package/src/{panels/toolbar.ts → surfaces/commandbar.ts} +414 -389
  317. package/src/surfaces/convert-repeater.json +309 -0
  318. package/src/surfaces/convert-repeater.ts +276 -0
  319. package/src/surfaces/data-actions.json +157 -0
  320. package/src/surfaces/data-actions.ts +115 -0
  321. package/src/surfaces/dialog.json +289 -0
  322. package/src/surfaces/dialog.ts +343 -0
  323. package/src/surfaces/diff-toolbar.json +266 -0
  324. package/src/surfaces/diff-toolbar.ts +139 -0
  325. package/src/surfaces/doc-header.json +739 -0
  326. package/src/surfaces/doc-header.ts +223 -0
  327. package/src/surfaces/empty-state.json +152 -0
  328. package/src/surfaces/empty-state.ts +173 -0
  329. package/src/surfaces/entry-editor.json +281 -0
  330. package/src/surfaces/entry-editor.ts +191 -0
  331. package/src/surfaces/expression-editor.json +977 -0
  332. package/src/surfaces/expression-editor.ts +266 -0
  333. package/src/surfaces/files-panel.json +592 -0
  334. package/src/surfaces/files-panel.ts +301 -0
  335. package/src/surfaces/formula-palette.json +364 -0
  336. package/src/surfaces/formula-palette.ts +340 -0
  337. package/src/surfaces/git-panel.json +1822 -0
  338. package/src/surfaces/git-panel.ts +272 -0
  339. package/src/surfaces/github-auth.json +112 -0
  340. package/src/surfaces/github-auth.ts +139 -0
  341. package/src/surfaces/github-publish.json +206 -0
  342. package/src/surfaces/github-publish.ts +169 -0
  343. package/src/surfaces/grid-cell.json +218 -0
  344. package/src/surfaces/grid-cell.ts +120 -0
  345. package/src/surfaces/grid-open.json +147 -0
  346. package/src/surfaces/grid-open.ts +87 -0
  347. package/src/surfaces/grid-panel.json +706 -0
  348. package/src/surfaces/grid-panel.ts +206 -0
  349. package/src/surfaces/grid-replace.json +179 -0
  350. package/src/surfaces/grid-replace.ts +89 -0
  351. package/src/surfaces/grid-views.json +446 -0
  352. package/src/surfaces/grid-views.ts +172 -0
  353. package/src/surfaces/inspector-dock.json +187 -0
  354. package/src/surfaces/inspector-dock.ts +161 -0
  355. package/src/surfaces/jump-bar.json +243 -0
  356. package/src/surfaces/jump-bar.ts +129 -0
  357. package/src/surfaces/jxsuite-update.json +146 -0
  358. package/src/surfaces/jxsuite-update.ts +149 -0
  359. package/src/surfaces/library-pane.json +1509 -0
  360. package/src/surfaces/library-pane.ts +363 -0
  361. package/src/surfaces/logic-panel.json +1949 -0
  362. package/src/surfaces/logic-panel.ts +369 -0
  363. package/src/surfaces/logic-workspace.json +685 -0
  364. package/src/surfaces/logic-workspace.ts +238 -0
  365. package/src/surfaces/media-browser.json +303 -0
  366. package/src/surfaces/media-browser.ts +145 -0
  367. package/src/surfaces/media-field.json +163 -0
  368. package/src/surfaces/media-field.ts +116 -0
  369. package/src/surfaces/media-pane.json +513 -0
  370. package/src/surfaces/media-pane.ts +197 -0
  371. package/src/surfaces/menu.json +232 -0
  372. package/src/surfaces/menu.ts +375 -0
  373. package/src/surfaces/navigator-dock.json +113 -0
  374. package/src/surfaces/navigator-dock.ts +130 -0
  375. package/src/surfaces/new-project.json +1654 -0
  376. package/src/surfaces/new-project.ts +411 -0
  377. package/src/surfaces/palette.json +535 -0
  378. package/src/surfaces/pane-context.json +1497 -0
  379. package/src/surfaces/pane-context.ts +513 -0
  380. package/src/surfaces/pane-grid.json +188 -0
  381. package/src/surfaces/pane-grid.ts +250 -0
  382. package/src/surfaces/panel-activity.json +563 -0
  383. package/src/surfaces/panel-activity.ts +187 -0
  384. package/src/surfaces/panel-data.json +141 -0
  385. package/src/surfaces/panel-data.ts +156 -0
  386. package/src/surfaces/panel-deploy-checklist.json +283 -0
  387. package/src/surfaces/panel-deploy-checklist.ts +175 -0
  388. package/src/surfaces/panel-elements.json +367 -0
  389. package/src/surfaces/panel-elements.ts +175 -0
  390. package/src/surfaces/panel-i18n.json +569 -0
  391. package/src/surfaces/panel-i18n.ts +212 -0
  392. package/src/surfaces/panel-imports.json +780 -0
  393. package/src/surfaces/panel-imports.ts +209 -0
  394. package/src/surfaces/panel-outline.json +734 -0
  395. package/src/surfaces/panel-outline.ts +326 -0
  396. package/src/surfaces/panel-page.json +1017 -0
  397. package/src/surfaces/panel-page.ts +240 -0
  398. package/src/surfaces/panel-problems.json +497 -0
  399. package/src/surfaces/panel-problems.ts +157 -0
  400. package/src/surfaces/panel-signals.json +1973 -0
  401. package/src/surfaces/panel-signals.ts +436 -0
  402. package/src/surfaces/panel-stylebook-layers.json +317 -0
  403. package/src/surfaces/panel-stylebook-layers.ts +155 -0
  404. package/src/surfaces/preferences.json +850 -0
  405. package/src/surfaces/preferences.ts +332 -0
  406. package/src/surfaces/progress-modal.json +110 -0
  407. package/src/surfaces/progress-modal.ts +150 -0
  408. package/src/surfaces/properties-panel.json +1578 -0
  409. package/src/surfaces/properties-panel.ts +337 -0
  410. package/src/surfaces/publish.json +945 -0
  411. package/src/surfaces/publish.ts +392 -0
  412. package/src/surfaces/push-plan.json +110 -0
  413. package/src/surfaces/push-plan.ts +87 -0
  414. package/src/surfaces/rail.json +255 -0
  415. package/src/surfaces/rail.ts +206 -0
  416. package/src/surfaces/reference-field.json +191 -0
  417. package/src/surfaces/reference-field.ts +168 -0
  418. package/src/surfaces/schema-builder.json +1011 -0
  419. package/src/surfaces/schema-builder.ts +181 -0
  420. package/src/surfaces/schema-form.json +1013 -0
  421. package/src/surfaces/schema-form.ts +287 -0
  422. package/src/surfaces/secret-field.json +60 -0
  423. package/src/surfaces/secret-field.ts +146 -0
  424. package/src/surfaces/seo.json +932 -0
  425. package/src/surfaces/seo.ts +334 -0
  426. package/src/surfaces/settings-contexts.json +538 -0
  427. package/src/surfaces/settings-contexts.ts +196 -0
  428. package/src/surfaces/settings-contributed.json +489 -0
  429. package/src/surfaces/settings-contributed.ts +199 -0
  430. package/src/surfaces/settings-css-vars.json +820 -0
  431. package/src/surfaces/settings-css-vars.ts +199 -0
  432. package/src/surfaces/settings-defs.json +1363 -0
  433. package/src/surfaces/settings-defs.ts +232 -0
  434. package/src/surfaces/settings-deploy.json +108 -0
  435. package/src/surfaces/settings-deploy.ts +135 -0
  436. package/src/surfaces/settings-extensions.json +486 -0
  437. package/src/surfaces/settings-extensions.ts +201 -0
  438. package/src/surfaces/settings-head.json +779 -0
  439. package/src/surfaces/settings-head.ts +397 -0
  440. package/src/surfaces/settings-locales.json +420 -0
  441. package/src/surfaces/settings-locales.ts +137 -0
  442. package/src/surfaces/settings-overview.json +423 -0
  443. package/src/surfaces/settings-overview.ts +218 -0
  444. package/src/surfaces/settings-packages.json +549 -0
  445. package/src/surfaces/settings-packages.ts +158 -0
  446. package/src/surfaces/settings-pane.json +147 -0
  447. package/src/surfaces/settings-pane.ts +150 -0
  448. package/src/surfaces/settings-rawjson.json +104 -0
  449. package/src/surfaces/settings-rawjson.ts +107 -0
  450. package/src/surfaces/shell.json +99 -0
  451. package/src/surfaces/shell.ts +30 -0
  452. package/src/surfaces/slash-menu.json +180 -0
  453. package/src/surfaces/slash-menu.ts +272 -0
  454. package/src/surfaces/statements.json +607 -0
  455. package/src/surfaces/statements.ts +238 -0
  456. package/src/surfaces/statusbar.json +94 -0
  457. package/src/{panels → surfaces}/statusbar.ts +163 -90
  458. package/src/surfaces/style-panel.json +1643 -0
  459. package/src/surfaces/style-panel.ts +338 -0
  460. package/src/surfaces/stylebook-chrome.json +122 -0
  461. package/src/surfaces/stylebook-chrome.ts +122 -0
  462. package/src/surfaces/tab-strip.json +448 -0
  463. package/src/surfaces/tab-strip.ts +200 -0
  464. package/src/surfaces/target-line.json +419 -0
  465. package/src/surfaces/target-line.ts +349 -0
  466. package/src/surfaces/toasts.json +189 -0
  467. package/src/surfaces/welcome.json +571 -0
  468. package/src/surfaces/welcome.ts +338 -0
  469. package/src/tabs/project-config.ts +71 -15
  470. package/src/tabs/tab.ts +52 -1
  471. package/src/types.ts +22 -0
  472. package/src/ui/ai-credentials-form.ts +104 -100
  473. package/src/ui/ai-managed-connect.ts +54 -36
  474. package/src/ui/ai-model-picker.ts +75 -47
  475. package/src/ui/cf-account-picker.ts +76 -141
  476. package/src/ui/color-selector.ts +80 -301
  477. package/src/ui/dynamic-slot.ts +43 -283
  478. package/src/ui/expression-editor.ts +797 -576
  479. package/src/ui/field-input.ts +8 -181
  480. package/src/ui/form-controls.ts +838 -366
  481. package/src/ui/formula-chips.ts +80 -85
  482. package/src/ui/kit.ts +33 -0
  483. package/src/ui/layers.ts +293 -593
  484. package/src/ui/media-picker.ts +203 -284
  485. package/src/ui/panel-resize.ts +251 -64
  486. package/src/ui/popover-surface.ts +153 -0
  487. package/src/ui/progress-modal.ts +22 -60
  488. package/src/ui/regions.ts +13 -7
  489. package/src/ui/schema-form.ts +906 -440
  490. package/src/ui/surface.ts +122 -0
  491. package/src/ui/unit-selector.ts +15 -131
  492. package/src/ui/value-source.ts +14 -0
  493. package/src/ui/virtual-window.ts +88 -9
  494. package/src/utils/canvas-media.ts +72 -0
  495. package/src/utils/edit-display.ts +7 -2
  496. package/src/utils/element-selectors.ts +69 -0
  497. package/src/utils/link-target.ts +2 -2
  498. package/src/utils/studio-utils.ts +0 -10
  499. package/src/view.ts +15 -0
  500. package/src/workspace/pane-derive.ts +36 -14
  501. package/src/workspace/workspace.ts +15 -6
  502. package/styles/canvas.css +72 -467
  503. package/styles/forced-colors.css +48 -82
  504. package/styles/forced-colors.json +41 -0
  505. package/styles/inspector.css +2 -1301
  506. package/styles/overlays.css +96 -2823
  507. package/styles/panels.css +6 -1423
  508. package/styles/shell-frame.css +272 -0
  509. package/styles/shell-frame.json +227 -0
  510. package/styles/shell.css +0 -1568
  511. package/styles/tokens.css +65 -136
  512. package/styles/tokens.json +84 -0
  513. package/dist/chunks/Overlay.dev-etqjwyf9.js +0 -23
  514. package/dist/chunks/adapter-jkcyhgtr.js +0 -14
  515. package/dist/chunks/cf-account-picker-en86v7x7.js +0 -163
  516. package/dist/chunks/cf-settings-45fgyx8y.js +0 -21
  517. package/dist/chunks/components-zmx10t62.js +0 -21
  518. package/dist/chunks/content-source-48xg86wj.js +0 -49
  519. package/dist/chunks/convert-to-repeater-43tfn7cs.js +0 -268
  520. package/dist/chunks/editor-cptq5jp1.js +0 -37
  521. package/dist/chunks/entry-commands-e63f1xc0.js +0 -46
  522. package/dist/chunks/extension-sections-txv08xxg.js +0 -311
  523. package/dist/chunks/file-ops-wbh4hhsx.js +0 -48
  524. package/dist/chunks/files-3kfkwhk4.js +0 -93
  525. package/dist/chunks/flow-qbfgrpeh.js +0 -634
  526. package/dist/chunks/focus-trap.esm-9t2drp0g.js +0 -1025
  527. package/dist/chunks/focus-visible-6eg70z18.js +0 -167
  528. package/dist/chunks/format-host-w16q81tf.js +0 -43
  529. package/dist/chunks/fs-events-hrrg328g.js +0 -21
  530. package/dist/chunks/git-panel-532whr1y.js +0 -59
  531. package/dist/chunks/index-zqxmqxmh.js +0 -109
  532. package/dist/chunks/jx-validate-8er6gbxy.js +0 -19
  533. package/dist/chunks/layers-panel-07hm7g5j.js +0 -67
  534. package/dist/chunks/layers-q75tj2mf.js +0 -45
  535. package/dist/chunks/lit-html-j055mkh8.js +0 -22
  536. package/dist/chunks/monaco-buffer-dqw0xxpx.js +0 -25
  537. package/dist/chunks/notify-yjm79n4s.js +0 -30
  538. package/dist/chunks/parse-24d0gay3.js +0 -19
  539. package/dist/chunks/platform-ba1j1gbh.js +0 -16
  540. package/dist/chunks/publish-panel-v9st96ja.js +0 -29
  541. package/dist/chunks/quick-search-zccjp6gv.js +0 -66
  542. package/dist/chunks/references-3qx8fkyp.js +0 -29
  543. package/dist/chunks/right-panel-vm3b6kwy.js +0 -46
  544. package/dist/chunks/section-registry-sx1ykvkw.js +0 -30
  545. package/dist/chunks/sp-overlay.dev-qgvhnxf4.js +0 -18
  546. package/dist/chunks/sp-popover.dev-n2etmqdp.js +0 -9
  547. package/dist/chunks/sp-progress-circle.dev-hryhj0es.js +0 -15
  548. package/dist/chunks/sp-tooltip.dev-4sastdnw.js +0 -15
  549. package/dist/chunks/sp-tray.dev-tkbscm06.js +0 -199
  550. package/dist/chunks/studio-0a5qrh01.js +0 -3
  551. package/dist/chunks/studio-0bq6eh7v.js +0 -12
  552. package/dist/chunks/studio-18j3bmcz.js +0 -14
  553. package/dist/chunks/studio-1ngbejft.js +0 -181
  554. package/dist/chunks/studio-4b5z1x43.js +0 -424
  555. package/dist/chunks/studio-4n2b5gzb.js +0 -357
  556. package/dist/chunks/studio-4rzyrtjz.js +0 -426
  557. package/dist/chunks/studio-5fx3g9jg.js +0 -4325
  558. package/dist/chunks/studio-5myknm0a.js +0 -1903
  559. package/dist/chunks/studio-7afd21nw.js +0 -67
  560. package/dist/chunks/studio-7nte2hj4.js +0 -613
  561. package/dist/chunks/studio-8jpzqk5s.js +0 -128
  562. package/dist/chunks/studio-8q517dfr.js +0 -24
  563. package/dist/chunks/studio-950k0dx6.js +0 -6939
  564. package/dist/chunks/studio-9n4mrmnb.js +0 -184
  565. package/dist/chunks/studio-a0tswy0w.js +0 -58
  566. package/dist/chunks/studio-ahnzd9rg.js +0 -23
  567. package/dist/chunks/studio-cmb03bg5.js +0 -13890
  568. package/dist/chunks/studio-d1ks410h.js +0 -175
  569. package/dist/chunks/studio-ef49pnzx.js +0 -387
  570. package/dist/chunks/studio-eqq1w638.js +0 -113
  571. package/dist/chunks/studio-ew0djmhg.js +0 -341
  572. package/dist/chunks/studio-gfgb8pe6.js +0 -1652
  573. package/dist/chunks/studio-h4rxscfc.js +0 -10
  574. package/dist/chunks/studio-jc994dnr.js +0 -1066
  575. package/dist/chunks/studio-kyh9avvv.js +0 -28
  576. package/dist/chunks/studio-m3hwn7y1.js +0 -120
  577. package/dist/chunks/studio-mtfnaqs9.js +0 -659
  578. package/dist/chunks/studio-n6j9t10q.js +0 -318
  579. package/dist/chunks/studio-n8b9wqwy.js +0 -291
  580. package/dist/chunks/studio-qcy05dp8.js +0 -1743
  581. package/dist/chunks/studio-qq6jym04.js +0 -53
  582. package/dist/chunks/studio-rdx8jcxy.js +0 -83
  583. package/dist/chunks/studio-rn9ta2mz.js +0 -111
  584. package/dist/chunks/studio-t7qb9db8.js +0 -665
  585. package/dist/chunks/studio-tmc5emrw.js +0 -8
  586. package/dist/chunks/studio-z6nw2ek2.js +0 -18
  587. package/dist/chunks/studio-zczps70q.js +0 -378
  588. package/dist/chunks/studio-zqazsct2.js +0 -1823
  589. package/dist/chunks/workspace-fjx52ryv.js +0 -89
  590. package/src/panels/activity-bar.ts +0 -251
  591. package/src/panels/panel-scheduler.ts +0 -219
  592. package/src/panels/style-inputs.ts +0 -237
  593. package/src/panels/target-line.ts +0 -274
  594. package/src/panels/welcome-screen.ts +0 -346
  595. package/src/ui/button-group.ts +0 -102
  596. package/src/ui/choice-field.ts +0 -72
  597. package/src/ui/field-row.ts +0 -249
  598. package/src/ui/formula-palette.ts +0 -213
  599. package/src/ui/icons.ts +0 -52
  600. package/src/ui/jx-theme.ts +0 -130
  601. package/src/ui/spectrum.ts +0 -340
  602. package/src/ui/value-selector.ts +0 -174
  603. package/src/ui/widgets.ts +0 -172
  604. /package/dist/chunks/{javascript-s56a8dpk.css → javascript-4abzjcy9.css} +0 -0
  605. /package/dist/chunks/{jsonMode-zr9sw0c7.css → jsonMode-vvmpmk34.css} +0 -0
  606. /package/dist/chunks/{monaco-setup-pav68602.css → monaco-setup-8vbyqnhr.css} +0 -0
  607. /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
  }