@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
@@ -4,7 +4,7 @@
4
4
  * template creation, centering, transform application, zoom indicator, and fit-to-screen.
5
5
  */
6
6
 
7
- import type { CanvasPanel, JsonValue } from "../types";
7
+ import type { CanvasPanel } from "../types";
8
8
 
9
9
  import { renderOnly } from "../store";
10
10
  import {
@@ -39,6 +39,7 @@ import {
39
39
  import type { CanvasStageHandle, CanvasStagePanelItem } from "../surfaces/canvas-stage";
40
40
  import type { AnyCommand, CommandRegistry } from "../commands/registry";
41
41
  import type { CommandArgValues } from "../commands/command-args";
42
+ import { isNumber, isString } from "@jxsuite/schema/guards";
42
43
  import type { Tab } from "../tabs/tab";
43
44
 
44
45
  /*
@@ -106,6 +107,22 @@ function setZoomOf(surface: CanvasSurface, zoom: number): void {
106
107
  }
107
108
  }
108
109
 
110
+ /** The {@link CanvasPanel} fields that hold the board's drawn nodes. */
111
+ type CanvasPanelNodeKey = "canvas" | "element" | "scrollContainer" | "viewport";
112
+
113
+ /**
114
+ * A panel record as it may be before {@link bindCanvasPanels} has run: its DOM fields can still be
115
+ * null, because {@link canvasPanelEntry} builds them that way. A bound {@link CanvasPanel} is
116
+ * assignable to it, so a reader that must tolerate an unbound board takes this type.
117
+ */
118
+ export type UnboundCanvasPanel = Omit<CanvasPanel, CanvasPanelNodeKey> &
119
+ Record<CanvasPanelNodeKey, HTMLElement | null>;
120
+
121
+ /** Whether the stage has wired a board's canvas and viewport nodes into its record yet. */
122
+ export function isPanelBound(panel: UnboundCanvasPanel): boolean {
123
+ return Boolean(panel.canvas && panel.viewport);
124
+ }
125
+
109
126
  /**
110
127
  * One artboard: the record the geometry reads, and the item the stage document draws.
111
128
  *
@@ -139,7 +156,7 @@ export function canvasPanelEntry(
139
156
  fullWidth: boolean,
140
157
  width: number | null = null,
141
158
  ): CanvasPanelEntry {
142
- const panel = {
159
+ const unbound: UnboundCanvasPanel = {
143
160
  _width: width || null,
144
161
  canvas: null,
145
162
  element: null,
@@ -148,7 +165,11 @@ export function canvasPanelEntry(
148
165
  renderScope: null,
149
166
  scrollContainer: null,
150
167
  viewport: null,
151
- } as unknown as CanvasPanel;
168
+ };
169
+ /* SAFETY: the record is handed out unbound and filled in place by `bindCanvasPanels` once the
170
+ stage has drawn it; every reader that can run before that (`tabOfMountedPanel`, `applyEditZoom`,
171
+ `updateActivePanelHeaders`) reads it as an UnboundCanvasPanel and checks the nodes first. */
172
+ const panel = unbound as CanvasPanel;
152
173
  /* The viewport takes the declared width, the canvas takes it too — but a FULL-WIDTH board's
153
174
  viewport does not, because "full width" means "as wide as the box you are in". Carried as two
154
175
  custom properties rather than as `width:` so `applyEditZoom`'s inline write on the canvas still
@@ -186,9 +207,13 @@ export function canvasPanelEntry(
186
207
  */
187
208
  export function bindCanvasPanels(entries: CanvasPanelEntry[], stage: CanvasStageHandle): void {
188
209
  for (const { item, panel } of entries) {
189
- panel.element = stage.panelNode(item.key, "panel") as HTMLElement;
190
- panel.viewport = stage.panelNode(item.key, "panel-viewport") as HTMLElement;
191
- panel.canvas = stage.panelNode(item.key, "panel-canvas") as HTMLElement;
210
+ /* SAFETY: callers bind only after `drawStage` has settled, and the stage document draws the
211
+ `panel`, `panel-viewport` and `panel-canvas` parts for every item it was given, keyed by
212
+ `item.key`, so each lookup finds its node. */
213
+ const node = (part: string) => stage.panelNode(item.key, part) as HTMLElement;
214
+ panel.element = node("panel");
215
+ panel.viewport = node("panel-viewport");
216
+ panel.canvas = node("panel-canvas");
192
217
  }
193
218
  }
194
219
 
@@ -289,6 +314,36 @@ export function clampEditZoom(zoom: number): number {
289
314
  return Math.min(EDIT_ZOOM_MAX, Math.max(EDIT_ZOOM_MIN, zoom));
290
315
  }
291
316
 
317
+ /**
318
+ * Where an Edit-mode zoom is anchored: the scroller, the zoom the canvas is DRAWN at now, and the
319
+ * layout-px offset into the canvas of the content line at viewport-y `anchorY` (the cursor for a
320
+ * wheel, the scroller's middle for a button or a chord).
321
+ *
322
+ * The drawn zoom is read from the panel's applied layout width rather than from `ui.editZoom`,
323
+ * which a wheel burst has already moved on: the reactive write lands per event and the DOM once a
324
+ * frame.
325
+ */
326
+ function editZoomAnchor(
327
+ surface: CanvasSurface,
328
+ layoutWidth: number | null,
329
+ anchorY: number | null,
330
+ ): { layoutY: number; scroller: HTMLElement; zoom: number } | null {
331
+ const scroller = surface.wrap.querySelector<HTMLElement>('[part="edit-canvas"]');
332
+ const canvasEl = surface.panels[0]?.canvas;
333
+ if (!scroller || !canvasEl) {
334
+ return null;
335
+ }
336
+ const column = surface.wrap.querySelector<HTMLElement>('[part="edit-column"]');
337
+ const renderWidth = column ? rectOf(column).width : 0;
338
+ const zoom = layoutWidth && renderWidth > 0 ? renderWidth / layoutWidth : 1;
339
+ let y = anchorY;
340
+ if (y === null) {
341
+ const box = rectOf(scroller);
342
+ y = box.top + box.height / 2;
343
+ }
344
+ return { layoutY: Math.max(0, (y - rectOf(canvasEl).top) / zoom), scroller, zoom };
345
+ }
346
+
292
347
  /**
293
348
  * Apply the active tab's edit-mode content zoom to the single edit panel.
294
349
  *
@@ -309,19 +364,23 @@ export function clampEditZoom(zoom: number): number {
309
364
  * bare style writes only; the iframe's own ResizeObserver re-posts `contentHeight` after the
310
365
  * reflow, which also finalizes the viewport height.
311
366
  */
312
- export function applyEditZoom(surface: CanvasSurface = activeCanvasSurface()) {
367
+ export function applyEditZoom(
368
+ surface: CanvasSurface = activeCanvasSurface(),
369
+ anchorY: number | null = null,
370
+ ) {
313
371
  // THIS pane's effective mode. It was the focused pane's, so a side pane in Edit was refused its
314
372
  // Own content zoom whenever the primary happened to be in Design.
315
373
  if (canvasModeOfPane(surface.paneId) !== "edit") {
316
374
  return;
317
375
  }
318
376
  const [panel] = surface.panels;
319
- if (!panel?.canvas || !panel.viewport) {
377
+ if (!panel || !isPanelBound(panel)) {
320
378
  return;
321
379
  }
322
380
  const canvasEl = panel.canvas;
323
381
  const iframe = canvasEl.querySelector("iframe");
324
382
  const editZoom = tabOfSurface(surface)?.session.ui.editZoom ?? 1;
383
+ const anchor = editZoomAnchor(surface, panel._width ?? null, anchorY);
325
384
  if (editZoom === 1) {
326
385
  // Exactly today's fluid behavior — no inline width, no transform, auto viewport height.
327
386
  panel._width = null;
@@ -357,6 +416,11 @@ export function applyEditZoom(surface: CanvasSurface = activeCanvasSurface()) {
357
416
  panel.viewport.style.height = `${iframe.offsetHeight * editZoom}px`;
358
417
  }
359
418
  }
419
+ if (anchor) {
420
+ // The content line that was under the anchor goes back under it: it sat `layoutY` layout px
421
+ // Into the canvas, which now draws it `layoutY * (new - old)` px further down.
422
+ anchor.scroller.scrollTop += anchor.layoutY * (editZoom - anchor.zoom);
423
+ }
360
424
  // Re-anchor the fixed block-action-bar over the rescaled canvas.
361
425
  renderOnly("overlays");
362
426
  }
@@ -386,17 +450,25 @@ export function setEditZoom(zoom: number, surface: CanvasSurface = activeCanvasS
386
450
  */
387
451
  const _editZoomRafs = new Map<string, number>();
388
452
 
453
+ /** The viewport-y each stage's pending frame zooms around — the latest wheel event's cursor. */
454
+ const _editZoomAnchors = new Map<string, number | null>();
455
+
389
456
  /**
390
457
  * Wheel-rate edit-zoom setter: the reactive `editZoom` write lands immediately (the zoom pod's
391
458
  * label tracks it), but the DOM work — an iframe width resize, i.e. a real reflow — is coalesced to
392
459
  * one `applyEditZoom()` per animation frame so a fast ctrl+scroll burst doesn't thrash layout.
393
460
  */
394
- export function requestEditZoom(zoom: number, surface: CanvasSurface = activeCanvasSurface()) {
461
+ export function requestEditZoom(
462
+ zoom: number,
463
+ surface: CanvasSurface = activeCanvasSurface(),
464
+ anchorY: number | null = null,
465
+ ) {
395
466
  const tab = tabOfSurface(surface);
396
467
  if (!tab) {
397
468
  return;
398
469
  }
399
470
  tab.session.ui.editZoom = clampEditZoom(zoom);
471
+ _editZoomAnchors.set(surface.paneId, anchorY);
400
472
  if (_editZoomRafs.has(surface.paneId)) {
401
473
  return;
402
474
  }
@@ -404,7 +476,9 @@ export function requestEditZoom(zoom: number, surface: CanvasSurface = activeCan
404
476
  surface.paneId,
405
477
  requestAnimationFrame(() => {
406
478
  _editZoomRafs.delete(surface.paneId);
407
- applyEditZoom(surface);
479
+ const at = _editZoomAnchors.get(surface.paneId) ?? null;
480
+ _editZoomAnchors.delete(surface.paneId);
481
+ applyEditZoom(surface, at);
408
482
  }),
409
483
  );
410
484
  }
@@ -522,12 +596,12 @@ export const FIT_WORDS: readonly string[] = ["width", "page", "none"];
522
596
  * assistant sees. It stays as the typed read the body needs (`args.fit` is `unknown`) and as the
523
597
  * refusal for a caller that reaches the body directly.
524
598
  */
525
- function fitArg(commandId: string, args: Record<string, unknown>, key: string): FitMode {
599
+ function fitArg(commandId: string, args: CommandArgValues, key: string): FitMode {
526
600
  const value = args[key];
527
- if (typeof value === "string" && FIT_WORDS.includes(value)) {
528
- return value as FitMode;
601
+ if (isFitWord(value)) {
602
+ return value;
529
603
  }
530
- if (typeof value === "number" && value >= PAN_ZOOM_MIN && value <= PAN_ZOOM_MAX) {
604
+ if (isNumber(value) && value >= PAN_ZOOM_MIN && value <= PAN_ZOOM_MAX) {
531
605
  return value;
532
606
  }
533
607
  throw new RangeError(
@@ -536,6 +610,11 @@ function fitArg(commandId: string, args: Record<string, unknown>, key: string):
536
610
  );
537
611
  }
538
612
 
613
+ /** Whether a value is one of {@link FIT_WORDS}, the named (non-scale) fits. */
614
+ function isFitWord(value: unknown): value is Exclude<FitMode, number> {
615
+ return isString(value) && FIT_WORDS.includes(value);
616
+ }
617
+
539
618
  /** Declared fits, keyed per tab + document. */
540
619
  const _fits = new Map<string, FitMode>();
541
620
 
@@ -592,7 +671,7 @@ export function applyFit(fit?: FitMode, surface: CanvasSurface = activeCanvasSur
592
671
  applyTransform(surface);
593
672
  return;
594
673
  }
595
- if (typeof chosen === "number") {
674
+ if (isNumber(chosen)) {
596
675
  setZoomOf(surface, clampPanZoom(chosen));
597
676
  applyTransform(surface);
598
677
  return;
@@ -607,22 +686,15 @@ export function setFit(fit: FitMode, surface: CanvasSurface = activeCanvasSurfac
607
686
  applyFit(fit, surface);
608
687
  }
609
688
 
610
- /**
611
- * Record the active document's CURRENT pan-zoom as its declared fit.
612
- *
613
- * For the gesture paths that move the zoom themselves and then have to say so — ctrl+scroll writes
614
- * `ui.zoom` directly, pinch will too. An author-chosen zoom is a numeric fit, so re-entering the
615
- * mode restores it instead of re-framing over the top of it.
616
- */
617
- export function markExplicitZoom(surface: CanvasSurface = activeCanvasSurface()): void {
618
- declareFit(clampPanZoom(zoomOf(surface)), surface);
619
- }
620
-
621
689
  /**
622
690
  * Set the active tab's pan-zoom on the author's behalf: clamped, declared as this document's fit,
623
691
  * and applied. Every author-facing pan-zoom control routes through here.
624
692
  */
625
- export function setUserZoom(zoom: number, surface: CanvasSurface = activeCanvasSurface()): void {
693
+ export function setUserZoom(
694
+ zoom: number,
695
+ surface: CanvasSurface = activeCanvasSurface(),
696
+ at: { clientX: number; clientY: number } | null = null,
697
+ ): void {
626
698
  const tab = tabOfSurface(surface);
627
699
  if (!tab) {
628
700
  return;
@@ -631,7 +703,23 @@ export function setUserZoom(zoom: number, surface: CanvasSurface = activeCanvasS
631
703
  // Synchronously on assignment, and the zoom pod reads `getFit()` while it renders — so declaring
632
704
  // Second means the control repaints one interaction behind the state it is reporting.
633
705
  const next = clampPanZoom(zoom);
706
+ const ratio = next / zoomOf(surface);
634
707
  declareFit(next, surface);
708
+ if (surface.panzoomWrap && ratio !== 1) {
709
+ /* Zoom AROUND a point: the cursor for a wheel, the stage's middle for a button or a chord.
710
+ Measured from the wrap's own drawn box, not the stage's: the wrap's layout origin is not the
711
+ stage's (it centres itself with `margin-block: auto`, and Stylebook puts a lead slot before
712
+ it), and the cursor-minus-stage arithmetic this replaced drifted by exactly that offset. A
713
+ content point `d` px into the drawn box is `d * ratio` px in after the zoom, so the pan
714
+ moves by the difference. */
715
+ const box = rectOf(surface.panzoomWrap);
716
+ const stage = rectOf(surface.wrap);
717
+ const x = at?.clientX ?? stage.left + stage.width / 2;
718
+ const y = at?.clientY ?? stage.top + stage.height / 2;
719
+ surface.panX += (x - box.left) * (1 - ratio);
720
+ surface.panY += (y - box.top) * (1 - ratio);
721
+ surface.needsCenter = false;
722
+ }
635
723
  setZoomOf(surface, next);
636
724
  applyTransform(surface);
637
725
  }
@@ -795,6 +883,11 @@ export type CanvasMode = (typeof CANVAS_MODES)[number];
795
883
  /** The base modes the preview flag composes with — the two the Canvas view axis shows it beside. */
796
884
  const PREVIEWABLE_BASE_MODES = new Set(["edit", "design"]);
797
885
 
886
+ /** Whether a base mode is one the preview flag composes with: a member of PREVIEWABLE_BASE_MODES. */
887
+ function isPreviewableBase(mode: string): mode is "edit" | "design" {
888
+ return PREVIEWABLE_BASE_MODES.has(mode);
889
+ }
890
+
798
891
  // ─── The Canvas view (§4.2) ──────────────────────────────────────────────────
799
892
  // `Edit │ Design` is a radio and `Preview` is a TOGGLE beside it, because that is what the storage
800
893
  // Has always been: `preview` is not a base mode, it is a flag that composes with one.
@@ -855,7 +948,13 @@ export function canvasBaseViewsFor(tab: ViewableTab): CanvasView[] {
855
948
  /** The base view the tab returns to — what the radio marks while preview is on. */
856
949
  export function canvasBaseViewOf(tab: ViewableTab): CanvasView | null {
857
950
  const base = tab.session.ui.canvasMode;
858
- return PREVIEWABLE_BASE_MODES.has(base) ? (base as CanvasView) : null;
951
+ return isPreviewableBase(base) ? base : null;
952
+ }
953
+
954
+ /** A tab's Preview toggle: whether it is offered, and whether it is pressed. */
955
+ export interface PreviewState {
956
+ available: boolean;
957
+ on: boolean;
859
958
  }
860
959
 
861
960
  /**
@@ -864,7 +963,7 @@ export function canvasBaseViewOf(tab: ViewableTab): CanvasView | null {
864
963
  * `available` is a fact about the DOCUMENT (its declared modes) and about the base it is currently
865
964
  * in — `source` composes with no preview, so the toggle is not offered there.
866
965
  */
867
- export function previewStateOf(tab: ViewableTab): { available: boolean; on: boolean } {
966
+ export function previewStateOf(tab: ViewableTab): PreviewState {
868
967
  return {
869
968
  available:
870
969
  tab.capabilities.modes.includes("preview") &&
@@ -983,7 +1082,7 @@ export function canvasViewCommands(deps: CanvasCommandDeps): AnyCommand[] {
983
1082
  * than the control it replaced, which is the opposite of the point.
984
1083
  */
985
1084
  function contextTab(commandId: string, args: CommandArgValues) {
986
- const { pane } = args as { pane?: unknown };
1085
+ const { pane } = args;
987
1086
  if (pane === undefined) {
988
1087
  return requireTab(commandId);
989
1088
  }
@@ -1015,8 +1114,8 @@ export function canvasViewCommands(deps: CanvasCommandDeps): AnyCommand[] {
1015
1114
 
1016
1115
  /** The pane a rendering-context verb addresses, named or focused. {@link repaint}'s own rule. */
1017
1116
  function paneOfArgs(args: CommandArgValues): string {
1018
- const { pane } = args as { pane?: unknown };
1019
- return typeof pane === "string" ? pane : workspace.activePaneId;
1117
+ const { pane } = args;
1118
+ return isString(pane) ? pane : workspace.activePaneId;
1020
1119
  }
1021
1120
 
1022
1121
  return [
@@ -1200,9 +1299,9 @@ export function canvasViewCommands(deps: CanvasCommandDeps): AnyCommand[] {
1200
1299
  * so it simply renders at the pane's width — which is the same clamp the drag applies, arrived
1201
1300
  * at by CSS instead of by arithmetic. Refusing would make the verb depend on the window size.
1202
1301
  */
1203
- run: (_commandCtx, args) => {
1302
+ run: (_commandCtx, args: CommandArgValues) => {
1204
1303
  const tab = contextTab("canvas.setEditWidth", args);
1205
- const { width } = args as { width?: unknown };
1304
+ const { width } = args;
1206
1305
  if (width === null) {
1207
1306
  clearEditWidth(paneOfArgs(args));
1208
1307
  repaint(args);
@@ -1253,10 +1352,10 @@ export function canvasViewCommands(deps: CanvasCommandDeps): AnyCommand[] {
1253
1352
  group: "3_canvas",
1254
1353
  requires: "an open document",
1255
1354
  when: documentOpen,
1256
- run: (_commandCtx, args) => {
1355
+ run: (_commandCtx, args: CommandArgValues) => {
1257
1356
  const tab = contextTab("canvas.setBreakpoint", args);
1258
- const { media } = args as { media?: unknown };
1259
- if (media !== null && typeof media !== "string") {
1357
+ const { media } = args;
1358
+ if (media !== null && !isString(media)) {
1260
1359
  throw new RangeError(
1261
1360
  `command "canvas.setBreakpoint" argument "media": expected a breakpoint key or null`,
1262
1361
  );
@@ -1265,9 +1364,7 @@ export function canvasViewCommands(deps: CanvasCommandDeps): AnyCommand[] {
1265
1364
  that does not exist — the rule `data.expandRow` applies to a state entry's name.
1266
1365
  `getEffectiveMedia` and not `document.$media`: the popover offers the SITE's breakpoints
1267
1366
  too, and a command that refused what the control offers would be worse than no command. */
1268
- const declared = Object.keys(
1269
- getEffectiveMedia(tab.doc.document?.$media as Record<string, string> | undefined),
1270
- );
1367
+ const declared = Object.keys(getEffectiveMedia(tab.doc.document.$media));
1271
1368
  if (media !== null && !declared.includes(media)) {
1272
1369
  throw new RangeError(
1273
1370
  `command "canvas.setBreakpoint" argument "media": "${media}" is not a breakpoint this ` +
@@ -1301,7 +1398,7 @@ export function canvasViewCommands(deps: CanvasCommandDeps): AnyCommand[] {
1301
1398
  args,
1302
1399
  "scheme",
1303
1400
  COLOR_SCHEMES,
1304
- ) as "auto" | "dark" | "light";
1401
+ );
1305
1402
  repaint(args);
1306
1403
  },
1307
1404
  title: "Set Color Scheme",
@@ -1417,16 +1514,18 @@ export function canvasViewCommands(deps: CanvasCommandDeps): AnyCommand[] {
1417
1514
  level: "document",
1418
1515
  menus: ["palette"],
1419
1516
  requires: "a popover in the open document",
1420
- run: (_commandCtx, args) => {
1517
+ run: (_commandCtx, args: CommandArgValues) => {
1421
1518
  const tab = contextTab("canvas.setPopoverOpen", args);
1422
- const raw = (args ?? {}) as { open?: unknown; path?: JxPath };
1519
+ /* SAFETY: `registry.run` coerces args against this record's schema before `run`, and the
1520
+ schema declares `path` an array of strings and numbers, which is what a JxPath is. */
1521
+ const explicitPath = args["path"] as JxPath | undefined;
1423
1522
  const open =
1424
- raw.open === undefined ? true : booleanArg("canvas.setPopoverOpen", args, "open");
1425
- if (!open && raw.path === undefined) {
1523
+ args["open"] === undefined ? true : booleanArg("canvas.setPopoverOpen", args, "open");
1524
+ if (!open && explicitPath === undefined) {
1426
1525
  deps.setOpenPopover(tab, null);
1427
1526
  return;
1428
1527
  }
1429
- const path = popoverPathFor(tab, raw.path);
1528
+ const path = popoverPathFor(tab, explicitPath);
1430
1529
  /* REFUSES a path that is not a popover rather than opening nothing — the rule
1431
1530
  `canvas.setBreakpoint` applies to a breakpoint key. A <dialog> is refused with it: its UA
1432
1531
  rules key off `open`, not `popover`, so de-popovering one falls back to a different rule
@@ -1477,16 +1576,18 @@ export function canvasViewCommands(deps: CanvasCommandDeps): AnyCommand[] {
1477
1576
  level: "document",
1478
1577
  menus: ["palette"],
1479
1578
  requires: "a dialog in the open document",
1480
- run: (_commandCtx, args) => {
1579
+ run: (_commandCtx, args: CommandArgValues) => {
1481
1580
  const tab = contextTab("canvas.setDialogOpen", args);
1482
- const raw = (args ?? {}) as { open?: unknown; path?: JxPath };
1581
+ /* SAFETY: `registry.run` coerces args against this record's schema before `run`, and the
1582
+ schema declares `path` an array of strings and numbers, which is what a JxPath is. */
1583
+ const explicitPath = args["path"] as JxPath | undefined;
1483
1584
  const open =
1484
- raw.open === undefined ? true : booleanArg("canvas.setDialogOpen", args, "open");
1485
- if (!open && raw.path === undefined) {
1585
+ args["open"] === undefined ? true : booleanArg("canvas.setDialogOpen", args, "open");
1586
+ if (!open && explicitPath === undefined) {
1486
1587
  deps.setOpenDialog(tab, null);
1487
1588
  return;
1488
1589
  }
1489
- const path = dialogPathFor(tab, raw.path);
1590
+ const path = dialogPathFor(tab, explicitPath);
1490
1591
  /* REFUSES a path that is not a dialog, for the same reason its twin refuses a non-popover:
1491
1592
  the canvas would draw it under the wrong rule and with the wrong name. */
1492
1593
  if (path === null) {
@@ -1528,14 +1629,14 @@ export function canvasViewCommands(deps: CanvasCommandDeps): AnyCommand[] {
1528
1629
  * (`scripts/screenshots/README.md`) forbids outright. The pointer and the camera use the same
1529
1630
  * door.
1530
1631
  */
1531
- run: (_commandCtx, args) => {
1632
+ run: (_commandCtx, args: CommandArgValues) => {
1532
1633
  // Resolve the tab first, so a pane with no document refuses before anything opens.
1533
1634
  contextTab("canvas.setResolvingOpen", args);
1534
- const { open, pane } = args as { open?: unknown; pane?: unknown };
1635
+ const { open, pane } = args;
1535
1636
  // `open` defaults to true and is never COERCED: `{ open: "no" }` would otherwise read as a
1536
1637
  // Close, the class of silent wrong answer this setter family exists to stop.
1537
1638
  deps.setResolvingOpen(
1538
- typeof pane === "string" ? pane : workspace.activePaneId,
1639
+ isString(pane) ? pane : workspace.activePaneId,
1539
1640
  open === undefined || booleanArg("canvas.setResolvingOpen", args, "open"),
1540
1641
  );
1541
1642
  },
@@ -1578,26 +1679,24 @@ export function canvasViewCommands(deps: CanvasCommandDeps): AnyCommand[] {
1578
1679
  group: "3_canvas",
1579
1680
  requires: "an open component document",
1580
1681
  when: documentOpen,
1581
- run: (_commandCtx, args) => {
1682
+ run: (_commandCtx, args: CommandArgValues) => {
1582
1683
  const tab = contextTab("canvas.setTestProp", args);
1583
1684
  const name = stringArg("canvas.setTestProp", args, "name");
1584
1685
  // REFUSES a prop the component does not declare, rather than seeding a value nothing reads
1585
1686
  // — the rule `data.expandRow` and `canvas.setBreakpoint` both apply to their own names.
1586
- const declared = Object.keys(
1587
- (tab.doc.document?.state as Record<string, unknown> | undefined) ?? {},
1588
- );
1687
+ const declared = Object.keys(tab.doc.document.state ?? {});
1589
1688
  if (!declared.includes(name)) {
1590
1689
  throw new RangeError(
1591
1690
  `command "canvas.setTestProp" argument "name": "${name}" is not a prop this component ` +
1592
1691
  `declares — it declares: ${declared.length > 0 ? declared.join(", ") : "nothing"}`,
1593
1692
  );
1594
1693
  }
1595
- const { value } = args as { value?: unknown };
1694
+ const { value } = args;
1596
1695
  const next = { ...tab.session.ui.previewProps };
1597
1696
  if (value === null || value === undefined) {
1598
1697
  delete next[name];
1599
1698
  } else {
1600
- next[name] = value as JsonValue;
1699
+ next[name] = value;
1601
1700
  }
1602
1701
  tab.session.ui.previewProps = Object.keys(next).length > 0 ? next : null;
1603
1702
  repaint(args);
@@ -1665,13 +1764,13 @@ export function registerCanvasViewCommands(
1665
1764
  */
1666
1765
  export function updateActivePanelHeaders(surface: CanvasSurface = activeCanvasSurface()) {
1667
1766
  const activeMedia = activeMediaOfPane(surface.paneId);
1668
- for (const p of surface.panels) {
1767
+ // A board the stage has not bound yet has no header to mark.
1768
+ const panels: readonly UnboundCanvasPanel[] = surface.panels;
1769
+ for (const p of panels) {
1669
1770
  const header = p.element?.querySelector('[part="panel-header"]');
1670
1771
  if (header) {
1671
1772
  const isActive =
1672
- (activeMedia === null && p.mediaName === "base") ||
1673
- (activeMedia === null && p.mediaName === null) ||
1674
- activeMedia === p.mediaName;
1773
+ (activeMedia === null && p.mediaName === "base") || activeMedia === p.mediaName;
1675
1774
  /* An ATTRIBUTE the document does not bind, written from outside it. Which board is current is
1676
1775
  a fact about the pane's session that changes without the stage being rebuilt — the same
1677
1776
  reason the pan transform is written rather than bound — so a reconcile can never disagree
@@ -12,8 +12,9 @@
12
12
  */
13
13
 
14
14
  import { activeTab } from "../workspace/workspace";
15
- import { getNodeAtPath } from "../state";
15
+ import { getNodeAtPath } from "@jxsuite/schema/doc-ops";
16
16
  import { documentHasDialog, isDialog } from "@jxsuite/schema/dialogs";
17
+ import { isNonNullObject } from "@jxsuite/schema/guards";
17
18
  import { primarySelection } from "../tabs/selection";
18
19
  import type { JxMutableNode } from "@jxsuite/schema/types";
19
20
  import type { JxPath } from "../state";
@@ -39,7 +40,8 @@ export function ancestorDialogPath(
39
40
  for (let end = path.length; end >= 0; end -= 1) {
40
41
  const prefix = path.slice(0, end);
41
42
  const node = getNodeAtPath(doc, prefix);
42
- if (node && typeof node === "object" && isDialog(node)) {
43
+ // A path can end on a text child or a value, so only an element is asked about its tag.
44
+ if (isNonNullObject(node) && isDialog(node)) {
43
45
  return prefix;
44
46
  }
45
47
  }
@@ -58,19 +60,22 @@ export function ancestorDialogPath(
58
60
  * @returns The dialog's path, or null.
59
61
  */
60
62
  export function dialogPathFor(tab: Tab, explicit?: JxPath): JxPath | null {
63
+ /* SAFETY: a widening, so it cannot hide a mismatch: `Tab` declares the document always present,
64
+ but a tab record without one must answer null rather than throw (tests/dialog-path.test.ts,
65
+ "a tab with no document"), and the widened type keeps that guard checked. */
61
66
  const doc = tab.doc.document as JxMutableNode | undefined;
62
67
  if (!doc) {
63
68
  return null;
64
69
  }
65
70
  if (explicit) {
66
71
  const node = getNodeAtPath(doc, explicit);
67
- return node && typeof node === "object" && isDialog(node) ? explicit : null;
72
+ return isNonNullObject(node) && isDialog(node) ? explicit : null;
68
73
  }
69
74
  return ancestorDialogPath(doc, primarySelection(tab.session.selection));
70
75
  }
71
76
 
72
77
  /** Whether the active document holds a `<dialog>` at all — the command's `enablement`. */
73
78
  export function activeDocumentHasDialog(): boolean {
74
- const doc = activeTab.value?.doc.document as JxMutableNode | undefined;
79
+ const doc = activeTab.value?.doc.document;
75
80
  return doc ? documentHasDialog(doc) : false;
76
81
  }
@@ -19,7 +19,6 @@ import { ancestorDialogPath } from "./dialog-path";
19
19
  import { postDialogOpen } from "./iframe-host";
20
20
  import { primarySelection } from "../tabs/selection";
21
21
  import { updateSession } from "../store";
22
- import type { JxMutableNode } from "@jxsuite/schema/types";
23
22
  import type { JxPath } from "../state";
24
23
  import type { Tab } from "../tabs/tab";
25
24
 
@@ -51,7 +50,7 @@ export function setOpenDialog(tab: Tab, path: JxPath | null): void {
51
50
  * @returns Whether anything changed.
52
51
  */
53
52
  export function openDialogFor(tab: Tab, path: JxPath | null): boolean {
54
- const target = ancestorDialogPath(tab.doc.document as JxMutableNode, path);
53
+ const target = ancestorDialogPath(tab.doc.document, path);
55
54
  if (target === null || samePath(tab.session.ui.openDialog, target)) {
56
55
  return false;
57
56
  }
@@ -35,6 +35,7 @@
35
35
  */
36
36
 
37
37
  import { deepEqual, matchChildren } from "@jxsuite/collab/diff-core";
38
+ import { isJsonObject } from "@jxsuite/schema/guards";
38
39
  import type { JxMutableNode, JxPath } from "@jxsuite/schema/types";
39
40
 
40
41
  /** What happened to a node. `modified` means the node survived and its own content changed. */
@@ -84,10 +85,6 @@ export interface ChangeMapOptions {
84
85
 
85
86
  const DEFAULT_MAX_CELLS = 250_000;
86
87
 
87
- function isPlainObject(value: unknown): value is Record<string, unknown> {
88
- return typeof value === "object" && value !== null && !Array.isArray(value);
89
- }
90
-
91
88
  class ChangeWalker {
92
89
  readonly original: ChangeMark[] = [];
93
90
  readonly current: ChangeMark[] = [];
@@ -121,12 +118,7 @@ class ChangeWalker {
121
118
  this.steps.push({ currentPath: bPath, kind, originalPath: aPath });
122
119
  }
123
120
 
124
- diffNode(
125
- aPath: JxPath,
126
- bPath: JxPath,
127
- a: Record<string, unknown>,
128
- b: Record<string, unknown>,
129
- ): void {
121
+ diffNode(aPath: JxPath, bPath: JxPath, a: JxMutableNode, b: JxMutableNode): void {
130
122
  const isRoot = aPath.length === 0 && bPath.length === 0;
131
123
  let ownChanged = false;
132
124
  for (const key of new Set([...Object.keys(a), ...Object.keys(b)])) {
@@ -161,8 +153,8 @@ class ChangeWalker {
161
153
  private diffChildren(
162
154
  aPath: JxPath,
163
155
  bPath: JxPath,
164
- ca: readonly unknown[],
165
- cb: readonly unknown[],
156
+ ca: readonly (JxMutableNode | string)[],
157
+ cb: readonly (JxMutableNode | string)[],
166
158
  ): void {
167
159
  if (ca.length * cb.length > this.maxCells) {
168
160
  this.degraded = true;
@@ -183,7 +175,7 @@ class ChangeWalker {
183
175
  if (!match.identical) {
184
176
  const aItem = ca[match.aIndex];
185
177
  const bItem = cb[match.bIndex];
186
- if (isPlainObject(aItem) && isPlainObject(bItem)) {
178
+ if (isJsonObject(aItem) && isJsonObject(bItem)) {
187
179
  this.diffNode(
188
180
  [...aPath, "children", match.aIndex],
189
181
  [...bPath, "children", match.bIndex],
@@ -211,11 +203,12 @@ class ChangeWalker {
211
203
  kind: "added" | "removed",
212
204
  aPath: JxPath,
213
205
  bPath: JxPath,
214
- item: unknown,
206
+ // Every caller reads in bounds, but an indexed read is typed possibly undefined; it is no element.
207
+ item: JxMutableNode | string | undefined,
215
208
  aIndex: number | null,
216
209
  bIndex: number | null,
217
210
  ): void {
218
- if (!isPlainObject(item)) {
211
+ if (!isJsonObject(item)) {
219
212
  this.emit("modified", aPath, bPath);
220
213
  return;
221
214
  }
@@ -241,7 +234,7 @@ export function buildChangeMap(
241
234
  opts: ChangeMapOptions = {},
242
235
  ): ChangeMap {
243
236
  const walker = new ChangeWalker(opts.maxCells ?? DEFAULT_MAX_CELLS);
244
- walker.diffNode([], [], a as Record<string, unknown>, b as Record<string, unknown>);
237
+ walker.diffNode([], [], a, b);
245
238
  return {
246
239
  current: walker.current,
247
240
  degraded: walker.degraded,