@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,347 @@
1
+ /// <reference lib="dom" />
2
+ /**
3
+ * Locales — the project's languages, its default, and how a locale appears in a URL.
4
+ *
5
+ * `project.json`'s `i18n` block was authorable only by hand: every other i18n surface in Studio
6
+ * (the parity panel, the pane's locale companion, the translation verbs) reads a locale list
7
+ * nothing in the app could write, so a project became multilingual in a text editor and only then
8
+ * grew any of the affordances that make it worth being.
9
+ *
10
+ * **One write, two doors.** {@link addProjectLocale} is the whole of "declare a language" and the
11
+ * `i18n.addLocale` command calls this function rather than repeating the patch — a second spelling
12
+ * of the same merge is how the command and the form would come to disagree about what happens to
13
+ * `defaultLocale` when the first locale is added.
14
+ *
15
+ * @docs studio/projects/settings
16
+ */
17
+
18
+ import { html, render as litRender, nothing } from "lit-html";
19
+ import { canonicalizeLocale, isWellFormedLocale, localeLabel } from "@jxsuite/schema/locale";
20
+ import { errorMessage } from "@jxsuite/schema/parse";
21
+ import { notify } from "../services/notify";
22
+ import { projectState } from "../store";
23
+ import { updateSiteConfig } from "../site-context";
24
+ import type { LocaleRouting } from "@jxsuite/schema/locale";
25
+ import type { ProjectConfig } from "@jxsuite/schema/types";
26
+
27
+ /**
28
+ * The last failed write per rendered section container.
29
+ *
30
+ * Keyed by container so two mounted copies (and two tests) never share a message — the same shape
31
+ * `project-sections.ts` and `general-settings.ts` use, and for the same reason: these sections save
32
+ * on change, so a silent rejection would read as "my edit just vanished".
33
+ */
34
+ const errors = new WeakMap<HTMLElement, string>();
35
+
36
+ /** Drop a parked error and re-render. */
37
+ function clearError(container: HTMLElement): void {
38
+ errors.delete(container);
39
+ }
40
+
41
+ /** The error line for a section, if its last write failed. */
42
+ function errorLine(container: HTMLElement) {
43
+ const message = errors.get(container);
44
+ return message === undefined
45
+ ? nothing
46
+ : html`<p class="settings-field-error" role="alert">${message}</p>`;
47
+ }
48
+
49
+ /**
50
+ * Persist a patch, surfacing the rejection instead of swallowing it.
51
+ *
52
+ * `updateSiteConfig` rejects on a failed write and the chokepoint has already filed the Problem, so
53
+ * all that is left here is to keep the form honest about what is on disk. A bare `void
54
+ * updateSiteConfig(...)` would drop the rejection and the field would silently snap back to the
55
+ * value the file still holds.
56
+ *
57
+ * @param {HTMLElement} container
58
+ * @param {Partial<ProjectConfig>} patch
59
+ * @param {() => void} rerender
60
+ */
61
+ async function persist(
62
+ container: HTMLElement,
63
+ patch: Partial<ProjectConfig>,
64
+ rerender: () => void,
65
+ ): Promise<void> {
66
+ try {
67
+ await updateSiteConfig(patch);
68
+ clearError(container);
69
+ } catch (error) {
70
+ errors.set(container, `Could not save project.json — ${errorMessage(error)}`);
71
+ }
72
+ rerender();
73
+ }
74
+
75
+ /** The live project configuration, or an empty one before a project is open. */
76
+ function config(): ProjectConfig {
77
+ return (projectState?.projectConfig ?? {}) as ProjectConfig;
78
+ }
79
+
80
+ /**
81
+ * The patch that removes the whole `i18n` block.
82
+ *
83
+ * `undefined` survives the object spread inside `updateSiteConfig` and `JSON.stringify` then drops
84
+ * the key, so removing the last locale leaves a project with no `i18n` at all rather than one
85
+ * declaring an empty list — which `resolveI18n` reports as "declared with no usable locale", a
86
+ * build error for a state the author reached by deleting things. The cast is the price of
87
+ * `exactOptionalPropertyTypes`, the same one `general-settings.ts`'s `CLEAR_URL` pays.
88
+ */
89
+ const CLEAR_I18N = { i18n: undefined } as unknown as Partial<ProjectConfig>;
90
+
91
+ /** The routing modes, with the sentence that says what each does to a URL. */
92
+ export const LOCALE_ROUTINGS: readonly { value: LocaleRouting; label: string }[] = [
93
+ { label: "Prefix every locale but the default", value: "prefix-except-default" },
94
+ { label: "Prefix every locale", value: "prefix-always" },
95
+ ];
96
+
97
+ /**
98
+ * The tags as the author wrote them, not as `resolveI18n` reports them.
99
+ *
100
+ * Reading the resolved list back would rewrite the file on the next save: `resolveI18n` unshifts a
101
+ * `defaultLocale` that is missing from `locales` and canonicalizes the case of every entry, so a
102
+ * patch built from it edits lines the author did not touch.
103
+ */
104
+ function declaredLocales(): string[] {
105
+ const raw = config().i18n?.locales;
106
+ return Array.isArray(raw) ? raw.filter((tag): tag is string => typeof tag === "string") : [];
107
+ }
108
+
109
+ /** Whether `tag` names a language the project already declares, whatever case it was written in. */
110
+ function alreadyDeclared(tag: string): boolean {
111
+ const canonical = canonicalizeLocale(tag);
112
+ return declaredLocales().some((declared) => canonicalizeLocale(declared) === canonical);
113
+ }
114
+
115
+ /**
116
+ * Add one tag to `i18n.locales` — the one write both this section and `i18n.addLocale` make.
117
+ *
118
+ * The patch spreads `config().i18n` because `commitProjectConfig` merges at the TOP LEVEL only: `{
119
+ * i18n: { locales } }` replaces the block wholesale and takes `defaultLocale` and `routing` with
120
+ * it, so a project that declared a default would silently lose it the first time a language was
121
+ * added.
122
+ *
123
+ * Refusals are notified rather than thrown: the palette reports a rejected `run` to the console and
124
+ * nowhere else, so a thrown refusal is one the author never sees. The form disables Add for both of
125
+ * them before it gets here, with the reason under the field.
126
+ *
127
+ * @param {string} tag - A BCP 47 language tag, in any case.
128
+ * @throws {Error} Whatever `updateSiteConfig` rejects with — the caller decides where a failed
129
+ * write shows.
130
+ */
131
+ export async function addProjectLocale(tag: string): Promise<void> {
132
+ const canonical = canonicalizeLocale(tag);
133
+ if (canonical === null) {
134
+ notify.error(`"${tag}" is not a well-formed language tag.`, {
135
+ detail:
136
+ "Languages are BCP 47 tags — a language, optionally a script and a region: `fr`, " +
137
+ "`pt-BR`, `zh-Hant`. Underscores and spaces are not part of the grammar.",
138
+ source: "Languages",
139
+ });
140
+ return;
141
+ }
142
+ if (alreadyDeclared(canonical)) {
143
+ notify.info(`${localeLabel(canonical)} is already one of this project's languages.`, {
144
+ source: "Languages",
145
+ });
146
+ return;
147
+ }
148
+ await updateSiteConfig({
149
+ i18n: { ...config().i18n, locales: [...declaredLocales(), canonical] },
150
+ });
151
+ }
152
+
153
+ /** Pending tag per container, so typing survives the section re-rendering. */
154
+ const pendingTag = new WeakMap<HTMLElement, string>();
155
+
156
+ /**
157
+ * Why the pending tag cannot be added, or `""` when it can.
158
+ *
159
+ * Both refusals are stated while the author is typing rather than after the Add: a language tag is
160
+ * the one field in this section whose value the author cannot check by looking at it, and "nothing
161
+ * happened" is what a silently refused Add looks like.
162
+ */
163
+ function pendingRefusal(container: HTMLElement): string {
164
+ const tag = (pendingTag.get(container) ?? "").trim();
165
+ if (tag === "") {
166
+ return "";
167
+ }
168
+ if (!isWellFormedLocale(tag)) {
169
+ return `"${tag}" is not a well-formed language tag. Try \`fr\`, \`pt-BR\` or \`zh-Hant\`.`;
170
+ }
171
+ return alreadyDeclared(tag) ? `${localeLabel(tag)} is already declared.` : "";
172
+ }
173
+
174
+ /**
175
+ * The project's languages, its default, and how a locale is spelled in a URL.
176
+ *
177
+ * Every handler re-reads `config()` instead of closing over the render's values: `projectState` is
178
+ * a plain module binding replaced wholesale on a project switch, and an extension or the raw JSON
179
+ * editor can write `i18n` between this render and the click that follows it.
180
+ *
181
+ * @param {HTMLElement} container
182
+ */
183
+ export function renderLocalesSection(container: HTMLElement): void {
184
+ const rerender = () => renderLocalesSection(container);
185
+ const locales = declaredLocales();
186
+ const { i18n } = config();
187
+ const pending = pendingTag.get(container) ?? "";
188
+ const refusal = pendingRefusal(container);
189
+
190
+ const add = () => {
191
+ const tag = (pendingTag.get(container) ?? "").trim();
192
+ if (tag === "" || pendingRefusal(container) !== "") {
193
+ return;
194
+ }
195
+ pendingTag.delete(container);
196
+ void persist(
197
+ container,
198
+ { i18n: { ...config().i18n, locales: [...declaredLocales(), canonicalizeLocale(tag)!] } },
199
+ rerender,
200
+ );
201
+ };
202
+
203
+ const remove = (tag: string) => {
204
+ const kept = declaredLocales().filter((declared) => declared !== tag);
205
+ if (kept.length === 0) {
206
+ /* The last language leaves with the block. An `i18n` holding an empty `locales` is a build
207
+ error (`resolveI18n`: "declared with no usable locale"), so removing the last row would
208
+ otherwise break the build of a project that is once again ordinary and monolingual. */
209
+ void persist(container, CLEAR_I18N, rerender);
210
+ return;
211
+ }
212
+ const current = config().i18n;
213
+ /* A `defaultLocale` naming a locale that is no longer declared does not go away — `resolveI18n`
214
+ puts it BACK at the head of the list, so deleting the default row without this would delete
215
+ nothing and reorder the rest. */
216
+ const orphaned =
217
+ current?.defaultLocale !== undefined &&
218
+ canonicalizeLocale(current.defaultLocale) === canonicalizeLocale(tag);
219
+ void persist(
220
+ container,
221
+ { i18n: { ...current, ...(orphaned ? { defaultLocale: kept[0]! } : {}), locales: kept } },
222
+ rerender,
223
+ );
224
+ };
225
+
226
+ const onDefaultChange = (e: Event) => {
227
+ void persist(
228
+ container,
229
+ { i18n: { ...config().i18n, defaultLocale: (e.target as HTMLInputElement).value } },
230
+ rerender,
231
+ );
232
+ };
233
+
234
+ const onRoutingChange = (e: Event) => {
235
+ void persist(
236
+ container,
237
+ {
238
+ i18n: {
239
+ ...config().i18n,
240
+ routing: (e.target as HTMLInputElement).value as LocaleRouting,
241
+ },
242
+ },
243
+ rerender,
244
+ );
245
+ };
246
+
247
+ const tpl = html`
248
+ <div class="settings-section">
249
+ <h3 class="settings-section-title">Locales</h3>
250
+ <p class="settings-field-desc">
251
+ The languages this site is written in. A translation is a file under the locale's directory
252
+ — pages/fr/about.json beside pages/about.json — so declaring a language here is what makes
253
+ that directory mean something to the build and to Studio.
254
+ </p>
255
+ ${errorLine(container)}
256
+ <div class="settings-field">
257
+ <label class="settings-field-label">Languages</label>
258
+ <div class="settings-list-panel">
259
+ ${
260
+ locales.length === 0
261
+ ? html`<div class="settings-empty-state">No languages declared.</div>`
262
+ : locales.map(
263
+ (tag) => html`
264
+ <div class="settings-row">
265
+ <span class="settings-row-name">${localeLabel(tag)}</span>
266
+ <span class="settings-locale-tag">${tag}</span>
267
+ <sp-action-button
268
+ size="xs"
269
+ quiet
270
+ title=${`Remove ${tag}`}
271
+ @click=${() => remove(tag)}
272
+ >
273
+ <sp-icon-delete slot="icon"></sp-icon-delete>
274
+ </sp-action-button>
275
+ </div>
276
+ `,
277
+ )
278
+ }
279
+ <div class="settings-inline-form">
280
+ <sp-textfield
281
+ size="s"
282
+ class="settings-locale-name"
283
+ placeholder="fr-CA"
284
+ .value=${pending}
285
+ @input=${(e: Event) => {
286
+ const before = pendingRefusal(container);
287
+ pendingTag.set(container, (e.target as HTMLInputElement).value);
288
+ /* Re-render only when the VERDICT changes, not per keystroke: redrawing on every
289
+ character would push `.value` back into a field the author is typing in, and
290
+ never redrawing would leave the refusal below it describing an older tag. */
291
+ if (pendingRefusal(container) !== before) {
292
+ rerender();
293
+ }
294
+ }}
295
+ @keydown=${(e: KeyboardEvent) => {
296
+ if (e.key === "Enter") {
297
+ add();
298
+ }
299
+ }}
300
+ ></sp-textfield>
301
+ <sp-action-button size="s" @click=${add}>Add</sp-action-button>
302
+ </div>
303
+ </div>
304
+ ${refusal === "" ? nothing : html`<p class="settings-field-error">${refusal}</p>`}
305
+ </div>
306
+ <div class="settings-field">
307
+ <label class="settings-field-label">Default Language</label>
308
+ <p class="settings-field-desc">
309
+ The language a page is written in when its file carries no locale directory.
310
+ </p>
311
+ <sp-picker
312
+ size="s"
313
+ label="Default Language"
314
+ class="settings-default-locale"
315
+ .value=${i18n?.defaultLocale ?? locales[0] ?? ""}
316
+ ?disabled=${locales.length === 0}
317
+ @change=${onDefaultChange}
318
+ >
319
+ ${locales.map(
320
+ (tag) => html`<sp-menu-item value=${tag}>${localeLabel(tag)}</sp-menu-item>`,
321
+ )}
322
+ </sp-picker>
323
+ </div>
324
+ <div class="settings-field">
325
+ <label class="settings-field-label">URL Routing</label>
326
+ <p class="settings-field-desc">
327
+ Whether the default language owns the unprefixed URLs, or every language is named in its
328
+ own.
329
+ </p>
330
+ <sp-picker
331
+ size="s"
332
+ label="URL Routing"
333
+ class="settings-locale-routing"
334
+ .value=${i18n?.routing ?? "prefix-except-default"}
335
+ ?disabled=${locales.length === 0}
336
+ @change=${onRoutingChange}
337
+ >
338
+ ${LOCALE_ROUTINGS.map(
339
+ (routing) => html`<sp-menu-item value=${routing.value}>${routing.label}</sp-menu-item>`,
340
+ )}
341
+ </sp-picker>
342
+ </div>
343
+ </div>
344
+ `;
345
+
346
+ litRender(tpl, container);
347
+ }
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Preferences-accounts.ts — every credential Studio holds, in one list that can also forget them.
3
+ *
4
+ * Studio stores three kinds of secret in three unrelated places and, until this file, could neither
5
+ * enumerate nor clear any of them:
6
+ *
7
+ * - The GitHub device-flow token (`github/github-auth.ts`) — `clearGithubToken()` had **zero
8
+ * callers**, so signing out was impossible from inside the app;
9
+ * - The AI provider key, endpoint and model (`services/ai-settings.ts`) — clearable only by saving a
10
+ * blank key into a dialog that did not say that is what a blank key means;
11
+ * - The Cloudflare publish token and account id (`services/cf-settings.ts`) — not clearable at all.
12
+ *
13
+ * Plan §9.3 puts them under Preferences › Accounts, "listed, revocable". This module is that list.
14
+ * It is deliberately UI-free — a record per account with a `revoke` — so the disconnect path is
15
+ * testable without a dialog, and so the same records can back a future Problems entry or an
16
+ * `accounts.revoke` command without being re-derived from a template.
17
+ *
18
+ * **No secret is ever returned.** A record says whether something is stored and what it is for;
19
+ * printing a token into a list is how a screenshot leaks one.
20
+ */
21
+
22
+ import { clearGithubToken, githubTokenLocation, githubTokenStored } from "../github/github-auth";
23
+ import { getCfAccountId, getCfToken, setCfAccountId, setCfToken } from "../services/cf-settings";
24
+ import {
25
+ getBaseUrl,
26
+ getModel,
27
+ hasOpenAiKey,
28
+ setBaseUrl,
29
+ setModel,
30
+ setOpenAiKey,
31
+ } from "../services/ai-settings";
32
+
33
+ /** One stored credential, as the Accounts section renders it. */
34
+ export interface AccountRecord {
35
+ /** Stable key — the value `revokeAccount` takes and the row's `data-account`. */
36
+ id: string;
37
+ /** The service, in the words its own docs use. */
38
+ label: string;
39
+ /** What is held, or what connecting would buy. Never the secret. */
40
+ detail: string;
41
+ connected: boolean;
42
+ /** Forget it. Idempotent: revoking a disconnected account is a no-op, not an error. */
43
+ revoke: () => void;
44
+ }
45
+
46
+ /**
47
+ * The three accounts, always all three.
48
+ *
49
+ * A disconnected account still gets a row: "you are not signed in to GitHub" is information, and a
50
+ * list that hides what is absent cannot answer the question the section exists to answer.
51
+ */
52
+ export function listAccounts(): AccountRecord[] {
53
+ const githubToken = githubTokenStored();
54
+ const cfToken = getCfToken();
55
+ const cfAccount = getCfAccountId();
56
+ const model = getModel();
57
+ const endpoint = getBaseUrl();
58
+ return [
59
+ {
60
+ id: "github",
61
+ label: "GitHub",
62
+ detail: githubToken
63
+ ? githubTokenLocation() === "desktop"
64
+ ? "Signed in — the token is in this app's config folder, readable only by you."
65
+ : "Signed in — a device-flow token is stored in this browser."
66
+ : "Not signed in. Cloning and pushing will ask when they need it.",
67
+ connected: Boolean(githubToken),
68
+ revoke: clearGithubToken,
69
+ },
70
+ {
71
+ id: "ai",
72
+ label: "AI provider",
73
+ detail: hasOpenAiKey()
74
+ ? `Key stored. Model ${model}${endpoint ? ` via ${endpoint}` : ""}.`
75
+ : "No key stored. The Assistant section is where one is entered.",
76
+ connected: hasOpenAiKey(),
77
+ revoke: () => {
78
+ setOpenAiKey("");
79
+ setBaseUrl("");
80
+ setModel("");
81
+ },
82
+ },
83
+ {
84
+ id: "cloudflare",
85
+ label: "Cloudflare",
86
+ detail: cfToken
87
+ ? `Connected${cfAccount ? ` — account ${cfAccount}` : ""}.`
88
+ : "Not connected. Publishing will ask when it needs it.",
89
+ connected: Boolean(cfToken),
90
+ revoke: () => {
91
+ setCfToken("");
92
+ setCfAccountId("");
93
+ },
94
+ },
95
+ ];
96
+ }
97
+
98
+ /**
99
+ * Revoke one account by id.
100
+ *
101
+ * @returns `true` when an account by that id exists, `false` when it does not — so a caller with a
102
+ * stale id finds out rather than silently doing nothing.
103
+ */
104
+ export function revokeAccount(id: string): boolean {
105
+ const account = listAccounts().find((candidate) => candidate.id === id);
106
+ if (!account) {
107
+ return false;
108
+ }
109
+ account.revoke();
110
+ notifyCredentialsChanged();
111
+ return true;
112
+ }
113
+
114
+ const _listeners = new Set<() => void>();
115
+
116
+ /**
117
+ * Subscribe to credential changes. Returns the unsubscribe.
118
+ *
119
+ * This is the seam that keeps the dependency one-directional: the assistant panel shows a "no
120
+ * provider connected" notice, Preferences is where a key is now entered and cleared, and the panel
121
+ * would otherwise have to be imported BY the dialog it opens. Instead both depend on this leaf.
122
+ */
123
+ export function onCredentialsChanged(listener: () => void): () => void {
124
+ _listeners.add(listener);
125
+ return () => {
126
+ _listeners.delete(listener);
127
+ };
128
+ }
129
+
130
+ /** Announce that a credential was saved or revoked. */
131
+ export function notifyCredentialsChanged(): void {
132
+ for (const listener of _listeners) {
133
+ listener();
134
+ }
135
+ }
136
+
137
+ /** Drop every subscription — tests only; the app subscribes once, at mount. */
138
+ export function resetCredentialListeners(): void {
139
+ _listeners.clear();
140
+ }