@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
@@ -25,28 +25,54 @@
25
25
  * press"), and rebindable — as a LAYER over the registry (`settings/preferences-keymap.ts`), so
26
26
  * the sheet stays a projection and the app's own record of a default is never edited.
27
27
  *
28
- * Modality: an `sp-dialog-wrapper` through {@link showDialog}, which is focus-managed and dismissed
29
- * by Escape — not `openModal`'s `inset:40px` blackout. Preferences does not suspend the app.
28
+ * **This module is the FLOW; `surfaces/preferences.json` is what it draws.** Every function below
29
+ * either answers a question about state or changes some, and the answers leave here as strings and
30
+ * booleans — `keyboardEmpty` is a sentence rather than a case name, a row's chord is already
31
+ * formatted, an account's buttons are already decided. Which of those add up to which branch of the
32
+ * document is `surfaces/preferences.ts`'s.
33
+ *
34
+ * **Only the showing section is projected.** `accountsView()` reads three credential stores and
35
+ * `keyboardView()` walks the whole registry, and a repaint of the theme toggle has no business
36
+ * doing either — so each is built when its own section is up and left empty otherwise, exactly as
37
+ * the four `*Tpl()` functions this replaced were called one at a time.
38
+ *
39
+ * Modality: a `jx-dialog` in the dialog layer, which is focus-managed, dismissed by Escape and
40
+ * backed by the platform's own top layer — not the full-viewport blackout the lit modal path used
41
+ * to paint. Preferences does not suspend the app.
30
42
  */
31
43
 
32
- import { html, nothing, render as litRender } from "lit-html";
33
- import { ref } from "lit-html/directives/ref.js";
34
44
  import { CHROME_THEMES, setChromeTheme, shell } from "../shell";
35
- import { showDialog } from "../ui/layers";
45
+ import { layerHost } from "../ui/layers";
36
46
  import { createAiCredentialsForm } from "../ui/ai-credentials-form";
37
47
  import { createManagedConnect } from "../ui/ai-managed-connect";
38
48
  import { activeRegistry } from "../commands/active-registry";
39
49
  import { shortcutReference, SCOPE_LABELS } from "../commands/reference";
40
50
  import { chordFromEvent } from "../commands/keymap";
41
- import { listAccounts, notifyCredentialsChanged, revokeAccount } from "./preferences-accounts";
51
+ import {
52
+ ensureCfConnection,
53
+ listAccounts,
54
+ notifyCredentialsChanged,
55
+ platformBrokersCf,
56
+ refreshCfConnection,
57
+ resetCfConnectionCache,
58
+ revokeAccount,
59
+ } from "./preferences-accounts";
42
60
  import { applyKeybindingOverrides, rebindCommand, resetKeybinding } from "./preferences-keymap";
43
- import { overlayRegion } from "../ui/regions";
61
+ import { openPreferencesSurface } from "../surfaces/preferences";
44
62
 
45
- import type { TemplateResult } from "lit-html";
46
63
  import type { ChromeTheme } from "../shell";
47
64
  import type { ShortcutRow } from "../commands/reference";
48
65
  import type { RebindResult } from "./preferences-keymap";
49
66
  import type { AnyCommand, CommandRegistry } from "../commands/registry";
67
+ import type {
68
+ PreferencesAccountRow,
69
+ PreferencesIsland,
70
+ PreferencesKeyGroup,
71
+ PreferencesSectionRow,
72
+ PreferencesSurfaceHandle,
73
+ PreferencesThemeRow,
74
+ PreferencesView,
75
+ } from "../surfaces/preferences";
50
76
 
51
77
  /*
52
78
  * The sections moved to `./preferences-sections`, a leaf with no imports, so the rail's gear menu
@@ -71,15 +97,12 @@ export type { PreferencesSection } from "./preferences-sections";
71
97
 
72
98
  let _section = DEFAULT_PREFERENCES_SECTION;
73
99
 
74
- /** Repaint the open sheet, if one is up. */
75
- let _rerender: (() => void) | null = null;
76
-
77
- /** Dismiss the open sheet, if one is up. */
78
- let _close: (() => void) | null = null;
100
+ /** The mounted sheet, or `null` while none is up. */
101
+ let _surface: PreferencesSurfaceHandle | null = null;
79
102
 
80
103
  /** Whether Preferences is on screen. */
81
104
  export function isPreferencesOpen(): boolean {
82
- return _close !== null;
105
+ return _surface !== null;
83
106
  }
84
107
 
85
108
  /** The section currently showing. Exposed for tests and for the `app.preferences` round-trip. */
@@ -88,7 +111,8 @@ export function preferencesSection(): string {
88
111
  }
89
112
 
90
113
  function repaint(): void {
91
- _rerender?.();
114
+ _surface?.update(view());
115
+ fillIslands();
92
116
  }
93
117
 
94
118
  /** Save/revoke both change what other surfaces show, so they announce as well as repaint. */
@@ -97,12 +121,25 @@ function credentialsChanged(): void {
97
121
  repaint();
98
122
  }
99
123
 
124
+ /**
125
+ * Showing a section is a chance to find out it is stale.
126
+ *
127
+ * Only Accounts has anything to re-read: the brokered Cloudflare connection lives on the server, so
128
+ * a grant that lapsed while Preferences was closed — or while the author was in Keyboard — would
129
+ * otherwise be reported from a cache taken before it did.
130
+ */
131
+ function sectionShown(): void {
132
+ if (_section === "accounts" && platformBrokersCf()) {
133
+ void refreshCfConnection().then(repaint);
134
+ }
135
+ }
136
+
100
137
  /** The shared credentials form — draft state lives inside the form's closure. */
101
138
  const credsForm = createAiCredentialsForm({
102
- intro: html`
103
- Any OpenAI-compatible key works. Stored locally on this machine; sent only to the Studio proxy
104
- (never to a third party except your chosen endpoint).
105
- `,
139
+ /* A sentence rather than a template: the form is a document now, and a document renders text
140
+ rather than another module's markup. */
141
+ intro:
142
+ "Any OpenAI-compatible key works. Stored locally on this machine; sent only to the Studio proxy (never to a third party except your chosen endpoint).",
106
143
  // Both buttons keep the sheet open: Preferences is a place, not a wizard step, and the previous
107
144
  // Dialog's habit of vanishing on Save is what made "did that take?" unanswerable.
108
145
  onCancel: repaint,
@@ -113,69 +150,157 @@ const credsForm = createAiCredentialsForm({
113
150
  /** Shared with the New Project modal's gates — see ui/ai-managed-connect.ts. */
114
151
  const managedConnect = createManagedConnect({ requestRender: credentialsChanged });
115
152
 
153
+ // ─── The Assistant islands ────────────────────────────────────────────────────
154
+
155
+ /*
156
+ * The two elements the Assistant section renders and does not fill.
157
+ *
158
+ * Both belong to a controller that outlives this sheet and is embedded by three other credentials
159
+ * gates, so the document renders an empty box for each and the elements are MOVED into it. Moving a
160
+ * node keeps everything inside it — which is the whole point: the credentials form is three fields
161
+ * a reader is typing into, and a repaint that rebuilt them would take the caret with it.
162
+ */
163
+ let _managedSlot: HTMLElement | null = null;
164
+ let _credsSlot: HTMLElement | null = null;
165
+
166
+ /** A slot was created by the document. Remember it, and fill it. */
167
+ function islandCreated(island: PreferencesIsland, host: HTMLElement): void {
168
+ if (island === "managed") {
169
+ _managedSlot = host;
170
+ } else {
171
+ _credsSlot = host;
172
+ }
173
+ fillIslands();
174
+ }
175
+
176
+ /**
177
+ * Put each controller's element in its slot, and bring both up to date.
178
+ *
179
+ * `render()` on either controller is an UPDATE when the surface already exists, so this is also how
180
+ * a repaint reaches them. The parent check is what keeps it from being a rebuild: re-appending a
181
+ * node that is already there would be a move, and a move of a focused field takes the caret.
182
+ */
183
+ function fillIslands(): void {
184
+ if (_managedSlot) {
185
+ managedConnect.ensureProbe();
186
+ const element = managedConnect.render();
187
+ if (element === null) {
188
+ _managedSlot.replaceChildren();
189
+ } else if (element.parentNode !== _managedSlot) {
190
+ _managedSlot.replaceChildren(element);
191
+ }
192
+ }
193
+ if (_credsSlot) {
194
+ const element = credsForm.render();
195
+ if (element.parentNode !== _credsSlot) {
196
+ _credsSlot.replaceChildren(element);
197
+ }
198
+ }
199
+ }
200
+
116
201
  // ─── Sections ─────────────────────────────────────────────────────────────────
117
202
 
118
- function appearanceTpl(): TemplateResult {
119
- return html`
120
- <div class="prefs-field">
121
- <span class="prefs-field-label">Theme</span>
122
- <sp-action-group compact selects="single" size="s">
123
- ${CHROME_THEMES.map(
124
- (theme) => html`
125
- <sp-action-button
126
- value=${theme}
127
- ?selected=${shell.theme === theme}
128
- @click=${() => {
129
- setChromeTheme(theme as ChromeTheme);
130
- repaint();
131
- }}
132
- >
133
- ${theme === "dark" ? "Dark" : "Light"}
134
- </sp-action-button>
135
- `,
136
- )}
137
- </sp-action-group>
138
- </div>
139
- `;
203
+ /** The nav, with the showing row marked as the accessibility tree already marks it. */
204
+ function sectionsView(): PreferencesSectionRow[] {
205
+ return PREFERENCES_SECTIONS.map((section) => ({
206
+ current: section.id === _section ? "true" : "false",
207
+ id: section.id,
208
+ title: section.title,
209
+ }));
210
+ }
211
+
212
+ /** The theme segments. `checked` is a string because that is what `aria-checked` takes. */
213
+ function themesView(): PreferencesThemeRow[] {
214
+ return CHROME_THEMES.map((theme) => ({
215
+ checked: shell.theme === theme ? "true" : "false",
216
+ label: theme === "dark" ? "Dark" : "Light",
217
+ value: theme,
218
+ }));
219
+ }
220
+
221
+ function selectSection(id: string): void {
222
+ _section = id;
223
+ // Leaving a section abandons what it was in the middle of: an armed key capture whose listener
224
+ // Is no longer mounted would otherwise swallow the first chord pressed on the author's NEXT
225
+ // Visit.
226
+ resetKeyboardState();
227
+ repaint();
228
+ sectionShown();
229
+ }
230
+
231
+ function setTheme(value: string): void {
232
+ setChromeTheme(value as ChromeTheme);
233
+ repaint();
140
234
  }
141
235
 
142
- function assistantTpl(): TemplateResult {
143
- managedConnect.ensureProbe();
144
- return html`<div class="prefs-assistant">${managedConnect.render()} ${credsForm.render()}</div>`;
236
+ // ─── Accounts ─────────────────────────────────────────────────────────────────
237
+
238
+ /**
239
+ * What each account button does, by the key the row carries.
240
+ *
241
+ * Rebuilt with the rows, because `listAccounts()` mints a fresh closure per call and the one a
242
+ * click must run is the one that was drawn. A key that is no longer in the map is a click on a row
243
+ * that has since been redrawn, which is a no-op rather than an error.
244
+ */
245
+ let _accountRuns = new Map<string, () => void | Promise<void>>();
246
+
247
+ /**
248
+ * The accounts, as rows.
249
+ *
250
+ * A record that declares `actions` decides its own verbs; everything else keeps the original rule —
251
+ * a connected credential offers Disconnect and a disconnected one offers nothing.
252
+ */
253
+ function accountsView(): PreferencesAccountRow[] {
254
+ // First paint asks the broker what it holds; the answer arrives as a credentials-changed repaint.
255
+ ensureCfConnection();
256
+ const runs = new Map<string, () => void | Promise<void>>();
257
+ const rows = listAccounts().map((account) => {
258
+ const declared =
259
+ account.actions ??
260
+ (account.connected
261
+ ? [
262
+ {
263
+ id: "disconnect",
264
+ label: "Disconnect",
265
+ run: () => {
266
+ revokeAccount(account.id);
267
+ },
268
+ variant: "negative",
269
+ },
270
+ ]
271
+ : []);
272
+ return {
273
+ actions: declared.map((action) => {
274
+ const key = `${account.id}/${action.id}`;
275
+ runs.set(key, action.run);
276
+ return {
277
+ id: action.id,
278
+ key,
279
+ label: action.label,
280
+ variant: action.variant ?? "secondary",
281
+ };
282
+ }),
283
+ detail: account.detail,
284
+ id: account.id,
285
+ label: account.label,
286
+ };
287
+ });
288
+ _accountRuns = runs;
289
+ return rows;
145
290
  }
146
291
 
147
- function accountsTpl(): TemplateResult {
148
- return html`
149
- <div class="prefs-accounts">
150
- ${listAccounts().map(
151
- (account) => html`
152
- <div class="prefs-account" data-account=${account.id}>
153
- <div class="prefs-account-text">
154
- <span class="prefs-account-label">${account.label}</span>
155
- <span class="prefs-account-detail">${account.detail}</span>
156
- </div>
157
- ${
158
- account.connected
159
- ? html`
160
- <sp-button
161
- size="s"
162
- variant="negative"
163
- treatment="outline"
164
- @click=${() => {
165
- revokeAccount(account.id);
166
- repaint();
167
- }}
168
- >
169
- Disconnect
170
- </sp-button>
171
- `
172
- : nothing
173
- }
174
- </div>
175
- `,
176
- )}
177
- </div>
178
- `;
292
+ /**
293
+ * Run one account button.
294
+ *
295
+ * Every one of them repaints when it settles, because a hosted Reconnect is a round trip through a
296
+ * Cloudflare popup and the row must not still be reading "expired" when it comes back.
297
+ */
298
+ function runAccountAction(key: string): void {
299
+ const run = _accountRuns.get(key);
300
+ if (!run) {
301
+ return;
302
+ }
303
+ void Promise.resolve(run()).finally(repaint);
179
304
  }
180
305
 
181
306
  // ─── Keyboard ─────────────────────────────────────────────────────────────────
@@ -189,8 +314,15 @@ function accountsTpl(): TemplateResult {
189
314
  */
190
315
  type KeyFilter = { kind: "text"; value: string } | { kind: "chord"; chord: string } | null;
191
316
 
192
- /** The next chord goes to the search box, or onto a command. `null` while nothing is listening. */
193
- type KeyCapture = { mode: "search" } | { mode: "rebind"; command: AnyCommand } | null;
317
+ /**
318
+ * The next chord goes to the search box, or onto a command. `null` while nothing is listening.
319
+ *
320
+ * A rebind capture holds the command's ID rather than its record, because the click that armed it
321
+ * arrived from a document and carried a string. The record is looked up when the chord lands, which
322
+ * is also the one moment at which "the registry went away under the open sheet" can be answered
323
+ * honestly rather than asserted against.
324
+ */
325
+ type KeyCapture = { mode: "search" } | { mode: "rebind"; commandId: string } | null;
194
326
 
195
327
  /** Keydowns that are not a chord yet: a modifier held on its way to one. */
196
328
  const MODIFIER_KEYS = new Set(["Meta", "Control", "Alt", "Shift", "CapsLock", "OS"]);
@@ -253,96 +385,87 @@ function onKeyboardKeyDown(event: KeyboardEvent): void {
253
385
  _keyFilter = { kind: "chord", chord };
254
386
  _keyRefusal = null;
255
387
  } else {
256
- const result = rebindCommand(registry, capture.command, chord);
257
- _keyRefusal = result.ok ? null : result;
388
+ const command = commandById(registry, capture.commandId);
389
+ if (command) {
390
+ const result = rebindCommand(registry, command, chord);
391
+ _keyRefusal = result.ok ? null : result;
392
+ }
258
393
  }
259
394
  repaint();
260
395
  }
261
396
 
262
- /** The refusal, and the one thing that can be done about it: go and look at the command holding it. */
263
- function keyRefusalTpl(): TemplateResult | typeof nothing {
264
- if (!_keyRefusal) {
265
- return nothing;
397
+ /** The record behind a row's id, or `undefined` when the registry no longer holds one. */
398
+ function commandById(registry: CommandRegistry, commandId: string): AnyCommand | undefined {
399
+ return [...registry.list()].find((candidate) => candidate.id === commandId);
400
+ }
401
+
402
+ /** Arm or disarm the search box's chord capture. */
403
+ function toggleKeystroke(): void {
404
+ _keyCapture = _keyCapture?.mode === "search" ? null : { mode: "search" };
405
+ _keyRefusal = null;
406
+ repaint();
407
+ }
408
+
409
+ /** Arm or disarm one row's rebind. */
410
+ function toggleRebind(commandId: string): void {
411
+ const capturing = _keyCapture?.mode === "rebind" && _keyCapture.commandId === commandId;
412
+ _keyCapture = capturing ? null : { mode: "rebind", commandId };
413
+ _keyRefusal = null;
414
+ repaint();
415
+ }
416
+
417
+ /** Put a rebound chord back, and forget whatever the last attempt said. */
418
+ function resetBinding(commandId: string): void {
419
+ const registry = activeRegistry();
420
+ if (registry) {
421
+ resetKeybinding(registry, commandId);
266
422
  }
267
- const { conflict } = _keyRefusal;
268
- return html`
269
- <div class="prefs-field">
270
- <sp-help-text variant="negative">${_keyRefusal.reason}</sp-help-text>
271
- ${
272
- conflict
273
- ? html`
274
- <sp-button
275
- size="s"
276
- treatment="outline"
277
- @click=${() => {
278
- _keyFilter = { kind: "text", value: conflict.title };
279
- _keyRefusal = null;
280
- repaint();
281
- }}
282
- >
283
- Show ${conflict.title}
284
- </sp-button>
285
- `
286
- : nothing
287
- }
288
- </div>
289
- `;
423
+ _keyRefusal = null;
424
+ repaint();
290
425
  }
291
426
 
292
- /**
293
- * One binding.
294
- *
295
- * The record is passed in rather than looked up: the rows and the commands come from the same
296
- * `registry.list()` call, so there is no window in which a row can name something the sheet cannot
297
- * resolve, and no assertion pretending so.
298
- */
299
- function keyRowTpl(
300
- row: ShortcutRow,
301
- command: AnyCommand,
302
- registry: CommandRegistry,
303
- ): TemplateResult {
304
- const capturing = _keyCapture?.mode === "rebind" && _keyCapture.command.id === row.commandId;
305
- return html`
306
- <div class="prefs-key" data-command=${row.commandId}>
307
- <kbd class="prefs-key-chord">
308
- ${capturing ? "Press a shortcut…" : registry.keymap.format(row.chord)}
309
- </kbd>
310
- <span class="prefs-key-title">${row.title}${row.overridden ? " — changed" : ""}</span>
311
- <sp-action-button
312
- size="s"
313
- quiet
314
- ?selected=${capturing}
315
- aria-label=${capturing ? `Stop changing ${row.title}` : `Change the shortcut for ${row.title}`}
316
- @click=${() => {
317
- _keyCapture = capturing ? null : { mode: "rebind", command };
318
- _keyRefusal = null;
319
- repaint();
320
- }}
321
- >
322
- ${capturing ? "Cancel" : "Change"}
323
- </sp-action-button>
324
- ${
325
- row.overridden
326
- ? html`
327
- <sp-action-button
328
- size="s"
329
- quiet
330
- aria-label=${`Reset the shortcut for ${row.title}`}
331
- @click=${() => {
332
- resetKeybinding(registry, row.commandId);
333
- _keyRefusal = null;
334
- repaint();
335
- }}
336
- >
337
- Reset
338
- </sp-action-button>
339
- `
340
- : nothing
341
- }
342
- </div>
343
- `;
427
+ /** Go and look at the command holding the chord that was refused. */
428
+ function showConflict(): void {
429
+ const conflict = _keyRefusal?.conflict;
430
+ if (!conflict) {
431
+ return;
432
+ }
433
+ _keyFilter = { kind: "text", value: conflict.title };
434
+ _keyRefusal = null;
435
+ repaint();
344
436
  }
345
437
 
438
+ /** Type into the search box. A keystroke capture is abandoned: the author changed their mind. */
439
+ function setKeyQuery(value: string): void {
440
+ _keyFilter = value ? { kind: "text", value } : null;
441
+ _keyCapture = null;
442
+ repaint();
443
+ }
444
+
445
+ /** The Keyboard half of the view, with nothing to say when its section is not up. */
446
+ type KeyboardView = Pick<
447
+ PreferencesView,
448
+ | "capturingSearch"
449
+ | "conflictLabel"
450
+ | "groups"
451
+ | "keyboardEmpty"
452
+ | "keyQuery"
453
+ | "keystrokeLabel"
454
+ | "refusal"
455
+ | "rowsEmpty"
456
+ >;
457
+
458
+ const KEYBOARD_IDLE: KeyboardView = {
459
+ capturingSearch: false,
460
+ conflictLabel: "",
461
+ groups: [],
462
+ keyboardEmpty: "",
463
+ keyQuery: "",
464
+ keystrokeLabel: "",
465
+ refusal: "",
466
+ rowsEmpty: "",
467
+ };
468
+
346
469
  /**
347
470
  * The keyboard sheet — every live binding, grouped by the scope it is live in.
348
471
  *
@@ -351,94 +474,80 @@ function keyRowTpl(
351
474
  * the same projection rather than patched into its output.
352
475
  *
353
476
  * A command that declares two chords has two rows and rebinding either one leaves it with one — a
354
- * user who asked for ⌥⌘Y is not also asking to keep ⌘Y, and Reset brings both back.
477
+ * user who asked for ⌥⌘Y is not also asking to keep ⌘Y, and Reset brings both back. The two rows
478
+ * share a `commandId` and differ by chord, which is why the row key is both.
355
479
  */
356
- function keyboardTpl(): TemplateResult {
480
+ function keyboardView(): KeyboardView {
357
481
  const registry = activeRegistry();
358
482
  const commands: readonly AnyCommand[] = registry ? [...registry.list()] : [];
359
483
  const rows = registry ? shortcutReference(commands, registry.keymap.overrides()) : [];
360
484
  if (!registry || rows.length === 0) {
361
- return html`<p class="prefs-empty">No commands are registered in this window.</p>`;
485
+ return { ...KEYBOARD_IDLE, keyboardEmpty: "No commands are registered in this window." };
362
486
  }
363
487
  const format = (chord: string) => registry.keymap.format(chord);
364
- const byId = new Map(commands.map((command) => [command.id, command]));
365
- const visible = rows.flatMap((row) => {
366
- const command = byId.get(row.commandId);
367
- return command && keyRowMatches(row, format) ? [{ command, row }] : [];
368
- });
369
- const scopes = [...new Set(visible.map(({ row }) => row.scope))];
488
+ const known = new Set(commands.map((command) => command.id));
489
+ const visible = rows.filter((row) => known.has(row.commandId) && keyRowMatches(row, format));
370
490
  const capturingSearch = _keyCapture?.mode === "search";
371
- return html`
372
- <div class="prefs-keys" @keydown=${onKeyboardKeyDown}>
373
- <div class="prefs-field">
374
- <sp-search
375
- size="s"
376
- placeholder="Find a shortcut…"
377
- .value=${
378
- _keyFilter === null
379
- ? ""
380
- : _keyFilter.kind === "chord"
381
- ? format(_keyFilter.chord)
382
- : _keyFilter.value
383
- }
384
- @input=${(event: Event) => {
385
- const { value } = event.target as HTMLInputElement;
386
- _keyFilter = value ? { kind: "text", value } : null;
387
- _keyCapture = null;
388
- repaint();
389
- }}
390
- @submit=${(event: Event) => event.preventDefault()}
391
- ></sp-search>
392
- <sp-action-button
393
- size="s"
394
- ?selected=${capturingSearch}
395
- @click=${() => {
396
- _keyCapture = capturingSearch ? null : { mode: "search" };
397
- _keyRefusal = null;
398
- repaint();
399
- }}
400
- >
401
- ${capturingSearch ? "Press it now" : "Search by keystroke"}
402
- </sp-action-button>
403
- </div>
404
- ${keyRefusalTpl()}
405
- ${
406
- visible.length === 0
407
- ? html`<p class="prefs-empty">
408
- ${
409
- _keyFilter?.kind === "chord"
410
- ? `Nothing is bound to ${format(_keyFilter.chord)}.`
411
- : "No shortcut matches that."
412
- }
413
- </p>`
414
- : scopes.map(
415
- (scope) => html`
416
- <h4 class="prefs-keys-scope">${SCOPE_LABELS[scope]}</h4>
417
- ${visible
418
- .filter(({ row }) => row.scope === scope)
419
- .map(({ command, row }) => keyRowTpl(row, command, registry))}
420
- `,
421
- )
422
- }
423
- </div>
424
- `;
425
- }
426
-
427
- function sectionBodyTpl(): TemplateResult {
428
- if (_section === "assistant") {
429
- return assistantTpl();
491
+ const groups: PreferencesKeyGroup[] = [];
492
+ const byScope = new Map<string, PreferencesKeyGroup>();
493
+ for (const row of visible) {
494
+ const capturing = _keyCapture?.mode === "rebind" && _keyCapture.commandId === row.commandId;
495
+ let group = byScope.get(row.scope);
496
+ if (!group) {
497
+ group = { label: SCOPE_LABELS[row.scope], rows: [], scope: row.scope };
498
+ byScope.set(row.scope, group);
499
+ groups.push(group);
500
+ }
501
+ group.rows.push({
502
+ capturing,
503
+ changeLabel: capturing ? "Cancel" : "Change",
504
+ changeName: capturing ? `Stop changing ${row.title}` : `Change the shortcut for ${row.title}`,
505
+ chord: capturing ? "Press a shortcut…" : format(row.chord),
506
+ commandId: row.commandId,
507
+ key: `${row.commandId}/${row.chord}`,
508
+ overridden: row.overridden,
509
+ resetName: `Reset the shortcut for ${row.title}`,
510
+ title: `${row.title}${row.overridden ? " — changed" : ""}`,
511
+ });
430
512
  }
431
- if (_section === "accounts") {
432
- return accountsTpl();
433
- }
434
- if (_section === "keyboard") {
435
- return keyboardTpl();
436
- }
437
- return appearanceTpl();
513
+ return {
514
+ capturingSearch,
515
+ conflictLabel: _keyRefusal?.conflict ? `Show ${_keyRefusal.conflict.title}` : "",
516
+ groups,
517
+ keyboardEmpty: "",
518
+ keyQuery:
519
+ _keyFilter === null
520
+ ? ""
521
+ : _keyFilter.kind === "chord"
522
+ ? format(_keyFilter.chord)
523
+ : _keyFilter.value,
524
+ keystrokeLabel: capturingSearch ? "Press it now" : "Search by keystroke",
525
+ refusal: _keyRefusal?.reason ?? "",
526
+ rowsEmpty:
527
+ visible.length > 0
528
+ ? ""
529
+ : _keyFilter?.kind === "chord"
530
+ ? `Nothing is bound to ${format(_keyFilter.chord)}.`
531
+ : "No shortcut matches that.",
532
+ };
438
533
  }
439
534
 
440
535
  // ─── The sheet ────────────────────────────────────────────────────────────────
441
536
 
537
+ /** Everything the document reads, built from the section that is actually showing. */
538
+ function view(): PreferencesView {
539
+ const active = PREFERENCES_SECTIONS.find((candidate) => candidate.id === _section);
540
+ return {
541
+ accounts: _section === "accounts" ? accountsView() : [],
542
+ section: _section,
543
+ sectionBlurb: active?.blurb ?? "",
544
+ sectionTitle: active?.title ?? "",
545
+ sections: sectionsView(),
546
+ themes: themesView(),
547
+ ...(_section === "keyboard" ? keyboardView() : KEYBOARD_IDLE),
548
+ };
549
+ }
550
+
442
551
  /**
443
552
  * Open Preferences, optionally on a named section. Resolves when it is dismissed.
444
553
  *
@@ -449,92 +558,60 @@ function sectionBodyTpl(): TemplateResult {
449
558
  * @param section One of {@link PREFERENCES_SECTIONS}' ids. An unknown id falls back to Appearance.
450
559
  */
451
560
  export function openPreferences(section?: string): Promise<null> {
561
+ /* Opening is a fresh look, so nothing may be reported from the last one: a brokered Cloudflare
562
+ grant can lapse between visits, and a row that repaints the previous answer first is stating
563
+ something it has no reason to believe. */
564
+ resetCfConnectionCache();
452
565
  _section = section !== undefined && isPreferencesSection(section) ? section : _section;
453
- if (_close) {
566
+ if (_surface) {
454
567
  repaint();
568
+ sectionShown();
455
569
  return Promise.resolve(null);
456
570
  }
457
571
  _section =
458
572
  section !== undefined && isPreferencesSection(section) ? section : DEFAULT_PREFERENCES_SECTION;
459
573
  credsForm.startEdit();
460
574
  resetKeyboardState();
461
- return showDialog<null>(
462
- (done) => {
463
- let wrapperEl: HTMLElement | null = null;
464
-
465
- function finish() {
466
- _rerender = null;
467
- _close = null;
468
- done(null);
469
- }
470
-
471
- function build(): TemplateResult {
472
- const active = PREFERENCES_SECTIONS.find((candidate) => candidate.id === _section);
473
- return html`
474
- <sp-dialog-wrapper
475
- open
476
- underlay
477
- headline="Preferences"
478
- cancel-label="Close"
479
- size="l"
480
- @cancel=${finish}
481
- @close=${finish}
482
- ${ref((el?: Element) => {
483
- if (el) {
484
- wrapperEl = el as HTMLElement;
485
- }
486
- })}
487
- >
488
- <div class="prefs-sheet">
489
- <nav class="prefs-nav" aria-label="Preferences sections">
490
- ${PREFERENCES_SECTIONS.map(
491
- (candidate) => html`
492
- <button
493
- type="button"
494
- class="prefs-nav-item ${candidate.id === _section ? "active" : ""}"
495
- aria-current=${candidate.id === _section ? "true" : "false"}
496
- @click=${() => {
497
- _section = candidate.id;
498
- // Leaving a section abandons what it was in the middle of: an armed key
499
- // Capture whose listener is no longer mounted would otherwise swallow the
500
- // First chord pressed on the author's NEXT visit.
501
- resetKeyboardState();
502
- repaint();
503
- }}
504
- >
505
- ${candidate.title}
506
- </button>
507
- `,
508
- )}
509
- </nav>
510
- <section class="prefs-body">
511
- <h3 class="prefs-title">${active?.title ?? ""}</h3>
512
- <p class="prefs-blurb">${active?.blurb ?? ""}</p>
513
- ${sectionBodyTpl()}
514
- </section>
515
- </div>
516
- </sp-dialog-wrapper>
517
- `;
518
- }
519
-
520
- _close = finish;
521
- _rerender = () => {
522
- // Resolved lazily: lit commits element refs before inserting the fragment, so the slot the
523
- // Sheet was rendered into is only reachable once the first render has landed.
524
- const host = wrapperEl?.parentElement;
525
- if (host) {
526
- litRender(build(), host);
575
+ sectionShown();
576
+ return new Promise<null>((resolve) => {
577
+ /* Guarded rather than trusted to the handle, because closing is what RAISES `onClosed` — so a
578
+ teardown that re-entered through it would call itself until the stack ran out. The close is
579
+ made HERE and not left to the platform: a `close` the flow did not ask for (Escape, or the
580
+ Close button) must also take the document out of the dialog layer, or the surface outlives
581
+ the dialog it drew. */
582
+ let closing = false;
583
+ _surface = openPreferencesSurface({
584
+ layer: layerHost("dialog"),
585
+ onAccountAction: runAccountAction,
586
+ onCaptureKey: onKeyboardKeyDown,
587
+ onClosed: () => {
588
+ if (closing) {
589
+ return;
527
590
  }
528
- };
529
- return build();
530
- },
531
- { region: overlayRegion("dialog", "preferences") },
532
- );
591
+ closing = true;
592
+ const handle = _surface;
593
+ _surface = null;
594
+ _managedSlot = null;
595
+ _credsSlot = null;
596
+ handle?.close();
597
+ resolve(null);
598
+ },
599
+ onIsland: islandCreated,
600
+ onKeyQuery: setKeyQuery,
601
+ onResetBinding: resetBinding,
602
+ onSelectSection: selectSection,
603
+ onSetTheme: setTheme,
604
+ onShowConflict: showConflict,
605
+ onToggleKeystroke: toggleKeystroke,
606
+ onToggleRebind: toggleRebind,
607
+ view: view(),
608
+ });
609
+ });
533
610
  }
534
611
 
535
612
  /** Close Preferences if it is open. Tests, and the shell teardown. */
536
613
  export function closePreferences(): void {
537
- _close?.();
614
+ _surface?.close();
538
615
  }
539
616
 
540
617
  // ─── Command ──────────────────────────────────────────────────────────────────