@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
@@ -43,7 +43,16 @@ import {
43
43
  tagExpressionSchema,
44
44
  tagNameSchema,
45
45
  } from "@jxsuite/schema/defs";
46
- import { isFunctionDef, isRef, isTemplateString } from "@jxsuite/schema/guards";
46
+ import {
47
+ isFunctionDef,
48
+ isJsonObject,
49
+ isNonNullObject,
50
+ isRef,
51
+ isString,
52
+ isTemplateString,
53
+ } from "@jxsuite/schema/guards";
54
+ import type { ReadonlyJsonObject, ReadonlyJsonValue } from "@jxsuite/schema/project-schemas";
55
+ import type { JsonValue, JxDocumentValue } from "@jxsuite/schema/types";
47
56
 
48
57
  /** A rung of the value ladder — one way a document position can produce its value. */
49
58
  export type SlotMode = "literal" | "ref" | "template" | "expression" | "function";
@@ -73,7 +82,7 @@ export const VALUE_SOURCE_HINTS: Record<SlotMode, string> = {
73
82
  };
74
83
 
75
84
  /** Detect which rung of the ladder a raw document value occupies. */
76
- export function slotMode(value: unknown): SlotMode {
85
+ export function slotMode(value: JxDocumentValue | JsonValue | undefined): SlotMode {
77
86
  if (isRef(value)) {
78
87
  return "ref";
79
88
  }
@@ -83,7 +92,7 @@ export function slotMode(value: unknown): SlotMode {
83
92
  if (isTemplateString(value)) {
84
93
  return "template";
85
94
  }
86
- if (value && typeof value === "object" && "$expression" in value) {
95
+ if (isNonNullObject(value) && "$expression" in value) {
87
96
  return "expression";
88
97
  }
89
98
  return "literal";
@@ -91,8 +100,6 @@ export function slotMode(value: unknown): SlotMode {
91
100
 
92
101
  // ─── Schema walk ────────────────────────────────────────────────────────────
93
102
 
94
- type SchemaNode = Record<string, unknown>;
95
-
96
103
  /**
97
104
  * The `on*` property shape that `@jxsuite/schema`'s generator synthesises for every EventHandler
98
105
  * IDL attribute (`buildEventHandlerProperties`). It is the only slot position with no declared
@@ -107,40 +114,45 @@ export const EVENT_HANDLER_SCHEMA = {
107
114
  } as const;
108
115
 
109
116
  /** The `$defs` a slot position can actually reach. Anything else is a bug, not a silent no-rung. */
110
- const REACHABLE_DEFS: Record<string, SchemaNode> = {
111
- ExpressionEntry: expressionEntrySchema as unknown as SchemaNode,
112
- FunctionDef: functionDefSchema as unknown as SchemaNode,
113
- RefObject: refObjectSchema as unknown as SchemaNode,
114
- StyleObject: styleObjectSchema as unknown as SchemaNode,
115
- };
117
+ const REACHABLE_DEFS = new Map<string, ReadonlyJsonObject>([
118
+ ["ExpressionEntry", expressionEntrySchema],
119
+ ["FunctionDef", functionDefSchema],
120
+ ["RefObject", refObjectSchema],
121
+ ["StyleObject", styleObjectSchema],
122
+ ]);
116
123
 
117
124
  /** A `{ $ref: … }` binding object: `required: ["$ref"]` with a `$ref` property. */
118
- function isRefObjectSchema(node: SchemaNode): boolean {
119
- const { required } = node;
125
+ function isRefObjectSchema(node: ReadonlyJsonObject): boolean {
126
+ const { properties, required } = node;
120
127
  return (
121
128
  Array.isArray(required) &&
122
129
  required.includes("$ref") &&
123
- Boolean((node.properties as SchemaNode | undefined)?.$ref)
130
+ isJsonObject(properties) &&
131
+ Boolean(properties.$ref)
124
132
  );
125
133
  }
126
134
 
127
135
  /** A `{ $expression: … }` entry. */
128
- function isExpressionEntrySchema(node: SchemaNode): boolean {
136
+ function isExpressionEntrySchema(node: ReadonlyJsonObject): boolean {
129
137
  const { required } = node;
130
138
  return Array.isArray(required) && required.includes("$expression");
131
139
  }
132
140
 
133
141
  /** A `$prototype: "Function"` declaration. */
134
- function isFunctionDefSchema(node: SchemaNode): boolean {
135
- const proto = (node.properties as SchemaNode | undefined)?.$prototype as SchemaNode | undefined;
136
- return proto?.const === "Function";
142
+ function isFunctionDefSchema(node: ReadonlyJsonObject): boolean {
143
+ const { properties } = node;
144
+ return (
145
+ isJsonObject(properties) &&
146
+ isJsonObject(properties.$prototype) &&
147
+ properties.$prototype.const === "Function"
148
+ );
137
149
  }
138
150
 
139
151
  /**
140
152
  * A string branch that will accept `${…}`. A pattern/enum/const-constrained string will not — a
141
153
  * `TagName` or a `#/state/…` pointer is a string, but not a place a template may be typed.
142
154
  */
143
- function isFreeStringSchema(node: SchemaNode): boolean {
155
+ function isFreeStringSchema(node: ReadonlyJsonObject): boolean {
144
156
  return (
145
157
  node.type === "string" &&
146
158
  node.pattern === undefined &&
@@ -155,23 +167,24 @@ interface WalkState {
155
167
  found: Set<SlotMode>;
156
168
  /** Pointers the walk could not resolve — surfaced so a schema move fails loudly. */
157
169
  unresolved: Set<string>;
158
- seen: Set<SchemaNode>;
170
+ seen: Set<ReadonlyJsonObject>;
159
171
  }
160
172
 
161
- function walk(node: unknown, state: WalkState): void {
162
- if (!node || typeof node !== "object") {
173
+ function walk(schema: ReadonlyJsonValue | undefined, state: WalkState): void {
174
+ /* An array is an object too, but it names no keyword: the walk below found nothing in one, so
175
+ refusing it here is the same answer reached sooner. */
176
+ if (!isJsonObject(schema)) {
163
177
  return;
164
178
  }
165
- const schema = node as SchemaNode;
166
179
  if (state.seen.has(schema)) {
167
180
  return;
168
181
  }
169
182
  state.seen.add(schema);
170
183
 
171
- const pointer = typeof schema.$ref === "string" ? schema.$ref : undefined;
184
+ const pointer = isString(schema.$ref) ? schema.$ref : undefined;
172
185
  if (pointer !== undefined && Object.keys(schema).length === 1) {
173
186
  const name = pointer.startsWith("#/$defs/") ? pointer.slice("#/$defs/".length) : pointer;
174
- const target = REACHABLE_DEFS[name];
187
+ const target = REACHABLE_DEFS.get(name);
175
188
  if (target) {
176
189
  walk(target, state);
177
190
  } else {
@@ -218,7 +231,7 @@ function walk(node: unknown, state: WalkState): void {
218
231
  state.found.add("literal");
219
232
  return;
220
233
  }
221
- if (typeof schema.type === "string" && SCALAR_TYPES.has(schema.type)) {
234
+ if (isString(schema.type) && SCALAR_TYPES.has(schema.type)) {
222
235
  state.found.add("literal");
223
236
  }
224
237
  }
@@ -230,7 +243,7 @@ export interface DerivedCaps {
230
243
  }
231
244
 
232
245
  /** Derive the rungs a subschema permits, and report any pointer the walk could not follow. */
233
- export function deriveSlotCapsDetailed(schema: unknown): DerivedCaps {
246
+ export function deriveSlotCapsDetailed(schema: ReadonlyJsonValue | undefined): DerivedCaps {
234
247
  const state: WalkState = { found: new Set(), seen: new Set(), unresolved: new Set() };
235
248
  walk(schema, state);
236
249
  return {
@@ -240,7 +253,7 @@ export function deriveSlotCapsDetailed(schema: unknown): DerivedCaps {
240
253
  }
241
254
 
242
255
  /** Derive the rungs a subschema permits, in ladder order. */
243
- export function deriveSlotCaps(schema: unknown): SlotMode[] {
256
+ export function deriveSlotCaps(schema: ReadonlyJsonValue | undefined): SlotMode[] {
244
257
  return deriveSlotCapsDetailed(schema).caps;
245
258
  }
246
259
 
@@ -322,7 +335,7 @@ export type SlotPosition = keyof typeof SLOT_POSITION_SCHEMAS;
322
335
  * There is deliberately no third form. A caller may not state which rungs it wants: the rungs are
323
336
  * derived from a schema or they are not derived at all.
324
337
  */
325
- export type SlotCapsSource = SlotPosition | { schema: unknown };
338
+ export type SlotCapsSource = SlotPosition | { schema: ReadonlyJsonValue };
326
339
 
327
340
  const capsCache = new Map<SlotPosition, SlotMode[]>();
328
341
 
@@ -339,7 +352,7 @@ export function capsForPosition(position: SlotPosition): SlotMode[] {
339
352
 
340
353
  /** The rungs on offer for a {@link SlotCapsSource} — a named position, or a schema handed in. */
341
354
  export function slotCaps(source: SlotCapsSource): SlotMode[] {
342
- return typeof source === "string" ? capsForPosition(source) : deriveSlotCaps(source.schema);
355
+ return isString(source) ? capsForPosition(source) : deriveSlotCaps(source.schema);
343
356
  }
344
357
 
345
358
  /**
@@ -351,7 +364,7 @@ export function slotCaps(source: SlotCapsSource): SlotMode[] {
351
364
  * own config at runtime — which no plugin schema declares because it is not the plugin's to
352
365
  * declare. Both are stated here, once, rather than assumed at each form.
353
366
  */
354
- export function configFieldSchema(field: unknown): { anyOf: unknown[] } {
367
+ export function configFieldSchema(field: ReadonlyJsonValue): ReadonlyJsonObject {
355
368
  const declared = deriveSlotCaps(field).length > 0 ? field : { type: "string" };
356
369
  return { anyOf: [declared, { $ref: "#/$defs/RefObject" }] };
357
370
  }
@@ -28,6 +28,7 @@
28
28
 
29
29
  /// <reference lib="dom" />
30
30
 
31
+ import { isFunction } from "@jxsuite/schema/guards";
31
32
  import { rectOf } from "../utils/geometry";
32
33
 
33
34
  /** The measurement a window is computed from. Everything is in CSS pixels. */
@@ -244,7 +245,7 @@ export function createVirtualWindow(options: VirtualWindowOptions): VirtualWindo
244
245
  };
245
246
 
246
247
  let resizeObserver: ResizeObserver | null = null;
247
- if (typeof ResizeObserver === "function") {
248
+ if (isFunction(globalThis.ResizeObserver)) {
248
249
  resizeObserver = new ResizeObserver(measureNextFrame);
249
250
  resizeObserver.observe(scroller);
250
251
  if (list) {
@@ -373,7 +374,7 @@ export function listWindow(
373
374
  return computeWindow({
374
375
  columns: spec.columns ?? 1,
375
376
  count: spec.count,
376
- ...(spec.overscanRows === undefined ? {} : { overscanRows: spec.overscanRows }),
377
+ overscanRows: spec.overscanRows ?? DEFAULT_OVERSCAN_ROWS,
377
378
  rowHeight: spec.rowHeight,
378
379
  scrollTop: listScrollTop(scroller, list),
379
380
  viewportHeight: scroller.clientHeight,
@@ -11,6 +11,8 @@
11
11
  * backends answer a base64 string for the same reason they accept one, and the caller wants bytes.
12
12
  */
13
13
 
14
+ import { isString } from "@jxsuite/schema/guards";
15
+
14
16
  /** Bytes per `String.fromCodePoint` call — spreading a whole large file blows the call stack. */
15
17
  const CHUNK = 32_768;
16
18
 
@@ -28,7 +30,7 @@ export function bytesToBase64(bytes: Uint8Array): string {
28
30
  * have always received base64 strings, so existing callers keep working.
29
31
  */
30
32
  export async function toBase64(data: string | File | Blob | ArrayBuffer): Promise<string> {
31
- if (typeof data === "string") {
33
+ if (isString(data)) {
32
34
  return data;
33
35
  }
34
36
  const buffer = data instanceof ArrayBuffer ? data : await data.arrayBuffer();
@@ -5,18 +5,50 @@
5
5
  * placeholders).
6
6
  */
7
7
 
8
- import type { JxMutableNode } from "@jxsuite/schema/types";
9
- import { displayTagName } from "@jxsuite/schema/guards";
8
+ import type { JxDocumentValue, JxMutableNode } from "@jxsuite/schema/types";
9
+ import { displayTagName, isNodeObject, isNonNullObject, isString } from "@jxsuite/schema/guards";
10
+ import { isDocumentContainer } from "./strip-events";
11
+ import type { RebuiltNode } from "./strip-events";
10
12
 
11
13
  const mediaTags = new Set(["img", "video", "source", "iframe", "audio"]);
12
- const TRANSPARENT_PX =
14
+ /** A 1×1 transparent GIF: what an unresolvable `src` binding shows in edit mode. */
15
+ export const TRANSPARENT_PX =
13
16
  "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
14
17
  // A neutral, visible gray placeholder for image-like bindings in edit mode (e.g. a component `image`
15
18
  // Prop bound to ${...}). The component renders <img src={prop}> internally, so a transparent pixel
16
19
  // Would collapse to nothing — a gray SVG box reads as an intentional placeholder instead.
17
- const PLACEHOLDER_IMG =
20
+ export const PLACEHOLDER_IMG =
18
21
  "data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='32'%20height='32'%3E%3Crect%20width='32'%20height='32'%20fill='%23d0d0d0'/%3E%3C/svg%3E";
19
22
 
23
+ /**
24
+ * Swap every image under `host` that fails to load for {@link PLACEHOLDER_IMG}, until `signal`
25
+ * aborts.
26
+ *
27
+ * For previews drawn in the PARENT realm, where nothing ran {@link prepareForEditMode}: a library
28
+ * card's file, a component whose image prop is a `${}` binding with nothing to bind it, a document
29
+ * whose `src` is empty. The browser drew each as its broken-image glyph. `error` does not bubble,
30
+ * so the listener captures — which also covers images a preview renders after it is mounted. An
31
+ * empty `src` fires `error` too, so it needs no separate test; an image already showing the
32
+ * placeholder is left alone, so a placeholder that cannot load (it always can) does not loop.
33
+ *
34
+ * @param {Element} host
35
+ * @param {AbortSignal} signal
36
+ */
37
+ export function placeholderBrokenImages(host: Element, signal: AbortSignal): void {
38
+ host.addEventListener(
39
+ "error",
40
+ (event) => {
41
+ const { target } = event;
42
+ if (target instanceof HTMLImageElement && target.getAttribute("src") !== PLACEHOLDER_IMG) {
43
+ target.setAttribute("src", PLACEHOLDER_IMG);
44
+ target.removeAttribute("srcset");
45
+ target.dataset.placeholder = "";
46
+ }
47
+ },
48
+ { capture: true, signal },
49
+ );
50
+ }
51
+
20
52
  // Prop/attribute names that feed an <img>/background, so a ${...} binding would otherwise render a
21
53
  // Broken image in edit mode. Matched by exact name or common camelCase suffix (featuredImage, heroBg).
22
54
  const IMAGE_KEY_RE =
@@ -132,6 +164,7 @@ export function templateToEditDisplay(str: string) {
132
164
  export function restoreTemplateExpressions(el: HTMLElement) {
133
165
  const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
134
166
  while (walker.nextNode()) {
167
+ // SAFETY: the walker was created with NodeFilter.SHOW_TEXT, so every node it stops on is Text.
135
168
  const node = walker.currentNode as Text;
136
169
  if (node.data.includes("\u276A")) {
137
170
  node.data = node.data.replaceAll(/\u276A\s*(.*?)\s*\u276B/g, "${$1}");
@@ -146,52 +179,64 @@ export function restoreTemplateExpressions(el: HTMLElement) {
146
179
  * edit-only device — it is never compiled or rendered in preview.
147
180
  *
148
181
  * @param {JxMutableNode} arrayObj
149
- * @returns {Record<string, unknown>}
182
+ * @returns {JxMutableNode}
150
183
  */
151
- function arrayToPerimeter(arrayObj: JxMutableNode): Record<string, unknown> {
184
+ function arrayToPerimeter(arrayObj: JxMutableNode): JxMutableNode {
152
185
  const template = arrayObj.map;
153
186
  return {
154
- children:
155
- template && typeof template === "object"
156
- ? [prepareForEditMode(template as JxMutableNode)]
157
- : [],
187
+ children: isNonNullObject(template) ? [prepareForEditMode(template)] : [],
158
188
  className: "repeater-perimeter",
159
189
  tagName: "div",
160
190
  };
161
191
  }
162
192
 
193
+ /**
194
+ * The text a `$ref` binding shows in edit mode: its pointer, with the `#/state/` prefix dropped.
195
+ *
196
+ * @param {string} ref
197
+ */
198
+ function refDisplayLabel(ref: string): string {
199
+ const label = ref.startsWith("#/state/") ? ref.slice(8) : ref;
200
+ return `{${label}}`;
201
+ }
202
+
163
203
  /**
164
204
  * Prepare a document for edit-mode rendering. Replaces template strings with readable literal text,
165
205
  * $prototype:Array with placeholders, and $ref bindings with display labels. Preserves state so the
166
- * runtime can still initialise scope.
206
+ * runtime can still initialise scope. Arrays are prepared member by member, and primitives and a
207
+ * cleared (`undefined`) key return unchanged, so the walker can recurse through any document
208
+ * value.
167
209
  *
168
210
  * @param {JxMutableNode} node
169
211
  * @returns {JxMutableNode}
170
212
  */
171
- export function prepareForEditMode(node: JxMutableNode): JxMutableNode {
172
- if (!node || typeof node !== "object") {
173
- return node;
174
- }
213
+ export function prepareForEditMode(node: JxMutableNode): JxMutableNode;
214
+ export function prepareForEditMode(node: JxDocumentValue): JxDocumentValue;
215
+ export function prepareForEditMode(node: JxDocumentValue | undefined): JxDocumentValue | undefined;
216
+ export function prepareForEditMode(node: JxDocumentValue | undefined): JxDocumentValue | undefined {
175
217
  if (Array.isArray(node)) {
176
218
  // Arrays of nodes round-trip element-wise; the array itself is not a node.
177
- return (node as JxMutableNode[]).map((n) => prepareForEditMode(n)) as unknown as JxMutableNode;
219
+ return node.map((n) => prepareForEditMode(n));
220
+ }
221
+ if (!isNodeObject(node)) {
222
+ return node;
178
223
  }
179
224
 
180
225
  // A mapped-array node itself → its edit-mode perimeter (e.g. when the patcher re-renders an
181
226
  // Array node directly). Members inside a children array funnel here via the children branch.
182
- if ((node as Record<string, unknown>).$prototype === "Array") {
183
- return arrayToPerimeter(node) as unknown as JxMutableNode;
227
+ if (node.$prototype === "Array") {
228
+ return arrayToPerimeter(node);
184
229
  }
185
230
 
186
- const /** @type {Record<string, unknown>} */ obj = node as Record<string, unknown>;
231
+ const obj = node;
187
232
 
188
- const out: Record<string, unknown> = {};
233
+ const out: RebuiltNode = {};
189
234
  let needsMediaPlaceholder = false;
190
- const isMediaElement = mediaTags.has((obj.tagName as string) || "");
235
+ const isMediaElement = isString(obj.tagName) && mediaTags.has(obj.tagName);
191
236
 
192
237
  // Check if this media element lacks a resolvable src (top-level or in attributes)
193
238
  if (isMediaElement) {
194
- const attrs = obj.attributes as Record<string, unknown> | undefined;
239
+ const attrs = obj.attributes;
195
240
  const topSrc = obj.src;
196
241
  const attrSrc = attrs?.src;
197
242
  const topPoster = obj.poster;
@@ -206,11 +251,11 @@ export function prepareForEditMode(node: JxMutableNode): JxMutableNode {
206
251
  for (const [k, v] of Object.entries(obj)) {
207
252
  if (k === "state" || k === "$media" || k === "$elements") {
208
253
  out[k] = v; // Preserve as-is for runtime resolution
209
- } else if (k === "$props" && v && typeof v === "object") {
254
+ } else if (k === "$props" && isDocumentContainer(v)) {
210
255
  // Process $props values: convert template strings to display format
211
- const propsOut: Record<string, unknown> = {};
256
+ const propsOut: RebuiltNode = {};
212
257
  for (const [pk, pv] of Object.entries(v)) {
213
- if (typeof pv === "string" && pv.includes("${")) {
258
+ if (isString(pv) && pv.includes("${")) {
214
259
  // Image-like prop → neutral placeholder (the component renders it as an <img src>); link
215
260
  // Targets → inert ""; everything else → the readable ❪ expr ❫ binding text.
216
261
  if (isImageUrlKey(pk)) {
@@ -220,10 +265,8 @@ export function prepareForEditMode(node: JxMutableNode): JxMutableNode {
220
265
  } else {
221
266
  propsOut[pk] = templateToEditDisplay(pv);
222
267
  }
223
- } else if (pv && typeof pv === "object" && (pv as Record<string, unknown>).$ref) {
224
- const ref = (pv as Record<string, unknown>).$ref as string;
225
- const label = ref.startsWith("#/state/") ? ref.slice(8) : ref;
226
- propsOut[pk] = `{${label}}`;
268
+ } else if (isNodeObject(pv) && pv.$ref) {
269
+ propsOut[pk] = refDisplayLabel(pv.$ref);
227
270
  } else {
228
271
  propsOut[pk] = pv;
229
272
  }
@@ -233,25 +276,21 @@ export function prepareForEditMode(node: JxMutableNode): JxMutableNode {
233
276
  if (Array.isArray(v)) {
234
277
  // Each member recurses; array pseudo-elements become a single repeater-perimeter element
235
278
  // (via the top-level array case) so they stay a 1:1 DOM node among their siblings.
236
- out.children = v.map((c) => prepareForEditMode(c as JxMutableNode));
237
- } else if (
238
- v &&
239
- typeof v === "object" &&
240
- (v as Record<string, unknown>).$prototype === "Array"
241
- ) {
279
+ out.children = v.map((c) => prepareForEditMode(c));
280
+ } else if (isNodeObject(v) && v.$prototype === "Array") {
242
281
  // Legacy whole-children repeater → a single perimeter as the sole child.
243
- out.children = [prepareForEditMode(v as JxMutableNode)];
282
+ out.children = [prepareForEditMode(v)];
244
283
  } else {
245
- out.children = prepareForEditMode(v as JxMutableNode);
284
+ out.children = prepareForEditMode(v);
246
285
  }
247
- } else if (k === "cases" && obj.$switch && v && typeof v === "object") {
286
+ } else if (k === "cases" && obj.$switch && isDocumentContainer(v)) {
248
287
  // Replace $switch cases with a placeholder showing the first case or a label
249
- const caseKeys = Object.keys(v);
250
- if (caseKeys.length > 0) {
251
- const firstCase = (v as Record<string, unknown>)[caseKeys[0]!];
288
+ const [firstEntry] = Object.entries(v);
289
+ if (firstEntry) {
290
+ const [, firstCase] = firstEntry;
252
291
  out.children =
253
- firstCase && typeof firstCase === "object" && !(firstCase as Record<string, unknown>).$ref
254
- ? [prepareForEditMode(firstCase as JxMutableNode)]
292
+ isDocumentContainer(firstCase) && !(isNodeObject(firstCase) && firstCase.$ref)
293
+ ? [prepareForEditMode(firstCase)]
255
294
  : [
256
295
  {
257
296
  style: {
@@ -265,29 +304,26 @@ export function prepareForEditMode(node: JxMutableNode): JxMutableNode {
265
304
  padding: "6px 10px",
266
305
  },
267
306
  tagName: "div",
268
- textContent: `[$switch: ${caseKeys.join(" | ")}]`,
307
+ textContent: `[$switch: ${Object.keys(v).join(" | ")}]`,
269
308
  },
270
309
  ];
271
310
  }
272
- } else if (k === "attributes" && isMediaElement && v && typeof v === "object") {
311
+ } else if (k === "attributes" && isMediaElement && isDocumentContainer(v)) {
273
312
  // Process attributes for media elements: replace src/poster with transparent pixel
274
- const attrs = v as Record<string, unknown>;
275
- const processed: Record<string, unknown> = {};
276
- for (const [ak, av] of Object.entries(attrs)) {
313
+ const processed: RebuiltNode = {};
314
+ for (const [ak, av] of Object.entries(v)) {
277
315
  if (ak === "src" || ak === "poster") {
278
- if (typeof av === "string" && av !== "" && !av.includes("${")) {
316
+ if (isString(av) && av !== "" && !av.includes("${")) {
279
317
  processed[ak] = av;
280
318
  } else {
281
319
  needsMediaPlaceholder = true;
282
320
  processed[ak] = TRANSPARENT_PX;
283
321
  }
284
- } else if (typeof av === "string" && av.includes("${")) {
322
+ } else if (isString(av) && av.includes("${")) {
285
323
  const isUrlAttr = ak === "href" || ak === "action";
286
324
  processed[ak] = isUrlAttr ? "" : templateToEditDisplay(av);
287
- } else if (av && typeof av === "object" && (av as Record<string, unknown>).$ref) {
288
- const ref = (av as Record<string, unknown>).$ref as string;
289
- const label = ref.startsWith("#/state/") ? ref.slice(8) : ref;
290
- processed[ak] = `{${label}}`;
325
+ } else if (isNodeObject(av) && av.$ref) {
326
+ processed[ak] = refDisplayLabel(av.$ref);
291
327
  } else {
292
328
  processed[ak] = av;
293
329
  }
@@ -298,10 +334,10 @@ export function prepareForEditMode(node: JxMutableNode): JxMutableNode {
298
334
  cannot be resolved; omitting the property lets the cascade answer, which is exactly what
299
335
  the `""` this used to write meant while declarations went inline. As a RULE, `color: ` is
300
336
  a malformed declaration instead of a no-op. Mirrored by iframe-patch's editModeStyle. */
301
- if (v && typeof v === "object") {
302
- const s: Record<string, unknown> = {};
337
+ if (isDocumentContainer(v)) {
338
+ const s: RebuiltNode = {};
303
339
  for (const [sk, sv] of Object.entries(v)) {
304
- if (typeof sv !== "string" || !sv.includes("${")) {
340
+ if (!isString(sv) || !sv.includes("${")) {
305
341
  s[sk] = sv;
306
342
  }
307
343
  }
@@ -309,10 +345,9 @@ export function prepareForEditMode(node: JxMutableNode): JxMutableNode {
309
345
  } else {
310
346
  out.style = v;
311
347
  }
312
- } else if (typeof v === "string" && v.includes("${")) {
348
+ } else if (isString(v) && v.includes("${")) {
313
349
  // Template string in a display property → show raw expression
314
- const isMediaSrc =
315
- (k === "src" || k === "poster") && mediaTags.has((obj.tagName as string) || "");
350
+ const isMediaSrc = (k === "src" || k === "poster") && isMediaElement;
316
351
  if (isMediaSrc) {
317
352
  needsMediaPlaceholder = true;
318
353
  out[k] = TRANSPARENT_PX;
@@ -320,42 +355,36 @@ export function prepareForEditMode(node: JxMutableNode): JxMutableNode {
320
355
  const isUrlAttr = k === "src" || k === "href" || k === "poster" || k === "action";
321
356
  out[k] = isUrlAttr ? "" : templateToEditDisplay(v);
322
357
  }
323
- } else if (v && typeof v === "object" && (v as Record<string, unknown>).$ref) {
358
+ } else if (isNodeObject(v) && v.$ref) {
324
359
  // $ref binding → show ref path as literal text
325
- const ref = (v as Record<string, unknown>).$ref as string;
326
- const label = ref.startsWith("#/state/") ? ref.slice(8) : ref;
327
- const isMediaSrc =
328
- (k === "src" || k === "poster") && mediaTags.has((obj.tagName as string) || "");
360
+ const label = refDisplayLabel(v.$ref);
361
+ const isMediaSrc = (k === "src" || k === "poster") && isMediaElement;
329
362
  if (isMediaSrc) {
330
363
  needsMediaPlaceholder = true;
331
364
  out[k] = TRANSPARENT_PX;
332
365
  } else {
333
- out[k] = `{${label}}`;
366
+ out[k] = label;
334
367
  }
335
368
  } else {
336
369
  // Empty src/poster on media elements → use transparent pixel placeholder
337
- if (
338
- (k === "src" || k === "poster") &&
339
- v === "" &&
340
- mediaTags.has((obj.tagName as string) || "")
341
- ) {
370
+ if ((k === "src" || k === "poster") && v === "" && isMediaElement) {
342
371
  needsMediaPlaceholder = true;
343
372
  out[k] = TRANSPARENT_PX;
344
373
  } else {
345
- out[k] = prepareForEditMode(v as JxMutableNode);
374
+ out[k] = prepareForEditMode(v);
346
375
  }
347
376
  }
348
377
  }
349
378
 
350
379
  // Mark empty elements with placeholder classes for design-mode visibility
351
- const placeholderClass = computeEmptyPlaceholderClass(out as JxMutableNode);
380
+ const placeholderClass = computeEmptyPlaceholderClass(out);
352
381
  if (placeholderClass) {
353
382
  out.className = out.className ? `${out.className} ${placeholderClass}` : placeholderClass;
354
383
  }
355
384
 
356
385
  // Media elements with missing/dynamic src get a placeholder class
357
386
  if (needsMediaPlaceholder) {
358
- const cls = (out.className as string) || "";
387
+ const cls = isString(out.className) ? out.className : "";
359
388
  if (!cls.includes("empty-media-placeholder")) {
360
389
  out.className = cls ? `${cls} empty-media-placeholder` : "empty-media-placeholder";
361
390
  }
@@ -14,7 +14,7 @@
14
14
  */
15
15
 
16
16
  import { COMMON_SELECTORS } from "../store";
17
- import { displayTagName } from "@jxsuite/schema/guards";
17
+ import { displayTagName, isNonNullObject } from "@jxsuite/schema/guards";
18
18
  import { isPopover } from "@jxsuite/schema/overlays";
19
19
  import type { JxMutableNode } from "@jxsuite/schema/types";
20
20
 
@@ -62,7 +62,8 @@ function extraSelectorsFor(node: JxMutableNode): string[] {
62
62
  * @returns The offerable selectors.
63
63
  */
64
64
  export function selectorsForNode(node: JxMutableNode | null | undefined): string[] {
65
- if (!node || typeof node !== "object") {
65
+ // The style panel reads the node with getNodeAtPath, which can answer a bare string text child.
66
+ if (!isNonNullObject(node)) {
66
67
  return [...COMMON_SELECTORS];
67
68
  }
68
69
  return [...new Set([...COMMON_SELECTORS, ...extraSelectorsFor(node)])];
@@ -1,5 +1,6 @@
1
1
  /** Google Fonts helpers — shared between head-panel (per-page) and head-editor (project-level). */
2
2
 
3
+ import { isString } from "@jxsuite/schema/guards";
3
4
  import type { JxHeadEntry } from "@jxsuite/schema/types";
4
5
 
5
6
  export const GFONTS_CSS_PREFIX = "https://fonts.googleapis.com/css2?";
@@ -11,29 +12,30 @@ export const GFONTS_PRECONNECT_ORIGINS = [
11
12
  /**
12
13
  * Check if a `$head` entry is a Google Fonts stylesheet link.
13
14
  *
14
- * @param {JxHeadEntry} entry
15
+ * @param {JxHeadEntry | null | undefined} entry A `$head` slot; a JSON array can hold `null`.
15
16
  * @returns {boolean}
16
17
  */
17
- export function isGoogleFontEntry(entry: JxHeadEntry) {
18
+ export function isGoogleFontEntry(entry: JxHeadEntry | null | undefined) {
18
19
  return (
19
20
  entry?.tagName === "link" &&
20
- entry?.attributes?.rel === "stylesheet" &&
21
- typeof entry?.attributes?.href === "string" &&
22
- (entry.attributes.href as string).startsWith(GFONTS_CSS_PREFIX)
21
+ entry.attributes?.rel === "stylesheet" &&
22
+ isString(entry.attributes.href) &&
23
+ entry.attributes.href.startsWith(GFONTS_CSS_PREFIX)
23
24
  );
24
25
  }
25
26
 
26
27
  /**
27
28
  * Check if a `$head` entry is a Google Fonts preconnect link.
28
29
  *
29
- * @param {JxHeadEntry} entry
30
+ * @param {JxHeadEntry | null | undefined} entry A `$head` slot; a JSON array can hold `null`.
30
31
  * @returns {boolean}
31
32
  */
32
- export function isGoogleFontPreconnect(entry: JxHeadEntry) {
33
+ export function isGoogleFontPreconnect(entry: JxHeadEntry | null | undefined) {
33
34
  return (
34
35
  entry?.tagName === "link" &&
35
- entry?.attributes?.rel === "preconnect" &&
36
- GFONTS_PRECONNECT_ORIGINS.includes(entry?.attributes?.href as string)
36
+ entry.attributes?.rel === "preconnect" &&
37
+ isString(entry.attributes.href) &&
38
+ GFONTS_PRECONNECT_ORIGINS.includes(entry.attributes.href)
37
39
  );
38
40
  }
39
41
 
@@ -68,14 +70,13 @@ export function buildGoogleFontUrl(family: string) {
68
70
  */
69
71
  export function ensureGoogleFontPreconnects(head: JxHeadEntry[]) {
70
72
  for (const origin of GFONTS_PRECONNECT_ORIGINS) {
73
+ // A `$head` array is JSON, so a slot can hold `null` whatever the element type says.
71
74
  const exists = head.some(
72
- (e: JxHeadEntry) =>
73
- e?.tagName === "link" &&
74
- e?.attributes?.rel === "preconnect" &&
75
- e?.attributes?.href === origin,
75
+ (e: JxHeadEntry | null | undefined) =>
76
+ e?.tagName === "link" && e.attributes?.rel === "preconnect" && e.attributes.href === origin,
76
77
  );
77
78
  if (!exists) {
78
- const attrs: Record<string, string | boolean> = {
79
+ const attrs: NonNullable<JxHeadEntry["attributes"]> = {
79
80
  href: origin,
80
81
  rel: "preconnect",
81
82
  };