@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,271 @@
1
+ /**
2
+ * Notify.ts — the third record type (plan §1), and the app's only way to say what happened.
3
+ *
4
+ * What this replaces: 78 `statusMessage()` call sites — 26 failures and 52 successes — rendered in
5
+ * identical 11px grey text in a 24px strip and destroyed after 3000 ms. A failure and a save looked
6
+ * the same, neither could be acted on, and both were gone before a reader who had looked away could
7
+ * read them. `statusMessage` is deleted, not wrapped; `scripts/check-styles.ts` bans the name from
8
+ * `src/` so it cannot regrow.
9
+ *
10
+ * **A notification is a record, and its lifetime is chosen by the action it requires** (§7.1):
11
+ *
12
+ * | Tier | Host | Lifetime | Use when |
13
+ * | --------- | -------------------------- | ------------------ | ------------------------------ |
14
+ * | `toast` | the fourth overlay layer | timed, dismissible | reversible, or needs no action |
15
+ * | `problem` | the Bottom dock's Problems | until it is fixed | it must be fixed |
16
+ *
17
+ * §7.1 names a third tier — **inline**, the `error` slot on `ui/field-row.ts`'s `renderFieldRow`.
18
+ * It is deliberately NOT a member of {@link NotificationTier}: an inline error is a value a control
19
+ * renders next to the field the user is editing, not a record the app posts to a host, and it has
20
+ * no store to live in. Declaring a tier here whose only possible outcome is being dropped on the
21
+ * floor would be exactly the silence this module exists to end, so the union has two members and
22
+ * the third tier arrives as `renderFieldRow`'s own parameter (P4.4).
23
+ *
24
+ * **Recovery is a command id, not a per-call-site closure.** `notify.error("Save failed", { action:
25
+ * "file.save" })` gives the toast and the Problems row a Retry button whose label, availability,
26
+ * disabled reason and keyboard chord all come off the command record — which is why this lives
27
+ * beside the registry rather than inside any one surface. A call site that wanted a Retry used to
28
+ * have to invent a button; now it names a capability that already exists.
29
+ *
30
+ * Nothing here renders. `ui/layers.ts` hosts the toasts and the Bottom dock hosts the Problems
31
+ * list; both read the two reactive arrays below, so a new surface can render notifications without
32
+ * this module learning it exists.
33
+ */
34
+
35
+ import { reactive } from "../reactivity";
36
+ import { announce } from "./announce";
37
+ import { now } from "./clock";
38
+
39
+ /** How bad it is. Chooses the icon, the colour, and the default tier. */
40
+ export const SEVERITIES = ["success", "info", "warn", "error"] as const;
41
+
42
+ export type Severity = (typeof SEVERITIES)[number];
43
+
44
+ /**
45
+ * The two hosted lifetimes.
46
+ *
47
+ * A tier is a HOST, not a rendering preference: a record whose tier is `problem` is one the app
48
+ * promises to keep until somebody fixes it, and a record whose tier is `toast` is one it promises
49
+ * to take away. See the module doc for why `inline` is not here.
50
+ */
51
+ export const TIERS = ["toast", "problem"] as const;
52
+
53
+ export type NotificationTier = (typeof TIERS)[number];
54
+
55
+ /** The default tier per severity. A failure must be fixed; everything else may be taken away. */
56
+ const DEFAULT_TIER: Readonly<Record<Severity, NotificationTier>> = {
57
+ error: "problem",
58
+ info: "toast",
59
+ success: "toast",
60
+ warn: "toast",
61
+ };
62
+
63
+ /**
64
+ * How long a toast rests before it is taken away, by severity.
65
+ *
66
+ * §7.1 caps the band at 4–8s. A warning that nobody has to act on still deserves the long end of
67
+ * it, because it is the one a reader is most likely to have looked away from.
68
+ */
69
+ export const TOAST_LIFETIME_MS: Readonly<Record<Severity, number>> = {
70
+ error: 8000,
71
+ info: 4000,
72
+ success: 4000,
73
+ warn: 8000,
74
+ };
75
+
76
+ /** What a caller may say about an outcome beyond its severity and its one line. */
77
+ export interface NotifyOptions {
78
+ /**
79
+ * Command id offering recovery — the Retry / Fix button.
80
+ *
81
+ * A command id rather than a callback so the button's LABEL is the command's title, its enabled
82
+ * state is the command's `enablement`, and its refusal sentence is the command's `requires`. Four
83
+ * facts a closure could carry none of.
84
+ */
85
+ action?: string;
86
+ /** Args passed to {@link NotifyOptions.action} when it runs. */
87
+ actionArgs?: Record<string, unknown>;
88
+ /** The long form — a stack, a captured log, a validator's output. Rendered in Problems only. */
89
+ detail?: string;
90
+ /** Who reported it: "Save", "Source Control", "Canvas", "Assistant". Groups the Problems list. */
91
+ source?: string;
92
+ /** The file or document the outcome is about, so Problems can click through to it. */
93
+ path?: string;
94
+ /** Override the tier {@link DEFAULT_TIER} would pick — a warning that MUST be fixed. */
95
+ tier?: NotificationTier;
96
+ /**
97
+ * Deduplication key. A second notification with the same key REPLACES the first rather than
98
+ * stacking beside it — the file watcher that fails once a second is one problem, not sixty.
99
+ */
100
+ key?: string;
101
+ /** Override the toast's rest time. Ignored for the `problem` tier, which has no timer. */
102
+ timeoutMs?: number;
103
+ }
104
+
105
+ /** One outcome, as recorded. Frozen: a host renders it, nothing edits it after the fact. */
106
+ export interface Notification {
107
+ readonly id: string;
108
+ readonly severity: Severity;
109
+ readonly message: string;
110
+ readonly tier: NotificationTier;
111
+ /** Epoch ms from {@link now}, so a pinned clock pins the "2m ago" a Problems row renders. */
112
+ readonly at: number;
113
+ readonly action?: string;
114
+ readonly actionArgs?: Record<string, unknown>;
115
+ readonly detail?: string;
116
+ readonly source?: string;
117
+ readonly path?: string;
118
+ readonly key?: string;
119
+ /** Rest time for a toast; `0` means "hold until dismissed". Absent on a problem. */
120
+ readonly timeoutMs?: number;
121
+ }
122
+
123
+ let _seq = 0;
124
+
125
+ /** Monotonic within a page load, and stable enough to be a lit `repeat` key. */
126
+ function nextId(): string {
127
+ _seq += 1;
128
+ return `n${_seq}`;
129
+ }
130
+
131
+ /**
132
+ * The live toast stack, oldest first. Reactive, so the host repaints by reading it.
133
+ *
134
+ * Exported as the array rather than behind a getter because that is what makes it trackable: a lit
135
+ * render effect that reads `toasts.length` re-runs when one arrives, with no subscription to
136
+ * remember to release.
137
+ */
138
+ export const toasts: Notification[] = reactive([]);
139
+
140
+ /**
141
+ * Everything that must be fixed, oldest first — what the Bottom dock's Problems tab renders and
142
+ * what the rail badge and the status bar count.
143
+ *
144
+ * The shape is the contract for that surface: each entry has an `id` to key on, a `severity` to
145
+ * sort and colour by, a `source` to group by, a `path` to click through to, an optional `detail`
146
+ * for the expanded row, and an optional `action` command id for its Fix / Retry button.
147
+ */
148
+ export const problems: Notification[] = reactive([]);
149
+
150
+ /** The most toasts on screen at once; the oldest is retired to make room. */
151
+ export const MAX_TOASTS = 4;
152
+
153
+ /** Which array a tier lives in. */
154
+ function storeFor(tier: NotificationTier): Notification[] {
155
+ return tier === "problem" ? problems : toasts;
156
+ }
157
+
158
+ /**
159
+ * Record an outcome and return it.
160
+ *
161
+ * The record is returned so a caller that owns the outcome's life — an operation that retries and
162
+ * succeeds — can {@link dismiss} its own problem instead of leaving a fixed thing on the list.
163
+ */
164
+ export function notify(
165
+ severity: Severity,
166
+ message: string,
167
+ options: NotifyOptions = {},
168
+ ): Notification {
169
+ const tier = options.tier ?? DEFAULT_TIER[severity];
170
+ const record: Notification = {
171
+ at: now(),
172
+ id: nextId(),
173
+ message,
174
+ severity,
175
+ tier,
176
+ ...(options.action === undefined ? {} : { action: options.action }),
177
+ ...(options.actionArgs === undefined ? {} : { actionArgs: options.actionArgs }),
178
+ ...(options.detail === undefined ? {} : { detail: options.detail }),
179
+ ...(options.source === undefined ? {} : { source: options.source }),
180
+ ...(options.path === undefined ? {} : { path: options.path }),
181
+ ...(options.key === undefined ? {} : { key: options.key }),
182
+ ...(tier === "toast" ? { timeoutMs: options.timeoutMs ?? TOAST_LIFETIME_MS[severity] } : {}),
183
+ };
184
+
185
+ const store = storeFor(tier);
186
+ if (record.key !== undefined) {
187
+ const at = store.findIndex((existing) => existing.key === record.key);
188
+ if (at !== -1) {
189
+ store.splice(at, 1);
190
+ }
191
+ }
192
+ store.push(record);
193
+ if (tier === "toast") {
194
+ while (toasts.length > MAX_TOASTS) {
195
+ toasts.shift();
196
+ }
197
+ }
198
+ /*
199
+ * Announce from HERE, not from a host.
200
+ *
201
+ * The `role="status"` region lived on the toast host, and `error` defaults to the `problem` tier —
202
+ * so a failure reached no live region at all, and a Problems-panel region would still announce
203
+ * nothing while another Bottom-dock tab was showing. One call site is what makes "posted" and
204
+ * "announced" the same event: a new host gets announcements without knowing this module exists.
205
+ *
206
+ * `source` is included because a screen-reader user has no visual grouping to tell them where the
207
+ * message came from, which is exactly what the panel's own column shows everyone else.
208
+ */
209
+ announce(
210
+ record.source === undefined ? record.message : `${record.source}: ${record.message}`,
211
+ severity === "error" ? "assertive" : "polite",
212
+ );
213
+ return record;
214
+ }
215
+
216
+ /** `notify.success(msg, opts)` and its three siblings — the shape §7.1 names. */
217
+ function severityHelper(severity: Severity) {
218
+ return (message: string, options: NotifyOptions = {}) => notify(severity, message, options);
219
+ }
220
+
221
+ notify.success = severityHelper("success");
222
+ notify.info = severityHelper("info");
223
+ notify.warn = severityHelper("warn");
224
+ notify.error = severityHelper("error");
225
+
226
+ /**
227
+ * Take one notification off whichever list holds it. Returns whether it was there.
228
+ *
229
+ * One function for both stores because a caller holding a record should not have to remember which
230
+ * tier it landed in — the record says so.
231
+ */
232
+ export function dismiss(id: string): boolean {
233
+ for (const store of [toasts, problems]) {
234
+ const at = store.findIndex((record) => record.id === id);
235
+ if (at !== -1) {
236
+ store.splice(at, 1);
237
+ return true;
238
+ }
239
+ }
240
+ return false;
241
+ }
242
+
243
+ /**
244
+ * Drop every problem a predicate accepts (all of them with no predicate). Returns how many went.
245
+ *
246
+ * The predicate form is what lets an operation clear its own previous failures when it starts again
247
+ * — `clearProblems((p) => p.source === "Save")` — so a fixed problem stops being listed without the
248
+ * fixer having kept the record's id.
249
+ */
250
+ export function clearProblems(match?: (record: Notification) => boolean): number {
251
+ const kept = match ? problems.filter((record) => !match(record)) : [];
252
+ const removed = problems.length - kept.length;
253
+ problems.splice(0, problems.length, ...kept);
254
+ return removed;
255
+ }
256
+
257
+ /** How many problems are outstanding, optionally of one severity — the badge and the status bar. */
258
+ export function problemCount(severity?: Severity): number {
259
+ return severity === undefined
260
+ ? problems.length
261
+ : problems.filter((record) => record.severity === severity).length;
262
+ }
263
+
264
+ /**
265
+ * Clear both stores. For tests and for the "close project" path — a problem is about a project, and
266
+ * carrying one across a project switch would attribute it to the wrong repository.
267
+ */
268
+ export function resetNotifications(): void {
269
+ toasts.splice(0);
270
+ problems.splice(0);
271
+ }
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Startup profiles — a named, defined cold start.
3
+ *
4
+ * "What does Studio look like to someone who has never opened it?" currently has no answer inside
5
+ * the app. Every consumer of that question reaches _around_ it: the screenshot runner injects
6
+ * `localStorage.clear()` before the first navigation, the verify recipe says "use a fresh browser
7
+ * profile", and a support thread says "try a private window". All three clear the whole ORIGIN —
8
+ * including keys Studio does not own — and none of them can be invoked from inside the running
9
+ * app.
10
+ *
11
+ * Three shipped things need it to be a real, addressable state:
12
+ *
13
+ * - **Per-project layout and session persistence** cannot be tested without a defined starting point;
14
+ * "whatever was in storage" is not one.
15
+ * - **Preferences: Reset Layout** is `applyProfile("fresh")` scoped to the layout keys, and the plan
16
+ * promises it is "always one action away".
17
+ * - **Support** wants "reproduce with a clean profile" to be a URL, not an instruction.
18
+ *
19
+ * The profile is selected by `?profile=<id>` and applied ONCE, at import time, before anything
20
+ * reads persisted state — {@link import("../shell").shell} calls this above its own `localStorage`
21
+ * read for exactly that reason. `?clock=` rides alongside because it is the other boot-time knob:
22
+ * both describe the world the app wakes up in, and neither changes behaviour after.
23
+ */
24
+
25
+ import { pinClock } from "./clock";
26
+
27
+ /** What a profile resets. */
28
+ export type ProfileReset = "none" | "app-state";
29
+
30
+ export interface StartupProfile {
31
+ id: string;
32
+ /** Shown wherever a profile is offered (Preferences, the palette). */
33
+ description: string;
34
+ reset: ProfileReset;
35
+ }
36
+
37
+ /**
38
+ * The profiles Studio declares.
39
+ *
40
+ * `default` is not a no-op by omission — it is a named state, so "the app started normally" and
41
+ * "the profile was unrecognised" are different outcomes and the second one is loud.
42
+ */
43
+ export const STARTUP_PROFILES: Readonly<Record<string, StartupProfile>> = {
44
+ default: {
45
+ description: "Resume whatever was persisted — layout, recents, settings, chat history.",
46
+ id: "default",
47
+ reset: "none",
48
+ },
49
+ fresh: {
50
+ description: "A first-run app: no layout, no recents, no settings, no history.",
51
+ id: "fresh",
52
+ reset: "app-state",
53
+ },
54
+ };
55
+
56
+ export const DEFAULT_PROFILE_ID = "default";
57
+
58
+ /**
59
+ * Studio's own persisted keys, by prefix.
60
+ *
61
+ * Scoped rather than `localStorage.clear()`: the dev server, the docs site and Studio share an
62
+ * origin, and a profile that wipes a neighbour's state is a worse bug than the one it fixes. Every
63
+ * key Studio writes begins with `jx` followed by one of these separators — the panel widths, the
64
+ * recents, the GitHub token, the AI and Cloudflare settings, the chat sessions, the update
65
+ * dismissals and the canvas debug flag.
66
+ */
67
+ const OWNED_KEY_PATTERN = /^jx[-._:]/;
68
+
69
+ /** Whether a storage key belongs to Studio and is therefore a profile's to clear. */
70
+ export function isStudioStorageKey(key: string): boolean {
71
+ return OWNED_KEY_PATTERN.test(key);
72
+ }
73
+
74
+ /**
75
+ * Drop every Studio-owned key from `localStorage`.
76
+ *
77
+ * @returns The keys removed, so a caller (Preferences, a test) can report what it did.
78
+ */
79
+ export function clearStudioStorage(): string[] {
80
+ const store = globalThis.localStorage as Storage | undefined;
81
+ if (!store) {
82
+ return [];
83
+ }
84
+ const doomed: string[] = [];
85
+ try {
86
+ for (let i = 0; i < store.length; i++) {
87
+ const key = store.key(i);
88
+ if (key && isStudioStorageKey(key)) {
89
+ doomed.push(key);
90
+ }
91
+ }
92
+ for (const key of doomed) {
93
+ store.removeItem(key);
94
+ }
95
+ } catch {
96
+ // Storage unavailable (private mode, disabled cookies) — there is nothing persisted to reset.
97
+ }
98
+ return doomed;
99
+ }
100
+
101
+ /**
102
+ * Apply a profile by id.
103
+ *
104
+ * @throws {RangeError} When the id is not declared. A misspelled profile that silently fell back to
105
+ * `default` would start the app in the state the caller was trying to avoid.
106
+ */
107
+ export function applyProfile(id: string): StartupProfile {
108
+ const profile = STARTUP_PROFILES[id];
109
+ if (!profile) {
110
+ throw new RangeError(
111
+ `unknown startup profile "${id}"; declared: ${Object.keys(STARTUP_PROFILES).join(", ")}`,
112
+ );
113
+ }
114
+ if (profile.reset === "app-state") {
115
+ clearStudioStorage();
116
+ }
117
+ return profile;
118
+ }
119
+
120
+ /** What {@link applyStartupProfile} resolved. */
121
+ export interface StartupState {
122
+ profile: StartupProfile;
123
+ /** The instant the clock was pinned to, or `null` when it follows the wall clock. */
124
+ clock: number | null;
125
+ }
126
+
127
+ let _startup: StartupState | null = null;
128
+
129
+ /**
130
+ * Read `?profile=` and `?clock=` and apply them. Idempotent — the first call wins, so importing
131
+ * this from a second module cannot re-clear storage the app has already written to.
132
+ *
133
+ * @param search The query string to read. Defaults to the live location, and is passed explicitly
134
+ * by the tests, which have no navigation.
135
+ */
136
+ export function applyStartupProfile(search?: string): StartupState {
137
+ if (_startup) {
138
+ return _startup;
139
+ }
140
+ const query = new URLSearchParams(search ?? globalThis.location?.search ?? "");
141
+ const profile = applyProfile(query.get("profile") ?? DEFAULT_PROFILE_ID);
142
+ const clockParam = query.get("clock");
143
+ _startup = { clock: clockParam === null ? null : pinClock(clockParam), profile };
144
+ return _startup;
145
+ }
146
+
147
+ /** What the app started as. `null` until {@link applyStartupProfile} has run. */
148
+ export function startupState(): StartupState | null {
149
+ return _startup;
150
+ }
151
+
152
+ /** Forget the resolved startup state, so the next call re-reads the query. Tests only. */
153
+ export function resetStartupProfile(): void {
154
+ _startup = null;
155
+ }
Binary file
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import type { JxMutableNode } from "@jxsuite/schema/types";
9
+ import { displayTagName } from "@jxsuite/schema/guards";
9
10
 
10
11
  interface TokenLintFinding {
11
12
  path: string;
@@ -112,14 +113,14 @@ export function flagHardcodedTokens(
112
113
  for (let i = 0; i < el.children.length; i++) {
113
114
  const child = el.children[i];
114
115
  if (child && typeof child === "object") {
115
- walk(child, `${pathPrefix} > ${child.tagName || `[${i}]`}`);
116
+ walk(child, `${pathPrefix} > ${displayTagName(child.tagName) || `[${i}]`}`);
116
117
  }
117
118
  }
118
119
  }
119
120
  }
120
121
 
121
122
  const tag = doc.tagName || "root";
122
- walk(doc, tag);
123
+ walk(doc, displayTagName(tag));
123
124
  return findings;
124
125
  }
125
126
 
@@ -5,6 +5,19 @@
5
5
  * `transactDoc()`), feeds the results back as `tool` messages, and re-streams — up to a capped
6
6
  * number of rounds (spec §10.2, ADR docs/ai-assistant-decision.md §6a).
7
7
  *
8
+ * Two §7.4 honesty rules live here rather than in the panel, because they are properties of the RUN
9
+ * and a panel can only render what the run recorded:
10
+ *
11
+ * - **A partial success is not a failure.** Running out of rounds called `setError`, which paints
12
+ * the turn red and — in `chat-state.ts` — DELETES the streaming message, so a turn that applied
13
+ * four edits and then hit the cap reported as an error that had also erased its own account of
14
+ * the four edits. The cap is now an ordinary assistant message whenever anything was applied, and
15
+ * an error only when nothing was.
16
+ * - **A batch belongs to the tab it edits.** `beginBatch(getTab())` ran once, against whichever tab
17
+ * happened to be active when the loop started. A turn that then moved to a second document closed
18
+ * its batch against the FIRST tab, so the second document's edits got neither a history snapshot
19
+ * nor a collab publish. The loop re-anchors the batch whenever the active tab changes under it.
20
+ *
8
21
  * @license MIT
9
22
  */
10
23
 
@@ -13,7 +26,8 @@ import type { StreamingClient } from "@jxsuite/ai/streaming-client";
13
26
  import type { ToolRegistry } from "@jxsuite/ai/tools";
14
27
 
15
28
  import type { Tab } from "../tabs/tab";
16
- import { beginBatch, endBatch } from "../tabs/transact";
29
+ import { batchTab, beginBatch, endBatch } from "../tabs/transact";
30
+ import { beginTurn, endTurn } from "./ai-writes";
17
31
 
18
32
  const MAX_ROUNDS = 5;
19
33
 
@@ -41,11 +55,33 @@ export async function runAgentLoop({
41
55
  const allErrors: string[] = [];
42
56
  const appliedSummaries: string[] = [];
43
57
 
44
- // Batch all tool-call mutations into a single undo step
58
+ /** The ledger is filed under the assistant message the turn ends on — see services/ai-writes. */
59
+ const turnId = () => chatState.messages.at(-1)?.id ?? "";
60
+ beginTurn(`turn:${chatState.messages.length}`);
61
+
62
+ // Batch all tool-call mutations into a single undo step, anchored on the tab being edited.
45
63
  if (getTab) {
46
64
  beginBatch(getTab());
47
65
  }
48
66
 
67
+ /**
68
+ * Close the batch and re-open it when the tools have moved to a different document.
69
+ *
70
+ * Runs after every tool execution rather than only inside `open_document`, because a tool is not
71
+ * the only thing that can change the active tab: project adoption replaces every tab in the
72
+ * workspace, and the user is free to click another tab while the model is still streaming.
73
+ */
74
+ const reanchorBatch = () => {
75
+ if (!getTab) {
76
+ return;
77
+ }
78
+ const current = getTab();
79
+ if (current !== batchTab()) {
80
+ endBatch();
81
+ beginBatch(current);
82
+ }
83
+ };
84
+
49
85
  try {
50
86
  for (let round = 1; round <= MAX_ROUNDS; round++) {
51
87
  const messages = chatState.toMessagesArray();
@@ -119,6 +155,7 @@ export async function runAgentLoop({
119
155
  error: `Failed to parse arguments: ${(error as Error).message}`,
120
156
  };
121
157
  }
158
+ reanchorBatch();
122
159
  if (!result.success && result.error) {
123
160
  allErrors.push(result.error);
124
161
  }
@@ -135,8 +172,9 @@ export async function runAgentLoop({
135
172
  }
136
173
 
137
174
  /*
138
- * Surface the actual errors so the user knows what went wrong, not just a generic
139
- * "I couldn't do it" message.
175
+ * The round cap. Surface the actual errors so the user knows what went wrong, not just a
176
+ * generic "I couldn't do it" — and, when anything was applied, say it as the ASSISTANT rather
177
+ * than as a failure (§7.4). A partial success is not a failure.
140
178
  */
141
179
  const uniqueErrors = [...new Set(allErrors)];
142
180
  const applied =
@@ -147,10 +185,18 @@ export async function runAgentLoop({
147
185
  uniqueErrors.length > 0
148
186
  ? `\n\nErrors encountered:\n${uniqueErrors.map((e) => `- ${e}`).join("\n")}`
149
187
  : "";
150
- chatState.setError(
151
- `I ran out of tool-call rounds (${MAX_ROUNDS}) before finishing.${applied}${errors}\n\nYou can continue by sending another message, or try a more specific request.`,
152
- );
188
+ const tail =
189
+ `I ran out of tool-call rounds (${MAX_ROUNDS}) before finishing.${applied}${errors}` +
190
+ `\n\nYou can continue by sending another message, or try a more specific request.`;
191
+ if (appliedSummaries.length > 0) {
192
+ chatState.beginAssistantTurn();
193
+ chatState.appendDelta(tail);
194
+ chatState.finishStream("length");
195
+ return;
196
+ }
197
+ chatState.setError(tail);
153
198
  } finally {
154
199
  endBatch();
200
+ endTurn(turnId());
155
201
  }
156
202
  }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * The one place a string becomes markup in the Studio shell — Trusted Types (W3C).
3
+ *
4
+ * **What the standard actually gates, verified rather than assumed.** The tempting reading is that
5
+ * `require-trusted-types-for 'script'` governs DOM injection sinks and leaves `eval` to
6
+ * `script-src`. That is wrong: under Trusted Types, `eval()` and `new Function()` are gated too,
7
+ * and throw when no default policy exists. Getting that backwards produces a plan that cannot be
8
+ * executed — which is why the shell does **not** enforce Trusted Types yet, and why this module
9
+ * ships the policy first (see `spec.md` §21.5).
10
+ *
11
+ * **This policy refuses to be a rubber stamp.** A `createHTML` that returns its input unchanged
12
+ * satisfies the API and defends nothing; it converts a real control into a type-level ceremony. So
13
+ * `createHTML` asserts, and throws on anything that looks like an injection — the value is meant to
14
+ * have come from `markdownToHtml`, which already drops raw HTML and `javascript:` URLs, and this is
15
+ * the check that says so out loud rather than trusting a call site to have remembered.
16
+ *
17
+ * **`createScript` and `createScriptURL` throw.** Nothing in the shell builds either from a string.
18
+ * The canvas interpreter does evaluate expressions, but it runs in the canvas iframe under its own
19
+ * policy — a permissive `createScript` here would re-permit evaluation for the whole shell to buy
20
+ * something the shell does not use.
21
+ *
22
+ * @docs studio/interface/problems-and-progress
23
+ */
24
+
25
+ /** The subset of the Trusted Types API this module uses — typed here so no DOM lib is required. */
26
+ interface TrustedTypesApi {
27
+ createPolicy: (
28
+ name: string,
29
+ rules: {
30
+ createHTML?: (input: string) => string;
31
+ createScript?: (input: string) => string;
32
+ createScriptURL?: (input: string) => string;
33
+ },
34
+ ) => { createHTML: (input: string) => unknown };
35
+ }
36
+
37
+ /** The policy name, which a CSP `trusted-types` directive would have to allow. */
38
+ export const STUDIO_POLICY_NAME = "jx-studio";
39
+
40
+ /**
41
+ * Patterns that must never survive sanitization.
42
+ *
43
+ * Deliberately a small, explicit list rather than a parser: this is an **assertion** over output
44
+ * that is already sanitized, not a sanitizer of its own. A second sanitizer here would be a second
45
+ * thing to keep correct, and the failure mode of a wrong one is a false sense of safety.
46
+ */
47
+ const FORBIDDEN: readonly { pattern: RegExp; what: string }[] = [
48
+ { pattern: /<\s*script\b/i, what: "a <script> element" },
49
+ { pattern: /<\s*iframe\b/i, what: "an <iframe> element" },
50
+ { pattern: /<\s*object\b/i, what: "an <object> element" },
51
+ { pattern: /<\s*embed\b/i, what: "an <embed> element" },
52
+ { pattern: /\son[a-z]+\s*=/i, what: "an inline event handler attribute" },
53
+ { pattern: /javascript:/i, what: "a javascript: URL" },
54
+ { pattern: /data:text\/html/i, what: "a data: URL carrying HTML" },
55
+ ];
56
+
57
+ /**
58
+ * Assert that a string is markup this app is willing to inject.
59
+ *
60
+ * @param {string} value
61
+ * @returns {string} The same string, when it passes.
62
+ * @throws {Error} Naming what it found, so the failure is diagnosable rather than a blank refusal.
63
+ */
64
+ export function assertSanitized(value: string): string {
65
+ for (const { pattern, what } of FORBIDDEN) {
66
+ if (pattern.test(value)) {
67
+ throw new Error(
68
+ `Refusing to inject markup containing ${what}. This value should have come from ` +
69
+ "markdownToHtml, which removes it — a value that reaches here with it still present " +
70
+ "means the sanitizer was bypassed, not that this check is too strict.",
71
+ );
72
+ }
73
+ }
74
+ return value;
75
+ }
76
+
77
+ /** Created once. A second `createPolicy` with the same name throws when policies are restricted. */
78
+ let policy: { createHTML: (input: string) => unknown } | null = null;
79
+
80
+ /**
81
+ * The shell's Trusted Types policy, or null where the API does not exist.
82
+ *
83
+ * @returns {{ createHTML: (input: string) => unknown } | null}
84
+ */
85
+ export function studioPolicy(): { createHTML: (input: string) => unknown } | null {
86
+ if (policy) {
87
+ return policy;
88
+ }
89
+ const api = (globalThis as unknown as { trustedTypes?: TrustedTypesApi }).trustedTypes;
90
+ if (!api) {
91
+ return null;
92
+ }
93
+ policy = api.createPolicy(STUDIO_POLICY_NAME, {
94
+ createHTML: assertSanitized,
95
+ createScript: () => {
96
+ throw new Error("The Studio shell does not build scripts from strings.");
97
+ },
98
+ createScriptURL: () => {
99
+ throw new Error("The Studio shell does not build script URLs from strings.");
100
+ },
101
+ });
102
+ return policy;
103
+ }
104
+
105
+ /**
106
+ * Turn already-sanitized markup into something an injection sink accepts.
107
+ *
108
+ * Returns a `TrustedHTML` where the API exists and the plain string where it does not — and in
109
+ * **both** cases the assertion has run, so the check is not contingent on a browser feature. A
110
+ * guard that only fires under Trusted Types would be a guard that never fires in the tests.
111
+ *
112
+ * @param {string} value Markup from `markdownToHtml`.
113
+ * @returns {unknown} A `TrustedHTML`, or the asserted string.
114
+ */
115
+ export function trustedHtml(value: string): unknown {
116
+ const active = studioPolicy();
117
+ return active ? active.createHTML(value) : assertSanitized(value);
118
+ }
119
+
120
+ /** Forget the memoized policy. Tests only — each one installs its own `trustedTypes` stub. */
121
+ export function resetStudioPolicy(): void {
122
+ policy = null;
123
+ }