@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
@@ -16,7 +16,8 @@ import type { CollabNegotiation } from "@jxsuite/collab/negotiate";
16
16
  // Static, and free to be: `reconnect.ts` imports nothing, so no Yjs reaches the initial bundle.
17
17
  import { createReconnectScheduler } from "@jxsuite/collab/reconnect";
18
18
  import type { WsCollabConnection } from "@jxsuite/collab/client";
19
- import type { ProjectConfig } from "@jxsuite/schema/types";
19
+ import { isBoolean, isJsonObject, isNonNullObject, isString } from "@jxsuite/schema/guards";
20
+ import type { JsonObject, JsonValue, JxMutableNode, ProjectConfig } from "@jxsuite/schema/types";
20
21
  import { componentMetaFrom } from "@jxsuite/schema/component-meta";
21
22
  import { streamImport } from "../services/import-client";
22
23
  import type {
@@ -55,7 +56,9 @@ import type {
55
56
  import { problemDetail, problemSlug } from "@jxsuite/protocol";
56
57
  import type { ReadFilesRequest, ReadFilesResult, UploadResult } from "@jxsuite/protocol";
57
58
  import { reportActionRequired } from "../account/action-required";
59
+ import type { ActionRequiredReport } from "../account/action-required";
58
60
  import { offeredActions, retryHint } from "../platform-errors";
61
+ import type { PlatformErrorInfo } from "../platform-errors";
59
62
  import { createReadBatcher } from "./read-batcher";
60
63
  import { normalizeProjectDir } from "../utils/project-dir";
61
64
  import type { BatchAnswer } from "./read-batcher";
@@ -79,7 +82,7 @@ interface ProjectInfoWire {
79
82
  name: string;
80
83
  defaultBranch: string;
81
84
  permission: "admin" | "write" | "read" | "none";
82
- projectConfig: Record<string, unknown> | null;
85
+ projectConfig: ProjectConfig | null;
83
86
  }
84
87
 
85
88
  /** One row of the platform's project catalogue (`GET /api/v1/projects`). */
@@ -91,6 +94,64 @@ interface ProjectListWire {
91
94
  permission: string;
92
95
  }
93
96
 
97
+ /**
98
+ * True when a `POST /files/read` body is the route answering: a JSON object whose `files` is an
99
+ * array. Only that much is checked here, because the batcher checks every row, and `omitted`,
100
+ * before it trusts any of them (read-batcher.ts `settleAnswer`).
101
+ */
102
+ function isReadFilesAnswer(body: JsonValue): body is JsonObject & ReadFilesResult {
103
+ return isJsonObject(body) && Array.isArray(body["files"]);
104
+ }
105
+
106
+ /** The options object {@link StudioPlatform.formatAction} hands through to `POST /format`. */
107
+ type FormatActionPayload = Parameters<NonNullable<StudioPlatform["formatAction"]>>[0];
108
+
109
+ /**
110
+ * A Cloudflare proxy reply (`/api/v1/cf/proxy` here, `/__studio/cf/proxy` on the dev server):
111
+ * Cloudflare's v4 envelope, or the proxy's own `error`.
112
+ */
113
+ export interface CfProxyEnvelope extends JsonObject {
114
+ success?: boolean;
115
+ result?: JsonValue;
116
+ errors?: { message: string }[];
117
+ error?: string;
118
+ }
119
+
120
+ /** Where a session file sits in its project, as `resolveSiteContext` reports it. */
121
+ interface SiteContext {
122
+ sitePath: string | null;
123
+ projectConfig?: ProjectConfig;
124
+ fileRelPath?: string;
125
+ }
126
+
127
+ /** The signed-in GitHub user, as `getUser` reports them. */
128
+ interface SignedInUser {
129
+ login: string;
130
+ name?: string;
131
+ avatarUrl?: string;
132
+ }
133
+
134
+ /** Body of `POST /api/v1/projects/import`: the repository, and the project folder when not its root. */
135
+ interface ImportProjectRequest {
136
+ owner: string;
137
+ name: string;
138
+ dir?: string;
139
+ }
140
+
141
+ /**
142
+ * A manifest's `dependencies` or `devDependencies` block, `{}` when absent. The object itself, not
143
+ * a copy, so `addPackage` can write into it.
144
+ */
145
+ function dependencyBlock(
146
+ pkg: JsonObject,
147
+ key: "dependencies" | "devDependencies",
148
+ ): Record<string, string> {
149
+ /* SAFETY: npm's package.json format defines both blocks as an object of package name → version
150
+ range string, and every member here that writes one (`addPackage`, `removePackage`) keeps it
151
+ that way. */
152
+ return (pkg[key] ?? {}) as Record<string, string>;
153
+ }
154
+
94
155
  interface SessionEventWire {
95
156
  kind: "fs" | "git";
96
157
  events?: FsEvent[];
@@ -110,9 +171,24 @@ interface ErrorBody {
110
171
  code?: string;
111
172
  }
112
173
 
174
+ /** Parse a fetch Response body as JSON, asserting the expected shape at the boundary. */
175
+ async function readJson<T>(res: Response): Promise<T> {
176
+ /* SAFETY: every response read here comes from a Studio Backend Protocol session route or one of
177
+ the platform's own `/api/v1/*` routes, whose bodies have the shapes @jxsuite/protocol, ../types
178
+ and the wire interfaces in this file describe; each caller names the type of the route it
179
+ called, and a JSON parse always yields a JsonValue. */
180
+ return (await res.json()) as T;
181
+ }
182
+
183
+ /** Parse JSON text a session read returned; JSON.parse only ever produces JSON values. */
184
+ function parseJson(text: string): JsonValue {
185
+ // SAFETY: JSON.parse returns exactly the JSON value the text encodes, or throws.
186
+ return JSON.parse(text) as JsonValue;
187
+ }
188
+
113
189
  async function errorMessage(res: Response, fallback: string): Promise<string> {
114
190
  try {
115
- return problemDetail(await res.json()) ?? fallback;
191
+ return problemDetail(await readJson<JsonValue>(res)) ?? fallback;
116
192
  } catch {
117
193
  return fallback;
118
194
  }
@@ -124,9 +200,9 @@ async function errorMessage(res: Response, fallback: string): Promise<string> {
124
200
  * says the same. Any other 403 (an expired grant, a revoked share) is not that verdict.
125
201
  */
126
202
  async function isReadOnlyRefusal(res: Response): Promise<boolean> {
127
- let body: (ErrorBody & { type?: unknown }) | null;
203
+ let body: StructuredBody | null;
128
204
  try {
129
- body = (await res.json()) as (ErrorBody & { type?: unknown }) | null;
205
+ body = await readJson<StructuredBody | null>(res);
130
206
  } catch {
131
207
  return false;
132
208
  }
@@ -136,16 +212,22 @@ async function isReadOnlyRefusal(res: Response): Promise<boolean> {
136
212
 
137
213
  /** A failure body's machine-readable members, each one optional and none of them trusted. */
138
214
  interface StructuredBody extends ErrorBody {
139
- type?: unknown;
140
- installUrl?: unknown;
141
- actions?: unknown;
142
- heading?: unknown;
143
- retry?: unknown;
215
+ type?: JsonValue;
216
+ installUrl?: JsonValue;
217
+ actions?: JsonValue;
218
+ heading?: JsonValue;
219
+ retry?: JsonValue;
220
+ }
221
+
222
+ /** What {@link platformError} carries on the Error it returns: the HTTP status, plus what it found. */
223
+ interface PlatformErrorDetails extends PlatformErrorInfo {
224
+ status: number;
144
225
  }
145
226
 
146
227
  /** The refusal's heading, when the body carries a usable one. */
147
228
  function headingOf(body: StructuredBody | null): string | undefined {
148
- return typeof body?.heading === "string" && body.heading ? body.heading : undefined;
229
+ const heading = body?.heading;
230
+ return isString(heading) && heading ? heading : undefined;
149
231
  }
150
232
 
151
233
  /**
@@ -161,7 +243,7 @@ function headingOf(body: StructuredBody | null): string | undefined {
161
243
  async function platformError(res: Response, fallback: string): Promise<Error> {
162
244
  let body: StructuredBody | null;
163
245
  try {
164
- body = (await res.json()) as StructuredBody | null;
246
+ body = await readJson<StructuredBody | null>(res);
165
247
  } catch {
166
248
  body = null;
167
249
  }
@@ -169,21 +251,31 @@ async function platformError(res: Response, fallback: string): Promise<Error> {
169
251
  const actions = offeredActions(body?.actions);
170
252
  const heading = headingOf(body);
171
253
  const retry = retryHint(body?.retry);
172
- return Object.assign(new Error(problemDetail(body) ?? fallback), {
173
- status: res.status,
174
- ...(code ? { code } : {}),
175
- ...(typeof body?.installUrl === "string" ? { installUrl: body.installUrl } : {}),
176
- ...(actions.length > 0 ? { actions } : {}),
177
- ...(heading === undefined ? {} : { heading }),
178
- ...(retry === undefined ? {} : { retry }),
179
- });
254
+ const installUrl = body?.installUrl;
255
+ const details: PlatformErrorDetails = { status: res.status };
256
+ if (code) {
257
+ details.code = code;
258
+ }
259
+ if (isString(installUrl)) {
260
+ details.installUrl = installUrl;
261
+ }
262
+ if (actions.length > 0) {
263
+ details.actions = actions;
264
+ }
265
+ if (heading !== undefined) {
266
+ details.heading = heading;
267
+ }
268
+ if (retry !== undefined) {
269
+ details.retry = retry;
270
+ }
271
+ return Object.assign(new Error(problemDetail(body) ?? fallback), details);
180
272
  }
181
273
 
182
274
  async function okJson<T>(res: Response, fallback: string): Promise<T> {
183
275
  if (!res.ok) {
184
276
  throw await platformError(res, fallback);
185
277
  }
186
- return (await res.json()) as T;
278
+ return readJson<T>(res);
187
279
  }
188
280
 
189
281
  /** Whether a failure body is an `action-required` refusal, by problem type or legacy code. */
@@ -201,7 +293,7 @@ function isActionRequiredBody(body: StructuredBody | null): boolean {
201
293
  async function reportRefusal(res: Response): Promise<void> {
202
294
  let body: StructuredBody | null;
203
295
  try {
204
- body = (await res.json()) as StructuredBody | null;
296
+ body = await readJson<StructuredBody | null>(res);
205
297
  } catch {
206
298
  return;
207
299
  }
@@ -215,12 +307,17 @@ async function reportRefusal(res: Response): Promise<void> {
215
307
  const detail = problemDetail(body);
216
308
  const heading = headingOf(body);
217
309
  const retry = retryHint(body?.retry);
218
- reportActionRequired({
219
- actions,
220
- ...(detail === null ? {} : { detail }),
221
- ...(heading === undefined ? {} : { heading }),
222
- ...(retry === undefined ? {} : { retry }),
223
- });
310
+ const report: ActionRequiredReport = { actions };
311
+ if (detail !== null) {
312
+ report.detail = detail;
313
+ }
314
+ if (heading !== undefined) {
315
+ report.heading = heading;
316
+ }
317
+ if (retry !== undefined) {
318
+ report.retry = retry;
319
+ }
320
+ reportActionRequired(report);
224
321
  }
225
322
 
226
323
  /**
@@ -298,6 +395,34 @@ export function projectRootKey(project: CloudProject): string {
298
395
  return `${project.owner}/${project.repo}@${revisionSpec(project)}`;
299
396
  }
300
397
 
398
+ /** `GET /api/v1/cf/connection`: the broker's row, with every diagnostic it may send. */
399
+ interface CfConnectionWire {
400
+ connected: boolean;
401
+ accountId?: string | null;
402
+ accountName?: string | null;
403
+ needsReconnect?: boolean;
404
+ needsAccount?: boolean;
405
+ code?: CfConnection["code"];
406
+ reason?: string;
407
+ hasRefreshToken?: boolean;
408
+ expiresAt?: number;
409
+ }
410
+
411
+ /**
412
+ * A same-origin window relay (`{source, status, reason?}`) that a broker or action window posts
413
+ * back. Only `source` is checked; `status` and `reason` are read as the relay protocol sends them.
414
+ */
415
+ interface WindowRelay {
416
+ source?: string;
417
+ status?: string;
418
+ reason?: string | null;
419
+ }
420
+
421
+ /** True when a `message` event carries a relay from `source` (`"jx-cf"`, `"jx-action"`). */
422
+ function isRelayEvent(event: MessageEvent, source: string): event is MessageEvent<WindowRelay> {
423
+ return isNonNullObject(event.data) && "source" in event.data && event.data.source === source;
424
+ }
425
+
301
426
  /**
302
427
  * Current hosted Cloudflare connection; null when none is brokered yet.
303
428
  *
@@ -312,33 +437,38 @@ async function fetchCfConnection(): Promise<CfConnection | null> {
312
437
  if (!res.ok) {
313
438
  return null;
314
439
  }
315
- const body = (await res.json()) as {
316
- connected: boolean;
317
- accountId?: string | null;
318
- accountName?: string | null;
319
- needsReconnect?: boolean;
320
- needsAccount?: boolean;
321
- code?: CfConnection["code"];
322
- reason?: string;
323
- hasRefreshToken?: boolean;
324
- expiresAt?: number;
325
- };
440
+ const body = await readJson<CfConnectionWire>(res);
326
441
  /* Cloud never emits {connected: false} to the PAL — no brokered row at all is `null`, and a row
327
442
  that cannot be used arrives as connected + needsReconnect. See CfConnection. */
328
443
  if (!body.connected) {
329
444
  return null;
330
445
  }
331
- return {
332
- connected: true,
333
- ...(body.accountId ? { accountId: body.accountId } : {}),
334
- ...(body.accountName ? { accountName: body.accountName } : {}),
335
- ...(body.needsReconnect ? { needsReconnect: true } : {}),
336
- ...(body.needsAccount ? { needsAccount: true } : {}),
337
- ...(body.code ? { code: body.code } : {}),
338
- ...(body.reason ? { reason: body.reason } : {}),
339
- ...(body.hasRefreshToken === undefined ? {} : { hasRefreshToken: body.hasRefreshToken }),
340
- ...(body.expiresAt === undefined ? {} : { expiresAt: body.expiresAt }),
341
- };
446
+ const connection: CfConnection = { connected: true };
447
+ if (body.accountId) {
448
+ connection.accountId = body.accountId;
449
+ }
450
+ if (body.accountName) {
451
+ connection.accountName = body.accountName;
452
+ }
453
+ if (body.needsReconnect) {
454
+ connection.needsReconnect = true;
455
+ }
456
+ if (body.needsAccount) {
457
+ connection.needsAccount = true;
458
+ }
459
+ if (body.code) {
460
+ connection.code = body.code;
461
+ }
462
+ if (body.reason) {
463
+ connection.reason = body.reason;
464
+ }
465
+ if (body.hasRefreshToken !== undefined) {
466
+ connection.hasRefreshToken = body.hasRefreshToken;
467
+ }
468
+ if (body.expiresAt !== undefined) {
469
+ connection.expiresAt = body.expiresAt;
470
+ }
471
+ return connection;
342
472
  }
343
473
 
344
474
  interface CfConnectFlow {
@@ -403,10 +533,10 @@ async function runCfConnect(
403
533
  if (event.origin !== location.origin) {
404
534
  return;
405
535
  }
406
- const data = event.data as { source?: string; status?: string; reason?: string | null };
407
- if (!data || data.source !== "jx-cf") {
536
+ if (!isRelayEvent(event, "jx-cf")) {
408
537
  return;
409
538
  }
539
+ const { data } = event;
410
540
  if (data.status === "error") {
411
541
  fail(data.reason ?? "Cloudflare authorization failed");
412
542
  return;
@@ -467,68 +597,76 @@ async function runCfConnect(
467
597
  interface MeWire {
468
598
  installations?: { id: number; account: string | null; manageUrl?: string }[];
469
599
  appInstallUrl?: string;
470
- entries?: unknown;
471
- notices?: unknown;
600
+ entries?: JsonValue;
601
+ notices?: JsonValue;
472
602
  }
473
603
 
474
604
  /** A wire value as a non-empty string, or undefined. */
475
- function wireText(value: unknown): string | undefined {
476
- return typeof value === "string" && value ? value : undefined;
605
+ function wireText(value: JsonValue | undefined): string | undefined {
606
+ return isString(value) && value ? value : undefined;
477
607
  }
478
608
 
479
609
  /**
480
610
  * The account rows a `/me` body holds, each one checked. A row needs an id, a label and a detail to
481
611
  * be drawn at all; a malformed one is dropped rather than drawn half-empty.
482
612
  */
483
- function accountEntries(value: unknown): AccountEntry[] {
613
+ function accountEntries(value: JsonValue | undefined): AccountEntry[] {
484
614
  if (!Array.isArray(value)) {
485
615
  return [];
486
616
  }
487
- return value.flatMap((raw: Record<string, unknown> | null): AccountEntry[] => {
488
- const id = wireText(raw?.["id"]);
489
- const label = wireText(raw?.["label"]);
490
- const detail = wireText(raw?.["detail"]);
617
+ return value.flatMap((raw): AccountEntry[] => {
618
+ // A row that is not an object has no id, label or detail to draw.
619
+ if (!isJsonObject(raw)) {
620
+ return [];
621
+ }
622
+ const id = wireText(raw["id"]);
623
+ const label = wireText(raw["label"]);
624
+ const detail = wireText(raw["detail"]);
491
625
  if (!id || !label || !detail) {
492
626
  return [];
493
627
  }
494
- const actions = offeredActions(raw?.["actions"]);
495
- return [
496
- {
497
- detail,
498
- id,
499
- label,
500
- ...(typeof raw?.["connected"] === "boolean" ? { connected: raw["connected"] } : {}),
501
- ...(actions.length > 0 ? { actions } : {}),
502
- },
503
- ];
628
+ const actions = offeredActions(raw["actions"]);
629
+ const { connected } = raw;
630
+ const entry: AccountEntry = { detail, id, label };
631
+ if (isBoolean(connected)) {
632
+ entry.connected = connected;
633
+ }
634
+ if (actions.length > 0) {
635
+ entry.actions = actions;
636
+ }
637
+ return [entry];
504
638
  });
505
639
  }
506
640
 
507
641
  /** The notices a `/me` body holds, each one checked, on the same terms as the rows. */
508
- function accountNotices(value: unknown): AccountNotice[] {
642
+ function accountNotices(value: JsonValue | undefined): AccountNotice[] {
509
643
  if (!Array.isArray(value)) {
510
644
  return [];
511
645
  }
512
- return value.flatMap((raw: Record<string, unknown> | null): AccountNotice[] => {
513
- const id = wireText(raw?.["id"]);
514
- const title = wireText(raw?.["title"]);
515
- const message = wireText(raw?.["message"]);
646
+ return value.flatMap((raw): AccountNotice[] => {
647
+ // A notice that is not an object has no id, title or message to show.
648
+ if (!isJsonObject(raw)) {
649
+ return [];
650
+ }
651
+ const id = wireText(raw["id"]);
652
+ const title = wireText(raw["title"]);
653
+ const message = wireText(raw["message"]);
516
654
  if (!id || !title || !message) {
517
655
  return [];
518
656
  }
519
- const actions = offeredActions(raw?.["actions"]);
520
- const level = raw?.["level"];
521
- const display = raw?.["display"];
522
- return [
523
- {
524
- id,
525
- message,
526
- title,
527
- ...(level === "info" || level === "warning" ? { level } : {}),
528
- ...(display === "banner" || display === "toast" ? { display } : {}),
529
- ...(actions.length > 0 ? { actions } : {}),
530
- },
531
- ];
657
+ const actions = offeredActions(raw["actions"]);
658
+ const { display, level } = raw;
659
+ const notice: AccountNotice = { id, message, title };
660
+ if (level === "info" || level === "warning") {
661
+ notice.level = level;
662
+ }
663
+ if (display === "banner" || display === "toast") {
664
+ notice.display = display;
665
+ }
666
+ if (actions.length > 0) {
667
+ notice.actions = actions;
668
+ }
669
+ return [notice];
532
670
  });
533
671
  }
534
672
 
@@ -578,10 +716,10 @@ function awaitAction(popup: Window): Promise<ActionOutcome> {
578
716
  if (event.origin !== location.origin) {
579
717
  return;
580
718
  }
581
- const data = event.data as { source?: string; status?: string; reason?: string | null };
582
- if (!data || data.source !== "jx-action") {
719
+ if (!isRelayEvent(event, "jx-action")) {
583
720
  return;
584
721
  }
722
+ const { data } = event;
585
723
  if (data.status === "done" || data.status === "canceled") {
586
724
  settle({ status: data.status });
587
725
  } else if (data.status === "error") {
@@ -624,7 +762,7 @@ export function parseRootKey(root: string): CloudProject | null {
624
762
  /** Every project this account can open, straight off the wire; [] when the catalogue is unreachable. */
625
763
  async function fetchProjects(): Promise<ProjectListWire[]> {
626
764
  const res = await fetch("/api/v1/projects", { credentials: "include" });
627
- return res.ok ? ((await res.json()) as ProjectListWire[]) : [];
765
+ return res.ok ? readJson<ProjectListWire[]>(res) : [];
628
766
  }
629
767
 
630
768
  /**
@@ -705,7 +843,7 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
705
843
  */
706
844
  const projectHint = project ? `${project.owner}/${project.repo}` : null;
707
845
 
708
- function postJson(path: string, body: unknown): Promise<Response> {
846
+ function postJson(path: string, body: JsonValue | FormatActionPayload): Promise<Response> {
709
847
  return api(path, {
710
848
  method: "POST",
711
849
  headers: { "Content-Type": "application/json" },
@@ -752,16 +890,16 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
752
890
  if (!res.ok) {
753
891
  return "retry-singly";
754
892
  }
755
- let body: unknown;
893
+ let body: JsonValue;
756
894
  try {
757
- body = await res.json();
895
+ body = await readJson<JsonValue>(res);
758
896
  } catch {
759
897
  return "unsupported";
760
898
  }
761
- if (!body || typeof body !== "object" || !Array.isArray((body as ReadFilesResult).files)) {
899
+ if (!isReadFilesAnswer(body)) {
762
900
  return "unsupported";
763
901
  }
764
- return body as ReadFilesResult;
902
+ return body;
765
903
  },
766
904
  fetchOne: readOne,
767
905
  });
@@ -774,7 +912,7 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
774
912
  */
775
913
  let packageJsonRead: Promise<string> | null = null;
776
914
 
777
- async function readPackageJson(): Promise<Record<string, unknown>> {
915
+ async function readPackageJson(): Promise<JsonObject> {
778
916
  let text: string;
779
917
  try {
780
918
  packageJsonRead ??= reader.read("package.json").finally(() => {
@@ -786,13 +924,15 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
786
924
  }
787
925
  // Parsed per caller: the members mutate what they get, and must not mutate each other's copy.
788
926
  try {
789
- return JSON.parse(text) as Record<string, unknown>;
927
+ // A manifest that is not a JSON object is as unusable as one that is not JSON at all.
928
+ const pkg = parseJson(text);
929
+ return isJsonObject(pkg) ? pkg : {};
790
930
  } catch {
791
931
  return {};
792
932
  }
793
933
  }
794
934
 
795
- async function writePackageJson(pkg: Record<string, unknown>): Promise<void> {
935
+ async function writePackageJson(pkg: JsonObject): Promise<void> {
796
936
  const res = await api("/file", {
797
937
  method: "PUT",
798
938
  headers: { "Content-Type": "application/json" },
@@ -803,6 +943,13 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
803
943
  }
804
944
  }
805
945
 
946
+ // The bound session's /raw tree; see `documentBaseUrl` below for why both members ride together.
947
+ const repoAssets: Pick<StudioPlatform, "assetSpace" | "documentBaseUrl"> = {};
948
+ if (base) {
949
+ repoAssets.assetSpace = "repo";
950
+ repoAssets.documentBaseUrl = `${base}/raw/`;
951
+ }
952
+
806
953
  const platform: StudioPlatform = {
807
954
  id: "cloud",
808
955
  projectRoot: root,
@@ -822,7 +969,7 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
822
969
  broken, and logs nothing. Declaring "repo" tells Studio to resolve each reference to the
823
970
  project file it names and address it under /raw. Until a project is bound there is no /raw to
824
971
  address, so the project-less hub declares neither and stays exactly as it was. */
825
- ...(base ? { assetSpace: "repo" as const, documentBaseUrl: `${base}/raw/` } : {}),
972
+ ...repoAssets,
826
973
  /* Open Project routes through Studio's repo picker (write-access repos via listRepos +
827
974
  importProject) — sessions are URL-bound, so openProject() below never opens a dialog. */
828
975
  openProjectPicker: "repo-list",
@@ -851,7 +998,7 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
851
998
  return null;
852
999
  }
853
1000
  const info = await projectInfo();
854
- const config = (info.projectConfig ?? {}) as ProjectConfig;
1001
+ const config: ProjectConfig = info.projectConfig ?? {};
855
1002
  return {
856
1003
  config,
857
1004
  handle: { root, name: config.name || info.name, projectConfig: config },
@@ -868,7 +1015,7 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
868
1015
  meta: { root, name: info.name },
869
1016
  info: {
870
1017
  isSiteProject: info.projectConfig !== null,
871
- projectConfig: (info.projectConfig as ProjectConfig | null) ?? null,
1018
+ projectConfig: info.projectConfig ?? null,
872
1019
  },
873
1020
  };
874
1021
  } catch {
@@ -920,7 +1067,11 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
920
1067
  const body = await okJson<Partial<UploadResult>>(res, `Upload failed: ${path}`);
921
1068
  // The session echoes the path today; falling back to the request keeps a backend that says
922
1069
  // Nothing working rather than writing `undefined` into a document.
923
- return { path: body.path ?? path, ...(body.size === undefined ? {} : { size: body.size }) };
1070
+ const uploaded: UploadResult = { path: body.path ?? path };
1071
+ if (body.size !== undefined) {
1072
+ uploaded.size = body.size;
1073
+ }
1074
+ return uploaded;
924
1075
  },
925
1076
 
926
1077
  async deleteFile(path: string) {
@@ -979,7 +1130,7 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
979
1130
  }
980
1131
  collabNegotiation ??= api("/collab")
981
1132
  .then(async (res) =>
982
- res.ok ? negotiateCollab(await res.json()) : { offer: [], refused: null },
1133
+ res.ok ? negotiateCollab(await readJson<JsonValue>(res)) : { offer: [], refused: null },
983
1134
  )
984
1135
  .catch(() => ({ offer: [], refused: null }));
985
1136
  const negotiated = await collabNegotiation;
@@ -1040,10 +1191,12 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
1040
1191
  const scheme = location.protocol === "https:" ? "wss" : "ws";
1041
1192
  const ws = new WebSocket(`${scheme}://${location.host}${base}/events`);
1042
1193
  socket = ws;
1043
- ws.addEventListener("message", (ev: MessageEvent) => {
1194
+ ws.addEventListener("message", (ev: MessageEvent<string>) => {
1044
1195
  let payload: SessionEventWire;
1045
1196
  try {
1046
- payload = JSON.parse(ev.data as string) as SessionEventWire;
1197
+ /* SAFETY: the session's /events socket sends only text frames, each one a JSON
1198
+ SessionEventWire (`kind` "fs" with its events, or "git" with the git event). */
1199
+ payload = JSON.parse(ev.data) as SessionEventWire;
1047
1200
  } catch {
1048
1201
  return;
1049
1202
  }
@@ -1086,7 +1239,7 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
1086
1239
  if (!res.ok) {
1087
1240
  return [];
1088
1241
  }
1089
- const body = (await res.json()) as { formats?: Record<string, unknown>[] };
1242
+ const body = await readJson<{ formats?: JsonObject[] }>(res);
1090
1243
  return body.formats ?? [];
1091
1244
  } catch {
1092
1245
  return [];
@@ -1100,7 +1253,7 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
1100
1253
  if (!res.ok) {
1101
1254
  return [];
1102
1255
  }
1103
- const body = (await res.json()) as { extensions?: ExtensionsInfo[] };
1256
+ const body = await readJson<{ extensions?: ExtensionsInfo[] }>(res);
1104
1257
  return body.extensions ?? [];
1105
1258
  } catch {
1106
1259
  return [];
@@ -1130,11 +1283,11 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
1130
1283
  if (!res.ok) {
1131
1284
  return [];
1132
1285
  }
1133
- const entries = (await res.json()) as ExtensionCatalogEntry[];
1286
+ const entries = await readJson<ExtensionCatalogEntry[]>(res);
1134
1287
  const pkg = await readPackageJson();
1135
1288
  const declared = new Set([
1136
- ...Object.keys((pkg["dependencies"] ?? {}) as Record<string, string>),
1137
- ...Object.keys((pkg["devDependencies"] ?? {}) as Record<string, string>),
1289
+ ...Object.keys(dependencyBlock(pkg, "dependencies")),
1290
+ ...Object.keys(dependencyBlock(pkg, "devDependencies")),
1138
1291
  ]);
1139
1292
  for (const entry of entries) {
1140
1293
  entry.bundled = true;
@@ -1161,7 +1314,7 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
1161
1314
  if (!res.ok) {
1162
1315
  return {};
1163
1316
  }
1164
- return (await res.json()) as ProjectSchemasResponse;
1317
+ return await readJson<ProjectSchemasResponse>(res);
1165
1318
  } catch {
1166
1319
  return {};
1167
1320
  }
@@ -1173,7 +1326,7 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
1173
1326
  * backends without the route surface the error below instead of silently mangling format
1174
1327
  * documents.
1175
1328
  */
1176
- async formatAction(payload: Record<string, unknown>) {
1329
+ async formatAction(payload) {
1177
1330
  const res = await postJson("/format", payload);
1178
1331
  if (!res.ok) {
1179
1332
  throw new Error(
@@ -1183,7 +1336,8 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
1183
1336
  ),
1184
1337
  );
1185
1338
  }
1186
- const data = (await res.json()) as { result?: unknown };
1339
+ // A successful answer is `{ result }`: the parsed document, or the serialized text.
1340
+ const data = await readJson<{ result: JxMutableNode | string }>(res);
1187
1341
  return data.result;
1188
1342
  },
1189
1343
 
@@ -1242,12 +1396,15 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
1242
1396
  const metas = await Promise.all(
1243
1397
  jsonPaths.map(async (path) => {
1244
1398
  try {
1245
- return componentMetaFrom(JSON.parse(await platform.readFile(path)), path);
1399
+ return componentMetaFrom(parseJson(await platform.readFile(path)), path);
1246
1400
  } catch {
1247
1401
  return null; // Not JSON, not readable, or not a component — all the same answer here.
1248
1402
  }
1249
1403
  }),
1250
1404
  );
1405
+ /* SAFETY: the two Bun backends answer discoverComponents with this same componentMetaFrom
1406
+ output, so ComponentMeta's narrower prop typing describes exactly what Studio already
1407
+ receives for these documents everywhere else. */
1251
1408
  return metas.filter((m): m is NonNullable<typeof m> => m !== null) as ComponentMeta[];
1252
1409
  },
1253
1410
 
@@ -1263,36 +1420,34 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
1263
1420
 
1264
1421
  async addPackage(name: string) {
1265
1422
  const pkg = await readPackageJson();
1266
- const deps = (pkg["dependencies"] ?? {}) as Record<string, string>;
1423
+ const deps = dependencyBlock(pkg, "dependencies");
1267
1424
  const at = name.lastIndexOf("@");
1268
1425
  const [pkgName, version] =
1269
1426
  at > 0 ? [name.slice(0, at), name.slice(at + 1)] : [name, "latest"];
1270
1427
  deps[pkgName] = version;
1271
1428
  pkg["dependencies"] = deps;
1272
1429
  await writePackageJson(pkg);
1273
- return { ok: true, name: pkgName, version };
1274
1430
  },
1275
1431
 
1276
1432
  async removePackage(name: string) {
1277
1433
  const pkg = await readPackageJson();
1278
1434
  for (const key of ["dependencies", "devDependencies"]) {
1279
- const deps = pkg[key] as Record<string, string> | undefined;
1280
- if (deps && name in deps) {
1435
+ const deps = pkg[key];
1436
+ if (isJsonObject(deps) && name in deps) {
1281
1437
  pkg[key] = Object.fromEntries(Object.entries(deps).filter(([dep]) => dep !== name));
1282
1438
  }
1283
1439
  }
1284
1440
  await writePackageJson(pkg);
1285
- return { ok: true, name };
1286
1441
  },
1287
1442
 
1288
1443
  async listPackages(): Promise<PackageInfo[]> {
1289
1444
  const pkg = await readPackageJson();
1290
1445
  const out: PackageInfo[] = [];
1291
- const deps = (pkg["dependencies"] ?? {}) as Record<string, string>;
1446
+ const deps = dependencyBlock(pkg, "dependencies");
1292
1447
  for (const [name, version] of Object.entries(deps)) {
1293
1448
  out.push({ name, version });
1294
1449
  }
1295
- const devDeps = (pkg["devDependencies"] ?? {}) as Record<string, string>;
1450
+ const devDeps = dependencyBlock(pkg, "devDependencies");
1296
1451
  for (const [name, version] of Object.entries(devDeps)) {
1297
1452
  out.push({ name, version, dev: true });
1298
1453
  }
@@ -1304,12 +1459,15 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
1304
1459
  async resolveSiteContext(filePath: string) {
1305
1460
  try {
1306
1461
  const info = await projectInfo();
1307
- const config = (info.projectConfig as ProjectConfig | null) ?? undefined;
1308
- return {
1309
- sitePath: root,
1310
- ...(config === undefined ? {} : { projectConfig: config }),
1311
- ...(filePath === root ? {} : { fileRelPath: filePath }),
1312
- };
1462
+ const config = info.projectConfig ?? undefined;
1463
+ const context: SiteContext = { sitePath: root };
1464
+ if (config !== undefined) {
1465
+ context.projectConfig = config;
1466
+ }
1467
+ if (filePath !== root) {
1468
+ context.fileRelPath = filePath;
1469
+ }
1470
+ return context;
1313
1471
  } catch {
1314
1472
  return { sitePath: null };
1315
1473
  }
@@ -1319,7 +1477,7 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
1319
1477
  try {
1320
1478
  const res = await api(`/locate?name=${encodeURIComponent(name)}`);
1321
1479
  if (res.ok) {
1322
- const body = (await res.json()) as { path?: string | null };
1480
+ const body = await readJson<{ path?: string | null }>(res);
1323
1481
  return body.path ?? null;
1324
1482
  }
1325
1483
  } catch {
@@ -1336,7 +1494,7 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
1336
1494
  if (!res.ok) {
1337
1495
  return [];
1338
1496
  }
1339
- return (await res.json()) as DirEntry[];
1497
+ return await readJson<DirEntry[]>(res);
1340
1498
  },
1341
1499
 
1342
1500
  // ─── Git (virtual engine in the ProjectSession DO) ─────────────────────
@@ -1458,7 +1616,7 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
1458
1616
  if (!res.ok) {
1459
1617
  return [];
1460
1618
  }
1461
- return (await res.json()) as StarterInfo[];
1619
+ return await readJson<StarterInfo[]>(res);
1462
1620
  },
1463
1621
 
1464
1622
  /** Welcome/recents open path: navigate this tab into the project's editor. */
@@ -1531,14 +1689,14 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
1531
1689
  missing App installation, the offered actions of an `action-required` refusal. */
1532
1690
  throw await platformError(res, "Failed to create project");
1533
1691
  }
1534
- const created = (await res.json()) as { owner: string; name: string; defaultBranch: string };
1692
+ const created = await readJson<{ owner: string; name: string; defaultBranch: string }>(res);
1535
1693
  return {
1536
1694
  root: projectRootKey({
1537
1695
  owner: created.owner,
1538
1696
  repo: created.name,
1539
1697
  branch: created.defaultBranch,
1540
1698
  }),
1541
- config: { name: opts.name } as ProjectConfig,
1699
+ config: { name: opts.name } satisfies ProjectConfig,
1542
1700
  };
1543
1701
  },
1544
1702
 
@@ -1576,17 +1734,20 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
1576
1734
  if (!res.ok) {
1577
1735
  return null;
1578
1736
  }
1579
- const me = (await res.json()) as {
1737
+ const me = await readJson<{
1580
1738
  user: { login: string; name: string | null; avatar_url: string | null } | null;
1581
- };
1739
+ }>(res);
1582
1740
  if (!me.user) {
1583
1741
  return null;
1584
1742
  }
1585
- return {
1586
- login: me.user.login,
1587
- ...(me.user.name ? { name: me.user.name } : {}),
1588
- ...(me.user.avatar_url ? { avatarUrl: me.user.avatar_url } : {}),
1589
- };
1743
+ const user: SignedInUser = { login: me.user.login };
1744
+ if (me.user.name) {
1745
+ user.name = me.user.name;
1746
+ }
1747
+ if (me.user.avatar_url) {
1748
+ user.avatarUrl = me.user.avatar_url;
1749
+ }
1750
+ return user;
1590
1751
  },
1591
1752
 
1592
1753
  /** Repositories reachable through the user's Jx Suite App installations (user + org). */
@@ -1595,7 +1756,7 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
1595
1756
  if (!res.ok) {
1596
1757
  throw new Error(await errorMessage(res, "Failed to list repositories"));
1597
1758
  }
1598
- const entries = (await res.json()) as (RepoInfo & { repoId: number })[];
1759
+ const entries = await readJson<(RepoInfo & { repoId: number })[]>(res);
1599
1760
  return entries.map((r) => ({
1600
1761
  owner: r.owner,
1601
1762
  name: r.name,
@@ -1636,24 +1797,29 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
1636
1797
  if (dir === null) {
1637
1798
  throw new Error("The project folder must stay inside the repository.");
1638
1799
  }
1800
+ const importRequest: ImportProjectRequest = { owner: opts.owner, name: opts.name };
1801
+ if (dir) {
1802
+ importRequest.dir = dir;
1803
+ }
1639
1804
  const res = await fetch("/api/v1/projects/import", {
1640
1805
  method: "POST",
1641
1806
  credentials: "include",
1642
1807
  headers: { "Content-Type": "application/json" },
1643
- body: JSON.stringify({ owner: opts.owner, name: opts.name, ...(dir ? { dir } : {}) }),
1808
+ body: JSON.stringify(importRequest),
1644
1809
  });
1645
1810
  const imported = await okJson<{ owner: string; name: string; defaultBranch: string }>(
1646
1811
  res,
1647
1812
  "Failed to import repository",
1648
1813
  );
1649
- return {
1650
- root: projectRootKey({
1651
- owner: imported.owner,
1652
- repo: imported.name,
1653
- branch: imported.defaultBranch,
1654
- ...(dir ? { dir } : {}),
1655
- }),
1814
+ const importedProject: CloudProject = {
1815
+ owner: imported.owner,
1816
+ repo: imported.name,
1817
+ branch: imported.defaultBranch,
1656
1818
  };
1819
+ if (dir) {
1820
+ importedProject.dir = dir;
1821
+ }
1822
+ return { root: projectRootKey(importedProject) };
1657
1823
  },
1658
1824
 
1659
1825
  /**
@@ -1667,10 +1833,10 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
1667
1833
  if (!res.ok) {
1668
1834
  return null;
1669
1835
  }
1670
- const me = (await res.json()) as MeWire;
1836
+ const me = await readJson<MeWire>(res);
1671
1837
  const entries = accountEntries(me.entries);
1672
1838
  const notices = accountNotices(me.notices);
1673
- return {
1839
+ const status: AccountStatus = {
1674
1840
  installations: (me.installations ?? []).map((entry) => {
1675
1841
  const installation: AccountStatus["installations"][number] = {
1676
1842
  id: entry.id,
@@ -1681,10 +1847,17 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
1681
1847
  }
1682
1848
  return installation;
1683
1849
  }),
1684
- ...(me.appInstallUrl ? { appInstallUrl: me.appInstallUrl } : {}),
1685
- ...(entries.length > 0 ? { entries } : {}),
1686
- ...(notices.length > 0 ? { notices } : {}),
1687
1850
  };
1851
+ if (me.appInstallUrl) {
1852
+ status.appInstallUrl = me.appInstallUrl;
1853
+ }
1854
+ if (entries.length > 0) {
1855
+ status.entries = entries;
1856
+ }
1857
+ if (notices.length > 0) {
1858
+ status.notices = notices;
1859
+ }
1860
+ return status;
1688
1861
  },
1689
1862
 
1690
1863
  /**
@@ -1823,34 +1996,31 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
1823
1996
  async cfApi(apiPath: string, init?: { method?: string; body?: unknown }) {
1824
1997
  /* The project rides in a HEADER, never the query: the platform forwards this path's query
1825
1998
  string to Cloudflare verbatim. */
1826
- const headers: Record<string, string> = {
1827
- ...(projectHint ? { "X-Jx-Project": projectHint } : {}),
1828
- ...(init?.body === undefined ? {} : { "Content-Type": "application/json" }),
1829
- };
1830
- const res = await fetch(`/api/v1/cf/proxy${apiPath}`, {
1831
- method: init?.method ?? "GET",
1832
- credentials: "include",
1833
- ...(Object.keys(headers).length === 0 ? {} : { headers }),
1834
- ...(init?.body === undefined ? {} : { body: JSON.stringify(init.body) }),
1835
- });
1999
+ const headers: Record<string, string> = {};
2000
+ if (projectHint) {
2001
+ headers["X-Jx-Project"] = projectHint;
2002
+ }
2003
+ if (init?.body !== undefined) {
2004
+ headers["Content-Type"] = "application/json";
2005
+ }
2006
+ const request: RequestInit = { method: init?.method ?? "GET", credentials: "include" };
2007
+ if (Object.keys(headers).length > 0) {
2008
+ request.headers = headers;
2009
+ }
2010
+ if (init?.body !== undefined) {
2011
+ request.body = JSON.stringify(init.body);
2012
+ }
2013
+ const res = await fetch(`/api/v1/cf/proxy${apiPath}`, request);
1836
2014
  /* An `action-required` refusal is the platform's answer, not Cloudflare's, so it is not
1837
2015
  dressed as one: it keeps its own sentence and the actions the publish flow offers. Read
1838
2016
  from a clone, because every other 403 is Cloudflare's own and is parsed below. */
1839
2017
  if (res.status === 403) {
1840
- const refusal = (await res
1841
- .clone()
1842
- .json()
1843
- .catch(() => null)) as StructuredBody | null;
2018
+ const refusal = await readJson<StructuredBody | null>(res.clone()).catch(() => null);
1844
2019
  if (isActionRequiredBody(refusal)) {
1845
2020
  throw await platformError(res, "Publishing was refused");
1846
2021
  }
1847
2022
  }
1848
- const envelope = (await res.json()) as {
1849
- success?: boolean;
1850
- result?: unknown;
1851
- errors?: { message: string }[];
1852
- error?: string;
1853
- };
2023
+ const envelope = await readJson<CfProxyEnvelope>(res);
1854
2024
  if (!res.ok || envelope.success === false) {
1855
2025
  const message =
1856
2026
  envelope.errors?.map((e) => e.message).join("; ") ?? envelope.error ?? res.statusText;