@jxsuite/studio 4.1.0 → 5.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 (612) hide show
  1. package/README.md +1 -1
  2. package/data/elements-meta.json +5 -15
  3. package/data/html-meta.json +51 -6
  4. package/dist/chunks/{2020-n6tfwnda.js → 2020-g79n01v9.js} +14 -14
  5. package/dist/chunks/action-group-sp6tna8v.js +118 -0
  6. package/dist/chunks/adapter-nj7tnjfn.js +13 -0
  7. package/dist/chunks/cf-account-picker-qwkm9zrf.js +495 -0
  8. package/dist/chunks/cf-settings-71bv5zn9.js +20 -0
  9. package/dist/chunks/color-area-v17df7b8.js +154 -0
  10. package/dist/chunks/color-field-anwybnkn.js +208 -0
  11. package/dist/chunks/color-slider-44ez96fc.js +79 -0
  12. package/dist/chunks/color-xgg3kfw7.js +35 -0
  13. package/dist/chunks/combobox-5e7jev23.js +236 -0
  14. package/dist/chunks/components-vj72gqtq.js +20 -0
  15. package/dist/chunks/content-source-5mex4x1h.js +46 -0
  16. package/dist/chunks/convert-to-repeater-n64h8znc.js +639 -0
  17. package/dist/chunks/dialog-s890b918.js +85 -0
  18. package/dist/chunks/editor-3qhbg51x.js +35 -0
  19. package/dist/chunks/entry-commands-234hjnqw.js +42 -0
  20. package/dist/chunks/extension-sections-3hpcambp.js +651 -0
  21. package/dist/chunks/file-ops-8yhd83xr.js +47 -0
  22. package/dist/chunks/files-hnxqxtsg.js +72 -0
  23. package/dist/chunks/format-host-779sfp2n.js +40 -0
  24. package/dist/chunks/fs-events-b88z7g4h.js +18 -0
  25. package/dist/chunks/git-panel-xwj8fpab.js +49 -0
  26. package/dist/chunks/icons-yy10wk9r.js +402 -0
  27. package/dist/chunks/iframe-entry-15g3n6xv.js +114 -0
  28. package/dist/chunks/iframe-entry-b27vq81n.js +203 -0
  29. package/dist/chunks/iframe-entry-nbyxee1y.js +154 -0
  30. package/dist/chunks/iframe-host-syr0f9sb.js +115 -0
  31. package/dist/chunks/{index-wcnt3r9g.js → index-mdmyx6s2.js} +11 -11
  32. package/dist/chunks/index-n4j7qxyr.js +109 -0
  33. package/dist/chunks/{javascript-q8kpmfah.js → javascript-xvrfrpkb.js} +8 -9
  34. package/dist/chunks/{jsonMode-dkr040ga.js → jsonMode-tyx2kj6p.js} +526 -527
  35. package/dist/chunks/jx-validate-j0wxk72w.js +15 -0
  36. package/dist/chunks/layers-ff81wthn.js +30 -0
  37. package/dist/chunks/layers-panel-pnnghw12.js +40 -0
  38. package/dist/chunks/listbox-6v2ac08j.js +44 -0
  39. package/dist/chunks/lit-html-m244w9nh.js +17 -0
  40. package/dist/chunks/media-picker-cqhhxhhe.js +33 -0
  41. package/dist/chunks/menu-9p2w46gj.js +15 -0
  42. package/dist/chunks/menu-q99sy9rf.js +285 -0
  43. package/dist/chunks/{monaco-binding-rj6xpx9y.js → monaco-binding-sw6en4dw.js} +12 -15
  44. package/dist/chunks/monaco-buffer-bhy3hb6m.js +24 -0
  45. package/dist/chunks/{monaco-setup-vx9bhp1d.js → monaco-setup-8r62fj6y.js} +27 -37
  46. package/dist/chunks/notify-628t0anh.js +21 -0
  47. package/dist/chunks/number-field-5cndyft8.js +72 -0
  48. package/dist/chunks/parse-0zx14y15.js +10 -0
  49. package/dist/chunks/platform-ts9cg1a4.js +15 -0
  50. package/dist/chunks/popover-47z3ytva.js +25 -0
  51. package/dist/chunks/publish-panel-5zt840sy.js +27 -0
  52. package/dist/chunks/quick-search-3748564r.js +45 -0
  53. package/dist/chunks/references-qvvxej7h.js +26 -0
  54. package/dist/chunks/right-panel-eccz92qt.js +45 -0
  55. package/dist/chunks/section-registry-5pq32t0q.js +25 -0
  56. package/dist/chunks/select-2r16ez41.js +49 -0
  57. package/dist/chunks/split-g7nm1rgb.js +191 -0
  58. package/dist/chunks/studio-0hqbpbd0.js +62 -0
  59. package/dist/chunks/{studio-kryvsw6j.js → studio-1k325pwf.js} +48 -48
  60. package/dist/chunks/{studio-b55y6ewx.js → studio-1knfqgy7.js} +58 -58
  61. package/dist/chunks/{studio-2b946z26.js → studio-1z1kcjv4.js} +3628 -6095
  62. package/dist/chunks/studio-3773086z.js +111 -0
  63. package/dist/chunks/{studio-4r1pv6zf.js → studio-38emantv.js} +22 -5
  64. package/dist/chunks/{studio-x813zp48.js → studio-3rz5md06.js} +30 -30
  65. package/dist/chunks/{studio-ynxwkrzd.js → studio-4wbb9ct0.js} +1953 -1785
  66. package/dist/chunks/studio-5te5tqng.js +81 -0
  67. package/dist/chunks/studio-5wt9nvhd.js +4763 -0
  68. package/dist/chunks/{studio-p4ngc046.js → studio-6ewqv390.js} +9 -9
  69. package/dist/chunks/{studio-9ab8gqsm.js → studio-6nzaktvy.js} +7 -7
  70. package/dist/chunks/studio-6xa21ej9.js +28 -0
  71. package/dist/chunks/{studio-5ehhackm.js → studio-727xh0e3.js} +19 -19
  72. package/dist/chunks/{studio-gcye7qka.js → studio-7e11jpa9.js} +21 -21
  73. package/dist/chunks/{studio-q7v10cg2.js → studio-7r42c6ca.js} +17 -7
  74. package/dist/chunks/studio-7xnmgv66.js +13615 -0
  75. package/dist/chunks/studio-8scdew0e.js +258 -0
  76. package/dist/chunks/{studio-fbf92ckf.js → studio-93zafjcm.js} +275 -163
  77. package/dist/chunks/{studio-dg7zyt5x.js → studio-98b3z07n.js} +1 -1
  78. package/dist/chunks/studio-a79k90n9.js +1162 -0
  79. package/dist/chunks/studio-aj37eer3.js +422 -0
  80. package/dist/chunks/studio-b0k5tqgp.js +2432 -0
  81. package/dist/chunks/studio-b4s9eykd.js +2811 -0
  82. package/dist/chunks/{studio-f3wqjtk5.js → studio-d0300636.js} +5076 -18
  83. package/dist/chunks/{studio-b9b6t1dg.js → studio-e1f24eff.js} +5664 -5666
  84. package/dist/chunks/studio-e1pgga3k.js +839 -0
  85. package/dist/chunks/{studio-g1wdche0.js → studio-esrxgwh6.js} +80 -80
  86. package/dist/chunks/{studio-q43a5y9h.js → studio-g3jevyjt.js} +16 -16
  87. package/dist/chunks/studio-gb13095a.js +2806 -0
  88. package/dist/chunks/{studio-3n4ndrzh.js → studio-h4nvnjvz.js} +303 -303
  89. package/dist/chunks/{studio-dm19q43r.js → studio-hhvs4sdg.js} +10 -10
  90. package/dist/chunks/{studio-x4qkdat5.js → studio-js8nv53n.js} +1749 -2481
  91. package/dist/chunks/{studio-qjy2z9bv.js → studio-jxp12rnj.js} +32 -137
  92. package/dist/chunks/{studio-trxrabqf.js → studio-k75n7vhp.js} +527 -283
  93. package/dist/chunks/{studio-q9jd1a60.js → studio-kcgff4r0.js} +15 -18
  94. package/dist/chunks/studio-kkqkcp3d.js +17668 -0
  95. package/dist/chunks/studio-mhhyamxf.js +763 -0
  96. package/dist/chunks/{studio-cft9r4r7.js → studio-nq5yf5mp.js} +22 -22
  97. package/dist/chunks/{studio-685s0ytq.js → studio-nqgawfks.js} +572 -572
  98. package/dist/chunks/studio-nzzpvzx9.js +185 -0
  99. package/dist/chunks/{studio-xrapxwwc.js → studio-p1dw8wfw.js} +55 -29
  100. package/dist/chunks/{studio-3bqpr0zh.js → studio-pxbgypmg.js} +299 -110
  101. package/dist/chunks/studio-q0a3yk4n.js +33 -0
  102. package/dist/chunks/{studio-wcy8trfx.js → studio-q3x06vmj.js} +23 -22
  103. package/dist/chunks/{studio-6yw4y3my.js → studio-qhhm6hbt.js} +8 -8
  104. package/dist/chunks/studio-qk4bfsq2.js +362 -0
  105. package/dist/chunks/{studio-msd847zn.js → studio-qk90fgqm.js} +361 -90
  106. package/dist/chunks/{studio-jkftxyvz.js → studio-r6nvacqs.js} +5861 -2154
  107. package/dist/chunks/{studio-53n8bp04.js → studio-r6xmmtfh.js} +1 -1
  108. package/dist/chunks/{studio-tvgyw9xw.js → studio-rnyf3f8e.js} +83 -250
  109. package/dist/chunks/studio-rs76k67r.js +12 -0
  110. package/dist/chunks/studio-sbjyc8bt.js +954 -0
  111. package/dist/chunks/{studio-1wagb550.js → studio-syg2tadj.js} +11 -11
  112. package/dist/chunks/{studio-t5wqwzdh.js → studio-tjjk7jvk.js} +1 -1
  113. package/dist/chunks/studio-tqgryxwg.js +1171 -0
  114. package/dist/chunks/studio-try13h8c.js +8448 -0
  115. package/dist/chunks/studio-vfq8qpjt.js +1219 -0
  116. package/dist/chunks/studio-vhv5tgxm.js +51 -0
  117. package/dist/chunks/{studio-ahn3pdgx.js → studio-vw7mzp6z.js} +52 -38
  118. package/dist/chunks/studio-wqpngpsk.js +174 -0
  119. package/dist/chunks/studio-wqz6dggm.js +1356 -0
  120. package/dist/chunks/{studio-92ab2jqe.js → studio-y9zedyjn.js} +14 -14
  121. package/dist/chunks/studio-yxa81f45.js +12 -0
  122. package/dist/chunks/studio-zdn2g6dt.js +148 -0
  123. package/dist/chunks/swatch-group-t0n6h2rn.js +139 -0
  124. package/dist/chunks/tabs-wbp1cdws.js +156 -0
  125. package/dist/chunks/textfield-abk6qyee.js +73 -0
  126. package/dist/chunks/toast-780pvfbv.js +27 -0
  127. package/dist/chunks/toast-host-3sqv46sn.js +116 -0
  128. package/dist/chunks/toolbar-xbh5bhtr.js +194 -0
  129. package/dist/chunks/tooltip-eptb614w.js +369 -0
  130. package/dist/chunks/tree-a73p0pqm.js +367 -0
  131. package/dist/chunks/{tsMode-76kr53q0.js → tsMode-jy4m86rv.js} +83 -85
  132. package/dist/chunks/workspace-t55jdjbx.js +69 -0
  133. package/dist/chunks/{ws-client-6shc5rze.js → ws-client-ny4zc51r.js} +20 -22
  134. package/dist/iframe-entry.js +1873 -673
  135. package/dist/studio.js +52757 -36535
  136. package/dist/workers/editor.worker.js +1040 -1085
  137. package/dist/workers/json.worker.js +1946 -1991
  138. package/dist/workers/ts.worker.js +4960 -5005
  139. package/index.html +1 -0
  140. package/package.json +18 -52
  141. package/src/about/about-modal.ts +72 -123
  142. package/src/browse/library-commands.ts +20 -49
  143. package/src/browse/library-layouts.ts +87 -218
  144. package/src/browse/library-pane.ts +406 -438
  145. package/src/canvas/canvas-helpers.ts +0 -49
  146. package/src/canvas/canvas-live-render.ts +25 -2
  147. package/src/canvas/canvas-render.ts +666 -231
  148. package/src/canvas/canvas-surface.ts +0 -1
  149. package/src/canvas/canvas-utils.ts +333 -113
  150. package/src/canvas/dialog-path.ts +76 -0
  151. package/src/canvas/dialog-state.ts +67 -0
  152. package/src/canvas/diff-marks.ts +252 -0
  153. package/src/canvas/diff-toolbar.ts +349 -0
  154. package/src/canvas/diff-view.ts +123 -0
  155. package/src/canvas/edit-width-drag.ts +187 -0
  156. package/src/canvas/edit-width.ts +150 -0
  157. package/src/canvas/iframe-entry.ts +125 -8
  158. package/src/canvas/iframe-host.ts +271 -15
  159. package/src/canvas/iframe-interaction.ts +133 -13
  160. package/src/canvas/iframe-patch.ts +7 -1
  161. package/src/canvas/iframe-position.ts +5 -6
  162. package/src/canvas/iframe-protocol.ts +106 -5
  163. package/src/canvas/iframe-render.ts +373 -5
  164. package/src/canvas/iframe-slash.ts +2 -2
  165. package/src/canvas/iframe-subtree.ts +5 -8
  166. package/src/canvas/path-mapping.ts +25 -3
  167. package/src/canvas/popover-path.ts +106 -0
  168. package/src/canvas/popover-state.ts +155 -0
  169. package/src/canvas/surface-registry.ts +21 -10
  170. package/src/collab/presence-chips.ts +51 -73
  171. package/src/commands/app-commands.ts +10 -0
  172. package/src/commands/budget.ts +12 -0
  173. package/src/commands/command-args.ts +404 -6
  174. package/src/commands/defaults.ts +44 -14
  175. package/src/commands/live-context.ts +11 -5
  176. package/src/commands/reference.ts +12 -2
  177. package/src/commands/registry.ts +177 -10
  178. package/src/commands/run-reported.ts +120 -0
  179. package/src/content/entry-commands.ts +16 -23
  180. package/src/content/entry-editor.ts +144 -101
  181. package/src/editor/canvas-slash-bridge.ts +2 -2
  182. package/src/editor/context-menu.ts +49 -200
  183. package/src/editor/convert-to-component.ts +42 -82
  184. package/src/editor/convert-to-repeater.ts +132 -173
  185. package/src/editor/inline-edit.ts +11 -0
  186. package/src/editor/shortcuts.ts +76 -49
  187. package/src/editor/slash-menu.ts +150 -157
  188. package/src/files/components.ts +39 -0
  189. package/src/files/file-ops.ts +117 -11
  190. package/src/files/files.ts +944 -660
  191. package/src/files/json-layout.ts +474 -0
  192. package/src/files/serialize-document.ts +8 -1
  193. package/src/format/convert-file.ts +16 -10
  194. package/src/format/format-host.ts +50 -1
  195. package/src/github/github-auth.ts +71 -29
  196. package/src/github/github-publish.ts +57 -65
  197. package/src/grid/cell-editors.ts +46 -4
  198. package/src/grid/cell-popovers.ts +65 -62
  199. package/src/grid/grid-open.ts +86 -64
  200. package/src/grid/grid-panel.ts +390 -469
  201. package/src/grid/redirects-grid.ts +40 -44
  202. package/src/hosting/layout.ts +2 -0
  203. package/src/i18n/i18n-commands.ts +137 -62
  204. package/src/media/media-pane.ts +83 -133
  205. package/src/new-project/add-repo-modal.ts +125 -176
  206. package/src/new-project/import-tab.ts +181 -252
  207. package/src/new-project/location-fields.ts +93 -160
  208. package/src/new-project/new-project-modal.ts +376 -367
  209. package/src/packages/jxsuite-update.ts +76 -18
  210. package/src/packages/pull-package-sync.ts +5 -1
  211. package/src/panels/activity-panel.ts +137 -104
  212. package/src/panels/ai-chat/chat-markdown.ts +15 -13
  213. package/src/panels/ai-chat/chat-view.ts +307 -472
  214. package/src/panels/ai-chat/composer.ts +175 -163
  215. package/src/panels/ai-chat/sessions-view.ts +31 -62
  216. package/src/panels/ai-panel.ts +213 -185
  217. package/src/panels/block-action-bar.ts +444 -645
  218. package/src/panels/bottom-dock.ts +112 -47
  219. package/src/panels/chat-panel.ts +19 -14
  220. package/src/panels/data-explorer.ts +141 -102
  221. package/src/panels/data-grid.ts +183 -150
  222. package/src/panels/dnd.ts +75 -20
  223. package/src/panels/editors.ts +26 -19
  224. package/src/panels/elements-panel.ts +243 -186
  225. package/src/panels/empty-state.ts +26 -42
  226. package/src/panels/events-panel.ts +950 -729
  227. package/src/panels/formula-workspace.ts +434 -207
  228. package/src/panels/frontmatter-fields.ts +185 -153
  229. package/src/panels/frontmatter-panel.ts +411 -187
  230. package/src/panels/git-diff-open.ts +148 -0
  231. package/src/panels/git-panel.ts +679 -605
  232. package/src/panels/head-panel.ts +617 -435
  233. package/src/panels/i18n-panel.ts +148 -135
  234. package/src/panels/imports-panel.ts +388 -414
  235. package/src/panels/jump-bar.ts +175 -147
  236. package/src/panels/layers-panel.ts +785 -671
  237. package/src/panels/left-panel.ts +166 -94
  238. package/src/panels/navigator-panels.ts +1 -1
  239. package/src/panels/pane-context.ts +529 -677
  240. package/src/panels/pane-grid.ts +265 -241
  241. package/src/panels/panel-registry.ts +15 -4
  242. package/src/panels/problems-panel.ts +181 -99
  243. package/src/panels/properties-panel.ts +1525 -966
  244. package/src/panels/provenance.ts +31 -113
  245. package/src/panels/quick-search.ts +223 -184
  246. package/src/panels/right-panel.ts +135 -169
  247. package/src/panels/seo-modal.ts +346 -167
  248. package/src/panels/settings-menu.ts +114 -661
  249. package/src/panels/settings-pane.ts +102 -86
  250. package/src/panels/signals-panel.ts +1318 -1172
  251. package/src/panels/statement-editor.ts +556 -428
  252. package/src/panels/style-panel.ts +1367 -630
  253. package/src/panels/style-utils.ts +36 -32
  254. package/src/panels/stylebook-doc.ts +30 -73
  255. package/src/panels/stylebook-layers-panel.ts +202 -109
  256. package/src/panels/stylebook-panel.ts +124 -88
  257. package/src/panels/tab-strip.ts +386 -288
  258. package/src/platforms/cloud.ts +106 -16
  259. package/src/platforms/devserver.ts +13 -0
  260. package/src/publish/deploy-checklist.ts +124 -78
  261. package/src/publish/publish-commands.ts +4 -12
  262. package/src/publish/publish-panel.ts +134 -328
  263. package/src/services/a11y-report.ts +100 -43
  264. package/src/services/ai-command-tools.ts +538 -0
  265. package/src/services/ai-project-tools.ts +8 -2
  266. package/src/services/ai-session-store.ts +6 -0
  267. package/src/services/ai-system-prompt.ts +112 -24
  268. package/src/services/ai-tools.ts +36 -212
  269. package/src/services/ai-write-report.ts +174 -0
  270. package/src/services/document-assistant.ts +59 -8
  271. package/src/services/idle.ts +16 -14
  272. package/src/services/live-surfaces.ts +237 -0
  273. package/src/services/model-uri.ts +75 -0
  274. package/src/services/notify.ts +63 -6
  275. package/src/services/popover-report.ts +325 -0
  276. package/src/services/surface-registry.ts +56 -0
  277. package/src/services/tool-executor.ts +6 -0
  278. package/src/settings/contexts-section.ts +301 -299
  279. package/src/settings/contributed-section.ts +361 -222
  280. package/src/settings/css-vars-editor.ts +320 -325
  281. package/src/settings/defs-editor.ts +572 -535
  282. package/src/settings/dependencies-editor.ts +79 -107
  283. package/src/settings/extension-commands.ts +379 -0
  284. package/src/settings/extension-rows.ts +113 -0
  285. package/src/settings/extensions-section.ts +287 -0
  286. package/src/settings/general-settings.ts +130 -181
  287. package/src/settings/head-editor.ts +10 -305
  288. package/src/settings/locales-section.ts +146 -196
  289. package/src/settings/preferences-accounts.ts +1 -1
  290. package/src/settings/preferences-dialog.ts +350 -330
  291. package/src/settings/project-sections.ts +108 -181
  292. package/src/settings/schema-field-ui.ts +11 -399
  293. package/src/settings/section-registry.ts +8 -1
  294. package/src/settings/settings-document.ts +31 -29
  295. package/src/shell/tree.ts +42 -87
  296. package/src/shell.ts +48 -36
  297. package/src/studio.ts +104 -69
  298. package/src/style/token-ref.ts +0 -39
  299. package/src/surfaces/about.json +263 -0
  300. package/src/surfaces/about.ts +163 -0
  301. package/src/surfaces/add-repo.json +486 -0
  302. package/src/surfaces/add-repo.ts +236 -0
  303. package/src/surfaces/ai-chat.json +2185 -0
  304. package/src/surfaces/ai-chat.ts +549 -0
  305. package/src/surfaces/ai-credentials-form.json +332 -0
  306. package/src/surfaces/ai-credentials-form.ts +151 -0
  307. package/src/surfaces/ai-managed-connect.json +108 -0
  308. package/src/surfaces/ai-managed-connect.ts +123 -0
  309. package/src/surfaces/ai-model-picker.json +114 -0
  310. package/src/surfaces/ai-model-picker.ts +139 -0
  311. package/src/surfaces/block-action-bar.json +640 -0
  312. package/src/surfaces/block-action-bar.ts +368 -0
  313. package/src/surfaces/bottom-dock.json +154 -0
  314. package/src/surfaces/bottom-dock.ts +125 -0
  315. package/src/surfaces/canvas-stage.json +444 -0
  316. package/src/surfaces/canvas-stage.ts +253 -0
  317. package/src/surfaces/cf-account-picker.json +300 -0
  318. package/src/surfaces/cf-account-picker.ts +214 -0
  319. package/src/surfaces/commandbar.json +985 -0
  320. package/src/{panels/toolbar.ts → surfaces/commandbar.ts} +417 -391
  321. package/src/surfaces/convert-repeater.json +309 -0
  322. package/src/surfaces/convert-repeater.ts +276 -0
  323. package/src/surfaces/data-actions.json +157 -0
  324. package/src/surfaces/data-actions.ts +115 -0
  325. package/src/surfaces/dialog.json +289 -0
  326. package/src/surfaces/dialog.ts +343 -0
  327. package/src/surfaces/diff-toolbar.json +266 -0
  328. package/src/surfaces/diff-toolbar.ts +139 -0
  329. package/src/surfaces/doc-header.json +739 -0
  330. package/src/surfaces/doc-header.ts +223 -0
  331. package/src/surfaces/empty-state.json +152 -0
  332. package/src/surfaces/empty-state.ts +173 -0
  333. package/src/surfaces/entry-editor.json +281 -0
  334. package/src/surfaces/entry-editor.ts +191 -0
  335. package/src/surfaces/expression-editor.json +977 -0
  336. package/src/surfaces/expression-editor.ts +266 -0
  337. package/src/surfaces/files-panel.json +592 -0
  338. package/src/surfaces/files-panel.ts +301 -0
  339. package/src/surfaces/formula-palette.json +364 -0
  340. package/src/surfaces/formula-palette.ts +340 -0
  341. package/src/surfaces/git-panel.json +1822 -0
  342. package/src/surfaces/git-panel.ts +272 -0
  343. package/src/surfaces/github-auth.json +112 -0
  344. package/src/surfaces/github-auth.ts +139 -0
  345. package/src/surfaces/github-publish.json +206 -0
  346. package/src/surfaces/github-publish.ts +169 -0
  347. package/src/surfaces/grid-cell.json +218 -0
  348. package/src/surfaces/grid-cell.ts +120 -0
  349. package/src/surfaces/grid-open.json +147 -0
  350. package/src/surfaces/grid-open.ts +87 -0
  351. package/src/surfaces/grid-panel.json +706 -0
  352. package/src/surfaces/grid-panel.ts +206 -0
  353. package/src/surfaces/grid-replace.json +179 -0
  354. package/src/surfaces/grid-replace.ts +89 -0
  355. package/src/surfaces/grid-views.json +446 -0
  356. package/src/surfaces/grid-views.ts +172 -0
  357. package/src/surfaces/inspector-dock.json +187 -0
  358. package/src/surfaces/inspector-dock.ts +161 -0
  359. package/src/surfaces/jump-bar.json +243 -0
  360. package/src/surfaces/jump-bar.ts +129 -0
  361. package/src/surfaces/jxsuite-update.json +146 -0
  362. package/src/surfaces/jxsuite-update.ts +149 -0
  363. package/src/surfaces/library-pane.json +1509 -0
  364. package/src/surfaces/library-pane.ts +363 -0
  365. package/src/surfaces/logic-panel.json +1949 -0
  366. package/src/surfaces/logic-panel.ts +369 -0
  367. package/src/surfaces/logic-workspace.json +685 -0
  368. package/src/surfaces/logic-workspace.ts +238 -0
  369. package/src/surfaces/media-browser.json +303 -0
  370. package/src/surfaces/media-browser.ts +145 -0
  371. package/src/surfaces/media-field.json +163 -0
  372. package/src/surfaces/media-field.ts +116 -0
  373. package/src/surfaces/media-pane.json +513 -0
  374. package/src/surfaces/media-pane.ts +197 -0
  375. package/src/surfaces/menu.json +232 -0
  376. package/src/surfaces/menu.ts +375 -0
  377. package/src/surfaces/navigator-dock.json +113 -0
  378. package/src/surfaces/navigator-dock.ts +130 -0
  379. package/src/surfaces/new-project.json +1654 -0
  380. package/src/surfaces/new-project.ts +411 -0
  381. package/src/surfaces/palette.json +535 -0
  382. package/src/surfaces/pane-context.json +1497 -0
  383. package/src/surfaces/pane-context.ts +513 -0
  384. package/src/surfaces/pane-grid.json +188 -0
  385. package/src/surfaces/pane-grid.ts +250 -0
  386. package/src/surfaces/panel-activity.json +563 -0
  387. package/src/surfaces/panel-activity.ts +187 -0
  388. package/src/surfaces/panel-data.json +141 -0
  389. package/src/surfaces/panel-data.ts +156 -0
  390. package/src/surfaces/panel-deploy-checklist.json +283 -0
  391. package/src/surfaces/panel-deploy-checklist.ts +175 -0
  392. package/src/surfaces/panel-elements.json +367 -0
  393. package/src/surfaces/panel-elements.ts +175 -0
  394. package/src/surfaces/panel-i18n.json +569 -0
  395. package/src/surfaces/panel-i18n.ts +212 -0
  396. package/src/surfaces/panel-imports.json +780 -0
  397. package/src/surfaces/panel-imports.ts +209 -0
  398. package/src/surfaces/panel-outline.json +734 -0
  399. package/src/surfaces/panel-outline.ts +326 -0
  400. package/src/surfaces/panel-page.json +1017 -0
  401. package/src/surfaces/panel-page.ts +240 -0
  402. package/src/surfaces/panel-problems.json +497 -0
  403. package/src/surfaces/panel-problems.ts +157 -0
  404. package/src/surfaces/panel-signals.json +1973 -0
  405. package/src/surfaces/panel-signals.ts +436 -0
  406. package/src/surfaces/panel-stylebook-layers.json +317 -0
  407. package/src/surfaces/panel-stylebook-layers.ts +155 -0
  408. package/src/surfaces/preferences.json +850 -0
  409. package/src/surfaces/preferences.ts +332 -0
  410. package/src/surfaces/progress-modal.json +110 -0
  411. package/src/surfaces/progress-modal.ts +150 -0
  412. package/src/surfaces/properties-panel.json +1578 -0
  413. package/src/surfaces/properties-panel.ts +337 -0
  414. package/src/surfaces/publish.json +945 -0
  415. package/src/surfaces/publish.ts +392 -0
  416. package/src/surfaces/push-plan.json +110 -0
  417. package/src/surfaces/push-plan.ts +87 -0
  418. package/src/surfaces/rail.json +255 -0
  419. package/src/surfaces/rail.ts +206 -0
  420. package/src/surfaces/reference-field.json +191 -0
  421. package/src/surfaces/reference-field.ts +168 -0
  422. package/src/surfaces/schema-builder.json +1011 -0
  423. package/src/surfaces/schema-builder.ts +181 -0
  424. package/src/surfaces/schema-form.json +1013 -0
  425. package/src/surfaces/schema-form.ts +287 -0
  426. package/src/surfaces/secret-field.json +60 -0
  427. package/src/surfaces/secret-field.ts +146 -0
  428. package/src/surfaces/seo.json +932 -0
  429. package/src/surfaces/seo.ts +334 -0
  430. package/src/surfaces/settings-contexts.json +538 -0
  431. package/src/surfaces/settings-contexts.ts +196 -0
  432. package/src/surfaces/settings-contributed.json +489 -0
  433. package/src/surfaces/settings-contributed.ts +199 -0
  434. package/src/surfaces/settings-css-vars.json +820 -0
  435. package/src/surfaces/settings-css-vars.ts +199 -0
  436. package/src/surfaces/settings-defs.json +1363 -0
  437. package/src/surfaces/settings-defs.ts +232 -0
  438. package/src/surfaces/settings-deploy.json +108 -0
  439. package/src/surfaces/settings-deploy.ts +135 -0
  440. package/src/surfaces/settings-extensions.json +486 -0
  441. package/src/surfaces/settings-extensions.ts +201 -0
  442. package/src/surfaces/settings-head.json +779 -0
  443. package/src/surfaces/settings-head.ts +397 -0
  444. package/src/surfaces/settings-locales.json +420 -0
  445. package/src/surfaces/settings-locales.ts +137 -0
  446. package/src/surfaces/settings-overview.json +423 -0
  447. package/src/surfaces/settings-overview.ts +218 -0
  448. package/src/surfaces/settings-packages.json +549 -0
  449. package/src/surfaces/settings-packages.ts +158 -0
  450. package/src/surfaces/settings-pane.json +147 -0
  451. package/src/surfaces/settings-pane.ts +150 -0
  452. package/src/surfaces/settings-rawjson.json +104 -0
  453. package/src/surfaces/settings-rawjson.ts +107 -0
  454. package/src/surfaces/shell.json +99 -0
  455. package/src/surfaces/shell.ts +30 -0
  456. package/src/surfaces/slash-menu.json +180 -0
  457. package/src/surfaces/slash-menu.ts +272 -0
  458. package/src/surfaces/statements.json +607 -0
  459. package/src/surfaces/statements.ts +238 -0
  460. package/src/surfaces/statusbar.json +94 -0
  461. package/src/{panels → surfaces}/statusbar.ts +164 -90
  462. package/src/surfaces/style-panel.json +1643 -0
  463. package/src/surfaces/style-panel.ts +338 -0
  464. package/src/surfaces/stylebook-chrome.json +122 -0
  465. package/src/surfaces/stylebook-chrome.ts +122 -0
  466. package/src/surfaces/tab-strip.json +448 -0
  467. package/src/surfaces/tab-strip.ts +200 -0
  468. package/src/surfaces/target-line.json +419 -0
  469. package/src/surfaces/target-line.ts +349 -0
  470. package/src/surfaces/toasts.json +189 -0
  471. package/src/surfaces/welcome.json +571 -0
  472. package/src/surfaces/welcome.ts +338 -0
  473. package/src/tabs/project-config.ts +111 -21
  474. package/src/tabs/tab.ts +52 -1
  475. package/src/types.ts +22 -0
  476. package/src/ui/ai-credentials-form.ts +104 -100
  477. package/src/ui/ai-managed-connect.ts +54 -36
  478. package/src/ui/ai-model-picker.ts +75 -47
  479. package/src/ui/cf-account-picker.ts +76 -141
  480. package/src/ui/color-selector.ts +80 -301
  481. package/src/ui/dynamic-slot.ts +43 -283
  482. package/src/ui/expression-editor.ts +797 -576
  483. package/src/ui/field-input.ts +8 -181
  484. package/src/ui/form-controls.ts +838 -366
  485. package/src/ui/formula-chips.ts +80 -85
  486. package/src/ui/kit.ts +33 -0
  487. package/src/ui/layers.ts +296 -593
  488. package/src/ui/media-picker.ts +203 -284
  489. package/src/ui/panel-resize.ts +251 -64
  490. package/src/ui/popover-surface.ts +153 -0
  491. package/src/ui/progress-modal.ts +22 -60
  492. package/src/ui/regions.ts +13 -7
  493. package/src/ui/schema-form.ts +906 -440
  494. package/src/ui/surface.ts +122 -0
  495. package/src/ui/unit-selector.ts +15 -131
  496. package/src/ui/value-source.ts +14 -0
  497. package/src/ui/virtual-window.ts +88 -9
  498. package/src/utils/canvas-media.ts +72 -0
  499. package/src/utils/edit-display.ts +7 -2
  500. package/src/utils/element-selectors.ts +69 -0
  501. package/src/utils/link-target.ts +2 -2
  502. package/src/utils/studio-utils.ts +0 -10
  503. package/src/view.ts +15 -0
  504. package/src/workspace/pane-derive.ts +36 -14
  505. package/src/workspace/workspace.ts +75 -6
  506. package/styles/canvas.css +72 -467
  507. package/styles/forced-colors.css +48 -82
  508. package/styles/forced-colors.json +41 -0
  509. package/styles/inspector.css +2 -1301
  510. package/styles/overlays.css +96 -2823
  511. package/styles/panels.css +6 -1423
  512. package/styles/shell-frame.css +272 -0
  513. package/styles/shell-frame.json +227 -0
  514. package/styles/shell.css +0 -1568
  515. package/styles/tokens.css +65 -136
  516. package/styles/tokens.json +84 -0
  517. package/dist/chunks/Overlay.dev-etqjwyf9.js +0 -23
  518. package/dist/chunks/adapter-jkcyhgtr.js +0 -14
  519. package/dist/chunks/cf-account-picker-en86v7x7.js +0 -163
  520. package/dist/chunks/cf-settings-45fgyx8y.js +0 -21
  521. package/dist/chunks/components-zmx10t62.js +0 -21
  522. package/dist/chunks/content-source-48xg86wj.js +0 -49
  523. package/dist/chunks/convert-to-repeater-43tfn7cs.js +0 -268
  524. package/dist/chunks/editor-cptq5jp1.js +0 -37
  525. package/dist/chunks/entry-commands-e63f1xc0.js +0 -46
  526. package/dist/chunks/extension-sections-txv08xxg.js +0 -311
  527. package/dist/chunks/file-ops-wbh4hhsx.js +0 -48
  528. package/dist/chunks/files-3kfkwhk4.js +0 -93
  529. package/dist/chunks/flow-qbfgrpeh.js +0 -634
  530. package/dist/chunks/focus-trap.esm-9t2drp0g.js +0 -1025
  531. package/dist/chunks/focus-visible-6eg70z18.js +0 -167
  532. package/dist/chunks/format-host-w16q81tf.js +0 -43
  533. package/dist/chunks/fs-events-hrrg328g.js +0 -21
  534. package/dist/chunks/git-panel-532whr1y.js +0 -59
  535. package/dist/chunks/index-zqxmqxmh.js +0 -109
  536. package/dist/chunks/jx-validate-8er6gbxy.js +0 -19
  537. package/dist/chunks/layers-panel-07hm7g5j.js +0 -67
  538. package/dist/chunks/layers-q75tj2mf.js +0 -45
  539. package/dist/chunks/lit-html-j055mkh8.js +0 -22
  540. package/dist/chunks/monaco-buffer-dqw0xxpx.js +0 -25
  541. package/dist/chunks/notify-yjm79n4s.js +0 -30
  542. package/dist/chunks/parse-24d0gay3.js +0 -19
  543. package/dist/chunks/platform-ba1j1gbh.js +0 -16
  544. package/dist/chunks/publish-panel-v9st96ja.js +0 -29
  545. package/dist/chunks/quick-search-zccjp6gv.js +0 -66
  546. package/dist/chunks/references-3qx8fkyp.js +0 -29
  547. package/dist/chunks/right-panel-vm3b6kwy.js +0 -46
  548. package/dist/chunks/section-registry-sx1ykvkw.js +0 -30
  549. package/dist/chunks/sp-overlay.dev-qgvhnxf4.js +0 -18
  550. package/dist/chunks/sp-popover.dev-n2etmqdp.js +0 -9
  551. package/dist/chunks/sp-progress-circle.dev-hryhj0es.js +0 -15
  552. package/dist/chunks/sp-tooltip.dev-4sastdnw.js +0 -15
  553. package/dist/chunks/sp-tray.dev-tkbscm06.js +0 -199
  554. package/dist/chunks/studio-0a5qrh01.js +0 -3
  555. package/dist/chunks/studio-0bq6eh7v.js +0 -12
  556. package/dist/chunks/studio-18j3bmcz.js +0 -14
  557. package/dist/chunks/studio-1ngbejft.js +0 -181
  558. package/dist/chunks/studio-4b5z1x43.js +0 -424
  559. package/dist/chunks/studio-4n2b5gzb.js +0 -357
  560. package/dist/chunks/studio-4rzyrtjz.js +0 -426
  561. package/dist/chunks/studio-5fx3g9jg.js +0 -4325
  562. package/dist/chunks/studio-5myknm0a.js +0 -1903
  563. package/dist/chunks/studio-7afd21nw.js +0 -67
  564. package/dist/chunks/studio-7nte2hj4.js +0 -613
  565. package/dist/chunks/studio-8jpzqk5s.js +0 -128
  566. package/dist/chunks/studio-8q517dfr.js +0 -24
  567. package/dist/chunks/studio-950k0dx6.js +0 -6939
  568. package/dist/chunks/studio-9n4mrmnb.js +0 -184
  569. package/dist/chunks/studio-a0tswy0w.js +0 -58
  570. package/dist/chunks/studio-ahnzd9rg.js +0 -23
  571. package/dist/chunks/studio-bgxt93ga.js +0 -62
  572. package/dist/chunks/studio-cmb03bg5.js +0 -13890
  573. package/dist/chunks/studio-d1ks410h.js +0 -175
  574. package/dist/chunks/studio-ef49pnzx.js +0 -387
  575. package/dist/chunks/studio-eqq1w638.js +0 -113
  576. package/dist/chunks/studio-ew0djmhg.js +0 -341
  577. package/dist/chunks/studio-gfgb8pe6.js +0 -1652
  578. package/dist/chunks/studio-h4rxscfc.js +0 -10
  579. package/dist/chunks/studio-jc994dnr.js +0 -1066
  580. package/dist/chunks/studio-kyh9avvv.js +0 -28
  581. package/dist/chunks/studio-m3hwn7y1.js +0 -120
  582. package/dist/chunks/studio-mtfnaqs9.js +0 -659
  583. package/dist/chunks/studio-n6j9t10q.js +0 -318
  584. package/dist/chunks/studio-n8b9wqwy.js +0 -291
  585. package/dist/chunks/studio-qcy05dp8.js +0 -1743
  586. package/dist/chunks/studio-qq6jym04.js +0 -53
  587. package/dist/chunks/studio-rdx8jcxy.js +0 -83
  588. package/dist/chunks/studio-rn9ta2mz.js +0 -111
  589. package/dist/chunks/studio-t7qb9db8.js +0 -665
  590. package/dist/chunks/studio-tmc5emrw.js +0 -8
  591. package/dist/chunks/studio-z6nw2ek2.js +0 -18
  592. package/dist/chunks/studio-zczps70q.js +0 -378
  593. package/dist/chunks/studio-zqazsct2.js +0 -1823
  594. package/dist/chunks/workspace-fjx52ryv.js +0 -89
  595. package/src/panels/activity-bar.ts +0 -251
  596. package/src/panels/panel-scheduler.ts +0 -219
  597. package/src/panels/style-inputs.ts +0 -237
  598. package/src/panels/target-line.ts +0 -274
  599. package/src/panels/welcome-screen.ts +0 -346
  600. package/src/ui/button-group.ts +0 -102
  601. package/src/ui/choice-field.ts +0 -72
  602. package/src/ui/field-row.ts +0 -249
  603. package/src/ui/formula-palette.ts +0 -213
  604. package/src/ui/icons.ts +0 -52
  605. package/src/ui/jx-theme.ts +0 -130
  606. package/src/ui/spectrum.ts +0 -340
  607. package/src/ui/value-selector.ts +0 -174
  608. package/src/ui/widgets.ts +0 -172
  609. /package/dist/chunks/{javascript-s56a8dpk.css → javascript-4abzjcy9.css} +0 -0
  610. /package/dist/chunks/{jsonMode-zr9sw0c7.css → jsonMode-vvmpmk34.css} +0 -0
  611. /package/dist/chunks/{monaco-setup-pav68602.css → monaco-setup-8vbyqnhr.css} +0 -0
  612. /package/dist/chunks/{tsMode-ef187txx.css → tsMode-hnxxb73h.css} +0 -0
@@ -6,8 +6,11 @@
6
6
  */
7
7
 
8
8
  import { postMessageChannel } from "./iframe-channel";
9
+ import { rectOf } from "../utils/geometry";
9
10
  import { displayTagName } from "@jxsuite/schema/guards";
10
11
  import {
12
+ applyCanvasDialogOpen,
13
+ applyCanvasPopoverOpen,
11
14
  applyPreviewColorScheme,
12
15
  applySiteStyle,
13
16
  installCanvasImageRetry,
@@ -44,7 +47,15 @@ import type { JxPath } from "../state";
44
47
  // ObserveScope MUST come from the runtime: the $defs refs are created by the runtime's copy of
45
48
  // @vue/reactivity, and dep tracking is per module instance — an effect from the studio's own copy
46
49
  // Would never re-run when a dev-proxy data source settles.
47
- import { observeScope, reapplyStyle, setResolveToken } from "@jxsuite/runtime";
50
+ import {
51
+ observeScope,
52
+ preloadModule,
53
+ reapplyStyle,
54
+ redefineElement,
55
+ resetDocumentStyles,
56
+ setResolveToken,
57
+ } from "@jxsuite/runtime";
58
+ import { KIT_LOADERS } from "@jxsuite/ui/loaders";
48
59
  import type { IframeChannel } from "./iframe-channel";
49
60
  import type {
50
61
  CanvasMode,
@@ -62,6 +73,21 @@ import { setBundleBase } from "../services/bundle-base";
62
73
  entry may, and tests/entry-anchors.test.ts for the guard. */
63
74
  setBundleBase(import.meta.url);
64
75
 
76
+ /*
77
+ * The kit's behaviours, as LOADERS. A project on the canvas may use a kit element — the kit's own
78
+ * stylebook pages do, and any site that imports one will — and the element's document names its
79
+ * sidecar by `jx-ui:` specifier, which is a scheme nothing serves. The shell answers it by
80
+ * importing every behaviour at boot (`registerUi()`); this frame answers it lazily, one chunk per
81
+ * behaviour on first use, so a page that draws no kit element loads none of them and a page that
82
+ * draws one loads only its own. Registered before anything renders, because the first document
83
+ * may be the one that needs it. The ELEMENTS are not registered here on purpose: the frame draws
84
+ * a project's definitions from the project's files, which is what makes an edit to a kit
85
+ * component show on the canvas rather than the bundled copy (embedding.md §6, ui.md §10).
86
+ */
87
+ for (const [specifier, load] of Object.entries(KIT_LOADERS)) {
88
+ preloadModule(specifier, load);
89
+ }
90
+
65
91
  /**
66
92
  * Resolve the drop placement for a forwarded cursor: point hit-test → nearest `[data-jx-path]` →
67
93
  * pure {@link computeDropInstruction}. Returns null when the cursor resolves to no droppable target.
@@ -227,6 +253,15 @@ export function startCanvasIframe(opts: {
227
253
  // The mode of the LIVE render — gates the interactive surfaces (inline editing, insert zones,
228
254
  // Grab-drags) that only design/edit modes own. Adopted alongside shadowDoc.
229
255
  let currentMode: CanvasMode = "design";
256
+ /**
257
+ * The popover the artboard is drawing open, as a serialized path, or null.
258
+ *
259
+ * Kept here rather than read back off the DOM because it has to survive a patch: an `attributes`
260
+ * op routes through `replaceSubtree`, which rebuilds the element and takes the attribute with it,
261
+ * and the frame must be able to put it back without a round trip to the host.
262
+ */
263
+ let popoverOpen: string | null = null;
264
+ let dialogOpen: string | null = null;
230
265
  /* The host's chord table. Empty until the first `keymap` message, which the host posts on
231
266
  `ready` — before any render, so there is nothing to type into during the gap. An empty table
232
267
  forwards nothing, which is the honest cold-start answer: the frame does not guess at what the
@@ -376,7 +411,7 @@ export function startCanvasIframe(opts: {
376
411
  getMode: () => currentMode,
377
412
  getShadowDoc: () => shadowDoc,
378
413
  });
379
- // Bridge the engine's slash menu to the parent's Spectrum menu (show/nav/select over the channel).
414
+ // Bridge the engine's slash menu to the parent's own menu (show/nav/select over the channel).
380
415
  const stopSlashBridge = startIframeSlashBridge(channel, container.ownerDocument);
381
416
  // Auto-recover canvas images that 404 on a cold first render (component <img>s created in
382
417
  // ConnectedCallback fire late, before the loopback server is warm). Re-fires the failed request a
@@ -392,8 +427,30 @@ export function startCanvasIframe(opts: {
392
427
  // The true content height.
393
428
  const MAX_CANVAS_HEIGHT = 30_000;
394
429
  let lastPostedHeight = -1;
430
+ /**
431
+ * The bottom edge of the open popover, relative to the container, or 0 when none is open.
432
+ *
433
+ * A popover is positioned, so it contributes nothing to `scrollHeight` and moves no ancestor box
434
+ * — which is why the `ResizeObserver` below never fires for one. It cannot feed back into an
435
+ * ever-growing height either: it is contained by the canvas's FIXED-SIZE query container, so
436
+ * growing the frame does not move it.
437
+ */
438
+ function openPopoverBottom(): number {
439
+ // Both overlay kinds: a dialog shown in place is positioned by the same forced rule.
440
+ const panels = container.querySelectorAll(
441
+ "[data-jx-popover][data-jx-popover-open], dialog[data-jx-dialog-open]",
442
+ );
443
+ const root = rectOf(container);
444
+ let bottom = 0;
445
+ for (const panel of panels) {
446
+ bottom = Math.max(bottom, rectOf(panel).bottom - root.top);
447
+ }
448
+ return bottom;
449
+ }
450
+
395
451
  function postContentHeight(): void {
396
- const measured = Math.min(container.scrollHeight, MAX_CANVAS_HEIGHT);
452
+ const content = Math.max(container.scrollHeight, openPopoverBottom());
453
+ const measured = Math.min(content, MAX_CANVAS_HEIGHT);
397
454
  if (measured > 0 && Math.abs(measured - lastPostedHeight) >= 1) {
398
455
  lastPostedHeight = measured;
399
456
  // A component-definition root (marked by makeStamper) is a fragment, not a page — tell the host
@@ -466,6 +523,16 @@ export function startCanvasIframe(opts: {
466
523
  quietFrames = quiet ? (changed ? 1 : quietFrames + 1) : 0;
467
524
  // Two consecutive quiet frames, then stop — the state is stable and any change re-arms us.
468
525
  if (quietFrames >= IDLE_QUIET_FRAMES) {
526
+ /* Measure once more now that the box has stopped moving. A CSS TRANSITION changes the content
527
+ height over time, and the two observers that normally catch that both miss the end of one:
528
+ `postContentHeight` fires at the START (from whatever changed the DOM) and reads a height
529
+ the transition has not reached, and the ResizeObserver stops firing once the box settles —
530
+ so the last value posted is a mid-flight one. A closing popover left a 141px component in a
531
+ 480px frame exactly this way.
532
+
533
+ Deduped by `lastPostedHeight`, so the common case posts nothing, and it terminates: this
534
+ post arms the watcher, the next settle measures the same height, and nothing is posted. */
535
+ postContentHeight();
469
536
  return;
470
537
  }
471
538
  // A page with a genuinely endless animation never goes quiet. Give up rather than burn a rAF
@@ -648,11 +715,8 @@ export function startCanvasIframe(opts: {
648
715
 
649
716
  const off = channel.onMessage((msg) => {
650
717
  if (msg.kind === "measure") {
651
- channel.post({
652
- hits: measureHits(msg.paths, container.ownerDocument),
653
- kind: "geometry",
654
- reqId: msg.reqId,
655
- });
718
+ const { hidden, hits } = measureHits(msg.paths, container.ownerDocument);
719
+ channel.post({ hidden, hits, kind: "geometry", reqId: msg.reqId });
656
720
  return;
657
721
  }
658
722
  if (msg.kind === "evalExpr") {
@@ -717,6 +781,11 @@ export function startCanvasIframe(opts: {
717
781
  }
718
782
  try {
719
783
  applyIframePatch(shadowDoc, msg.forwardOps, container, renderCtx, msg.echoPaths);
784
+ /* An `attributes` op routes through `replaceSubtree`, which rebuilds the element and takes
785
+ `data-jx-popover-open` with it — so a style edit on the open panel would close it under
786
+ the author's hands. Idempotent, so paying for it on every patch is a no-op. */
787
+ applyCanvasPopoverOpen(container, popoverOpen);
788
+ applyCanvasDialogOpen(container, dialogOpen);
720
789
  // Put the caret back where the author left it. Restoring the SELECTION re-activates the
721
790
  // Block through the editing host's own selectionchange path — there is no separate
722
791
  // "re-enter" step, because a caret in a block IS the edit.
@@ -811,6 +880,38 @@ export function startCanvasIframe(opts: {
811
880
  applyPreviewColorScheme(container.ownerDocument, msg.scheme);
812
881
  return;
813
882
  }
883
+ if (msg.kind === "setPopoverOpen") {
884
+ /* Refused in preview HERE as well as in the host. Preview renders popovers natively and the
885
+ canvas attribute does not exist there, so a stray message would silently do nothing —
886
+ better to refuse it in both realms, which is the discipline §4.2 states for every editing
887
+ affordance: neither side may assume the other's build is current. */
888
+ if (currentMode === "preview") {
889
+ return;
890
+ }
891
+ popoverOpen = msg.path === null ? null : serializeJxPath(msg.path);
892
+ applyCanvasPopoverOpen(container, popoverOpen);
893
+ applyCanvasDialogOpen(container, dialogOpen);
894
+ // The panel entering or leaving flow changes the content box, and the ResizeObserver only
895
+ // Sees `#jx-canvas-root` — an absolutely positioned panel does not move it.
896
+ postContentHeight();
897
+ return;
898
+ }
899
+ if (msg.kind === "setDialogOpen") {
900
+ // The dialog twin, refused in preview for the reason given above.
901
+ if (currentMode === "preview") {
902
+ return;
903
+ }
904
+ dialogOpen = msg.path === null ? null : serializeJxPath(msg.path);
905
+ applyCanvasDialogOpen(container, dialogOpen);
906
+ postContentHeight();
907
+ return;
908
+ }
909
+ if (msg.kind === "redefineElement") {
910
+ /* The canvas half of embedding.md §7. The definition is replaced in THIS realm; instances
911
+ already on the canvas keep the old one until the host's next render replaces them. */
912
+ void redefineElement(msg.doc as never, msg.base);
913
+ return;
914
+ }
814
915
  if (msg.kind === "setLocale") {
815
916
  /* The other document-level attribute flip, and the whole visible half of an axis-3 locale:
816
917
  `dir` is what makes an RTL preview actually mirror, and `lang` is what CSS's `:lang()` and
@@ -858,6 +959,12 @@ export function startCanvasIframe(opts: {
858
959
  stopDataScopeWatch?.();
859
960
  stopDataScopeWatch = null;
860
961
  handle?.dispose();
962
+ /* And its stylesheets. Scope teardown releases each element's rules on its own, so this is
963
+ belt to that braces — but a full re-render replaces every element in the frame, and the
964
+ canvas is the one place where "the sheet only ever grows" would be a live leak rather
965
+ than a theoretical one. It used to be exactly that, one orphaned `<style>` per styled
966
+ element per render. */
967
+ resetDocumentStyles(container.ownerDocument);
861
968
  handle = await renderResolvedDocument({
862
969
  assets: msg.assets ?? null,
863
970
  container,
@@ -870,11 +977,21 @@ export function startCanvasIframe(opts: {
870
977
  pageContentOffset: mapperCtx.pageContentOffset,
871
978
  pageContentPrefix: mapperCtx.pageContentPrefix,
872
979
  },
980
+ diffMarks: msg.diffMarks ?? null,
873
981
  mode: msg.mode,
982
+ popoverOpen: msg.popoverOpen ? serializeJxPath(msg.popoverOpen) : null,
983
+ dialogOpen: msg.dialogOpen ? serializeJxPath(msg.dialogOpen) : null,
874
984
  siteStyle: msg.siteStyle,
875
985
  ...(msg.allowAutoRequests ? { allowAutoRequests: true } : {}),
876
986
  });
877
987
  if (gen === latestGen) {
988
+ /* A render replaces the DOM, so the open attribute goes with it. The host sends the value
989
+ on the render message for exactly this reason; a frame that kept only its own copy
990
+ would reopen the wrong panel after a tab switch. */
991
+ popoverOpen = msg.popoverOpen ? serializeJxPath(msg.popoverOpen) : null;
992
+ dialogOpen = msg.dialogOpen ? serializeJxPath(msg.dialogOpen) : null;
993
+ applyCanvasPopoverOpen(container, popoverOpen);
994
+ applyCanvasDialogOpen(container, dialogOpen);
878
995
  // Adopt this generation's shadow doc + render context only once it's the live render.
879
996
  shadowDoc = rawDoc;
880
997
  renderCtx = handle.ctx;
@@ -38,12 +38,15 @@ import {
38
38
  tabOfContainer,
39
39
  } from "./canvas-surface";
40
40
  import { cloneSelection, primarySelection, toggleSelected } from "../tabs/selection";
41
+ import { activeRegistry } from "../commands/active-registry";
41
42
  import { getNodeAtPath } from "../state";
42
43
  import type { JxPath } from "../state";
43
44
  import { setLayoutSelection, shell } from "../shell";
44
45
  import { formatEditableVerdicts } from "../format/constraints";
45
46
  import { formatByName } from "../format/format-host";
46
47
  import { collabState } from "../collab/collab-state";
48
+ import { DIALOG_COMMANDS, isDialog, POPOVER_COMMANDS } from "@jxsuite/schema/dialogs";
49
+ import { isPopover } from "@jxsuite/schema/overlays";
47
50
  import { localeDirection } from "@jxsuite/schema/locale";
48
51
  import { getPlatform, hasPlatform } from "../platform";
49
52
  import type {
@@ -56,6 +59,7 @@ import type {
56
59
  IframeToParent,
57
60
  InsertZone,
58
61
  NodeHit,
62
+ WireDiffMarks,
59
63
  ParentToIframe,
60
64
  SelectionSnapshot,
61
65
  SerializableRect,
@@ -121,6 +125,22 @@ interface HostState {
121
125
  contentHeight: number | null;
122
126
  /** Whether the last measured content was a component-definition fragment (drops the 480px floor). */
123
127
  contentFragment: boolean;
128
+ /**
129
+ * The popover this host is drawing open, as posted. Kept so `mountIframeCanvas` can seed it onto
130
+ * the `render` message — a render replaces the DOM, so a re-mount would otherwise lose it.
131
+ */
132
+ popoverOpen: JxPath | null;
133
+ /** The dialog this host is drawing open, kept for the same reason. */
134
+ dialogOpen: JxPath | null;
135
+ /**
136
+ * Paths the frame resolved but could not measure, as serialized keys.
137
+ *
138
+ * Not a rect and not an absence: a node that IS in the document and IS in the DOM but is not
139
+ * rendered. The Outline draws these rows with a "not rendered in the canvas" mark, which is the
140
+ * honest answer for a selection that draws no box — the alternative was a 0×0 marker in the
141
+ * artboard's corner, which read as a bug in the editor rather than a fact about the document.
142
+ */
143
+ hiddenPaths: Set<string>;
124
144
  /** Whether an inline-edit session is live in this host's iframe (drives the format toolbar). */
125
145
  editing: boolean;
126
146
  /** The prop a live plain session edits (prop-bound text) — null for rich sessions/none. */
@@ -775,9 +795,25 @@ export async function revealCanvasPath(
775
795
  path: readonly (string | number)[],
776
796
  ): Promise<CanvasPoint | null> {
777
797
  const host = hostForPath();
778
- if (!host) {
779
- return null;
780
- }
798
+ return host ? revealCanvasPathIn(host, path) : null;
799
+ }
800
+
801
+ /**
802
+ * {@link revealCanvasPath}, against an artboard the caller has already resolved.
803
+ *
804
+ * **A diff artboard cannot be found by `hostForPath`.** That resolver prefers the host rendering
805
+ * the focused tab and otherwise takes any ready page host — and BOTH git-diff artboards mount with
806
+ * `tabId: null` (it is what stops them routing mutations anywhere), so neither can match the
807
+ * focused tab and the fallback returns an arbitrary one of the two. A change stepper asking for
808
+ * "the node at this path" would measure and pan whichever it happened to get, on either side, in
809
+ * either pane.
810
+ *
811
+ * Callers with a panel in hand reach their host through {@link hostForCanvas} and come here.
812
+ */
813
+ export async function revealCanvasPathIn(
814
+ host: HostState,
815
+ path: readonly (string | number)[],
816
+ ): Promise<CanvasPoint | null> {
781
817
  const before = await measureIn(host, path);
782
818
  if (!before) {
783
819
  return null;
@@ -793,6 +829,22 @@ export async function revealCanvasPath(
793
829
  return measureIn(host, path);
794
830
  }
795
831
 
832
+ /**
833
+ * Measure one node in one artboard, without moving anything.
834
+ *
835
+ * The stepper needs BOTH sides' rects before it pans, because it pans to their union — the two
836
+ * artboards share one `.panzoom-wrap` and therefore one vertical position, and a change sitting at
837
+ * different heights on the two boards is only fully on screen if the move accounts for both.
838
+ * {@link revealCanvasPathIn} measures and moves in one go, which is the wrong shape for that.
839
+ */
840
+ export function measureInCanvas(
841
+ canvasEl: HTMLElement,
842
+ path: readonly (string | number)[],
843
+ ): Promise<CanvasPoint | null> {
844
+ const host = hostForCanvas(canvasEl);
845
+ return host ? measureIn(host, path) : Promise.resolve(null);
846
+ }
847
+
796
848
  /**
797
849
  * Pin the `.canvas-panel-viewport` (the white "page" surface, the canvas element's parent) to the
798
850
  * SCALED content height when edit-mode content zoom is active. A `transform: scale()` on the canvas
@@ -912,7 +964,7 @@ export const INSERT_HIDE_DELAY = 300;
912
964
  * The parent-realm insertion handler: open the slash menu anchored at the "+" `btn` and, on select,
913
965
  * run `transactDoc → mutateInsertNode` for the captured `zone`. Injected from studio.ts (which owns
914
966
  * the slash-menu / transact / defaultDef wiring) so this host module — and its tests — stay free of
915
- * the lit/Spectrum slash-menu and the mutation pipeline, mirroring the native-drag handler.
967
+ * the slash-menu and the mutation pipeline, mirroring the native-drag handler.
916
968
  */
917
969
  let insertZoneClickHandler: ((btn: HTMLElement, zone: InsertZone) => void) | null = null;
918
970
 
@@ -1030,8 +1082,8 @@ export interface CanvasSlashRequest {
1030
1082
 
1031
1083
  /**
1032
1084
  * The parent-realm slash-menu surface the canvas iframe drives (show at a rect, navigate by key,
1033
- * dismiss). Injected from studio.ts (which owns the lit/Spectrum menu) so this host module — and
1034
- * its tests — stay free of it, mirroring {@link insertZoneClickHandler}.
1085
+ * dismiss). Injected from studio.ts (which owns the menu) so this host module — and its tests —
1086
+ * stay free of it, mirroring {@link insertZoneClickHandler}.
1035
1087
  */
1036
1088
  export interface CanvasSlashHandler {
1037
1089
  show: (req: CanvasSlashRequest) => void;
@@ -1161,6 +1213,50 @@ export function setIframePatchEscalation(fn: (paneId: string) => void): void {
1161
1213
  patchEscalation = fn;
1162
1214
  }
1163
1215
 
1216
+ /**
1217
+ * Tell every frame showing `tab` which popover to draw open.
1218
+ *
1219
+ * Fans out by `tabId` for the same reason `postPatchToHosts` does: two panes can show the same
1220
+ * document, and both must agree about which panel is open — it is the same element in both. A
1221
+ * PREVIEW host is skipped, and the frame refuses the message as well, because preview renders
1222
+ * popovers natively and the canvas attribute does not exist there.
1223
+ *
1224
+ * Also recorded on the host, so a re-mount can seed it onto the `render` message. A render replaces
1225
+ * the DOM and would otherwise close the panel the author was editing.
1226
+ *
1227
+ * @param tab The tab whose canvas is affected.
1228
+ * @param path The popover's document path, or null to close whatever is open.
1229
+ */
1230
+ export function postPopoverOpen(tab: { id: string }, path: JxPath | null): void {
1231
+ for (const host of liveHosts) {
1232
+ if (!host.iframe.isConnected) {
1233
+ liveHosts.delete(host);
1234
+ continue;
1235
+ }
1236
+ if (host.ready && host.tabId === tab.id && !host.preview) {
1237
+ host.popoverOpen = path;
1238
+ host.channel.post({ kind: "setPopoverOpen", path });
1239
+ }
1240
+ }
1241
+ }
1242
+
1243
+ /**
1244
+ * The dialog twin of {@link postPopoverOpen}: tell every editable frame showing `tab` which dialog
1245
+ * to draw open.
1246
+ */
1247
+ export function postDialogOpen(tab: { id: string }, path: JxPath | null): void {
1248
+ for (const host of liveHosts) {
1249
+ if (!host.iframe.isConnected) {
1250
+ liveHosts.delete(host);
1251
+ continue;
1252
+ }
1253
+ if (host.ready && host.tabId === tab.id && !host.preview) {
1254
+ host.dialogOpen = path;
1255
+ host.channel.post({ kind: "setDialogOpen", path });
1256
+ }
1257
+ }
1258
+ }
1259
+
1164
1260
  /**
1165
1261
  * Post a surgical patch (value-carrying forward ops) to every ready live iframe host rendering
1166
1262
  * `tabId`'s document — a still-connected host showing another tab's doc must never fold a foreign
@@ -1729,6 +1825,9 @@ function ensureHost(canvasEl: HTMLElement): HostState {
1729
1825
  channel,
1730
1826
  contentFragment: false,
1731
1827
  contentHeight: null,
1828
+ hiddenPaths: new Set<string>(),
1829
+ popoverOpen: null,
1830
+ dialogOpen: null,
1732
1831
  editing: false,
1733
1832
  editingProp: null,
1734
1833
  iframe,
@@ -1907,6 +2006,77 @@ function handleMessage(state: HostState, msg: IframeToParent): void {
1907
2006
  canvasPointerDownHandler?.();
1908
2007
  return;
1909
2008
  }
2009
+ case "popoverTargetClick": {
2010
+ /* The click's OTHER half — the `hit` beside it still selects the button. Routed through the
2011
+ command rather than written here, so the trigger, the palette, the Style tab's selector and
2012
+ the assistant are four renderings of one record (§13) rather than four writers.
2013
+
2014
+ `show`/`hide`/`toggle` collapse to a state because the record is a setter: the frame knows
2015
+ which panel is open only through the host, so `toggle` is resolved HERE, where the model
2016
+ is, and never inside the frame. Refused in preview by the host's own gate below. */
2017
+ const tab = hostTab(state);
2018
+ if (!tab || state.preview) {
2019
+ return;
2020
+ }
2021
+ const targeted =
2022
+ JSON.stringify(tab.session.ui.openPopover) === JSON.stringify(msg.targetPath);
2023
+ const open = msg.action === "show" || (msg.action === "toggle" && !targeted);
2024
+ /* A `hide` closes ITS OWN target and no other: `hidePopover()` on a popover that is not
2025
+ showing does nothing, so an invoker for A must leave B alone. `show` and `toggle` still
2026
+ always run — a toggle either opens the one it names or closes the one it names. */
2027
+ if (open || targeted) {
2028
+ void activeRegistry()?.run("canvas.setPopoverOpen", {
2029
+ open,
2030
+ path: msg.targetPath,
2031
+ });
2032
+ }
2033
+ return;
2034
+ }
2035
+ case "commandTargetClick": {
2036
+ /* An invoker's click, answered the way `popoverTargetClick` is: through the record, with a
2037
+ toggle resolved HERE against the model. A popover command lands on the popover verb, a
2038
+ dialog command on the dialog verb; `hide-popover`, `close` and `request-close` close only
2039
+ the overlay they name when it is the open one, and a custom command is the document's own
2040
+ business. */
2041
+ const tab = hostTab(state);
2042
+ if (!tab || state.preview) {
2043
+ return;
2044
+ }
2045
+ const targeted = (open: JxPath | null) =>
2046
+ JSON.stringify(open) === JSON.stringify(msg.targetPath);
2047
+ /* The command's FAMILY is not the target's KIND, and the dispatch needs both. A
2048
+ `show-popover` aimed at a `<dialog>`, or a `show-modal` at a popover, is an authoring
2049
+ mistake Problems already reports as `command-target-mismatch`, and the platform's answer to
2050
+ it is to ignore the click (spec.md §8.7) — so the canvas ignores it too. Routing on the
2051
+ family alone handed the popover verb a dialog's path, and the record REFUSES one: the
2052
+ `RangeError` came straight back out of this message listener, taking every handler queued
2053
+ behind that message with it. The null check is the same guard for a `targetPath` an edit
2054
+ has since invalidated, which threw for the same reason. */
2055
+ const target = getNodeAtPath(tab.doc.document, msg.targetPath);
2056
+ if (!target || typeof target !== "object") {
2057
+ return;
2058
+ }
2059
+ if (POPOVER_COMMANDS.has(msg.command)) {
2060
+ if (!isPopover(target)) {
2061
+ return;
2062
+ }
2063
+ const open =
2064
+ msg.command === "show-popover" ||
2065
+ (msg.command === "toggle-popover" && !targeted(tab.session.ui.openPopover));
2066
+ if (open || targeted(tab.session.ui.openPopover)) {
2067
+ void activeRegistry()?.run("canvas.setPopoverOpen", { open, path: msg.targetPath });
2068
+ }
2069
+ } else if (DIALOG_COMMANDS.has(msg.command)) {
2070
+ if (!isDialog(target)) {
2071
+ return;
2072
+ }
2073
+ const open = msg.command === "show-modal";
2074
+ if (open || targeted(tab.session.ui.openDialog)) {
2075
+ void activeRegistry()?.run("canvas.setDialogOpen", { open, path: msg.targetPath });
2076
+ }
2077
+ }
2078
+ return;
2079
+ }
1910
2080
  case "hit": {
1911
2081
  /* A click in a canvas is a click in a PANE, and the parent realm never saw it. See
1912
2082
  `focusHostPane`; it goes first so everything below writes into the pane the person is now
@@ -1925,7 +2095,14 @@ function handleMessage(state: HostState, msg: IframeToParent): void {
1925
2095
  // Block action bar anchors to the panel the selection was actually made in, not panel 0.
1926
2096
  let panelMedia: string | null = null;
1927
2097
  const clicked = panelHostingCanvas(state.canvasEl)?.panel;
1928
- if (clicked && !clicked.mediaName?.startsWith("git-diff")) {
2098
+ /* A panel with NO media name is not an artboard, and a click in it is not a statement about
2099
+ the breakpoint. Edit draws exactly one full-width column (`canvasPanelTemplate(null, …)`,
2100
+ so `mediaName` is `""`), which `panelMediaToActiveMedia` mapped to `null` — so every click
2101
+ into the page silently reset the pane to Base, undoing the size switcher and, now, any
2102
+ width the author had dragged the column to. Design's own panels are `"base"` or a real
2103
+ breakpoint key, so requiring a name changes nothing there; the only case it drops is the
2104
+ no-`$media` single panel, whose project has no breakpoint to be reset FROM. */
2105
+ if (clicked?.mediaName && !clicked.mediaName.startsWith("git-diff")) {
1929
2106
  panelMedia = panelMediaToActiveMedia(clicked.mediaName);
1930
2107
  // The breakpoint belongs to the tab THIS host renders, resolved the same way every other
1931
2108
  // Doc-touching message in this switch resolves it. `updateUi` writes to `activeTab`, which
@@ -2089,9 +2266,13 @@ function handleMessage(state: HostState, msg: IframeToParent): void {
2089
2266
  const sbTag = state.stylebook ? shell.stylebook.selection : null;
2090
2267
  state.overlay.setSelection(rect, sbTag ? `<${sbTag}>` : null);
2091
2268
  state.overlay.setCoSelection(co);
2092
- if (rect) {
2093
- state.lastSelectionRect = rect;
2094
- }
2269
+ /* Unconditional. `if (rect)` never CLEARED it, so a selection the frame could not measure —
2270
+ now including any node it reports under `hidden` — left the previous rect standing, and
2271
+ `getEditBarAnchorRect` went on returning it. The block action bar stayed anchored to a box
2272
+ that was no longer on screen. `repositionBlockActionBar` already hides the bar on a null
2273
+ anchor, so nothing else has to change. */
2274
+ state.lastSelectionRect = rect;
2275
+ state.hiddenPaths = new Set((msg.hidden ?? []).map((p) => JSON.stringify(p)));
2095
2276
  }
2096
2277
  return;
2097
2278
  }
@@ -2658,7 +2839,12 @@ type PreparedRender = Omit<
2658
2839
  /** One render pass's prepared payloads, by document IDENTITY. */
2659
2840
  type PreparedDocs = Map<
2660
2841
  JxMutableNode,
2661
- { tabId: string | null; viewTabId: string | null; payload: Promise<PreparedRender> }
2842
+ {
2843
+ tabId: string | null;
2844
+ viewTabId: string | null;
2845
+ mode: string | null;
2846
+ payload: Promise<PreparedRender>;
2847
+ }
2662
2848
  >;
2663
2849
 
2664
2850
  /**
@@ -2696,6 +2882,11 @@ const PREPARED_PASS_LIMIT = 4;
2696
2882
  * THAT — the document path, the layout toggle, the preview params and the mode. A mismatch in
2697
2883
  * either re-prepares rather than reusing. `tabId` alone would not do: an override render nulls it,
2698
2884
  * so two git-diff documents would agree on `null` while being views of different tabs.
2885
+ *
2886
+ * The pane's MODE is the third part of the key, because it is now an input to the resolution rather
2887
+ * than something derived from `viewTabId` inside it. Two views of one document in different modes
2888
+ * are two different renders — a Diff lens and the pane it follows are exactly that — and without
2889
+ * this they would agree on both ids and share a payload resolved for whichever asked first.
2699
2890
  */
2700
2891
  const preparedPasses = new Map<number, PreparedDocs>();
2701
2892
 
@@ -2739,11 +2930,17 @@ function preparePassRender(
2739
2930
  tabId: string | null,
2740
2931
  viewTab: Tab | null,
2741
2932
  paneId: string,
2933
+ modeOverride: string | null = null,
2742
2934
  ): Promise<PreparedRender> {
2743
2935
  const byDoc = preparedDocsFor(gen);
2744
2936
  const viewTabId = viewTab?.id ?? null;
2745
2937
  const cached = byDoc.get(doc);
2746
- if (cached && cached.tabId === tabId && cached.viewTabId === viewTabId) {
2938
+ if (
2939
+ cached &&
2940
+ cached.tabId === tabId &&
2941
+ cached.viewTabId === viewTabId &&
2942
+ cached.mode === modeOverride
2943
+ ) {
2747
2944
  return cached.payload;
2748
2945
  }
2749
2946
  // One-shot per PANE's pass, not per host and not globally. `allowAutoRequestsOnNextRender` arms
@@ -2755,7 +2952,7 @@ function preparePassRender(
2755
2952
  const allowAutoRequests = consumeAllowAutoRequests(paneId);
2756
2953
  const payload = timeSpanAsync(SPAN_PREPARE_RENDER, async (): Promise<PreparedRender> => {
2757
2954
  canvasPerf.renderPreparations += 1;
2758
- const resolved = await resolveCanvasDocument(doc, viewTab);
2955
+ const resolved = await resolveCanvasDocument(doc, viewTab, modeOverride);
2759
2956
  // The doc must be structured-cloneable to cross postMessage. A Jx document is JSON by contract,
2760
2957
  // So a JSON round-trip (NOT structuredClone, which would throw) drops residual functions /
2761
2958
  // Reactive proxy artifacts that would otherwise raise DataCloneError and silently drop the
@@ -2779,6 +2976,10 @@ function preparePassRender(
2779
2976
  doc: cloneableDoc,
2780
2977
  docBase: resolved.docBase ?? `${canvasBaseOrigin()}/`,
2781
2978
  mapperCtx: resolved.mapperCtx,
2979
+ /* Still a cast, but no longer a lie: `WireMapperCtx.canvasMode` is typed `string` (it mirrors
2980
+ `PathMapCtx`, which carries the mode for `isEditableMode` and takes `CanvasMode | string`),
2981
+ so this narrows a string to the union. It used to narrow it to a union that did not list
2982
+ `git-diff` while git-diff renders went through here every day. */
2782
2983
  mode: resolved.mapperCtx.canvasMode as CanvasMode,
2783
2984
  shadowDoc: cloneableShadow,
2784
2985
  siteStyle: resolved.siteStyle,
@@ -2786,7 +2987,7 @@ function preparePassRender(
2786
2987
  ...(allowAutoRequests ? { allowAutoRequests: true } : {}),
2787
2988
  };
2788
2989
  });
2789
- byDoc.set(doc, { payload, tabId, viewTabId });
2990
+ byDoc.set(doc, { mode: modeOverride, payload, tabId, viewTabId });
2790
2991
  return payload;
2791
2992
  }
2792
2993
 
@@ -2816,6 +3017,11 @@ function preparePassRender(
2816
3017
  * `paneOfContainer` does everywhere else stage content is handed a host and nothing else; the one
2817
3018
  * production caller (`canvas-render.ts`) passes `tabOfPane(surface.paneId)` explicitly, because it
2818
3019
  * has already resolved it to pick the document.
3020
+ *
3021
+ * `modeOverride` is the third question in the same family, and it exists because `viewTab` cannot
3022
+ * answer it: a lens draws the source pane's document in a mode that is never written onto that tab,
3023
+ * so resolving the mode from `viewTab` gave a Diff lens's artboards the SOURCE pane's mode. See
3024
+ * {@link resolveCanvasDocument}. Null derives it from `viewTab` as before.
2819
3025
  */
2820
3026
  export async function mountIframeCanvas(
2821
3027
  gen: number,
@@ -2824,6 +3030,8 @@ export async function mountIframeCanvas(
2824
3030
  widthPx?: number | null,
2825
3031
  tabId: string | null = null,
2826
3032
  viewTab: Tab | null = tabOfContainer(canvasEl),
3033
+ modeOverride: string | null = null,
3034
+ diffMarks: WireDiffMarks | null = null,
2827
3035
  ): Promise<void> {
2828
3036
  const state = ensureHost(canvasEl);
2829
3037
  state.pendingTabIds.set(gen, tabId);
@@ -2834,16 +3042,35 @@ export async function mountIframeCanvas(
2834
3042
  // Own `latestGen`), so the parent must NOT gate on `view.renderGeneration`: during boot many
2835
3043
  // Renders fire and the generation is usually stale by the time resolution finishes, which would
2836
3044
  // Otherwise drop every post.
2837
- const prepared = await preparePassRender(gen, doc, tabId, viewTab, paneOfContainer(canvasEl));
3045
+ const prepared = await preparePassRender(
3046
+ gen,
3047
+ doc,
3048
+ tabId,
3049
+ viewTab,
3050
+ paneOfContainer(canvasEl),
3051
+ modeOverride,
3052
+ );
2838
3053
  const message: ParentToIframe = {
2839
3054
  ...prepared,
2840
3055
  // Per-TAB, and read at POST time so a scheme flip that raced the shared resolution is not
2841
3056
  // Baked into the payload every host shares. `viewTab` is this artboard's tab — defaulted from
2842
3057
  // `tabOfContainer(canvasEl)`, the same route the rest of this mount takes.
2843
3058
  colorScheme: schemeWireFor(viewTab),
3059
+ /* Per-ARTBOARD, and post-time for a sharper version of the same reason. `preparePassRender`
3060
+ memoizes on the document and hands the SAME payload object to every host in the pass, and a
3061
+ git-diff pass has two hosts holding two documents — but marks are a function of BOTH sides,
3062
+ not of the document this host draws, so they are not derivable from that cache key at all.
3063
+ Baking them in would make the two artboards race for whose marks the pass kept. */
3064
+ ...(diffMarks ? { diffMarks } : {}),
2844
3065
  gen,
2845
3066
  kind: "render",
3067
+ // Read at POST time for the same reason `colorScheme` is: a render replaces the DOM, so a panel
3068
+ // The author opened before this pass would close under them without it.
3069
+ popoverOpen: viewTab?.session.ui.openPopover ?? null,
3070
+ dialogOpen: viewTab?.session.ui.openDialog ?? null,
2846
3071
  };
3072
+ state.popoverOpen = message.popoverOpen ?? null;
3073
+ state.dialogOpen = message.dialogOpen ?? null;
2847
3074
  // Preview is the fidelity view: no editing messages are honoured from it, no overlay is painted
2848
3075
  // Over it, and the frame stays viewport-sized so it scrolls for real. A mode switch to preview
2849
3076
  // Mid-split must likewise not start an edit session in the preview render. The flag and the frame
@@ -3000,6 +3227,35 @@ export function postLocaleToLiveHosts(locale: string | null, root?: HTMLElement
3000
3227
  }
3001
3228
  }
3002
3229
 
3230
+ /**
3231
+ * Replace an element's definition in every live frame (embedding.md §7's canvas half).
3232
+ *
3233
+ * Unscoped on purpose, where the locale and colour-scheme posts take a stage: a definition is a
3234
+ * fact about the REALM, not about a pane, and a frame that kept the old one would draw a different
3235
+ * element from the one beside it. Instances already on a canvas keep the definition they rendered
3236
+ * until their host renders again, which is why {@link redefineElementOnCanvases} follows this with a
3237
+ * render of every pane rather than trusting the message alone.
3238
+ *
3239
+ * The document crosses `postMessage`, so it is cloned to plain data first: a definition that came
3240
+ * off a tab's reactive record is a proxy, and a proxy cannot be structured-cloned.
3241
+ */
3242
+ export function postRedefineElementToLiveHosts(doc: JxMutableNode, base: string): number {
3243
+ // oxlint-disable-next-line unicorn/prefer-structured-clone
3244
+ const plain = JSON.parse(JSON.stringify(doc)) as JxMutableNode;
3245
+ let posted = 0;
3246
+ for (const host of liveHosts) {
3247
+ if (!host.iframe.isConnected) {
3248
+ liveHosts.delete(host);
3249
+ continue;
3250
+ }
3251
+ if (host.ready) {
3252
+ host.channel.post({ base, doc: plain, kind: "redefineElement" });
3253
+ posted += 1;
3254
+ }
3255
+ }
3256
+ return posted;
3257
+ }
3258
+
3003
3259
  /**
3004
3260
  * Push the project's current site style to every ready PAGE host as an in-place sheet replace (live
3005
3261
  * design-token editing — stylebook hosts pre-merge site style into the specimen doc and are