@jxsuite/studio 6.1.0 → 6.1.2

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 (645) hide show
  1. package/dist/chunks/2020-f2gkpy8d.js +4 -0
  2. package/dist/chunks/{action-group-2mqby73c.js → action-group-4aan7g6g.js} +3 -3
  3. package/dist/chunks/adapter-4912g339.js +4 -0
  4. package/dist/chunks/cf-account-picker-wccaenh6.js +4 -0
  5. package/dist/chunks/cf-settings-br6105tr.js +4 -0
  6. package/dist/chunks/color-28ksk2qm.js +4 -0
  7. package/dist/chunks/color-area-0fj8a4ga.js +4 -0
  8. package/dist/chunks/color-field-xch8kx2g.js +4 -0
  9. package/dist/chunks/color-slider-jxd8v350.js +4 -0
  10. package/dist/chunks/combobox-p0efwjke.js +4 -0
  11. package/dist/chunks/components-db68vsn7.js +4 -0
  12. package/dist/chunks/content-source-1449qesq.js +4 -0
  13. package/dist/chunks/convert-to-repeater-5h457egj.js +4 -0
  14. package/dist/chunks/dialog-c4x05vyx.js +4 -0
  15. package/dist/chunks/editor-x9jn86a7.js +4 -0
  16. package/dist/chunks/entry-commands-may24cpt.js +4 -0
  17. package/dist/chunks/extension-sections-pvr14vxr.js +4 -0
  18. package/dist/chunks/file-ops-x9v54pcj.js +4 -0
  19. package/dist/chunks/files-j3knqf0s.js +4 -0
  20. package/dist/chunks/format-host-tyre2trb.js +4 -0
  21. package/dist/chunks/fs-events-64ma9htc.js +4 -0
  22. package/dist/chunks/git-panel-84ps5dex.js +4 -0
  23. package/dist/chunks/grid-view-d2wq9ezg.js +51 -0
  24. package/dist/chunks/icons-pnfz478q.js +4 -0
  25. package/dist/chunks/iframe-entry-fvbk89gm.js +5 -0
  26. package/dist/chunks/iframe-entry-jdcktez8.js +5 -0
  27. package/dist/chunks/iframe-entry-x0ygf2yr.js +5 -0
  28. package/dist/chunks/iframe-entry-zap785cw.js +5 -0
  29. package/dist/chunks/iframe-host-ck6vejt5.js +4 -0
  30. package/dist/chunks/index-b8xw3vy9.js +4 -0
  31. package/dist/chunks/{index-gybvv7a6.js → index-zpjmzqfk.js} +3 -3
  32. package/dist/chunks/{javascript-rnvdp3k4.js → javascript-m54mnwp6.js} +3 -3
  33. package/dist/chunks/jsonMode-w15cmrcm.js +13 -0
  34. package/dist/chunks/jx-validate-at3zn8wa.js +4 -0
  35. package/dist/chunks/layers-h4xp1pss.js +4 -0
  36. package/dist/chunks/layers-panel-m5hw1d7g.js +4 -0
  37. package/dist/chunks/listbox-e74qjyq6.js +4 -0
  38. package/dist/chunks/lit-html-8t8t3dnr.js +4 -0
  39. package/dist/chunks/media-picker-2cweexc5.js +4 -0
  40. package/dist/chunks/menu-n6yzswmy.js +4 -0
  41. package/dist/chunks/menu-ynw13y0s.js +4 -0
  42. package/dist/chunks/monaco-binding-7gx83x3r.js +4 -0
  43. package/dist/chunks/monaco-buffer-qzxn567r.js +4 -0
  44. package/dist/chunks/monaco-setup-gxf6656g.js +4 -0
  45. package/dist/chunks/notify-cj5zbrs2.js +4 -0
  46. package/dist/chunks/number-field-y62xq0ty.js +4 -0
  47. package/dist/chunks/parse-b7ddhr8m.js +4 -0
  48. package/dist/chunks/platform-3dfws9js.js +4 -0
  49. package/dist/chunks/popover-jt2gxmzs.js +4 -0
  50. package/dist/chunks/publish-panel-1gvxbj21.js +4 -0
  51. package/dist/chunks/quick-search-0dajrwqg.js +4 -0
  52. package/dist/chunks/references-49qgt404.js +4 -0
  53. package/dist/chunks/right-panel-jm1bwe53.js +4 -0
  54. package/dist/chunks/section-registry-6kwc4fky.js +4 -0
  55. package/dist/chunks/select-rzds841e.js +4 -0
  56. package/dist/chunks/split-n2gfpm5m.js +4 -0
  57. package/dist/chunks/{studio-5g0ag7c0.js → studio-0q988f9w.js} +3 -3
  58. package/dist/chunks/studio-0rt52vpq.js +5 -0
  59. package/dist/chunks/studio-18vgbhs5.js +5 -0
  60. package/dist/chunks/{studio-pxg4ap1e.js → studio-1fxkaaea.js} +4 -4
  61. package/dist/chunks/studio-24mccdrj.js +5 -0
  62. package/dist/chunks/studio-2eh507zk.js +9 -0
  63. package/dist/chunks/studio-2hcbccfv.js +5 -0
  64. package/dist/chunks/studio-2j1cb95x.js +5 -0
  65. package/dist/chunks/studio-2jbs3rxv.js +5 -0
  66. package/dist/chunks/{studio-qbj760tx.js → studio-485046te.js} +7 -7
  67. package/dist/chunks/studio-4h8k4dw9.js +5 -0
  68. package/dist/chunks/studio-5f6n18kx.js +5 -0
  69. package/dist/chunks/studio-5gthpky6.js +5 -0
  70. package/dist/chunks/studio-5ve6bbep.js +5 -0
  71. package/dist/chunks/studio-6ep3js6b.js +9 -0
  72. package/dist/chunks/studio-6wkmf3wz.js +9 -0
  73. package/dist/chunks/studio-841rf3cg.js +5 -0
  74. package/dist/chunks/studio-8gmwgheg.js +5 -0
  75. package/dist/chunks/studio-8qm0ep50.js +5 -0
  76. package/dist/chunks/{studio-anyrgzvb.js → studio-90pq9km1.js} +4 -4
  77. package/dist/chunks/studio-924p1tm1.js +6 -0
  78. package/dist/chunks/{studio-v2bvmbjj.js → studio-a3g98wjr.js} +4 -4
  79. package/dist/chunks/studio-a3shpz9y.js +5 -0
  80. package/dist/chunks/studio-amsbaaar.js +6 -0
  81. package/dist/chunks/studio-at5spjbc.js +7 -0
  82. package/dist/chunks/studio-b2xv945e.js +5 -0
  83. package/dist/chunks/studio-bn5nkafw.js +5 -0
  84. package/dist/chunks/studio-bw6q5xjf.js +9 -0
  85. package/dist/chunks/studio-c7q0t9x1.js +5 -0
  86. package/dist/chunks/studio-camnhc96.js +6 -0
  87. package/dist/chunks/studio-cffqvnzd.js +5 -0
  88. package/dist/chunks/studio-d81xfh1g.js +5 -0
  89. package/dist/chunks/studio-dgz8d0c1.js +5 -0
  90. package/dist/chunks/studio-efvfrsf8.js +5 -0
  91. package/dist/chunks/studio-er30bpcx.js +5 -0
  92. package/dist/chunks/studio-ezscgr64.js +5 -0
  93. package/dist/chunks/studio-ffrrncvz.js +5 -0
  94. package/dist/chunks/studio-fpphgn8y.js +10 -0
  95. package/dist/chunks/studio-j1kvhdjk.js +4 -0
  96. package/dist/chunks/{studio-txjjzrsm.js → studio-j430n16g.js} +5 -5
  97. package/dist/chunks/studio-jjkg84nj.js +5 -0
  98. package/dist/chunks/studio-m0q32ars.js +4 -0
  99. package/dist/chunks/studio-m6bdst11.js +5 -0
  100. package/dist/chunks/studio-nv2cptd4.js +64 -0
  101. package/dist/chunks/studio-nwfrc98n.js +5 -0
  102. package/dist/chunks/studio-p1wgvp7r.js +5 -0
  103. package/dist/chunks/studio-pag2wncm.js +5 -0
  104. package/dist/chunks/studio-qvanvzfd.js +5 -0
  105. package/dist/chunks/studio-qwa64kt7.js +5 -0
  106. package/dist/chunks/studio-rk1zc4x3.js +928 -0
  107. package/dist/chunks/studio-se2r3ptc.js +5 -0
  108. package/dist/chunks/studio-sg2a0dh0.js +5 -0
  109. package/dist/chunks/studio-tjzjhe68.js +5 -0
  110. package/dist/chunks/{studio-0x3jyb8r.js → studio-tq0rtfwj.js} +4 -4
  111. package/dist/chunks/{studio-cpxkgjrj.js → studio-v0jzqdjm.js} +4 -4
  112. package/dist/chunks/studio-v2w7q8r1.js +7 -0
  113. package/dist/chunks/studio-v5vjq1h0.js +4 -0
  114. package/dist/chunks/studio-v8aga3vp.js +14 -0
  115. package/dist/chunks/studio-vfcdrfmv.js +5 -0
  116. package/dist/chunks/studio-vmfkgdjf.js +5 -0
  117. package/dist/chunks/studio-wt1816jj.js +5 -0
  118. package/dist/chunks/studio-wz73q5kq.js +4 -0
  119. package/dist/chunks/studio-y04qtxvp.js +7 -0
  120. package/dist/chunks/studio-y21g8zzg.js +5 -0
  121. package/dist/chunks/studio-y64hrt8p.js +12 -0
  122. package/dist/chunks/studio-ynn90f58.js +16 -0
  123. package/dist/chunks/studio-yxz682sx.js +5 -0
  124. package/dist/chunks/studio-zp7snvkq.js +5 -0
  125. package/dist/chunks/studio-zq4z6tm6.js +5 -0
  126. package/dist/chunks/swatch-group-ewh24n1f.js +4 -0
  127. package/dist/chunks/tabs-f5cw3tgp.js +4 -0
  128. package/dist/chunks/textfield-wewc8rq2.js +4 -0
  129. package/dist/chunks/toast-c9fn075p.js +4 -0
  130. package/dist/chunks/toast-host-ke5z4ng7.js +4 -0
  131. package/dist/chunks/{toolbar-2nnzqhrj.js → toolbar-44cm5cta.js} +3 -3
  132. package/dist/chunks/tooltip-5v8ngwj2.js +4 -0
  133. package/dist/chunks/tree-mdq3v5cw.js +4 -0
  134. package/dist/chunks/tsMode-sf49ee9x.js +14 -0
  135. package/dist/chunks/workspace-jtfahpee.js +4 -0
  136. package/dist/chunks/ws-client-nze0k6mv.js +4 -0
  137. package/dist/iframe-entry.js +16 -16
  138. package/dist/manifest.json +63 -63
  139. package/dist/studio.js +98 -98
  140. package/dist/workers/ts.worker.js +3 -3
  141. package/package.json +11 -11
  142. package/src/about/about-modal.ts +4 -3
  143. package/src/account/action-flow.ts +45 -38
  144. package/src/account-status.ts +7 -2
  145. package/src/browse/library-layouts.ts +8 -5
  146. package/src/browse/library-model.ts +18 -6
  147. package/src/browse/library-pane.ts +37 -21
  148. package/src/browse/library-preview.ts +14 -9
  149. package/src/browse/library-source.ts +1 -1
  150. package/src/canvas/asset-refs.ts +24 -9
  151. package/src/canvas/asset-resolve.ts +19 -23
  152. package/src/canvas/canvas-helpers.ts +7 -6
  153. package/src/canvas/canvas-live-render.ts +44 -53
  154. package/src/canvas/canvas-patcher.ts +30 -30
  155. package/src/canvas/canvas-perf.ts +11 -5
  156. package/src/canvas/canvas-render.ts +66 -42
  157. package/src/canvas/canvas-surface.ts +5 -3
  158. package/src/canvas/canvas-utils.ts +163 -64
  159. package/src/canvas/dialog-path.ts +9 -4
  160. package/src/canvas/dialog-state.ts +1 -2
  161. package/src/canvas/diff-marks.ts +9 -16
  162. package/src/canvas/diff-toolbar.ts +8 -7
  163. package/src/canvas/edit-width-drag.ts +4 -1
  164. package/src/canvas/edit-width.ts +1 -3
  165. package/src/canvas/iframe-channel.ts +27 -13
  166. package/src/canvas/iframe-drop.ts +13 -12
  167. package/src/canvas/iframe-editable-root.ts +29 -16
  168. package/src/canvas/iframe-entry.ts +80 -63
  169. package/src/canvas/iframe-eval.ts +73 -49
  170. package/src/canvas/iframe-host.ts +203 -118
  171. package/src/canvas/iframe-inline-edit.ts +69 -32
  172. package/src/canvas/iframe-insert.ts +5 -4
  173. package/src/canvas/iframe-interaction.ts +37 -26
  174. package/src/canvas/iframe-overlay.ts +1 -1
  175. package/src/canvas/iframe-patch.ts +18 -15
  176. package/src/canvas/iframe-position.ts +12 -6
  177. package/src/canvas/iframe-protocol.ts +15 -16
  178. package/src/canvas/iframe-render.ts +41 -36
  179. package/src/canvas/iframe-slash.ts +24 -11
  180. package/src/canvas/iframe-subtree.ts +6 -2
  181. package/src/canvas/path-mapping.ts +19 -12
  182. package/src/canvas/popover-path.ts +7 -3
  183. package/src/canvas/popover-state.ts +1 -2
  184. package/src/canvas/serialize-scope.ts +13 -5
  185. package/src/canvas/stage-scrollbar.ts +2 -1
  186. package/src/canvas/surface-registry.ts +21 -3
  187. package/src/collab/collab-commands.ts +7 -2
  188. package/src/collab/collab-session.ts +84 -45
  189. package/src/collab/collab-state.ts +4 -3
  190. package/src/collab/monaco-binding.ts +16 -13
  191. package/src/collab/monaco-cursors.ts +5 -2
  192. package/src/commands/budget.ts +3 -3
  193. package/src/commands/command-args.ts +103 -71
  194. package/src/commands/context.ts +22 -20
  195. package/src/commands/defaults.ts +4 -1
  196. package/src/commands/keymap.ts +47 -43
  197. package/src/commands/levels.ts +3 -3
  198. package/src/commands/live-context.ts +10 -8
  199. package/src/commands/reference.ts +2 -1
  200. package/src/commands/registry.ts +12 -10
  201. package/src/commands/run-reported.ts +15 -10
  202. package/src/component-props.ts +46 -30
  203. package/src/content/collection-match.ts +5 -4
  204. package/src/content/draft-state.ts +4 -3
  205. package/src/content/entry-commands.ts +3 -2
  206. package/src/content/entry-editor.ts +29 -17
  207. package/src/content/entry-fields.ts +12 -7
  208. package/src/content/entry-model.ts +21 -12
  209. package/src/editor/canvas-context-menu.ts +1 -2
  210. package/src/editor/canvas-slash-bridge.ts +3 -6
  211. package/src/editor/content-slice.ts +16 -14
  212. package/src/editor/context-menu.ts +35 -22
  213. package/src/editor/convert-targets.ts +30 -35
  214. package/src/editor/convert-to-component.ts +11 -5
  215. package/src/editor/convert-to-repeater.ts +34 -22
  216. package/src/editor/editable-tags.ts +17 -2
  217. package/src/editor/file-drop-action.ts +21 -22
  218. package/src/editor/inline-edit-apply.ts +35 -17
  219. package/src/editor/inline-edit.ts +66 -50
  220. package/src/editor/inline-format.ts +59 -56
  221. package/src/editor/inline-link.ts +40 -15
  222. package/src/editor/insert-zone-action.ts +1 -1
  223. package/src/editor/merge-tags.ts +58 -19
  224. package/src/editor/repeater-scope.ts +39 -28
  225. package/src/editor/shortcuts.ts +87 -47
  226. package/src/editor/slash-menu.ts +1 -6
  227. package/src/files/asset-versions.ts +4 -4
  228. package/src/files/components.ts +15 -10
  229. package/src/files/elements.ts +5 -6
  230. package/src/files/file-ops.ts +77 -36
  231. package/src/files/files.ts +90 -95
  232. package/src/files/media-meta.ts +2 -1
  233. package/src/files/media-paths.ts +6 -1
  234. package/src/files/serialize-document.ts +2 -2
  235. package/src/format/constraints.ts +1 -1
  236. package/src/format/convert-file.ts +27 -13
  237. package/src/format/format-choices.ts +2 -1
  238. package/src/format/format-host.ts +76 -50
  239. package/src/github/github-auth-status.ts +27 -3
  240. package/src/github/github-auth.ts +19 -9
  241. package/src/github/github-publish.ts +24 -20
  242. package/src/grid/cell-editors.ts +46 -31
  243. package/src/grid/cell-popovers.ts +5 -3
  244. package/src/grid/edit-buffer.ts +10 -9
  245. package/src/grid/grid-controller.ts +20 -22
  246. package/src/grid/grid-layout.ts +21 -5
  247. package/src/grid/grid-open.ts +3 -6
  248. package/src/grid/grid-panel.ts +11 -4
  249. package/src/grid/grid-source.ts +8 -8
  250. package/src/grid/grid-view.ts +14 -17
  251. package/src/grid/redirects-grid.ts +14 -5
  252. package/src/grid/redirects.ts +16 -14
  253. package/src/grid/schema-columns.ts +25 -19
  254. package/src/grid/sources/connector-source.ts +47 -20
  255. package/src/grid/sources/content-source.ts +25 -24
  256. package/src/grid/sources/csv-file-source.ts +2 -2
  257. package/src/grid/tabulator-tables.d.ts +55 -34
  258. package/src/hosting/stage.ts +7 -7
  259. package/src/i18n/i18n-commands.ts +1 -2
  260. package/src/media/media-pane.ts +4 -1
  261. package/src/new-project/add-repo-modal.ts +20 -19
  262. package/src/new-project/import-tab.ts +10 -14
  263. package/src/new-project/location-fields.ts +2 -1
  264. package/src/new-project/new-project-modal.ts +12 -9
  265. package/src/packages/jxsuite-update.ts +4 -3
  266. package/src/packages/pull-package-sync.ts +26 -25
  267. package/src/page-params.ts +97 -68
  268. package/src/panels/activity-panel.ts +17 -9
  269. package/src/panels/ai-chat/attached-context.ts +13 -7
  270. package/src/panels/ai-chat/chat-view.ts +80 -73
  271. package/src/panels/ai-chat/composer.ts +30 -33
  272. package/src/panels/ai-chat/sessions-view.ts +1 -2
  273. package/src/panels/ai-panel.ts +23 -20
  274. package/src/panels/block-action-bar.ts +46 -35
  275. package/src/panels/bottom-dock.ts +5 -3
  276. package/src/panels/canvas-dnd-bridge.ts +31 -24
  277. package/src/panels/data-explorer.ts +62 -25
  278. package/src/panels/data-grid.ts +14 -11
  279. package/src/panels/dnd.ts +108 -118
  280. package/src/panels/editors.ts +55 -22
  281. package/src/panels/elements-panel.ts +10 -3
  282. package/src/panels/events-panel.ts +84 -69
  283. package/src/panels/formula-workspace.ts +76 -51
  284. package/src/panels/frontmatter-fields.ts +24 -19
  285. package/src/panels/frontmatter-panel.ts +44 -34
  286. package/src/panels/git-diff-open.ts +4 -4
  287. package/src/panels/git-panel.ts +49 -61
  288. package/src/panels/head-panel.ts +131 -111
  289. package/src/panels/i18n-panel.ts +10 -3
  290. package/src/panels/imports-panel.ts +23 -47
  291. package/src/panels/jump-bar.ts +48 -29
  292. package/src/panels/layers-panel.ts +97 -74
  293. package/src/panels/left-panel.ts +3 -3
  294. package/src/panels/pane-context.ts +31 -22
  295. package/src/panels/pane-grid.ts +49 -20
  296. package/src/panels/panel-registry.ts +22 -37
  297. package/src/panels/properties-panel.ts +175 -121
  298. package/src/panels/quick-search.ts +53 -31
  299. package/src/panels/right-panel.ts +3 -3
  300. package/src/panels/seo-modal.ts +13 -17
  301. package/src/panels/settings-menu.ts +15 -15
  302. package/src/panels/signals-panel.ts +153 -131
  303. package/src/panels/statement-editor.ts +230 -140
  304. package/src/panels/style-panel.ts +254 -216
  305. package/src/panels/style-utils.ts +114 -51
  306. package/src/panels/stylebook-doc.ts +57 -54
  307. package/src/panels/stylebook-layers-panel.ts +9 -9
  308. package/src/panels/stylebook-panel.ts +13 -17
  309. package/src/panels/tab-drop.ts +27 -11
  310. package/src/panels/tab-strip.ts +36 -26
  311. package/src/platform-errors.ts +57 -43
  312. package/src/platform.ts +58 -30
  313. package/src/platforms/cloud.ts +345 -175
  314. package/src/platforms/default-platform.ts +10 -4
  315. package/src/platforms/devserver.ts +174 -127
  316. package/src/platforms/read-batcher.ts +16 -8
  317. package/src/project-list.ts +2 -1
  318. package/src/publish/deploy-checklist.ts +8 -2
  319. package/src/publish/pages-service.ts +28 -13
  320. package/src/publish/publish-commands.ts +5 -4
  321. package/src/publish/publish-panel.ts +10 -6
  322. package/src/recent-projects.ts +15 -8
  323. package/src/resize-edges.ts +26 -36
  324. package/src/services/a11y-report.ts +26 -19
  325. package/src/services/agent-seed.ts +23 -11
  326. package/src/services/ai-ask.ts +9 -12
  327. package/src/services/ai-command-tools.ts +68 -62
  328. package/src/services/ai-import-tools.ts +80 -67
  329. package/src/services/ai-models.ts +17 -13
  330. package/src/services/ai-project-tools.ts +68 -32
  331. package/src/services/ai-session-store.ts +19 -10
  332. package/src/services/ai-system-prompt.ts +46 -20
  333. package/src/services/ai-tools.ts +71 -27
  334. package/src/services/ai-write-report.ts +20 -12
  335. package/src/services/ai-writes.ts +4 -4
  336. package/src/services/automation.ts +58 -31
  337. package/src/services/cem-export.ts +149 -58
  338. package/src/services/clock.ts +6 -1
  339. package/src/services/code-services.ts +37 -31
  340. package/src/services/context-manager.ts +14 -10
  341. package/src/services/context-resolver.ts +20 -8
  342. package/src/services/data-service.ts +19 -15
  343. package/src/services/directory-picker.ts +18 -12
  344. package/src/services/document-assistant.ts +39 -19
  345. package/src/services/gated-registry.ts +10 -6
  346. package/src/services/harness/chat-reducer.ts +23 -1
  347. package/src/services/import-client.ts +91 -38
  348. package/src/services/import-seed.ts +8 -2
  349. package/src/services/jx-validate.ts +28 -31
  350. package/src/services/live-preview.ts +0 -0
  351. package/src/services/live-surfaces.ts +6 -3
  352. package/src/services/model-uri.ts +10 -2
  353. package/src/services/module-preloads.ts +12 -8
  354. package/src/services/monaco-buffer.ts +43 -24
  355. package/src/services/monaco-lazy.ts +7 -8
  356. package/src/services/monaco-setup.ts +23 -16
  357. package/src/services/notify.ts +44 -17
  358. package/src/services/popover-report.ts +25 -10
  359. package/src/services/preview-eval.ts +20 -8
  360. package/src/services/profile.ts +13 -6
  361. package/src/services/project-path.ts +13 -10
  362. package/src/services/references.ts +0 -0
  363. package/src/services/render-critic.ts +8 -7
  364. package/src/services/settings/kernel.ts +13 -4
  365. package/src/services/settings/write-queue.ts +9 -9
  366. package/src/services/token-lint.ts +12 -10
  367. package/src/services/tool-executor.ts +17 -5
  368. package/src/services/tool-outcomes.ts +44 -24
  369. package/src/services/trusted-types.ts +24 -7
  370. package/src/settings/contexts-section.ts +11 -8
  371. package/src/settings/contributed-section.ts +79 -60
  372. package/src/settings/css-vars-editor.ts +37 -16
  373. package/src/settings/defs-editor.ts +38 -35
  374. package/src/settings/extension-rows.ts +16 -8
  375. package/src/settings/extension-sections.ts +23 -13
  376. package/src/settings/extensions-section.ts +4 -1
  377. package/src/settings/general-settings.ts +11 -8
  378. package/src/settings/locales-section.ts +20 -12
  379. package/src/settings/preferences-dialog.ts +9 -6
  380. package/src/settings/preferences-keymap.ts +7 -5
  381. package/src/settings/preferences-sections.ts +11 -5
  382. package/src/settings/project-sections.ts +1 -1
  383. package/src/settings/schema-field-ui.ts +9 -0
  384. package/src/settings/settings-document.ts +5 -5
  385. package/src/shell/tree.ts +6 -5
  386. package/src/shell.ts +104 -81
  387. package/src/site-context.ts +37 -22
  388. package/src/state.ts +41 -49
  389. package/src/store.ts +54 -34
  390. package/src/studio.ts +39 -77
  391. package/src/style/live-preview.ts +1 -1
  392. package/src/style/project-styles.ts +6 -7
  393. package/src/style/token-ref.ts +5 -4
  394. package/src/surfaces/about.ts +18 -19
  395. package/src/surfaces/add-repo.ts +17 -19
  396. package/src/surfaces/ai-chat.ts +31 -36
  397. package/src/surfaces/ai-credentials-form.ts +6 -16
  398. package/src/surfaces/ai-managed-connect.ts +3 -4
  399. package/src/surfaces/ai-model-picker.ts +3 -5
  400. package/src/surfaces/block-action-bar.ts +22 -11
  401. package/src/surfaces/boot-failure.ts +4 -5
  402. package/src/surfaces/bottom-dock.ts +9 -18
  403. package/src/surfaces/canvas-stage.ts +12 -11
  404. package/src/surfaces/cf-account-picker.ts +17 -18
  405. package/src/surfaces/commandbar.ts +14 -9
  406. package/src/surfaces/convert-repeater.ts +17 -19
  407. package/src/surfaces/data-actions.ts +9 -7
  408. package/src/surfaces/dialog.ts +26 -28
  409. package/src/surfaces/diff-toolbar.ts +9 -7
  410. package/src/surfaces/doc-header.ts +10 -19
  411. package/src/surfaces/empty-state.ts +7 -10
  412. package/src/surfaces/entry-editor.ts +14 -11
  413. package/src/surfaces/expression-editor.ts +11 -25
  414. package/src/surfaces/files-panel.ts +27 -14
  415. package/src/surfaces/formula-palette.ts +4 -5
  416. package/src/surfaces/git-panel.ts +7 -15
  417. package/src/surfaces/github-auth.ts +16 -20
  418. package/src/surfaces/github-publish.ts +17 -22
  419. package/src/surfaces/grid-cell.ts +30 -31
  420. package/src/surfaces/grid-open.ts +10 -14
  421. package/src/surfaces/grid-panel.ts +13 -9
  422. package/src/surfaces/grid-replace.ts +13 -13
  423. package/src/surfaces/grid-views.ts +15 -18
  424. package/src/surfaces/inspector-dock.ts +13 -12
  425. package/src/surfaces/jump-bar.ts +5 -6
  426. package/src/surfaces/jxsuite-update.ts +16 -22
  427. package/src/surfaces/library-pane.ts +25 -20
  428. package/src/surfaces/logic-panel.ts +17 -30
  429. package/src/surfaces/logic-workspace.ts +13 -15
  430. package/src/surfaces/media-browser.ts +11 -10
  431. package/src/surfaces/media-field.ts +4 -5
  432. package/src/surfaces/media-pane.ts +8 -7
  433. package/src/surfaces/menu.ts +29 -23
  434. package/src/surfaces/navigator-dock.ts +11 -12
  435. package/src/surfaces/new-project.ts +27 -26
  436. package/src/surfaces/pane-context.ts +20 -25
  437. package/src/surfaces/pane-grid.ts +15 -10
  438. package/src/surfaces/panel-activity.ts +5 -6
  439. package/src/surfaces/panel-data.ts +3 -4
  440. package/src/surfaces/panel-deploy-checklist.ts +5 -6
  441. package/src/surfaces/panel-elements.ts +5 -7
  442. package/src/surfaces/panel-i18n.ts +5 -7
  443. package/src/surfaces/panel-imports.ts +6 -13
  444. package/src/surfaces/panel-outline.ts +25 -19
  445. package/src/surfaces/panel-page.ts +11 -18
  446. package/src/surfaces/panel-problems.ts +5 -7
  447. package/src/surfaces/panel-signals.ts +20 -34
  448. package/src/surfaces/panel-stylebook-layers.ts +5 -8
  449. package/src/surfaces/preferences.ts +21 -11
  450. package/src/surfaces/progress-modal.ts +15 -9
  451. package/src/surfaces/properties-panel.json +1 -1
  452. package/src/surfaces/properties-panel.ts +20 -42
  453. package/src/surfaces/publish.ts +18 -18
  454. package/src/surfaces/push-plan.ts +7 -8
  455. package/src/surfaces/rail.json +126 -73
  456. package/src/surfaces/rail.ts +38 -12
  457. package/src/surfaces/reference-field.ts +11 -15
  458. package/src/surfaces/schema-builder.ts +16 -22
  459. package/src/surfaces/schema-form.ts +15 -25
  460. package/src/surfaces/secret-field.ts +9 -6
  461. package/src/surfaces/seo.ts +21 -15
  462. package/src/surfaces/settings-contexts.ts +9 -7
  463. package/src/surfaces/settings-contributed.ts +14 -10
  464. package/src/surfaces/settings-css-vars.ts +5 -12
  465. package/src/surfaces/settings-defs.json +2 -2
  466. package/src/surfaces/settings-defs.ts +17 -21
  467. package/src/surfaces/settings-deploy.ts +8 -15
  468. package/src/surfaces/settings-extensions.ts +5 -6
  469. package/src/surfaces/settings-head.ts +33 -39
  470. package/src/surfaces/settings-locales.ts +8 -20
  471. package/src/surfaces/settings-overview.ts +5 -6
  472. package/src/surfaces/settings-packages.ts +8 -7
  473. package/src/surfaces/settings-pane.ts +9 -18
  474. package/src/surfaces/settings-rawjson.ts +5 -6
  475. package/src/surfaces/shell.ts +1 -2
  476. package/src/surfaces/slash-menu.ts +19 -9
  477. package/src/surfaces/statements.ts +13 -18
  478. package/src/surfaces/statusbar.ts +17 -14
  479. package/src/surfaces/style-panel.ts +18 -31
  480. package/src/surfaces/stylebook-chrome.ts +3 -5
  481. package/src/surfaces/tab-strip.ts +14 -13
  482. package/src/surfaces/target-line.ts +6 -7
  483. package/src/surfaces/welcome.ts +5 -7
  484. package/src/tabs/patch-ops.ts +4 -3
  485. package/src/tabs/project-config.ts +32 -21
  486. package/src/tabs/selection.ts +36 -8
  487. package/src/tabs/tab.ts +52 -41
  488. package/src/tabs/transact.ts +98 -53
  489. package/src/tabs/typing.ts +31 -0
  490. package/src/types.ts +32 -17
  491. package/src/ui/ai-credentials-form.ts +10 -13
  492. package/src/ui/ai-model-picker.ts +3 -3
  493. package/src/ui/cf-account-picker.ts +7 -9
  494. package/src/ui/color-selector.ts +9 -5
  495. package/src/ui/dynamic-slot.ts +7 -8
  496. package/src/ui/expression-editor.ts +112 -105
  497. package/src/ui/form-controls.ts +57 -48
  498. package/src/ui/formula-catalog.ts +239 -230
  499. package/src/ui/formula-chips.ts +33 -18
  500. package/src/ui/layers.ts +44 -31
  501. package/src/ui/media-picker.ts +18 -9
  502. package/src/ui/popover-surface.ts +20 -12
  503. package/src/ui/progress-modal.ts +13 -8
  504. package/src/ui/regions.ts +7 -6
  505. package/src/ui/schema-form.ts +113 -70
  506. package/src/ui/surface.ts +71 -8
  507. package/src/ui/timing.ts +9 -0
  508. package/src/ui/value-source.ts +44 -31
  509. package/src/ui/virtual-window.ts +3 -2
  510. package/src/utils/base64.ts +3 -1
  511. package/src/utils/edit-display.ts +103 -74
  512. package/src/utils/element-selectors.ts +3 -2
  513. package/src/utils/google-fonts.ts +15 -14
  514. package/src/utils/inherited-style.ts +18 -20
  515. package/src/utils/link-target.ts +1 -1
  516. package/src/utils/preview-format.ts +25 -3
  517. package/src/utils/strip-events.ts +49 -22
  518. package/src/utils/studio-utils.ts +29 -14
  519. package/src/view.ts +2 -3
  520. package/src/workspace/pane-derive.ts +10 -12
  521. package/src/workspace/session.ts +53 -43
  522. package/src/workspace/workspace.ts +47 -36
  523. package/dist/chunks/2020-nkxms0ay.js +0 -4
  524. package/dist/chunks/adapter-m948ab5f.js +0 -4
  525. package/dist/chunks/cf-account-picker-n8hr38v7.js +0 -4
  526. package/dist/chunks/cf-settings-356xdtne.js +0 -4
  527. package/dist/chunks/color-area-jqhna8zy.js +0 -4
  528. package/dist/chunks/color-field-embq5ctg.js +0 -4
  529. package/dist/chunks/color-fpezvnkt.js +0 -4
  530. package/dist/chunks/color-slider-1qmw7wqy.js +0 -4
  531. package/dist/chunks/combobox-773hdzyj.js +0 -4
  532. package/dist/chunks/components-yk3t844e.js +0 -4
  533. package/dist/chunks/content-source-bawmyc09.js +0 -4
  534. package/dist/chunks/convert-to-repeater-7wns8kf1.js +0 -4
  535. package/dist/chunks/dialog-mtm9fq5t.js +0 -4
  536. package/dist/chunks/editor-6sc8nacq.js +0 -4
  537. package/dist/chunks/entry-commands-8ay9fh81.js +0 -4
  538. package/dist/chunks/extension-sections-na7rj3nr.js +0 -4
  539. package/dist/chunks/file-ops-gye0qw56.js +0 -4
  540. package/dist/chunks/files-dmc2xvcq.js +0 -4
  541. package/dist/chunks/format-host-t76b53f8.js +0 -4
  542. package/dist/chunks/fs-events-cg4tpz06.js +0 -4
  543. package/dist/chunks/git-panel-yef7bshy.js +0 -4
  544. package/dist/chunks/grid-view-7tt4bxw1.js +0 -51
  545. package/dist/chunks/icons-qbh8akb1.js +0 -4
  546. package/dist/chunks/iframe-entry-f7mqjwpm.js +0 -5
  547. package/dist/chunks/iframe-entry-haw7jkvz.js +0 -5
  548. package/dist/chunks/iframe-entry-z6qrytv3.js +0 -5
  549. package/dist/chunks/iframe-host-tmz30gfp.js +0 -4
  550. package/dist/chunks/index-nag78a9h.js +0 -4
  551. package/dist/chunks/jsonMode-zhwsprqk.js +0 -13
  552. package/dist/chunks/jx-validate-3gbp5nkn.js +0 -4
  553. package/dist/chunks/layers-fvz99859.js +0 -4
  554. package/dist/chunks/layers-panel-gchmbf9p.js +0 -4
  555. package/dist/chunks/listbox-s8fkmnq8.js +0 -4
  556. package/dist/chunks/lit-html-9agdspkt.js +0 -4
  557. package/dist/chunks/media-picker-rhjvyy00.js +0 -4
  558. package/dist/chunks/menu-2e4y9j7b.js +0 -4
  559. package/dist/chunks/menu-37d07z9m.js +0 -4
  560. package/dist/chunks/monaco-binding-2jnhjjvr.js +0 -4
  561. package/dist/chunks/monaco-buffer-rp21359g.js +0 -4
  562. package/dist/chunks/monaco-setup-vhh7gayr.js +0 -4
  563. package/dist/chunks/notify-h3www560.js +0 -4
  564. package/dist/chunks/number-field-gw7sq8rn.js +0 -4
  565. package/dist/chunks/parse-897fvqah.js +0 -4
  566. package/dist/chunks/platform-hz34wcd7.js +0 -4
  567. package/dist/chunks/popover-8htra4er.js +0 -4
  568. package/dist/chunks/publish-panel-exsmjzyj.js +0 -4
  569. package/dist/chunks/quick-search-pgaqeh0m.js +0 -4
  570. package/dist/chunks/references-2cgdxnb8.js +0 -4
  571. package/dist/chunks/right-panel-jf856w5w.js +0 -4
  572. package/dist/chunks/section-registry-jnf8r5gy.js +0 -4
  573. package/dist/chunks/select-n3v7gwas.js +0 -4
  574. package/dist/chunks/split-pff8f3ze.js +0 -4
  575. package/dist/chunks/studio-0f55h9ye.js +0 -5
  576. package/dist/chunks/studio-0sqdhnew.js +0 -12
  577. package/dist/chunks/studio-19x1c75w.js +0 -5
  578. package/dist/chunks/studio-2sqcndak.js +0 -5
  579. package/dist/chunks/studio-3aey7dd3.js +0 -5
  580. package/dist/chunks/studio-3bpdkq2s.js +0 -5
  581. package/dist/chunks/studio-47y6fbpf.js +0 -5
  582. package/dist/chunks/studio-4m3826v2.js +0 -5
  583. package/dist/chunks/studio-4nx2dj1g.js +0 -4
  584. package/dist/chunks/studio-4xv5s47r.js +0 -9
  585. package/dist/chunks/studio-5g4t8v6w.js +0 -5
  586. package/dist/chunks/studio-5py436sf.js +0 -5
  587. package/dist/chunks/studio-77kt594t.js +0 -5
  588. package/dist/chunks/studio-8f87n763.js +0 -10
  589. package/dist/chunks/studio-8mb00b84.js +0 -5
  590. package/dist/chunks/studio-8p51gq2h.js +0 -6
  591. package/dist/chunks/studio-954ds834.js +0 -5
  592. package/dist/chunks/studio-9ft45c83.js +0 -9
  593. package/dist/chunks/studio-a2138x5k.js +0 -7
  594. package/dist/chunks/studio-a9yzstqj.js +0 -5
  595. package/dist/chunks/studio-acdksxx6.js +0 -5
  596. package/dist/chunks/studio-ar8jcf2y.js +0 -5
  597. package/dist/chunks/studio-bmn4mn9j.js +0 -7
  598. package/dist/chunks/studio-e7xxa0sj.js +0 -5
  599. package/dist/chunks/studio-ecaz25vk.js +0 -5
  600. package/dist/chunks/studio-ery42sqr.js +0 -4
  601. package/dist/chunks/studio-fzdywr6p.js +0 -9
  602. package/dist/chunks/studio-g5fmf0c1.js +0 -5
  603. package/dist/chunks/studio-g7nse7zb.js +0 -5
  604. package/dist/chunks/studio-geq1ea6k.js +0 -14
  605. package/dist/chunks/studio-ghkb32r8.js +0 -5
  606. package/dist/chunks/studio-ghrhy2y7.js +0 -5
  607. package/dist/chunks/studio-grawhey1.js +0 -5
  608. package/dist/chunks/studio-h0xgswew.js +0 -5
  609. package/dist/chunks/studio-h9c4p2tx.js +0 -5
  610. package/dist/chunks/studio-jyzj5cxs.js +0 -5
  611. package/dist/chunks/studio-mgx8yhnz.js +0 -64
  612. package/dist/chunks/studio-mpb04y1d.js +0 -6
  613. package/dist/chunks/studio-n17mty64.js +0 -5
  614. package/dist/chunks/studio-nwh2eqsk.js +0 -6
  615. package/dist/chunks/studio-q1h883bt.js +0 -16
  616. package/dist/chunks/studio-q4tv82h7.js +0 -9
  617. package/dist/chunks/studio-qpqa7vk3.js +0 -5
  618. package/dist/chunks/studio-qqz8e5gk.js +0 -5
  619. package/dist/chunks/studio-qvasen3f.js +0 -5
  620. package/dist/chunks/studio-rggdb3hm.js +0 -5
  621. package/dist/chunks/studio-sghy936h.js +0 -4
  622. package/dist/chunks/studio-skprc6ef.js +0 -5
  623. package/dist/chunks/studio-tt2nactf.js +0 -5
  624. package/dist/chunks/studio-tyasmc39.js +0 -5
  625. package/dist/chunks/studio-vk9ezads.js +0 -5
  626. package/dist/chunks/studio-vzacdfsc.js +0 -4
  627. package/dist/chunks/studio-w4c0xmrw.js +0 -5
  628. package/dist/chunks/studio-wfjqhhbk.js +0 -5
  629. package/dist/chunks/studio-wgh5kxpj.js +0 -5
  630. package/dist/chunks/studio-xasj0796.js +0 -5
  631. package/dist/chunks/studio-y78s8r2n.js +0 -5
  632. package/dist/chunks/studio-z8w183sh.js +0 -5
  633. package/dist/chunks/studio-zscd9ty4.js +0 -928
  634. package/dist/chunks/studio-zxp13sss.js +0 -7
  635. package/dist/chunks/studio-zygnmm01.js +0 -5
  636. package/dist/chunks/swatch-group-8qh9a3bm.js +0 -4
  637. package/dist/chunks/tabs-jwthg40w.js +0 -4
  638. package/dist/chunks/textfield-spy3hc77.js +0 -4
  639. package/dist/chunks/toast-4d35zd64.js +0 -4
  640. package/dist/chunks/toast-host-8sjmfed3.js +0 -4
  641. package/dist/chunks/tooltip-fd6g9xy3.js +0 -4
  642. package/dist/chunks/tree-h19v1jfz.js +0 -4
  643. package/dist/chunks/tsMode-gr0m4dzr.js +0 -14
  644. package/dist/chunks/workspace-m15qtcqp.js +0 -4
  645. package/dist/chunks/ws-client-nf61frr1.js +0 -4
@@ -46,6 +46,7 @@ import { formatEditableVerdicts } from "../format/constraints";
46
46
  import { formatByName } from "../format/format-host";
47
47
  import { collabState } from "../collab/collab-state";
48
48
  import { DIALOG_COMMANDS, isDialog, POPOVER_COMMANDS } from "@jxsuite/schema/dialogs";
49
+ import { isNodeObject } from "@jxsuite/schema/guards";
49
50
  import { isPopover } from "@jxsuite/schema/overlays";
50
51
  import { localeDirection } from "@jxsuite/schema/locale";
51
52
  import { getPlatform, hasPlatform } from "../platform";
@@ -66,19 +67,70 @@ import type {
66
67
  SerializableRect,
67
68
  SerializedKey,
68
69
  SyncedChord,
69
- WireDocOp,
70
70
  } from "./iframe-protocol";
71
+ import type { JxDocOp } from "../tabs/patch-ops";
71
72
  import type { IframeChannel } from "./iframe-channel";
72
73
  import type { OverlayLayer, OverlayPlacement } from "./iframe-overlay";
73
74
  import type { SlashCommand } from "../editor/inline-edit";
74
75
  import type { Tab } from "../tabs/tab";
75
- import type { JxExpressionNode, JxMutableNode } from "@jxsuite/schema/types";
76
+ import type { JxExpressionNode, JxMutableNode, JxStyle } from "@jxsuite/schema/types";
76
77
  import { bundleUrl } from "../services/bundle-base";
77
78
 
78
- /** A rect in PARENT coordinates (overlay-local from {@link canvasRectToParent}, same field shape). */
79
- type ParentRect = OverlayPlacement;
79
+ /**
80
+ * A plain-data copy of a value bound for `postMessage`.
81
+ *
82
+ * A JSON round trip rather than `structuredClone`, which throws on exactly what this exists to
83
+ * drop: reactive-proxy internals and stray functions riding on a value read off a live store.
84
+ */
85
+ function plainCopy<T>(value: T): T {
86
+ /* SAFETY: Every value posted through here is document or config data, which is JSON by contract,
87
+ so the round trip returns the same shape minus only what the realm boundary cannot carry. */
88
+ // oxlint-disable-next-line unicorn/prefer-structured-clone
89
+ return JSON.parse(JSON.stringify(value)) as T;
90
+ }
91
+
92
+ /** A deferred re-entry into inline editing, waiting for the host's DOM to reflect a split/insert. */
93
+ interface PendingEnterEdit {
94
+ path: (string | number)[];
95
+ minGen: number;
96
+ offset?: number;
97
+ /** Set when the deferred re-entry is a PROP host rather than a block (see deferEnterEdit). */
98
+ prop?: string;
99
+ }
100
+
101
+ /** The `enterEdit` message a host posts to re-enter inline editing. */
102
+ type EnterEditMsg = Extract<ParentToIframe, { kind: "enterEdit" }>;
103
+
104
+ /** A full-render message, as one host receives it. */
105
+ type RenderMsg = Extract<ParentToIframe, { kind: "render" }>;
106
+
107
+ /**
108
+ * The source data a drag session retains for its drop, exactly what {@link applyDropInstruction}
109
+ * consumes.
110
+ */
111
+ type RetainedDragData = Parameters<typeof applyDropInstruction>[2];
80
112
 
81
- interface HostState {
113
+ /** The empirical zoom of a host's iframe and the iframe's parent-viewport offset. */
114
+ export interface DragGeometry {
115
+ scale: number;
116
+ rect: { left: number; top: number };
117
+ }
118
+
119
+ /** The current edit session's editing flag + latest selection snapshot, for the parent toolbar. */
120
+ export interface EditSnapshotState {
121
+ editing: boolean;
122
+ editingProp: string | null;
123
+ snapshot: SelectionSnapshot | null;
124
+ }
125
+
126
+ /**
127
+ * One artboard's iframe, overlay, selection and edit session.
128
+ *
129
+ * Exported for the drag coordinator, which holds one as an opaque handle and passes it back to the
130
+ * session API ({@link beginDragSession}/{@link hostDragGeometry}/{@link postDragMessage}) without
131
+ * reading its fields.
132
+ */
133
+ export interface HostState {
82
134
  iframe: HTMLIFrameElement;
83
135
  /** The `.canvas-panel-canvas` element hosting the iframe + overlay (the WeakMap key). */
84
136
  canvasEl: HTMLElement;
@@ -101,7 +153,7 @@ interface HostState {
101
153
  /** Primary selected path (mirrors `session.selection`'s last entry), for hover de-dupe. */
102
154
  selectionPath: (string | number)[] | null;
103
155
  /** The whole selection this host last measured, so a re-measure redraws every box. */
104
- selectionPaths: JxPath[];
156
+ selectionPaths: readonly JxPath[];
105
157
  /** Id of the most recent selection `measure` request, so stale `geometry` replies are dropped. */
106
158
  selReqId: number;
107
159
  /**
@@ -157,7 +209,9 @@ interface HostState {
157
209
  /** Highest snapshot `seq` seen, so stale (re-ordered) snapshots are dropped. */
158
210
  lastSnapshotSeq: number;
159
211
  /** The last non-null selection rect drawn (parent/overlay coords) — toolbar position fallback. */
160
- lastSelectionRect: ParentRect | null;
212
+ lastSelectionRect: OverlayPlacement | null;
213
+ /** The node the pointer last hovered (iframe-viewport rect), so a zoom can hold it still. */
214
+ lastHover: NodeHit | null;
161
215
  /**
162
216
  * The gen of the render/patch this iframe's DOM currently reflects (set from `renderComplete`/
163
217
  * `patchComplete`). Cross-frame drag replies (`dragOver`/`dropResult`) are dropped unless their
@@ -226,13 +280,7 @@ interface HostState {
226
280
  * render acks (`renderComplete`) at a bumped one — both satisfy `gen >= minGen`. An immediate
227
281
  * `enterEdit` would race the escalated ASYNC render and silently fail to find the element.
228
282
  */
229
- pendingEnterEdit: {
230
- path: (string | number)[];
231
- minGen: number;
232
- offset?: number;
233
- /** Set when the deferred re-entry is a PROP host rather than a block (see deferEnterEdit). */
234
- prop?: string;
235
- } | null;
283
+ pendingEnterEdit: PendingEnterEdit | null;
236
284
  /**
237
285
  * Stylebook capability (set by {@link mountStylebookCanvas}, cleared by page mounts). The specimen
238
286
  * doc's paths decode to TAGS, not tab-document paths: hits route to the injected stylebook
@@ -248,13 +296,6 @@ interface HostState {
248
296
  panReqId: number;
249
297
  }
250
298
 
251
- /**
252
- * Opaque drag-target host handle for the coordinator bridge. The bridge passes it back to the
253
- * session API ({@link beginDragSession}/{@link hostDragGeometry}/{@link postDragMessage}) without
254
- * reading its fields.
255
- */
256
- export type DragHost = HostState;
257
-
258
299
  const hosts = new WeakMap<HTMLElement, HostState>();
259
300
 
260
301
  /** Every live host, so the selection watcher can re-measure each one when selection changes. */
@@ -269,7 +310,7 @@ const liveHosts = new Set<HostState>();
269
310
  let currentDragSeq = 0;
270
311
 
271
312
  /** Parent-retained source data keyed by `dragSeq` (the block fragment never crosses the boundary). */
272
- const retainedSrcData = new Map<number, Record<string, unknown>>();
313
+ const retainedSrcData = new Map<number, RetainedDragData>();
273
314
 
274
315
  /**
275
316
  * The last session whose cursor-carrying `dragOver` arrived FROM an iframe, and when. While a
@@ -419,9 +460,10 @@ export function requestCanvasEval(
419
460
  }
420
461
  evalReqId += 1;
421
462
  const reqId = evalReqId;
422
- // Expression nodes come off the reactive document — only plain values may cross postMessage.
423
- // oxlint-disable-next-line unicorn/prefer-structured-clone
424
- const wireExprs = JSON.parse(JSON.stringify(exprs)) as { id: string; node: JxExpressionNode }[];
463
+ /* Expression nodes come off the reactive document — only plain values may cross postMessage.
464
+ SAFETY: A request's nodes are expression nodes by the caller's contract, and the frame re-checks
465
+ `operator` before evaluating one (`iframe-eval.ts`), reporting anything else per expression. */
466
+ const wireExprs = plainCopy(exprs) as { id: string; node: JxExpressionNode }[];
425
467
  target.channel.post({
426
468
  contextPath: contextPath ? [...contextPath] : null,
427
469
  exprs: wireExprs,
@@ -539,7 +581,7 @@ export function liveDragHostAt(cursor: { x: number; y: number }): HostState | nu
539
581
  export function beginDragSession(
540
582
  host: HostState,
541
583
  src: DragSrcKind,
542
- srcData: Record<string, unknown>,
584
+ srcData: RetainedDragData,
543
585
  ): number {
544
586
  currentDragSeq += 1;
545
587
  retainedSrcData.set(currentDragSeq, srcData);
@@ -567,7 +609,7 @@ export function beginDragSession(
567
609
  export function adoptDragSession(
568
610
  _host: HostState,
569
611
  _src: DragSrcKind,
570
- srcData: Record<string, unknown>,
612
+ srcData: RetainedDragData,
571
613
  seq: number,
572
614
  ): number {
573
615
  currentDragSeq = seq;
@@ -585,10 +627,7 @@ export function currentDragSession(): number {
585
627
  * read FRESH so a pan/zoom mid-drag is reflected. NOT `effectiveZoom()` (a separate path that can
586
628
  * desync). The matching iframe parent-viewport rect is returned so the cursor map cancels the pan.
587
629
  */
588
- export function hostDragGeometry(host: HostState): {
589
- scale: number;
590
- rect: { left: number; top: number };
591
- } {
630
+ export function hostDragGeometry(host: HostState): DragGeometry {
592
631
  const rect = rectOf(host.iframe);
593
632
  const scale = host.iframe.clientWidth > 0 ? rect.width / host.iframe.clientWidth : 1;
594
633
  return { rect, scale };
@@ -749,6 +788,38 @@ function measureIn(
749
788
  });
750
789
  }
751
790
 
791
+ /** An element on screen that a zoom should hold still: where it is now, and where it went. */
792
+ export interface PointerAnchor {
793
+ /** The element's top-document `top` when the anchor was taken. */
794
+ top: number;
795
+ /** Measure it again, after the zoom has reflowed the frame; null when it is gone. */
796
+ settle: () => Promise<number | null>;
797
+ }
798
+
799
+ /**
800
+ * The node under the pointer in `canvasEl`'s frame, as a zoom anchor — or null when the pointer has
801
+ * hovered nothing there.
802
+ *
803
+ * Edit's zoom REFLOWS: the frame's layout width changes, text rewraps, and everything above the
804
+ * cursor changes height, so no arithmetic on the old geometry can say where the line under the
805
+ * cursor will land. The frame can: it reported that node on hover, and it measures it again once
806
+ * the new layout exists.
807
+ */
808
+ export function pointerAnchor(canvasEl: HTMLElement): PointerAnchor | null {
809
+ const host = hostForCanvas(canvasEl);
810
+ const hover = host?.lastHover;
811
+ if (!host || !hover) {
812
+ return null;
813
+ }
814
+ return {
815
+ settle: async () => {
816
+ const point = await measureIn(host, hover.path);
817
+ return point?.top ?? null;
818
+ },
819
+ top: pointForRect(host, hover.rect).top,
820
+ };
821
+ }
822
+
752
823
  /**
753
824
  * Where the node at `path` is on screen right now, or null when no canvas can answer.
754
825
  *
@@ -1283,7 +1354,7 @@ export function postDialogOpen(tab: { id: string }, path: JxPath | null): void {
1283
1354
  * `host` is deliberately not a pane-scoped parameter name (see `PANE_PARAM_NAMES`), and the body
1284
1355
  * reads no focus: `panelHostingCanvas` resolves the stage from the host's own element.
1285
1356
  */
1286
- export function postPatchToHosts(forwardOps: WireDocOp[], tabId: string | null): number {
1357
+ export function postPatchToHosts(forwardOps: JxDocOp[], tabId: string | null): number {
1287
1358
  let posted = 0;
1288
1359
  for (const host of liveHosts) {
1289
1360
  if (!host.iframe.isConnected) {
@@ -1423,14 +1494,14 @@ function hostTab(state: HostState): Tab | null {
1423
1494
  }
1424
1495
 
1425
1496
  /**
1426
- * The tab a drag TARGET is showing — the one public reader on the opaque {@link DragHost} handle.
1497
+ * The tab a drag TARGET is showing — the one public reader on the opaque {@link HostState} handle.
1427
1498
  *
1428
1499
  * `panels/canvas-dnd-bridge.ts` binds a session to the host under the cursor and then had nothing
1429
1500
  * to ask it, so the one fact it needed about the drag — what the dragged node is CALLED — came from
1430
1501
  * `activeTab`. The bridge is not supposed to read the handle's fields (that is what "opaque"
1431
1502
  * means), so the answer is a function rather than a widened type.
1432
1503
  */
1433
- export function dragHostTab(host: DragHost): Tab | null {
1504
+ export function dragHostTab(host: HostState): Tab | null {
1434
1505
  return hostTab(host);
1435
1506
  }
1436
1507
 
@@ -1465,7 +1536,7 @@ function shownSelections(): Map<string, readonly JxPath[]> {
1465
1536
  if (!tab) {
1466
1537
  continue;
1467
1538
  }
1468
- const sel = (tab.session.selection ?? []) as readonly JxPath[];
1539
+ const sel: readonly JxPath[] = tab.session.selection;
1469
1540
  // Track the selection deeply enough that a change WITHIN the set re-measures: the effect
1470
1541
  // Reads every path, not just the array reference.
1471
1542
  void sel.map((path) => path.join("/")).join("|");
@@ -1664,7 +1735,7 @@ function requestSelection(host: HostState, sel: readonly JxPath[]): void {
1664
1735
  }
1665
1736
  const primary = primarySelection(sel);
1666
1737
  host.selectionPath = primary;
1667
- host.selectionPaths = sel as JxPath[];
1738
+ host.selectionPaths = sel;
1668
1739
  if (!host.iframe.isConnected) {
1669
1740
  liveHosts.delete(host);
1670
1741
  return;
@@ -1913,6 +1984,7 @@ function ensureHost(canvasEl: HTMLElement): HostState {
1913
1984
  coSelectionKeys: new Set<string>(),
1914
1985
  insertZone: null,
1915
1986
  lastRenderedGen: -1,
1987
+ lastHover: null,
1916
1988
  lastSelectionRect: null,
1917
1989
  lastSnapshotSeq: 0,
1918
1990
  overlay,
@@ -1982,7 +2054,7 @@ function hideInsertZoneNow(state: HostState): void {
1982
2054
  */
1983
2055
  function insertButtonHovered(state: HostState): boolean {
1984
2056
  const btn = state.overlay.insertButton;
1985
- return state.insertHover || (typeof btn.matches === "function" && btn.matches(":hover"));
2057
+ return state.insertHover || btn.matches(":hover");
1986
2058
  }
1987
2059
 
1988
2060
  /**
@@ -2039,6 +2111,7 @@ const PREVIEW_BLOCKED: ReadonlySet<IframeToParent["kind"]> = new Set([
2039
2111
  "dropResult",
2040
2112
  "editCommit",
2041
2113
  "editCommitProp",
2114
+ "editInput",
2042
2115
  "editInsert",
2043
2116
  "editMerge",
2044
2117
  "editRangeReplace",
@@ -2137,7 +2210,8 @@ function handleMessage(state: HostState, msg: IframeToParent): void {
2137
2210
  behind that message with it. The null check is the same guard for a `targetPath` an edit
2138
2211
  has since invalidated, which threw for the same reason. */
2139
2212
  const target = getNodeAtPath(tab.doc.document, msg.targetPath);
2140
- if (!target || typeof target !== "object") {
2213
+ // A path can resolve to a string child or to nothing at all, whatever the declared type says.
2214
+ if (!isNodeObject(target)) {
2141
2215
  return;
2142
2216
  }
2143
2217
  if (POPOVER_COMMANDS.has(msg.command)) {
@@ -2272,6 +2346,7 @@ function handleMessage(state: HostState, msg: IframeToParent): void {
2272
2346
  }
2273
2347
  return;
2274
2348
  }
2349
+ state.lastHover = msg.hit;
2275
2350
  drawHover(state, msg.hit);
2276
2351
  return;
2277
2352
  }
@@ -2333,7 +2408,7 @@ function handleMessage(state: HostState, msg: IframeToParent): void {
2333
2408
  // The iframe could not resolve is simply absent from `hits`.
2334
2409
  const primaryKey = JSON.stringify(state.selectionPath);
2335
2410
  const co: OverlayPlacement[] = [];
2336
- let rect: ParentRect | null = null;
2411
+ let rect: OverlayPlacement | null = null;
2337
2412
  if (state.stylebook) {
2338
2413
  const [hit] = msg.hits;
2339
2414
  rect = hit ? canvasRectToParent(hit.rect) : null;
@@ -2629,6 +2704,15 @@ function handleMessage(state: HostState, msg: IframeToParent): void {
2629
2704
  }
2630
2705
  return;
2631
2706
  }
2707
+ case "editInput": {
2708
+ // The first keystroke of a burst: the text lands on the idle tick, but the tab has unsaved
2709
+ // Work from now, and Save's enablement reads exactly this flag.
2710
+ const tab = hostTab(state);
2711
+ if (tab) {
2712
+ tab.doc.dirty = true;
2713
+ }
2714
+ return;
2715
+ }
2632
2716
  case "editCommit": {
2633
2717
  // Route to the tab this host's iframe renders — NOT activeTab, which may have changed while
2634
2718
  // The message was in flight (the cross-document bleed).
@@ -2716,9 +2800,8 @@ function handleMessage(state: HostState, msg: IframeToParent): void {
2716
2800
  const { rect: ifr, scale } = hostDragGeometry(state);
2717
2801
  const left = msg.rect.x * scale + ifr.left;
2718
2802
  const top = msg.rect.y * scale + ifr.top;
2719
- canvasSlashHandler?.show({
2803
+ const request: CanvasSlashRequest = {
2720
2804
  filter: msg.filter,
2721
- ...(msg.showFilter === true ? { showFilter: true } : {}),
2722
2805
  onDismiss: () => state.channel.post({ kind: "slashDismissed" }),
2723
2806
  onSelect: (cmd) => state.channel.post({ cmd: { ...cmd }, kind: "slashSelect" }),
2724
2807
  rect: {
@@ -2728,7 +2811,11 @@ function handleMessage(state: HostState, msg: IframeToParent): void {
2728
2811
  top,
2729
2812
  width: msg.rect.width * scale,
2730
2813
  },
2731
- });
2814
+ };
2815
+ if (msg.showFilter === true) {
2816
+ request.showFilter = true;
2817
+ }
2818
+ canvasSlashHandler?.show(request);
2732
2819
  return;
2733
2820
  }
2734
2821
  case "slashNav": {
@@ -2825,12 +2912,14 @@ function deferEnterEdit(
2825
2912
  offset?: number,
2826
2913
  prop?: string,
2827
2914
  ): void {
2828
- state.pendingEnterEdit = {
2829
- minGen: state.lastRenderedGen,
2830
- path: [...path],
2831
- ...(offset === undefined ? {} : { offset }),
2832
- ...(prop === undefined ? {} : { prop }),
2833
- };
2915
+ const pending: PendingEnterEdit = { minGen: state.lastRenderedGen, path: [...path] };
2916
+ if (offset !== undefined) {
2917
+ pending.offset = offset;
2918
+ }
2919
+ if (prop !== undefined) {
2920
+ pending.prop = prop;
2921
+ }
2922
+ state.pendingEnterEdit = pending;
2834
2923
  }
2835
2924
 
2836
2925
  /** Ask the host's iframe to (re-)enter inline editing on `path` (a plain copy crosses the bridge). */
@@ -2840,12 +2929,14 @@ function reenterEdit(
2840
2929
  offset?: number,
2841
2930
  prop?: string,
2842
2931
  ): void {
2843
- state.channel.post({
2844
- kind: "enterEdit",
2845
- path: [...path],
2846
- ...(offset === undefined ? {} : { offset }),
2847
- ...(prop === undefined ? {} : { prop }),
2848
- });
2932
+ const enter: EnterEditMsg = { kind: "enterEdit", path: [...path] };
2933
+ if (offset !== undefined) {
2934
+ enter.offset = offset;
2935
+ }
2936
+ if (prop !== undefined) {
2937
+ enter.prop = prop;
2938
+ }
2939
+ state.channel.post(enter);
2849
2940
  }
2850
2941
 
2851
2942
  /**
@@ -3041,11 +3132,9 @@ function preparePassRender(
3041
3132
  // So a JSON round-trip (NOT structuredClone, which would throw) drops residual functions /
3042
3133
  // Reactive proxy artifacts that would otherwise raise DataCloneError and silently drop the
3043
3134
  // Entire message.
3044
- // oxlint-disable-next-line unicorn/prefer-structured-clone
3045
- const cloneableDoc = JSON.parse(JSON.stringify(resolved.renderDoc)) as unknown;
3135
+ const cloneableDoc = plainCopy(resolved.renderDoc);
3046
3136
  // The RAW page doc (forward-op + data-jx-path coordinate space) crosses as the shadow doc.
3047
- // oxlint-disable-next-line unicorn/prefer-structured-clone
3048
- const cloneableShadow = JSON.parse(JSON.stringify(doc)) as unknown;
3137
+ const cloneableShadow = plainCopy(doc);
3049
3138
  // Which tags can hold a caret depends on the document's vocabulary, so the format's verdicts
3050
3139
  // Ride with the render rather than being baked into the frame. Absent for a native document,
3051
3140
  // Where the studio's own element metadata answers on its own.
@@ -3055,21 +3144,27 @@ function preparePassRender(
3055
3144
  const renderTab = tabId ? (workspace.tabs.get(tabId) ?? null) : null;
3056
3145
  const formatElements = formatByName(renderTab?.doc.sourceFormat)?.studio?.elements;
3057
3146
  const editableTags = formatElements ? formatEditableVerdicts(formatElements) : undefined;
3058
- return {
3147
+ const prepared: PreparedRender = {
3059
3148
  assets: resolved.assets,
3060
3149
  doc: cloneableDoc,
3061
3150
  docBase: resolved.docBase ?? `${canvasBaseOrigin()}/`,
3062
3151
  mapperCtx: resolved.mapperCtx,
3063
- /* Still a cast, but no longer a lie: `WireMapperCtx.canvasMode` is typed `string` (it mirrors
3064
- `PathMapCtx`, which carries the mode for `isEditableMode` and takes `CanvasMode | string`),
3065
- so this narrows a string to the union. It used to narrow it to a union that did not list
3066
- `git-diff` while git-diff renders went through here every day. */
3152
+ /* SAFETY: `WireMapperCtx.canvasMode` is typed `string` only because it mirrors `PathMapCtx`,
3153
+ which takes `CanvasMode | string`; `resolveCanvasDocument` fills it with the mode this host
3154
+ was asked to render (`modeOverride ?? canvasModeOfTab(tab)`), and only a canvas mode ever
3155
+ mounts a host. It used to narrow to a union that did not list `git-diff` while git-diff
3156
+ renders went through here every day. */
3067
3157
  mode: resolved.mapperCtx.canvasMode as CanvasMode,
3068
3158
  shadowDoc: cloneableShadow,
3069
3159
  siteStyle: resolved.siteStyle,
3070
- ...(editableTags ? { editableTags } : {}),
3071
- ...(allowAutoRequests ? { allowAutoRequests: true } : {}),
3072
3160
  };
3161
+ if (editableTags) {
3162
+ prepared.editableTags = editableTags;
3163
+ }
3164
+ if (allowAutoRequests) {
3165
+ prepared.allowAutoRequests = true;
3166
+ }
3167
+ return prepared;
3073
3168
  });
3074
3169
  byDoc.set(doc, { mode: modeOverride, payload, tabId, viewTabId });
3075
3170
  return payload;
@@ -3134,18 +3229,12 @@ export async function mountIframeCanvas(
3134
3229
  paneOfContainer(canvasEl),
3135
3230
  modeOverride,
3136
3231
  );
3137
- const message: ParentToIframe = {
3232
+ const message: RenderMsg = {
3138
3233
  ...prepared,
3139
3234
  // Per-TAB, and read at POST time so a scheme flip that raced the shared resolution is not
3140
3235
  // Baked into the payload every host shares. `viewTab` is this artboard's tab — defaulted from
3141
3236
  // `tabOfContainer(canvasEl)`, the same route the rest of this mount takes.
3142
3237
  colorScheme: schemeWireFor(viewTab),
3143
- /* Per-ARTBOARD, and post-time for a sharper version of the same reason. `preparePassRender`
3144
- memoizes on the document and hands the SAME payload object to every host in the pass, and a
3145
- git-diff pass has two hosts holding two documents — but marks are a function of BOTH sides,
3146
- not of the document this host draws, so they are not derivable from that cache key at all.
3147
- Baking them in would make the two artboards race for whose marks the pass kept. */
3148
- ...(diffMarks ? { diffMarks } : {}),
3149
3238
  gen,
3150
3239
  kind: "render",
3151
3240
  // Read at POST time for the same reason `colorScheme` is: a render replaces the DOM, so a panel
@@ -3153,6 +3242,15 @@ export async function mountIframeCanvas(
3153
3242
  popoverOpen: viewTab?.session.ui.openPopover ?? null,
3154
3243
  dialogOpen: viewTab?.session.ui.openDialog ?? null,
3155
3244
  };
3245
+ /* Diff marks are per-ARTBOARD, and post-time for a sharper version of the colour scheme's
3246
+ reason. `preparePassRender` memoizes on the document and hands the SAME payload object to every
3247
+ host in the pass, and a git-diff pass has two hosts holding two documents — but marks are a
3248
+ function of BOTH sides, not of the document this host draws, so they are not derivable from
3249
+ that cache key at all. Baking them in would make the two artboards race for whose marks the
3250
+ pass kept. */
3251
+ if (diffMarks) {
3252
+ message.diffMarks = diffMarks;
3253
+ }
3156
3254
  state.popoverOpen = message.popoverOpen ?? null;
3157
3255
  state.dialogOpen = message.dialogOpen ?? null;
3158
3256
  // Preview is the fidelity view: no editing messages are honoured from it, no overlay is painted
@@ -3204,10 +3302,8 @@ export function mountStylebookCanvas(
3204
3302
  state.iframe.style.width = widthPx ? `${widthPx}px` : "100%";
3205
3303
  // Two independent plain clones: the iframe renders `doc` and folds styleUpdates into `shadowDoc`
3206
3304
  // (and fake test channels pass messages by reference, so sharing one object would alias them).
3207
- // oxlint-disable-next-line unicorn/prefer-structured-clone
3208
- const cloneableDoc = JSON.parse(JSON.stringify(generated.doc)) as unknown;
3209
- // oxlint-disable-next-line unicorn/prefer-structured-clone
3210
- const cloneableShadow = JSON.parse(JSON.stringify(generated.doc)) as unknown;
3305
+ const cloneableDoc = plainCopy(generated.doc);
3306
+ const cloneableShadow = plainCopy(generated.doc);
3211
3307
  const message: ParentToIframe = {
3212
3308
  // The specimen carries no tab identity, but the STAGE it is mounted into does: a stylebook is
3213
3309
  // Opened as a tab like anything else, and its `previewColorScheme` is that tab's.
@@ -3325,8 +3421,7 @@ export function postLocaleToLiveHosts(locale: string | null, root?: HTMLElement
3325
3421
  * off a tab's reactive record is a proxy, and a proxy cannot be structured-cloned.
3326
3422
  */
3327
3423
  export function postRedefineElementToLiveHosts(doc: JxMutableNode, base: string): number {
3328
- // oxlint-disable-next-line unicorn/prefer-structured-clone
3329
- const plain = JSON.parse(JSON.stringify(doc)) as JxMutableNode;
3424
+ const plain = plainCopy(doc);
3330
3425
  let posted = 0;
3331
3426
  for (const host of liveHosts) {
3332
3427
  if (!host.iframe.isConnected) {
@@ -3352,13 +3447,8 @@ export function postSiteStyleToLiveHosts(): void {
3352
3447
  return;
3353
3448
  }
3354
3449
  // Site config comes off a reactive store — only plain values may cross postMessage.
3355
- // oxlint-disable-next-line unicorn/prefer-structured-clone
3356
- const siteStyle = JSON.parse(JSON.stringify(config.style ?? null)) as Record<
3357
- string,
3358
- unknown
3359
- > | null;
3360
- // oxlint-disable-next-line unicorn/prefer-structured-clone
3361
- const media = JSON.parse(JSON.stringify(config.$media ?? {})) as Record<string, string>;
3450
+ const siteStyle = plainCopy(config.style ?? null);
3451
+ const media = plainCopy(config.$media ?? {});
3362
3452
  for (const host of liveHosts) {
3363
3453
  if (!host.iframe.isConnected) {
3364
3454
  liveHosts.delete(host);
@@ -3376,11 +3466,10 @@ export function postSiteStyleToLiveHosts(): void {
3376
3466
  * received it — zero means no stylebook iframe is live yet and the caller should fall through to a
3377
3467
  * full render. Each post is followed by a selection re-measure so the box tracks the reflow.
3378
3468
  */
3379
- export function postStyleUpdateToStylebookHosts(style: Record<string, unknown>): number {
3469
+ export function postStyleUpdateToStylebookHosts(style: JxStyle): number {
3380
3470
  let posted = 0;
3381
3471
  // Style objects come off the reactive doc — only plain values may cross postMessage.
3382
- // oxlint-disable-next-line unicorn/prefer-structured-clone
3383
- const cloneable = JSON.parse(JSON.stringify(style)) as Record<string, unknown>;
3472
+ const cloneable = plainCopy(style);
3384
3473
  for (const host of liveHosts) {
3385
3474
  if (!host.iframe.isConnected) {
3386
3475
  liveHosts.delete(host);
@@ -3416,12 +3505,8 @@ export function panToStylebookTag(tag: string): void {
3416
3505
 
3417
3506
  // ─── Format-toolbar bridge (Phase 4b-2) ─────────────────────────────────────────
3418
3507
 
3419
- /** The current edit session's editing flag + latest selection snapshot, for the parent toolbar. */
3420
- export function getEditSnapshot(): {
3421
- editing: boolean;
3422
- editingProp: string | null;
3423
- snapshot: SelectionSnapshot | null;
3424
- } {
3508
+ /** The current edit session's state, for the parent toolbar. */
3509
+ export function getEditSnapshot(): EditSnapshotState {
3425
3510
  /* The same connectivity guard {@link isCaretActive} has, and for the same reason: a session
3426
3511
  belongs to a frame, and a frame that has left the document has no caret in it. `releaseHost`
3427
3512
  clears `activeEditHost`, so this is the second line rather than the first — it covers the frame
@@ -3532,31 +3617,40 @@ export function commitActiveEditSession(): void {
3532
3617
  /** The live host backing the active panel's canvas (for non-edit selection-bar positioning). */
3533
3618
  function hostForActivePanel(): HostState | null {
3534
3619
  const panel = getActivePanel();
3535
- return panel ? hostForCanvas(panel.canvas as HTMLElement) : null;
3620
+ return panel ? hostForCanvas(panel.canvas) : null;
3536
3621
  }
3537
3622
 
3538
3623
  /**
3539
3624
  * The format toolbar's anchor rect, in PARENT-VIEWPORT space (the bar is `position:fixed`). Both
3540
- * source rects — the edit session's caret snapshot and the `lastSelectionRect` fallback — are in
3541
- * UNSCALED iframe-viewport px (D-2: the overlay draws them inside the scaled panzoom-wrap, so the
3542
- * browser applies the zoom there); the fixed bar gets no such free ride, so scale by the live
3543
- * empirical zoom ({@link hostDragGeometry}) and add the iframe's on-screen offset, whose GBCR
3544
- * already bakes in pan + zoom + ancestor scroll. The empirical ratio covers BOTH scale sources:
3545
- * design mode's panzoom-wrap transform and edit mode's content-zoom counter-scale (where it
3546
- * evaluates to exactly `editZoom` — the iframe's layout width is `renderWidth / editZoom` while its
3547
- * rendered width is `renderWidth`).
3548
- */
3549
- export function getEditBarAnchorRect(): ParentRect | null {
3550
- // The format toolbar follows the live caret/selection snapshot of the active edit session; the
3551
- // Structural bar (tag badge / parent selector / move / convert / drag handle) must still position
3552
- // On a plain selection with no inline-edit session, so fall back to the active panel's host and
3553
- // Its last measured selection rect.
3625
+ * source rects — the selected element's `lastSelectionRect` and the edit session's caret-snapshot
3626
+ * fallback — are in UNSCALED iframe-viewport px (D-2: the overlay draws them inside the scaled
3627
+ * panzoom-wrap, so the browser applies the zoom there); the fixed bar gets no such free ride, so
3628
+ * scale by the live empirical zoom ({@link hostDragGeometry}) and add the iframe's on-screen
3629
+ * offset, whose GBCR already bakes in pan + zoom + ancestor scroll. The empirical ratio covers BOTH
3630
+ * scale sources: design mode's panzoom-wrap transform and edit mode's content-zoom counter-scale
3631
+ * (where it evaluates to exactly `editZoom` — the iframe's layout width is `renderWidth / editZoom`
3632
+ * while its rendered width is `renderWidth`).
3633
+ */
3634
+ export function getEditBarAnchorRect(): OverlayPlacement | null {
3635
+ // The bar rests on the SELECTED ELEMENT's box, edit session or not. It used to prefer the caret's
3636
+ // Snapshot rect while editing, so it hopped line to line with the caret and sat over the text the
3637
+ // Author was typing; the snapshot still feeds the format state, never the position. Only an edit
3638
+ // Session with no measured element yet falls back to the caret rect.
3554
3639
  const editHost = activeEditHost;
3555
3640
  const host = editHost ?? hostForActivePanel();
3556
3641
  if (!host) {
3557
3642
  return null;
3558
3643
  }
3559
3644
  const { rect: ifr, scale } = hostDragGeometry(host);
3645
+ if (host.lastSelectionRect) {
3646
+ // Overlay-local (same top-left + coordinate space as the iframe viewport).
3647
+ return {
3648
+ height: host.lastSelectionRect.height * scale,
3649
+ left: host.lastSelectionRect.left * scale + ifr.left,
3650
+ top: host.lastSelectionRect.top * scale + ifr.top,
3651
+ width: host.lastSelectionRect.width * scale,
3652
+ };
3653
+ }
3560
3654
  const snapRect = host === editHost ? host.snapshot?.rect : null;
3561
3655
  if (snapRect) {
3562
3656
  return {
@@ -3566,14 +3660,5 @@ export function getEditBarAnchorRect(): ParentRect | null {
3566
3660
  width: snapRect.width * scale,
3567
3661
  };
3568
3662
  }
3569
- if (host.lastSelectionRect) {
3570
- // The fallback rect is overlay-local (same top-left + coordinate space as the iframe viewport).
3571
- return {
3572
- height: host.lastSelectionRect.height * scale,
3573
- left: host.lastSelectionRect.left * scale + ifr.left,
3574
- top: host.lastSelectionRect.top * scale + ifr.top,
3575
- width: host.lastSelectionRect.width * scale,
3576
- };
3577
- }
3578
3663
  return null;
3579
3664
  }