@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
@@ -28,7 +28,7 @@ import {
28
28
  } from "../format/format-host";
29
29
  import { notify } from "../services/notify";
30
30
  import { activeTab } from "../workspace/workspace";
31
- import { serializeJson } from "../files/json-layout";
31
+ import { serializeJson } from "@jxsuite/schema/json-layout";
32
32
  import { invalidateReferenceEntries } from "../ui/form-controls";
33
33
  import { reloadDraftAwareGrids } from "../grid/sources/content-source";
34
34
  import { setIncludeDrafts } from "./draft-state";
@@ -64,7 +64,7 @@ async function seedText(collection: EntryCollection): Promise<string> {
64
64
  formatForPath(`untitled${collection.ext}`)?.name ??
65
65
  defaultContentFormat()?.name;
66
66
  if (collection.ext === ".json" || formatName === undefined) {
67
- // The save serializer with no layout (`files/json-layout.ts`): a seed has no source text, and
67
+ // The save serializer with no layout (`@jxsuite/schema/json-layout`): a seed has no source text, and
68
68
  // This is what the formatter makes of one, newline included.
69
69
  return serializeJson(seed, null);
70
70
  }
@@ -190,9 +190,10 @@ export function contentCommands(): AnyCommand[] {
190
190
  when: (ctx) => ctx.project.open,
191
191
  enablement: () => creatableCollections().length > 0,
192
192
  undo: "project",
193
- /* No `aiTool`, by §12.4's second deletion rule: `createEntry` awaits `createFileIn`, which
194
- awaits the New File prompt for the name; a cancel resolves with nothing thrown. An optional
195
- `name` argument that skips the prompt is the way back in, and is a UX call (issue 273). */
193
+ /* No `aiTool`, by studio-ui-guidelines.md §12.4's second deletion rule: `createEntry` awaits
194
+ `createFileIn`, which awaits the New File prompt for the name; a cancel resolves with
195
+ nothing thrown. An optional `name` argument that skips the prompt is the way back in, and
196
+ is a UX call (issue 273). */
196
197
  run: async (_ctx, args) => {
197
198
  const names = creatableCollections();
198
199
  await createEntry(enumArg("content.newEntry", args, "collection", names));
@@ -210,8 +211,8 @@ export function contentCommands(): AnyCommand[] {
210
211
  group: "1_file",
211
212
  requires: "an open project",
212
213
  when: (ctx) => ctx.project.open,
213
- /* No `aiTool`, by §12.4's first deletion rule: this opens a surface; the model has
214
- `open_document`. */
214
+ /* No `aiTool`, by studio-ui-guidelines.md §12.4's first deletion rule: this opens a surface;
215
+ the model has `open_document`. */
215
216
  run: async (_ctx, args) => {
216
217
  await openEntryEditor(stringArg("content.openEntry", args, "path"));
217
218
  },
@@ -233,12 +234,12 @@ export function contentCommands(): AnyCommand[] {
233
234
  when: (ctx) => ctx.document.open,
234
235
  enablement: () => activeEntryTab() !== null,
235
236
  undo: "document",
236
- /* No `aiTool`, by §12.4's fourth deletion rule: the draft flag is ONE FIELD on the entry,
237
- and the model already writes fields — `set_property` at the root path for a JSON entry
238
- (`mutateDocumentField` is the same `set-key` at `[]`), `write_file` for a frontmatter one.
239
- The state is reachable — `open_document` opens a collection file as readily as
240
- `content.openEntry` does — so the reason is redundancy, not reach. Six lines to re-enter
241
- should a task want the flag by name rather than by key. */
237
+ /* No `aiTool`, by studio-ui-guidelines.md §12.4's fourth deletion rule: the draft flag is ONE
238
+ FIELD on the entry, and the model already writes fields — `set_property` at the root path
239
+ for a JSON entry (`mutateDocumentField` is the same `set-key` at `[]`), `write_file` for a
240
+ frontmatter one. The state is reachable — `open_document` opens a collection file as
241
+ readily as `content.openEntry` does — so the reason is redundancy, not reach. Six lines to
242
+ re-enter should a task want the flag by name rather than by key. */
242
243
  run: (_ctx, args) => {
243
244
  const tab = activeEntryTab();
244
245
  if (!tab) {
@@ -259,7 +260,8 @@ export function contentCommands(): AnyCommand[] {
259
260
  group: "2_view",
260
261
  requires: "an open project",
261
262
  when: (ctx) => ctx.project.open,
262
- /* No `aiTool`, by §12.4's first deletion rule: a listing filter for a person. */
263
+ /* No `aiTool`, by studio-ui-guidelines.md §12.4's first deletion rule: a listing filter for a
264
+ person. */
263
265
  /*
264
266
  * Set the flag, then repaint what reads it.
265
267
  *
@@ -1,6 +1,6 @@
1
1
  /// <reference lib="dom" />
2
2
  /**
3
- * The **Entry editor** — a content entry's fields, as a form, in a pane (§7.4, plan §9.2).
3
+ * The **Entry editor** (site-architecture.md §7.4): a content entry's fields, as a form, in a pane.
4
4
  *
5
5
  * Until now a markdown entry could be edited two ways and neither was its schema: the canvas edited
6
6
  * its BODY, and the Document Header card drew a field list from `panels/frontmatter-fields.ts`'s
@@ -179,7 +179,7 @@ function formContext(tab: Tab): SchemaFormContext {
179
179
  * Required fields this entry does not have.
180
180
  *
181
181
  * Deliberately keyed on ABSENCE, not emptiness. A seeded entry's `title: ""` is present and the
182
- * author has not done anything wrong yet, so §7.1 says not to paint it red; a required key that is
182
+ * author has not done anything wrong yet, so §16.5 says not to paint it red; a required key that is
183
183
  * missing from the file is a fact about the document, and saying so is the whole reason the form
184
184
  * knows the schema.
185
185
  */
@@ -28,7 +28,7 @@
28
28
  * editing. That is the point.
29
29
  */
30
30
 
31
- import { cloneValue } from "@jxsuite/collab/ops";
31
+ import { cloneValue } from "@jxsuite/schema/doc-ops";
32
32
  import { notePreviewOverlayEdit } from "../preview/preview-overlay";
33
33
  import { recordDocOp } from "../tabs/patch-ops";
34
34
  import { mutateUpdateFrontmatter, transactDoc } from "../tabs/transact";
@@ -1,7 +1,7 @@
1
1
  /// <reference lib="dom" />
2
2
  // ─── Clipboard & Context Menu ─────────────────────────────────────────────────
3
3
  /**
4
- * The element context menu is a RENDERING of the command registry (UX-REDESIGN-PLAN §5.5).
4
+ * The element context menu is a RENDERING of the command registry (studio-ui-guidelines.md §12).
5
5
  *
6
6
  * This file used to hold an 18-item literal of `{ label, action, danger }` records — a fourth
7
7
  * hand-maintained copy of verbs that also exist in the toolbar, the block action bar and the
@@ -283,7 +283,7 @@ export function pasteStyles() {
283
283
  return;
284
284
  }
285
285
  const style = jsonClone(workspace.styleClipboard);
286
- // Pasting one style onto six cards is the §6.5 example, and it is one undo step: `transactDoc`
286
+ // Pasting one style onto six cards is one decision and one undo step (§6.7): `transactDoc`
287
287
  // Records everything its callback mutates as a single history entry.
288
288
  transactDoc(tab, (t) => {
289
289
  for (const sel of targets) {
@@ -754,8 +754,8 @@ let _registry: CommandRegistry | null = null;
754
754
  */
755
755
  export function contextMenuRegistry(): CommandRegistry {
756
756
  /*
757
- * THE APP'S REGISTRY, when there is one — §5.5's "the file keeps positioning and popover
758
- * rendering only".
757
+ * THE APP'S REGISTRY, when there is one — a menu is a rendering of the records (§13), so this
758
+ * file keeps positioning and popover rendering only.
759
759
  *
760
760
  * The private registry below existed because the menu's context had to describe the RIGHT-CLICKED
761
761
  * node while the app's described the ambient selection. Those are the same node now:
@@ -6,9 +6,10 @@
6
6
  * The naming question is `showPromptDialog` — one headline, one sentence, one field, one refusal —
7
7
  * which is exactly the shape this used to hand-render as an `sp-dialog-wrapper` of its own, down to
8
8
  * the Enter binding, the focus move and the select-all. That helper is already a document over the
9
- * kit (`src/surfaces/dialog.json`, studio-ui-guidelines §8.7), so the conversion here is a DELETION
10
- * rather than a second surface: a prompt authored beside the prompt would be a second answer to a
11
- * question Studio has settled once, and the two would drift the first time one of them was fixed.
9
+ * kit (`src/surfaces/dialog.json`, studio-ui-guidelines.md §8.7), so the conversion here is a
10
+ * DELETION rather than a second surface: a prompt authored beside the prompt would be a second
11
+ * answer to a question Studio has settled once, and the two would drift the first time one of them
12
+ * was fixed.
12
13
  *
13
14
  * What is left is the flow, which is all this module ever owned that mattered: what the default
14
15
  * name is, what makes a name usable, whether the document actually took the reference, and what is
@@ -25,7 +26,7 @@ import { transact } from "../tabs/transact";
25
26
  import { componentRegistry, computeRelativePath, loadComponentRegistry } from "../files/components";
26
27
  import { getPlatform } from "../platform";
27
28
  import { jsonClone } from "../utils/studio-utils";
28
- import { serializeJson } from "../files/json-layout";
29
+ import { serializeJson } from "@jxsuite/schema/json-layout";
29
30
  import { notify } from "../services/notify";
30
31
  import { showPromptDialog } from "../ui/layers";
31
32
  import { validateComponentSlots } from "../services/cem-export";
@@ -106,7 +107,7 @@ export async function convertToComponent() {
106
107
  // Write component file and refresh registry
107
108
  try {
108
109
  const platform = getPlatform();
109
- /* Through the save serializer (`files/json-layout.ts`) rather than `JSON.stringify`, so the new
110
+ /* Through the save serializer (`@jxsuite/schema/json-layout`) rather than `JSON.stringify`, so the new
110
111
  file lands in the formatter's layout with its trailing newline and the first save from its
111
112
  tab is a clean diff. No layout to carry: the subtree was lifted out of another document, and
112
113
  its record there is keyed by that document's pointers. */
@@ -67,9 +67,9 @@ export interface InlineAction {
67
67
  * REMOVED from the data, and the field stays only so an older `elements-meta.json` still parses.
68
68
  *
69
69
  * It held `"Cmd+B"` — a mac spelling, printed verbatim into every tooltip on every platform,
70
- * which is the hardcoded-chord defect plan §5.3 exists to kill. The chord now lives on the
71
- * `format.*` record (`panels/block-action-bar.ts`) and is formatted by the keymap's one
72
- * formatter. Nothing reads this.
70
+ * which is the hardcoded-chord defect §13.3 exists to kill. The chord now lives on the `format.*`
71
+ * record (`panels/block-action-bar.ts`) and is formatted by the keymap's one formatter. Nothing
72
+ * reads this.
73
73
  */
74
74
  shortcut?: string;
75
75
  }
@@ -218,11 +218,11 @@ function selectFirstChild(): void {
218
218
  /**
219
219
  * Walk out of the selection by one rung.
220
220
  *
221
- * This is the Escape ladder (plan §5.3), and it is one rung short of complete: the block action bar
222
- * is not on the registry yet, so "Escape returns to the bar's selection" has nowhere to read focus
223
- * from. What ships is the rest of it — a nested selection selects its parent, the document element
224
- * clears — which replaces an Escape that always cleared regardless of depth and so threw away the
225
- * whole path to get out of one node.
221
+ * This is the Escape ladder (leave the block action bar, select the parent, clear), and it is one
222
+ * rung short of complete: the block action bar is not on the registry yet, so "Escape returns to
223
+ * the bar's selection" has nowhere to read focus from. What ships is the rest of it — a nested
224
+ * selection selects its parent, the document element clears — which replaces an Escape that always
225
+ * cleared regardless of depth and so threw away the whole path to get out of one node.
226
226
  */
227
227
  function selectParent(): void {
228
228
  const tab = activeTab.value;
@@ -255,7 +255,7 @@ function insertSiblingParagraph(): void {
255
255
  }
256
256
 
257
257
  /**
258
- * Duplicate the whole selection in ONE transaction, so a batch is one undo step (§6.5).
258
+ * Duplicate the whole selection in ONE transaction, so a batch is one undo step (§6.7).
259
259
  *
260
260
  * With one path selected this is `mutateDuplicateNode` on that path and nothing else — the batch
261
261
  * form calls the single form, it does not reimplement it.
@@ -354,12 +354,12 @@ function zoomBy(ctx: CommandContext, factor: number, redraw: () => void): void {
354
354
  // ─── Shell verbs ──────────────────────────────────────────────────────────────
355
355
 
356
356
  /**
357
- * The Command Bar's dock toggles, mapped onto the two docks that exist.
357
+ * The Command Bar's dock toggles, mapped onto the two side docks.
358
358
  *
359
- * `"bottom"` is the plan's Problems/Terminal dock (§3.1) and has no shell row yet, so it is absent
360
- * here and {@link toggleShellDock} routes ⌘J to the Assistant instead — which is no longer a dock at
361
- * all but the Inspector's fourth tab, and is still the third thing that chord could mean. When the
362
- * bottom dock lands it gets a row here and that branch goes away.
359
+ * `"bottom"` is absent: the Bottom dock (§16.3) is toggled by `shell.ts`'s own
360
+ * `view.toggleBottomDock` (⌘J), and no default record passes `"bottom"` to `toggleDock`. The
361
+ * `bottom` branch of {@link toggleShellDock}, which selects the Assistant (the Inspector's fourth
362
+ * tab), predates that dock.
363
363
  */
364
364
  const DOCK_FOR_COMMAND: Readonly<Record<Exclude<CommandDockId, "bottom">, DockId>> = {
365
365
  inspector: "right",
@@ -403,9 +403,9 @@ let zenRestore: Partial<Record<DockId, boolean>> | null = null;
403
403
  /**
404
404
  * Collapse every dock; the same chord puts them back exactly as they were.
405
405
  *
406
- * "Reversible by the same key" (plan §5.3) is the whole requirement, and it is why the previous
407
- * state is snapshotted rather than assumed: an author who had the assistant closed does not want
408
- * leaving Zen to open it.
406
+ * Being reversible by the same key is the whole requirement, and it is why the previous state is
407
+ * snapshotted rather than assumed: an author who had the assistant closed does not want leaving Zen
408
+ * to open it.
409
409
  */
410
410
  function toggleZen(): void {
411
411
  if (zenRestore) {
@@ -427,7 +427,7 @@ function toggleZen(): void {
427
427
  // ─── Region focus (F6) ────────────────────────────────────────────────────────
428
428
 
429
429
  /**
430
- * The F6 ring, in reading order: rail → navigator → pane → inspector → dock → status (plan §5.3).
430
+ * The F6 ring, in reading order: rail → navigator → pane → inspector → dock → status.
431
431
  *
432
432
  * `shell.focusRegion` has enumerated these six values since the shell record landed and nothing
433
433
  * could act on it, because there was no map from the enum to the DOM. `ui/regions.ts` is that map;
@@ -445,9 +445,9 @@ export const REGION_CYCLE: readonly FocusRegion[] = [
445
445
  /**
446
446
  * The next region in the ring that is actually on screen, or `null` when none is.
447
447
  *
448
- * Absent regions are SKIPPED rather than focused-and-lost: the bottom dock does not exist until P4
449
- * and a collapsed Navigator has no host, so a ring that did not skip would strand the caret every
450
- * second press. Pure, and injectable, so the walk is testable without a shell.
448
+ * Absent regions are SKIPPED rather than focused-and-lost: a closed Bottom dock has no region
449
+ * (§16.3) and a collapsed Navigator has no host, so a ring that did not skip would strand the caret
450
+ * every second press. Pure, and injectable, so the walk is testable without a shell.
451
451
  */
452
452
  export function nextRegion(
453
453
  current: FocusRegion,
@@ -518,7 +518,7 @@ function cycleRegion(direction: 1 | -1): void {
518
518
  // ─── Panel and inspector focus (⌘1–8, ⌘⇧1–4) ─────────────────────────────────
519
519
 
520
520
  /**
521
- * Toggle-FOCUS, not toggle-visible (plan §5.3).
521
+ * Toggle-FOCUS, not toggle-visible.
522
522
  *
523
523
  * ⌘1 from the canvas reveals Files and puts the caret in it; ⌘1 again — with Files already focused
524
524
  * — collapses the dock and hands the caret back to the pane. The distinction matters because the
@@ -992,6 +992,72 @@ const SELF_SCROLLING_MODES: ReadonlySet<string> = new Set([
992
992
  "settings",
993
993
  ]);
994
994
 
995
+ /**
996
+ * Can a box between `target` and the stage still answer this wheel itself?
997
+ *
998
+ * Edit forwards every wheel to the page's scroller (see the branch below), which was the whole
999
+ * story while the stage cell held nothing but the page. It also holds the Document Header band now,
1000
+ * a capped scroll container standing beside the scroller — and a `preventDefault` from an ANCESTOR
1001
+ * listener cancels the native scroll of whatever the pointer is over, so a form taller than the cap
1002
+ * could not be wheeled at all while the page scrolled underneath it instead. So the stage takes the
1003
+ * wheel only when nothing inside it wants one: the walk stops at the stage cell, and it steps over
1004
+ * the page's scroller because that box is what the forwarding is FOR — a wheel forwarded out of the
1005
+ * cross-origin frame is dispatched on the cell itself and reaches no scrollable element on the way,
1006
+ * and letting the browser scroll the page natively would chain out of it into the shell.
1007
+ *
1008
+ * @param {WheelEvent} e
1009
+ * @param {HTMLElement} stage The cell the listener is on — the walk's upper bound, exclusive.
1010
+ * @param {HTMLElement | null} pageScroller The box the Edit branch scrolls itself.
1011
+ */
1012
+ function wheelBelongsInside(
1013
+ e: WheelEvent,
1014
+ stage: HTMLElement,
1015
+ pageScroller: HTMLElement | null,
1016
+ ): boolean {
1017
+ const { target } = e;
1018
+ let el: Element | null =
1019
+ target instanceof Element ? target : ((target as Node | null)?.parentElement ?? null);
1020
+ while (el && el !== stage) {
1021
+ if (el instanceof HTMLElement && el !== pageScroller && canScrollBy(el, e.deltaX, e.deltaY)) {
1022
+ return true;
1023
+ }
1024
+ el = el.parentElement;
1025
+ }
1026
+ return false;
1027
+ }
1028
+
1029
+ /**
1030
+ * Is `el` a scroll container with room left in the direction this wheel asks for?
1031
+ *
1032
+ * Both halves are load-bearing: a box already at the end of its range answers nothing, and left the
1033
+ * wheel it would chain the scroll out to its ancestors — which is the shell, and exactly what the
1034
+ * Edit branch's `preventDefault` is there to stop. The 1px slack is the subpixel one a fractional
1035
+ * layout leaves at the end of a range. Overflow is read the way `ui/virtual-window.ts`'s scroller
1036
+ * walk reads it, cheap test first: happy-dom performs no layout, so the sizes are 0 for every
1037
+ * element and no test pays for a `getComputedStyle` it could not answer anyway.
1038
+ *
1039
+ * @param {HTMLElement} el
1040
+ * @param {number} dx
1041
+ * @param {number} dy
1042
+ */
1043
+ function canScrollBy(el: HTMLElement, dx: number, dy: number): boolean {
1044
+ const downRoom = dy > 0 && el.scrollTop < el.scrollHeight - el.clientHeight - 1;
1045
+ const upRoom = dy < 0 && el.scrollTop > 0;
1046
+ const acrossRoom =
1047
+ (dx > 0 && el.scrollLeft < el.scrollWidth - el.clientWidth - 1) ||
1048
+ (dx < 0 && el.scrollLeft > 0);
1049
+ if (!downRoom && !upRoom && !acrossRoom) {
1050
+ return false;
1051
+ }
1052
+ const style = getComputedStyle(el);
1053
+ const scrolls = (overflow: string) =>
1054
+ overflow === "auto" || overflow === "scroll" || overflow === "overlay";
1055
+ return (
1056
+ (downRoom || upRoom ? scrolls(style.overflowY) : false) ||
1057
+ (acrossRoom ? scrolls(style.overflowX) : false)
1058
+ );
1059
+ }
1060
+
995
1061
  /** The stage-context reader, published by {@link initShortcuts} for {@link installStageGestures}. */
996
1062
  let _stageContext: StageContext | null = null;
997
1063
 
@@ -1033,7 +1099,9 @@ export function installStageGestures(surface: CanvasSurface): () => void {
1033
1099
  /* The stage's own scroller, by the `part` the stage document draws it with — the same
1034
1100
  attribute every other consumer of the canvas's chrome now addresses it by. */
1035
1101
  const sc = canvasWrap.querySelector<HTMLElement>('[part="edit-canvas"]');
1036
- if (sc) {
1102
+ /* …unless the pointer is over a box in this cell that scrolls itself — the Document Header
1103
+ band, which Edit docks beside the scroller. See {@link wheelBelongsInside}. */
1104
+ if (sc && !wheelBelongsInside(e, canvasWrap, sc)) {
1037
1105
  e.preventDefault();
1038
1106
  sc.scrollTop += e.deltaY;
1039
1107
  sc.scrollLeft += e.deltaX;
@@ -7,16 +7,17 @@
7
7
  * its markup, its ARIA and every value in its style — and this module is the decisions: what is on
8
8
  * offer, what the filter matches, which row is active, and what a pick does.
9
9
  *
10
- * **Why this is not `surfaces/menu.json`.** Every other Studio menu is that surface, and §12.5 says
11
- * a second list of actions is a defect — but a `jx-menu` owns DOM focus by contract: showing one
12
- * moves the caret onto its first row. This panel exists to filter a caret that is somewhere else,
13
- * inside the canvas's `contenteditable` and usually inside the canvas IFRAME, and every character
14
- * typed after the `/` has to keep landing there. A panel that takes the keyboard ends the
15
- * interaction it exists to serve. So the panel is a listbox that marks its active row and never
16
- * takes focus, and the keys are taken where they actually land — a document-level capturing
17
- * listener for this realm, and {@link handleSlashMenuKey} called straight across the bridge for the
18
- * other one. That listener is not a focus trap and cannot become one: it forwards four keys to the
19
- * same driver the bridge uses, and everything else keeps going to the caret.
10
+ * **Why this is not `surfaces/menu.json`.** Every other Studio menu is that surface, and
11
+ * studio-ui-guidelines.md §12.5 says a second list of actions is a defect — but a `jx-menu` owns
12
+ * DOM focus by contract: showing one moves the caret onto its first row. This panel exists to
13
+ * filter a caret that is somewhere else, inside the canvas's `contenteditable` and usually inside
14
+ * the canvas IFRAME, and every character typed after the `/` has to keep landing there. A panel
15
+ * that takes the keyboard ends the interaction it exists to serve. So the panel is a listbox that
16
+ * marks its active row and never takes focus, and the keys are taken where they actually land — a
17
+ * document-level capturing listener for this realm, and {@link handleSlashMenuKey} called straight
18
+ * across the bridge for the other one. That listener is not a focus trap and cannot become one: it
19
+ * forwards four keys to the same driver the bridge uses, and everything else keeps going to the
20
+ * caret.
20
21
  *
21
22
  * Light dismissal, the top layer and Escape-on-the-topmost-popover are the platform's, through the
22
23
  * `jx-popover` the document is rooted in; the `mousedown` capture listener that used to hand-roll
@@ -2,11 +2,10 @@
2
2
  * Elements.ts — what belongs in a document's (or a project's) `$elements`, decided once.
3
3
  *
4
4
  * `$elements` is the list of component modules a document may use. Three surfaces wrote it, each
5
- * with its own idea of what "already imported" means, and plan §11.2 names all three: the Packages
6
- * panel's cherry-pick checkboxes, its "Add component…" picker, and the automatic push when a
7
- * component is dropped on the canvas. "One `enableElement()` / `disableElement()` service behind
8
- * all three. Today the same array is written three ways with no shared UI and no shared
9
- * validation."
5
+ * with its own idea of what "already imported" means: the Packages panel's cherry-pick checkboxes,
6
+ * its "Add component…" picker, and the automatic push when a component is dropped on the canvas.
7
+ * §9.1.3 puts one `enableElement()` / `disableElement()` service behind all three, because the same
8
+ * array was written three ways with no shared UI and no shared validation.
10
9
  *
11
10
  * They really did disagree. The canvas drop matched a local component by `ref.endsWith(basename)`,
12
11
  * so `./components/card.json` counted `./vendor/card.json` as already imported and the drop
@@ -39,7 +39,7 @@ import type { CollabParser } from "../collab/collab-session";
39
39
  import { flushCanvasEdits } from "../canvas/iframe-host";
40
40
  import { flushPreviewOverlay } from "../preview/preview-overlay";
41
41
  import { serializeDocument } from "./serialize-document";
42
- import { parseJsonDocument } from "./json-layout";
42
+ import { parseJsonDocument } from "@jxsuite/schema/json-layout";
43
43
  import {
44
44
  defaultContentFormat,
45
45
  formatByName,
@@ -77,8 +77,8 @@ import { activeRegistry } from "../commands/active-registry";
77
77
  import { runActiveReported, runReported } from "../commands/run-reported";
78
78
  import { collectionOfPath } from "../content/entry-model";
79
79
  import { confirmFileDelete, parseSourceForPath, renamePromptMessage } from "./file-ops";
80
- import { parseJsonDocument } from "./json-layout";
81
- import type { JsonLayout } from "./json-layout";
80
+ import { parseJsonDocument } from "@jxsuite/schema/json-layout";
81
+ import type { JsonLayout } from "@jxsuite/schema/json-layout";
82
82
  import { invalidateUsages } from "../services/references";
83
83
  import {
84
84
  documentExtensions,
@@ -379,10 +379,10 @@ export async function openHomePage() {
379
379
  /**
380
380
  * Reopen the documents this project was last left with, or its home page if there are none.
381
381
  *
382
- * Plan §4.4, and P3's "Newly possible": **the session survives a relaunch.** Every one of the three
383
- * ways into a project — the `?project=` bootstrap, the PAL picker and a recent-project open — ended
384
- * at `openHomePage()`, so nine open documents, a split and the mode you were in were lost each
385
- * time. The per-project record's own interface said "session state grows into this shape".
382
+ * §14.8: **the session survives a relaunch.** Every one of the three ways into a project — the
383
+ * `?project=` bootstrap, the PAL picker and a recent-project open — ended at `openHomePage()`, so
384
+ * nine open documents, a split and the mode you were in were lost each time. The per-project
385
+ * record's own interface said "session state grows into this shape".
386
386
  *
387
387
  * ONE function for all three, because the three used to be three calls to `openHomePage` and this
388
388
  * is exactly the kind of behaviour that lands on two of them.
@@ -538,10 +538,10 @@ let _fileList: HTMLElement | null = null;
538
538
  /**
539
539
  * Every DRAWN row's element, by path.
540
540
  *
541
- * Held rather than queried, which is the §9.4 rule and, for a windowed list, the only correct
542
- * answer: the nodes are re-used for different files as the window slides, so a selector run a frame
543
- * later resolves to whichever file is standing there now. Entries whose element has left the
544
- * document are swept on the next projection.
541
+ * Held rather than queried, which is the studio-ui-guidelines.md §9.4 rule and, for a windowed
542
+ * list, the only correct answer: the nodes are re-used for different files as the window slides, so
543
+ * a selector run a frame later resolves to whichever file is standing there now. Entries whose
544
+ * element has left the document are swept on the next projection.
545
545
  */
546
546
  const _fileRowEls = new Map<string, HTMLElement>();
547
547
  /** The scroll watch that repaints the tree as its scroller moves. */
@@ -1668,9 +1668,9 @@ function fileMenuRows(entry: { name: string; path: string; type: string }): Menu
1668
1668
  *
1669
1669
  * The KIT menu, not a popover of this module's own: `openMenu()` owns the panel, the roving focus,
1670
1670
  * the typeahead, the light dismissal and Escape, and it is the answer this shell already settled on
1671
- * (§8.4). What went with the `sp-popover` it replaces is the hand-written clamp — a `ref` that
1672
- * measured the panel a frame after it opened and moved it back inside the viewport — because the
1673
- * kit does that for every menu instead of this one doing it for itself.
1671
+ * (studio-ui-guidelines.md §8.4). What went with the `sp-popover` it replaces is the hand-written
1672
+ * clamp — a `ref` that measured the panel a frame after it opened and moved it back inside the
1673
+ * viewport — because the kit does that for every menu instead of this one doing it for itself.
1674
1674
  */
1675
1675
  function showFileContextMenu(path: string, x: number, y: number): void {
1676
1676
  const row = fileRowAt(path);
@@ -2235,7 +2235,10 @@ async function deleteFile(
2235
2235
  export interface OpenFileOpts {
2236
2236
  /** Which pane. Defaults to the focused one. */
2237
2237
  paneId?: string;
2238
- /** Open as a disposable preview tab (§4.3) — browsing rather than committing. */
2238
+ /**
2239
+ * Open as a disposable preview tab — browsing rather than committing
2240
+ * (`docs/studio/interface/tabs.md#preview-and-pinned-documents`).
2241
+ */
2239
2242
  preview?: boolean;
2240
2243
  /** False leaves the keyboard where it is. Defaults to true. */
2241
2244
  focus?: boolean;
@@ -2489,7 +2492,7 @@ export async function reloadFileInTab(path: string) {
2489
2492
  * Contribute the Files panel.
2490
2493
  *
2491
2494
  * `level: "project"` because it WRITES project files — create, rename, delete, move. It reads the
2492
- * focused document only to highlight a row, and principle 3 files a surface by what it writes.
2495
+ * focused document only to highlight a row, and §13.2 files a surface by what it writes.
2493
2496
  */
2494
2497
  export function registerFilesPanel(): void {
2495
2498
  registerPanel({
@@ -2520,8 +2523,9 @@ const FILE_ACTIONS: FilesPanelActions = {
2520
2523
  },
2521
2524
  openProject: () => {
2522
2525
  /* The DECLARED command, run through the registry — never a second opener of this surface's own
2523
- (§12.5). It is the one the ⌘O chord, the palette and the status bar's PROJECT field already
2524
- run, and it is the only one that knows about the picker modal and the "new window" target. */
2526
+ (studio-ui-guidelines.md §12.5). It is the one the ⌘O chord, the palette and the status
2527
+ bar's PROJECT field already run, and it is the only one that knows about the picker modal
2528
+ and the "new window" target. */
2525
2529
  void runActiveReported("project.open", undefined, "Files");
2526
2530
  },
2527
2531
  refresh: () => {
@@ -19,7 +19,7 @@ import {
19
19
  formatSerialize,
20
20
  loadFormats,
21
21
  } from "../format/format-host";
22
- import { serializeJson } from "./json-layout";
22
+ import { serializeJson } from "@jxsuite/schema/json-layout";
23
23
  import type { Tab } from "../tabs/tab";
24
24
 
25
25
  /**
@@ -27,8 +27,8 @@ import {
27
27
  stringProperty,
28
28
  } from "../commands/command-args";
29
29
  import { convertTargetExtensions, convertTargets } from "./format-choices";
30
- import { parseJsonDocument, serializeJson } from "../files/json-layout";
31
- import type { JsonLayout } from "../files/json-layout";
30
+ import { parseJsonDocument, serializeJson } from "@jxsuite/schema/json-layout";
31
+ import type { JsonLayout } from "@jxsuite/schema/json-layout";
32
32
  import type { AnyCommand, CommandRegistry } from "../commands/registry";
33
33
 
34
34
  /** What a conversion will do, resolved in full before the reader is asked to confirm it. */
@@ -99,7 +99,7 @@ async function writeDocument(
99
99
  ): Promise<string> {
100
100
  const merged = { ...frontmatter, ...document };
101
101
  if (targetExt === ".json") {
102
- /* The same serializer a save uses (`files/json-layout.ts`), with NO layout: the source was
102
+ /* The same serializer a save uses (`@jxsuite/schema/json-layout`), with NO layout: the source was
103
103
  markdown or CSV, so there is no JSON text whose line breaks could be kept. Every object
104
104
  expands and every short array stays on one line — which is exactly what the repository's
105
105
  formatter would make of `JSON.stringify` output, so the converted file needs no reformat and
@@ -497,8 +497,8 @@ export function fileFormatCommands(): AnyCommand[] {
497
497
  // Nothing restores it: the file has moved and its references have been rewritten with it.
498
498
  // The confirmation carrying the count is what stands in for an undo.
499
499
  undo: "none",
500
- /* No `aiTool`, by §12.4's second deletion rule: `run` awaits a confirm dialog the person
501
- answers. */
500
+ /* No `aiTool`, by studio-ui-guidelines.md §12.4's second deletion rule: `run` awaits a
501
+ confirm dialog the person answers. */
502
502
  run: async (_ctx, args: Record<string, unknown>) => {
503
503
  const target = typeof args.format === "string" ? args.format : undefined;
504
504
  await convertFile(
@@ -0,0 +1,107 @@
1
+ /**
2
+ * The GitHub credential's state — whether one exists, where it lives, and how to forget it.
3
+ *
4
+ * Split out of `github-auth.ts` because a launcher's PAL-init bundle needs exactly one thing from
5
+ * that module before Studio itself has loaded: {@link hydrateGithubToken}, so the accounts pane can
6
+ * say "signed in" on the first frame (`desktop/src/boot.ts`). `github-auth.ts` also pulls in
7
+ * `notify`, `../ui/layers` and the device-flow dialog surface for the actual sign-in UI — none of
8
+ * which a bundler drops just because only this module's exports are imported, so a launcher that
9
+ * imported the combined file got a second, independent copy of Lit's runtime alongside the one the
10
+ * studio bundle it shares a window with already loads (`packages/desktop/src/init.ts`, the
11
+ * "Multiple versions of Lit loaded" warning). This file has no UI dependency at all, so a launcher
12
+ * that imports only this gets none of that.
13
+ *
14
+ * `github-auth.ts` re-exports everything here, so every other caller in Studio is unaffected.
15
+ */
16
+
17
+ /// <reference lib="dom" />
18
+
19
+ const STORAGE_KEY = "jx_github_token";
20
+
21
+ /**
22
+ * The desktop launcher's RFC 8252 sign-in, when this build is running inside one.
23
+ *
24
+ * Launcher-only, like `updater` and `windowControls`, and reached the same way — a loopback
25
+ * redirect needs a loopback server, which a page in a browser does not have.
26
+ */
27
+ export interface NativeGithubAuth {
28
+ signIn: (force?: boolean) => Promise<{ token: string }>;
29
+ signOut: () => Promise<{ ok: boolean }>;
30
+ status: () => Promise<{ stored: boolean }>;
31
+ }
32
+
33
+ export function nativeAuth(): NativeGithubAuth | null {
34
+ return (
35
+ (globalThis as unknown as { __jxPlatform?: { githubAuth?: NativeGithubAuth } }).__jxPlatform
36
+ ?.githubAuth ?? null
37
+ );
38
+ }
39
+
40
+ /**
41
+ * The token the desktop store handed us this session.
42
+ *
43
+ * On the desktop the token at rest is a `0600` file in the user's config directory, **not**
44
+ * `localStorage` — so it is not readable by script in the webview between sessions, and it does not
45
+ * survive in a place a stray extension or a copied profile can reach. Holding it in a module
46
+ * variable is what keeps {@link getGithubToken} synchronous for its callers.
47
+ */
48
+ let nativeToken: string | null = null;
49
+
50
+ /** Whether the desktop store holds a token, as of the last time it was asked. */
51
+ let nativeStored = false;
52
+
53
+ /** Record the token a native sign-in just returned — {@link authenticateGithub}'s own setter. */
54
+ export function setNativeToken(token: string | null): void {
55
+ nativeToken = token;
56
+ }
57
+
58
+ /**
59
+ * Record that the desktop's credential store does (or does not) hold a token.
60
+ *
61
+ * The launcher calls this once at startup. It carries a **boolean, not the token**: the accounts
62
+ * pane only needs to know whether one exists, and handing the webview a credential it has not been
63
+ * asked to use is the exposure the 0600 store exists to remove. When a sign-in does happen the
64
+ * token arrives through {@link setNativeToken} and lives in memory for that session.
65
+ *
66
+ * @param {boolean} stored
67
+ */
68
+ export function hydrateGithubToken(stored: boolean): void {
69
+ nativeStored = stored;
70
+ }
71
+
72
+ /**
73
+ * Whether a GitHub credential exists, wherever it lives.
74
+ *
75
+ * Separate from {@link getGithubToken} because the two questions genuinely differ on the desktop: a
76
+ * token can be stored on disk and not yet in this session's memory, and the accounts pane asks
77
+ * "signed in?" while rendering — synchronously — where an API caller asks for the credential itself
78
+ * and can await it.
79
+ *
80
+ * @returns {boolean}
81
+ */
82
+ export function githubTokenStored(): boolean {
83
+ return nativeToken !== null || nativeStored || localStorage.getItem(STORAGE_KEY) !== null;
84
+ }
85
+
86
+ /** Where the credential this build would use actually lives, for text that says so. */
87
+ export function githubTokenLocation(): "desktop" | "browser" {
88
+ return nativeAuth() ? "desktop" : "browser";
89
+ }
90
+
91
+ export function getGithubToken(): string | null {
92
+ return nativeToken ?? localStorage.getItem(STORAGE_KEY);
93
+ }
94
+
95
+ export function clearGithubToken(): void {
96
+ nativeToken = null;
97
+ nativeStored = false;
98
+ localStorage.removeItem(STORAGE_KEY);
99
+ // Fire-and-forget: the accounts pane's revoke is synchronous, and a store that failed to forget
100
+ // The token is not something the user can act on from there.
101
+ void nativeAuth()
102
+ ?.signOut()
103
+ .catch(() => {});
104
+ }
105
+
106
+ /** The key {@link authenticateGithub}'s device flow writes the browser-stored token under. */
107
+ export { STORAGE_KEY };