@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
@@ -24,6 +24,11 @@ interface BuildSystemPromptOptions {
24
24
  projectRoot?: string | undefined;
25
25
  /** Whether a project is open. Defaults to `!!projectRoot` (kept explicit for tests). */
26
26
  hasProject?: boolean | undefined;
27
+ /**
28
+ * See {@link AiToolState.treeEditable}. Defaults to true so a caller with no registry — the
29
+ * prompt-shape tests — advertises the same list it always did.
30
+ */
31
+ treeEditable?: boolean | undefined;
27
32
  /** Project-relative file paths for the inventory section (project modes; capped). */
28
33
  fileInventory?: string[] | undefined;
29
34
  }
@@ -34,7 +39,20 @@ const FILE_INVENTORY_CAP = 100;
34
39
  // ─── Tool tiers (single source of truth for prompt AND gating) ──────────────
35
40
 
36
41
  /** Studio state a tool tier requires. Used by the gating registry AND the prompt tool list. */
37
- export type AiToolTier = "no-project" | "project" | "document";
42
+ /**
43
+ * `document-tree` is `document` plus the condition the HUMAN's equivalent verbs already carry.
44
+ *
45
+ * `Command.aiTool`'s contract is "the human's gate and the agent's gate stay one predicate", and
46
+ * these two gates were not one. The registry's `selection.delete` / `duplicate` / `moveUp` require
47
+ * `editor.kind === "canvas"` because the Outline renders whatever the active tab's document is —
48
+ * with Project Settings open, `project.json` drawn as a layer tree. The assistant's tier asked only
49
+ * whether a tab existed, so in that exact state the agent was advertised `remove_node` and
50
+ * `move_node` and executed them against the file that defines the project, while the human's
51
+ * `delete_node` was refused. `remove_node` self-refuses only the document root (`path.length < 2`),
52
+ * a weaker test than `structurallyEditable`, so a repeater template or `$switch` case was removable
53
+ * by the agent and not by the person.
54
+ */
55
+ export type AiToolTier = "no-project" | "project" | "document" | "document-tree";
38
56
 
39
57
  export interface AiToolInfo {
40
58
  name: string;
@@ -108,63 +126,79 @@ export const AI_TOOL_TIERS: AiToolInfo[] = [
108
126
  },
109
127
  {
110
128
  name: "set_property",
111
- tier: "document",
129
+ tier: "document-tree",
112
130
  blurb:
113
131
  "set_property(path, key, value) — set or remove a property on the node at path (tagName, textContent, className, style, attributes, $props…). Pass value: null to remove.",
114
132
  },
115
133
  {
116
134
  name: "set_style",
117
- tier: "document",
135
+ tier: "document-tree",
118
136
  blurb:
119
137
  'set_style(path, property, value) — set or remove a CSS style property (camelCase) on a node. Values as strings: "10px", "var(--color-accent)". Pass value: null to remove.',
120
138
  },
121
139
  {
122
140
  name: "set_text",
123
- tier: "document",
141
+ tier: "document-tree",
124
142
  blurb: 'set_text(path, value) — convenient alias for set_property with key: "textContent".',
125
143
  },
126
144
  {
127
145
  name: "add_child",
128
- tier: "document",
146
+ tier: "document-tree",
129
147
  blurb:
130
148
  "add_child(parentPath, index, node) — insert a new node into the children of parentPath at index.",
131
149
  },
132
150
  {
133
151
  name: "remove_node",
134
- tier: "document",
152
+ tier: "document-tree",
135
153
  blurb: "remove_node(path) — remove the node at path.",
136
154
  },
137
155
  {
138
156
  name: "move_node",
139
- tier: "document",
157
+ tier: "document-tree",
140
158
  blurb: "move_node(fromPath, toParentPath, toIndex) — move a node from one location to another.",
141
159
  },
142
160
  {
143
161
  name: "add_state",
144
- tier: "document",
162
+ tier: "document-tree",
145
163
  blurb:
146
164
  "add_state(key, value) — add a reactive state variable under the document's 'state' object. Value can be scalar, typed, computed, function, or data source.",
147
165
  },
148
166
  {
149
167
  name: "update_state",
150
- tier: "document",
168
+ tier: "document-tree",
151
169
  blurb: "update_state(key, value) — update or remove (value: null) an existing state variable.",
152
170
  },
153
171
  ];
154
172
 
173
+ /** The studio facts a tier is decided by. An object because four positional booleans is a trap. */
174
+ export interface AiToolState {
175
+ hasProject: boolean;
176
+ hasDocument: boolean;
177
+ /**
178
+ * The open document is an element tree the canvas is editing — `editor.kind === "canvas"`.
179
+ *
180
+ * Read from the COMMAND CONTEXT the human's verbs are evaluated against, not recomputed here, so
181
+ * the two gates are one predicate rather than two that agree until someone edits one of them.
182
+ */
183
+ treeEditable: boolean;
184
+ }
185
+
155
186
  /**
156
187
  * Whether a tier's tools are active for the given studio state. Shared semantics with the gating
157
188
  * registry: bootstrap tools vanish once a project opens; project tools need a project; document
158
189
  * tools need an active document (even in single-file mode without a project).
159
190
  */
160
- export function tierActive(tier: AiToolTier, hasProject: boolean, hasDocument: boolean): boolean {
191
+ export function tierActive(tier: AiToolTier, state: AiToolState): boolean {
161
192
  if (tier === "no-project") {
162
- return !hasProject;
193
+ return !state.hasProject;
163
194
  }
164
195
  if (tier === "project") {
165
- return hasProject;
196
+ return state.hasProject;
197
+ }
198
+ if (tier === "document-tree") {
199
+ return state.hasDocument && state.treeEditable;
166
200
  }
167
- return hasDocument;
201
+ return state.hasDocument;
168
202
  }
169
203
 
170
204
  // ─── Jx Schema Reference (condensed) ────────────────────────────────────────
@@ -525,12 +559,17 @@ export function buildSystemPrompt({
525
559
  components,
526
560
  projectRoot,
527
561
  hasProject = Boolean(projectRoot),
562
+ treeEditable = true,
528
563
  fileInventory,
529
564
  }: BuildSystemPromptOptions = {}) {
530
565
  const hasDocument = Boolean(document);
531
566
 
532
567
  // 1. Role, state-appropriate workflow, and the tool list for the current state.
533
- const toolList = AI_TOOL_TIERS.filter((t) => tierActive(t.tier, hasProject, hasDocument))
568
+ // The list the model is TOLD about and the list the gate will honour are the same filter, so a
569
+ // Refusal is never a surprise to it.
570
+ const toolList = AI_TOOL_TIERS.filter((t) =>
571
+ tierActive(t.tier, { hasDocument, hasProject, treeEditable }),
572
+ )
534
573
  .map((t) => `- ${t.blurb}`)
535
574
  .join("\n");
536
575
 
@@ -27,6 +27,7 @@ import {
27
27
  } from "../tabs/transact";
28
28
  import type { JxNodeValue } from "../tabs/transact";
29
29
  import { validateDoc } from "./jx-validate";
30
+ import { recordWrite } from "./ai-writes";
30
31
  import { flagHardcodedTokens, formatTokenHints } from "./token-lint";
31
32
 
32
33
  const PATH_DESCRIPTION =
@@ -127,7 +128,24 @@ async function applyAndValidate(
127
128
  const before = new Set(await validate(rawBefore));
128
129
  const renderOkBefore = renderCheck ? await renderCheck(rawBefore) : { ok: true };
129
130
 
130
- transactDoc(tab, mutationFn);
131
+ /* AND THE WRITE CAN BE REFUSED, in which case the model must be told rather than congratulated.
132
+ The collab gate pauses structural editing while source is canonical, and this went on to
133
+ record a ledger entry ("Changed N files") and validate the UNCHANGED document — which produces
134
+ no new errors, so the tool answered `success: true`. The model then built its next three edits
135
+ on a change that never existed. A refusal is an ordinary state of the room, so it is an error
136
+ result with a reason the model can act on (wait, or ask the author), not a thrown exception. */
137
+ if (!transactDoc(tab, mutationFn)) {
138
+ return {
139
+ success: false,
140
+ error:
141
+ "The document is frozen: a collaborator is editing its source, so structural edits are " +
142
+ "paused. Nothing was changed. Try again once source editing ends.",
143
+ };
144
+ }
145
+ /* One ledger entry per mutation, so the panel's "Changed N files" counts documents the model
146
+ touched rather than sentences it wrote (§7.4). `disk: false` is the load-bearing half: this
147
+ went through transactDoc, so the tab's history — and "Restore to here" — can reach it. */
148
+ recordWrite({ disk: false, ok: true, path: tab.documentPath ?? "(untitled)", tool: summary });
131
149
 
132
150
  const rawAfter = toRaw(tab.doc.document);
133
151
  const after = await validate(rawAfter);
@@ -734,14 +752,23 @@ export function registerAiTools(
734
752
  }
735
753
  try {
736
754
  await saveFile(relPath, JSON.stringify(content, null, 2));
755
+ recordWrite({ disk: true, ok: true, path: relPath, tool: "create_component" });
737
756
  return {
738
757
  success: true,
739
758
  summary: await reconcileAfterWrite(relPath, `Created component at "${relPath}".`),
740
759
  };
741
760
  } catch (error) {
761
+ const message = error instanceof Error ? error.message : String(error);
762
+ recordWrite({
763
+ disk: true,
764
+ error: message,
765
+ ok: false,
766
+ path: relPath,
767
+ tool: "create_component",
768
+ });
742
769
  return {
743
770
  success: false,
744
- error: `Failed to write file: ${error instanceof Error ? error.message : String(error)}`,
771
+ error: `Failed to write file: ${message}`,
745
772
  };
746
773
  }
747
774
  },
@@ -802,14 +829,23 @@ export function registerAiTools(
802
829
  }
803
830
  try {
804
831
  await saveFile(relPath, JSON.stringify(content, null, 2));
832
+ recordWrite({ disk: true, ok: true, path: relPath, tool: "create_page" });
805
833
  return {
806
834
  success: true,
807
835
  summary: await reconcileAfterWrite(relPath, `Created page at "${relPath}".`),
808
836
  };
809
837
  } catch (error) {
838
+ const message = error instanceof Error ? error.message : String(error);
839
+ recordWrite({
840
+ disk: true,
841
+ error: message,
842
+ ok: false,
843
+ path: relPath,
844
+ tool: "create_page",
845
+ });
810
846
  return {
811
847
  success: false,
812
- error: `Failed to write file: ${error instanceof Error ? error.message : String(error)}`,
848
+ error: `Failed to write file: ${message}`,
813
849
  };
814
850
  }
815
851
  },
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Ai-writes.ts — what the assistant changed, per turn, and whether you can take it back.
3
+ *
4
+ * §7.4 (AI honesty). Two facts about an assistant turn were legible only to the model:
5
+ *
6
+ * 1. **Which files it changed.** The loop collected `result.summary` strings and fed them back into
7
+ * the conversation; the human got a paragraph of prose in which "I updated the hero" and "I
8
+ * rewrote layouts/base.json" are the same shape of sentence.
9
+ * 2. **Which of those changes undo can reach.** Document tools mutate through `transactDoc`, so ⌘Z
10
+ * covers them. `write_file` and `write_project_config` go straight to disk through the platform,
11
+ * where there is no history and never was — and the caveat was appended to the MODEL-facing tool
12
+ * summary, telling the party that cannot press ⌘Z and not the party that can.
13
+ *
14
+ * A ledger rather than a parse of the summaries, because a summary is prose the model reads and a
15
+ * ledger is a record the UI renders — deriving one from the other would make the panel's honesty
16
+ * depend on the wording of a sentence written for somebody else.
17
+ *
18
+ * The tools record; the loop bounds the turn; the panel renders. Nothing here knows about lit, and
19
+ * nothing here imports the loop, so a tool can be honest without either.
20
+ */
21
+
22
+ /** One change the assistant made, as recorded by the tool that made it. */
23
+ export interface AiWrite {
24
+ /** Project-relative path, or the document path for an in-editor mutation. */
25
+ path: string;
26
+ /** The tool that did it — the command title the chip renders. */
27
+ tool: string;
28
+ /**
29
+ * True when the change went to disk with no transaction behind it.
30
+ *
31
+ * This is the undo caveat, as a fact rather than a sentence: a `disk` write is NOT reachable by
32
+ * ⌘Z, by the tab's history, or by "Restore to here". The panel says so, in those words, to the
33
+ * person who would otherwise press ⌘Z and believe it worked.
34
+ */
35
+ disk: boolean;
36
+ /** False when the tool reported a failure — a listed attempt that changed nothing. */
37
+ ok: boolean;
38
+ /** Why it failed, when it did. */
39
+ error?: string;
40
+ }
41
+
42
+ /** Every write recorded during one assistant turn, in the order the tools made them. */
43
+ export interface AiTurn {
44
+ /** The id of the assistant message the turn produced — how the panel finds its own ledger. */
45
+ id: string;
46
+ writes: AiWrite[];
47
+ }
48
+
49
+ /** Completed turns, oldest first. Bounded: a session's history is the session store's job. */
50
+ const turns: AiTurn[] = [];
51
+
52
+ /** How many turns of ledger are kept. Older ones drop their summary; the messages remain. */
53
+ export const MAX_TURNS = 50;
54
+
55
+ let open: AiTurn | null = null;
56
+
57
+ /**
58
+ * Start recording. Called by the agent loop once per user turn.
59
+ *
60
+ * Idempotent on the same id so a nested or re-entered loop cannot split one turn's writes across
61
+ * two ledgers — which is the same class of bug as the batch that was opened against whichever tab
62
+ * happened to be active.
63
+ *
64
+ * @param {string} id
65
+ */
66
+ export function beginTurn(id: string): void {
67
+ if (open?.id === id) {
68
+ return;
69
+ }
70
+ open = { id, writes: [] };
71
+ }
72
+
73
+ /**
74
+ * Record one change. A no-op when no turn is open, so a tool invoked outside the loop — a command,
75
+ * a test — costs nothing and reports nothing.
76
+ *
77
+ * @param {AiWrite} write
78
+ */
79
+ export function recordWrite(write: AiWrite): void {
80
+ open?.writes.push(write);
81
+ }
82
+
83
+ /**
84
+ * Close the open turn and file it under `id`.
85
+ *
86
+ * The id arrives at the END because it is the assistant message's id, which does not exist when the
87
+ * turn starts — the loop opens the turn before it knows what the model will say.
88
+ *
89
+ * @param {string} id - The assistant message the turn produced
90
+ * @returns {AiWrite[]} What was recorded
91
+ */
92
+ export function endTurn(id: string): AiWrite[] {
93
+ if (!open) {
94
+ return [];
95
+ }
96
+ const turn: AiTurn = { id, writes: open.writes };
97
+ open = null;
98
+ if (turn.writes.length === 0) {
99
+ return [];
100
+ }
101
+ turns.push(turn);
102
+ while (turns.length > MAX_TURNS) {
103
+ turns.shift();
104
+ }
105
+ return turn.writes;
106
+ }
107
+
108
+ /**
109
+ * What the turn that produced this assistant message changed. Empty for a turn that changed
110
+ * nothing, which is why the panel renders no summary at all rather than "Changed 0 files".
111
+ *
112
+ * @param {string} id
113
+ * @returns {AiWrite[]}
114
+ */
115
+ export function writesForTurn(id: string): AiWrite[] {
116
+ return turns.find((turn) => turn.id === id)?.writes ?? [];
117
+ }
118
+
119
+ /**
120
+ * The one-line summary the panel puts above the expander.
121
+ *
122
+ * Counts DISTINCT paths, not writes: a turn that edits the same document six times changed one
123
+ * file, and saying "Changed 6 files" would be the same dishonesty in the other direction. Disk
124
+ * writes are called out separately because they are the ones undo cannot reach.
125
+ *
126
+ * @param {AiWrite[]} writes
127
+ * @returns {string}
128
+ */
129
+ export function summarizeWrites(writes: AiWrite[]): string {
130
+ const applied = writes.filter((w) => w.ok);
131
+ const paths = new Set(applied.map((w) => w.path));
132
+ const failed = writes.length - applied.length;
133
+ if (paths.size === 0) {
134
+ return failed > 0 ? `${failed} change${failed === 1 ? "" : "s"} failed` : "";
135
+ }
136
+ const files = `Changed ${paths.size} file${paths.size === 1 ? "" : "s"}`;
137
+ const diskPaths = new Set(applied.filter((w) => w.disk).map((w) => w.path));
138
+ const parts = [files];
139
+ if (diskPaths.size > 0) {
140
+ parts.push(
141
+ `${diskPaths.size} written to disk — undo cannot reach ${diskPaths.size === 1 ? "it" : "them"}`,
142
+ );
143
+ }
144
+ if (failed > 0) {
145
+ parts.push(`${failed} failed`);
146
+ }
147
+ return parts.join(" · ");
148
+ }
149
+
150
+ /** Drop every ledger. For tests and for the "new chat" / "close project" paths. */
151
+ export function resetAiWrites(): void {
152
+ turns.splice(0);
153
+ open = null;
154
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * The app's live region: one element, one call site, everything the app says out loud.
3
+ *
4
+ * **Why this exists.** `notify.ts` is the app's only way to say what happened, and its default tier
5
+ * routes **error ⇒ problem**. But the `role="status"` region lived on the _toast_ host, and the
6
+ * Problems panel had none at all — so a **failure reached no live region whatsoever**. The app
7
+ * would post "Save failed", show it in a panel, and a screen-reader user would be told nothing. A
8
+ * panel-local region would not have fixed it either: the Problems list lives in the Bottom dock,
9
+ * and a region inside a hidden tab announces nothing.
10
+ *
11
+ * So the announcer is not a panel's concern. It is called from {@link notify} itself — one call
12
+ * site, so a record cannot be posted without being announced, and a future host gets announcements
13
+ * for free rather than having to remember.
14
+ *
15
+ * **Two regions, because politeness is not a style choice** (WAI-ARIA §5.2.9). `assertive`
16
+ * interrupts whatever the reader is listening to, which is right for a failure and rude for a save.
17
+ * `polite` waits for a pause. Mixing both in one region does not work — the attribute is read when
18
+ * the region is created, not when the text changes — so there are two, and the severity picks.
19
+ *
20
+ * **The text is cleared, then set on a later frame.** A live region announces a _change_; writing
21
+ * the same string twice is not one, so a repeated failure would be silent. Clearing first makes
22
+ * every post a change.
23
+ *
24
+ * @docs studio/interface/problems-and-progress
25
+ */
26
+
27
+ /** How urgently a message interrupts. Errors interrupt; everything else waits for a pause. */
28
+ export type Politeness = "assertive" | "polite";
29
+
30
+ const REGION_ID: Readonly<Record<Politeness, string>> = {
31
+ assertive: "jx-live-assertive",
32
+ polite: "jx-live-polite",
33
+ };
34
+
35
+ /**
36
+ * Find (or create) a live region.
37
+ *
38
+ * It is appended to `<body>` rather than to any panel, and it is visually hidden with the clip-rect
39
+ * idiom rather than `display: none` — a display-none region is removed from the accessibility tree
40
+ * entirely and announces nothing, which is the classic way to ship a live region that does not
41
+ * work.
42
+ */
43
+ function region(politeness: Politeness): HTMLElement | null {
44
+ if (typeof document === "undefined") {
45
+ return null;
46
+ }
47
+ const id = REGION_ID[politeness];
48
+ const existing = document.querySelector<HTMLElement>(`#${id}`);
49
+ if (existing) {
50
+ return existing;
51
+ }
52
+ const node = document.createElement("div");
53
+ node.id = id;
54
+ node.setAttribute("aria-live", politeness);
55
+ /* `status` for polite and `alert` for assertive: the implicit roles, so an assistive technology
56
+ that reads roles rather than the attribute agrees with one that does the opposite. */
57
+ node.setAttribute("role", politeness === "assertive" ? "alert" : "status");
58
+ node.setAttribute("aria-atomic", "true");
59
+ node.style.cssText =
60
+ "position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;" +
61
+ "clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0";
62
+ document.body.append(node);
63
+ return node;
64
+ }
65
+
66
+ /**
67
+ * Say something out loud.
68
+ *
69
+ * @param {string} message What to announce. Empty strings are ignored — a live region that receives
70
+ * one announces nothing, so posting it only hides a caller's bug.
71
+ * @param {Politeness} [politeness] Defaults to `polite`.
72
+ */
73
+ export function announce(message: string, politeness: Politeness = "polite"): void {
74
+ if (message === "") {
75
+ return;
76
+ }
77
+ const node = region(politeness);
78
+ if (!node) {
79
+ return;
80
+ }
81
+ node.textContent = "";
82
+ /* A live region announces a CHANGE. Writing the same string twice is not one, so a second
83
+ identical failure would be silent without the clear-then-set on a later turn. */
84
+ setTimeout(() => {
85
+ node.textContent = message;
86
+ }, 0);
87
+ }
88
+
89
+ /** Remove both regions. Tests only — a leaked region makes the next test's assertions ambiguous. */
90
+ export function resetAnnouncer(): void {
91
+ if (typeof document === "undefined") {
92
+ return;
93
+ }
94
+ for (const id of Object.values(REGION_ID)) {
95
+ document.querySelector(`#${id}`)?.remove();
96
+ }
97
+ }