@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
@@ -5,8 +5,9 @@
5
5
  * between this and the Manage view it replaces. That view's category filter, its search box and its
6
6
  * grid/table switch existed ONLY as buttons inside a modal, so `scripts/screenshots/automation.ts`
7
7
  * carried a hand-maintained `BROWSE_CATEGORY_LABELS` table and pressed the buttons through an XPath
8
- * matched on their rendered text — plan §13's exact complaint. `library.setCategory` is the same
9
- * action the button runs, and the button is now a rendering of the record.
8
+ * matched on their rendered text, exactly what the shot contract's R1 forbids
9
+ * (`scripts/screenshots/README.md`). `library.setCategory` is the same action the button runs, and
10
+ * the button is now a rendering of the record.
10
11
  *
11
12
  * Every setter is idempotent and names the STATE it reaches, never a delta: there is no
12
13
  * `library.toggleLayout`, because a toggle against unstated state is what silently inverted
@@ -51,7 +52,7 @@ export function libraryCommands(): AnyCommand[] {
51
52
  {
52
53
  category: "Project",
53
54
  id: "library.open",
54
- // ⌘⇧E — §9.1's "File: Browse Library (⌘⇧E)". The Library is the content surface for a site
55
+ // ⌘⇧E, as studio.md §9.1.2 names it. The Library is the content surface for a site
55
56
  // With a collection, and it was reachable only by palette search and one overflow menu item.
56
57
  keybinding: "mod+shift+e",
57
58
  level: "project",
@@ -59,9 +60,9 @@ export function libraryCommands(): AnyCommand[] {
59
60
  group: "1_file",
60
61
  requires: "an open project",
61
62
  when: (ctx) => ctx.project.open,
62
- /* No `aiTool`, by §12.4's first deletion rule: chrome. None of the Library family is
63
- projected — every one of these verbs shapes a listing a person reads, and the model has
64
- `list_files` / `search_files` for the same facts. */
63
+ /* No `aiTool`, by studio-ui-guidelines.md §12.4's first deletion rule: chrome. None of the
64
+ Library family is projected — every one of these verbs shapes a listing a person reads,
65
+ and the model has `list_files` / `search_files` for the same facts. */
65
66
  run: () => {
66
67
  openLibraryTab();
67
68
  },
@@ -81,7 +82,7 @@ export function libraryCommands(): AnyCommand[] {
81
82
  group: "1_file",
82
83
  requires: "an open project",
83
84
  when: (ctx) => ctx.project.open,
84
- /* No `aiTool`: a listing filter for a person (§12.4, rule 1). */
85
+ /* No `aiTool`: a listing filter for a person (studio-ui-guidelines.md §12.4, rule 1). */
85
86
  run: (_ctx, args) => {
86
87
  const key = enumArg("library.setCategory", args, "category", LIBRARY_CATEGORY_KEYS);
87
88
  setLibraryCategory(key);
@@ -104,7 +105,7 @@ export function libraryCommands(): AnyCommand[] {
104
105
  group: "1_file",
105
106
  requires: "an open project",
106
107
  when: (ctx) => ctx.project.open,
107
- /* No `aiTool`: a listing filter for a person (§12.4, rule 1). */
108
+ /* No `aiTool`: a listing filter for a person (studio-ui-guidelines.md §12.4, rule 1). */
108
109
  run: (_ctx, args) => {
109
110
  const layout = enumArg<LibraryLayout>("library.setLayout", args, "layout", LIBRARY_LAYOUTS);
110
111
  setLibraryLayout(layout);
@@ -126,7 +127,8 @@ export function libraryCommands(): AnyCommand[] {
126
127
  group: "1_file",
127
128
  requires: "an open project",
128
129
  when: (ctx) => ctx.project.open,
129
- /* No `aiTool`: a listing filter for a person; the model has `search_files` (§12.4, rule 1). */
130
+ /* No `aiTool`: a listing filter for a person; the model has `search_files`
131
+ (studio-ui-guidelines.md §12.4, rule 1). */
130
132
  run: (_ctx, args) => {
131
133
  setLibrarySearch(optionalStringArg("library.setSearch", args, "query") ?? "");
132
134
  },
@@ -154,7 +156,7 @@ export function libraryCommands(): AnyCommand[] {
154
156
  // A monolingual project has no facet to set: the Library draws no picker, and offering the
155
157
  // Verb would name a value space with one member in it.
156
158
  when: (ctx) => ctx.project.open && ctx.project.isMultilingual,
157
- /* No `aiTool`: a listing filter for a person (§12.4, rule 1). */
159
+ /* No `aiTool`: a listing filter for a person (studio-ui-guidelines.md §12.4, rule 1). */
158
160
  run: (_ctx, args) => {
159
161
  const choice = enumArg("library.setLocale", args, "locale", localeChoices());
160
162
  setLibraryLocale(choice === "all" ? "" : choice);
@@ -169,7 +171,7 @@ export function libraryCommands(): AnyCommand[] {
169
171
  group: "1_file",
170
172
  requires: "an open project",
171
173
  when: (ctx) => ctx.project.open,
172
- /* No `aiTool`: chrome (§12.4, rule 1). */
174
+ /* No `aiTool`: chrome (studio-ui-guidelines.md §12.4, rule 1). */
173
175
  run: () => refreshLibrary(),
174
176
  title: "Library: Rescan Files",
175
177
  },
@@ -193,8 +195,8 @@ export function libraryCommands(): AnyCommand[] {
193
195
  requires: "an open project",
194
196
  when: (ctx) => ctx.project.open,
195
197
  undo: "project",
196
- /* No `aiTool`, by §12.4's second deletion rule: it waits on the New File prompt;
197
- `create_page` / `create_component` cover the job. */
198
+ /* No `aiTool`, by studio-ui-guidelines.md §12.4's second deletion rule: it waits on the New
199
+ File prompt; `create_page` / `create_component` cover the job. */
198
200
  run: async (_ctx, args) => {
199
201
  const declared = libraryNewEntries().map((entry) => entry.key);
200
202
  const key = enumArg("library.newEntry", args, "type", declared);
@@ -798,8 +798,8 @@ export async function createLibraryEntry(key: string): Promise<string | null> {
798
798
  *
799
799
  * The active category's directory when it has one — printed on the drop zone, so the destination is
800
800
  * visible BEFORE the drop. "All" has none, and rather than falling back to a default nobody chose,
801
- * it asks. That fallback is exactly the surprise §7 names: the file arrives, the toast says it
802
- * worked, and it is in a directory the author never picked.
801
+ * it asks. That fallback is exactly the surprise studio.md §16 exists to refuse: the file arrives,
802
+ * the toast says it worked, and it is in a directory the author never picked.
803
803
  */
804
804
  export async function resolveUploadDir(): Promise<string | null> {
805
805
  const named = uploadDirForCategory(libraryView.category);
@@ -35,8 +35,9 @@ import {
35
35
  import { collabSourceContext } from "../collab/collab-session";
36
36
  import { buildChangeMap } from "./diff-marks";
37
37
  import type { ChangeMap, ChangeMark } from "./diff-marks";
38
- import { clearDiffView, diffViewOf, setDiffChangeMap } from "./diff-view";
38
+ import { clearDiffView, diffShowsCode, setDiffChangeMap } from "./diff-view";
39
39
  import { canRenderComparison } from "../panels/git-diff-open";
40
+ import { releaseStageScrollbar, trackStageScrollbar } from "./stage-scrollbar";
40
41
  import { renderDiffToolbar, setDiffRepaint, setDiffToolbarHost } from "./diff-toolbar";
41
42
  import { attachCursorStyles } from "../collab/monaco-cursors";
42
43
  import type { AwarenessLike } from "../collab/monaco-cursors";
@@ -44,7 +45,7 @@ import type { BindingAwareness } from "../collab/monaco-binding";
44
45
  import { monacoTheme, shell } from "../shell";
45
46
  import { parseSourceForPath } from "../files/file-ops";
46
47
  import { serializeDocument } from "../files/serialize-document";
47
- import { parseJsonDocument, serializeJson } from "../files/json-layout";
48
+ import { parseJsonDocument, serializeJson } from "@jxsuite/schema/json-layout";
48
49
  import { detachGridPanel, gridPanelMounted, renderGridMode } from "../grid/grid-panel";
49
50
  import { detachLibraryPane, libraryPaneMounted, renderLibraryMode } from "../browse/library-pane";
50
51
  import { detachEntryPane, entryPaneMounted, renderEntryMode } from "../content/entry-editor";
@@ -165,7 +166,7 @@ async function sourceContent(tab: Tab) {
165
166
  if (formatByName(tab.doc.sourceFormat)) {
166
167
  return serializeDocument(tab);
167
168
  }
168
- // The file's own layout (`files/json-layout.ts`), so the buffer shows the bytes a save writes and
169
+ // The file's own layout (`@jxsuite/schema/json-layout`), so the buffer shows the bytes a save writes and
169
170
  // A commit from it reads the same layout back — what `serializeDocument`'s JSON branch does.
170
171
  return serializeJson(tab.doc.document, tab.doc.layout ?? null);
171
172
  }
@@ -231,7 +232,7 @@ function hardClearCanvasWrap(canvasWrap: HTMLElement) {
231
232
 
232
233
  /**
233
234
  * Where each stage's non-artboard hosts landed, keyed by part (and by the static mark that tells
234
- * two of one part apart: a handle's side, a card's placement).
235
+ * the two width handles apart: `data-side`, the one part the stage document draws twice).
235
236
  *
236
237
  * Per STAGE rather than per module, for the reason every other field on `CanvasSurface` is: two
237
238
  * panes each have a Monaco host and a card slot, and one slot would hand the second pane's stage
@@ -345,7 +346,6 @@ function drawStage(
345
346
  */
346
347
  function editorStageView(frame: "source" | "code"): CanvasStageView {
347
348
  return {
348
- columnHeader: "hidden",
349
349
  frame,
350
350
  framePart: "panzoom",
351
351
  frameVars: "",
@@ -514,6 +514,25 @@ function disposeDiffEditor(surface: CanvasSurface): void {
514
514
  surface.monacoDiffEditor = null;
515
515
  }
516
516
 
517
+ /**
518
+ * One artboard's marks, with a modification given the face that side wears.
519
+ *
520
+ * {@link ChangeMap} names a modification once, semantically; the two artboards draw it as the before
521
+ * and the after. The split happens here because this is the only place that knows which document
522
+ * each mark set is about to be painted on.
523
+ */
524
+ function sideMarks(
525
+ marks: readonly ChangeMark[] | undefined,
526
+ modified: "modified-before" | "modified-after",
527
+ ): WireDiffMarks | null {
528
+ return (
529
+ marks?.map((mark) => ({
530
+ kind: mark.kind === "modified" ? modified : mark.kind,
531
+ path: mark.path,
532
+ })) ?? null
533
+ );
534
+ }
535
+
517
536
  /**
518
537
  * Mount the comparison's Monaco diff editor: HEAD on the left, the working copy on the right.
519
538
  *
@@ -532,40 +551,10 @@ function disposeDiffEditor(surface: CanvasSurface): void {
532
551
  * A RETARGET is the second, independent race, and `mountStillWanted` cannot catch it: it answers
533
552
  * false when the slot is already filled, so a comparison switching to a different file would keep
534
553
  * the old editor and its claimed URIs. That one is disposed synchronously, before the await.
535
- */
536
- /**
537
- * Whether this pane draws the comparison as TEXT.
538
554
  *
539
- * One definition, because two callers must never disagree about it: the render branch chooses which
540
- * half to build, and the mount's post-await guard re-asks whether that half is still wanted. They
541
- * were two spellings — the branch said "not renderable OR the author chose Code", the guard said
542
- * only "the author chose Code" — so for a file with no visual half the branch built the Code
543
- * container and the guard then refused to mount into it. An empty stage, no error, and a toolbar
544
- * correctly describing an editor that was never there.
555
+ * The guard asks {@link diffShowsCode}, the same predicate the render branch chose the half with
556
+ * (`canvas/diff-view.ts` says why there is only one).
545
557
  */
546
- /**
547
- * One artboard's marks, with a modification given the face that side wears.
548
- *
549
- * {@link ChangeMap} names a modification once, semantically; the two artboards draw it as the before
550
- * and the after. The split happens here because this is the only place that knows which document
551
- * each mark set is about to be painted on.
552
- */
553
- function sideMarks(
554
- marks: readonly ChangeMark[] | undefined,
555
- modified: "modified-before" | "modified-after",
556
- ): WireDiffMarks | null {
557
- return (
558
- marks?.map((mark) => ({
559
- kind: mark.kind === "modified" ? modified : mark.kind,
560
- path: mark.path,
561
- })) ?? null
562
- );
563
- }
564
-
565
- function diffShowsCode(paneId: string, filePath: string): boolean {
566
- return !canRenderComparison(filePath) || diffViewOf(paneId) === "code";
567
- }
568
-
569
558
  async function mountDiffEditor(
570
559
  surface: CanvasSurface,
571
560
  container: Element,
@@ -581,7 +570,8 @@ async function mountDiffEditor(
581
570
  !mountStillWanted(
582
571
  container,
583
572
  surface.monacoDiffEditor,
584
- () => canvasModeOfPane(paneId) === "git-diff" && diffShowsCode(paneId, key),
573
+ () =>
574
+ canvasModeOfPane(paneId) === "git-diff" && diffShowsCode(paneId, canRenderComparison(key)),
585
575
  )
586
576
  ) {
587
577
  return;
@@ -629,6 +619,7 @@ function resetCanvasView(surface: CanvasSurface) {
629
619
  surface.centerObserver.disconnect();
630
620
  surface.centerObserver = null;
631
621
  }
622
+ releaseStageScrollbar(surface);
632
623
  for (const p of surface.panels) {
633
624
  p.renderScope?.stop();
634
625
  p.renderScope = null;
@@ -641,6 +632,7 @@ function resetCanvasView(surface: CanvasSurface) {
641
632
  canvasWrap.style.padding = "";
642
633
  canvasWrap.style.alignItems = "";
643
634
  canvasWrap.style.flexDirection = "";
635
+ canvasWrap.style.gap = "";
644
636
  canvasWrap.style.display = "";
645
637
  canvasWrap.style.overflow = "";
646
638
  dismissBlockActionBar();
@@ -1029,23 +1021,24 @@ function renderCanvasImpl(surface: CanvasSurface) {
1029
1021
  // Re-rendering the previous mode's panels in place.
1030
1022
  const canvasMode = canvasModeOfPane(surface.paneId);
1031
1023
 
1032
- /* The Document Header card (§3.2 ⑧) is drawn by the STAGE, not by a band above it. Two surfaces
1033
- draw a page you can author: the centered Edit column, where the card goes INSIDE the document
1034
- column and scrolls with the artefact, and the Design artboards, where it is pinned above the
1035
- panzoom surface because a form drawn at the artboard's scale is a picture of a form, not a
1036
- control. `hasDocumentHeader` is the only remaining predicate and it is a fact about the
1037
- DOCUMENT — the one condition left, because the mode is the only other thing that decides
1038
- whether a page is being authored. It used to carry two more clauses, suppressing the card while
1039
- a function body or a formula was open on the grounds that those sub-editors took the whole
1040
- stage. They open in the dock's Logic tab now (P8) and the page is still on screen behind it, so
1041
- those clauses only DETACHED the visible card: it stopped re-rendering and quietly showed
1042
- frontmatter from before the edit. */
1024
+ /* The Document Header card is drawn by the STAGE, and only by Edit's. It is docked
1025
+ above the page's scroller, a band across the pane at 1:1: its width is the PANE's rather than
1026
+ the breakpoint's, and the page scrolls under it rather than taking it along. It used to go
1027
+ INSIDE Edit's column, where it was exactly as wide as whatever breakpoint or dragged width the
1028
+ column had and scrolled off with the first screen of the page, and it was pinned above Design's
1029
+ artboards as well. Design omits it now, so the artboards get the whole pane, and nothing is
1030
+ lost by that: the Navigator's Page panel carries the same fields in every mode.
1031
+ `hasDocumentHeader` is the other predicate, and it is a fact about the DOCUMENT. It used to
1032
+ carry two more clauses, suppressing the card while a function body or a formula was open on
1033
+ the grounds that those sub-editors took the whole stage. They open in the dock's Logic tab now
1034
+ (§16.3) and the page is still on screen behind it, so those clauses only DETACHED the visible
1035
+ card: it stopped re-rendering and quietly showed frontmatter from before the edit. */
1043
1036
  /* …and NOT in a lens. The Document Header is an editing surface — Title, Route, SEO, the raw
1044
1037
  frontmatter — over a document this pane does not own, and two cards editing one document's
1045
1038
  frontmatter side by side is two writers for one field. A lens is a view; the card belongs to
1046
1039
  the pane that owns the tab. */
1047
1040
  const wantsDocHeader =
1048
- (canvasMode === "edit" || canvasMode === "design") &&
1041
+ canvasMode === "edit" &&
1049
1042
  derivationOfPane(surface.paneId)?.kind !== "lens" &&
1050
1043
  hasDocumentHeader(tab);
1051
1044
  if (!wantsDocHeader) {
@@ -1084,9 +1077,9 @@ function renderCanvasImpl(surface: CanvasSurface) {
1084
1077
 
1085
1078
  /* There are no `editingFunction` / `editingFormula` branches here, and that is the point.
1086
1079
  Both used to RETURN from this function, which is what made the logic editors a takeover: the
1087
- stage kept whatever DOM it was last painted with and stopped tracking the document. P8 moved
1088
- both into the dock's Logic tab, `panels/bottom-dock.ts` reveals it from its own effect, and
1089
- `panels/editors.ts`'s `syncFunctionEditor` owns the Monaco instance from the panel's
1080
+ stage kept whatever DOM it was last painted with and stopped tracking the document. Both open
1081
+ in the dock's Logic tab now (§16.3), `panels/bottom-dock.ts` reveals it from its own effect,
1082
+ and `panels/editors.ts`'s `syncFunctionEditor` owns the Monaco instance from the panel's
1090
1083
  `afterRender` — including disposing it. Nothing about the canvas render depends on a logic
1091
1084
  target any more, so the page underneath the dock keeps rendering while you edit a formula. */
1092
1085
 
@@ -1235,12 +1228,15 @@ function renderCanvasImpl(surface: CanvasSurface) {
1235
1228
  surface.centerObserver.disconnect();
1236
1229
  surface.centerObserver = null;
1237
1230
  }
1231
+ /* Edit's scroller leaves with its stage, and the pod must not keep Edit's inset in a mode that
1232
+ has no scrollbar under it: missed here, Design's pod would float 15px short of its corner. */
1233
+ releaseStageScrollbar(surface);
1238
1234
 
1239
1235
  // Destroy the grid panel if switching away from grid mode
1240
1236
  detachGridPanel(surface.paneId);
1241
1237
 
1242
1238
  // The Library holds an IntersectionObserver and an LRU of live runtime subtrees; leaving the
1243
- // Mode without releasing them is exactly the unbounded retention P7.1 exists to remove.
1239
+ // Mode without releasing them is exactly the unbounded retention its LRU cap exists to remove.
1244
1240
  detachLibraryPane(surface.paneId);
1245
1241
 
1246
1242
  // Same for the entry form, whose effect scope subscribes to the document's frontmatter
@@ -1268,9 +1264,9 @@ function renderCanvasImpl(surface: CanvasSurface) {
1268
1264
  canvasWrap.style.padding = "";
1269
1265
  canvasWrap.style.alignItems = "";
1270
1266
  canvasWrap.style.flexDirection = "";
1267
+ canvasWrap.style.gap = "";
1271
1268
  canvasWrap.style.display = "";
1272
1269
  canvasWrap.style.overflow = "";
1273
- canvasWrap.style.overflow = "";
1274
1270
 
1275
1271
  // Dismiss open popovers/toolbars that are no longer relevant
1276
1272
  dismissBlockActionBar();
@@ -1342,7 +1338,6 @@ function renderCanvasImpl(surface: CanvasSurface) {
1342
1338
  void drawStage(
1343
1339
  surface,
1344
1340
  {
1345
- columnHeader: "hidden",
1346
1341
  frame: "boards",
1347
1342
  framePart: "preview-stage",
1348
1343
  frameVars: `--preview-w:${previewWidth}px`,
@@ -1362,7 +1357,7 @@ function renderCanvasImpl(surface: CanvasSurface) {
1362
1357
  return;
1363
1358
  }
1364
1359
 
1365
- /* Settings mode: the Project Settings document (plan §9.3). Same non-iframe-editor pattern as
1360
+ /* Settings mode: the Project Settings document (§17.1). Same non-iframe-editor pattern as
1366
1361
  grid and stylebook — the panel owns its own reactivity from here — and the reason the seven
1367
1362
  settings screenshots now crop `pane.primary` instead of `overlay.dialog:settings` without one
1368
1363
  manifest step changing. */
@@ -1468,7 +1463,7 @@ function renderCanvasImpl(surface: CanvasSurface) {
1468
1463
  a Diff lens, the Editor axis) and only this one sees every arrival. Forcing it here also
1469
1464
  leaves `diffView` alone, so a document opened after a `.css` still comes up Visual. */
1470
1465
  const renderable = canRenderComparison(gitDiffState.filePath ?? "");
1471
- if (diffShowsCode(surface.paneId, gitDiffState.filePath ?? "")) {
1466
+ if (diffShowsCode(surface.paneId, renderable)) {
1472
1467
  /* A null map is how the toolbar learns there is no visual half to offer: it draws Code as a
1473
1468
  static label rather than as half of a choice, and reports the line marks instead of a node
1474
1469
  count. Cleared rather than left, so a pane that compared a document and then a stylesheet
@@ -1504,7 +1499,6 @@ function renderCanvasImpl(surface: CanvasSurface) {
1504
1499
  const diffStage = drawStage(
1505
1500
  surface,
1506
1501
  {
1507
- columnHeader: "hidden",
1508
1502
  frame: "boards",
1509
1503
  framePart: "panzoom",
1510
1504
  frameVars: "",
@@ -1632,8 +1626,9 @@ function renderCanvasImpl(surface: CanvasSurface) {
1632
1626
  * that computes the SAME width it last committed skips the write — and the imperative value
1633
1627
  * stays. That is not hypothetical: drag a page down to 330px, open a different document in the
1634
1628
  * pane, and the new one is 330px wide too, because both passes computed the base width and lit
1635
- * wrote neither. One writer, applied after the render, is the fix (§9.4 of the UI guidelines);
1636
- * `applyEditZoom` below already has exactly this shape for exactly this reason.
1629
+ * wrote neither. One writer, applied after the render, is the fix
1630
+ * (studio-ui-guidelines.md §9.4); `applyEditZoom` below already has exactly this shape for
1631
+ * exactly this reason.
1637
1632
  */
1638
1633
  const entry = canvasPanelEntry(null, null, true);
1639
1634
  // A component-definition doc (root tag is a custom element) is a fragment, not a page: it should
@@ -1641,21 +1636,38 @@ function renderCanvasImpl(surface: CanvasSurface) {
1641
1636
  const rootTag = (S.document as { tagName?: unknown }).tagName;
1642
1637
  const isComponentDoc = typeof rootTag === "string" && rootTag.includes("-");
1643
1638
  const { featureToggles: editToggles } = S.ui;
1639
+ /* The stage stacks when it carries the card: the band first, the page's scroller below it.
1640
+ Set on every render rather than on the transition, because whether a document HAS a header
1641
+ changes with the tab and not with the mode. `editors.ts` and `formula-workspace.ts` already
1642
+ claim the column this way — `#canvas-wrap` is a row by default and each surface states its
1643
+ own axis. Stretched, because the band is as wide as the PANE: that is the whole difference
1644
+ between docking the card here and drawing it in the column, whose width is the breakpoint's
1645
+ or the drag's.
1646
+ And FLUSH: the cell's own 24px gap (`pane-grid.json`) between two side-by-side surfaces is
1647
+ dead space between a docked band and the box below it. The scroller clips its content at its
1648
+ top edge, so the gap put that edge 24px below the band's border with nothing drawn in
1649
+ between — the page ended at an invisible line and the band read as a hairline over one
1650
+ continuous surface. At 0 the clip happens AT the border, which is what makes the border read
1651
+ as the band's edge; the scroller's own 32px of top padding is the breathing room. */
1652
+ canvasWrap.style.flexDirection = wantsDocHeader ? "column" : "";
1653
+ canvasWrap.style.alignItems = wantsDocHeader ? "stretch" : "";
1654
+ canvasWrap.style.gap = wantsDocHeader ? "0px" : "";
1644
1655
  void drawStage(
1645
1656
  surface,
1646
1657
  {
1647
- /* The card is the column's FIRST child by contract (`tests/canvas-render.test.ts`, "Edit
1648
- puts it INSIDE the document column"). The two handles are not in the column at all: they
1649
- are `jx-split`s standing beside it in the canvas's row, because the canvas is the track
1650
- a splitter has to measure and the column is the thing being resized. */
1651
- columnHeader: wantsDocHeader ? "shown" : "hidden",
1658
+ /* The card LEADS the frame (`tests/canvas-render.test.ts`, "Edit docks it above the page's
1659
+ scroller"): a sibling of the scroller rather than a block of the column, so the page
1660
+ scrolls under it and the breakpoint does not size it. The two handles are not in the
1661
+ column either: they are `jx-split`s standing beside it in the canvas's row, because the
1662
+ canvas is the track a splitter has to measure and the column is the thing being
1663
+ resized. */
1652
1664
  frame: "boards",
1653
1665
  framePart: "edit-canvas",
1654
1666
  frameVars: "",
1655
1667
  handles: "shown",
1656
1668
  hug: isComponentDoc,
1657
1669
  innerPart: "edit-column",
1658
- lead: "none",
1670
+ lead: wantsDocHeader ? "header" : "none",
1659
1671
  panels: [entry.item],
1660
1672
  },
1661
1673
  [entry],
@@ -1664,7 +1676,13 @@ function renderCanvasImpl(surface: CanvasSurface) {
1664
1676
  return;
1665
1677
  }
1666
1678
  const editColumn = stageHost(surface, "edit-column");
1667
- entry.panel.scrollContainer = stageHost(surface, "edit-canvas") as HTMLElement;
1679
+ const scroller = stageHost(surface, "edit-canvas");
1680
+ entry.panel.scrollContainer = scroller as HTMLElement;
1681
+ /* The one mode whose stage SCROLLS under the zoom pod, so the one that tells the pane how
1682
+ wide its scrollbar is. The pod floats in the pane's chrome layer, which spans the gutter
1683
+ too, and without this it sat 3px into the track. A same-scroller repaint is a no-op, and
1684
+ every teardown below releases it back to 0. See `canvas/stage-scrollbar.ts`. */
1685
+ trackStageScrollbar(surface, scroller);
1668
1686
  // The one writer of the column's width — see the note above.
1669
1687
  if (editColumn) {
1670
1688
  editColumn.style.maxWidth = `${Math.round(columnWidth)}px`;
@@ -1696,13 +1714,9 @@ function renderCanvasImpl(surface: CanvasSurface) {
1696
1714
  canvasWrap.style.padding = "0";
1697
1715
  canvasWrap.style.overflow = "hidden";
1698
1716
  }
1699
- /* The stage stacks when it carries the card: header first, artboards below. Set on every render
1700
- rather than on the transition, because whether a document HAS a header changes with the tab and
1701
- not with the mode. `editors.ts` and `formula-workspace.ts` already claim the column this way —
1702
- `#canvas-wrap` is a row by default and each surface states its own axis. */
1703
- canvasWrap.style.flexDirection = wantsDocHeader ? "column" : "";
1704
- canvasWrap.style.alignItems = wantsDocHeader ? "stretch" : "";
1705
- const designLead = wantsDocHeader ? "header" : "none";
1717
+ /* No Document Header here, so the stage cell stays the row it is by default: the card is Edit's
1718
+ alone (see `wantsDocHeader`), and the transition into this mode has already cleared the two
1719
+ inline writes Edit makes to stack it. */
1706
1720
 
1707
1721
  const {
1708
1722
  sizeBreakpoints,
@@ -1727,14 +1741,13 @@ function renderCanvasImpl(surface: CanvasSurface) {
1727
1741
  void drawStage(
1728
1742
  surface,
1729
1743
  {
1730
- columnHeader: "hidden",
1731
1744
  frame: "boards",
1732
1745
  framePart: "panzoom",
1733
1746
  frameVars: "",
1734
1747
  handles: "hidden",
1735
1748
  hug: false,
1736
1749
  innerPart: "boards",
1737
- lead: designLead,
1750
+ lead: "none",
1738
1751
  panels: [entry.item],
1739
1752
  },
1740
1753
  [entry],
@@ -1793,14 +1806,13 @@ function renderCanvasImpl(surface: CanvasSurface) {
1793
1806
  const stageDrawn = drawStage(
1794
1807
  surface,
1795
1808
  {
1796
- columnHeader: "hidden",
1797
1809
  frame: "boards",
1798
1810
  framePart: "panzoom",
1799
1811
  frameVars: "",
1800
1812
  handles: "hidden",
1801
1813
  hug: false,
1802
1814
  innerPart: "boards",
1803
- lead: designLead,
1815
+ lead: "none",
1804
1816
  panels: panelEntries.map((e) => e.item),
1805
1817
  },
1806
1818
  panelEntries,
@@ -1968,6 +1980,7 @@ setSurfaceTeardown((surface) => {
1968
1980
  disposeSourceEditor(surface);
1969
1981
  surface.centerObserver?.disconnect();
1970
1982
  surface.centerObserver = null;
1983
+ releaseStageScrollbar(surface);
1971
1984
  surface.panzoomWrap = null;
1972
1985
  detachCanvasStage(surface);
1973
1986
  });
@@ -2109,10 +2122,11 @@ export function selectionCommands(): AnyCommand[] {
2109
2122
  requires: "an open document",
2110
2123
  when: (ctx) => ctx.document.open,
2111
2124
  /*
2112
- * No `aiTool`, by §12.4's fourth deletion rule (redundant with a tool the model has): the
2113
- * assistant's bridge runs THIS record by id as the selector step of every selection-level
2114
- * tool — `delete_node(paths)` is `selection.setPaths` then `selection.delete` — so a second
2115
- * selection tool beside `select_node` would be prompt cost with no second job.
2125
+ * No `aiTool`, by studio-ui-guidelines.md §12.4's fourth deletion rule (redundant with a
2126
+ * tool the model has): the assistant's bridge runs THIS record by id as the selector step of
2127
+ * every selection-level tool — `delete_node(paths)` is `selection.setPaths` then
2128
+ * `selection.delete` — so a second selection tool beside `select_node` would be prompt cost
2129
+ * with no second job.
2116
2130
  */
2117
2131
  /**
2118
2132
  * The idempotent SET for the whole selection, beside `selection.set`'s single path.
@@ -2,8 +2,8 @@
2
2
  /**
3
3
  * Canvas surfaces — one stage per pane, asked about the pane it belongs to.
4
4
  *
5
- * A pane is the unit of split, focus and zoom (§4.1), so it is also the unit of RENDER. The panels
6
- * a pass mounted, the host element they mounted into, the mode that pass was drawn in and the full
5
+ * A pane is the unit of split, focus and zoom (§18), so it is also the unit of RENDER. The panels a
6
+ * pass mounted, the host element they mounted into, the mode that pass was drawn in and the full
7
7
  * render a failed patch escalates to are all facts about ONE pane; they were app-level singletons
8
8
  * only because the shell had a single stage.
9
9
  *