@jxsuite/studio 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (419) hide show
  1. package/canvas.html +21 -0
  2. package/dist/chunks/Overlay.dev-qfjzkms8.js +23 -0
  3. package/dist/chunks/{Overlay.dev-cszx7yn5.js.map → Overlay.dev-qfjzkms8.js.map} +1 -1
  4. package/dist/chunks/{adapter-z8fkmbmv.js → adapter-qfnesn5v.js} +2 -2
  5. package/dist/chunks/{cf-settings-mmyv436d.js → cf-settings-73sh5z4p.js} +3 -3
  6. package/dist/chunks/{components-0hzentwq.js → components-2bpqp4mq.js} +5 -7
  7. package/dist/chunks/{components-0hzentwq.js.map → components-2bpqp4mq.js.map} +1 -1
  8. package/dist/chunks/content-source-34gnkbnn.js +38 -0
  9. package/dist/chunks/content-source-34gnkbnn.js.map +9 -0
  10. package/dist/chunks/convert-to-repeater-t8kxre5s.js +258 -0
  11. package/dist/chunks/convert-to-repeater-t8kxre5s.js.map +10 -0
  12. package/dist/chunks/extension-sections-6mqydz35.js +301 -0
  13. package/dist/chunks/extension-sections-6mqydz35.js.map +11 -0
  14. package/dist/chunks/files-8kn321h1.js +70 -0
  15. package/dist/chunks/files-8kn321h1.js.map +9 -0
  16. package/dist/chunks/{format-host-2c188n41.js → format-host-mkwsmt8d.js} +3 -3
  17. package/dist/chunks/git-panel-tjbr191d.js +51 -0
  18. package/dist/chunks/git-panel-tjbr191d.js.map +9 -0
  19. package/dist/chunks/{index-baeg2x3y.js → index-7pxchcce.js} +4 -3
  20. package/dist/chunks/{index-baeg2x3y.js.map → index-7pxchcce.js.map} +3 -3
  21. package/dist/chunks/{jx-validate-qx8degj7.js → jx-validate-v6qgknrs.js} +3 -3
  22. package/dist/chunks/layers-panel-87pb25km.js +58 -0
  23. package/dist/chunks/layers-panel-87pb25km.js.map +9 -0
  24. package/dist/chunks/{monaco-setup-zjdp9zdw.js → monaco-setup-e0yh25j4.js} +4 -75
  25. package/dist/chunks/{monaco-setup-zjdp9zdw.js.map → monaco-setup-e0yh25j4.js.map} +3 -4
  26. package/dist/chunks/publish-panel-m0fcgdrt.js +21 -0
  27. package/dist/chunks/publish-panel-m0fcgdrt.js.map +9 -0
  28. package/dist/chunks/quick-search-rhs6cbsb.js +53 -0
  29. package/dist/chunks/quick-search-rhs6cbsb.js.map +9 -0
  30. package/dist/chunks/right-panel-zpdxd98t.js +37 -0
  31. package/dist/chunks/right-panel-zpdxd98t.js.map +9 -0
  32. package/dist/chunks/section-registry-zmckaxf4.js +30 -0
  33. package/dist/chunks/section-registry-zmckaxf4.js.map +9 -0
  34. package/dist/chunks/sp-overlay.dev-9ran0sfq.js +18 -0
  35. package/dist/chunks/sp-overlay.dev-9ran0sfq.js.map +9 -0
  36. package/dist/chunks/sp-popover.dev-rebmdncr.js +9 -0
  37. package/dist/chunks/sp-popover.dev-rebmdncr.js.map +9 -0
  38. package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js +15 -0
  39. package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js.map +9 -0
  40. package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js +15 -0
  41. package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js.map +9 -0
  42. package/dist/chunks/{sp-tray.dev-pb2fsd65.js → sp-tray.dev-ey59vfh7.js} +10 -8
  43. package/dist/chunks/{sp-tray.dev-pb2fsd65.js.map → sp-tray.dev-ey59vfh7.js.map} +2 -2
  44. package/dist/chunks/{studio-9qr6fq7p.js → studio-0rqqhrdy.js} +10 -378
  45. package/dist/chunks/{studio-9qr6fq7p.js.map → studio-0rqqhrdy.js.map} +4 -14
  46. package/dist/chunks/{studio-5x6tqqec.js → studio-2sj1jj3v.js} +10 -8
  47. package/dist/chunks/{studio-5x6tqqec.js.map → studio-2sj1jj3v.js.map} +2 -2
  48. package/dist/chunks/{studio-h3fw0wn8.js → studio-3fk1ac9z.js} +6 -4
  49. package/dist/chunks/{studio-h3fw0wn8.js.map → studio-3fk1ac9z.js.map} +2 -2
  50. package/dist/chunks/studio-41exgzhq.js +6690 -0
  51. package/dist/chunks/studio-41exgzhq.js.map +38 -0
  52. package/dist/chunks/{studio-7v2bpweq.js → studio-4mqb9a0j.js} +6 -4
  53. package/dist/chunks/{studio-7v2bpweq.js.map → studio-4mqb9a0j.js.map} +2 -2
  54. package/dist/chunks/studio-50k77cca.js +1787 -0
  55. package/dist/chunks/studio-50k77cca.js.map +18 -0
  56. package/dist/chunks/{studio-56v3zwk6.js → studio-6a0h7sne.js} +10 -8
  57. package/dist/chunks/{studio-56v3zwk6.js.map → studio-6a0h7sne.js.map} +2 -2
  58. package/dist/chunks/studio-6f0am8f2.js +53 -0
  59. package/dist/chunks/studio-6f0am8f2.js.map +10 -0
  60. package/dist/chunks/studio-6nmjc5qk.js +7002 -0
  61. package/dist/chunks/studio-6nmjc5qk.js.map +32 -0
  62. package/dist/chunks/studio-7afd21nw.js +67 -0
  63. package/dist/chunks/studio-7afd21nw.js.map +10 -0
  64. package/dist/chunks/{studio-xze4zrs1.js → studio-7bmdvzne.js} +2 -2
  65. package/dist/chunks/studio-7bmdvzne.js.map +10 -0
  66. package/dist/chunks/studio-8jpzqk5s.js +128 -0
  67. package/dist/chunks/studio-8jpzqk5s.js.map +10 -0
  68. package/dist/chunks/studio-94m77tgk.js +1482 -0
  69. package/dist/chunks/studio-94m77tgk.js.map +13 -0
  70. package/dist/chunks/studio-9je6w5sq.js +1000 -0
  71. package/dist/chunks/studio-9je6w5sq.js.map +16 -0
  72. package/dist/chunks/{studio-w7c6kvk7.js → studio-a7pzveq1.js} +2 -2
  73. package/dist/chunks/{studio-97qcgvdn.js → studio-adn0z1k3.js} +14 -8
  74. package/dist/chunks/{studio-97qcgvdn.js.map → studio-adn0z1k3.js.map} +3 -3
  75. package/dist/chunks/{studio-kr88ryr7.js → studio-bsxxx3x2.js} +17 -15
  76. package/dist/chunks/{studio-kr88ryr7.js.map → studio-bsxxx3x2.js.map} +7 -6
  77. package/dist/chunks/studio-bw5dz6km.js +608 -0
  78. package/dist/chunks/studio-bw5dz6km.js.map +21 -0
  79. package/dist/chunks/studio-c6s5getc.js +4232 -0
  80. package/dist/chunks/studio-c6s5getc.js.map +39 -0
  81. package/dist/chunks/studio-cjm7fdjf.js +451 -0
  82. package/dist/chunks/studio-cjm7fdjf.js.map +14 -0
  83. package/dist/chunks/{studio-cx91mnh8.js → studio-ckwz4d3x.js} +53 -53
  84. package/dist/chunks/{studio-cx91mnh8.js.map → studio-ckwz4d3x.js.map} +5 -5
  85. package/dist/chunks/studio-dg7zyt5x.js +28 -0
  86. package/dist/chunks/studio-dg7zyt5x.js.map +10 -0
  87. package/dist/chunks/studio-dzcyvvvz.js +166 -0
  88. package/dist/chunks/studio-dzcyvvvz.js.map +10 -0
  89. package/dist/chunks/{studio-7kqe41sa.js → studio-evkr5d3b.js} +15 -7
  90. package/dist/chunks/{studio-7kqe41sa.js.map → studio-evkr5d3b.js.map} +5 -4
  91. package/dist/chunks/studio-f12h6177.js +411 -0
  92. package/dist/chunks/studio-f12h6177.js.map +13 -0
  93. package/dist/chunks/studio-hbp1s8m9.js +1817 -0
  94. package/dist/chunks/studio-hbp1s8m9.js.map +14 -0
  95. package/dist/chunks/studio-k2sxjph5.js +15295 -0
  96. package/dist/chunks/studio-k2sxjph5.js.map +235 -0
  97. package/dist/chunks/studio-myje51t3.js +385 -0
  98. package/dist/chunks/studio-myje51t3.js.map +12 -0
  99. package/dist/chunks/studio-na6rwzhw.js +353 -0
  100. package/dist/chunks/studio-na6rwzhw.js.map +11 -0
  101. package/dist/chunks/{studio-4k0m4fmp.js → studio-p471gg5n.js} +126 -3
  102. package/dist/chunks/studio-p471gg5n.js.map +14 -0
  103. package/dist/chunks/{studio-9bjwf0d4.js → studio-rgwcpast.js} +5 -46
  104. package/dist/chunks/{studio-9bjwf0d4.js.map → studio-rgwcpast.js.map} +4 -5
  105. package/dist/chunks/{studio-hhj92gcr.js → studio-s519zc99.js} +3 -3
  106. package/dist/chunks/studio-sas7mt3k.js +75 -0
  107. package/dist/chunks/studio-sas7mt3k.js.map +10 -0
  108. package/dist/chunks/studio-t5wqwzdh.js +74 -0
  109. package/dist/chunks/studio-t5wqwzdh.js.map +10 -0
  110. package/dist/chunks/{studio-9qpspwfn.js → studio-temv5bw4.js} +5 -5
  111. package/dist/chunks/{studio-9qpspwfn.js.map → studio-temv5bw4.js.map} +1 -1
  112. package/dist/chunks/{studio-qkvt37c0.js → studio-tr20m8g8.js} +669 -18
  113. package/dist/chunks/studio-tr20m8g8.js.map +9 -0
  114. package/dist/chunks/{studio-ygzav7z5.js → studio-v96qfnww.js} +5514 -4291
  115. package/dist/chunks/studio-v96qfnww.js.map +54 -0
  116. package/dist/chunks/studio-vcetc91k.js +659 -0
  117. package/dist/chunks/studio-vcetc91k.js.map +10 -0
  118. package/dist/chunks/{studio-n9zmpxjg.js → studio-vmymjefp.js} +2972 -4937
  119. package/dist/chunks/studio-vmymjefp.js.map +98 -0
  120. package/dist/chunks/{studio-bghqfmea.js → studio-wwseqyw0.js} +2 -2
  121. package/dist/chunks/studio-x9hmy5kr.js +390 -0
  122. package/dist/chunks/studio-x9hmy5kr.js.map +10 -0
  123. package/dist/chunks/studio-xa0ce0n7.js +335 -0
  124. package/dist/chunks/studio-xa0ce0n7.js.map +10 -0
  125. package/dist/chunks/studio-xm3dbxvn.js +378 -0
  126. package/dist/chunks/studio-xm3dbxvn.js.map +19 -0
  127. package/dist/chunks/{studio-nqw433kv.js → studio-xmvbhg31.js} +2 -2
  128. package/dist/chunks/studio-zv37yrfa.js +1054 -0
  129. package/dist/chunks/studio-zv37yrfa.js.map +13 -0
  130. package/dist/chunks/{ws-client-q7hkz3j9.js → ws-client-654ts66j.js} +6 -4
  131. package/dist/chunks/ws-client-654ts66j.js.map +11 -0
  132. package/dist/iframe-entry.js +668 -230
  133. package/dist/studio.js +23711 -35185
  134. package/index.html +35 -4685
  135. package/package.json +17 -15
  136. package/src/about/about-modal.ts +42 -11
  137. package/src/browse/library-commands.ts +240 -0
  138. package/src/browse/library-layouts.ts +369 -0
  139. package/src/browse/library-model.ts +396 -0
  140. package/src/browse/library-pane.ts +1049 -0
  141. package/src/browse/library-preview.ts +252 -0
  142. package/src/browse/library-source.ts +150 -0
  143. package/src/canvas/canvas-helpers.ts +33 -9
  144. package/src/canvas/canvas-live-render.ts +45 -29
  145. package/src/canvas/canvas-patcher.ts +151 -31
  146. package/src/canvas/canvas-perf.ts +46 -7
  147. package/src/canvas/canvas-render.ts +1092 -220
  148. package/src/canvas/canvas-surface.ts +240 -0
  149. package/src/canvas/canvas-utils.ts +1166 -124
  150. package/src/canvas/iframe-drop.ts +2 -1
  151. package/src/canvas/iframe-entry.ts +241 -4
  152. package/src/canvas/iframe-host.ts +1226 -143
  153. package/src/canvas/iframe-inline-edit.ts +31 -0
  154. package/src/canvas/iframe-interaction.ts +76 -16
  155. package/src/canvas/iframe-keys.ts +107 -45
  156. package/src/canvas/iframe-overlay.ts +38 -1
  157. package/src/canvas/iframe-protocol.ts +137 -2
  158. package/src/canvas/iframe-render.ts +70 -2
  159. package/src/canvas/iframe-slash.ts +6 -0
  160. package/src/canvas/path-mapping.ts +56 -6
  161. package/src/canvas/surface-registry.ts +292 -0
  162. package/src/collab/collab-commands.ts +244 -0
  163. package/src/collab/collab-session.ts +198 -32
  164. package/src/collab/collab-state.ts +28 -2
  165. package/src/collab/presence-chips.ts +100 -9
  166. package/src/commands/active-registry.ts +42 -0
  167. package/src/commands/app-commands.ts +176 -0
  168. package/src/commands/budget.ts +158 -0
  169. package/src/commands/command-args.ts +253 -0
  170. package/src/commands/context.ts +387 -0
  171. package/src/commands/defaults.ts +580 -0
  172. package/src/commands/keymap.ts +571 -0
  173. package/src/commands/levels.ts +345 -0
  174. package/src/commands/live-context.ts +193 -0
  175. package/src/commands/reference.ts +227 -0
  176. package/src/commands/registry.ts +293 -0
  177. package/src/component-props.ts +3 -2
  178. package/src/content/draft-state.ts +106 -0
  179. package/src/content/entry-commands.ts +282 -0
  180. package/src/content/entry-editor.ts +327 -0
  181. package/src/content/entry-fields.ts +116 -0
  182. package/src/content/entry-model.ts +175 -0
  183. package/src/editor/canvas-context-menu.ts +17 -11
  184. package/src/editor/canvas-slash-bridge.ts +11 -4
  185. package/src/editor/context-menu.ts +804 -204
  186. package/src/editor/convert-to-component.ts +31 -10
  187. package/src/editor/convert-to-repeater.ts +3 -2
  188. package/src/editor/inline-edit-apply.ts +5 -5
  189. package/src/editor/inline-edit.ts +86 -50
  190. package/src/editor/insert-zone-action.ts +1 -1
  191. package/src/editor/shortcuts.ts +1043 -329
  192. package/src/editor/slash-menu.ts +1 -0
  193. package/src/files/elements.ts +166 -0
  194. package/src/files/file-ops.ts +178 -31
  195. package/src/files/files.ts +898 -268
  196. package/src/files/fs-events.ts +21 -0
  197. package/src/files/media-meta.ts +203 -0
  198. package/src/files/media-paths.ts +141 -0
  199. package/src/files/media-upload.ts +7 -3
  200. package/src/files/media-usage.ts +189 -0
  201. package/src/github/github-auth.ts +235 -33
  202. package/src/github/github-publish.ts +80 -21
  203. package/src/grid/grid-controller.ts +152 -12
  204. package/src/grid/grid-layout.ts +225 -20
  205. package/src/grid/grid-open.ts +120 -0
  206. package/src/grid/grid-panel.ts +554 -39
  207. package/src/grid/grid-source.ts +27 -1
  208. package/src/grid/redirects-grid.ts +599 -0
  209. package/src/grid/redirects.ts +398 -0
  210. package/src/grid/sources/content-source.ts +108 -2
  211. package/src/i18n/i18n-commands.ts +322 -0
  212. package/src/new-project/add-repo-modal.ts +8 -10
  213. package/src/new-project/import-tab.ts +34 -9
  214. package/src/new-project/location-fields.ts +9 -1
  215. package/src/new-project/new-project-modal.ts +270 -262
  216. package/src/packages/jxsuite-update.ts +97 -48
  217. package/src/packages/pull-package-sync.ts +18 -9
  218. package/src/page-params.ts +2 -1
  219. package/src/panels/activity-bar.ts +152 -91
  220. package/src/panels/activity-panel.ts +481 -0
  221. package/src/panels/ai-chat/chat-markdown.ts +8 -3
  222. package/src/panels/ai-chat/chat-view.ts +245 -18
  223. package/src/panels/ai-chat/composer.ts +31 -5
  224. package/src/panels/ai-chat/sessions-view.ts +17 -10
  225. package/src/panels/ai-panel.ts +279 -120
  226. package/src/panels/block-action-bar.ts +1115 -224
  227. package/src/panels/bottom-dock.ts +337 -0
  228. package/src/panels/canvas-dnd-bridge.ts +37 -8
  229. package/src/panels/chat-panel.ts +29 -12
  230. package/src/panels/data-explorer.ts +254 -112
  231. package/src/panels/data-grid.ts +10 -4
  232. package/src/panels/dnd.ts +28 -51
  233. package/src/panels/editors.ts +434 -107
  234. package/src/panels/elements-panel.ts +66 -2
  235. package/src/panels/empty-state.ts +112 -0
  236. package/src/panels/events-panel.ts +596 -82
  237. package/src/panels/formula-workspace.ts +408 -218
  238. package/src/panels/frontmatter-fields.ts +51 -10
  239. package/src/panels/frontmatter-panel.ts +357 -79
  240. package/src/panels/git-panel.ts +375 -164
  241. package/src/panels/head-panel.ts +791 -85
  242. package/src/panels/i18n-panel.ts +592 -0
  243. package/src/panels/imports-panel.ts +95 -85
  244. package/src/panels/jump-bar.ts +584 -0
  245. package/src/panels/layers-panel.ts +1106 -328
  246. package/src/panels/left-panel.ts +109 -204
  247. package/src/panels/navigator-panels.ts +131 -0
  248. package/src/panels/overlays.ts +7 -2
  249. package/src/panels/pane-context.ts +1477 -0
  250. package/src/panels/pane-grid.ts +406 -0
  251. package/src/panels/panel-registry.ts +340 -0
  252. package/src/panels/panel-scheduler.ts +75 -0
  253. package/src/panels/problems-panel.ts +234 -0
  254. package/src/panels/properties-panel.ts +946 -1139
  255. package/src/panels/provenance.ts +258 -0
  256. package/src/panels/quick-search.ts +791 -122
  257. package/src/panels/right-panel.ts +228 -102
  258. package/src/panels/seo-modal.ts +395 -0
  259. package/src/panels/settings-pane.ts +203 -0
  260. package/src/panels/signals-panel.ts +354 -137
  261. package/src/panels/statement-editor.ts +41 -25
  262. package/src/panels/statusbar.ts +342 -106
  263. package/src/panels/style-panel.ts +974 -463
  264. package/src/panels/style-utils.ts +3 -3
  265. package/src/panels/stylebook-doc.ts +4 -2
  266. package/src/panels/stylebook-layers-panel.ts +3 -2
  267. package/src/panels/stylebook-panel.ts +51 -26
  268. package/src/panels/tab-strip.ts +1058 -78
  269. package/src/panels/target-line.ts +274 -0
  270. package/src/panels/toolbar.ts +631 -484
  271. package/src/panels/welcome-screen.ts +301 -198
  272. package/src/platform-errors.ts +26 -3
  273. package/src/platform.ts +96 -2
  274. package/src/platforms/cloud.ts +71 -8
  275. package/src/platforms/devserver.ts +92 -29
  276. package/src/publish/deploy-checklist.ts +383 -0
  277. package/src/publish/pages-service.ts +10 -1
  278. package/src/publish/publish-commands.ts +218 -0
  279. package/src/publish/publish-panel.ts +110 -16
  280. package/src/recent-projects.ts +3 -2
  281. package/src/services/a11y-report.ts +505 -0
  282. package/src/services/ai-models.ts +75 -2
  283. package/src/services/ai-project-tools.ts +29 -10
  284. package/src/services/ai-system-prompt.ts +53 -14
  285. package/src/services/ai-tools.ts +39 -3
  286. package/src/services/ai-writes.ts +154 -0
  287. package/src/services/announce.ts +97 -0
  288. package/src/services/automation.ts +424 -175
  289. package/src/services/cem-export.ts +9 -4
  290. package/src/services/clock.ts +61 -0
  291. package/src/services/document-assistant.ts +36 -8
  292. package/src/services/idle.ts +179 -0
  293. package/src/services/import-client.ts +26 -0
  294. package/src/services/monaco-buffer.ts +627 -0
  295. package/src/services/monaco-lazy.ts +36 -0
  296. package/src/services/notify.ts +271 -0
  297. package/src/services/profile.ts +155 -0
  298. package/src/services/references.ts +0 -0
  299. package/src/services/token-lint.ts +3 -2
  300. package/src/services/tool-executor.ts +53 -7
  301. package/src/services/trusted-types.ts +123 -0
  302. package/src/settings/contexts-section.ts +427 -0
  303. package/src/settings/contributed-section.ts +149 -11
  304. package/src/settings/css-vars-editor.ts +306 -349
  305. package/src/settings/defs-editor.ts +93 -34
  306. package/src/settings/dependencies-editor.ts +7 -7
  307. package/src/settings/extension-sections.ts +37 -5
  308. package/src/settings/general-settings.ts +204 -121
  309. package/src/settings/locales-section.ts +347 -0
  310. package/src/settings/preferences-accounts.ts +140 -0
  311. package/src/settings/preferences-dialog.ts +620 -0
  312. package/src/settings/preferences-keymap.ts +270 -0
  313. package/src/settings/project-sections.ts +260 -0
  314. package/src/settings/section-registry.ts +169 -0
  315. package/src/settings/settings-document.ts +385 -0
  316. package/src/shell.ts +1549 -0
  317. package/src/site-context.ts +45 -20
  318. package/src/state.ts +14 -275
  319. package/src/store.ts +88 -43
  320. package/src/studio.ts +790 -346
  321. package/src/style/live-preview.ts +36 -0
  322. package/src/style/project-styles.ts +349 -0
  323. package/src/style/token-ref.ts +123 -0
  324. package/src/tabs/patch-ops.ts +16 -4
  325. package/src/tabs/project-config.ts +416 -0
  326. package/src/tabs/selection.ts +323 -0
  327. package/src/tabs/tab.ts +182 -47
  328. package/src/tabs/transact.ts +209 -28
  329. package/src/types.ts +68 -13
  330. package/src/ui/ai-credentials-form.ts +39 -35
  331. package/src/ui/ai-managed-connect.ts +113 -0
  332. package/src/ui/dynamic-slot.ts +224 -81
  333. package/src/ui/expression-editor.ts +71 -63
  334. package/src/ui/field-input.ts +4 -6
  335. package/src/ui/field-row.ts +215 -38
  336. package/src/ui/form-controls.ts +194 -7
  337. package/src/ui/layers.ts +492 -69
  338. package/src/ui/media-picker.ts +115 -25
  339. package/src/ui/panel-resize.ts +109 -91
  340. package/src/ui/progress-modal.ts +131 -50
  341. package/src/ui/regions.ts +462 -0
  342. package/src/ui/schema-form.ts +229 -45
  343. package/src/ui/spectrum.ts +24 -8
  344. package/src/ui/timing.ts +49 -0
  345. package/src/ui/value-selector.ts +20 -0
  346. package/src/ui/value-source.ts +348 -0
  347. package/src/ui/virtual-window.ts +400 -0
  348. package/src/utils/edit-display.ts +3 -2
  349. package/src/utils/inherited-style.ts +67 -37
  350. package/src/view.ts +64 -78
  351. package/src/workspace/pane-derive.ts +1258 -0
  352. package/src/workspace/session.ts +317 -0
  353. package/src/workspace/workspace.ts +1345 -23
  354. package/styles/canvas.css +653 -0
  355. package/styles/forced-colors.css +106 -0
  356. package/styles/inspector.css +1454 -0
  357. package/styles/overlays.css +2704 -0
  358. package/styles/panels.css +1701 -0
  359. package/styles/shell.css +1423 -0
  360. package/styles/tokens.css +151 -0
  361. package/dist/chunks/Overlay.dev-cszx7yn5.js +0 -22
  362. package/dist/chunks/extension-sections-qct0gt8f.js +0 -531
  363. package/dist/chunks/extension-sections-qct0gt8f.js.map +0 -12
  364. package/dist/chunks/files-02zn5y24.js +0 -50
  365. package/dist/chunks/files-02zn5y24.js.map +0 -9
  366. package/dist/chunks/git-panel-mbb0y0hz.js +0 -28
  367. package/dist/chunks/git-panel-mbb0y0hz.js.map +0 -9
  368. package/dist/chunks/layers-panel-9qekmqk1.js +0 -26
  369. package/dist/chunks/layers-panel-9qekmqk1.js.map +0 -9
  370. package/dist/chunks/settings-modal-gj0hrah4.js +0 -29
  371. package/dist/chunks/settings-modal-gj0hrah4.js.map +0 -9
  372. package/dist/chunks/sp-overlay.dev-earn5as8.js +0 -17
  373. package/dist/chunks/sp-overlay.dev-earn5as8.js.map +0 -9
  374. package/dist/chunks/sp-popover.dev-frxtfejn.js +0 -8
  375. package/dist/chunks/sp-popover.dev-frxtfejn.js.map +0 -9
  376. package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js +0 -14
  377. package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js.map +0 -9
  378. package/dist/chunks/sp-tooltip.dev-a9fywesm.js +0 -21
  379. package/dist/chunks/sp-tooltip.dev-a9fywesm.js.map +0 -10
  380. package/dist/chunks/studio-4k0m4fmp.js.map +0 -13
  381. package/dist/chunks/studio-568nx4t5.js +0 -469
  382. package/dist/chunks/studio-568nx4t5.js.map +0 -14
  383. package/dist/chunks/studio-9smfzqhc.js +0 -19
  384. package/dist/chunks/studio-9smfzqhc.js.map +0 -10
  385. package/dist/chunks/studio-asvf0j9e.js +0 -429
  386. package/dist/chunks/studio-asvf0j9e.js.map +0 -13
  387. package/dist/chunks/studio-d77qm3pk.js +0 -3427
  388. package/dist/chunks/studio-d77qm3pk.js.map +0 -26
  389. package/dist/chunks/studio-j6gqyjf4.js +0 -1854
  390. package/dist/chunks/studio-j6gqyjf4.js.map +0 -18
  391. package/dist/chunks/studio-n9zmpxjg.js.map +0 -115
  392. package/dist/chunks/studio-qkvt37c0.js.map +0 -9
  393. package/dist/chunks/studio-rq0vxb19.js +0 -3744
  394. package/dist/chunks/studio-rq0vxb19.js.map +0 -22
  395. package/dist/chunks/studio-wd6ewdvd.js +0 -853
  396. package/dist/chunks/studio-wd6ewdvd.js.map +0 -12
  397. package/dist/chunks/studio-xq0t7n5t.js +0 -140
  398. package/dist/chunks/studio-xq0t7n5t.js.map +0 -10
  399. package/dist/chunks/studio-xze4zrs1.js.map +0 -10
  400. package/dist/chunks/studio-ygzav7z5.js.map +0 -46
  401. package/dist/chunks/studio-zmy7241e.js +0 -399
  402. package/dist/chunks/studio-zmy7241e.js.map +0 -12
  403. package/dist/chunks/ws-client-q7hkz3j9.js.map +0 -11
  404. package/src/browse/browse-modal.ts +0 -69
  405. package/src/browse/browse.ts +0 -904
  406. package/src/canvas/canvas-diff.ts +0 -216
  407. package/src/canvas/nested-site-style.ts +0 -58
  408. package/src/new-project/design-fields.ts +0 -264
  409. package/src/new-project/templates.ts +0 -61
  410. package/src/panels/tab-bar.ts +0 -548
  411. package/src/settings/settings-modal.ts +0 -203
  412. /package/dist/chunks/{adapter-z8fkmbmv.js.map → adapter-qfnesn5v.js.map} +0 -0
  413. /package/dist/chunks/{cf-settings-mmyv436d.js.map → cf-settings-73sh5z4p.js.map} +0 -0
  414. /package/dist/chunks/{format-host-2c188n41.js.map → format-host-mkwsmt8d.js.map} +0 -0
  415. /package/dist/chunks/{jx-validate-qx8degj7.js.map → jx-validate-v6qgknrs.js.map} +0 -0
  416. /package/dist/chunks/{studio-w7c6kvk7.js.map → studio-a7pzveq1.js.map} +0 -0
  417. /package/dist/chunks/{studio-hhj92gcr.js.map → studio-s519zc99.js.map} +0 -0
  418. /package/dist/chunks/{studio-bghqfmea.js.map → studio-wwseqyw0.js.map} +0 -0
  419. /package/dist/chunks/{studio-nqw433kv.js.map → studio-xmvbhg31.js.map} +0 -0
@@ -0,0 +1,620 @@
1
+ /// <reference lib="dom" />
2
+ /**
3
+ * Preferences-dialog.ts — ⌘, the application-preferences surface Studio did not have.
4
+ *
5
+ * Plan §9.3 draws the line this file finally makes real. **Project Settings** configures a project
6
+ * and belongs to the project (breakpoints, definitions, deploy); **Preferences** configures the
7
+ * application and follows you between them. Studio shipped only the first, behind a gear in the
8
+ * rail — the slot every other editor spends on the second — and so had nowhere at all to put the
9
+ * chrome theme, the assistant's provider key, the keyboard, or the three credentials it holds.
10
+ *
11
+ * Four sections, and each one closes a hole that was named in the plan:
12
+ *
13
+ * - **Appearance** — the chrome theme. `shell.theme` and `view.setTheme` already existed; nothing
14
+ * rendered them, so the only way to change the theme was to run a command by hand.
15
+ * - **Assistant** — the provider key. It used to live in `Assistant: Settings…`, a dialog reachable
16
+ * only from inside the assistant panel, which meant a key had to be configured from the surface
17
+ * that was broken for want of one. That dialog is deleted; this is where it went.
18
+ * - **Accounts** — GitHub, the AI provider and Cloudflare, listed with a Disconnect each. Before
19
+ * this, `clearGithubToken()` had zero callers: signing out of GitHub was not expressible.
20
+ * - **Keyboard** — GENERATED from `commands/reference.ts`'s `shortcutReference()`, the same
21
+ * projection `docs/studio/interface/shortcuts.md` is built from. The registry is the one place a
22
+ * chord is declared, so the sheet cannot drift from the app or from the docs. Per §13.5 there is
23
+ * deliberately no screenshot of it — photographing generated content is a bug. It is searchable
24
+ * two ways, because there are two questions ("what is the shortcut for X" and "what did I just
25
+ * press"), and rebindable — as a LAYER over the registry (`settings/preferences-keymap.ts`), so
26
+ * the sheet stays a projection and the app's own record of a default is never edited.
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.
30
+ */
31
+
32
+ import { html, nothing, render as litRender } from "lit-html";
33
+ import { ref } from "lit-html/directives/ref.js";
34
+ import { CHROME_THEMES, setChromeTheme, shell } from "../shell";
35
+ import { showDialog } from "../ui/layers";
36
+ import { createAiCredentialsForm } from "../ui/ai-credentials-form";
37
+ import { createManagedConnect } from "../ui/ai-managed-connect";
38
+ import { activeRegistry } from "../commands/active-registry";
39
+ import { shortcutReference, SCOPE_LABELS } from "../commands/reference";
40
+ import { chordFromEvent } from "../commands/keymap";
41
+ import { listAccounts, notifyCredentialsChanged, revokeAccount } from "./preferences-accounts";
42
+ import { applyKeybindingOverrides, rebindCommand, resetKeybinding } from "./preferences-keymap";
43
+ import { overlayRegion } from "../ui/regions";
44
+
45
+ import type { TemplateResult } from "lit-html";
46
+ import type { ChromeTheme } from "../shell";
47
+ import type { ShortcutRow } from "../commands/reference";
48
+ import type { RebindResult } from "./preferences-keymap";
49
+ import type { AnyCommand, CommandRegistry } from "../commands/registry";
50
+
51
+ /** One section of the sheet. The `id` is what `app.preferences { section }` accepts. */
52
+ export interface PreferencesSection {
53
+ id: string;
54
+ title: string;
55
+ /** The one-line answer to "what is in here", shown under the heading. */
56
+ blurb: string;
57
+ }
58
+
59
+ /**
60
+ * The sections, in sheet order.
61
+ *
62
+ * §9.3 lists six for the finished surface (Editor behaviour and Updates/About are the other two).
63
+ * Four are built; a section is added here when it has something to configure, never before — an
64
+ * empty pane is the "declared but unbuilt" state the rail already refuses to render.
65
+ */
66
+ export const PREFERENCES_SECTIONS: readonly PreferencesSection[] = [
67
+ { id: "appearance", title: "Appearance", blurb: "How the Studio chrome looks." },
68
+ {
69
+ id: "assistant",
70
+ title: "Assistant",
71
+ blurb: "The AI provider the assistant talks to. Stored on this machine.",
72
+ },
73
+ {
74
+ id: "accounts",
75
+ title: "Accounts",
76
+ blurb: "Every credential Studio holds, and how to make it forget one.",
77
+ },
78
+ {
79
+ id: "keyboard",
80
+ title: "Keyboard",
81
+ blurb: "Generated from the command registry — the app's own record of every chord.",
82
+ },
83
+ ];
84
+
85
+ /** The section the sheet opens on when none is named. */
86
+ export const DEFAULT_PREFERENCES_SECTION = PREFERENCES_SECTIONS[0]!.id;
87
+
88
+ /** Whether a string names a section. */
89
+ export function isPreferencesSection(id: unknown): boolean {
90
+ return PREFERENCES_SECTIONS.some((section) => section.id === id);
91
+ }
92
+
93
+ // ─── Open state ───────────────────────────────────────────────────────────────
94
+
95
+ let _section = DEFAULT_PREFERENCES_SECTION;
96
+
97
+ /** Repaint the open sheet, if one is up. */
98
+ let _rerender: (() => void) | null = null;
99
+
100
+ /** Dismiss the open sheet, if one is up. */
101
+ let _close: (() => void) | null = null;
102
+
103
+ /** Whether Preferences is on screen. */
104
+ export function isPreferencesOpen(): boolean {
105
+ return _close !== null;
106
+ }
107
+
108
+ /** The section currently showing. Exposed for tests and for the `app.preferences` round-trip. */
109
+ export function preferencesSection(): string {
110
+ return _section;
111
+ }
112
+
113
+ function repaint(): void {
114
+ _rerender?.();
115
+ }
116
+
117
+ /** Save/revoke both change what other surfaces show, so they announce as well as repaint. */
118
+ function credentialsChanged(): void {
119
+ notifyCredentialsChanged();
120
+ repaint();
121
+ }
122
+
123
+ /** The shared credentials form — draft state lives inside the form's closure. */
124
+ const credsForm = createAiCredentialsForm({
125
+ intro: html`
126
+ Any OpenAI-compatible key works. Stored locally on this machine; sent only to the Studio proxy
127
+ (never to a third party except your chosen endpoint).
128
+ `,
129
+ // Both buttons keep the sheet open: Preferences is a place, not a wizard step, and the previous
130
+ // Dialog's habit of vanishing on Save is what made "did that take?" unanswerable.
131
+ onCancel: repaint,
132
+ onSaved: credentialsChanged,
133
+ requestRender: repaint,
134
+ });
135
+
136
+ /** Shared with the New Project modal's gates — see ui/ai-managed-connect.ts. */
137
+ const managedConnect = createManagedConnect({ requestRender: credentialsChanged });
138
+
139
+ // ─── Sections ─────────────────────────────────────────────────────────────────
140
+
141
+ function appearanceTpl(): TemplateResult {
142
+ return html`
143
+ <div class="prefs-field">
144
+ <span class="prefs-field-label">Theme</span>
145
+ <sp-action-group compact selects="single" size="s">
146
+ ${CHROME_THEMES.map(
147
+ (theme) => html`
148
+ <sp-action-button
149
+ value=${theme}
150
+ ?selected=${shell.theme === theme}
151
+ @click=${() => {
152
+ setChromeTheme(theme as ChromeTheme);
153
+ repaint();
154
+ }}
155
+ >
156
+ ${theme === "dark" ? "Dark" : "Light"}
157
+ </sp-action-button>
158
+ `,
159
+ )}
160
+ </sp-action-group>
161
+ </div>
162
+ `;
163
+ }
164
+
165
+ function assistantTpl(): TemplateResult {
166
+ managedConnect.ensureProbe();
167
+ return html`<div class="prefs-assistant">${managedConnect.render()} ${credsForm.render()}</div>`;
168
+ }
169
+
170
+ function accountsTpl(): TemplateResult {
171
+ return html`
172
+ <div class="prefs-accounts">
173
+ ${listAccounts().map(
174
+ (account) => html`
175
+ <div class="prefs-account" data-account=${account.id}>
176
+ <div class="prefs-account-text">
177
+ <span class="prefs-account-label">${account.label}</span>
178
+ <span class="prefs-account-detail">${account.detail}</span>
179
+ </div>
180
+ ${
181
+ account.connected
182
+ ? html`
183
+ <sp-button
184
+ size="s"
185
+ variant="negative"
186
+ treatment="outline"
187
+ @click=${() => {
188
+ revokeAccount(account.id);
189
+ repaint();
190
+ }}
191
+ >
192
+ Disconnect
193
+ </sp-button>
194
+ `
195
+ : nothing
196
+ }
197
+ </div>
198
+ `,
199
+ )}
200
+ </div>
201
+ `;
202
+ }
203
+
204
+ // ─── Keyboard ─────────────────────────────────────────────────────────────────
205
+
206
+ /**
207
+ * What the sheet is showing, if not everything.
208
+ *
209
+ * Two kinds because there are two questions. `text` answers "what is the shortcut for Duplicate";
210
+ * `chord` answers "what did I just press", which is the one a list cannot answer by being read — a
211
+ * chord's printed form is not what a person searching for it would type.
212
+ */
213
+ type KeyFilter = { kind: "text"; value: string } | { kind: "chord"; chord: string } | null;
214
+
215
+ /** The next chord goes to the search box, or onto a command. `null` while nothing is listening. */
216
+ type KeyCapture = { mode: "search" } | { mode: "rebind"; command: AnyCommand } | null;
217
+
218
+ /** Keydowns that are not a chord yet: a modifier held on its way to one. */
219
+ const MODIFIER_KEYS = new Set(["Meta", "Control", "Alt", "Shift", "CapsLock", "OS"]);
220
+
221
+ let _keyFilter: KeyFilter = null;
222
+ let _keyCapture: KeyCapture = null;
223
+ /** The last refusal, held until the author does something else. */
224
+ let _keyRefusal: (RebindResult & { ok: false }) | null = null;
225
+
226
+ /** Forget the Keyboard section's transient state. Opening the sheet should never resume a capture. */
227
+ function resetKeyboardState(): void {
228
+ _keyFilter = null;
229
+ _keyCapture = null;
230
+ _keyRefusal = null;
231
+ }
232
+
233
+ /** Whether a row survives the current filter. `format` is injected so one platform decision holds. */
234
+ function keyRowMatches(row: ShortcutRow, format: (chord: string) => string): boolean {
235
+ if (!_keyFilter) {
236
+ return true;
237
+ }
238
+ if (_keyFilter.kind === "chord") {
239
+ return row.chord === _keyFilter.chord;
240
+ }
241
+ const query = _keyFilter.value.toLowerCase();
242
+ return (
243
+ row.title.toLowerCase().includes(query) ||
244
+ row.commandId.toLowerCase().includes(query) ||
245
+ format(row.chord).toLowerCase().includes(query)
246
+ );
247
+ }
248
+
249
+ /**
250
+ * One keydown while the sheet is listening.
251
+ *
252
+ * `stopPropagation` is the load-bearing line: the dispatcher is a `document` listener and
253
+ * Preferences deliberately does not suspend the app (§15), so without it, capturing ⌘S would SAVE
254
+ * while the author was trying to bind it.
255
+ */
256
+ function onKeyboardKeyDown(event: KeyboardEvent): void {
257
+ if (!_keyCapture || MODIFIER_KEYS.has(event.key)) {
258
+ return;
259
+ }
260
+ event.preventDefault();
261
+ event.stopPropagation();
262
+ const capture = _keyCapture;
263
+ _keyCapture = null;
264
+ if (event.key === "Escape") {
265
+ _keyRefusal = null;
266
+ repaint();
267
+ return;
268
+ }
269
+ const registry = activeRegistry();
270
+ if (!registry) {
271
+ repaint();
272
+ return;
273
+ }
274
+ const chord = chordFromEvent(event, registry.keymap.mac);
275
+ if (capture.mode === "search") {
276
+ _keyFilter = { kind: "chord", chord };
277
+ _keyRefusal = null;
278
+ } else {
279
+ const result = rebindCommand(registry, capture.command, chord);
280
+ _keyRefusal = result.ok ? null : result;
281
+ }
282
+ repaint();
283
+ }
284
+
285
+ /** The refusal, and the one thing that can be done about it: go and look at the command holding it. */
286
+ function keyRefusalTpl(): TemplateResult | typeof nothing {
287
+ if (!_keyRefusal) {
288
+ return nothing;
289
+ }
290
+ const { conflict } = _keyRefusal;
291
+ return html`
292
+ <div class="prefs-field">
293
+ <sp-help-text variant="negative">${_keyRefusal.reason}</sp-help-text>
294
+ ${
295
+ conflict
296
+ ? html`
297
+ <sp-button
298
+ size="s"
299
+ treatment="outline"
300
+ @click=${() => {
301
+ _keyFilter = { kind: "text", value: conflict.title };
302
+ _keyRefusal = null;
303
+ repaint();
304
+ }}
305
+ >
306
+ Show ${conflict.title}
307
+ </sp-button>
308
+ `
309
+ : nothing
310
+ }
311
+ </div>
312
+ `;
313
+ }
314
+
315
+ /**
316
+ * One binding.
317
+ *
318
+ * The record is passed in rather than looked up: the rows and the commands come from the same
319
+ * `registry.list()` call, so there is no window in which a row can name something the sheet cannot
320
+ * resolve, and no assertion pretending so.
321
+ */
322
+ function keyRowTpl(
323
+ row: ShortcutRow,
324
+ command: AnyCommand,
325
+ registry: CommandRegistry,
326
+ ): TemplateResult {
327
+ const capturing = _keyCapture?.mode === "rebind" && _keyCapture.command.id === row.commandId;
328
+ return html`
329
+ <div class="prefs-key" data-command=${row.commandId}>
330
+ <kbd class="prefs-key-chord">
331
+ ${capturing ? "Press a shortcut…" : registry.keymap.format(row.chord)}
332
+ </kbd>
333
+ <span class="prefs-key-title">${row.title}${row.overridden ? " — changed" : ""}</span>
334
+ <sp-action-button
335
+ size="s"
336
+ quiet
337
+ ?selected=${capturing}
338
+ aria-label=${capturing ? `Stop changing ${row.title}` : `Change the shortcut for ${row.title}`}
339
+ @click=${() => {
340
+ _keyCapture = capturing ? null : { mode: "rebind", command };
341
+ _keyRefusal = null;
342
+ repaint();
343
+ }}
344
+ >
345
+ ${capturing ? "Cancel" : "Change"}
346
+ </sp-action-button>
347
+ ${
348
+ row.overridden
349
+ ? html`
350
+ <sp-action-button
351
+ size="s"
352
+ quiet
353
+ aria-label=${`Reset the shortcut for ${row.title}`}
354
+ @click=${() => {
355
+ resetKeybinding(registry, row.commandId);
356
+ _keyRefusal = null;
357
+ repaint();
358
+ }}
359
+ >
360
+ Reset
361
+ </sp-action-button>
362
+ `
363
+ : nothing
364
+ }
365
+ </div>
366
+ `;
367
+ }
368
+
369
+ /**
370
+ * The keyboard sheet — every live binding, grouped by the scope it is live in.
371
+ *
372
+ * The rows come from the running registry through `shortcutReference`, so a command registered by
373
+ * an extension appears here without this file knowing it exists, and the user's layer is passed to
374
+ * the same projection rather than patched into its output.
375
+ *
376
+ * A command that declares two chords has two rows and rebinding either one leaves it with one — a
377
+ * user who asked for ⌥⌘Y is not also asking to keep ⌘Y, and Reset brings both back.
378
+ */
379
+ function keyboardTpl(): TemplateResult {
380
+ const registry = activeRegistry();
381
+ const commands: readonly AnyCommand[] = registry ? [...registry.list()] : [];
382
+ const rows = registry ? shortcutReference(commands, registry.keymap.overrides()) : [];
383
+ if (!registry || rows.length === 0) {
384
+ return html`<p class="prefs-empty">No commands are registered in this window.</p>`;
385
+ }
386
+ const format = (chord: string) => registry.keymap.format(chord);
387
+ const byId = new Map(commands.map((command) => [command.id, command]));
388
+ const visible = rows.flatMap((row) => {
389
+ const command = byId.get(row.commandId);
390
+ return command && keyRowMatches(row, format) ? [{ command, row }] : [];
391
+ });
392
+ const scopes = [...new Set(visible.map(({ row }) => row.scope))];
393
+ const capturingSearch = _keyCapture?.mode === "search";
394
+ return html`
395
+ <div class="prefs-keys" @keydown=${onKeyboardKeyDown}>
396
+ <div class="prefs-field">
397
+ <sp-search
398
+ size="s"
399
+ placeholder="Find a shortcut…"
400
+ .value=${
401
+ _keyFilter === null
402
+ ? ""
403
+ : _keyFilter.kind === "chord"
404
+ ? format(_keyFilter.chord)
405
+ : _keyFilter.value
406
+ }
407
+ @input=${(event: Event) => {
408
+ const { value } = event.target as HTMLInputElement;
409
+ _keyFilter = value ? { kind: "text", value } : null;
410
+ _keyCapture = null;
411
+ repaint();
412
+ }}
413
+ @submit=${(event: Event) => event.preventDefault()}
414
+ ></sp-search>
415
+ <sp-action-button
416
+ size="s"
417
+ ?selected=${capturingSearch}
418
+ @click=${() => {
419
+ _keyCapture = capturingSearch ? null : { mode: "search" };
420
+ _keyRefusal = null;
421
+ repaint();
422
+ }}
423
+ >
424
+ ${capturingSearch ? "Press it now" : "Search by keystroke"}
425
+ </sp-action-button>
426
+ </div>
427
+ ${keyRefusalTpl()}
428
+ ${
429
+ visible.length === 0
430
+ ? html`<p class="prefs-empty">
431
+ ${
432
+ _keyFilter?.kind === "chord"
433
+ ? `Nothing is bound to ${format(_keyFilter.chord)}.`
434
+ : "No shortcut matches that."
435
+ }
436
+ </p>`
437
+ : scopes.map(
438
+ (scope) => html`
439
+ <h4 class="prefs-keys-scope">${SCOPE_LABELS[scope]}</h4>
440
+ ${visible
441
+ .filter(({ row }) => row.scope === scope)
442
+ .map(({ command, row }) => keyRowTpl(row, command, registry))}
443
+ `,
444
+ )
445
+ }
446
+ </div>
447
+ `;
448
+ }
449
+
450
+ function sectionBodyTpl(): TemplateResult {
451
+ if (_section === "assistant") {
452
+ return assistantTpl();
453
+ }
454
+ if (_section === "accounts") {
455
+ return accountsTpl();
456
+ }
457
+ if (_section === "keyboard") {
458
+ return keyboardTpl();
459
+ }
460
+ return appearanceTpl();
461
+ }
462
+
463
+ // ─── The sheet ────────────────────────────────────────────────────────────────
464
+
465
+ /**
466
+ * Open Preferences, optionally on a named section. Resolves when it is dismissed.
467
+ *
468
+ * Re-opening while it is already up SELECTS the section instead of stacking a second sheet — which
469
+ * is what makes "Assistant: no provider connected → Preferences" land on the Assistant section from
470
+ * inside the assistant, rather than on top of itself.
471
+ *
472
+ * @param section One of {@link PREFERENCES_SECTIONS}' ids. An unknown id falls back to Appearance.
473
+ */
474
+ export function openPreferences(section?: string): Promise<null> {
475
+ _section = section !== undefined && isPreferencesSection(section) ? section : _section;
476
+ if (_close) {
477
+ repaint();
478
+ return Promise.resolve(null);
479
+ }
480
+ _section =
481
+ section !== undefined && isPreferencesSection(section) ? section : DEFAULT_PREFERENCES_SECTION;
482
+ credsForm.startEdit();
483
+ resetKeyboardState();
484
+ return showDialog<null>(
485
+ (done) => {
486
+ let wrapperEl: HTMLElement | null = null;
487
+
488
+ function finish() {
489
+ _rerender = null;
490
+ _close = null;
491
+ done(null);
492
+ }
493
+
494
+ function build(): TemplateResult {
495
+ const active = PREFERENCES_SECTIONS.find((candidate) => candidate.id === _section);
496
+ return html`
497
+ <sp-dialog-wrapper
498
+ open
499
+ underlay
500
+ headline="Preferences"
501
+ cancel-label="Close"
502
+ size="l"
503
+ @cancel=${finish}
504
+ @close=${finish}
505
+ ${ref((el?: Element) => {
506
+ if (el) {
507
+ wrapperEl = el as HTMLElement;
508
+ }
509
+ })}
510
+ >
511
+ <div class="prefs-sheet">
512
+ <nav class="prefs-nav" aria-label="Preferences sections">
513
+ ${PREFERENCES_SECTIONS.map(
514
+ (candidate) => html`
515
+ <button
516
+ type="button"
517
+ class="prefs-nav-item ${candidate.id === _section ? "active" : ""}"
518
+ aria-current=${candidate.id === _section ? "true" : "false"}
519
+ @click=${() => {
520
+ _section = candidate.id;
521
+ // Leaving a section abandons what it was in the middle of: an armed key
522
+ // Capture whose listener is no longer mounted would otherwise swallow the
523
+ // First chord pressed on the author's NEXT visit.
524
+ resetKeyboardState();
525
+ repaint();
526
+ }}
527
+ >
528
+ ${candidate.title}
529
+ </button>
530
+ `,
531
+ )}
532
+ </nav>
533
+ <section class="prefs-body">
534
+ <h3 class="prefs-title">${active?.title ?? ""}</h3>
535
+ <p class="prefs-blurb">${active?.blurb ?? ""}</p>
536
+ ${sectionBodyTpl()}
537
+ </section>
538
+ </div>
539
+ </sp-dialog-wrapper>
540
+ `;
541
+ }
542
+
543
+ _close = finish;
544
+ _rerender = () => {
545
+ // Resolved lazily: lit commits element refs before inserting the fragment, so the slot the
546
+ // Sheet was rendered into is only reachable once the first render has landed.
547
+ const host = wrapperEl?.parentElement;
548
+ if (host) {
549
+ litRender(build(), host);
550
+ }
551
+ };
552
+ return build();
553
+ },
554
+ { region: overlayRegion("dialog", "preferences") },
555
+ );
556
+ }
557
+
558
+ /** Close Preferences if it is open. Tests, and the shell teardown. */
559
+ export function closePreferences(): void {
560
+ _close?.();
561
+ }
562
+
563
+ // ─── Command ──────────────────────────────────────────────────────────────────
564
+
565
+ /**
566
+ * `app.preferences` — ⌘, everywhere, with no precondition.
567
+ *
568
+ * Application level, and deliberately not gated on an open project: the theme, the provider key and
569
+ * the keyboard are all configurable from the welcome screen, which is exactly where a first-run
570
+ * user is when they need them.
571
+ */
572
+ export function preferencesCommands(): AnyCommand[] {
573
+ return [
574
+ {
575
+ args: {
576
+ additionalProperties: false,
577
+ properties: {
578
+ section: {
579
+ type: "string",
580
+ enum: PREFERENCES_SECTIONS.map((section) => section.id),
581
+ description: "Which Preferences section to show. Defaults to Appearance.",
582
+ },
583
+ },
584
+ required: [],
585
+ type: "object",
586
+ },
587
+ category: "View",
588
+ id: "app.preferences",
589
+ level: "application",
590
+ keybinding: "mod+,",
591
+ menus: ["commandbar/overflow", "palette"],
592
+ group: "7_settings",
593
+ run: (_ctx, args) => {
594
+ const section = (args as { section?: unknown } | undefined)?.section;
595
+ if (section !== undefined && !isPreferencesSection(section)) {
596
+ throw new RangeError(
597
+ `command "app.preferences" argument "section": "${String(section)}" is not a ` +
598
+ `Preferences section — declared: ` +
599
+ `${PREFERENCES_SECTIONS.map((candidate) => candidate.id).join(", ")}`,
600
+ );
601
+ }
602
+ void openPreferences(section as string | undefined);
603
+ },
604
+ title: "Preferences…",
605
+ },
606
+ ];
607
+ }
608
+
609
+ /**
610
+ * Register the preferences verb, and apply the preferences that are already stored.
611
+ *
612
+ * The keyboard layer is applied HERE rather than at the end of the bootstrap because the keymap
613
+ * holds the layer itself: every record registered after this call — including the ones the
614
+ * bootstrap registers below it — is indexed against the author's overrides, so a rebinding survives
615
+ * a reload without anything having to run last.
616
+ */
617
+ export function registerPreferencesCommands(registry: CommandRegistry): void {
618
+ registry.registerAll(preferencesCommands());
619
+ applyKeybindingOverrides(registry);
620
+ }