@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
@@ -12,16 +12,7 @@
12
12
  import type { AssetContext } from "./asset-resolve";
13
13
  import type { JxDocOp } from "../tabs/patch-ops";
14
14
  import type { JxContentResult, SlashCommand } from "../editor/inline-edit";
15
- import type { JxExpressionNode, JxMutableNode } from "@jxsuite/schema/types";
16
-
17
- /**
18
- * The wire form of a value-carrying document op (the `forward` half of a recorded
19
- * {@link JxDocOpPair} — see {@link file://../tabs/patch-ops.ts}). It is structurally a
20
- * {@link JxDocOp}: it carries the inserted/replaced `node` and the set `value`, NOT just a path, so
21
- * the iframe can fold it into its shadow doc and re-render subtrees without ever reading the
22
- * parent's reactive document.
23
- */
24
- export type WireDocOp = JxDocOp;
15
+ import type { JsonObject, JxExpressionNode, JxMutableNode, JxStyle } from "@jxsuite/schema/types";
25
16
 
26
17
  export const CANVAS_MODES = ["preview", "design", "edit", "stylebook", "git-diff"] as const;
27
18
 
@@ -37,7 +28,7 @@ export const CANVAS_MODES = ["preview", "design", "edit", "stylebook", "git-diff
37
28
  export type CanvasMode = (typeof CANVAS_MODES)[number];
38
29
 
39
30
  export function isCanvasMode(value: unknown): value is CanvasMode {
40
- return typeof value === "string" && (CANVAS_MODES as readonly string[]).includes(value);
31
+ return typeof value === "string" && CANVAS_MODES.some((mode) => mode === value);
41
32
  }
42
33
 
43
34
  /**
@@ -121,7 +112,7 @@ export type ParentToIframe =
121
112
  */
122
113
  assets?: AssetContext | null;
123
114
  mapperCtx: WireMapperCtx;
124
- siteStyle: Record<string, unknown> | null;
115
+ siteStyle: JxStyle | null;
125
116
  // Forced color-scheme preview (spec.md §9.5): "light"/"dark" sets data-color-scheme on the
126
117
  // Iframe root, null clears it (auto — follow the OS).
127
118
  colorScheme: "light" | "dark" | null;
@@ -239,7 +230,7 @@ export type ParentToIframe =
239
230
  // Render-free; the superseding render carries the same style via its own siteStyle.
240
231
  | {
241
232
  kind: "siteStyleUpdate";
242
- siteStyle: Record<string, unknown> | null;
233
+ siteStyle: JxStyle | null;
243
234
  media: Record<string, string>;
244
235
  }
245
236
  // Ask the iframe to measure the given document paths and post their current rects back. Used to
@@ -255,7 +246,12 @@ export type ParentToIframe =
255
246
  // In place. `gen` matches the last render so the iframe drops patches superseded by a re-render.
256
247
  | {
257
248
  kind: "patch";
258
- forwardOps: WireDocOp[];
249
+ /**
250
+ * The `forward` halves of the recorded {@link JxDocOp} pairs (tabs/patch-ops.ts). Each carries
251
+ * the inserted/replaced `node` and the set `value`, not just a path, so the iframe folds it
252
+ * into its shadow doc without ever reading the parent's reactive document.
253
+ */
254
+ forwardOps: JxDocOp[];
259
255
  gen: number;
260
256
  /**
261
257
  * Paths whose DOM the RECIPIENT already has correct, because this patch is the echo of an
@@ -273,7 +269,7 @@ export type ParentToIframe =
273
269
  // Regenerates the whole scoped-CSS cascade (real @media included) without a re-render. `style` is
274
270
  // Already transposed parent-side (transposeStylebookStyle); `gen` must equal the iframe's
275
271
  // RenderedGen — a stale update is dropped (the superseding render carries the same style).
276
- | { kind: "styleUpdate"; style: Record<string, unknown>; gen: number }
272
+ | { kind: "styleUpdate"; style: JxStyle; gen: number }
277
273
  // Enter inline editing on the node at `path` (used to re-enter after a split/insert re-renders).
278
274
  | {
279
275
  kind: "enterEdit";
@@ -490,7 +486,7 @@ export type IframeToParent =
490
486
  // `S.canvas.scope` so the data-explorer panel shows live data instead of "pending" (the iframe,
491
487
  // Not the parent, now resolves the scope). `gen` lets the parent drop a snapshot from a superseded
492
488
  // Render. Posted right after `renderComplete`; values are JSON-safe deep clones (see serialize-scope).
493
- | { kind: "dataScope"; gen: number; scope: Record<string, unknown> }
489
+ | { kind: "dataScope"; gen: number; scope: JsonObject }
494
490
  // The frame's own quiescence, ANSWERED rather than polled (studio.md §13.5).
495
491
  // Nothing in the parent realm can see inside a cross-origin frame, so a parent that wanted to
496
492
  // Know whether the canvas had settled could only sleep. The frame samples itself at its own rAF
@@ -563,6 +559,9 @@ export type IframeToParent =
563
559
  // Inline editing started in the iframe (the parent shows the format toolbar from here). `prop`
564
560
  // Names the component-instance prop when the session edits prop-bound text (plain sessions).
565
561
  | { kind: "editStart"; path: (string | number)[]; prop?: string }
562
+ // The first keystroke of a typing burst, ahead of the idle tick's commit: the parent marks the tab
563
+ // Dirty now, so Save is enabled the instant there is text a save would keep.
564
+ | { kind: "editInput" }
566
565
  // Committed inline-edit content (rich `children` else `textContent`) for the parent to persist.
567
566
  | {
568
567
  kind: "editCommit";
@@ -38,7 +38,8 @@ import { elementForPath } from "./iframe-position";
38
38
  import type { AssetContext } from "./asset-resolve";
39
39
  import { SITE_STYLE_ID, buildSiteStyleCSS } from "@jxsuite/site/site-style";
40
40
  import type { CanvasMode, WireDiffMarks } from "./iframe-protocol";
41
- import type { JxDocument } from "@jxsuite/schema/types";
41
+ import { isNonNullObject, isString } from "@jxsuite/schema/guards";
42
+ import type { JxDocument, JxElement, JxStyle } from "@jxsuite/schema/types";
42
43
  import type { PathMapCtx } from "./path-mapping";
43
44
 
44
45
  /**
@@ -61,18 +62,12 @@ export interface RenderHandle {
61
62
  ctx: IframeRenderCtx;
62
63
  }
63
64
 
64
- interface HeadEntry {
65
- tagName?: string;
66
- attributes?: Record<string, unknown>;
67
- textContent?: string;
68
- }
69
-
70
65
  /**
71
66
  * Register the document's `$elements` (components) in this iframe realm so the runtime can render
72
67
  * them.
73
68
  */
74
69
  export async function registerElements(doc: JxDocument, docBase: string): Promise<void> {
75
- const elements = (doc as { $elements?: unknown[] }).$elements;
70
+ const elements = doc.$elements;
76
71
  if (!Array.isArray(elements)) {
77
72
  return;
78
73
  }
@@ -81,13 +76,16 @@ export async function registerElements(doc: JxDocument, docBase: string): Promis
81
76
  await Promise.all(
82
77
  elements.map(async (entry) => {
83
78
  const task = (async () => {
84
- if (typeof entry === "string") {
79
+ if (isString(entry)) {
85
80
  const specifier =
86
81
  entry.startsWith("/") || entry.startsWith(".") ? entry : `/node_modules/${entry}`;
87
82
  await import(specifier);
88
- } else if (entry && typeof entry === "object" && "$ref" in entry) {
89
- await defineElement(new URL(String((entry as { $ref: string }).$ref), docBase).href);
90
- } else if (entry && typeof entry === "object") {
83
+ } else if (isNonNullObject(entry) && "$ref" in entry) {
84
+ await defineElement(new URL(String(entry.$ref), docBase).href);
85
+ } else if (isNonNullObject(entry)) {
86
+ /* SAFETY: an `$elements` entry that is neither a specifier nor a `$ref` is an inline
87
+ * component definition by the schema (JxDocument is JxElement with a literal tagName).
88
+ * A malformed one throws inside this task, which the race below catches and logs. */
91
89
  await defineElement(entry as JxDocument, docBase);
92
90
  }
93
91
  })();
@@ -696,11 +694,11 @@ export function syncStylebookCss(doc: Document, mode: CanvasMode): void {
696
694
  * selectors can win (spec.md §9.5), and so removed tokens can't linger on reused iframes.
697
695
  */
698
696
  export function applySiteStyle(
699
- siteStyle: Record<string, unknown> | null | undefined,
697
+ siteStyle: JxStyle | null | undefined,
700
698
  mediaQueries: Record<string, string> = {},
701
699
  ): void {
702
700
  const existing = document.head.querySelector(`#${SITE_STYLE_ID}`);
703
- if (!siteStyle || typeof siteStyle !== "object") {
701
+ if (!siteStyle || !isNonNullObject(siteStyle)) {
704
702
  existing?.remove();
705
703
  return;
706
704
  }
@@ -746,15 +744,15 @@ export function applyPreviewColorScheme(doc: Document, scheme: "light" | "dark"
746
744
  * @docs studio/interface/canvas
747
745
  */
748
746
  export function applyCanvasPopoverOpen(root: ParentNode, path: string | null): void {
749
- for (const el of root.querySelectorAll("[data-jx-popover-open]")) {
750
- delete (el as HTMLElement).dataset.jxPopoverOpen;
747
+ for (const el of root.querySelectorAll<HTMLElement>("[data-jx-popover-open]")) {
748
+ delete el.dataset.jxPopoverOpen;
751
749
  }
752
750
  if (path === null) {
753
751
  return;
754
752
  }
755
- const target = root.querySelector(`[data-jx-popover]${jxPathSelector(path)}`);
753
+ const target = root.querySelector<HTMLElement>(`[data-jx-popover]${jxPathSelector(path)}`);
756
754
  if (target) {
757
- (target as HTMLElement).dataset.jxPopoverOpen = "";
755
+ target.dataset.jxPopoverOpen = "";
758
756
  }
759
757
  }
760
758
 
@@ -783,26 +781,27 @@ export function applyCanvasPopoverOpen(root: ParentNode, path: string | null): v
783
781
  * backdrop and all.
784
782
  */
785
783
  export function applyCanvasDialogOpen(root: ParentNode, path: string | null): void {
786
- for (const el of root.querySelectorAll("[data-jx-dialog-open]")) {
787
- delete (el as HTMLElement).dataset.jxDialogOpen;
784
+ for (const el of root.querySelectorAll<HTMLElement>("[data-jx-dialog-open]")) {
785
+ delete el.dataset.jxDialogOpen;
788
786
  }
789
787
  if (path === null) {
790
788
  return;
791
789
  }
792
- const target = root.querySelector(`dialog${jxPathSelector(path)}`);
790
+ const target = root.querySelector<HTMLElement>(`dialog${jxPathSelector(path)}`);
793
791
  if (target) {
794
- (target as HTMLElement).dataset.jxDialogOpen = "";
792
+ target.dataset.jxDialogOpen = "";
795
793
  }
796
794
  }
797
795
 
798
796
  /** Inject the document's `$head` (link/meta/script) into the iframe's <head>, de-duped by href/src. */
799
797
  export function injectHead(doc: JxDocument, assets: AssetContext | null = null): void {
800
- const head = (doc as { $head?: HeadEntry[] }).$head;
798
+ const head = doc.$head;
801
799
  if (!Array.isArray(head)) {
802
800
  return;
803
801
  }
804
802
  for (const entry of head) {
805
- if (!entry?.tagName) {
803
+ // A `$head` array read off disk can hold a null or a tagless entry; neither emits anything.
804
+ if (!isNonNullObject(entry) || !entry.tagName) {
806
805
  continue;
807
806
  }
808
807
  const tag = String(entry.tagName).toLowerCase();
@@ -810,10 +809,10 @@ export function injectHead(doc: JxDocument, assets: AssetContext | null = null):
810
809
  if (tag === "script" && !entry.attributes?.src) {
811
810
  continue;
812
811
  }
813
- const attrs = { ...entry.attributes } as Record<string, unknown>;
812
+ const attrs = { ...entry.attributes };
814
813
  for (const key of ["href", "src"]) {
815
814
  const val = attrs[key];
816
- if (typeof val !== "string" || val === "") {
815
+ if (!isString(val) || val === "") {
817
816
  continue;
818
817
  }
819
818
  if (!val.startsWith("/") && !val.startsWith(".") && !val.startsWith("http")) {
@@ -833,7 +832,7 @@ export function injectHead(doc: JxDocument, assets: AssetContext | null = null):
833
832
  el.setAttribute(k, String(v));
834
833
  }
835
834
  if (entry.textContent) {
836
- el.textContent = entry.textContent;
835
+ el.textContent = String(entry.textContent);
837
836
  }
838
837
  document.head.append(el);
839
838
  }
@@ -856,7 +855,7 @@ export function makeStamper(ctx: PathMapCtx) {
856
855
  (island) => path.length > island.length && island.every((seg, i) => path[i] === seg),
857
856
  );
858
857
 
859
- return (created: Node, path: (string | number)[], def: unknown) => {
858
+ return (created: Node, path: (string | number)[], def: JxElement | string) => {
860
859
  if (!(created instanceof HTMLElement)) {
861
860
  return;
862
861
  }
@@ -866,7 +865,10 @@ export function makeStamper(ctx: PathMapCtx) {
866
865
  // Editable tree and re-render a live instance with default state. Mark the root so the
867
866
  // Runtime's element class leaves it alone (see defineElement's connectedCallback guard).
868
867
  const isDefinitionRoot =
869
- path.length === 0 && (def as { tagName?: string } | null)?.tagName?.includes("-");
868
+ path.length === 0 &&
869
+ isNonNullObject(def) &&
870
+ isString(def.tagName) &&
871
+ def.tagName.includes("-");
870
872
  if (isDefinitionRoot) {
871
873
  created.dataset.jxDefinitionRoot = "";
872
874
  }
@@ -984,7 +986,7 @@ export async function renderResolvedDocument(opts: {
984
986
  docBase: string;
985
987
  mode: CanvasMode;
986
988
  mapperCtx: PathMapCtx;
987
- siteStyle?: Record<string, unknown> | null;
989
+ siteStyle?: JxStyle | null;
988
990
  /**
989
991
  * How this host addresses project media, or null when the canvas origin serves the site's own URL
990
992
  * space (desktop, `jx dev`) and references need no resolution at all.
@@ -1018,8 +1020,11 @@ export async function renderResolvedDocument(opts: {
1018
1020
  // Sections feeding back into an ever-growing height. Set every render (the iframe always wants it).
1019
1021
  setCanvasViewportTranspose(true);
1020
1022
  // De-link `<a href>` in design/edit so clicks select the anchor instead of navigating the iframe;
1021
- // Preview keeps real links live (mirrors the server-function gate above).
1022
- setCanvasDelinkAnchors(opts.mode !== "preview");
1023
+ // Preview keeps real links live (mirrors the server-function gate above). So does Project Styles:
1024
+ // An `<a>` without `href` is not a link, so `:link`, `:any-link` and the UA's link colour could
1025
+ // Never match its specimen, and a project could not see the link styles it was setting there.
1026
+ // Its clicks are refused in the frame instead (`iframe-interaction.ts`).
1027
+ setCanvasDelinkAnchors(opts.mode !== "preview" && opts.mode !== "stylebook");
1023
1028
  /* De-popover in the same modes and for the same reason: an OPEN popover is in the top layer,
1024
1029
  whose containing block is the viewport — a fiction here, since the frame is sized to its own
1025
1030
  content — and which contributes to no ancestor's scrollable overflow, so the artboard could
@@ -1034,7 +1039,7 @@ export async function renderResolvedDocument(opts: {
1034
1039
  // Iframe clears it (page-level templates are inert in design/edit via prepareForEditMode, so only
1035
1040
  // Component internals get stamped).
1036
1041
  setStampPropBindings(opts.mode === "design" || opts.mode === "edit");
1037
- applySiteStyle(opts.siteStyle, (opts.doc as { $media?: Record<string, string> }).$media ?? {});
1042
+ applySiteStyle(opts.siteStyle, opts.doc.$media ?? {});
1038
1043
  injectHead(opts.doc, assets);
1039
1044
  syncEditModeCss(opts.container.ownerDocument, opts.mode);
1040
1045
  // The substitute rules for the attributes the de-link above renamed. Same predicate as the
@@ -1049,7 +1054,7 @@ export async function renderResolvedDocument(opts: {
1049
1054
  // But no own `$media` resolves the breakpoint to its real query (the iframe path calls buildScope
1050
1055
  // Directly and never the runtime's `Jx()` entry, which is the only other place _rootMedia is set).
1051
1056
  // Set it every render (even to `{}`) so a stale map from a previous document cannot leak.
1052
- setRootMedia((opts.doc as { $media?: Record<string, string> }).$media ?? {});
1057
+ setRootMedia(opts.doc.$media ?? {});
1053
1058
  // Register components BEFORE renderNode. The runtime only applies a custom element's `$props` when
1054
1059
  // That element is ALREADY defined (renderNode gates renderCustomElementWithProps on a truthy
1055
1060
  // `customElements.get(tagName)`); if we register after render, every component paints with its
@@ -1063,8 +1068,8 @@ export async function renderResolvedDocument(opts: {
1063
1068
  // The scope MUST be the runtime's (runScoped): renderNode creates its binding effects with the
1064
1069
  // Runtime's copy of @vue/reactivity, and scope collection is per module instance — a studio
1065
1070
  // EffectScope here collects nothing and dispose() would leak every effect of this render.
1066
- const { result: el, stop } = runScoped(
1067
- () => renderNode(opts.doc, $defs, { _path: [], onNodeCreated }) as HTMLElement,
1071
+ const { result: el, stop } = runScoped(() =>
1072
+ renderNode(opts.doc, $defs, { _path: [], onNodeCreated }),
1068
1073
  );
1069
1074
  opts.container.replaceChildren(el);
1070
1075
  applyCanvasPopoverOpen(opts.container, opts.popoverOpen ?? null);
@@ -21,7 +21,17 @@ import type { IframeToParent, ParentToIframe } from "./iframe-protocol";
21
21
 
22
22
  type SlashNavKey = Extract<IframeToParent, { kind: "slashNav" }>["key"];
23
23
 
24
- const NAV_KEYS = new Set(["ArrowUp", "ArrowDown", "Enter", "Escape"]);
24
+ const NAV_KEYS = new Set<string>([
25
+ "ArrowUp",
26
+ "ArrowDown",
27
+ "Enter",
28
+ "Escape",
29
+ ] satisfies SlashNavKey[]);
30
+
31
+ /** Whether `key` is one of the four keys the parent menu navigates by. */
32
+ function isNavKey(key: string): key is SlashNavKey {
33
+ return NAV_KEYS.has(key);
34
+ }
25
35
 
26
36
  /**
27
37
  * Register the bridge SlashController for this iframe realm and wire its keyboard/dismissal
@@ -55,28 +65,31 @@ export function startIframeSlashBridge(
55
65
  open = true;
56
66
  lastPostedFilter = filter;
57
67
  const r = rectOf(anchorEl);
58
- channel.post({
68
+ const message: Extract<IframeToParent, { kind: "slashShow" }> = {
59
69
  filter,
60
70
  kind: "slashShow",
61
71
  rect: { height: r.height, width: r.width, x: r.left, y: r.top },
62
- /* Carried, because the parent draws the menu and only the FRAME knows why it opened. A
63
- menu opened by name has no "/…" run in the document to filter with, so it needs its own
64
- field; the "/" gesture needs none, because the author types into the block. This option
65
- was dropped at this boundary and the loss was invisible — the menu still appeared, just
66
- with no way past fifteen items but scrolling. */
67
- ...(cbs.showFilter === true ? { showFilter: true } : {}),
68
- });
72
+ };
73
+ /* Carried, because the parent draws the menu and only the FRAME knows why it opened. A
74
+ menu opened by name has no "/…" run in the document to filter with, so it needs its own
75
+ field; the "/" gesture needs none, because the author types into the block. This option
76
+ was dropped at this boundary and the loss was invisible — the menu still appeared, just
77
+ with no way past fifteen items but scrolling. */
78
+ if (cbs.showFilter === true) {
79
+ message.showFilter = true;
80
+ }
81
+ channel.post(message);
69
82
  },
70
83
  };
71
84
  setSlashController(controller);
72
85
 
73
86
  const onKeyDown = (e: KeyboardEvent) => {
74
- if (!open || !NAV_KEYS.has(e.key)) {
87
+ if (!open || !isNavKey(e.key)) {
75
88
  return;
76
89
  }
77
90
  e.preventDefault();
78
91
  e.stopPropagation();
79
- channel.post({ key: e.key as SlashNavKey, kind: "slashNav" });
92
+ channel.post({ key: e.key, kind: "slashNav" });
80
93
  };
81
94
  const onMouseDown = () => {
82
95
  // The parent menu's outside-click listener can't see iframe clicks — any in-iframe press while
@@ -12,6 +12,7 @@
12
12
  */
13
13
 
14
14
  import { releaseElementStyles, renderNode, runScoped } from "@jxsuite/runtime";
15
+ import { isNumber, isString } from "@jxsuite/schema/guards";
15
16
  import { getNodeAtPath } from "../state";
16
17
  import { makeStamper } from "./iframe-render";
17
18
  import { prepareForEditMode } from "../utils/edit-display";
@@ -39,11 +40,14 @@ export function renderSubtreeIframe(
39
40
  docPath: JxPath,
40
41
  ctx: IframeRenderCtx,
41
42
  ): HTMLElement | Text {
43
+ /* SAFETY: a widening of getNodeAtPath's declared return. A patch path addresses an entry of a
44
+ * `children` array, which the schema types as `JxMutableNode | string`, and a missing step
45
+ * answers undefined, so those three cases are the whole range. */
42
46
  const node = getNodeAtPath(shadowDoc, docPath) as JxMutableNode | string | undefined;
43
47
  if (node === undefined) {
44
48
  throw new Error(`iframe-patch-node-not-found:${docPath.join("/")}`);
45
49
  }
46
- const def = typeof node === "string" ? node : prepareForEditMode(stripEventHandlers(node));
50
+ const def = isString(node) ? node : prepareForEditMode(stripEventHandlers(node));
47
51
  const { result: rendered, stop } = runScoped(() =>
48
52
  renderNode(def, ctx.defs, {
49
53
  _path: docPathToRenderPath(docPath, ctx),
@@ -98,7 +102,7 @@ function docPathToRenderPath(docPath: JxPath, ctx: IframeRenderCtx): (string | n
98
102
  const { mapperCtx } = ctx;
99
103
  if (mapperCtx.layoutWrapped && mapperCtx.pageContentPrefix && docPath[0] === "children") {
100
104
  const [, idx] = docPath;
101
- return typeof idx === "number"
105
+ return isNumber(idx)
102
106
  ? [
103
107
  ...mapperCtx.pageContentPrefix,
104
108
  idx + (mapperCtx.pageContentOffset ?? 0),
@@ -11,6 +11,8 @@
11
11
  * result for the `data-jx-path` attribute.
12
12
  */
13
13
 
14
+ import { isJsonObject, isNonNullObject, isNumber, isString } from "@jxsuite/schema/guards";
15
+ import type { JxElement } from "@jxsuite/schema/types";
14
16
  import type { JxPath } from "../state";
15
17
 
16
18
  /**
@@ -83,22 +85,27 @@ export function wrapsPageContent(path: JxPath, prefix: JxPath | null): boolean {
83
85
  * Resolve a runtime render path to a document path (or flag it as layout-originated). Pure — the
84
86
  * caller decides whether to record the result in a WeakMap or `data-jx-*` attributes.
85
87
  */
86
- export function classifyRenderNode(path: JxPath, def: unknown, ctx: PathMapCtx): RenderNodeClass {
88
+ export function classifyRenderNode(
89
+ path: JxPath,
90
+ def: JxElement | string,
91
+ ctx: PathMapCtx,
92
+ ): RenderNodeClass {
87
93
  // Layout-originated nodes have no page-document path; the caller stamps their origin instead
88
94
  // (data-jx-layout-path / data-jx-layout-file).
89
- const marker =
90
- ctx.layoutWrapped && typeof def === "object"
91
- ? (def as { $__layout?: unknown } | null)?.$__layout
92
- : undefined;
95
+ const marker = ctx.layoutWrapped && isNonNullObject(def) ? def["$__layout"] : undefined;
93
96
  if (marker) {
94
- const origin = (typeof marker === "object" ? marker : {}) as Partial<LayoutMarker>;
97
+ // A legacy bare `true` marker carries no origin, so it reads as an empty one.
98
+ const origin = isJsonObject(marker) ? marker : {};
99
+ const { file, path: originPath } = origin;
95
100
  return {
96
101
  // The root is never chrome even when the layout distributed nothing: it IS the render, and a
97
102
  // Dimmed, frozen root would be a canvas nobody can touch.
98
103
  chrome: path.length > 0 && !wrapsPageContent(path, ctx.pageContentPrefix),
99
104
  kind: "layout",
100
- layoutFile: typeof origin.file === "string" ? origin.file : "",
101
- layoutPath: Array.isArray(origin.path) ? origin.path : [],
105
+ layoutFile: isString(file) ? file : "",
106
+ /* SAFETY: markLayoutNodes (canvas-live-render.ts) is the only writer of `$__layout`, and the
107
+ * `path` it stamps is a LayoutMarker's JxPath. */
108
+ layoutPath: Array.isArray(originPath) ? (originPath as JxPath) : [],
102
109
  };
103
110
  }
104
111
 
@@ -112,10 +119,9 @@ export function classifyRenderNode(path: JxPath, def: unknown, ctx: PathMapCtx):
112
119
  if (path.length >= pfx.length && pfx.every((seg, i) => path[i] === seg)) {
113
120
  const rest = path.slice(pfx.length);
114
121
  const [containerIdx] = rest;
115
- mappedPath =
116
- typeof containerIdx === "number"
117
- ? ["children", containerIdx - (ctx.pageContentOffset ?? 0), ...rest.slice(1)]
118
- : ["children", ...rest];
122
+ mappedPath = isNumber(containerIdx)
123
+ ? ["children", containerIdx - (ctx.pageContentOffset ?? 0), ...rest.slice(1)]
124
+ : ["children", ...rest];
119
125
  }
120
126
  }
121
127
 
@@ -154,5 +160,6 @@ export function serializeJxPath(path: JxPath): string {
154
160
 
155
161
  /** Decode a `data-jx-path` attribute back into a document path. */
156
162
  export function parseJxPath(serialized: string): JxPath {
163
+ // SAFETY: only serializeJxPath writes `data-jx-path`, and it is JSON.stringify of a JxPath.
157
164
  return JSON.parse(serialized) as JxPath;
158
165
  }
@@ -14,6 +14,7 @@
14
14
  import { activeTab } from "../workspace/workspace";
15
15
  import { getNodeAtPath } from "../state";
16
16
  import { documentHasPopover, isPopover } from "@jxsuite/schema/overlays";
17
+ import { isNonNullObject } from "@jxsuite/schema/guards";
17
18
  import { INVOKER_TAGS, POPOVER_TAGS } from "@jxsuite/ui";
18
19
  import { primarySelection } from "../tabs/selection";
19
20
  import type { JxMutableNode } from "@jxsuite/schema/types";
@@ -49,7 +50,7 @@ export function ancestorPopoverPath(
49
50
  for (let end = path.length; end >= 0; end -= 1) {
50
51
  const prefix = path.slice(0, end);
51
52
  const node = getNodeAtPath(doc, prefix);
52
- if (node && typeof node === "object" && isPopover(node, KIT_SCOPE)) {
53
+ if (isNonNullObject(node) && isPopover(node, KIT_SCOPE)) {
53
54
  return prefix;
54
55
  }
55
56
  }
@@ -72,13 +73,16 @@ export function ancestorPopoverPath(
72
73
  * @returns The popover's path, or null.
73
74
  */
74
75
  export function popoverPathFor(tab: Tab, explicit?: JxPath): JxPath | null {
76
+ /* SAFETY: a widening, not a narrowing. A tab built by createTab always carries a document, but
77
+ * the command is a public entry point and the contract (pinned in popover-state.test.ts) is that
78
+ * a tab record without one answers null rather than throwing inside getNodeAtPath. */
75
79
  const doc = tab.doc.document as JxMutableNode | undefined;
76
80
  if (!doc) {
77
81
  return null;
78
82
  }
79
83
  if (explicit) {
80
84
  const node = getNodeAtPath(doc, explicit);
81
- return node && typeof node === "object" && isPopover(node, KIT_SCOPE) ? explicit : null;
85
+ return isNonNullObject(node) && isPopover(node, KIT_SCOPE) ? explicit : null;
82
86
  }
83
87
  return ancestorPopoverPath(doc, primarySelection(tab.session.selection));
84
88
  }
@@ -101,6 +105,6 @@ export function popoverPathFor(tab: Tab, explicit?: JxPath): JxPath | null {
101
105
  * @returns True when the focused document declares at least one popover.
102
106
  */
103
107
  export function activeDocumentHasPopover(): boolean {
104
- const doc = activeTab.value?.doc.document as JxMutableNode | undefined;
108
+ const doc = activeTab.value?.doc.document;
105
109
  return doc ? documentHasPopover(doc, KIT_SCOPE) : false;
106
110
  }
@@ -31,7 +31,6 @@ import { effect, pauseTracking, resetTracking } from "../reactivity";
31
31
  import { postPopoverOpen, revealCanvasPath } from "./iframe-host";
32
32
  import { primarySelection } from "../tabs/selection";
33
33
  import { updateSession } from "../store";
34
- import type { JxMutableNode } from "@jxsuite/schema/types";
35
34
  import type { JxPath } from "../state";
36
35
  import type { Tab } from "../tabs/tab";
37
36
 
@@ -59,7 +58,7 @@ export function setOpenPopover(tab: Tab, path: JxPath | null): void {
59
58
 
60
59
  /** Open the popover containing `path`, if any. Returns whether anything changed. */
61
60
  function openPopoverFor(tab: Tab, path: JxPath | null): boolean {
62
- const target = ancestorPopoverPath(tab.doc.document as JxMutableNode, path);
61
+ const target = ancestorPopoverPath(tab.doc.document, path);
63
62
  if (target === null || samePath(tab.session.ui.openPopover, target)) {
64
63
  return false;
65
64
  }
@@ -12,6 +12,10 @@
12
12
  * Pure and DOM-free — no reactivity/runtime imports — so it stays trivially unit-testable.
13
13
  */
14
14
 
15
+ import { isFunction } from "@jxsuite/schema/guards";
16
+ import type { JsonObject, JsonValue } from "@jxsuite/schema/types";
17
+ import type { JxScope } from "@jxsuite/runtime/types";
18
+
15
19
  /**
16
20
  * Per-value size cap (in JSON-string chars). A single large content collection can be tens of KB;
17
21
  * past this cap we store a small placeholder so one big data source can't bloat every render's
@@ -34,18 +38,21 @@ const LARGE_VALUE_PLACEHOLDER = "[large value omitted]";
34
38
  * @param defs The resolved `$defs` scope (may be a Vue reactive proxy).
35
39
  * @returns A plain object safe to `postMessage` (every value is JSON-round-trippable).
36
40
  */
37
- export function serializeDataScope(defs: Record<string, unknown>): Record<string, unknown> {
38
- const out: Record<string, unknown> = {};
41
+ export function serializeDataScope(defs: JxScope): JsonObject {
42
+ const out: JsonObject = {};
39
43
  for (const key of Object.keys(defs)) {
40
44
  try {
41
45
  // Reading through the reactive proxy auto-unwraps a top-level ref/computed to its value, and
42
46
  // Can THROW (a computed getter that throws) — done inside the try so that maps the key to null
43
47
  // Rather than aborting the whole snapshot.
44
48
  const value = defs[key];
45
- if (typeof value === "function") {
49
+ if (isFunction(value)) {
46
50
  continue; // Data sources only — a resolved server/handler fn isn't a "value".
47
51
  }
48
- const json = JSON.stringify(value);
52
+ /* SAFETY: a widening of lib.d.ts's `string` to the real range. JSON.stringify answers
53
+ * undefined for a top-level undefined, a symbol, or a value whose toJSON() returns undefined
54
+ * (ECMA-262 SerializeJSONProperty), and the branch below depends on seeing that. */
55
+ const json = JSON.stringify(value) as string | undefined;
49
56
  if (json === undefined) {
50
57
  // JSON.stringify returns undefined for a bare function/symbol/undefined at the top level.
51
58
  out[key] = null;
@@ -55,7 +62,8 @@ export function serializeDataScope(defs: Record<string, unknown>): Record<string
55
62
  out[key] = LARGE_VALUE_PLACEHOLDER;
56
63
  continue;
57
64
  }
58
- out[key] = JSON.parse(json) as unknown;
65
+ // SAFETY: `json` is JSON.stringify's own output above, so it parses back to JSON data.
66
+ out[key] = JSON.parse(json) as JsonValue;
59
67
  } catch {
60
68
  // A circular structure or a throwing getter → the key exists but carries no serializable value.
61
69
  out[key] = null;
@@ -32,6 +32,7 @@
32
32
  */
33
33
 
34
34
  import { PANE_SELECTOR } from "../surfaces/pane-grid";
35
+ import { stageOf } from "./surface-registry";
35
36
  import type { CanvasSurface } from "./surface-registry";
36
37
 
37
38
  /**
@@ -61,7 +62,7 @@ export function scrollbarWidthOf(scroller: HTMLElement): number {
61
62
 
62
63
  /** The cell a stage belongs to, or the document root when it is outside every cell. */
63
64
  function cellOf(surface: CanvasSurface): HTMLElement {
64
- return surface.wrap?.closest<HTMLElement>(PANE_SELECTOR) ?? document.documentElement;
65
+ return stageOf(surface)?.closest<HTMLElement>(PANE_SELECTOR) ?? document.documentElement;
65
66
  }
66
67
 
67
68
  /**
@@ -154,7 +154,7 @@ export function surfaceForPane(paneId: string): CanvasSurface {
154
154
 
155
155
  /** A surface record with nothing in it. One place the per-stage defaults are written down. */
156
156
  function freshSurface(paneId: string): CanvasSurface {
157
- return {
157
+ const record: Omit<CanvasSurface, "wrap"> & { wrap: HTMLElement | null } = {
158
158
  centerObserver: null,
159
159
  monacoDiffEditor: null,
160
160
  monacoEditor: null,
@@ -171,8 +171,26 @@ function freshSurface(paneId: string): CanvasSurface {
171
171
  renderGeneration: 0,
172
172
  sourceCollabCleanup: null,
173
173
  stageScrollbar: null,
174
- wrap: null as unknown as HTMLElement,
174
+ wrap: null,
175
175
  };
176
+ /* SAFETY: `wrap` is the only member that differs from CanvasSurface, and it is null only until
177
+ * registerCanvasSurface attaches the pane's stage. The grid registers a cell's stage as it builds
178
+ * the cell, before any renderer runs (§18.1 rule 1), and the paths that can run earlier read the
179
+ * element through stageOf(), which keeps the nullable type. */
180
+ return record as CanvasSurface;
181
+ }
182
+
183
+ /**
184
+ * The surface's stage element, or null before {@link registerCanvasSurface} attaches one.
185
+ *
186
+ * `wrap` is typed non-null for the renderers, which only run after registration. The gestures and
187
+ * teardowns that can reach a surface before its host exists read it through here instead.
188
+ *
189
+ * @param {CanvasSurface} surface
190
+ * @returns {HTMLElement | null}
191
+ */
192
+ export function stageOf(surface: CanvasSurface): HTMLElement | null {
193
+ return surface.wrap;
176
194
  }
177
195
 
178
196
  /**
@@ -199,7 +217,7 @@ export function stageContaining(node: Node | null): CanvasSurface | null {
199
217
  return null;
200
218
  }
201
219
  for (const surface of _surfaces.values()) {
202
- if (surface.wrap?.contains(node)) {
220
+ if (stageOf(surface)?.contains(node)) {
203
221
  return surface;
204
222
  }
205
223
  }
@@ -19,7 +19,12 @@ import { collabState } from "./collab-state";
19
19
  import { isCollabEnabled, setCollabEnabled } from "./collab-session";
20
20
  import { notify } from "../services/notify";
21
21
  import { activeTab } from "../workspace/workspace";
22
- import { booleanArg, booleanProperty, stringProperty } from "../commands/command-args";
22
+ import {
23
+ booleanArg,
24
+ booleanProperty,
25
+ optionalStringArg,
26
+ stringProperty,
27
+ } from "../commands/command-args";
23
28
 
24
29
  import type { AnyCommand, CommandRegistry } from "../commands/registry";
25
30
  import type { Tab } from "../tabs/tab";
@@ -173,7 +178,7 @@ export function collabCommands(): AnyCommand[] {
173
178
  if (!active) {
174
179
  return;
175
180
  }
176
- const login = (args as { login?: string } | undefined)?.login;
181
+ const login = optionalStringArg("collab.follow", args, "login");
177
182
  const { peers } = collabState(active);
178
183
  const peer = login ? peers.find((p) => p.state.user.login === login) : peers[0];
179
184
  if (!peer) {