@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
@@ -38,6 +38,11 @@ export interface CloudSignal {
38
38
  project: CloudProject | null;
39
39
  }
40
40
 
41
+ declare global {
42
+ /** Published by the cloud shell's boot module before the studio bundle evaluates. */
43
+ var __jxCloud: CloudSignal | undefined;
44
+ }
45
+
41
46
  /**
42
47
  * Why Studio will not fall back to the dev-server adapter, most specific evidence first.
43
48
  *
@@ -86,13 +91,14 @@ const BACKENDLESS_SCHEMES: ReadonlySet<string> = new Set(["views:", "file:"]);
86
91
 
87
92
  /** Read the boot environment from the live globals and document. */
88
93
  export function bootEnvironment(): BootEnvironment {
89
- const doc = (globalThis as { document?: Document }).document;
94
+ // A DOM-less host (a test runner, a worker) has no document or location to read.
95
+ const doc = typeof document === "undefined" ? undefined : document;
90
96
  const meta = `meta[name="${BOOT_META.name}"][content="${BOOT_META.content}"]`;
91
97
  return {
92
- cloud: (globalThis as unknown as { __jxCloud?: CloudSignal }).__jxCloud != null,
98
+ cloud: globalThis.__jxCloud != null,
93
99
  declared: doc?.querySelector(meta) != null,
94
100
  launcher: launcherSignal(),
95
- protocol: (globalThis as { location?: Location }).location?.protocol ?? "",
101
+ protocol: typeof location === "undefined" ? "" : location.protocol,
96
102
  };
97
103
  }
98
104
 
@@ -159,6 +165,6 @@ export function resolveDefaultPlatform(): StudioPlatform {
159
165
  if (refusal) {
160
166
  throw new PlatformUnavailableError(refusal);
161
167
  }
162
- const cloud = (globalThis as unknown as { __jxCloud?: CloudSignal }).__jxCloud;
168
+ const cloud = globalThis.__jxCloud;
163
169
  return cloud ? createCloudPlatform(cloud.project) : createDevServerPlatform();
164
170
  }
@@ -13,9 +13,9 @@ import { canPickDirectory, pickDirectoryPath } from "../services/directory-picke
13
13
  import { negotiateCollab } from "@jxsuite/collab/negotiate";
14
14
  import type { CollabNegotiation } from "@jxsuite/collab/negotiate";
15
15
  import type { WsCollabConnection } from "@jxsuite/collab/client";
16
- import type { ProjectConfig } from "@jxsuite/schema/types";
16
+ import { isFunction } from "@jxsuite/schema/guards";
17
+ import type { JsonObject, JsonValue, JxMutableNode, ProjectConfig } from "@jxsuite/schema/types";
17
18
  import type {
18
- CreateProjectDestination,
19
19
  DataConnectionsResponse,
20
20
  DataConnectionTestResult,
21
21
  DataPushResult,
@@ -35,20 +35,22 @@ import type {
35
35
  ProjectSchemasResponse,
36
36
  ReferencesResult,
37
37
  RenameResult,
38
- SecretsSetRequest,
38
+ KeyValuePatch,
39
39
  SecretsSetResponse,
40
40
  SiteBuildResult,
41
41
  SitePreviewResult,
42
42
  StarterInfo,
43
+ StudioPlatform,
43
44
  } from "../types";
44
45
  import { problemDetail, problemMessage } from "@jxsuite/protocol";
46
+ import type { CfProxyEnvelope } from "./cloud";
45
47
  import type { UploadResult } from "@jxsuite/protocol";
46
48
 
47
- /** A directory entry from the server, tolerating extra wire fields. */
48
- type WireDirEntry = DirEntry & Record<string, unknown>;
49
-
50
49
  /** Parse a fetch Response body as JSON, asserting the expected shape at the boundary. */
51
50
  async function readJson<T>(res: Response): Promise<T> {
51
+ /* SAFETY: every route read here is a `/__studio/*` route of @jxsuite/server (specs/server.md §4),
52
+ whose replies have the shapes ../types and the wire interfaces in this file describe, and each
53
+ caller names the type of the route it called. */
52
54
  return (await res.json()) as T;
53
55
  }
54
56
 
@@ -56,11 +58,46 @@ interface ErrorBody {
56
58
  error?: string;
57
59
  }
58
60
 
61
+ /** One entry of the `/__studio/sites` glob: a site's folder and its parsed project.json. */
59
62
  interface SiteEntry {
60
- config: unknown;
63
+ config: ProjectConfig | null;
61
64
  path: string;
62
65
  }
63
66
 
67
+ /**
68
+ * `GET /__studio/project-info`: what the server knows about a folder. The route sends more than
69
+ * Studio reads (`projectRoot` among them), and those members ride along untouched.
70
+ */
71
+ interface ProjectInfoReply {
72
+ isSiteProject: boolean;
73
+ projectConfig?: ProjectConfig | null;
74
+ directories?: string[];
75
+ [member: string]: JsonValue | ProjectConfig | undefined;
76
+ }
77
+
78
+ /** The body of an `fs` event on the `/__reload` stream (the server's watcher batches them). */
79
+ interface FsEventBatch {
80
+ events?: FsEvent[];
81
+ }
82
+
83
+ /** `showDirectoryPicker()` (WICG File System Access), which only Chromium-based browsers expose. */
84
+ type ShowDirectoryPicker = (options: { mode: string }) => Promise<FileSystemDirectoryHandle>;
85
+
86
+ /** True when the window implements the File System Access directory picker. */
87
+ function hasDirectoryPicker(
88
+ host: Window,
89
+ ): host is Window & { showDirectoryPicker: ShowDirectoryPicker } {
90
+ return "showDirectoryPicker" in host && isFunction(host.showDirectoryPicker);
91
+ }
92
+
93
+ /** The options object {@link StudioPlatform.createProject} takes. */
94
+ type CreateProjectOptions = Parameters<StudioPlatform["createProject"]>[0];
95
+
96
+ /** What a row insert or update answers: the row as stored. */
97
+ interface DataRowWrite {
98
+ row: JsonObject;
99
+ }
100
+
64
101
  /**
65
102
  * Create a DevServerPlatform instance.
66
103
  *
@@ -98,6 +135,50 @@ export function createDevServerPlatform() {
98
135
  */
99
136
  let _collabConnection: Promise<WsCollabConnection> | null = null;
100
137
 
138
+ /**
139
+ * Bind the server to `root` — the binding its rootless routes and its static fallback (every
140
+ * media thumbnail, every canvas image) resolve against.
141
+ *
142
+ * **The binding is one per SERVER, not per tab.** So an empty root posts nothing: a Studio tab
143
+ * opened with no project used to post `{ root: "" }` at boot, which unbound the server for every
144
+ * other tab and turned their images into 404s. And a tab re-claims it (see
145
+ * {@link reclaimBinding}) whenever it could have been taken.
146
+ */
147
+ async function postActivate(root: string): Promise<void> {
148
+ if (!root) {
149
+ return;
150
+ }
151
+ const res = await fetch("/__studio/activate", {
152
+ body: JSON.stringify({ root }),
153
+ headers: { "Content-Type": "application/json" },
154
+ method: "POST",
155
+ });
156
+ if (!res.ok) {
157
+ const body = await readJson<ErrorBody>(res).catch((): ErrorBody => ({}));
158
+ throw new Error(
159
+ `Could not open ${root}: ${problemDetail(body) ?? `activation failed (${res.status})`}`,
160
+ );
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Re-bind the server to this tab's project. Two events can take the binding away without this tab
166
+ * hearing of it: another Studio tab activating ITS project, and a restarted `bun run dev`, which
167
+ * comes back bound to nothing. So a tab re-claims it when the author comes back to it (focus) and
168
+ * when the reload stream reconnects (a restart). Only a tab with a project claims anything.
169
+ */
170
+ function reclaimBinding(): void {
171
+ if (!_projectRoot) {
172
+ return;
173
+ }
174
+ _activation = postActivate(_projectRoot).catch((error) => {
175
+ console.error("Project re-activation failed:", error);
176
+ });
177
+ }
178
+ if (typeof window !== "undefined") {
179
+ window.addEventListener("focus", reclaimBinding);
180
+ }
181
+
101
182
  /**
102
183
  * Prefix a project-relative path with the active project root for server API calls.
103
184
  *
@@ -127,6 +208,36 @@ export function createDevServerPlatform() {
127
208
  return p.startsWith(`${_projectRoot}/`) ? p.slice(_projectRoot.length + 1) : p;
128
209
  }
129
210
 
211
+ /**
212
+ * Native folder chooser for the New Project modal's Browse… button. Present only when the browser
213
+ * implements `showDirectoryPicker` (Chromium-based); elsewhere the member stays undefined and the
214
+ * modal falls back to a typed Location field.
215
+ *
216
+ * The picked handle carries no filesystem path, so it is resolved by the id the picker tags the
217
+ * folder with (see `@jxsuite/studio/directory-picker`). Resolves `null` only when the user
218
+ * dismisses the chooser; every other failure rejects with a sentence the modal shows under
219
+ * Location. So `locate` keeps the helper's contract: `null` is "no folder holds this id" (the
220
+ * route's 200 `{ path: null }`), and a non-ok answer throws the problem the server sent rather
221
+ * than posing as a miss. A network failure (`TypeError: Failed to fetch`, e.g. a page that is not
222
+ * being served by the dev server at all) propagates as is, and the picker names it.
223
+ */
224
+ const directoryPicking: Pick<StudioPlatform, "pickDirectory"> = {};
225
+ if (canPickDirectory()) {
226
+ directoryPicking.pickDirectory = async (): Promise<string | null> =>
227
+ pickDirectoryPath(async ({ id, name }) => {
228
+ const res = await fetch(
229
+ `/__studio/locate-directory?name=${encodeURIComponent(name)}&id=${encodeURIComponent(id)}`,
230
+ );
231
+ if (!res.ok) {
232
+ // A proxy, or a `views://` host with no dev server behind it, may answer in HTML.
233
+ const body = await readJson<JsonValue>(res).catch(() => null);
234
+ throw new Error(problemMessage(body, res.status));
235
+ }
236
+ const found = await readJson<{ path?: string | null }>(res);
237
+ return found.path ?? null;
238
+ });
239
+ }
240
+
130
241
  return {
131
242
  id: "devserver",
132
243
 
@@ -145,7 +256,7 @@ export function createDevServerPlatform() {
145
256
  // Fire-and-forget: callers that need the binding in place before reading await activate()
146
257
  // Themselves. The rejection is reported rather than dropped — an unactivated root leaves
147
258
  // Every rootless endpoint (git especially) operating on the server's OWN root.
148
- _activation = this.activate(_projectRoot).catch((error: unknown) => {
259
+ _activation = this.activate(_projectRoot).catch((error) => {
149
260
  console.error("Project activation failed:", error);
150
261
  });
151
262
  }
@@ -162,35 +273,21 @@ export function createDevServerPlatform() {
162
273
  * @param {string} [root]
163
274
  */
164
275
  async activate(root?: string) {
165
- const r = root ?? _projectRoot;
166
- const res = await fetch("/__studio/activate", {
167
- body: JSON.stringify({ root: r }),
168
- headers: { "Content-Type": "application/json" },
169
- method: "POST",
170
- });
171
- if (!res.ok) {
172
- const body = (await readJson<ErrorBody>(res).catch(() => ({}))) as ErrorBody;
173
- throw new Error(
174
- `Could not open ${r}: ${problemDetail(body) ?? `activation failed (${res.status})`}`,
175
- );
176
- }
276
+ await postActivate(root ?? _projectRoot);
177
277
  },
178
278
 
179
279
  // ─── Project opening ──────────────────────────────────────────────────
180
280
 
181
281
  async openProject() {
182
282
  // Use Chrome's showDirectoryPicker API
183
- if (!("showDirectoryPicker" in window)) {
283
+ const host = window;
284
+ if (!hasDirectoryPicker(host)) {
184
285
  throw new Error("showDirectoryPicker not available — use a Chromium-based browser");
185
286
  }
186
287
 
187
288
  let dirHandle;
188
289
  try {
189
- dirHandle = await (
190
- window as unknown as {
191
- showDirectoryPicker: (opts: { mode: string }) => Promise<FileSystemDirectoryHandle>;
192
- }
193
- ).showDirectoryPicker({ mode: "readwrite" });
290
+ dirHandle = await host.showDirectoryPicker({ mode: "readwrite" });
194
291
  } catch (error) {
195
292
  // User cancelled the picker
196
293
  if (error instanceof Error && error.name === "AbortError") {
@@ -208,6 +305,9 @@ export function createDevServerPlatform() {
208
305
  }
209
306
 
210
307
  const file = await siteHandle.getFile();
308
+ /* SAFETY: the user picked this folder as a project, and project.json is that project's own
309
+ config file; it is matched below against the server's parse of the same file, and every
310
+ adapter hands project.json to Studio as the ProjectConfig the session edits. */
211
311
  const config = JSON.parse(await file.text()) as ProjectConfig;
212
312
 
213
313
  // Resolve server-relative path by matching against known sites
@@ -264,12 +364,7 @@ export function createDevServerPlatform() {
264
364
  ? await readJson<{ name: string; root: string }>(projectRes)
265
365
  : { name: "project", root: "." };
266
366
  const info = infoRes.ok
267
- ? await readJson<{
268
- isSiteProject: boolean;
269
- projectConfig?: ProjectConfig | null;
270
- directories?: string[];
271
- [key: string]: unknown;
272
- }>(infoRes)
367
+ ? await readJson<ProjectInfoReply>(infoRes)
273
368
  : { isSiteProject: false };
274
369
  return { info, meta };
275
370
  } catch {
@@ -282,27 +377,8 @@ export function createDevServerPlatform() {
282
377
  /**
283
378
  * Scaffold a project at `destination.parent/directory`. The dev server refuses the request
284
379
  * outright when no destination is supplied — it will not fall back to its own root.
285
- *
286
- * @param {{
287
- * name: string;
288
- * description?: string;
289
- * url?: string;
290
- * adapter?: string;
291
- * directory: string;
292
- * destination: CreateProjectDestination;
293
- * }} opts
294
380
  */
295
- async createProject(opts: {
296
- name: string;
297
- description?: string;
298
- url?: string;
299
- adapter?: string;
300
- directory: string;
301
- destination: CreateProjectDestination;
302
- starter?: string;
303
- template?: string;
304
- design?: Record<string, unknown>;
305
- }) {
381
+ async createProject(opts: CreateProjectOptions) {
306
382
  const res = await fetch("/__studio/create-project", {
307
383
  body: JSON.stringify(opts),
308
384
  headers: { "Content-Type": "application/json" },
@@ -315,37 +391,7 @@ export function createDevServerPlatform() {
315
391
  return await res.json();
316
392
  },
317
393
 
318
- /**
319
- * Native folder chooser for the New Project modal's Browse… button. Present only when the
320
- * browser implements `showDirectoryPicker` (Chromium-based); elsewhere the member stays
321
- * undefined and the modal falls back to a typed Location field.
322
- *
323
- * The picked handle carries no filesystem path, so it is resolved by the id the picker tags the
324
- * folder with (see `@jxsuite/studio/directory-picker`). Resolves `null` only when the user
325
- * dismisses the chooser; every other failure rejects with a sentence the modal shows under
326
- * Location. So `locate` keeps the helper's contract: `null` is "no folder holds this id" (the
327
- * route's 200 `{ path: null }`), and a non-ok answer throws the problem the server sent rather
328
- * than posing as a miss. A network failure (`TypeError: Failed to fetch`, e.g. a page that is
329
- * not being served by the dev server at all) propagates as is, and the picker names it.
330
- */
331
- ...(canPickDirectory()
332
- ? {
333
- async pickDirectory(): Promise<string | null> {
334
- return pickDirectoryPath(async ({ id, name }) => {
335
- const res = await fetch(
336
- `/__studio/locate-directory?name=${encodeURIComponent(name)}&id=${encodeURIComponent(id)}`,
337
- );
338
- if (!res.ok) {
339
- // A proxy, or a `views://` host with no dev server behind it, may answer in HTML.
340
- const body: unknown = await res.json().catch(() => null);
341
- throw new Error(problemMessage(body, res.status));
342
- }
343
- const found = await readJson<{ path?: string | null }>(res);
344
- return found.path ?? null;
345
- });
346
- },
347
- }
348
- : {}),
394
+ ...directoryPicking,
349
395
 
350
396
  /** List starter templates from the dev server. */
351
397
  async listStarters(): Promise<StarterInfo[]> {
@@ -374,7 +420,7 @@ export function createDevServerPlatform() {
374
420
  if (!res.ok) {
375
421
  throw new Error(`Failed to list directory: ${dir}`);
376
422
  }
377
- const entries = await readJson<WireDirEntry[]>(res);
423
+ const entries = await readJson<DirEntry[]>(res);
378
424
  for (const e of entries) {
379
425
  e.path = stripRoot(e.path);
380
426
  }
@@ -432,10 +478,14 @@ export function createDevServerPlatform() {
432
478
  if (!res.ok) {
433
479
  throw new Error(`Upload failed: ${path}`);
434
480
  }
435
- const body = (await res.json()) as Partial<UploadResult>;
481
+ const body = await readJson<Partial<UploadResult>>(res);
436
482
  // The dev server echoes the path today; falling back to the request keeps a backend that says
437
483
  // Nothing working rather than writing `undefined` into a document.
438
- return { path: body.path ?? path, ...(body.size === undefined ? {} : { size: body.size }) };
484
+ const uploaded: UploadResult = { path: body.path ?? path };
485
+ if (body.size !== undefined) {
486
+ uploaded.size = body.size;
487
+ }
488
+ return uploaded;
439
489
  },
440
490
 
441
491
  /** @param {string} path */
@@ -514,13 +564,17 @@ export function createDevServerPlatform() {
514
564
  es.addEventListener("open", () => {
515
565
  opens += 1;
516
566
  if (opens > 1) {
567
+ // A reconnect is usually a restarted server, which comes back bound to no project.
568
+ reclaimBinding();
517
569
  options?.onResync?.("reconnect");
518
570
  }
519
571
  });
520
- es.addEventListener("fs", (ev: MessageEvent) => {
521
- let payload: { events?: FsEvent[] };
572
+ es.addEventListener("fs", (ev: MessageEvent<string>) => {
573
+ let payload: FsEventBatch;
522
574
  try {
523
- payload = JSON.parse(ev.data as string) as { events?: FsEvent[] };
575
+ /* SAFETY: `fs` events on /__reload are sent only by @jxsuite/server's watcher, as
576
+ broadcastEvent("fs", { events }) over its batched FsEvent buffer (server watch.ts). */
577
+ payload = JSON.parse(ev.data) as FsEventBatch;
524
578
  } catch {
525
579
  return;
526
580
  }
@@ -562,7 +616,7 @@ export function createDevServerPlatform() {
562
616
  }
563
617
  if (_collabProbe === null) {
564
618
  _collabProbe = fetch("/__studio/collab")
565
- .then(async (res) => (res.ok ? negotiateCollab(await res.json()) : null))
619
+ .then(async (res) => (res.ok ? negotiateCollab(await readJson<JsonValue>(res)) : null))
566
620
  .catch(() => null);
567
621
  }
568
622
  const negotiation = await _collabProbe;
@@ -659,7 +713,7 @@ export function createDevServerPlatform() {
659
713
  if (!res.ok) {
660
714
  return false;
661
715
  }
662
- const data = (await res.json()) as { needsInstall?: boolean };
716
+ const data = await readJson<{ needsInstall?: boolean }>(res);
663
717
  return Boolean(data.needsInstall);
664
718
  },
665
719
 
@@ -686,11 +740,8 @@ export function createDevServerPlatform() {
686
740
 
687
741
  // ─── Code services (optional) ─────────────────────────────────────────
688
742
 
689
- /**
690
- * @param {string} action
691
- * @param {unknown} payload
692
- */
693
- async codeService(action: string, payload: unknown) {
743
+ /** Run one code-service action on the server; null when the route fails or is absent. */
744
+ async codeService(action: string, payload: Parameters<StudioPlatform["codeService"]>[1]) {
694
745
  try {
695
746
  const res = await fetch(`/__studio/code/${action}`, {
696
747
  body: JSON.stringify(payload),
@@ -753,7 +804,7 @@ export function createDevServerPlatform() {
753
804
  if (!res.ok) {
754
805
  return [];
755
806
  }
756
- const entries = await readJson<WireDirEntry[]>(res);
807
+ const entries = await readJson<DirEntry[]>(res);
757
808
  for (const e of entries) {
758
809
  e.path = stripRoot(e.path);
759
810
  }
@@ -876,26 +927,26 @@ export function createDevServerPlatform() {
876
927
  return data;
877
928
  },
878
929
 
879
- async dataInsertRow(req: DataRowInsert): Promise<{ row: Record<string, unknown> }> {
930
+ async dataInsertRow(req: DataRowInsert): Promise<DataRowWrite> {
880
931
  const res = await fetch(`/__studio/data/rows?dir=${encodeURIComponent(serverPath("."))}`, {
881
932
  body: JSON.stringify(req),
882
933
  headers: { "Content-Type": "application/json" },
883
934
  method: "POST",
884
935
  });
885
- const data = await readJson<{ row: Record<string, unknown> } & ErrorBody>(res);
936
+ const data = await readJson<DataRowWrite & ErrorBody>(res);
886
937
  if (!res.ok) {
887
938
  throw new Error(problemDetail(data) ?? "Insert failed");
888
939
  }
889
940
  return data;
890
941
  },
891
942
 
892
- async dataUpdateRow(req: DataRowUpdate): Promise<{ row: Record<string, unknown> }> {
943
+ async dataUpdateRow(req: DataRowUpdate): Promise<DataRowWrite> {
893
944
  const res = await fetch(`/__studio/data/rows?dir=${encodeURIComponent(serverPath("."))}`, {
894
945
  body: JSON.stringify(req),
895
946
  headers: { "Content-Type": "application/json" },
896
947
  method: "PUT",
897
948
  });
898
- const data = await readJson<{ row: Record<string, unknown> } & ErrorBody>(res);
949
+ const data = await readJson<DataRowWrite & ErrorBody>(res);
899
950
  if (!res.ok) {
900
951
  throw new Error(problemDetail(data) ?? "Update failed");
901
952
  }
@@ -931,7 +982,7 @@ export function createDevServerPlatform() {
931
982
  },
932
983
 
933
984
  /** Write/remove secrets in the dev server's .dev.vars; names-only response. */
934
- async setSecrets(req: SecretsSetRequest): Promise<SecretsSetResponse> {
985
+ async setSecrets(req: KeyValuePatch): Promise<SecretsSetResponse> {
935
986
  const res = await fetch(`/__studio/secrets?dir=${encodeURIComponent(serverPath("."))}`, {
936
987
  body: JSON.stringify(req),
937
988
  headers: { "Content-Type": "application/json" },
@@ -947,15 +998,17 @@ export function createDevServerPlatform() {
947
998
  /**
948
999
  * Invoke a format capability (parse/serialize) server-side.
949
1000
  *
950
- * @param {Record<string, unknown>} payload — { format, action, source?, doc?, options? }
1001
+ * @param payload — { format, action, source?, doc?, options? }
951
1002
  */
952
- async formatAction(payload: Record<string, unknown>) {
1003
+ async formatAction(payload: Parameters<NonNullable<StudioPlatform["formatAction"]>>[0]) {
953
1004
  const res = await fetch("/__studio/format", {
954
1005
  body: JSON.stringify({ ...payload, dir: serverPath(".") }),
955
1006
  headers: { "Content-Type": "application/json" },
956
1007
  method: "POST",
957
1008
  });
958
- const data = await readJson<{ error?: string; result?: unknown }>(res);
1009
+ /* The format route answers `{ result }` on success (the document a format class's `parse`
1010
+ built, or the text its `serialize` wrote) and a problem document otherwise. */
1011
+ const data = await readJson<JsonObject & { result: JxMutableNode | string }>(res);
959
1012
  if (!res.ok) {
960
1013
  throw new Error(problemDetail(data) ?? "Format action failed");
961
1014
  }
@@ -981,14 +1034,13 @@ export function createDevServerPlatform() {
981
1034
  if (!res.ok) {
982
1035
  return null;
983
1036
  }
984
- const { schema } = await readJson<{ schema: unknown }>(res);
1037
+ const { schema } = await readJson<{ schema: JsonValue }>(res);
985
1038
  return schema;
986
1039
  },
987
1040
 
988
1041
  // ─── Class resolution (dev-proxy) ─────────────────────────────────────
989
1042
 
990
- /** @param {Record<string, unknown>} body */
991
- async resolveClass(body: Record<string, unknown>) {
1043
+ async resolveClass(body: Parameters<NonNullable<StudioPlatform["resolveClass"]>>[0]) {
992
1044
  const res = await fetch("/__jx_resolve__", {
993
1045
  body: JSON.stringify(body),
994
1046
  headers: { "content-type": "application/json" },
@@ -997,7 +1049,7 @@ export function createDevServerPlatform() {
997
1049
  if (!res.ok) {
998
1050
  throw new Error(`Class resolution failed: ${res.status}`);
999
1051
  }
1000
- return await readJson<unknown>(res);
1052
+ return await readJson<JsonValue>(res);
1001
1053
  },
1002
1054
 
1003
1055
  // ─── Git operations ──────────────────────────────────────────────────
@@ -1194,7 +1246,7 @@ export function createDevServerPlatform() {
1194
1246
  const body = await readJson<ErrorBody>(res);
1195
1247
  throw new Error(problemDetail(body) ?? "The site could not be built.");
1196
1248
  }
1197
- return (await res.json()) as SiteBuildResult;
1249
+ return await readJson<SiteBuildResult>(res);
1198
1250
  },
1199
1251
 
1200
1252
  /**
@@ -1213,7 +1265,7 @@ export function createDevServerPlatform() {
1213
1265
  const body = await readJson<ErrorBody>(res);
1214
1266
  throw new Error(problemDetail(body) ?? "The site could not be previewed.");
1215
1267
  }
1216
- return (await res.json()) as SitePreviewResult;
1268
+ return await readJson<SitePreviewResult>(res);
1217
1269
  },
1218
1270
 
1219
1271
  async setPreviewOverlay(path: string, contents: string) {
@@ -1265,12 +1317,7 @@ export function createDevServerPlatform() {
1265
1317
  headers: { "Content-Type": "application/json", "X-CF-Token": token },
1266
1318
  body: JSON.stringify({ path: apiPath, method: init?.method ?? "GET", body: init?.body }),
1267
1319
  });
1268
- const envelope = (await res.json()) as {
1269
- success?: boolean;
1270
- result?: unknown;
1271
- errors?: { message: string }[];
1272
- error?: string;
1273
- };
1320
+ const envelope = await readJson<CfProxyEnvelope>(res);
1274
1321
  if (!res.ok || envelope.success === false) {
1275
1322
  const message =
1276
1323
  envelope.errors?.map((e) => e.message).join("; ") ?? envelope.error ?? res.statusText;
@@ -1287,10 +1334,13 @@ export function createDevServerPlatform() {
1287
1334
  return null;
1288
1335
  }
1289
1336
  try {
1290
- const accounts = (await this.cfApi?.("/accounts")) as { id: string; name: string }[];
1291
- if (!accounts?.length) {
1337
+ const result = await this.cfApi("/accounts");
1338
+ if (!Array.isArray(result) || result.length === 0) {
1292
1339
  return { connected: false };
1293
1340
  }
1341
+ /* SAFETY: cfApi resolved the v4 envelope's `result`, and Cloudflare documents GET /accounts
1342
+ as answering an array of account objects, each with a string `id` and `name`. */
1343
+ const accounts = result as { id: string; name: string }[];
1294
1344
  const chosen = accounts.find((a) => a.id === getCfAccountId()) ?? accounts[0]!;
1295
1345
  setCfAccountId(chosen.id);
1296
1346
  return { connected: true, accountId: chosen.id, accountName: chosen.name };
@@ -1308,14 +1358,11 @@ export function createDevServerPlatform() {
1308
1358
  return [];
1309
1359
  }
1310
1360
  const sites = await readJson<SiteEntry[]>(res);
1311
- return sites.map((site) => {
1312
- const config = site.config as { name?: string } | null;
1313
- return {
1314
- name: config?.name || site.path.split("/").at(-1) || site.path,
1315
- root: site.path,
1316
- description: site.path,
1317
- };
1318
- });
1361
+ return sites.map((site) => ({
1362
+ name: site.config?.name || site.path.split("/").at(-1) || site.path,
1363
+ root: site.path,
1364
+ description: site.path,
1365
+ }));
1319
1366
  },
1320
1367
 
1321
1368
  // ─── AI Assistant (Stack B: OpenAI-compatible SSE proxy) ───────────────────
@@ -21,6 +21,7 @@
21
21
 
22
22
  import { READ_FILES_MAX_PATHS } from "@jxsuite/protocol";
23
23
  import type { ReadFilesResult } from "@jxsuite/protocol";
24
+ import { isNonNullObject, isString } from "@jxsuite/schema/guards";
24
25
 
25
26
  /**
26
27
  * What one batch request came back as.
@@ -57,14 +58,20 @@ export interface ReadBatcher {
57
58
 
58
59
  interface Waiter {
59
60
  resolve: (content: string) => void;
60
- reject: (error: unknown) => void;
61
+ /** Rejects with whatever the transport or `fetchOne` rejected with, unchanged. */
62
+ reject: (cause: unknown) => void;
61
63
  }
62
64
 
63
65
  /** Path → every caller waiting on it. Same-path reads in one window share ONE request and result. */
64
66
  type Pending = Map<string, Waiter[]>;
65
67
 
66
- function isObject(value: unknown): value is object {
67
- return typeof value === "object" && value !== null;
68
+ /**
69
+ * A row's outcome as the batcher reads it. Every protocol outcome fits it, but the rows come off
70
+ * the wire unchecked, so `content` may be any value and either member may be missing.
71
+ */
72
+ interface WireOutcome {
73
+ content?: unknown;
74
+ error?: { message?: string } | undefined;
68
75
  }
69
76
 
70
77
  /**
@@ -108,7 +115,7 @@ export function createReadBatcher(options: ReadBatcherOptions): ReadBatcher {
108
115
  waiter.resolve(content);
109
116
  }
110
117
  },
111
- (error: unknown) => {
118
+ (error) => {
112
119
  for (const waiter of waiters) {
113
120
  waiter.reject(error);
114
121
  }
@@ -182,7 +189,7 @@ export function createReadBatcher(options: ReadBatcherOptions): ReadBatcher {
182
189
  }
183
190
  /* Anything that is not an object cannot be a ReadFilesResult (a `null` body, a bare string).
184
191
  The route did not answer this chunk, so it is served singly — the same as a failed call. */
185
- if (answer === "retry-singly" || answer === "unsupported" || !isObject(answer)) {
192
+ if (answer === "retry-singly" || answer === "unsupported" || !isNonNullObject(answer)) {
186
193
  if (answer === "unsupported") {
187
194
  batching = false;
188
195
  }
@@ -213,14 +220,15 @@ export function createReadBatcher(options: ReadBatcherOptions): ReadBatcher {
213
220
  }
214
221
  chunk.delete(path);
215
222
  answered += 1;
216
- if (typeof (outcome as { content?: unknown } | undefined)?.content === "string") {
217
- const { content } = outcome as { content: string };
223
+ const wire: WireOutcome | undefined = outcome;
224
+ const content = wire?.content;
225
+ if (isString(content)) {
218
226
  for (const waiter of waiters) {
219
227
  waiter.resolve(content);
220
228
  }
221
229
  } else {
222
230
  // Per path: one missing file (404), one binary file (415) fails ITS readers and no others.
223
- const message = (outcome as { error?: { message?: string } } | undefined)?.error?.message;
231
+ const message = wire?.error?.message;
224
232
  const error = new Error(message || `Failed to read file: ${path}`);
225
233
  for (const waiter of waiters) {
226
234
  waiter.reject(error);
@@ -3,6 +3,7 @@
3
3
  * member (dev server sites, cloud platforms). Hydrated once at boot (studio.ts) like the
4
4
  * recent-projects cache; the welcome screen reads it synchronously.
5
5
  */
6
+ import { isFunction } from "@jxsuite/schema/guards";
6
7
  import { getPlatform, hasPlatform } from "./platform";
7
8
  import type { ProjectListEntry } from "./types";
8
9
 
@@ -10,7 +11,7 @@ let cache: ProjectListEntry[] = [];
10
11
 
11
12
  /** True when the active platform exposes a project catalogue. */
12
13
  export function platformListsProjects(): boolean {
13
- return hasPlatform() && typeof getPlatform().listProjects === "function";
14
+ return hasPlatform() && isFunction(getPlatform().listProjects);
14
15
  }
15
16
 
16
17
  /** Refresh the cache from the platform; resolves to [] when unsupported or failing. */