@jxsuite/studio 5.2.0 → 5.4.0

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 (398) hide show
  1. package/data/css-meta.json +28 -27
  2. package/dist/chunks/{cf-account-picker-h4dab9bq.js → cf-account-picker-3f08rry6.js} +21 -18
  3. package/dist/chunks/{cf-settings-adg74qsk.js → cf-settings-m79r7p5y.js} +4 -4
  4. package/dist/chunks/{components-5y78e05d.js → components-xm826zjw.js} +3 -3
  5. package/dist/chunks/{content-source-sgg1sv6n.js → content-source-jt7rbcbc.js} +22 -22
  6. package/dist/chunks/{convert-to-repeater-mf1cxgfn.js → convert-to-repeater-vg38bq99.js} +22 -22
  7. package/dist/chunks/{editor-3qhbg51x.js → editor-ddy019sn.js} +2 -2
  8. package/dist/chunks/entry-commands-64nb2a9a.js +42 -0
  9. package/dist/chunks/{extension-sections-c6zwzbre.js → extension-sections-r73an5s6.js} +31 -30
  10. package/dist/chunks/{file-ops-vrpq1jjk.js → file-ops-3xan8mnj.js} +21 -21
  11. package/dist/chunks/{files-msaf95y6.js → files-egsb0ry8.js} +30 -29
  12. package/dist/chunks/{format-host-f64fnesp.js → format-host-mym8qnd0.js} +3 -3
  13. package/dist/chunks/{fs-events-3y6y0nrm.js → fs-events-z2gg8qjt.js} +4 -4
  14. package/dist/chunks/{git-panel-bv5e22j0.js → git-panel-a05atk6q.js} +20 -20
  15. package/dist/chunks/{grid-view-1p6hsyjx.css → grid-view-3g1h2re8.css} +956 -139
  16. package/dist/chunks/{grid-view-paj67nr5.js → grid-view-qaqer9kr.js} +24 -24
  17. package/dist/chunks/{icons-yy10wk9r.js → icons-drxq9q10.js} +8 -2
  18. package/dist/chunks/{iframe-host-dh5np8vk.js → iframe-host-psetcv20.js} +11 -11
  19. package/dist/chunks/{index-n4j7qxyr.js → index-9yjae3hz.js} +4 -4
  20. package/dist/chunks/{javascript-4abzjcy9.css → javascript-bc9z461d.css} +688 -54
  21. package/dist/chunks/{javascript-xvrfrpkb.js → javascript-hr5sg57n.js} +2 -2
  22. package/dist/chunks/{jsonMode-vvmpmk34.css → jsonMode-qqd9nm1t.css} +1120 -303
  23. package/dist/chunks/{jsonMode-tyx2kj6p.js → jsonMode-zs88v0wr.js} +4 -4
  24. package/dist/chunks/{jx-validate-j0wxk72w.js → jx-validate-epp2z3s3.js} +2 -2
  25. package/dist/chunks/layers-panel-83a16ytx.js +41 -0
  26. package/dist/chunks/{layers-1vcfdbet.js → layers-s6mc01ry.js} +3 -3
  27. package/dist/chunks/{listbox-6v2ac08j.js → listbox-b7jxnhq1.js} +19 -3
  28. package/dist/chunks/{media-picker-p49nhqhs.js → media-picker-nhn6rv4w.js} +11 -11
  29. package/dist/chunks/{menu-qht48vnc.js → menu-8j4qxjrp.js} +4 -4
  30. package/dist/chunks/{monaco-binding-sw6en4dw.js → monaco-binding-e8d9wzyk.js} +3 -3
  31. package/dist/chunks/{monaco-buffer-pykwbm92.js → monaco-buffer-jaq1t9zj.js} +2 -2
  32. package/dist/chunks/{monaco-setup-8vbyqnhr.css → monaco-setup-ey9cjcm3.css} +954 -137
  33. package/dist/chunks/{monaco-setup-8r62fj6y.js → monaco-setup-gsjdysze.js} +7 -7
  34. package/dist/chunks/{platform-ts9cg1a4.js → platform-fnchmhy7.js} +5 -3
  35. package/dist/chunks/publish-panel-ak6hxfma.js +27 -0
  36. package/dist/chunks/quick-search-c63mkt3b.js +47 -0
  37. package/dist/chunks/{references-h69xp9zz.js → references-f7bdv3z5.js} +3 -3
  38. package/dist/chunks/right-panel-t5n8wttg.js +45 -0
  39. package/dist/chunks/{split-g7nm1rgb.js → split-4cz4b6ef.js} +14 -3
  40. package/dist/chunks/{studio-sar8g54p.js → studio-01269reb.js} +8 -8
  41. package/dist/chunks/{studio-rnyf3f8e.js → studio-0a4gncx0.js} +3 -3
  42. package/dist/chunks/{studio-e1f24eff.js → studio-0d4p4wxj.js} +6778 -2993
  43. package/dist/chunks/{studio-4wbb9ct0.js → studio-1tejxvba.js} +3006 -1855
  44. package/dist/chunks/{studio-p6pmwkvh.js → studio-3nprwbd9.js} +147 -28
  45. package/dist/chunks/{studio-4c041r8y.js → studio-40z39dme.js} +3 -3
  46. package/dist/chunks/{studio-68fydsrj.js → studio-482eeazk.js} +20 -20
  47. package/dist/chunks/{studio-y4f9fdge.js → studio-4998spet.js} +10 -9
  48. package/dist/chunks/{studio-nq5yf5mp.js → studio-557bmn0x.js} +2 -2
  49. package/dist/chunks/{studio-hdc2jt3z.js → studio-5w26wza5.js} +3 -3
  50. package/dist/chunks/{studio-6nzaktvy.js → studio-6pwn9j8c.js} +6 -3
  51. package/dist/chunks/{studio-wnsyffg1.js → studio-6vg0x5fz.js} +3 -2
  52. package/dist/chunks/{studio-vn5hske5.js → studio-7d3c1m59.js} +12 -5
  53. package/dist/chunks/{studio-28vs5k2y.js → studio-9vsvz7w7.js} +7 -7
  54. package/dist/chunks/{studio-t3adrr56.js → studio-9wm0xzpf.js} +19 -19
  55. package/dist/chunks/{studio-38emantv.js → studio-a5h5j33a.js} +19 -5
  56. package/dist/chunks/{studio-ympe0n7a.js → studio-ag3z6shb.js} +10 -4
  57. package/dist/chunks/studio-aj9drheq.js +177 -0
  58. package/dist/chunks/{studio-vt0tj32j.js → studio-apv9x70c.js} +189 -130
  59. package/dist/chunks/{studio-c04nzbby.js → studio-b9q3znz8.js} +2 -2
  60. package/dist/chunks/{studio-2mdbbc76.js → studio-bh47w0cy.js} +9 -10
  61. package/dist/chunks/{studio-ez11dmdh.js → studio-dgdm6vvt.js} +264 -52
  62. package/dist/chunks/{studio-5nz00bhh.js → studio-dwdcr2e8.js} +4 -4
  63. package/dist/chunks/{studio-nqgawfks.js → studio-fbcyz234.js} +146 -139
  64. package/dist/chunks/{studio-kcgff4r0.js → studio-fgmfksz8.js} +2 -2
  65. package/dist/chunks/{studio-x72j58vw.js → studio-fqpkpq1t.js} +7 -4
  66. package/dist/chunks/{studio-15f3kxqe.js → studio-fy320dfc.js} +62 -68
  67. package/dist/chunks/{studio-zjjde71a.js → studio-gjj4bbfx.js} +2 -2
  68. package/dist/chunks/{studio-66arbpc2.js → studio-jfhxy8h3.js} +16 -15
  69. package/dist/chunks/{studio-d0300636.js → studio-ks2zzth2.js} +17 -2
  70. package/dist/chunks/{studio-c07s5ckd.js → studio-kwewmhhq.js} +4 -4
  71. package/dist/chunks/{studio-01jeck2y.js → studio-m58xn8at.js} +26 -25
  72. package/dist/chunks/{studio-mpzwze9x.js → studio-mfkh1q8d.js} +17 -17
  73. package/dist/chunks/{studio-etvx9s2g.js → studio-mrh0a92f.js} +17 -17
  74. package/dist/chunks/{studio-qa7fzxan.js → studio-mt79tn56.js} +3 -3
  75. package/dist/chunks/{studio-hhskq2mr.js → studio-ne593z1j.js} +39 -32
  76. package/dist/chunks/{studio-h4nvnjvz.js → studio-ptfcxkz5.js} +418 -232
  77. package/dist/chunks/{studio-4xmjcrg3.js → studio-qxye31h7.js} +7 -7
  78. package/dist/chunks/{studio-a1pswps3.js → studio-rav5bpmp.js} +81 -59
  79. package/dist/chunks/{studio-ncn9e189.js → studio-rwmmbrth.js} +6 -6
  80. package/dist/chunks/{studio-en82c639.js → studio-sceg8ws0.js} +8 -8
  81. package/dist/chunks/{studio-wjpgxywa.js → studio-sjas5b3a.js} +19 -13
  82. package/dist/chunks/{studio-4r14yzyy.js → studio-vd67b66d.js} +3 -3
  83. package/dist/chunks/{studio-14d9tq4d.js → studio-w475cbf9.js} +10 -10
  84. package/dist/chunks/{studio-ctm7bhx5.js → studio-w7gpcvfj.js} +9 -9
  85. package/dist/chunks/{studio-9j6sfeba.js → studio-xrbfdxgk.js} +7 -7
  86. package/dist/chunks/{studio-x10fp4tp.js → studio-y49b6mjv.js} +35 -35
  87. package/dist/chunks/{studio-5t7ena5z.js → studio-ygvr9e4q.js} +10 -10
  88. package/dist/chunks/{studio-z2smx5ce.js → studio-yxmmjy2a.js} +9 -6
  89. package/dist/chunks/{tsMode-jy4m86rv.js → tsMode-ay4c78ne.js} +4 -4
  90. package/dist/chunks/{tsMode-hnxxb73h.css → tsMode-c72wyz27.css} +954 -137
  91. package/dist/chunks/{workspace-bnw54cha.js → workspace-b9k2frks.js} +5 -5
  92. package/dist/chunks/{ws-client-ny4zc51r.js → ws-client-erze4czt.js} +356 -20
  93. package/dist/codicon.ttf +0 -0
  94. package/dist/iframe-entry.js +20 -18
  95. package/dist/studio.css +956 -139
  96. package/dist/studio.js +1511 -851
  97. package/dist/workers/editor.worker.js +196 -107
  98. package/dist/workers/json.worker.js +196 -107
  99. package/dist/workers/ts.worker.js +196 -107
  100. package/package.json +18 -17
  101. package/src/about/about-modal.ts +4 -3
  102. package/src/browse/library-commands.ts +15 -13
  103. package/src/browse/library-pane.ts +2 -2
  104. package/src/canvas/canvas-render.ts +97 -83
  105. package/src/canvas/canvas-surface.ts +2 -2
  106. package/src/canvas/canvas-utils.ts +42 -32
  107. package/src/canvas/diff-toolbar.ts +21 -2
  108. package/src/canvas/diff-view.ts +30 -6
  109. package/src/canvas/iframe-entry.ts +2 -2
  110. package/src/canvas/iframe-host.ts +5 -5
  111. package/src/canvas/iframe-interaction.ts +7 -6
  112. package/src/canvas/iframe-overlay.ts +1 -1
  113. package/src/canvas/iframe-protocol.ts +6 -5
  114. package/src/canvas/iframe-render.ts +3 -3
  115. package/src/canvas/popover-state.ts +1 -1
  116. package/src/canvas/stage-scrollbar.ts +112 -0
  117. package/src/canvas/surface-registry.ts +9 -2
  118. package/src/collab/collab-commands.ts +8 -6
  119. package/src/collab/collab-session.ts +4 -4
  120. package/src/collab/collab-state.ts +4 -4
  121. package/src/collab/presence-chips.ts +3 -3
  122. package/src/commands/app-commands.ts +7 -7
  123. package/src/commands/budget.ts +17 -16
  124. package/src/commands/command-args.ts +10 -9
  125. package/src/commands/context.ts +20 -10
  126. package/src/commands/defaults.ts +30 -29
  127. package/src/commands/levels.ts +8 -6
  128. package/src/commands/live-context.ts +12 -10
  129. package/src/commands/reference.ts +5 -5
  130. package/src/commands/registry.ts +2 -2
  131. package/src/content/draft-state.ts +9 -8
  132. package/src/content/entry-commands.ts +16 -14
  133. package/src/content/entry-editor.ts +2 -2
  134. package/src/content/entry-fields.ts +1 -1
  135. package/src/editor/context-menu.ts +4 -4
  136. package/src/editor/convert-to-component.ts +6 -5
  137. package/src/editor/inline-edit.ts +3 -3
  138. package/src/editor/shortcuts.ts +88 -20
  139. package/src/editor/slash-menu.ts +11 -10
  140. package/src/files/elements.ts +4 -5
  141. package/src/files/file-ops.ts +1 -1
  142. package/src/files/files.ts +21 -17
  143. package/src/files/serialize-document.ts +1 -1
  144. package/src/format/convert-file.ts +5 -5
  145. package/src/github/github-auth-status.ts +107 -0
  146. package/src/github/github-auth.ts +18 -82
  147. package/src/github/github-publish.ts +7 -6
  148. package/src/grid/cell-editors.ts +8 -7
  149. package/src/grid/grid-layout.ts +1 -1
  150. package/src/grid/grid-open.ts +6 -5
  151. package/src/grid/grid-panel.ts +13 -12
  152. package/src/grid/redirects-grid.ts +14 -11
  153. package/src/grid/redirects.ts +10 -5
  154. package/src/grid/sources/content-source.ts +10 -9
  155. package/src/hosting/document.ts +28 -1
  156. package/src/hosting/stage.ts +15 -4
  157. package/src/i18n/i18n-commands.ts +9 -7
  158. package/src/new-project/location-fields.ts +16 -3
  159. package/src/new-project/new-project-modal.ts +23 -7
  160. package/src/page-params.ts +6 -6
  161. package/src/panels/activity-panel.ts +11 -10
  162. package/src/panels/ai-chat/chat-view.ts +27 -14
  163. package/src/panels/ai-chat/composer.ts +10 -3
  164. package/src/panels/ai-panel.ts +87 -29
  165. package/src/panels/block-action-bar.ts +59 -33
  166. package/src/panels/bottom-dock.ts +22 -22
  167. package/src/panels/chat-panel.ts +40 -5
  168. package/src/panels/data-explorer.ts +9 -9
  169. package/src/panels/data-grid.ts +1 -1
  170. package/src/panels/editors.ts +8 -8
  171. package/src/panels/elements-panel.ts +7 -8
  172. package/src/panels/empty-state.ts +1 -1
  173. package/src/panels/events-panel.ts +13 -12
  174. package/src/panels/formula-workspace.ts +8 -5
  175. package/src/panels/frontmatter-fields.ts +4 -1
  176. package/src/panels/frontmatter-panel.ts +18 -18
  177. package/src/panels/git-panel.ts +19 -13
  178. package/src/panels/head-panel.ts +9 -10
  179. package/src/panels/i18n-panel.ts +6 -4
  180. package/src/panels/imports-panel.ts +1 -1
  181. package/src/panels/jump-bar.ts +20 -18
  182. package/src/panels/layers-panel.ts +19 -17
  183. package/src/panels/left-panel.ts +3 -3
  184. package/src/panels/navigator-panels.ts +8 -8
  185. package/src/panels/overlays.ts +1 -1
  186. package/src/panels/pane-context.ts +56 -28
  187. package/src/panels/pane-grid.ts +9 -8
  188. package/src/panels/panel-registry.ts +47 -19
  189. package/src/panels/problems-panel.ts +11 -12
  190. package/src/panels/properties-panel.ts +21 -20
  191. package/src/panels/provenance.ts +3 -3
  192. package/src/panels/quick-search.ts +15 -11
  193. package/src/panels/right-panel.ts +4 -3
  194. package/src/panels/seo-modal.ts +5 -4
  195. package/src/panels/settings-menu.ts +9 -9
  196. package/src/panels/settings-pane.ts +3 -3
  197. package/src/panels/signals-panel.ts +16 -9
  198. package/src/panels/statement-editor.ts +12 -9
  199. package/src/panels/style-panel.ts +45 -41
  200. package/src/panels/stylebook-panel.ts +0 -1
  201. package/src/panels/tab-strip.ts +26 -23
  202. package/src/platform-errors.ts +2 -2
  203. package/src/platform.ts +44 -4
  204. package/src/platforms/cloud.ts +11 -1
  205. package/src/platforms/default-platform.ts +141 -4
  206. package/src/platforms/devserver.ts +23 -3
  207. package/src/publish/deploy-checklist.ts +11 -10
  208. package/src/publish/publish-commands.ts +9 -8
  209. package/src/services/a11y-report.ts +1 -1
  210. package/src/services/ai-ask.ts +39 -16
  211. package/src/services/ai-command-tools.ts +28 -14
  212. package/src/services/ai-import-tools.ts +43 -29
  213. package/src/services/ai-project-tools.ts +20 -38
  214. package/src/services/ai-settings.ts +55 -2
  215. package/src/services/ai-tools.ts +69 -58
  216. package/src/services/ai-write-report.ts +4 -4
  217. package/src/services/ai-writes.ts +74 -55
  218. package/src/services/automation.ts +12 -12
  219. package/src/services/clock.ts +2 -1
  220. package/src/services/connection.ts +2 -2
  221. package/src/services/context-manager.ts +69 -6
  222. package/src/services/directory-picker.ts +93 -27
  223. package/src/services/document-assistant.ts +86 -17
  224. package/src/services/gated-registry.ts +4 -3
  225. package/src/services/idle.ts +5 -4
  226. package/src/services/live-surfaces.ts +2 -1
  227. package/src/services/module-preloads.ts +67 -0
  228. package/src/services/notify.ts +9 -7
  229. package/src/services/popover-report.ts +3 -3
  230. package/src/services/preview-eval.ts +1 -1
  231. package/src/services/project-adoption.ts +34 -1
  232. package/src/services/project-path.ts +53 -0
  233. package/src/services/settings/kernel.ts +13 -2
  234. package/src/services/tool-executor.ts +135 -30
  235. package/src/services/tool-outcomes.ts +143 -0
  236. package/src/settings/contexts-section.ts +8 -8
  237. package/src/settings/contributed-section.ts +6 -6
  238. package/src/settings/css-vars-editor.ts +6 -5
  239. package/src/settings/extension-commands.ts +19 -18
  240. package/src/settings/extension-sections.ts +7 -7
  241. package/src/settings/extensions-section.ts +3 -3
  242. package/src/settings/general-settings.ts +21 -18
  243. package/src/settings/preferences-accounts.ts +3 -3
  244. package/src/settings/preferences-dialog.ts +9 -8
  245. package/src/settings/preferences-sections.ts +1 -1
  246. package/src/settings/project-sections.ts +2 -2
  247. package/src/settings/section-registry.ts +7 -6
  248. package/src/settings/settings-document.ts +41 -43
  249. package/src/shell/tree.ts +29 -0
  250. package/src/shell.ts +63 -55
  251. package/src/site-context.ts +1 -1
  252. package/src/store.ts +2 -2
  253. package/src/studio.ts +52 -28
  254. package/src/style/live-preview.ts +8 -8
  255. package/src/style/project-styles.ts +8 -9
  256. package/src/style/token-ref.ts +2 -2
  257. package/src/surfaces/about.json +6 -2
  258. package/src/surfaces/add-repo.json +2 -2
  259. package/src/surfaces/add-repo.ts +2 -2
  260. package/src/surfaces/ai-chat.json +14 -6
  261. package/src/surfaces/ai-chat.ts +4 -2
  262. package/src/surfaces/ai-credentials-form.json +53 -49
  263. package/src/surfaces/ai-credentials-form.ts +18 -4
  264. package/src/surfaces/ai-managed-connect.json +4 -4
  265. package/src/surfaces/block-action-bar.json +4 -4
  266. package/src/surfaces/block-action-bar.ts +3 -3
  267. package/src/surfaces/boot-failure.json +228 -0
  268. package/src/surfaces/boot-failure.ts +156 -0
  269. package/src/surfaces/bottom-dock.json +1 -1
  270. package/src/surfaces/canvas-stage.json +9 -40
  271. package/src/surfaces/canvas-stage.ts +9 -9
  272. package/src/surfaces/cf-account-picker.json +11 -8
  273. package/src/surfaces/commandbar.ts +11 -10
  274. package/src/surfaces/convert-repeater.ts +3 -2
  275. package/src/surfaces/dialog.json +7 -1
  276. package/src/surfaces/dialog.ts +2 -2
  277. package/src/surfaces/doc-header.json +6 -13
  278. package/src/surfaces/doc-header.ts +4 -5
  279. package/src/surfaces/entry-editor.json +1 -1
  280. package/src/surfaces/entry-editor.ts +2 -1
  281. package/src/surfaces/expression-editor.json +5 -5
  282. package/src/surfaces/files-panel.json +4 -3
  283. package/src/surfaces/files-panel.ts +1 -1
  284. package/src/surfaces/formula-palette.json +10 -9
  285. package/src/surfaces/formula-palette.ts +11 -7
  286. package/src/surfaces/git-panel.json +167 -109
  287. package/src/surfaces/git-panel.ts +2 -2
  288. package/src/surfaces/github-auth.json +2 -1
  289. package/src/surfaces/github-publish.ts +3 -3
  290. package/src/surfaces/grid-open.json +1 -1
  291. package/src/surfaces/grid-panel.json +2 -2
  292. package/src/surfaces/grid-views.json +1 -1
  293. package/src/surfaces/inspector-dock.json +2 -2
  294. package/src/surfaces/jump-bar.json +1 -1
  295. package/src/surfaces/jump-bar.ts +1 -1
  296. package/src/surfaces/library-pane.ts +2 -1
  297. package/src/surfaces/logic-panel.json +7 -7
  298. package/src/surfaces/logic-panel.ts +6 -4
  299. package/src/surfaces/logic-workspace.json +2 -2
  300. package/src/surfaces/logic-workspace.ts +2 -2
  301. package/src/surfaces/media-browser.ts +3 -3
  302. package/src/surfaces/media-pane.ts +2 -1
  303. package/src/surfaces/menu.ts +5 -5
  304. package/src/surfaces/new-project.json +1 -1
  305. package/src/surfaces/palette.json +8 -7
  306. package/src/surfaces/pane-context.json +86 -57
  307. package/src/surfaces/pane-context.ts +2 -1
  308. package/src/surfaces/pane-grid.json +3 -3
  309. package/src/surfaces/panel-activity.json +2 -1
  310. package/src/surfaces/panel-deploy-checklist.json +1 -1
  311. package/src/surfaces/panel-elements.ts +2 -1
  312. package/src/surfaces/panel-i18n.ts +2 -1
  313. package/src/surfaces/panel-outline.json +1 -1
  314. package/src/surfaces/panel-outline.ts +4 -3
  315. package/src/surfaces/panel-page.json +2 -2
  316. package/src/surfaces/panel-page.ts +4 -1
  317. package/src/surfaces/panel-problems.json +4 -3
  318. package/src/surfaces/panel-problems.ts +2 -2
  319. package/src/surfaces/panel-signals.json +23 -8
  320. package/src/surfaces/panel-signals.ts +5 -1
  321. package/src/surfaces/panel-stylebook-layers.json +1 -1
  322. package/src/surfaces/panel-stylebook-layers.ts +2 -2
  323. package/src/surfaces/preferences.json +21 -8
  324. package/src/surfaces/preferences.ts +9 -1
  325. package/src/surfaces/properties-panel.json +4 -4
  326. package/src/surfaces/properties-panel.ts +2 -2
  327. package/src/surfaces/publish.json +1 -1
  328. package/src/surfaces/publish.ts +2 -1
  329. package/src/surfaces/push-plan.json +4 -3
  330. package/src/surfaces/rail.json +2 -2
  331. package/src/surfaces/rail.ts +10 -4
  332. package/src/surfaces/reference-field.json +1 -1
  333. package/src/surfaces/schema-form.json +2 -2
  334. package/src/surfaces/schema-form.ts +1 -1
  335. package/src/surfaces/seo.json +1 -1
  336. package/src/surfaces/seo.ts +2 -2
  337. package/src/surfaces/settings-contributed.ts +2 -1
  338. package/src/surfaces/settings-css-vars.json +4 -4
  339. package/src/surfaces/settings-deploy.json +4 -4
  340. package/src/surfaces/settings-deploy.ts +2 -1
  341. package/src/surfaces/settings-extensions.json +6 -6
  342. package/src/surfaces/settings-extensions.ts +6 -2
  343. package/src/surfaces/settings-locales.json +4 -4
  344. package/src/surfaces/settings-overview.ts +2 -1
  345. package/src/surfaces/settings-pane.ts +3 -2
  346. package/src/surfaces/settings-rawjson.ts +1 -1
  347. package/src/surfaces/slash-menu.ts +4 -4
  348. package/src/surfaces/statements.json +3 -3
  349. package/src/surfaces/statusbar.ts +23 -23
  350. package/src/surfaces/style-panel.json +9 -5
  351. package/src/surfaces/style-panel.ts +3 -3
  352. package/src/surfaces/tab-strip.json +2 -2
  353. package/src/surfaces/target-line.json +1 -1
  354. package/src/surfaces/target-line.ts +12 -10
  355. package/src/surfaces/welcome.json +7 -2
  356. package/src/tabs/doc-op-apply.ts +5 -5
  357. package/src/tabs/patch-ops.ts +3 -3
  358. package/src/tabs/project-config.ts +10 -10
  359. package/src/tabs/selection.ts +1 -1
  360. package/src/tabs/tab.ts +13 -14
  361. package/src/tabs/transact.ts +18 -24
  362. package/src/types.ts +27 -3
  363. package/src/ui/ai-credentials-form.ts +74 -16
  364. package/src/ui/ai-managed-connect.ts +68 -19
  365. package/src/ui/color-selector.ts +1 -1
  366. package/src/ui/expression-editor.ts +3 -3
  367. package/src/ui/form-controls.ts +6 -5
  368. package/src/ui/formula-catalog.ts +4 -4
  369. package/src/ui/formula-chips.ts +2 -2
  370. package/src/ui/layers.ts +20 -18
  371. package/src/ui/panel-resize.ts +8 -28
  372. package/src/ui/popover-surface.ts +2 -2
  373. package/src/ui/progress-modal.ts +3 -3
  374. package/src/ui/regions.ts +14 -9
  375. package/src/ui/schema-form.ts +10 -9
  376. package/src/ui/timing.ts +9 -7
  377. package/src/ui/value-source.ts +4 -4
  378. package/src/ui/virtual-window.ts +58 -5
  379. package/src/utils/base64.ts +20 -1
  380. package/src/utils/canvas-media.ts +1 -1
  381. package/src/utils/studio-utils.ts +0 -31
  382. package/src/view.ts +4 -3
  383. package/src/workspace/pane-derive.ts +14 -13
  384. package/src/workspace/session.ts +7 -6
  385. package/src/workspace/workspace.ts +49 -39
  386. package/styles/canvas.css +1 -27
  387. package/styles/inspector.css +4 -3
  388. package/styles/overlays.css +1 -1
  389. package/styles/tokens.css +17 -1
  390. package/styles/tokens.json +6 -1
  391. package/dist/chunks/entry-commands-w9qsxa6q.js +0 -42
  392. package/dist/chunks/layers-panel-fksbdg4q.js +0 -40
  393. package/dist/chunks/publish-panel-vz23wwmg.js +0 -27
  394. package/dist/chunks/quick-search-e9rp74m8.js +0 -46
  395. package/dist/chunks/right-panel-j97t2yvy.js +0 -45
  396. package/dist/chunks/studio-727xh0e3.js +0 -76
  397. package/src/files/json-layout.ts +0 -474
  398. package/src/services/ai-turn-signal.ts +0 -70
@@ -12,8 +12,8 @@
12
12
  * scheme layer, nested selector)`. It has always computed that tuple — it is the per-field key and
13
13
  * the five-branch if/else below — and it used to hide it behind three disconnected widgets on two
14
14
  * different bars. `target-line.ts` renders it as one sentence instead, and the three widgets are
15
- * gone: the breakpoint and scheme axes are selected on the pane context bar (region ⑦), and the
16
- * selector is the one axis this tab owns.
15
+ * gone: the breakpoint and scheme axes are selected on the pane context bar, and the selector is
16
+ * the one axis this tab owns.
17
17
  *
18
18
  * Every row then says where its value came from. `provenance.ts` supplies the vocabulary;
19
19
  * `computeInheritedSources()` supplies the donor breakpoint the cascade walk always knew and this
@@ -28,17 +28,18 @@
28
28
  * bound property, and an equal write is skipped. `renderOnly("rightPanel")` is gone from this file
29
29
  * with it.
30
30
  *
31
- * **Two controls are lists, and the list is the KIT MENU** (`surfaces/menu.ts`, §12.5). The unit
32
- * picker and the keyword suggestions were each an `sp-overlay` + `sp-menu` of their own; the Value
33
- * Source picker was a third. All three are `openMenu()` now, which is also how roving focus,
34
- * typeahead and Escape arrive here without a line of keyboard code.
31
+ * **Two controls are lists, and the list is the KIT MENU** (`surfaces/menu.ts`,
32
+ * `studio-ui-guidelines.md` §12.5). The unit picker and the keyword suggestions were each an
33
+ * `sp-overlay` + `sp-menu` of their own; the Value Source picker was a third. All three are
34
+ * `openMenu()` now, which is also how roving focus, typeahead and Escape arrive here without a line
35
+ * of keyboard code.
35
36
  *
36
37
  * **Nothing here is an island any more.** One control was: `specs/ui.md` §5.6 (Colour) was Pending,
37
38
  * so a colour row drew an announced `[part="control-host"]` and a lit `paintColorControl` filled
38
- * it, and that was the last Spectrum on this surface. §5.6 landed. The row is a `jx-color-field` in
39
- * the document beside every other control, its palette is a `jx-swatch-group` slotted into the
40
- * picker, and `ui/color-selector.ts` is what remains: which colours THIS PROJECT has named, which
41
- * is a question no kit element can answer.
39
+ * it, and that was the last Spectrum on this surface. `ui.md` §5.6 landed. The row is a
40
+ * `jx-color-field` in the document beside every other control, its palette is a `jx-swatch-group`
41
+ * slotted into the picker, and `ui/color-selector.ts` is what remains: which colours THIS PROJECT
42
+ * has named, which is a question no kit element can answer.
42
43
  *
43
44
  * @docs studio/design/style-inspector
44
45
  */
@@ -68,7 +69,6 @@ import {
68
69
  transactDoc,
69
70
  } from "../tabs/transact";
70
71
  import {
71
- abbreviateValue,
72
72
  friendlyNameToVar,
73
73
  inferInputType,
74
74
  kebabToLabel,
@@ -337,7 +337,7 @@ export function resetAffectedDisclosure(): void {
337
337
  *
338
338
  * The breakpoint and the colour scheme are DEFINED in Project Settings › Contexts and SELECTED on
339
339
  * the pane context bar. A Target Line segment that opened its own list would be the third selector
340
- * §6.4 forbids, so it routes to the definition site through the same command the context bar's
340
+ * §6.2 forbids, so it routes to the definition site through the same command the context bar's
341
341
  * "Manage contexts…" footer runs.
342
342
  */
343
343
  function runCommand(id: string, args?: Record<string, unknown>): void {
@@ -543,7 +543,7 @@ interface ProvenanceCtx {
543
543
  signals: string[];
544
544
  clear: (prop: string) => void;
545
545
  /**
546
- * Properties the selected elements DISAGREE about, and how many elements are selected (§6.5).
546
+ * Properties the selected elements DISAGREE about, and how many elements are selected (§6.7).
547
547
  *
548
548
  * Empty for a selection of one — which is what keeps every other branch of `provenanceOf`
549
549
  * reachable exactly as often as it was before the selection became a set.
@@ -727,7 +727,7 @@ interface RowActions {
727
727
  choices?: StyleChoice[];
728
728
  choicesLabel?: string;
729
729
  choose?: (value: string) => void;
730
- /** The Value Source ladder for this position (§6.3). */
730
+ /** The Value Source ladder for this position (§6.6). */
731
731
  source?: {
732
732
  mode: SlotMode;
733
733
  caps: SlotMode[];
@@ -771,27 +771,30 @@ function isNumericValue(value: string): boolean {
771
771
  }
772
772
 
773
773
  /**
774
- * Kit glyph names for the values a button-group offers.
774
+ * The buttons a button-group row draws, and the overflow list it hands to the kit menu.
775
775
  *
776
- * Only names the kit ACTUALLY has (`@jxsuite/ui/icons`) are here, and every other value falls back
777
- * to `abbreviateValue`, which is the same fallback the group has always had for a value with no
778
- * icon. The alternative was to guess an approximation — `align-left` for `justify-content:
779
- * flex-start`, say, which is a text-alignment glyph — and a glyph that means something else is
780
- * worse than three letters that mean what they say. The seventeen names still missing are recorded
781
- * against the kit rather than approximated here.
776
+ * A value's glyph is its css-meta `$icons` entry VERBATIM: those values are kit manifest names
777
+ * (ui.md §8, one key space), so there is nothing to translate. A value with no entry — `nowrap`,
778
+ * `wrap-reverse`, `normal`, `auto` — draws its own CSS KEYWORD instead, in full and in lower case,
779
+ * and its button is the only kind that carries any text.
780
+ *
781
+ * The keyword is written out rather than abbreviated. It used to go through an `abbreviateValue`
782
+ * table in `utils/studio-utils.ts`, and four segments of a row that is otherwise pictures then read
783
+ * `no-wr`, `wr-rev`, `norm` and `auto` — a reader cannot decode three of them. `wr-rev` is as
784
+ * opaque as the glyph-less Display row this pass set out to fix, and it sat beside a wrap arrow
785
+ * that a reader COULD decode, so the row taught them the buttons were unreadable. A word takes
786
+ * about 30px more than a glyph; the group's text segments pay 6px of inline padding for it
787
+ * (`jx-action-group`), and no row draws more than two. That table had no other caller, so it went
788
+ * with the abbreviations rather than staying behind as a spelling nothing spells.
789
+ *
790
+ * There used to be a table here from css-meta's own semantic names (`display-flex`,
791
+ * `justify-start`) to the eight of them the kit happened to ship verbatim. It was a second spelling
792
+ * of glyph names beside the manifest, it had drifted from the translations the kit had already
793
+ * reasoned out, and every name it lacked resolved to `""` in silence, which is how the whole
794
+ * Display row shipped as abbreviations. So there is no runtime `hasIcon` guard either: a name the
795
+ * manifest lacks is a red `tests/metadata.test.ts`, which is where a miss is caught before it ships
796
+ * rather than degraded after.
782
797
  */
783
- const BUTTON_GLYPHS: Readonly<Record<string, string>> = {
784
- "arrow-down": "arrow-down",
785
- "arrow-left": "arrow-left",
786
- "arrow-right": "arrow-right",
787
- "arrow-up": "arrow-up",
788
- "text-align-center": "text-align-center",
789
- "text-align-justify": "text-align-justify",
790
- "text-align-left": "text-align-left",
791
- "text-align-right": "text-align-right",
792
- };
793
-
794
- /** The buttons a button-group row draws, and the overflow list it hands to the kit menu. */
795
798
  function buttonGroup(
796
799
  entry: CssPropertyEntry,
797
800
  rowKey: string,
@@ -806,12 +809,12 @@ function buttonGroup(
806
809
  ? enumValues.filter((v) => !buttonValues.includes(v))
807
810
  : [];
808
811
  const buttons = values.map((v) => {
809
- const icon = BUTTON_GLYPHS[iconMap[v] ?? ""] ?? "";
812
+ const icon = iconMap[v] ?? "";
810
813
  return {
811
814
  icon,
812
815
  row: rowKey,
813
816
  selected: v === value,
814
- text: icon === "" ? abbreviateValue(v) : "",
817
+ text: icon === "" ? v : "",
815
818
  title: v,
816
819
  value: v,
817
820
  };
@@ -1060,7 +1063,7 @@ function fieldRow(
1060
1063
  row.placeholder = placeholder;
1061
1064
  applyChip(row, actions, opts.provenance);
1062
1065
 
1063
- // The Value Source ladder (§6.3), and only where the position has one to offer. A longhand child
1066
+ // The Value Source ladder (§6.6), and only where the position has one to offer. A longhand child
1064
1067
  // Is not a document position of its own — its value is a slice of the shorthand above it — so it
1065
1068
  // Carries no chip, which is what it has always done.
1066
1069
  if (!opts.child) {
@@ -1395,7 +1398,7 @@ function buildView(canvasMode: () => string): StylePanelView {
1395
1398
  return emptyView("Open a page to style what you click.", true);
1396
1399
  }
1397
1400
  // No separate `Styling: <h1>` header. It said the same thing the Target Line's scope chip says,
1398
- // Except that it said it as a caption, after the fact, and without the blast radius (§6.1).
1401
+ // Except that it said it as a caption, after the fact, and without the blast radius (§6.2).
1399
1402
  return buildEditor(tab, node, {
1400
1403
  effectiveStyle: getEffectiveStyle(node.style),
1401
1404
  stylebookSelector: shell.stylebook.selection,
@@ -1445,7 +1448,7 @@ function buildEditor(
1445
1448
  const mediaTab = tab.session.ui.activeMedia || null;
1446
1449
  const { activeSelector } = tab.session.ui;
1447
1450
 
1448
- // ── Scheme-layer routing (spec §9.5) ────────────────────────────────────────
1451
+ // ── Scheme-layer routing (spec.md §9.5) ─────────────────────────────────────
1449
1452
  // With the tab-bar scheme control forcing a scheme that has a matching declared scheme query,
1450
1453
  // Base-context edits target that scheme's `@--name` block — no extra sidebar tabs. Breakpoint
1451
1454
  // Tabs stay breakpoint-scoped (scheme × breakpoint compound blocks are unsupported).
@@ -1476,7 +1479,7 @@ function buildEditor(
1476
1479
  ]),
1477
1480
  ];
1478
1481
 
1479
- // ── The Target Line (§6.1) ─────────────────────────────────────────────────
1482
+ // ── The Target Line (§6.2) ─────────────────────────────────────────────────
1480
1483
  const stylebookTag = stylebookTagOf(stylebookSelector);
1481
1484
  const elementLabel =
1482
1485
  stylebookTag ?? (typeof node.tagName === "string" ? node.tagName : "element");
@@ -1510,7 +1513,8 @@ function buildEditor(
1510
1513
  },
1511
1514
  onAddCustom: () => {
1512
1515
  // One flow, through `ui/layers.ts` — the imperative `<input>` this replaces was appended to
1513
- // A bar lit-html owns, bypassing both the renderer and the overlay stack (§11.4).
1516
+ // A bar lit-html owns, bypassing both the renderer and the overlay stack (§6.2, "Nested
1517
+ // Selector Context").
1514
1518
  void showPromptDialog("Add Selector", {
1515
1519
  confirmLabel: "Use",
1516
1520
  message: "A state or nested rule to edit under this element.",
@@ -1537,7 +1541,7 @@ function buildEditor(
1537
1541
  style === ownRootStyle
1538
1542
  ? activeStyle
1539
1543
  : resolveContextStyle(ownRootStyle, activeSelector, editMedia);
1540
- // ── The selection, and what it means to commit to it (§6.5) ────────────────
1544
+ // ── The selection, and what it means to commit to it (§6.7) ────────────────
1541
1545
  // Every selected element gets the same write, inside ONE `transactDoc` — so setting padding on
1542
1546
  // Six cards is one decision and one undo step. With one element selected this loop runs once
1543
1547
  // Against `sel` and is indistinguishable from the single-target commit it replaces.
@@ -196,7 +196,6 @@ export async function renderStylebookMode(
196
196
  const stage = await ctx.drawStage(
197
197
  surface,
198
198
  {
199
- columnHeader: "hidden",
200
199
  frame: "boards",
201
200
  framePart: "panzoom",
202
201
  // The bar floats over the stage, so the catalogue starts below it.
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * Tab strip — one strip PER PANE, above each pane's editor.
4
4
  *
5
- * A tab belongs to a pane and the pane is what splits (§4.3), so the strip is a rendering of
5
+ * A tab belongs to a pane and the pane is what splits (§14.4), so the strip is a rendering of
6
6
  * `Pane.tabOrder` / `Pane.activeTabId`, not of a workspace-wide list. The host for each pane is
7
7
  * addressed by REGION id — `pane.primary/tabs`, `pane.secondary/tabs` — rather than by element id,
8
8
  * so the shell can move or rename the divs without touching this file.
@@ -185,7 +185,8 @@ export function mount(host: HTMLElement) {
185
185
  source.data.type === "tab"
186
186
  ? { kind: "tab", tabId: source.data.tabId as string }
187
187
  : { kind: "file", path: source.data.path as string };
188
- // The tail target does not exist until a pane with no tabs is asked to grow one (§7).
188
+ // The tail target does not exist until a pane with no tabs is asked to grow one (§14.4's
189
+ // "Between panes" row; `project` draws it).
189
190
  render();
190
191
  // `render()` alone does not write `dragKey` — only `syncDrag()` does — so without this the
191
192
  // Source chip's `data-dragging` mark would not appear until the pointer's first move.
@@ -566,8 +567,8 @@ function project(pane: Pane): TabStripValues {
566
567
  tabs: pane.tabOrder.flatMap((id, index) => chipView(pane, id, index, labels)),
567
568
  trailing: _overflowing.get(pane.id) === true,
568
569
  trailingGlyph: "⌄",
569
- // ONE accessible name (§10): the glyph is hidden, so `title` is both the tooltip and the name
570
- // — `title` + a matching `aria-label` would announce it twice.
570
+ // ONE accessible name (`studio-ui-guidelines.md` §10): the glyph is hidden, so `title` is both
571
+ // The tooltip and the name — `title` + a matching `aria-label` would announce it twice.
571
572
  trailingTitle: "Show hidden tabs",
572
573
  };
573
574
  }
@@ -594,9 +595,10 @@ function chipView(
594
595
  if (!tab) {
595
596
  return [];
596
597
  }
597
- /* The draft pill (§7.6). On the CHIP, not only inside the entry editor: the mistake this
598
- prevents — publishing something you believed was private — is made while glancing at a row of
599
- tabs, and it is made about a document that may not even be the active one. */
598
+ /* The draft pill (`site-architecture.md` §7.6). On the CHIP, not only inside the entry editor:
599
+ the mistake this prevents — publishing something you believed was private — is made while
600
+ glancing at a row of tabs, and it is made about a document that may not even be the active
601
+ one. */
600
602
  const pill = entryDraftPill(tab);
601
603
  return [
602
604
  {
@@ -678,8 +680,9 @@ function adoptChip(host: HTMLElement, element: HTMLElement, id: string): void {
678
680
 
679
681
  /**
680
682
  * Make a strip's OWN element a drop target — the empty tail after the last chip, and, for an empty
681
- * pane growing one during a drag (§7), the whole of it. Innermost wins, so a chip under the pointer
682
- * answers first; this only ever resolves a drop that landed on the strip itself.
683
+ * pane growing one during a drag (§14.4, "Between panes"), the whole of it. Innermost wins, so a
684
+ * chip under the pointer answers first; this only ever resolves a drop that landed on the strip
685
+ * itself.
683
686
  */
684
687
  function adoptStrip(host: HTMLElement, element: HTMLElement): void {
685
688
  _stripDnd.get(host)?.();
@@ -848,11 +851,11 @@ export function dismissOverflowMenu() {
848
851
  * List the off-screen tabs. Falls back to every tab when nothing measures as hidden, because an
849
852
  * empty menu is a dead control and happy-dom (plus any zero-height layout) measures everything at 0.
850
853
  *
851
- * **The kit menu, not a popover of this strip's own** (guidelines §8.4). `surfaces/menu.ts` owns
852
- * the panel, the roving caret, the typeahead, Escape and the light dismissal, and it clamps the
853
- * panel into the viewport once it has been laid out — so the `right:`/`top:` arithmetic that used
854
- * to sit in a style attribute here is one `place` callback, measured against the panel's real box
855
- * instead of guessed from the chevron's.
854
+ * **The kit menu, not a popover of this strip's own** (`studio-ui-guidelines.md` §8.4).
855
+ * `surfaces/menu.ts` owns the panel, the roving caret, the typeahead, Escape and the light
856
+ * dismissal, and it clamps the panel into the viewport once it has been laid out — so the
857
+ * `right:`/`top:` arithmetic that used to sit in a style attribute here is one `place` callback,
858
+ * measured against the panel's real box instead of guessed from the chevron's.
856
859
  *
857
860
  * A row states whether it is the tab the pane is already showing. The strip's own chips carry that
858
861
  * as `aria-selected`, and a menu row cannot: `checked` on every row is the shape this shell already
@@ -1038,12 +1041,12 @@ function placedTabItems(tab: Tab): MenuRowProjection[] {
1038
1041
  * beside the gesture that needs them (`surfaces/tab-strip.json`), so what arrives here is the tab
1039
1042
  * and the point — which is everything a menu is a function of.
1040
1043
  *
1041
- * **The rows go to the kit menu** (`surfaces/menu.ts`, guidelines §12.5), which is why there is no
1042
- * row template in this file any more. Three things that were written here went with it: the clamp
1043
- * that kept a right-click near the screen edge readable — `onMenuToggle` clamps every panel once it
1044
- * has been laid out, against the panel's real box rather than a guessed one — the `Needs …` line a
1045
- * disabled row prints, and the refusal to dismiss when a disabled row is clicked, which is
1046
- * `jx-menu-item`'s own `onClick` guard.
1044
+ * **The rows go to the kit menu** (`surfaces/menu.ts`, `studio-ui-guidelines.md` §12.5), which is
1045
+ * why there is no row template in this file any more. Three things that were written here went with
1046
+ * it: the clamp that kept a right-click near the screen edge readable — `onMenuToggle` clamps every
1047
+ * panel once it has been laid out, against the panel's real box rather than a guessed one — the
1048
+ * `Needs …` line a disabled row prints, and the refusal to dismiss when a disabled row is clicked,
1049
+ * which is `jx-menu-item`'s own `onClick` guard.
1047
1050
  */
1048
1051
  function openTabContextMenu(tab: Tab, clientX: number, clientY: number) {
1049
1052
  dismissTabContextMenu();
@@ -1340,9 +1343,9 @@ async function confirmClose(tab: Tab): Promise<boolean> {
1340
1343
  *
1341
1344
  * **Three ways out, not two.** This asked "Close without saving?" over a two-way confirm, so the
1342
1345
  * only buttons were the one that threw the work away and the one that did nothing — a dialog that
1343
- * cannot do the thing the user most likely wants. §8.7's table assigns "Unsaved-work decisions" to
1344
- * `showSaveDiscardDialog`, and this is that decision — except when a save is not one of the ways
1345
- * out at all, which is {@link confirmClose}'s job to tell apart.
1346
+ * cannot do the thing the user most likely wants. `studio-ui-guidelines.md` §8.7's table assigns
1347
+ * "Unsaved-work decisions" to `showSaveDiscardDialog`, and this is that decision — except when a
1348
+ * save is not one of the ways out at all, which is {@link confirmClose}'s job to tell apart.
1346
1349
  *
1347
1350
  * **A failed save must not close the tab.** `saveFile` reports its own failures and returns whether
1348
1351
  * the bytes landed; treating "Save" as "save and then close regardless" would turn a write error
@@ -8,8 +8,8 @@
8
8
  * **RFC 9457 reconciles cleanly with this, because a problem's `type` IS the code.** A backend
9
9
  * answering `type: "https://jxsuite.com/problems/needs-installation-access"` is saying exactly what
10
10
  * `code: "needs_installation_access"` said; `problemSlug` derives the one from the other, and
11
- * `installUrl` is the extension member (§3.2) that type documents. So this module reads both, and
12
- * every surface above it keeps branching on `code` without knowing which shape arrived.
11
+ * `installUrl` is the extension member (RFC 9457 §3.2) that type documents. So this module reads
12
+ * both, and every surface above it keeps branching on `code` without knowing which shape arrived.
13
13
  */
14
14
 
15
15
  import { problemSlug } from "@jxsuite/protocol";
package/src/platform.ts CHANGED
@@ -13,20 +13,60 @@
13
13
  *
14
14
  * **Every call is counted.** `getPlatform()` hands out a counting proxy, not the raw adapter, so
15
15
  * "is Studio waiting on I/O?" is one number rather than thirty hand-instrumented methods
16
- * (`probe.idle()` condition 4, spec §13.5). Wrapping at the READ point rather than at
16
+ * (`probe.idle()`'s in-flight I/O condition, §13.5). Wrapping at the READ point rather than at
17
17
  * `registerPlatform` is deliberate: desktop pre-registers its adapter on `window.__jxPlatform`
18
18
  * without ever calling the registrar, and this is the seam both paths share.
19
19
  */
20
20
 
21
21
  import type { StudioPlatform } from "./types";
22
22
 
23
- const g = globalThis as unknown as { __jxPlatform?: StudioPlatform };
23
+ /** What a launcher's boot module recorded when its adapter could not be constructed. */
24
+ export interface LauncherBootError {
25
+ message: string;
26
+ stack?: string | undefined;
27
+ }
28
+
29
+ /**
30
+ * A launcher's announcement that it owns this window's adapter (spec/desktop.md §3.3).
31
+ *
32
+ * Published by the launcher boot module's FIRST import, before any other module in its bundle
33
+ * evaluates, so it exists even when a later import throws. Present with no adapter registered, it
34
+ * means the launcher failed, and Studio must say so rather than fall back to the dev server. That
35
+ * fallback is what desktop 5.0.0-5.1.3 did for months: its init bundle threw on import, and every
36
+ * window ran the dev-server adapter against a `views://` origin with nothing behind it.
37
+ */
38
+ export interface LauncherSignal {
39
+ /** Which launcher announced (`"electrobun"`, `"chromium"`), once its boot module's body ran. */
40
+ launcher?: string | undefined;
41
+ /** The first error the page raised during boot, or the adapter factory's own throw. */
42
+ error?: LauncherBootError | undefined;
43
+ }
44
+
45
+ const g = globalThis as unknown as {
46
+ __jxPlatform?: StudioPlatform;
47
+ __jxLauncher?: LauncherSignal;
48
+ };
24
49
 
25
50
  /** @param {StudioPlatform} platform */
26
51
  export function registerPlatform(platform: StudioPlatform) {
27
52
  g.__jxPlatform = platform;
28
53
  }
29
54
 
55
+ /**
56
+ * Publish the launcher signal on `globalThis.__jxLauncher`, or return the one already published.
57
+ *
58
+ * Idempotent, so an error recorded by the first announcer survives a second call.
59
+ */
60
+ export function announceLauncher(): LauncherSignal {
61
+ g.__jxLauncher ??= {};
62
+ return g.__jxLauncher;
63
+ }
64
+
65
+ /** The launcher signal, when a launcher's boot module announced itself in this window. */
66
+ export function launcherSignal(): LauncherSignal | undefined {
67
+ return g.__jxLauncher;
68
+ }
69
+
30
70
  // ─── In-flight accounting ─────────────────────────────────────────────────────
31
71
 
32
72
  /** Method name → how many of its calls have not settled. Entries are deleted at zero. */
@@ -36,8 +76,8 @@ const inFlight = new Map<string, number>();
36
76
  * One entry per platform call whose promise has not settled, named by its method.
37
77
  *
38
78
  * Repeats are meaningful: two concurrent `gitStatus` reads are two reasons the shell is not
39
- * settled, and the rejection message that names them is the whole point of §13.4 — 115 sleeps were
40
- * 115 places that could not fail.
79
+ * settled, and the rejection message that names them is the whole point of "Determinism" in
80
+ * `scripts/screenshots/README.md` — 115 sleeps were 115 places that could not fail.
41
81
  */
42
82
  export function platformInFlight(): string[] {
43
83
  const names: string[] = [];
@@ -496,6 +496,16 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
496
496
  return data.content;
497
497
  },
498
498
 
499
+ /* Raw bytes. The session origin serves this route, so no CORS question arises — the shell and
500
+ the API are the same origin on cloud, which is exactly what `assetSpace: "repo"` is about. */
501
+ async readFileBytes(path: string) {
502
+ const res = await api(`/file/bytes?path=${encodeURIComponent(path)}`);
503
+ if (!res.ok) {
504
+ throw new Error(await errorMessage(res, `Failed to read file bytes: ${path}`));
505
+ }
506
+ return res.arrayBuffer();
507
+ },
508
+
499
509
  async writeFile(path: string, content: string) {
500
510
  const res = await api("/file", {
501
511
  method: "PUT",
@@ -724,7 +734,7 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
724
734
  *
725
735
  * Degrades to `{}` rather than throwing: a backend too old to serve the route, or a project
726
736
  * whose extensions the session cannot compose, keeps the bundled core schemas. That fallback
727
- * under-suggests extension extras but never reports false errors (§5.3).
737
+ * under-suggests extension extras but never reports false errors (extensions.md §5.3).
728
738
  */
729
739
  async fetchProjectSchemas(): Promise<ProjectSchemasResponse> {
730
740
  try {
@@ -1,15 +1,33 @@
1
1
  /// <reference lib="dom" />
2
2
  /**
3
- * Chooses the default PAL adapter when no platform was pre-registered on `window.__jxPlatform`.
3
+ * Chooses the default PAL adapter when no platform was pre-registered on `window.__jxPlatform` — or
4
+ * refuses to, when a launcher was supposed to have registered one.
4
5
  *
5
6
  * The cloud shell (the platform repo's `web/edit-init.ts`) publishes a `window.__jxCloud` signal
6
7
  * _instead of_ constructing the adapter itself, so the cloud adapter — and its collab WebSocket
7
8
  * client, which owns the shared `Y.Doc` — is created HERE, inside the studio bundle, sharing
8
9
  * studio's single `yjs` instance. Creating it in the shell bundle loads a SECOND `yjs`, which
9
10
  * breaks collab's cross-module `instanceof` checks (the Y↔JxDocOp conversion silently stops
10
- * reconciling). The dev server sends no signal and falls back to the dev-server adapter; desktop
11
- * pre-registers its own adapter on `__jxPlatform`, so this resolver never runs there.
11
+ * reconciling). The dev server sends no signal and gets the dev-server adapter.
12
+ *
13
+ * **This resolver DOES run on desktop — whenever desktop failed.** A launcher registers its own
14
+ * adapter on `__jxPlatform` from a boot module that evaluates before this bundle, and this header
15
+ * used to conclude that the resolver never ran there. That held only while the boot module worked.
16
+ * Desktop 5.0.0 to 5.1.3 shipped an init bundle that threw on import, nothing registered, and this
17
+ * resolver answered with the dev-server adapter inside a `views://` window: every panel drew,
18
+ * "Create Project" said "Failed to fetch", and Browse did nothing at all. A half-working app is the
19
+ * worst failure a launcher can have, because it reads as a bug in whatever the user clicked last.
20
+ *
21
+ * So the fallback is now conditional on there being nobody it could be standing in for.
22
+ * {@link bootRefusal} is that decision, and {@link resolveDefaultPlatform} throws
23
+ * {@link PlatformUnavailableError} rather than guess; `studio.ts` catches it and draws the
24
+ * boot-failure screen (`surfaces/boot-failure.ts`) in place of the frame.
25
+ *
26
+ * @docs extending/embedding/platform-adapter
12
27
  */
28
+ import { BOOT_META } from "../hosting/document";
29
+ import { launcherSignal } from "../platform";
30
+ import type { LauncherSignal } from "../platform";
13
31
  import type { StudioPlatform } from "../types";
14
32
  import type { CloudProject } from "./cloud";
15
33
  import { createCloudPlatform } from "./cloud";
@@ -20,8 +38,127 @@ export interface CloudSignal {
20
38
  project: CloudProject | null;
21
39
  }
22
40
 
23
- /** Returns the cloud adapter when the shell signalled cloud, else the dev-server adapter. */
41
+ /**
42
+ * Why Studio will not fall back to the dev-server adapter, most specific evidence first.
43
+ *
44
+ * - `launcher` — a launcher's boot module announced itself (`globalThis.__jxLauncher`) and still no
45
+ * adapter was registered. The strongest evidence there is: the launcher ran, failed, and may have
46
+ * recorded why. The error is deliberately NOT copied here — the failure view reads
47
+ * `launcherSignal()?.error` when it renders, so an error recorded after this was decided still
48
+ * reaches the screen.
49
+ * - `declared` — the document carries {@link BOOT_META}, so its host declared a boot module, and
50
+ * nothing announced. The boot script never ran: a 404, a syntax error, a blocked load.
51
+ * - `scheme` — neither, but the page was opened from `views:` or `file:`. The dev-server adapter is
52
+ * relative `fetch`, `EventSource` and a WebSocket against the page's own origin, and those two
53
+ * schemes have no HTTP server behind them by construction. This covers a staged `index.html` old
54
+ * enough to predate the meta.
55
+ */
56
+ export type BootRefusal =
57
+ | { kind: "launcher"; launcher: string | undefined }
58
+ | { kind: "declared" }
59
+ | { kind: "scheme"; protocol: string };
60
+
61
+ /** The four facts {@link bootRefusal} decides from, read once so the rule itself stays pure. */
62
+ export interface BootEnvironment {
63
+ /** The launcher signal, when a launcher's boot module announced itself. */
64
+ launcher: LauncherSignal | undefined;
65
+ /** Whether the cloud shell published its `__jxCloud` signal. */
66
+ cloud: boolean;
67
+ /** Whether the document carries the {@link BOOT_META} marker. */
68
+ declared: boolean;
69
+ /** `location.protocol`, colon included. */
70
+ protocol: string;
71
+ }
72
+
73
+ /**
74
+ * Schemes the dev-server adapter can never work from.
75
+ *
76
+ * ONLY these two, and the list is short on purpose. The embedding docs promise that a host serving
77
+ * the HTTP protocol reuses the stock dev-server adapter unchanged, and a third-party shell may well
78
+ * serve it through a custom scheme — `tauri://localhost` proxies to a real server. Refusing every
79
+ * scheme that is not `http:` would break that host to cover a case the meta and the launcher signal
80
+ * already cover for every host this repository ships. `views:` is Electrobun's own bundle scheme
81
+ * and `file:` is a document opened from disk; neither can be proxying anything. happy-dom's
82
+ * `about:` is absent for the same reason, which is also what lets a test import the entry without
83
+ * inventing a URL.
84
+ */
85
+ const BACKENDLESS_SCHEMES: ReadonlySet<string> = new Set(["views:", "file:"]);
86
+
87
+ /** Read the boot environment from the live globals and document. */
88
+ export function bootEnvironment(): BootEnvironment {
89
+ const doc = (globalThis as { document?: Document }).document;
90
+ const meta = `meta[name="${BOOT_META.name}"][content="${BOOT_META.content}"]`;
91
+ return {
92
+ cloud: (globalThis as unknown as { __jxCloud?: CloudSignal }).__jxCloud != null,
93
+ declared: doc?.querySelector(meta) != null,
94
+ launcher: launcherSignal(),
95
+ protocol: (globalThis as { location?: Location }).location?.protocol ?? "",
96
+ };
97
+ }
98
+
99
+ /**
100
+ * Whether the dev-server fallback must be refused, and why — `null` when it may run.
101
+ *
102
+ * The order is the argument. A launcher signal beats the cloud signal because it is direct evidence
103
+ * of a launcher that failed, and a window is not both. The cloud signal beats the meta because the
104
+ * cloud shell declares a boot module too — `edit-init.js` IS its boot module — and publishing the
105
+ * signal is that module succeeding.
106
+ *
107
+ * @param env The environment to judge; the live one by default.
108
+ */
109
+ export function bootRefusal(env: BootEnvironment = bootEnvironment()): BootRefusal | null {
110
+ if (env.launcher) {
111
+ return { kind: "launcher", launcher: env.launcher.launcher };
112
+ }
113
+ if (env.cloud) {
114
+ return null;
115
+ }
116
+ if (env.declared) {
117
+ return { kind: "declared" };
118
+ }
119
+ if (BACKENDLESS_SCHEMES.has(env.protocol)) {
120
+ return { kind: "scheme", protocol: env.protocol };
121
+ }
122
+ return null;
123
+ }
124
+
125
+ /** The console's one-line account of a refusal; the screen says it at more length. */
126
+ function refusalMessage(refusal: BootRefusal): string {
127
+ switch (refusal.kind) {
128
+ case "launcher": {
129
+ return `Jx Studio's launcher (${refusal.launcher ?? "unnamed"}) announced itself but registered no platform adapter.`;
130
+ }
131
+ case "declared": {
132
+ return "Jx Studio's page declares a boot module, but nothing registered a platform adapter.";
133
+ }
134
+ default: {
135
+ return `Jx Studio was opened from a ${refusal.protocol} URL, which has no backend for the dev-server adapter.`;
136
+ }
137
+ }
138
+ }
139
+
140
+ /** Thrown by {@link resolveDefaultPlatform} when {@link bootRefusal} refuses the fallback. */
141
+ export class PlatformUnavailableError extends Error {
142
+ readonly refusal: BootRefusal;
143
+
144
+ constructor(refusal: BootRefusal) {
145
+ super(refusalMessage(refusal));
146
+ this.name = "PlatformUnavailableError";
147
+ this.refusal = refusal;
148
+ }
149
+ }
150
+
151
+ /**
152
+ * The cloud adapter when the shell signalled cloud, else the dev-server adapter.
153
+ *
154
+ * @throws {PlatformUnavailableError} When a launcher was declared or announced and registered
155
+ * nothing, or the page's scheme has no server behind it — see {@link bootRefusal}.
156
+ */
24
157
  export function resolveDefaultPlatform(): StudioPlatform {
158
+ const refusal = bootRefusal();
159
+ if (refusal) {
160
+ throw new PlatformUnavailableError(refusal);
161
+ }
25
162
  const cloud = (globalThis as unknown as { __jxCloud?: CloudSignal }).__jxCloud;
26
163
  return cloud ? createCloudPlatform(cloud.project) : createDevServerPlatform();
27
164
  }
@@ -320,7 +320,12 @@ export function createDevServerPlatform() {
320
320
  * undefined and the modal falls back to a typed Location field.
321
321
  *
322
322
  * The picked handle carries no filesystem path, so it is resolved by the id the picker tags the
323
- * folder with (see `@jxsuite/studio/directory-picker`).
323
+ * folder with (see `@jxsuite/studio/directory-picker`). Resolves `null` only when the user
324
+ * dismisses the chooser; every other failure rejects with a sentence the modal shows under
325
+ * Location. So `locate` keeps the helper's contract: `null` is "no folder holds this id" (the
326
+ * route's 200 `{ path: null }`), and a non-ok answer throws the problem the server sent rather
327
+ * than posing as a miss. A network failure (`TypeError: Failed to fetch`, e.g. a page that is
328
+ * not being served by the dev server at all) propagates as is, and the picker names it.
324
329
  */
325
330
  ...(canPickDirectory()
326
331
  ? {
@@ -330,9 +335,11 @@ export function createDevServerPlatform() {
330
335
  `/__studio/locate-directory?name=${encodeURIComponent(name)}&id=${encodeURIComponent(id)}`,
331
336
  );
332
337
  if (!res.ok) {
333
- return null;
338
+ // A proxy, or a `views://` host with no dev server behind it, may answer in HTML.
339
+ const body: unknown = await res.json().catch(() => null);
340
+ throw new Error(problemMessage(body, res.status));
334
341
  }
335
- const found = await readJson<{ path?: string }>(res);
342
+ const found = await readJson<{ path?: string | null }>(res);
336
343
  return found.path ?? null;
337
344
  });
338
345
  },
@@ -383,6 +390,19 @@ export function createDevServerPlatform() {
383
390
  return data.content;
384
391
  },
385
392
 
393
+ /**
394
+ * Raw bytes, undecoded. Same-origin here, so the response body is simply the file.
395
+ *
396
+ * @param {string} path
397
+ */
398
+ async readFileBytes(path: string) {
399
+ const res = await fetch(`/__studio/file/bytes?path=${encodeURIComponent(serverPath(path))}`);
400
+ if (!res.ok) {
401
+ throw new Error(`Failed to read file bytes: ${path}`);
402
+ }
403
+ return res.arrayBuffer();
404
+ },
405
+
386
406
  /**
387
407
  * @param {string} path
388
408
  * @param {string} content