@jxsuite/studio 5.3.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 (389) hide show
  1. package/data/css-meta.json +28 -27
  2. package/dist/chunks/{cf-account-picker-nwnwdm6p.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-4rzvmp0m.js → content-source-jt7rbcbc.js} +22 -22
  6. package/dist/chunks/{convert-to-repeater-jxsv62v7.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-2mrptyek.js → extension-sections-r73an5s6.js} +30 -29
  10. package/dist/chunks/{file-ops-knfqshrm.js → file-ops-3xan8mnj.js} +21 -21
  11. package/dist/chunks/{files-y6fr4he9.js → files-egsb0ry8.js} +28 -28
  12. package/dist/chunks/{format-host-1k4qrcqd.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-2rkrsjva.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-a7b87n2c.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-scpw01h8.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/layers-panel-83a16ytx.js +41 -0
  25. package/dist/chunks/{layers-jnvdbqva.js → layers-s6mc01ry.js} +3 -3
  26. package/dist/chunks/{media-picker-f66nav16.js → media-picker-nhn6rv4w.js} +11 -11
  27. package/dist/chunks/{menu-1dfhry4s.js → menu-8j4qxjrp.js} +4 -4
  28. package/dist/chunks/{monaco-binding-sw6en4dw.js → monaco-binding-e8d9wzyk.js} +3 -3
  29. package/dist/chunks/{monaco-buffer-pykwbm92.js → monaco-buffer-jaq1t9zj.js} +2 -2
  30. package/dist/chunks/{monaco-setup-8vbyqnhr.css → monaco-setup-ey9cjcm3.css} +954 -137
  31. package/dist/chunks/{monaco-setup-km6abc6q.js → monaco-setup-gsjdysze.js} +6 -6
  32. package/dist/chunks/{platform-ts9cg1a4.js → platform-fnchmhy7.js} +5 -3
  33. package/dist/chunks/publish-panel-ak6hxfma.js +27 -0
  34. package/dist/chunks/quick-search-c63mkt3b.js +47 -0
  35. package/dist/chunks/{references-h69xp9zz.js → references-f7bdv3z5.js} +3 -3
  36. package/dist/chunks/right-panel-t5n8wttg.js +45 -0
  37. package/dist/chunks/{studio-ze46gzjh.js → studio-01269reb.js} +7 -7
  38. package/dist/chunks/{studio-rnyf3f8e.js → studio-0a4gncx0.js} +3 -3
  39. package/dist/chunks/{studio-e1f24eff.js → studio-0d4p4wxj.js} +6778 -2993
  40. package/dist/chunks/{studio-4wbb9ct0.js → studio-1tejxvba.js} +3006 -1855
  41. package/dist/chunks/{studio-d1n2bykn.js → studio-3nprwbd9.js} +117 -25
  42. package/dist/chunks/{studio-4c041r8y.js → studio-40z39dme.js} +3 -3
  43. package/dist/chunks/{studio-151w0xnh.js → studio-482eeazk.js} +20 -20
  44. package/dist/chunks/{studio-cgprcdxn.js → studio-4998spet.js} +10 -9
  45. package/dist/chunks/{studio-nq5yf5mp.js → studio-557bmn0x.js} +2 -2
  46. package/dist/chunks/{studio-rtskkm43.js → studio-5w26wza5.js} +3 -3
  47. package/dist/chunks/{studio-6nzaktvy.js → studio-6pwn9j8c.js} +6 -3
  48. package/dist/chunks/{studio-wnsyffg1.js → studio-6vg0x5fz.js} +3 -2
  49. package/dist/chunks/{studio-vn5hske5.js → studio-7d3c1m59.js} +12 -5
  50. package/dist/chunks/{studio-6gryj0sg.js → studio-9vsvz7w7.js} +6 -6
  51. package/dist/chunks/{studio-50wfwy44.js → studio-9wm0xzpf.js} +19 -19
  52. package/dist/chunks/{studio-0j7r7s9g.js → studio-ag3z6shb.js} +10 -4
  53. package/dist/chunks/studio-aj9drheq.js +177 -0
  54. package/dist/chunks/{studio-xajk70w0.js → studio-apv9x70c.js} +189 -130
  55. package/dist/chunks/{studio-c04nzbby.js → studio-b9q3znz8.js} +2 -2
  56. package/dist/chunks/{studio-nfx354t1.js → studio-bh47w0cy.js} +9 -10
  57. package/dist/chunks/{studio-gk2twyh4.js → studio-dgdm6vvt.js} +264 -52
  58. package/dist/chunks/{studio-gtfq1dkz.js → studio-dwdcr2e8.js} +4 -4
  59. package/dist/chunks/{studio-nqgawfks.js → studio-fbcyz234.js} +146 -139
  60. package/dist/chunks/{studio-27q131j3.js → studio-fqpkpq1t.js} +7 -4
  61. package/dist/chunks/{studio-c0qgc3tn.js → studio-fy320dfc.js} +62 -68
  62. package/dist/chunks/{studio-zjjde71a.js → studio-gjj4bbfx.js} +2 -2
  63. package/dist/chunks/{studio-g33v4fe9.js → studio-jfhxy8h3.js} +7 -7
  64. package/dist/chunks/{studio-ab87phj1.js → studio-kwewmhhq.js} +4 -4
  65. package/dist/chunks/{studio-7rrb1esn.js → studio-m58xn8at.js} +25 -24
  66. package/dist/chunks/{studio-fzjx96t2.js → studio-mfkh1q8d.js} +17 -17
  67. package/dist/chunks/{studio-drg8qj9k.js → studio-mrh0a92f.js} +17 -17
  68. package/dist/chunks/{studio-jt27ta6x.js → studio-mt79tn56.js} +3 -3
  69. package/dist/chunks/{studio-b66rsh78.js → studio-ne593z1j.js} +38 -31
  70. package/dist/chunks/{studio-h4nvnjvz.js → studio-ptfcxkz5.js} +418 -232
  71. package/dist/chunks/{studio-dd1qqkry.js → studio-qxye31h7.js} +7 -7
  72. package/dist/chunks/{studio-ye9drbk8.js → studio-rav5bpmp.js} +73 -52
  73. package/dist/chunks/{studio-6kvgrkrp.js → studio-rwmmbrth.js} +6 -6
  74. package/dist/chunks/{studio-b7xacrwe.js → studio-sceg8ws0.js} +8 -8
  75. package/dist/chunks/{studio-msc1ee1m.js → studio-sjas5b3a.js} +19 -13
  76. package/dist/chunks/{studio-a65w20ex.js → studio-vd67b66d.js} +3 -3
  77. package/dist/chunks/{studio-7144epdr.js → studio-w475cbf9.js} +10 -10
  78. package/dist/chunks/{studio-4f3psrk1.js → studio-w7gpcvfj.js} +9 -9
  79. package/dist/chunks/{studio-nsgw7dbk.js → studio-xrbfdxgk.js} +7 -7
  80. package/dist/chunks/{studio-ke09tn9k.js → studio-y49b6mjv.js} +35 -35
  81. package/dist/chunks/{studio-j0w60gft.js → studio-ygvr9e4q.js} +10 -10
  82. package/dist/chunks/{studio-z2smx5ce.js → studio-yxmmjy2a.js} +9 -6
  83. package/dist/chunks/{tsMode-jy4m86rv.js → tsMode-ay4c78ne.js} +4 -4
  84. package/dist/chunks/{tsMode-hnxxb73h.css → tsMode-c72wyz27.css} +954 -137
  85. package/dist/chunks/{workspace-np10mayv.js → workspace-b9k2frks.js} +5 -5
  86. package/dist/chunks/{ws-client-ny4zc51r.js → ws-client-erze4czt.js} +356 -20
  87. package/dist/codicon.ttf +0 -0
  88. package/dist/iframe-entry.js +18 -16
  89. package/dist/studio.css +956 -139
  90. package/dist/studio.js +1453 -830
  91. package/dist/workers/editor.worker.js +196 -107
  92. package/dist/workers/json.worker.js +196 -107
  93. package/dist/workers/ts.worker.js +196 -107
  94. package/package.json +15 -14
  95. package/src/about/about-modal.ts +4 -3
  96. package/src/browse/library-commands.ts +15 -13
  97. package/src/browse/library-pane.ts +2 -2
  98. package/src/canvas/canvas-render.ts +97 -83
  99. package/src/canvas/canvas-surface.ts +2 -2
  100. package/src/canvas/canvas-utils.ts +42 -32
  101. package/src/canvas/diff-toolbar.ts +21 -2
  102. package/src/canvas/diff-view.ts +30 -6
  103. package/src/canvas/iframe-entry.ts +2 -2
  104. package/src/canvas/iframe-host.ts +5 -5
  105. package/src/canvas/iframe-interaction.ts +7 -6
  106. package/src/canvas/iframe-overlay.ts +1 -1
  107. package/src/canvas/iframe-protocol.ts +6 -5
  108. package/src/canvas/iframe-render.ts +3 -3
  109. package/src/canvas/popover-state.ts +1 -1
  110. package/src/canvas/stage-scrollbar.ts +112 -0
  111. package/src/canvas/surface-registry.ts +9 -2
  112. package/src/collab/collab-commands.ts +8 -6
  113. package/src/collab/collab-session.ts +4 -4
  114. package/src/collab/collab-state.ts +4 -4
  115. package/src/collab/presence-chips.ts +3 -3
  116. package/src/commands/app-commands.ts +7 -7
  117. package/src/commands/budget.ts +17 -16
  118. package/src/commands/command-args.ts +10 -9
  119. package/src/commands/context.ts +20 -10
  120. package/src/commands/defaults.ts +30 -29
  121. package/src/commands/levels.ts +8 -6
  122. package/src/commands/live-context.ts +12 -10
  123. package/src/commands/reference.ts +5 -5
  124. package/src/commands/registry.ts +2 -2
  125. package/src/content/draft-state.ts +9 -8
  126. package/src/content/entry-commands.ts +16 -14
  127. package/src/content/entry-editor.ts +2 -2
  128. package/src/content/entry-fields.ts +1 -1
  129. package/src/editor/context-menu.ts +4 -4
  130. package/src/editor/convert-to-component.ts +6 -5
  131. package/src/editor/inline-edit.ts +3 -3
  132. package/src/editor/shortcuts.ts +88 -20
  133. package/src/editor/slash-menu.ts +11 -10
  134. package/src/files/elements.ts +4 -5
  135. package/src/files/file-ops.ts +1 -1
  136. package/src/files/files.ts +21 -17
  137. package/src/files/serialize-document.ts +1 -1
  138. package/src/format/convert-file.ts +5 -5
  139. package/src/github/github-auth-status.ts +107 -0
  140. package/src/github/github-auth.ts +18 -82
  141. package/src/github/github-publish.ts +7 -6
  142. package/src/grid/cell-editors.ts +8 -7
  143. package/src/grid/grid-layout.ts +1 -1
  144. package/src/grid/grid-open.ts +6 -5
  145. package/src/grid/grid-panel.ts +13 -12
  146. package/src/grid/redirects-grid.ts +14 -11
  147. package/src/grid/redirects.ts +10 -5
  148. package/src/grid/sources/content-source.ts +10 -9
  149. package/src/hosting/document.ts +28 -1
  150. package/src/hosting/stage.ts +15 -4
  151. package/src/i18n/i18n-commands.ts +9 -7
  152. package/src/new-project/location-fields.ts +16 -3
  153. package/src/new-project/new-project-modal.ts +23 -7
  154. package/src/page-params.ts +6 -6
  155. package/src/panels/activity-panel.ts +11 -10
  156. package/src/panels/ai-chat/chat-view.ts +27 -14
  157. package/src/panels/ai-chat/composer.ts +10 -3
  158. package/src/panels/ai-panel.ts +87 -29
  159. package/src/panels/block-action-bar.ts +59 -33
  160. package/src/panels/bottom-dock.ts +22 -22
  161. package/src/panels/data-explorer.ts +9 -9
  162. package/src/panels/data-grid.ts +1 -1
  163. package/src/panels/editors.ts +8 -8
  164. package/src/panels/elements-panel.ts +7 -8
  165. package/src/panels/empty-state.ts +1 -1
  166. package/src/panels/events-panel.ts +13 -12
  167. package/src/panels/formula-workspace.ts +8 -5
  168. package/src/panels/frontmatter-fields.ts +4 -1
  169. package/src/panels/frontmatter-panel.ts +18 -18
  170. package/src/panels/git-panel.ts +19 -13
  171. package/src/panels/head-panel.ts +9 -10
  172. package/src/panels/i18n-panel.ts +6 -4
  173. package/src/panels/imports-panel.ts +1 -1
  174. package/src/panels/jump-bar.ts +20 -18
  175. package/src/panels/layers-panel.ts +19 -17
  176. package/src/panels/left-panel.ts +3 -3
  177. package/src/panels/navigator-panels.ts +8 -8
  178. package/src/panels/overlays.ts +1 -1
  179. package/src/panels/pane-context.ts +56 -28
  180. package/src/panels/pane-grid.ts +9 -8
  181. package/src/panels/panel-registry.ts +47 -19
  182. package/src/panels/problems-panel.ts +11 -12
  183. package/src/panels/properties-panel.ts +21 -20
  184. package/src/panels/provenance.ts +3 -3
  185. package/src/panels/quick-search.ts +15 -11
  186. package/src/panels/right-panel.ts +4 -3
  187. package/src/panels/seo-modal.ts +5 -4
  188. package/src/panels/settings-menu.ts +9 -9
  189. package/src/panels/settings-pane.ts +3 -3
  190. package/src/panels/signals-panel.ts +16 -9
  191. package/src/panels/statement-editor.ts +12 -9
  192. package/src/panels/style-panel.ts +45 -41
  193. package/src/panels/stylebook-panel.ts +0 -1
  194. package/src/panels/tab-strip.ts +26 -23
  195. package/src/platform-errors.ts +2 -2
  196. package/src/platform.ts +44 -4
  197. package/src/platforms/cloud.ts +11 -1
  198. package/src/platforms/default-platform.ts +141 -4
  199. package/src/platforms/devserver.ts +23 -3
  200. package/src/publish/deploy-checklist.ts +11 -10
  201. package/src/publish/publish-commands.ts +9 -8
  202. package/src/services/a11y-report.ts +1 -1
  203. package/src/services/ai-ask.ts +39 -16
  204. package/src/services/ai-command-tools.ts +28 -14
  205. package/src/services/ai-import-tools.ts +43 -29
  206. package/src/services/ai-project-tools.ts +20 -38
  207. package/src/services/ai-settings.ts +55 -2
  208. package/src/services/ai-tools.ts +69 -58
  209. package/src/services/ai-write-report.ts +4 -4
  210. package/src/services/ai-writes.ts +74 -55
  211. package/src/services/automation.ts +12 -12
  212. package/src/services/clock.ts +2 -1
  213. package/src/services/connection.ts +2 -2
  214. package/src/services/context-manager.ts +69 -6
  215. package/src/services/directory-picker.ts +93 -27
  216. package/src/services/document-assistant.ts +86 -17
  217. package/src/services/gated-registry.ts +4 -3
  218. package/src/services/idle.ts +5 -4
  219. package/src/services/live-surfaces.ts +2 -1
  220. package/src/services/notify.ts +9 -7
  221. package/src/services/popover-report.ts +3 -3
  222. package/src/services/preview-eval.ts +1 -1
  223. package/src/services/project-adoption.ts +34 -1
  224. package/src/services/project-path.ts +53 -0
  225. package/src/services/settings/kernel.ts +13 -2
  226. package/src/services/tool-executor.ts +135 -30
  227. package/src/services/tool-outcomes.ts +143 -0
  228. package/src/settings/contexts-section.ts +8 -8
  229. package/src/settings/contributed-section.ts +6 -6
  230. package/src/settings/css-vars-editor.ts +6 -5
  231. package/src/settings/extension-commands.ts +19 -18
  232. package/src/settings/extension-sections.ts +7 -7
  233. package/src/settings/extensions-section.ts +3 -3
  234. package/src/settings/general-settings.ts +21 -18
  235. package/src/settings/preferences-accounts.ts +3 -3
  236. package/src/settings/preferences-dialog.ts +9 -8
  237. package/src/settings/preferences-sections.ts +1 -1
  238. package/src/settings/project-sections.ts +2 -2
  239. package/src/settings/section-registry.ts +7 -6
  240. package/src/settings/settings-document.ts +41 -43
  241. package/src/shell/tree.ts +29 -0
  242. package/src/shell.ts +63 -55
  243. package/src/site-context.ts +1 -1
  244. package/src/store.ts +2 -2
  245. package/src/studio.ts +46 -28
  246. package/src/style/live-preview.ts +8 -8
  247. package/src/style/project-styles.ts +8 -9
  248. package/src/style/token-ref.ts +2 -2
  249. package/src/surfaces/about.json +6 -2
  250. package/src/surfaces/add-repo.json +2 -2
  251. package/src/surfaces/add-repo.ts +2 -2
  252. package/src/surfaces/ai-chat.json +14 -6
  253. package/src/surfaces/ai-chat.ts +4 -2
  254. package/src/surfaces/ai-credentials-form.json +53 -49
  255. package/src/surfaces/ai-credentials-form.ts +18 -4
  256. package/src/surfaces/ai-managed-connect.json +4 -4
  257. package/src/surfaces/block-action-bar.json +4 -4
  258. package/src/surfaces/block-action-bar.ts +3 -3
  259. package/src/surfaces/boot-failure.json +228 -0
  260. package/src/surfaces/boot-failure.ts +156 -0
  261. package/src/surfaces/bottom-dock.json +1 -1
  262. package/src/surfaces/canvas-stage.json +9 -40
  263. package/src/surfaces/canvas-stage.ts +9 -9
  264. package/src/surfaces/cf-account-picker.json +11 -8
  265. package/src/surfaces/commandbar.ts +11 -10
  266. package/src/surfaces/convert-repeater.ts +3 -2
  267. package/src/surfaces/dialog.json +7 -1
  268. package/src/surfaces/dialog.ts +2 -2
  269. package/src/surfaces/doc-header.json +6 -13
  270. package/src/surfaces/doc-header.ts +4 -5
  271. package/src/surfaces/entry-editor.json +1 -1
  272. package/src/surfaces/entry-editor.ts +2 -1
  273. package/src/surfaces/expression-editor.json +5 -5
  274. package/src/surfaces/files-panel.json +4 -3
  275. package/src/surfaces/files-panel.ts +1 -1
  276. package/src/surfaces/formula-palette.json +2 -2
  277. package/src/surfaces/formula-palette.ts +1 -1
  278. package/src/surfaces/git-panel.json +167 -109
  279. package/src/surfaces/git-panel.ts +2 -2
  280. package/src/surfaces/github-auth.json +2 -1
  281. package/src/surfaces/github-publish.ts +3 -3
  282. package/src/surfaces/grid-open.json +1 -1
  283. package/src/surfaces/grid-panel.json +2 -2
  284. package/src/surfaces/grid-views.json +1 -1
  285. package/src/surfaces/inspector-dock.json +2 -2
  286. package/src/surfaces/jump-bar.json +1 -1
  287. package/src/surfaces/jump-bar.ts +1 -1
  288. package/src/surfaces/library-pane.ts +2 -1
  289. package/src/surfaces/logic-panel.json +7 -7
  290. package/src/surfaces/logic-panel.ts +6 -4
  291. package/src/surfaces/logic-workspace.json +2 -2
  292. package/src/surfaces/logic-workspace.ts +2 -2
  293. package/src/surfaces/media-browser.ts +3 -3
  294. package/src/surfaces/media-pane.ts +2 -1
  295. package/src/surfaces/menu.ts +5 -5
  296. package/src/surfaces/new-project.json +1 -1
  297. package/src/surfaces/pane-context.json +86 -57
  298. package/src/surfaces/pane-context.ts +2 -1
  299. package/src/surfaces/pane-grid.json +3 -3
  300. package/src/surfaces/panel-activity.json +2 -1
  301. package/src/surfaces/panel-deploy-checklist.json +1 -1
  302. package/src/surfaces/panel-elements.ts +2 -1
  303. package/src/surfaces/panel-i18n.ts +2 -1
  304. package/src/surfaces/panel-outline.json +1 -1
  305. package/src/surfaces/panel-outline.ts +4 -3
  306. package/src/surfaces/panel-page.json +2 -2
  307. package/src/surfaces/panel-page.ts +4 -1
  308. package/src/surfaces/panel-problems.json +4 -3
  309. package/src/surfaces/panel-problems.ts +2 -2
  310. package/src/surfaces/panel-signals.json +23 -8
  311. package/src/surfaces/panel-signals.ts +5 -1
  312. package/src/surfaces/panel-stylebook-layers.json +1 -1
  313. package/src/surfaces/panel-stylebook-layers.ts +2 -2
  314. package/src/surfaces/preferences.json +21 -8
  315. package/src/surfaces/preferences.ts +9 -1
  316. package/src/surfaces/properties-panel.json +4 -4
  317. package/src/surfaces/properties-panel.ts +2 -2
  318. package/src/surfaces/publish.json +1 -1
  319. package/src/surfaces/publish.ts +2 -1
  320. package/src/surfaces/push-plan.json +4 -3
  321. package/src/surfaces/rail.json +2 -2
  322. package/src/surfaces/rail.ts +10 -4
  323. package/src/surfaces/reference-field.json +1 -1
  324. package/src/surfaces/schema-form.json +2 -2
  325. package/src/surfaces/schema-form.ts +1 -1
  326. package/src/surfaces/seo.json +1 -1
  327. package/src/surfaces/seo.ts +2 -2
  328. package/src/surfaces/settings-contributed.ts +2 -1
  329. package/src/surfaces/settings-css-vars.json +4 -4
  330. package/src/surfaces/settings-deploy.json +4 -4
  331. package/src/surfaces/settings-deploy.ts +2 -1
  332. package/src/surfaces/settings-extensions.json +6 -6
  333. package/src/surfaces/settings-extensions.ts +6 -2
  334. package/src/surfaces/settings-locales.json +4 -4
  335. package/src/surfaces/settings-overview.ts +2 -1
  336. package/src/surfaces/settings-pane.ts +3 -2
  337. package/src/surfaces/settings-rawjson.ts +1 -1
  338. package/src/surfaces/slash-menu.ts +4 -4
  339. package/src/surfaces/statements.json +3 -3
  340. package/src/surfaces/statusbar.ts +23 -23
  341. package/src/surfaces/style-panel.json +9 -5
  342. package/src/surfaces/style-panel.ts +3 -3
  343. package/src/surfaces/tab-strip.json +2 -2
  344. package/src/surfaces/target-line.json +1 -1
  345. package/src/surfaces/target-line.ts +12 -10
  346. package/src/surfaces/welcome.json +7 -2
  347. package/src/tabs/doc-op-apply.ts +5 -5
  348. package/src/tabs/patch-ops.ts +3 -3
  349. package/src/tabs/project-config.ts +10 -10
  350. package/src/tabs/selection.ts +1 -1
  351. package/src/tabs/tab.ts +13 -14
  352. package/src/tabs/transact.ts +18 -24
  353. package/src/types.ts +27 -3
  354. package/src/ui/ai-credentials-form.ts +74 -16
  355. package/src/ui/ai-managed-connect.ts +68 -19
  356. package/src/ui/color-selector.ts +1 -1
  357. package/src/ui/expression-editor.ts +3 -3
  358. package/src/ui/form-controls.ts +6 -5
  359. package/src/ui/formula-catalog.ts +4 -4
  360. package/src/ui/formula-chips.ts +2 -2
  361. package/src/ui/layers.ts +20 -18
  362. package/src/ui/panel-resize.ts +5 -5
  363. package/src/ui/popover-surface.ts +2 -2
  364. package/src/ui/progress-modal.ts +3 -3
  365. package/src/ui/regions.ts +14 -9
  366. package/src/ui/schema-form.ts +10 -9
  367. package/src/ui/timing.ts +9 -7
  368. package/src/ui/value-source.ts +4 -4
  369. package/src/ui/virtual-window.ts +1 -1
  370. package/src/utils/base64.ts +20 -1
  371. package/src/utils/canvas-media.ts +1 -1
  372. package/src/utils/studio-utils.ts +0 -31
  373. package/src/view.ts +4 -3
  374. package/src/workspace/pane-derive.ts +14 -13
  375. package/src/workspace/session.ts +7 -6
  376. package/src/workspace/workspace.ts +49 -39
  377. package/styles/canvas.css +1 -27
  378. package/styles/inspector.css +4 -3
  379. package/styles/overlays.css +1 -1
  380. package/styles/tokens.css +17 -1
  381. package/styles/tokens.json +6 -1
  382. package/dist/chunks/entry-commands-1wnsqn5d.js +0 -42
  383. package/dist/chunks/layers-panel-73gfkcga.js +0 -41
  384. package/dist/chunks/publish-panel-z1zd2da1.js +0 -27
  385. package/dist/chunks/quick-search-zdv7h8ht.js +0 -47
  386. package/dist/chunks/right-panel-889vbnm5.js +0 -45
  387. package/dist/chunks/studio-727xh0e3.js +0 -76
  388. package/src/files/json-layout.ts +0 -474
  389. package/src/services/ai-turn-signal.ts +0 -70
@@ -46,6 +46,13 @@
46
46
  * on the desktop it is in the launcher's `0600` credential store and this module holds it only in
47
47
  * memory for the session. `settings/preferences-accounts.ts` lists it as _stored or not_ and is the
48
48
  * only place it can be revoked (`studio.md` §15 rule 1).
49
+ *
50
+ * The credential STATE (whether one exists, where, how to clear it) lives in
51
+ * `github-auth-status.ts` and is re-exported here. This module keeps only the sign-in FLOW, which
52
+ * is the half that needs `notify` and the device-flow dialog surface — a launcher's PAL-init bundle
53
+ * wants the state without ever wanting the flow, and importing this whole file for
54
+ * `hydrateGithubToken` alone used to cost it a second copy of Lit's runtime
55
+ * (`github-auth-status.ts`'s own header has the full account).
49
56
  */
50
57
 
51
58
  import { errorMessage } from "@jxsuite/schema/parse";
@@ -53,74 +60,18 @@ import { notify } from "../services/notify";
53
60
  import { layerHost } from "../ui/layers";
54
61
  import { openGithubAuthSurface } from "../surfaces/github-auth";
55
62
  import type { GithubAuthSurfaceHandle } from "../surfaces/github-auth";
63
+ import { getGithubToken, nativeAuth, setNativeToken, STORAGE_KEY } from "./github-auth-status";
56
64
 
57
- const CLIENT_ID = "Ov23liYVlMFpgjOEPXJH";
58
- const STORAGE_KEY = "jx_github_token";
59
-
60
- /**
61
- * The desktop launcher's RFC 8252 sign-in, when this build is running inside one.
62
- *
63
- * Launcher-only, like `updater` and `windowControls`, and reached the same way — a loopback
64
- * redirect needs a loopback server, which a page in a browser does not have.
65
- */
66
- interface NativeGithubAuth {
67
- signIn: (force?: boolean) => Promise<{ token: string }>;
68
- signOut: () => Promise<{ ok: boolean }>;
69
- status: () => Promise<{ stored: boolean }>;
70
- }
71
-
72
- function nativeAuth(): NativeGithubAuth | null {
73
- return (
74
- (globalThis as unknown as { __jxPlatform?: { githubAuth?: NativeGithubAuth } }).__jxPlatform
75
- ?.githubAuth ?? null
76
- );
77
- }
78
-
79
- /**
80
- * The token the desktop store handed us this session.
81
- *
82
- * On the desktop the token at rest is a `0600` file in the user's config directory, **not**
83
- * `localStorage` — so it is not readable by script in the webview between sessions, and it does not
84
- * survive in a place a stray extension or a copied profile can reach. Holding it in a module
85
- * variable is what keeps {@link getGithubToken} synchronous for its callers.
86
- */
87
- let nativeToken: string | null = null;
88
-
89
- /** Whether the desktop store holds a token, as of the last time it was asked. */
90
- let nativeStored = false;
91
-
92
- /**
93
- * Record that the desktop's credential store does (or does not) hold a token.
94
- *
95
- * The launcher calls this once at startup. It carries a **boolean, not the token**: the accounts
96
- * pane only needs to know whether one exists, and handing the webview a credential it has not been
97
- * asked to use is the exposure the 0600 store exists to remove. When a sign-in does happen the
98
- * token arrives through {@link authenticateGithub} and lives in memory for that session.
99
- *
100
- * @param {boolean} stored
101
- */
102
- export function hydrateGithubToken(stored: boolean): void {
103
- nativeStored = stored;
104
- }
105
-
106
- /**
107
- * Whether a GitHub credential exists, wherever it lives.
108
- *
109
- * Separate from {@link getGithubToken} because the two questions genuinely differ on the desktop: a
110
- * token can be stored on disk and not yet in this session's memory, and the accounts pane asks
111
- * "signed in?" while rendering — synchronously — where an API caller asks for the credential itself
112
- * and can await it.
113
- *
114
- * @returns {boolean}
115
- */
116
- export function githubTokenStored(): boolean {
117
- return nativeToken !== null || nativeStored || localStorage.getItem(STORAGE_KEY) !== null;
118
- }
65
+ // Every other caller in Studio still reaches these through this module.
66
+ export {
67
+ clearGithubToken,
68
+ getGithubToken,
69
+ githubTokenLocation,
70
+ githubTokenStored,
71
+ hydrateGithubToken,
72
+ } from "./github-auth-status";
119
73
 
120
- /** Where the credential this build would use actually lives, for text that says so. */
121
- export function githubTokenLocation(): "desktop" | "browser" {
122
- return nativeAuth() ? "desktop" : "browser";
123
- }
74
+ const CLIENT_ID = "Ov23liYVlMFpgjOEPXJH";
124
75
 
125
76
  /** One key for the whole sign-in, so a second attempt REPLACES the first report. */
126
77
  const NOTIFY_KEY = "github.auth";
@@ -156,21 +107,6 @@ interface TokenResponse {
156
107
  error?: string;
157
108
  }
158
109
 
159
- export function getGithubToken() {
160
- return nativeToken ?? localStorage.getItem(STORAGE_KEY);
161
- }
162
-
163
- export function clearGithubToken() {
164
- nativeToken = null;
165
- nativeStored = false;
166
- localStorage.removeItem(STORAGE_KEY);
167
- // Fire-and-forget: the accounts pane's revoke is synchronous, and a store that failed to forget
168
- // The token is not something the user can act on from there.
169
- void nativeAuth()
170
- ?.signOut()
171
- .catch(() => {});
172
- }
173
-
174
110
  /** The request never completed — a Problem, with the one sentence that covers both causes. */
175
111
  function reportUnreachable(error: unknown): void {
176
112
  notify.error("Could not reach GitHub to sign in.", {
@@ -241,7 +177,7 @@ export async function authenticateGithub() {
241
177
  if (native) {
242
178
  try {
243
179
  const { token } = await native.signIn();
244
- nativeToken = token;
180
+ setNativeToken(token);
245
181
  return token;
246
182
  } catch (error) {
247
183
  reportRefused("GitHub sign-in was not completed.", errorMessage(error));
@@ -3,18 +3,19 @@
3
3
  * Create a GitHub repository for a local project, add it as `origin`, and push.
4
4
  *
5
5
  * **This is not publishing, and it used to say it was.** Two unrelated operations shared the verb
6
- * (plan §9.5): this one creates a git repository, and `publish/publish-panel.ts` connects a hosting
7
- * provider that then builds the site. A user who wanted the second and found the first got a GitHub
8
- * repository and no site, and a user who wanted the first read "Publish to GitHub" and reasonably
9
- * expected a URL. The function, the dialog headline, the activity title and the notification source
10
- * now all say _Create GitHub Repository_; `Publish:` belongs to `publish/publish-commands.ts`.
6
+ * (`docs/studio/publish.md`): this one creates a git repository, and `publish/publish-panel.ts`
7
+ * connects a hosting provider that then builds the site. A user who wanted the second and found the
8
+ * first got a GitHub repository and no site, and a user who wanted the first read "Publish to
9
+ * GitHub" and reasonably expected a URL. The function, the dialog headline, the activity title and
10
+ * the notification source now all say _Create GitHub Repository_; `Publish:` belongs to
11
+ * `publish/publish-commands.ts`.
11
12
  *
12
13
  * **It is an Activity, not three toasts.** Three `notify.info` lines sharing one key replaced each
13
14
  * other in a corner while a multi-second, three-request, partially-committing operation ran with no
14
15
  * log and no cancel — and its middle step (`gitAddRemote`) had no error path at all, so a remote
15
16
  * that already existed failed the push with a message about pushing. `beginActivity` gives it the
16
17
  * ordered steps, the captured log, and `fail()`, which raises the Problem the failure needs and
17
- * carries the log as its detail (§7.3). The caller therefore never also notifies.
18
+ * carries the log as its detail (§16.4). The caller therefore never also notifies.
18
19
  *
19
20
  * **The dialog is a document.** `surfaces/github-publish.json` collects the name, the description
20
21
  * and the visibility; this module keeps the token, the requests and the activity. What it used to
@@ -40,13 +40,14 @@
40
40
  * registry keyed to nodes the engine has already thrown away — and would repaint asynchronously
41
41
  * while the reader scrolls.
42
42
  *
43
- * So the boundary is the cell, and it is the same boundary §9.4 already draws around Tabulator: the
44
- * engine is an island, and this file is the inside of it. The two kinds whose picker is bigger than
45
- * a cell escaped it by leaving the cell entirely, and that is the shape any future conversion has
46
- * to take — a document ANCHORED at the cell, not mounted in it. What would actually unblock the
47
- * rest is a synchronous mount path in the runtime (a document rendered into a detached host with
48
- * the kit already registered, no awaits), at which point 1 and 2 dissolve and only the formatter's
49
- * lifetime problem is left, which needs a teardown hook Tabulator does not have.
43
+ * So the boundary is the cell, and it is the same boundary studio-ui-guidelines.md §9.4 already
44
+ * draws around Tabulator: the engine is an island, and this file is the inside of it. The two kinds
45
+ * whose picker is bigger than a cell escaped it by leaving the cell entirely, and that is the shape
46
+ * any future conversion has to take — a document ANCHORED at the cell, not mounted in it. What
47
+ * would actually unblock the rest is a synchronous mount path in the runtime (a document rendered
48
+ * into a detached host with the kit already registered, no awaits), at which point 1 and 2 dissolve
49
+ * and only the formatter's lifetime problem is left, which needs a teardown hook Tabulator does not
50
+ * have.
50
51
  */
51
52
  import { html, render } from "lit-html";
52
53
  /* The two cell editors below are re-rendered while they are open — the pill editor calls its own
@@ -5,7 +5,7 @@
5
5
  * the grid shipped; a saved view is the same record with three more facets and a name, written to
6
6
  * the same key. The alternative — a second `jx-grid-views:` store beside this one — is how a grid
7
7
  * ends up with a column order that a view cannot capture and a view that a resize does not update,
8
- * which is the class of bug plan §12 P7.2 asks this item to avoid by construction.
8
+ * which is the class of bug saved views (site-architecture.md §7.2) must avoid by construction.
9
9
  *
10
10
  * **The working layout is the truth; a view is a copy of it.** Every control writes the working
11
11
  * layout (`saveGridLayout`), so a grid remembers how you left it whether or not you ever name a
@@ -250,8 +250,8 @@ export async function openGridSourcePicker(): Promise<void> {
250
250
  * `collection.editInGrid` used to be a press on the file tree's context-menu row, matched by the
251
251
  * item's rendered label ("Edit Collection in Grid") through an XPath — so the shot that used it
252
252
  * also had to open the menu first, and renaming the row broke both steps. Naming the COLLECTION
253
- * instead of the control is what plan §13's R1 asks for: the menu row and the command are the same
254
- * action, and only one of them is addressable.
253
+ * instead of the control is what the shot contract's R1 (`scripts/screenshots/README.md`) asks for:
254
+ * the menu row and the command are the same action, and only one of them is addressable.
255
255
  *
256
256
  * Both refuse a source the project does not declare. `data.openGrid` is additionally gated on the
257
257
  * platform serving the data routes (`capability.dataRows`), so on a build without them the command
@@ -272,8 +272,8 @@ export function gridCommands(): AnyCommand[] {
272
272
  group: "5_data",
273
273
  requires: "a project that declares content collections",
274
274
  when: (ctx) => ctx.project.open,
275
- /* No `aiTool`, by §12.4's first deletion rule: this opens a surface for a person; the grid is
276
- not a tool surface. */
275
+ /* No `aiTool`, by studio-ui-guidelines.md §12.4's first deletion rule: this opens a surface
276
+ for a person; the grid is not a tool surface. */
277
277
  run: (_commandCtx, args) => {
278
278
  const name = stringArg("collection.editInGrid", args, "name");
279
279
  const declared = collectionDirs().map((c) => c.name);
@@ -308,7 +308,8 @@ export function gridCommands(): AnyCommand[] {
308
308
  requires: "a platform that serves the data routes",
309
309
  when: (ctx) => ctx.project.open,
310
310
  enablement: (ctx) => ctx.capability.dataRows,
311
- /* No `aiTool`, by §12.4's first deletion rule: this opens a surface for a person. */
311
+ /* No `aiTool`, by studio-ui-guidelines.md §12.4's first deletion rule: this opens a surface
312
+ for a person. */
312
313
  run: (_commandCtx, args) => {
313
314
  const table = stringArg("data.openGrid", args, "table");
314
315
  openConnectorGrid(optionalStringArg("data.openGrid", args, "connection"), table);
@@ -24,13 +24,13 @@
24
24
  * palette, and the host node arriving all reach the surface through
25
25
  * {@link ActiveGridPanel.bump}.
26
26
  *
27
- * **The panel owns the view state** (plan §12 P7.2). Saved views persist through `grid-layout.ts` —
28
- * one store, the grid id as its key, so "per collection" needs no code — and this module is what
29
- * drives them onto the four surfaces they touch: the engine (column order, width and visibility,
30
- * applied by a REBUILD, which is cheap because the data never leaves the controller), the
31
- * controller (sort and grouping, which are row order and belong to the data), and the surface's own
32
- * filter box. Applying a view is therefore one function with one order of operations, not five
33
- * controls that each half-remember what the others did.
27
+ * **The panel owns the view state** (saved views, site-architecture.md §7.2). Saved views persist
28
+ * through `grid-layout.ts` — one store, the grid id as its key, so "per collection" needs no code —
29
+ * and this module is what drives them onto the four surfaces they touch: the engine (column order,
30
+ * width and visibility, applied by a REBUILD, which is cheap because the data never leaves the
31
+ * controller), the controller (sort and grouping, which are row order and belong to the data), and
32
+ * the surface's own filter box. Applying a view is therefore one function with one order of
33
+ * operations, not five controls that each half-remember what the others did.
34
34
  */
35
35
  import { effect, effectScope, reactive } from "../reactivity";
36
36
  import { showConfirmDialog, showPromptDialog } from "../ui/layers";
@@ -259,9 +259,9 @@ function projectViews(controller: GridController, at: { x: number; y: number }):
259
259
  * The View panel: saved views, then the four facets one of them is made of.
260
260
  *
261
261
  * One control rather than four, because the chrome budget is a cap on named things in the toolbar
262
- * (plan §2, principle 9) and these four are only ever adjusted together. Each edit writes the
263
- * working layout and applies immediately — there is no Apply button, so there is no state in which
264
- * the panel shows something the grid is not already doing.
262
+ * (studio-ui-guidelines.md §12.2) and these four are only ever adjusted together. Each edit writes
263
+ * the working layout and applies immediately — there is no Apply button, so there is no state in
264
+ * which the panel shows something the grid is not already doing.
265
265
  */
266
266
  function openViewPopover(
267
267
  controller: GridController,
@@ -727,7 +727,8 @@ export function gridViewCommands(): AnyCommand[] {
727
727
  requires: "a grid on screen",
728
728
  when: (ctx) => ctx.project.open,
729
729
  enablement: () => activeGridSurface() !== null,
730
- /* No `aiTool`, by §12.4's second deletion rule: it prompts for a name. */
730
+ /* No `aiTool`, by studio-ui-guidelines.md §12.4's second deletion rule: it prompts for a
731
+ name. */
731
732
  run: async () => {
732
733
  const { controller, panel } = requireSurface("grid.saveView");
733
734
  await promptSaveView(controller, panel);
@@ -744,7 +745,7 @@ export function gridViewCommands(): AnyCommand[] {
744
745
  requires: "a grid on screen",
745
746
  when: (ctx) => ctx.project.open,
746
747
  enablement: () => activeGridSurface() !== null,
747
- /* No `aiTool`, by §12.4's first deletion rule: chrome. */
748
+ /* No `aiTool`, by studio-ui-guidelines.md §12.4's first deletion rule: chrome. */
748
749
  run: (_ctx, args) => {
749
750
  const { gridId, name } = viewNameArg("grid.applyView", args);
750
751
  const { panel } = requireSurface("grid.applyView");
@@ -1,7 +1,7 @@
1
1
  /// <reference lib="dom" />
2
2
  /**
3
- * Redirects as a {@link GridSource} — the editor §11.4 asks for, over the table the grid already
4
- * is.
3
+ * Redirects as a {@link GridSource} — the editor site-architecture.md §11.4 asks for, over the
4
+ * table the grid already is.
5
5
  *
6
6
  * `project.json`'s `redirects` map is tabular, so it does not need a bespoke editor: it needs the
7
7
  * one Studio already has. Columns, inline editing, add/delete rows, undo, the dirty dot, ⌘S,
@@ -13,8 +13,8 @@
13
13
  *
14
14
  * **Every write goes through the one door.** Rows commit with {@link commitProjectConfig}, so a
15
15
  * redirect edit is a transaction on the `project.json` document with the same undo, the same
16
- * serialisation and the same refusal-with-a-Problem as every other configuration write (P6.1). This
17
- * module never touches `platform.writeFile`.
16
+ * serialisation and the same refusal-with-a-Problem as every other configuration write (§17.1).
17
+ * This module never touches `platform.writeFile`.
18
18
  *
19
19
  * **Row keys are positions.** A redirect's identity in `project.json` IS its source string, and the
20
20
  * source is the cell an author most often edits — so keying rows by it would turn every rename into
@@ -403,9 +403,10 @@ export async function openRedirectsGrid(): Promise<GridController> {
403
403
  * The paste box.
404
404
  *
405
405
  * It is `showPromptDialog` with a multiline field, not a dialog of its own. An import is one value
406
- * the author pastes and one answer they give, which is what that flow already is (§12.5); what it
407
- * needed was a field tall enough to read the value back in, and a monospaced one, because both
408
- * formats are column-aligned in the file they were copied out of.
406
+ * the author pastes and one answer they give, which is what that flow already is
407
+ * (studio-ui-guidelines.md §12.5); what it needed was a field tall enough to read the value back
408
+ * in, and a monospaced one, because both formats are column-aligned in the file they were copied
409
+ * out of.
409
410
  */
410
411
  export function promptRedirectImport(): Promise<string | null> {
411
412
  return showPromptDialog("Import Redirects", {
@@ -503,8 +504,9 @@ export function redirectsCommands(): AnyCommand[] {
503
504
  group: "5_data",
504
505
  requires: "an open project",
505
506
  when: (ctx) => ctx.project.open,
506
- /* No `aiTool`, by §12.4's first deletion rule: this opens a surface for a person, and the
507
- check it runs on the way is `validate_redirects`, which is projected. */
507
+ /* No `aiTool`, by studio-ui-guidelines.md §12.4's first deletion rule: this opens a surface
508
+ for a person, and the check it runs on the way is `validate_redirects`, which is
509
+ projected. */
508
510
  run: async () => {
509
511
  await openRedirectsGrid();
510
512
  reportRedirectProblems(rulesFromConfig(currentConfig()), await projectRoutes());
@@ -568,8 +570,9 @@ export function redirectsCommands(): AnyCommand[] {
568
570
  group: "5_data",
569
571
  requires: "an open project",
570
572
  when: (ctx) => ctx.project.open,
571
- /* No `aiTool`, by §12.4's second deletion rule: without `text`, `run` awaits a paste dialog
572
- the person answers, and the staged rows are a surface for that person to review. */
573
+ /* No `aiTool`, by studio-ui-guidelines.md §12.4's second deletion rule: without `text`, `run`
574
+ awaits a paste dialog the person answers, and the staged rows are a surface for that person
575
+ to review. */
573
576
  run: async (_ctx, args) => {
574
577
  const given = optionalStringArg("redirects.import", args, "text");
575
578
  const text = given ?? (await promptRedirectImport());
@@ -17,7 +17,8 @@
17
17
  * - **Shadow** — the project has a real route the rule's source covers. On a host that serves static
18
18
  * assets before consulting `_redirects` (Netlify, Cloudflare Pages) the page wins and the rule is
19
19
  * dead config; in `dist/` the compiler's own meta-refresh file overwrites the page and warns
20
- * (§11.1). Two hosts, two answers, and neither is what the author intended by writing both.
20
+ * (site-architecture.md §11.1). Two hosts, two answers, and neither is what the author intended
21
+ * by writing both.
21
22
  *
22
23
  * **What is deliberately NOT decided.** A destination containing `:param` or `*` is not followed:
23
24
  * its concrete target depends on the request, so calling it a chain would be a guess. Nothing is
@@ -67,7 +68,10 @@ export function normalizePath(path: string): string {
67
68
  return trimmed.length > 1 && trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed;
68
69
  }
69
70
 
70
- /** Whether a path is a URLPattern rather than a literal — `:param` or `*` (§11.2). */
71
+ /**
72
+ * Whether a path is a URLPattern (site-architecture.md §11.2) rather than a literal — `:param` or
73
+ * `*`.
74
+ */
71
75
  export function isPattern(path: string): boolean {
72
76
  return path.includes(":") || path.includes("*");
73
77
  }
@@ -80,9 +84,10 @@ export function isExternal(path: string): boolean {
80
84
  /**
81
85
  * Whether `path` is covered by `pattern`.
82
86
  *
83
- * URLPattern-lite over pathname segments, which is all §11.2 uses: `:name` matches exactly one
84
- * non-empty segment and `*` matches the rest, including nothing. Enough to answer "does this rule
85
- * ever fire for that request", which is the only question chain, loop and shadow ask.
87
+ * URLPattern-lite over pathname segments, which is all site-architecture.md §11.2 uses: `:name`
88
+ * matches exactly one non-empty segment and `*` matches the rest, including nothing. Enough to
89
+ * answer "does this rule ever fire for that request", which is the only question chain, loop and
90
+ * shadow ask.
86
91
  */
87
92
  export function matchesPattern(pattern: string, path: string): boolean {
88
93
  const p = normalizePath(pattern).split("/");
@@ -12,12 +12,12 @@
12
12
  * Collections type their columns from the content-type schema (plus inferred extras found in the
13
13
  * data); the pages grid is fully inference-driven.
14
14
  *
15
- * **A collection listing obeys the draft perspective (§7.6).** `content/draft-state.ts` owns both
16
- * the one flag and the one definition of what a draft is, and this source calls it rather than
17
- * testing `draft === true` itself — which is what makes "including drafts" mean the same thing here
18
- * as it does on the tab chip. It can, because that module imports nothing but `reactivity`: the
19
- * richer `content/entry-model.ts` asks THIS file where a collection's entries live, so an edge back
20
- * to it would be the cycle `import/no-cycle` rejects.
15
+ * **A collection listing obeys the draft perspective (site-architecture.md §7.6).**
16
+ * `content/draft-state.ts` owns both the one flag and the one definition of what a draft is, and
17
+ * this source calls it rather than testing `draft === true` itself — which is what makes "including
18
+ * drafts" mean the same thing here as it does on the tab chip. It can, because that module imports
19
+ * nothing but `reactivity`: the richer `content/entry-model.ts` asks THIS file where a collection's
20
+ * entries live, so an edge back to it would be the cycle `import/no-cycle` rejects.
21
21
  */
22
22
  import { getPlatform } from "../../platform";
23
23
  import { projectState } from "../../store";
@@ -207,9 +207,10 @@ interface EntryFileSourceOptions {
207
207
  /** Inferred columns to front-load, in order (e.g. title/description for pages). */
208
208
  priorityFields?: string[];
209
209
  /**
210
- * Whether this listing is subject to the draft perspective (§7.6) — collections are, the pages
211
- * tree is not. A draft is a property of a content ENTRY: a page carrying `draft: true` would
212
- * vanish from the only tree that lists it, with no collection schema to explain why.
210
+ * Whether this listing is subject to the draft perspective (site-architecture.md §7.6) —
211
+ * collections are, the pages tree is not. A draft is a property of a content ENTRY: a page
212
+ * carrying `draft: true` would vanish from the only tree that lists it, with no collection schema
213
+ * to explain why.
213
214
  */
214
215
  drafts?: boolean;
215
216
  }
@@ -24,6 +24,20 @@ import type { AssetBase } from "./layout";
24
24
  /** The default base: the tree served at the document's own directory, in the package's own shape. */
25
25
  export const IN_PLACE: AssetBase = { mode: "nested", prefix: "./" };
26
26
 
27
+ /**
28
+ * The marker a document carries when it declares boot modules: `<meta name="jx-boot"
29
+ * content="launcher">`.
30
+ *
31
+ * It is how the studio entry tells "nobody registered an adapter because this is the dev server"
32
+ * from "somebody was supposed to and did not". The two looked identical from inside the bundle, and
33
+ * Studio answered both with the dev-server adapter, so a launcher whose boot script 404'd or failed
34
+ * to parse got a window that drew, fetched `/__studio/*` from an origin with nothing behind it, and
35
+ * failed on the first click. A meta rather than a script global because it is in the markup before
36
+ * any script runs, and so survives exactly the failures it exists to report: a boot module that
37
+ * never loaded cannot have set anything. `platforms/default-platform.ts` is the reader.
38
+ */
39
+ export const BOOT_META = { name: "jx-boot", content: "launcher" } as const;
40
+
27
41
  export interface DocumentOptions {
28
42
  /** Where the asset tree is mounted. Defaults to {@link IN_PLACE}. */
29
43
  readonly base?: AssetBase | undefined;
@@ -39,6 +53,12 @@ export interface DocumentOptions {
39
53
  * or publishes the `__jxCloud` signal for the studio entry to build the adapter from. It must do
40
54
  * so SYNCHRONOUSLY — a module script with top-level await does not block a later script tag, and
41
55
  * the entry reads the global as it evaluates.
56
+ *
57
+ * **Declaring a boot module turns the dev-server fallback off.** A non-empty list stamps
58
+ * {@link BOOT_META} into the head, and a studio entry that then finds no adapter and no cloud
59
+ * signal draws a boot-failure screen instead of registering the dev-server adapter
60
+ * (`platforms/default-platform.ts`). A host that serves the HTTP protocol at its own origin and
61
+ * wants that fallback declares no boot module.
42
62
  */
43
63
  readonly boot?: readonly string[] | undefined;
44
64
  /** Document title. Defaults to "Jx Studio". */
@@ -66,12 +86,19 @@ export function studioShellHtml(options: DocumentOptions = {}): string {
66
86
  .map((path) => ` <link rel="stylesheet" href="${url(path)}" />`)
67
87
  .join("\n");
68
88
  const boot = (options.boot ?? []).map((mod) => ` ${scriptTag(mod)}`).join("\n");
89
+ /* Only when there IS a boot module: `packages/studio/index.html` is generated with none and CI
90
+ diffs it byte for byte, and the dev server it is served by is the one host that should fall
91
+ back. The `<head>` below stays a literal, attribute-free tag — Electrobun's macOS CEF path
92
+ injects its preload by splicing after that exact string. */
93
+ const bootMeta = boot
94
+ ? ` <meta name="${BOOT_META.name}" content="${BOOT_META.content}" />\n`
95
+ : "";
69
96
  return `<!doctype html>
70
97
  <html lang="en">
71
98
  <head>
72
99
  <meta charset="utf-8" />
73
100
  <meta name="viewport" content="width=device-width, initial-scale=1" />
74
- <title>${options.title ?? "Jx Studio"}</title>
101
+ ${bootMeta} <title>${options.title ?? "Jx Studio"}</title>
75
102
  <link rel="icon" href="${url(STUDIO_FAVICON)}" />
76
103
  ${links}
77
104
  </head>
@@ -23,6 +23,17 @@ import type { DocumentOptions } from "./document";
23
23
  /** The installed package root, resolved off this module rather than off a caller's cwd. */
24
24
  export const STUDIO_PACKAGE_DIR = resolve(import.meta.dir, "..", "..");
25
25
 
26
+ /**
27
+ * Normalize a `Glob.scan` result to forward slashes. Private and un-exported, like
28
+ * `project-session.ts`'s own `toPosix()`: this is the one module under `src/` that touches
29
+ * filesystem paths at all, and its published `files` entry (`package.json`) covers `src/` but not
30
+ * the repo's dev-tooling `scripts/lib/posix-path.ts`, so a cross-boundary import here would 404 for
31
+ * anyone who installed this package rather than checked out the monorepo.
32
+ */
33
+ function toPosix(p: string): string {
34
+ return p.replaceAll("\\", "/");
35
+ }
36
+
26
37
  export interface StageOptions {
27
38
  /** Package root to read from — an installed copy, or a `--link` checkout. */
28
39
  readonly from?: string | undefined;
@@ -108,13 +119,13 @@ export async function stageStudioAssets(
108
119
  const dest = destOf(destDir, a.path, layout);
109
120
  if (a.dir) {
110
121
  await mkdir(dest, { recursive: true });
111
- for (const rel of new Glob("**/*").scanSync(src)) {
112
- const posix = rel.replaceAll("\\", "/");
122
+ for (const rawRel of new Glob("**/*").scanSync(src)) {
123
+ const posix = toPosix(rawRel);
113
124
  if (skipMap(posix)) {
114
125
  continue;
115
126
  }
116
- const file = join(src, rel);
117
- const to = join(dest, rel);
127
+ const file = join(src, posix);
128
+ const to = join(dest, posix);
118
129
  await mkdir(dirname(to), { recursive: true });
119
130
  await cp(file, to);
120
131
  written.push(`${a.path}/${posix}`);
@@ -204,8 +204,8 @@ export function i18nCommands(): AnyCommand[] {
204
204
  with its `requires` sentence. The palette's availability is the person's contract; an
205
205
  argument-only gate needs a shape the registry does not have. Same on `createTranslation`. */
206
206
  when: (ctx) => ctx.project.isMultilingual && ctx.document.open,
207
- /* No `aiTool`, by §12.4's first deletion rule: this opens a surface for a person, and the
208
- model has `open_document` for the file it names. */
207
+ /* No `aiTool`, by studio-ui-guidelines.md §12.4's first deletion rule: this opens a surface
208
+ for a person, and the model has `open_document` for the file it names. */
209
209
  run: async (_ctx, args) => {
210
210
  const source = addressedDocument("i18n.openTranslation", args);
211
211
  const locale = stringArg("i18n.openTranslation", args, "locale");
@@ -261,10 +261,11 @@ export function i18nCommands(): AnyCommand[] {
261
261
  /* The document history cannot hold this: the change is a file that did not exist, and
262
262
  `undo: "project"` would claim `project.json` moved, which it did not. */
263
263
  undo: "none",
264
- /* No `aiTool`, by §12.4's second deletion rule: `createFileIn` awaits the New File prompt
265
- for the name, which the loop does not count as interactive, and a cancel resolves `null`
266
- with nothing thrown — a report would describe a file that was never made. An optional
267
- `name` argument that skips the prompt is the way back in, and is a UX call (issue 273). */
264
+ /* No `aiTool`, by studio-ui-guidelines.md §12.4's second deletion rule: `createFileIn` awaits
265
+ the New File prompt for the name, which the loop does not count as interactive, and a
266
+ cancel resolves `null` with nothing thrown — a report would describe a file that was never
267
+ made. An optional `name` argument that skips the prompt is the way back in, and is a UX
268
+ call (issue 273). */
268
269
  run: async (_ctx, args) => {
269
270
  const source = addressedDocument("i18n.createTranslation", args);
270
271
  const locale = stringArg("i18n.createTranslation", args, "locale");
@@ -319,7 +320,8 @@ export function i18nCommands(): AnyCommand[] {
319
320
  group: "2_view",
320
321
  requires: "a project that declares more than one language",
321
322
  when: (ctx) => ctx.project.isMultilingual,
322
- /* No `aiTool`, by §12.4's first deletion rule: the panel is a surface for a person. */
323
+ /* No `aiTool`, by studio-ui-guidelines.md §12.4's first deletion rule: the panel is a surface
324
+ for a person. */
323
325
  /* The panel is off the rail — it spends no rail slot and shifts no ⌘1-8 chord — so this
324
326
  command and the generated `panel.focus.i18n` are its only two doors, and both open the
325
327
  same one. The TITLES must differ even though the behaviour does not: the palette is a flat
@@ -4,9 +4,9 @@
4
4
  * Two shapes, chosen by the platform's `createDestination` (specs/desktop.md §4.5):
5
5
  *
6
6
  * - `"path"` (desktop, dev server): a required **Location** (absolute parent directory) plus the
7
- * folder name. Desktop backs the Browse… button with the native dialog via `pickDirectory`; the
8
- * dev server has no dialog, so the path is typed. Nothing is ever written to a directory the user
9
- * did not name.
7
+ * folder name. The Browse… button is backed by `pickDirectory` wherever the platform has one:
8
+ * desktop's system dialog, and the dev server's `showDirectoryPicker` in a Chromium browser.
9
+ * Elsewhere the path is typed. Nothing is ever written to a directory the user did not name.
10
10
  * - `"repo"` (cloud): the repository location — owner (personal account or organization), the
11
11
  * repository name, and its visibility.
12
12
  *
@@ -21,6 +21,7 @@
21
21
  * @docs studio/projects/create
22
22
  */
23
23
 
24
+ import { errorMessage } from "@jxsuite/schema/parse";
24
25
  import { getPlatform } from "../platform";
25
26
  import type { CreateProjectDestination, RepoInfo } from "../types";
26
27
 
@@ -253,6 +254,13 @@ export function setLocationVisibility(value: string): void {
253
254
  * Re-entrant by refusal rather than by disabling alone: the button is drawn disabled while the
254
255
  * native dialog is up, and a synthetic click does not consult that.
255
256
  *
257
+ * **Never a silent no-op, and never a rejection.** `null` from `pickDirectory` is the user
258
+ * cancelling, so nothing is said and the standing state is left alone. Anything else rejects with a
259
+ * displayable message (see `StudioPlatform.pickDirectory`), which lands in the destination error
260
+ * under the Location field, where typing a path clears it. The modal calls this as `void
261
+ * browseLocation(...)`, so a rejection escaping here would be an unhandled one: the exact "picked a
262
+ * folder and nothing happened" this contract exists to rule out.
263
+ *
256
264
  * @param rerender Repaint the wizard — once for the busy label, once for the answer.
257
265
  */
258
266
  export async function browseLocation(rerender: () => void): Promise<void> {
@@ -268,6 +276,11 @@ export async function browseLocation(rerender: () => void): Promise<void> {
268
276
  _parent = picked;
269
277
  _error = "";
270
278
  }
279
+ } catch (error) {
280
+ /* The dialog could not open, the folder could not be tagged, the backend was unreachable or
281
+ found no match, an RPC timed out. Whatever the platform, the sentence goes under Location. */
282
+ _error =
283
+ errorMessage(error) || "The folder could not be chosen. Type its path into Location instead.";
271
284
  } finally {
272
285
  _browsing = false;
273
286
  rerender();
@@ -46,7 +46,8 @@ import { getPlatform } from "../platform";
46
46
  import { installUrlOf } from "../platform-errors";
47
47
  import { hasAiCredentials } from "../services/ai-models";
48
48
  import { setPendingAgentPrompt } from "../services/agent-seed";
49
- import { initProjectRepo } from "../files/files";
49
+ import { adoptCreatedProject } from "../services/project-adoption";
50
+ import { notify } from "../services/notify";
50
51
  import { runImportHandoff } from "../services/import-seed";
51
52
  import { createAiCredentialsForm } from "../ui/ai-credentials-form";
52
53
  import { createManagedConnect } from "../ui/ai-managed-connect";
@@ -328,14 +329,29 @@ function finish(result: { root: string; config: ProjectConfig } | null) {
328
329
 
329
330
  /**
330
331
  * Every path out of the wizard that produced a project HERE: initialise version control for it,
331
- * then hand it to the caller. A scaffold is not a repository, and Delete and Rename are one click
332
- * away.
332
+ * open it, then hand it to the caller. A scaffold is not a repository and an unopened project is
333
+ * not a created one, and Delete and Rename are one click away.
333
334
  *
334
- * The Import path does not come through here any more — `import_site` creates the project, so the
335
- * obligation moved with it into `services/project-adoption.ts`, which both bootstrap tools share.
335
+ * `adoptCreatedProject` is the same sequence `create_project`/`import_site` run — this is the third
336
+ * caller `services/project-adoption.ts`'s own doc comment already names. It is what makes
337
+ * `project.new` from the command palette work at all while a project is open: that entry point
338
+ * never looks at what this promise resolves with (`newProjectCommands` below fires and forgets), so
339
+ * adopting has to happen HERE rather than being left to whichever caller opened the wizard.
340
+ *
341
+ * The Import path does not come through here — `import_site` creates the project, so it already
342
+ * runs this same sequence itself.
336
343
  */
337
344
  async function finishCreated(result: { root: string; config: ProjectConfig }) {
338
- await initProjectRepo(result.root);
345
+ const { adopted, error, openedElsewhere } = await adoptCreatedProject(result.root, {
346
+ // The wizard runs outside the agent loop — there is never a batch to flush and re-anchor.
347
+ getTab: () => null,
348
+ });
349
+ if (!adopted && !openedElsewhere) {
350
+ notify.warn("Created the project, but could not open it here.", {
351
+ ...(error === null ? {} : { detail: error }),
352
+ source: "New Project",
353
+ });
354
+ }
339
355
  finish(result);
340
356
  }
341
357
 
@@ -680,7 +696,7 @@ export function newProjectCommands(): AnyCommand[] {
680
696
  menus: ["commandbar/overflow", "palette"],
681
697
  group: "1_file",
682
698
  /* No `aiTool`: application-level, refused at registration; it opens the wizard, and the
683
- model's own door is `create_project` / `import_site` (§12.4). */
699
+ model's own door is `create_project` / `import_site` (studio-ui-guidelines.md §12.4). */
684
700
  run: (_commandCtx, args) => {
685
701
  const tab =
686
702
  (args as { tab?: unknown }).tab === undefined