@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
@@ -1,7 +1,14 @@
1
1
  /// <reference lib="dom" />
2
2
  import { toRaw } from "../reactivity";
3
3
  import { jsonClone } from "../utils/studio-utils";
4
- import { childIndex, getNodeAtPath, isAncestor, parentElementPath, pathsEqual } from "../state";
4
+ import { childIndex, getNodeAtPath, parentElementPath, pathsEqual } from "../state";
5
+ import {
6
+ cloneSelection,
7
+ isSpliceablePath,
8
+ primarySelection,
9
+ pruneSelection,
10
+ structuralBatch,
11
+ } from "./selection";
5
12
  import { applyDocOpToDoc, childArray, cloneValue } from "./doc-op-apply";
6
13
  import {
7
14
  beginRecording,
@@ -125,13 +132,68 @@ export function setTransactGate(fn: TransactGate | null) {
125
132
  _transactGate = fn;
126
133
  }
127
134
 
135
+ /**
136
+ * Undo a transaction that threw part-way through, using the pairs it managed to record.
137
+ *
138
+ * **Why the inverses are enough.** Every mutator records its op AFTER its splice or assignment
139
+ * succeeds, so `record.docOps` describes exactly the changes that landed — no more, no less.
140
+ * Replaying their inverses in reverse is the same replay `undo` performs on a committed entry, run
141
+ * against a document that never got a history entry. A mutator that changed the document WITHOUT
142
+ * recording is already broken for undo; this is no weaker a guarantee than the one history gives.
143
+ *
144
+ * The document root reference is replaced so every effect re-reads, and nothing is marked consumed
145
+ * — the canvas takes the full-render path, because the patch ops recorded for the half-applied
146
+ * state describe changes that no longer exist.
147
+ */
148
+ function rollbackFailedTransaction(
149
+ tab: Tab,
150
+ record: TransactionRecord,
151
+ selectionBefore: JxPath[],
152
+ dirtyBefore: boolean,
153
+ ) {
154
+ for (let i = record.docOps.length - 1; i >= 0; i--) {
155
+ applyDocOpToDoc(tab.doc.document, toRaw(record.docOps[i]!.inverse) as JxDocOp);
156
+ }
157
+ for (let i = record.fmOps.length - 1; i >= 0; i--) {
158
+ const op = record.fmOps[i]!;
159
+ applyFmState(tab, op.field, op.before);
160
+ }
161
+ tab.doc.document = { ...toRaw(tab.doc.document) };
162
+ // Mutators repair the selection as they go (`pruneSelection`, "select the clone"); rolling their
163
+ // Edits back and leaving the selection pointing at coordinates that no longer exist would be its
164
+ // Own corruption.
165
+ tab.session.selection = selectionBefore;
166
+ // `mutateUpdateFrontmatter` marks the tab dirty itself, mid-mutation. Rolling its write back and
167
+ // Leaving the mark standing would report unsaved changes that no longer exist; restoring the
168
+ // Captured value is right in both directions, since nothing but this transaction could have
169
+ // Moved it.
170
+ tab.doc.dirty = dirtyBefore;
171
+ }
172
+
128
173
  /**
129
174
  * Apply a document mutation transactionally: push to history and mark dirty. The mutationFn
130
175
  * receives the tab and should mutate tab.doc.document in place.
131
176
  *
177
+ * **All of it or none of it.** A mutationFn that throws part-way — a batch whose third splice hits
178
+ * a coordinate that is not one — used to leave the earlier splices standing in the live reactive
179
+ * tree while the throw skipped the new document reference, the history entry, the dirty mark and
180
+ * the collab observer: the document was changed on screen, unreachable by undo, and invisible to
181
+ * Save. The exception now rolls the applied ops back ({@link rollbackFailedTransaction}) and only
182
+ * then rethrows, so a failed transaction is indistinguishable from one that never ran.
183
+ *
184
+ * **AND IT SAYS WHETHER IT WROTE**, because {@link setTransactGate} made "returned normally" and
185
+ * "changed the document" two different facts and nothing downstream could tell them apart. Under
186
+ * the source-canonical freeze the dock's body commit is refused here — and its caller went on to
187
+ * mark the buffer settled, so `tabBufferUnsaved`, `shouldWarnOnClose` and `hasUnsavedTabs` all
188
+ * reported "nothing to lose" about text that only existed in the buffer. Every close then took it
189
+ * with no prompt. A refusal is not an exception (it is an ordinary, expected state of the room), so
190
+ * it is a return value: `false` means the document is exactly as it was.
191
+ *
132
192
  * @param {Tab | null} tab
133
193
  * @param {(tab: Tab) => void} mutationFn
134
194
  * @param {{ skipHistory?: boolean; origin?: TransactOrigin }} [opts]
195
+ * @returns {boolean} True when the mutation was applied; false when there was no tab, or the gate
196
+ * refused it.
135
197
  */
136
198
  export function transactDoc(
137
199
  tab: Tab | null,
@@ -141,21 +203,31 @@ export function transactDoc(
141
203
  origin = "user",
142
204
  coalesceKey = null,
143
205
  }: { skipHistory?: boolean; origin?: TransactOrigin; coalesceKey?: string | null } = {},
144
- ) {
206
+ ): boolean {
145
207
  if (!tab) {
146
- return;
208
+ return false;
147
209
  }
148
210
  if (origin !== "remote" && _transactGate?.(tab, origin)) {
149
- return;
211
+ return false;
150
212
  }
151
- const selectionBefore = tab.session.selection ? [...tab.session.selection] : null;
213
+ const selectionBefore = cloneSelection(tab.session.selection);
214
+ const dirtyBefore = tab.doc.dirty;
152
215
  beginRecording();
153
216
  let record: TransactionRecord;
217
+ let failure: { error: unknown } | null = null;
154
218
  try {
155
219
  mutationFn(tab);
220
+ } catch (error) {
221
+ // Boxed rather than tested for truthiness: a mutation is free to throw a falsy value, and
222
+ // "did it throw" must not become "was what it threw interesting".
223
+ failure = { error };
156
224
  } finally {
157
225
  record = endRecording();
158
226
  }
227
+ if (failure) {
228
+ rollbackFailedTransaction(tab, record, selectionBefore, dirtyBefore);
229
+ throw failure.error;
230
+ }
159
231
 
160
232
  // Replace the document root reference so effects tracking tab.doc.document re-trigger.
161
233
  // Nested objects are shared — the mutation already modified them in place.
@@ -174,7 +246,7 @@ export function transactDoc(
174
246
  ? { patchable: false, reason: "unrecorded-mutation" }
175
247
  : consumer.classify(tab, record.ops);
176
248
  if (verdict.patchable) {
177
- consumer!.markConsumed(newRef);
249
+ consumer!.markConsumed(newRef, tab);
178
250
  }
179
251
 
180
252
  tab.doc.document = newRef;
@@ -185,6 +257,7 @@ export function transactDoc(
185
257
  } catch (error) {
186
258
  consumer!.escalate(
187
259
  `patch-apply-failed: ${error instanceof Error ? error.message : String(error)}`,
260
+ tab,
188
261
  );
189
262
  }
190
263
  }
@@ -196,6 +269,7 @@ export function transactDoc(
196
269
  tab.doc.dirty = true;
197
270
 
198
271
  _transactObserver?.(tab, record, origin);
272
+ return true;
199
273
  }
200
274
 
201
275
  /**
@@ -207,13 +281,13 @@ export function transactDoc(
207
281
  * @param {Tab} tab
208
282
  * @param {JxMutableNode} raw Post-mutation raw document
209
283
  * @param {TransactionRecord} record
210
- * @param {JxPath | null} selectionBefore
284
+ * @param {JxPath[]} selectionBefore
211
285
  */
212
286
  function pushHistoryEntry(
213
287
  tab: Tab,
214
288
  raw: JxMutableNode,
215
289
  record: TransactionRecord,
216
- selectionBefore: JxPath | null,
290
+ selectionBefore: JxPath[],
217
291
  coalesceKey: string | null = null,
218
292
  ) {
219
293
  const truncated = tab.history.snapshots.slice(0, tab.history.index + 1);
@@ -234,7 +308,7 @@ function pushHistoryEntry(
234
308
  // Leaving the node holding both keys at once. Undo replays inverses in reverse order, so
235
309
  // Appending yields "undo the last commit, then the one before it, …".
236
310
  prev.inverseOps = [...prev.inverseOps, ...record.docOps.map((pair) => pair.inverse)];
237
- prev.selection = tab.session.selection ? [...tab.session.selection] : null;
311
+ prev.selection = cloneSelection(tab.session.selection);
238
312
  if (prev.document) {
239
313
  prev.document = jsonClone(raw);
240
314
  }
@@ -250,7 +324,7 @@ function pushHistoryEntry(
250
324
  forwardOps: useOps ? record.docOps.map((p) => p.forward) : null,
251
325
  inverseOps: useOps ? record.docOps.map((p) => p.inverse) : null,
252
326
  coalesceKey,
253
- selection: tab.session.selection ? [...tab.session.selection] : null,
327
+ selection: cloneSelection(tab.session.selection),
254
328
  selectionBefore,
255
329
  });
256
330
  if (truncated.length > HISTORY_LIMIT) {
@@ -299,13 +373,14 @@ function materializeState(
299
373
  * @param {Tab | null} tab
300
374
  * @param {(doc: JxMutableNode) => void} fn
301
375
  * @param {{ skipHistory?: boolean; origin?: TransactOrigin }} [opts]
376
+ * @returns {boolean} Whatever {@link transactDoc} answered — whether the mutation was applied.
302
377
  */
303
378
  export function transact(
304
379
  tab: Tab | null,
305
380
  fn: (doc: JxMutableNode) => void,
306
381
  opts?: { skipHistory?: boolean; origin?: TransactOrigin },
307
- ) {
308
- transactDoc(tab, (t) => fn(t.doc.document), opts);
382
+ ): boolean {
383
+ return transactDoc(tab, (t) => fn(t.doc.document), opts);
309
384
  }
310
385
 
311
386
  /**
@@ -424,7 +499,7 @@ export function endBatch() {
424
499
  const raw = toRaw(tab.doc.document);
425
500
  const snapshot = {
426
501
  document: jsonClone(raw),
427
- selection: tab.session.selection ? [...tab.session.selection] : null,
502
+ selection: cloneSelection(tab.session.selection),
428
503
  };
429
504
  const truncated = tab.history.snapshots.slice(0, tab.history.index + 1);
430
505
  truncated.push(snapshot);
@@ -444,6 +519,19 @@ export function isBatching(): boolean {
444
519
  return _batchTab !== null;
445
520
  }
446
521
 
522
+ /**
523
+ * Which tab the open batch belongs to, or `null` when none is open.
524
+ *
525
+ * The agent loop needs this to keep a batch honest across a tab change. A batch is opened against
526
+ * one tab and closed against the same one — {@link endBatch} pushes ITS history snapshot and fires
527
+ * the collab notifier for IT — so a loop whose tools moved to a second document has to close the
528
+ * first batch and open a second, or the second document's edits get neither a history entry nor a
529
+ * collab publish. Reading the batched tab is how the loop can tell.
530
+ */
531
+ export function batchTab(): Tab | null {
532
+ return _batchTab;
533
+ }
534
+
447
535
  // ─── Undo / Redo ─────────────────────────────────────────────────────────────
448
536
 
449
537
  /**
@@ -487,7 +575,7 @@ function restoreState(tab: Tab, index: number) {
487
575
  const { snapshots } = tab.history;
488
576
  tab.doc.document = materializeState(snapshots, index);
489
577
  const snap = snapshots[index]!;
490
- tab.session.selection = snap.selection ? [...toRaw(snap.selection)] : null;
578
+ tab.session.selection = cloneSelection(toRaw(snap.selection));
491
579
  }
492
580
 
493
581
  /** Debug-flag assertion: ops-based undo/redo must land on the same state replay produces. */
@@ -524,7 +612,7 @@ export function undo(tab: Tab) {
524
612
  if ((inverseOps && inverseOps.length > 0) || (fmOps && fmOps.length > 0)) {
525
613
  // Surgical path: apply inverse ops through the normal transaction pipeline (the canvas
526
614
  // Patches in place when it can), in reverse recording order.
527
- transactDoc(
615
+ const applied = transactDoc(
528
616
  tab,
529
617
  (t) => {
530
618
  if (inverseOps) {
@@ -541,7 +629,15 @@ export function undo(tab: Tab) {
541
629
  },
542
630
  { origin: "history", skipHistory: true },
543
631
  );
544
- tab.session.selection = entry.selectionBefore ? [...toRaw(entry.selectionBefore)] : null;
632
+ /* A REFUSED REPLAY IS NOT A STEP TAKEN. `"history"` is not `"remote"`, so the gate refuses this
633
+ exactly as it refuses a direct edit — and everything below assumed the ops landed. Moving the
634
+ index over a document that did not change desynchronises the log from the tree: the next redo
635
+ replays a forward op onto a state it was never the inverse of, and `doc.dirty` claims an edit
636
+ nobody made. Undo that cannot run is undo that does nothing, which is the honest answer. */
637
+ if (!applied) {
638
+ return;
639
+ }
640
+ tab.session.selection = cloneSelection(toRaw(entry.selectionBefore ?? []));
545
641
  tab.history.index -= 1;
546
642
  assertHistoryConsistency(tab, tab.history.index);
547
643
  } else {
@@ -565,7 +661,7 @@ export function redo(tab: Tab) {
565
661
  const forwardOps = patchHistoryEnabled() ? entry.forwardOps : null;
566
662
  const fmOps = patchHistoryEnabled() ? entry.fmOps : null;
567
663
  if ((forwardOps && forwardOps.length > 0) || (fmOps && fmOps.length > 0)) {
568
- transactDoc(
664
+ const applied = transactDoc(
569
665
  tab,
570
666
  (t) => {
571
667
  if (forwardOps) {
@@ -582,7 +678,12 @@ export function redo(tab: Tab) {
582
678
  },
583
679
  { origin: "history", skipHistory: true },
584
680
  );
585
- tab.session.selection = entry.selection ? [...toRaw(entry.selection)] : null;
681
+ // Same as {@link undo}: a refused replay leaves the document alone, so the index must stay
682
+ // Where it is or the log stops describing the tree.
683
+ if (!applied) {
684
+ return;
685
+ }
686
+ tab.session.selection = cloneSelection(toRaw(entry.selection));
586
687
  tab.history.index += 1;
587
688
  assertHistoryConsistency(tab, tab.history.index);
588
689
  } else {
@@ -619,11 +720,18 @@ export function mutateInsertNode(
619
720
  }
620
721
 
621
722
  /**
723
+ * Remove the node at `path` from its parent's children array.
724
+ *
725
+ * The guard is {@link isSpliceablePath}, not `path.length < 2`: the shorter test admits the
726
+ * Outline's map-template and `$switch`-case rows, whose `parentElementPath` is a children ARRAY
727
+ * rather than a node — `.children.splice` on it throws, and the throw is what left a batch's
728
+ * earlier removals applied and unrecorded.
729
+ *
622
730
  * @param {Tab} tab
623
731
  * @param {JxPath} path
624
732
  */
625
733
  export function mutateRemoveNode(tab: Tab, path: JxPath) {
626
- if (!path || path.length < 2) {
734
+ if (!path || !isSpliceablePath(path)) {
627
735
  return;
628
736
  }
629
737
  const elemPath = parentElementPath(path) as JxPath;
@@ -637,9 +745,7 @@ export function mutateRemoveNode(tab: Tab, path: JxPath) {
637
745
  forward: { index: idx, op: "remove-child", parentPath: elemPath },
638
746
  inverse: { index: idx, node: cloneValue(removed), op: "insert-child", parentPath: elemPath },
639
747
  });
640
- if (tab.session.selection && isAncestor(path, tab.session.selection)) {
641
- tab.session.selection = null;
642
- }
748
+ tab.session.selection = pruneSelection(tab.session.selection, path);
643
749
  }
644
750
 
645
751
  /**
@@ -647,7 +753,7 @@ export function mutateRemoveNode(tab: Tab, path: JxPath) {
647
753
  * @param {JxPath} path
648
754
  */
649
755
  export function mutateDuplicateNode(tab: Tab, path: JxPath) {
650
- if (!path || path.length < 2) {
756
+ if (!path || !isSpliceablePath(path)) {
651
757
  return;
652
758
  }
653
759
  const node = getNodeAtPath(tab.doc.document, path);
@@ -663,7 +769,66 @@ export function mutateDuplicateNode(tab: Tab, path: JxPath) {
663
769
  forward: { index: idx + 1, node: cloneValue(clone), op: "insert-child", parentPath: elemPath },
664
770
  inverse: { index: idx + 1, op: "remove-child", parentPath: elemPath },
665
771
  });
666
- tab.session.selection = [...elemPath, "children", idx + 1];
772
+ tab.session.selection = [[...elemPath, "children", idx + 1]];
773
+ }
774
+
775
+ /**
776
+ * Remove every selected node, in ONE transaction and therefore ONE undo step (§6.5).
777
+ *
778
+ * Three things make a batch different from a loop. `structuralBatch` drops paths contained by
779
+ * another selected path — deleting a `<section>` and a paragraph inside it is one deletion — drops
780
+ * paths that name no splice coordinate at all (the document element, a repeater's map template, a
781
+ * `$switch` case, each of which the Outline offers as a ctrl-clickable row), and orders what
782
+ * survives LAST-node-first, so each splice happens above coordinates no later step depends on. A
783
+ * forward loop would delete `children/0` and then `children/1`, which by then is the node that used
784
+ * to be `children/2`.
785
+ *
786
+ * Selection repair is `mutateRemoveNode`'s, applied once per removal: a path is dropped when the
787
+ * node it addresses has gone. With a single path in, that is exactly the old behaviour — the
788
+ * selection ends up empty.
789
+ *
790
+ * @param {Tab} tab
791
+ * @param {readonly JxPath[]} paths
792
+ */
793
+ export function mutateRemoveNodes(tab: Tab, paths: readonly JxPath[]) {
794
+ for (const path of structuralBatch(paths)) {
795
+ mutateRemoveNode(tab, path);
796
+ }
797
+ }
798
+
799
+ /**
800
+ * Duplicate every selected node in one transaction, selecting the clones.
801
+ *
802
+ * Same descending order, for the same reason: inserting a clone at `idx + 1` renumbers every later
803
+ * sibling, so the batch starts at the last. The resulting selection is the clones in document
804
+ * order, which means a duplicate-then-duplicate-again walks down the page the way repeating the
805
+ * single-node command always did.
806
+ *
807
+ * @param {Tab} tab
808
+ * @param {readonly JxPath[]} paths
809
+ */
810
+ export function mutateDuplicateNodes(tab: Tab, paths: readonly JxPath[]) {
811
+ let clones: JxPath[] = [];
812
+ for (const path of structuralBatch(paths)) {
813
+ const before = tab.session.selection;
814
+ mutateDuplicateNode(tab, path);
815
+ const made = primarySelection(tab.session.selection);
816
+ // `mutateDuplicateNode` refuses a path that addresses nothing and leaves the selection alone;
817
+ // Only a real insertion contributes a clone.
818
+ if (!made || tab.session.selection === before) {
819
+ continue;
820
+ }
821
+ // Descending order keeps the ORIGINALS valid, but not the clones already made: this insertion
822
+ // Sits before them, so every one of their coordinates moved up by one. Translating them here
823
+ // Is the difference between "the copies are selected" and "two of the originals are".
824
+ const parentPath = parentElementPath(made) as JxPath;
825
+ const spliceIndex = childIndex(made) as number;
826
+ clones = clones.map((clone) => shiftPrefixedIndex(parentPath, clone, spliceIndex, 1, true));
827
+ clones.unshift(made);
828
+ }
829
+ if (clones.length > 0) {
830
+ tab.session.selection = clones;
831
+ }
667
832
  }
668
833
 
669
834
  /**
@@ -672,7 +837,7 @@ export function mutateDuplicateNode(tab: Tab, path: JxPath) {
672
837
  * @param {string} wrapperTag
673
838
  */
674
839
  export function mutateWrapNode(tab: Tab, path: JxPath, wrapperTag = "div") {
675
- if (!path || path.length < 2) {
840
+ if (!path || !isSpliceablePath(path)) {
676
841
  return;
677
842
  }
678
843
  const node = getNodeAtPath(tab.doc.document, path);
@@ -692,7 +857,7 @@ export function mutateWrapNode(tab: Tab, path: JxPath, wrapperTag = "div") {
692
857
  forward: { index: idx, node: cloneValue(wrapper), op: "set-child", parentPath: elemPath },
693
858
  inverse: { index: idx, node: original, op: "set-child", parentPath: elemPath },
694
859
  });
695
- tab.session.selection = [...elemPath, "children", idx];
860
+ tab.session.selection = [[...elemPath, "children", idx]];
696
861
  }
697
862
 
698
863
  /**
@@ -743,7 +908,7 @@ export function mutateMoveNode(tab: Tab, fromPath: JxPath, toParentPath: JxPath,
743
908
  },
744
909
  });
745
910
 
746
- if (pathsEqual(tab.session.selection, fromPath)) {
911
+ if (pathsEqual(primarySelection(tab.session.selection), fromPath)) {
747
912
  let idx = toIndex;
748
913
  if (
749
914
  fromParentPath.length === toParentPath.length &&
@@ -752,11 +917,24 @@ export function mutateMoveNode(tab: Tab, fromPath: JxPath, toParentPath: JxPath,
752
917
  ) {
753
918
  idx = toIndex - 1;
754
919
  }
755
- tab.session.selection = [...toParentPath, "children", idx];
920
+ tab.session.selection = [[...toParentPath, "children", idx]];
756
921
  }
757
922
  }
758
923
 
759
924
  /**
925
+ * Set (or, for an empty value, delete) one property on the node `path` names.
926
+ *
927
+ * **A path that no longer resolves is a no-op, not a throw.** `getNodeAtPath` answers `undefined`
928
+ * for a coordinate that has been deleted, and `node[key]` on that is `undefined is not an object` —
929
+ * an exception thrown from inside a mutation, which {@link transactDoc} correctly rolls back and
930
+ * rethrows into whatever asked for the edit. For a panel that is one stale click; for the dock's
931
+ * debounced body commit it escaped through `commitBufferWrites` and out of the dock's
932
+ * `afterRender`, aborting the repaint with the editor undisposed and its 500ms timer still armed.
933
+ * Deletions arrive from collaborators and from the author's own undo while an editor is open, so a
934
+ * stale path is an ordinary state of the document rather than a programming error, and the ordinary
935
+ * answer is to change nothing. Callers that must distinguish "wrote" from "nothing to write to"
936
+ * resolve the node themselves first — `panels/editors.ts`'s `bodyWriter` does exactly that.
937
+ *
760
938
  * @param {Tab} tab
761
939
  * @param {JxPath} path
762
940
  * @param {string} key
@@ -764,6 +942,9 @@ export function mutateMoveNode(tab: Tab, fromPath: JxPath, toParentPath: JxPath,
764
942
  */
765
943
  export function mutateUpdateProperty(tab: Tab, path: JxPath, key: string, value?: JxNodeValue) {
766
944
  const node = getNodeAtPath(tab.doc.document, path);
945
+ if (!node) {
946
+ return;
947
+ }
767
948
  const prev = node[key];
768
949
  const before = cloneValue(prev);
769
950
  if (value === undefined || value === null || value === "") {
package/src/types.ts CHANGED
@@ -38,6 +38,7 @@ import type {
38
38
  ProjectListEntry,
39
39
  ProjectSchemasResponse,
40
40
  RecentProjectEntry,
41
+ ReferencesResult,
41
42
  RenameResult,
42
43
  SecretsSetRequest,
43
44
  SecretsSetResponse,
@@ -85,6 +86,9 @@ export type {
85
86
  ProjectSchemasResponse,
86
87
  PullRequestInfo,
87
88
  RecentProjectEntry,
89
+ ReferenceFile,
90
+ ReferenceHit,
91
+ ReferencesResult,
88
92
  RenameResult,
89
93
  SecretsListResponse,
90
94
  SecretsSetRequest,
@@ -130,6 +134,28 @@ export type CreateProjectDestination =
130
134
  | { kind: "path"; parent: string }
131
135
  | { kind: "repo"; owner: string; repo: string; private: boolean };
132
136
 
137
+ /**
138
+ * What a site build reports back.
139
+ *
140
+ * `errors` is a list rather than a thrown exception because a partial build still produced pages:
141
+ * the author is better served by opening the page they asked for with the failures named beside it
142
+ * than by a refusal that says only that something went wrong.
143
+ */
144
+ export interface SiteBuildResult {
145
+ routes: number;
146
+ files: number;
147
+ errors: string[];
148
+ /**
149
+ * Origin the built site is browsable at, e.g. `http://127.0.0.1:41234`.
150
+ *
151
+ * The backend names it because only the backend knows it: the built site is served on a port of
152
+ * its own, not on the editor's, since the editor's paths mean the project's SOURCES and a built
153
+ * page means its own output by the very same paths. Absent when the backend serves no preview,
154
+ * and `View: Open in Browser` then says so rather than guessing an origin.
155
+ */
156
+ url?: string;
157
+ }
158
+
133
159
  export interface StudioPlatform {
134
160
  id: string;
135
161
  projectRoot: string;
@@ -167,6 +193,20 @@ export interface StudioPlatform {
167
193
  uploadFile: (path: string, data: string | File | Blob | ArrayBuffer) => Promise<unknown>;
168
194
  deleteFile: (path: string) => Promise<void>;
169
195
  renameFile: (from: string, to: string) => Promise<RenameResult>;
196
+ /**
197
+ * Where a file or a component tag is used across the project — the read side of the same walker
198
+ * `renameFile` writes through. Backs `capability.findReferences`, and with it the inspector's
199
+ * "Used on N pages", `Selection: Find Usages`, and the reference count inside every delete and
200
+ * rename confirmation.
201
+ *
202
+ * Optional so a backend without the route hides those three renderings rather than reporting a
203
+ * confident zero — the one answer a destructive dialog must never invent. Every shipped host
204
+ * implements it: the walker lives in `@jxsuite/server`, which desktop and cloud both run.
205
+ *
206
+ * At least one of `path` / `tagName` must be given. A `path` naming a component document
207
+ * contributes its own root tag, so one call answers "as a file AND as an element" together.
208
+ */
209
+ findReferences?: (target: { path?: string; tagName?: string }) => Promise<ReferencesResult>;
170
210
  createDirectory: (path: string) => Promise<void>;
171
211
  /**
172
212
  * Subscribe to backend filesystem change events for the active project. Returns an unsubscribe
@@ -266,6 +306,15 @@ export interface StudioPlatform {
266
306
  gitDiscard: (files: string[]) => Promise<void>;
267
307
  gitClone?: (url: string) => Promise<{ ok: boolean; root: string }>;
268
308
  gitInit: () => Promise<void>;
309
+ /**
310
+ * Build the site to its output directory, so a reader opens what the author is looking at.
311
+ *
312
+ * `View: Open in Browser` runs this first and reports what it says. Optional because it is a
313
+ * capability, not an assumption: a backend that cannot build (a read-only cloud viewer) simply
314
+ * does not declare it, and the command says so rather than opening whatever stale output the last
315
+ * build happened to leave — which for most projects is nothing at all.
316
+ */
317
+ buildSite?: () => Promise<SiteBuildResult>;
269
318
  gitAddRemote: (name: string, url: string) => Promise<void>;
270
319
  /**
271
320
  * How the New Project modal collects a destination, and which `CreateProjectDestination` variant
@@ -321,8 +370,23 @@ export interface StudioPlatform {
321
370
  /** Stack B AI assistant: URL of the OpenAI-compatible SSE chat proxy (`/__studio/ai/chat`). */
322
371
  aiChatUrl: () => string | Promise<string>;
323
372
  // ─── Multi-window (desktop only; undefined on dev-server) ───────────────────
324
- /** Open a project in a new window, focusing an existing window if it is already open. */
325
- openProjectInNewWindow?: (root: string) => Promise<void>;
373
+ /**
374
+ * Open a project in a new window, focusing an existing window if it is already open.
375
+ *
376
+ * `focused` says which of the two happened, so the caller reports the outcome instead of
377
+ * announcing the intent — "opened in a new window" is a lie when the project was already open
378
+ * somewhere and that window merely came to the front.
379
+ */
380
+ openProjectInNewWindow?: (root: string) => Promise<{ focused: boolean }>;
381
+ /**
382
+ * Pick a project WITHOUT binding this window to it — the answer to "which project", separated
383
+ * from the act of opening it here. Backs the New Window branch of Open Project: `openProject()`
384
+ * re-roots this window's backend as a side effect of picking, so it cannot ask a question whose
385
+ * answer is "not in this window". Resolves null when the user cancels the picker.
386
+ *
387
+ * Desktop only. Without it Studio does not offer the choice, because it could not honour it.
388
+ */
389
+ pickProject?: () => Promise<{ root: string; name: string } | null>;
326
390
  /** Open a fresh welcome window. */
327
391
  newWindow?: () => Promise<void>;
328
392
  /**
@@ -444,15 +508,6 @@ export interface CanvasPanel {
444
508
  renderScope: { stop: () => void; run: <T>(fn: () => T) => T | undefined } | null;
445
509
  }
446
510
 
447
- export interface DocumentStackEntry {
448
- document: JxMutableNode;
449
- documentPath: string | null;
450
- selection: JxPath | null;
451
- dirty?: boolean;
452
- mode?: string;
453
- sourceFormat?: string | null;
454
- }
455
-
456
511
  export interface FunctionEditDef {
457
512
  type: string;
458
513
  defName?: string;
@@ -464,8 +519,8 @@ export interface FunctionEditDef {
464
519
  }
465
520
 
466
521
  /**
467
- * Identifies which document position's `$expression` the full-screen formula workspace is editing:
468
- * a state entry (`type: "def"` + defName) or an element event binding (`type: "event"` + path +
522
+ * Identifies which document position's `$expression` the Bottom dock's Logic tab is editing: a
523
+ * state entry (`type: "def"` + defName) or an element event binding (`type: "event"` + path +
469
524
  * eventKey). Mirrors FunctionEditDef, the Monaco function editor's target shape.
470
525
  */
471
526
  export interface FormulaEditDef {