@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
@@ -674,11 +674,17 @@ export function resetZoom(surface: CanvasSurface = activeCanvasSurface()) {
674
674
  * (`iframe-host.ts`'s `pointForRect` composes the frame offset and the empirical scale before it
675
675
  * hands a rect back), so one arithmetic serves a rect measured from a parent DOM element and a rect
676
676
  * measured inside a canvas iframe alike.
677
+ *
678
+ * **The viewport is the box that SCROLLS, not the stage.** Edit docks the Document Header card
679
+ * above its scroller, inside the same stage cell, so there the stage is the band plus the page and
680
+ * its centre is half a band too high: a reveal would land the node that far above the middle of
681
+ * what the author can actually see. {@link revealScroller} is that box wherever there is one, and
682
+ * where there is none (Design, Stylebook, a comparison) the stage is still the viewport.
677
683
  */
678
684
  function centeringOffset(surface: CanvasSurface, rect: { top: number; height: number }): number {
679
- const wrapRect = rectOf(surface.wrap);
680
- const elCenterY = rect.top + rect.height / 2 - wrapRect.top;
681
- return wrapRect.height / 2 - elCenterY;
685
+ const viewport = rectOf(revealScroller(surface) ?? surface.wrap);
686
+ const elCenterY = rect.top + rect.height / 2 - viewport.top;
687
+ return viewport.height / 2 - elCenterY;
682
688
  }
683
689
 
684
690
  /**
@@ -789,7 +795,7 @@ export type CanvasMode = (typeof CANVAS_MODES)[number];
789
795
  /** The base modes the preview flag composes with — the two the Canvas view axis shows it beside. */
790
796
  const PREVIEWABLE_BASE_MODES = new Set(["edit", "design"]);
791
797
 
792
- // ─── §4.2 axis 2 — the Canvas view ───────────────────────────────────────────
798
+ // ─── The Canvas view (§4.2) ──────────────────────────────────────────────────
793
799
  // `Edit │ Design` is a radio and `Preview` is a TOGGLE beside it, because that is what the storage
794
800
  // Has always been: `preview` is not a base mode, it is a flag that composes with one.
795
801
  //
@@ -826,8 +832,9 @@ interface ViewableTab {
826
832
  /**
827
833
  * The views this document supports, in axis order — the control's entries.
828
834
  *
829
- * Filtered by `capabilities.modes` for the same reason the editor-kind dropdown is (§4.2): a
830
- * permanently dead segment is a question the app refuses to answer.
835
+ * Filtered by `capabilities.modes` for the same reason the Editor control is (the pane context bar,
836
+ * `docs/studio/interface/tabs.md`): a permanently dead segment is a question the app refuses to
837
+ * answer.
831
838
  */
832
839
  export function canvasViewsFor(tab: ViewableTab): CanvasView[] {
833
840
  return CANVAS_VIEWS.filter((value) => tab.capabilities.modes.includes(value));
@@ -937,16 +944,16 @@ const PANZOOM_MODES = new Set(["design", "stylebook", "git-diff"]);
937
944
 
938
945
  const documentOpen = (ctx: { document: { open: boolean } }) => ctx.document.open;
939
946
 
940
- /** The three values `session.ui.previewColorScheme` may hold (spec §9.5). */
947
+ /** The three values `session.ui.previewColorScheme` may hold (spec.md §9.5). */
941
948
  const COLOR_SCHEMES = ["auto", "light", "dark"] as const;
942
949
 
943
950
  /**
944
951
  * The canvas view verbs — `setMode`, `setZoom`, `setEditZoom`.
945
952
  *
946
- * **These are the setters that retire `canvas.togglePreview`** (plan §13.3 clause 3). A toggle
947
- * cannot say which state it ends in, so six screenshots taken through it photographed whichever way
948
- * the default happened to point that week; `canvas.setMode { mode: "preview" }` means the same
949
- * thing twice in a row and from any starting state.
953
+ * **These are the setters that retire `canvas.togglePreview`** (the idempotence rule, §13.5). A
954
+ * toggle cannot say which state it ends in, so six screenshots taken through it photographed
955
+ * whichever way the default happened to point that week; `canvas.setMode { mode: "preview" }` means
956
+ * the same thing twice in a row and from any starting state.
950
957
  *
951
958
  * Three refusals, each a wrong picture that used to be accepted silently:
952
959
  *
@@ -1025,13 +1032,14 @@ export function canvasViewCommands(deps: CanvasCommandDeps): AnyCommand[] {
1025
1032
  requires: "an open document",
1026
1033
  when: documentOpen,
1027
1034
  /*
1028
- * Projected, and it was deleted once under §12.4's first rule (chrome). The rule is about a
1029
- * verb whose WHOLE effect is what the person is looking at, and this one's is not: the tree
1030
- * tools are gated on `editor.kind`, which is derived from the mode (`editorKindForMode`), so
1031
- * a document the person left in Code — or the model itself moved — refuses every tree write
1032
- * with "requires an open document whose element tree the canvas is editing", and without this
1033
- * verb the model had no way back to a state it can read. `run` acts on the active tab and
1034
- * the model reads the result: `after.editor.kind` is the same fact the gate reads.
1035
+ * Projected, and it was deleted once under studio-ui-guidelines.md §12.4's first rule
1036
+ * (chrome). The rule is about a verb whose WHOLE effect is what the person is looking at, and
1037
+ * this one's is not: the tree tools are gated on `editor.kind`, which is derived from the
1038
+ * mode (`editorKindForMode`), so a document the person left in Code — or the model itself
1039
+ * moved — refuses every tree write with "requires an open document whose element tree the
1040
+ * canvas is editing", and without this verb the model had no way back to a state it can
1041
+ * read. `run` acts on the active tab and the model reads the result: `after.editor.kind` is
1042
+ * the same fact the gate reads.
1035
1043
  */
1036
1044
  aiTool: {
1037
1045
  description:
@@ -1210,7 +1218,7 @@ export function canvasViewCommands(deps: CanvasCommandDeps): AnyCommand[] {
1210
1218
  title: "Set Edit Width",
1211
1219
  },
1212
1220
  /*
1213
- * ── The rendering context (§4.2's control ③) ──────────────────────────────────────────────
1221
+ * ── The rendering context (§6.2) ──────────────────────────────────────────────────────────
1214
1222
  *
1215
1223
  * Three setters for the three axes the Context popover offers. The popover wrote
1216
1224
  * `session.ui.activeMedia` / `previewColorScheme` / `showLayout` through `updateUi` directly, so
@@ -1218,11 +1226,11 @@ export function canvasViewCommands(deps: CanvasCommandDeps): AnyCommand[] {
1218
1226
  * assistant, and not bindable — in a design whose first principle is that a capability exists
1219
1227
  * as a `Command` record and every surface projects it.
1220
1228
  *
1221
- * SETTERS, not cycles. §5.3's keymap declares `⌘⌥↑`/`⌘⌥↓` and `⌘⌥⇧S` to cycle the size and
1222
- * scheme axes, and a chord carries no argument, so those chords need `next`/`prev` records of
1223
- * their own — a delta each, which is what §13's R1 forbids a screenshot from naming. Naming the
1224
- * state you end in works from every surface; the chords are a separate decision and are not
1225
- * made here.
1229
+ * SETTERS, not cycles. The cycle chords once proposed for the size and scheme axes
1230
+ * (`⌘⌥↑`/`⌘⌥↓`, `⌘⌥⇧S`) carry no argument, so they would need `next`/`prev` records of their
1231
+ * own — a delta each, which the shot contract's R1 (`scripts/screenshots/README.md`) forbids a
1232
+ * screenshot from naming. Naming the state you end in works from every surface; the chords are
1233
+ * a separate decision and are not made here (§6.2).
1226
1234
  */
1227
1235
  {
1228
1236
  args: {
@@ -1302,12 +1310,13 @@ export function canvasViewCommands(deps: CanvasCommandDeps): AnyCommand[] {
1302
1310
  * The language a pane RENDERS AS — and only that.
1303
1311
  *
1304
1312
  * Jx has no message catalogue: a translation is a different file in a different directory
1305
- * (site-architecture §13.3), so "show this page in French" is a navigation and the locale
1313
+ * (site-architecture.md §13.3), so "show this page in French" is a navigation and the locale
1306
1314
  * preset owns it. What is left over is genuinely a rendering context, and it is the half of
1307
- * §13.4 the canvas can show today: the artboard root's `lang` and `dir`. That is why this is a
1308
- * verb and not a label — an RTL locale mirrors the layout on screen. (§13.4's other half,
1309
- * `$page.locale` in the rendered state, is injected by the compiler and not by the canvas's
1310
- * `substitutePreviewParams`; previewing it is a separate change to the render path.)
1315
+ * site-architecture.md §13.4 the canvas can show today: the artboard root's `lang` and `dir`.
1316
+ * That is why this is a verb and not a label — an RTL locale mirrors the layout on screen.
1317
+ * (That section's other half, `$page.locale` in the rendered state, is injected by the
1318
+ * compiler and not by the canvas's `substitutePreviewParams`; previewing it is a separate
1319
+ * change to the render path.)
1311
1320
  *
1312
1321
  * It lives in this file rather than in `i18n/i18n-commands.ts` because `contextTab`, `paneArg`
1313
1322
  * and `repaint` are local closures here, and every rendering-context verb needs all three: the
@@ -1514,9 +1523,10 @@ export function canvasViewCommands(deps: CanvasCommandDeps): AnyCommand[] {
1514
1523
  when: documentOpen,
1515
1524
  /*
1516
1525
  * The route params and component test props live in a popover now, and a transient surface
1517
- * opens by COMMAND rather than by clicking (plan §13.2) — otherwise the one shot that types a
1518
- * test value would need a CSS selector to reach it, which §13's contract forbids outright.
1519
- * The pointer and the camera use the same door.
1526
+ * opens by COMMAND rather than by clicking (§6.2) — otherwise the one shot that types a test
1527
+ * value would need a CSS selector to reach it, which the shot contract
1528
+ * (`scripts/screenshots/README.md`) forbids outright. The pointer and the camera use the same
1529
+ * door.
1520
1530
  */
1521
1531
  run: (_commandCtx, args) => {
1522
1532
  // Resolve the tab first, so a pane with no document refuses before anything opens.
@@ -79,9 +79,27 @@ export function setDiffRepaint(repaint: (paneId: string) => void): void {
79
79
  _repaint = repaint;
80
80
  }
81
81
 
82
+ /**
83
+ * How this module asks the pane CHROME to redraw, injected by `studio.ts`.
84
+ *
85
+ * The zoom pod is not drawn over the Code half (`panels/pane-context.ts`, `podFor`), and the half
86
+ * lives in `diff-view.ts`, which is deliberately not reactive — so nothing would tell the pod the
87
+ * half had changed, and it would go on floating over Monaco until something unrelated repainted the
88
+ * bar. Injected rather than imported for {@link setDiffRepaint}'s reason: the chrome module is far
89
+ * outside this one's graph, and `studio.ts` is the one place that already holds both. Null until
90
+ * then, which is only ever a test that drives the toolbar without the shell.
91
+ */
92
+ let _chromeRepaint: (() => void) | null = null;
93
+
94
+ /** Register the chrome repaint. Called once, from `studio.ts`. */
95
+ export function setDiffChromeRepaint(repaint: () => void): void {
96
+ _chromeRepaint = repaint;
97
+ }
98
+
82
99
  /**
83
100
  * Rebuild the whole stage, because Visual and Code are different renders rather than different
84
- * styling: one draws two artboards on a pan/zoom surface, the other one Monaco.
101
+ * styling: one draws two artboards on a pan/zoom surface, the other one Monaco. And redraw the
102
+ * pane's chrome after it, because whether the zoom pod is drawn at all depends on the half.
85
103
  *
86
104
  * `prevCanvasMode` is nulled first — the documented "this stage's structure is stale" signal, the
87
105
  * same one `resetCanvasView` ends on. Without it the repaint sees `modeChanged === false` (the
@@ -90,6 +108,7 @@ export function setDiffRepaint(repaint: (paneId: string) => void): void {
90
108
  function repaintDiffStage(paneId: string): void {
91
109
  surfaceForPane(paneId).prevCanvasMode = null;
92
110
  _repaint(paneId);
111
+ _chromeRepaint?.();
93
112
  }
94
113
 
95
114
  /** Redraw one pane's toolbar in place, without touching its artboards. */
@@ -332,7 +351,7 @@ export function diffCommands(): AnyCommand[] {
332
351
  title: "Set Diff View",
333
352
  undo: "none",
334
353
  when: (ctx) => ctx.editor.kind === "diff",
335
- /* No `aiTool`, by §12.4's first deletion rule: chrome. */
354
+ /* No `aiTool`, by studio-ui-guidelines.md §12.4's first deletion rule: chrome. */
336
355
  run: (_ctx, args) => {
337
356
  // An idempotent SETTER, never a toggle: the screenshot contract refuses a `toggle*` id, and
338
357
  // A verb whose result depends on the state it is called in cannot be photographed honestly.
@@ -3,10 +3,11 @@
3
3
  *
4
4
  * **The store is a module-local `Map` keyed by pane**, the shape `_editWidths` in `edit-width.ts`
5
5
  * and `_fits` in `canvas-utils.ts` already use. The value is transient — it is rebuilt by the
6
- * render pass that computes the comparison — and its only readers are that pass, the stepper and
7
- * the diff header, so a field on `CanvasSurface` would buy nothing and a field on `shell` would be
8
- * an app-level slot for a per-pane fact, which is exactly the defect the Diff LENS was built to
9
- * avoid (`workspace/pane-derive.ts`, finding 4: one app-level `diffState` meant two panes drew one
6
+ * render pass that computes the comparison — and its only readers are that pass, the stepper, the
7
+ * diff header and the zoom pod (which asks only which half is showing), so a field on
8
+ * `CanvasSurface` would buy nothing and a field on `shell` would be an app-level slot for a
9
+ * per-pane fact, which is exactly the defect the Diff LENS was built to avoid
10
+ * (`workspace/pane-derive.ts`, finding 4: one app-level `diffState` meant two panes drew one
10
11
  * comparison).
11
12
  *
12
13
  * **Keyed by PANE, because two panes can compare two different files at once** — the Source Control
@@ -14,8 +15,8 @@
14
15
  * source tab's `session.ui`. `canvasModeOfPane` and `derivationOfPane` are keyed the same way for
15
16
  * the same reason.
16
17
  *
17
- * **This file is a LEAF.** It imports one type and nothing else, so the render pass, the stepper
18
- * and the header can all reach it without any of them reaching each other.
18
+ * **This file is a LEAF.** It imports one type and nothing else, so the render pass, the stepper,
19
+ * the header and the pane chrome can all reach it without any of them reaching each other.
19
20
  */
20
21
 
21
22
  import type { ChangeMap } from "./diff-marks";
@@ -102,11 +103,34 @@ export function diffViewOf(paneId: string): DiffView {
102
103
  return _records.get(paneId)?.view ?? "visual";
103
104
  }
104
105
 
106
+ /**
107
+ * Whether this pane draws its comparison as TEXT: the file has no visual half, or the author chose
108
+ * Code.
109
+ *
110
+ * One definition, because three callers must never disagree about it. `canvas-render.ts`'s branch
111
+ * chooses which half to build and its mount's post-await guard re-asks whether that half is still
112
+ * wanted; they were two spellings — the branch said "not renderable OR the author chose Code", the
113
+ * guard said only "the author chose Code" — so for a file with no visual half the branch built the
114
+ * Code container and the guard then refused to mount into it. An empty stage, no error, and a
115
+ * toolbar correctly describing an editor that was never there. The third is the zoom pod
116
+ * (`panels/pane-context.ts`), which is not drawn over the Code half: nothing there zooms, and it
117
+ * sat on Monaco's own scrollbar.
118
+ *
119
+ * **Renderability is an ARGUMENT** — `canRenderComparison(path)` from `panels/git-diff-open.ts` —
120
+ * so this file stays the leaf its header promises. Taking the path would have pulled the opener
121
+ * (and the media pane behind it) into everything that reads a pane's review state.
122
+ */
123
+ export function diffShowsCode(paneId: string, renderable: boolean): boolean {
124
+ return !renderable || diffViewOf(paneId) === "code";
125
+ }
126
+
105
127
  /**
106
128
  * Show the visual comparison or the code one.
107
129
  *
108
130
  * The caller pairs this with a repaint: the store is deliberately not reactive (its writers are a
109
131
  * render pass and a toolbar click, both of which already know to redraw), so nothing observes it.
132
+ * That repaint is the stage AND the pane chrome, because the zoom pod reads this through
133
+ * {@link diffShowsCode} — see `repaintDiffStage` in `diff-toolbar.ts`.
110
134
  */
111
135
  export function setDiffView(paneId: string, view: DiffView): void {
112
136
  recordFor(paneId).view = view;
@@ -209,7 +209,7 @@ export function patchDisturbsActiveEdit(forwardOps: JxDocOp[]): boolean {
209
209
  return false;
210
210
  }
211
211
 
212
- /** Consecutive quiet animation frames before the frame declares itself settled (§13.4). */
212
+ /** Consecutive quiet animation frames before the frame declares itself settled (§13.5). */
213
213
  export const IDLE_QUIET_FRAMES = 2;
214
214
 
215
215
  /**
@@ -466,7 +466,7 @@ export function startCanvasIframe(opts: {
466
466
  const heightObserver = ResizeObs ? new ResizeObs(() => postContentHeight()) : null;
467
467
 
468
468
  // ─── Cross-realm quiescence ─────────────────────────────────────────────────
469
- // The frame answers "have I settled?" instead of being polled (§13.4 condition 5). Nothing in the
469
+ // The frame answers "have I settled?" instead of being polled (§13.5). Nothing in the
470
470
  // Parent realm can look inside a cross-origin frame, so the alternative was `wait: {ms}` — and 115
471
471
  // Of those were 115 places a slow subsystem got answered with +500 ms and the wrong picture was
472
472
  // Accepted. Sampling lives HERE, next to the render that changes the answer.
@@ -203,7 +203,7 @@ interface HostState {
203
203
  * The frame's own last quiescence report (`{kind: "idle"}`), or null before the first one.
204
204
  *
205
205
  * Held PER HOST from the start. `shot.ts`'s "Studio's only child frame" is a coin flip the moment
206
- * P8 adds a second pane, and a global would have to be unpicked exactly then.
206
+ * a second pane exists, and a global would have to be unpicked exactly then.
207
207
  */
208
208
  idle: Omit<Extract<IframeToParent, { kind: "idle" }>, "kind"> | null;
209
209
  /**
@@ -607,7 +607,7 @@ function hostLabel(host: HostState): string {
607
607
  * Empty means every live frame's DOM reflects what this host told it, its fonts have loaded, no
608
608
  * animation is running and no image retry is in flight. Naming the blockers is the whole point:
609
609
  * `probe.idle()` rejects with this list, so a slow subsystem identifies itself instead of being
610
- * answered with `+500 ms` and a wrong capture (plan §13.4).
610
+ * answered with `+500 ms` and a wrong capture ("Determinism", `scripts/screenshots/README.md`).
611
611
  */
612
612
  export function canvasIdleBlockers(): string[] {
613
613
  const blockers: string[] = [];
@@ -660,8 +660,8 @@ export function canvasIdleBlockers(): string[] {
660
660
  *
661
661
  * The caller gets a point it can act on directly — click it, scroll to it, anchor a popover to it —
662
662
  * without knowing that a canvas iframe, a panzoom transform and an edit-zoom scale sit between the
663
- * document and the screen. P4.6's find-references jump, P8.4's jump bar and collab follow-peer all
664
- * want exactly this, and so does anything driving Studio from outside.
663
+ * document and the screen. The find-references jump, the jump bar and collab follow-peer all want
664
+ * exactly this, and so does anything driving Studio from outside.
665
665
  */
666
666
  export interface CanvasPoint {
667
667
  x: number;
@@ -1551,7 +1551,7 @@ function requestPresence(host: HostState): void {
1551
1551
  if (!structuralSelection || peer.state.focusedPath !== tab?.documentPath) {
1552
1552
  continue;
1553
1553
  }
1554
- // A peer publishes their whole selection SET (§6.5). Every path gets its own box under the
1554
+ // A peer publishes their whole selection SET (§6.7). Every path gets its own box under the
1555
1555
  // Same name and colour — the meta map is keyed by path, so a peer selecting six nodes draws
1556
1556
  // Six boxes and a peer selecting one draws exactly the one box it always did.
1557
1557
  for (const path of structuralSelection) {
@@ -294,9 +294,9 @@ export function startInteraction(
294
294
  }
295
295
  const hit = nearestHit(e.target);
296
296
  if (hit) {
297
- // Ctrl/Cmd is the accumulate gesture (§6.5). The iframe reports the modifier and nothing
298
- // Else — it holds no selection state, so what a modified click MEANS is the parent's to
299
- // Decide, exactly as the unmodified click already was.
297
+ // Ctrl/Cmd is the accumulate gesture (studio-ui-guidelines.md §8.1). The iframe reports the
298
+ // Modifier and nothing else — it holds no selection state, so what a modified click MEANS is
299
+ // The parent's to decide, exactly as the unmodified click already was.
300
300
  const mouse = e as MouseEvent;
301
301
  channel.post({
302
302
  additive: mouse.ctrlKey === true || mouse.metaKey === true,
@@ -427,9 +427,10 @@ export function startInteraction(
427
427
  *
428
428
  * This called `preventDefault()` before looking, "legacy parity" with a handler that did the
429
429
  * same. The parent then posts `path: null`, `showContextMenu` returns early on it, and the
430
- * result is a right-click that suppresses the browser menu and shows nothing in its place:
431
- * plan §10's dead zone, named there as the thing to fix. The margin around the artboard is
432
- * exactly where a reader reaches for View Source or Inspect.
430
+ * result is a right-click that suppresses the browser menu and shows nothing in its place, a
431
+ * dead zone where the docs promise the browser's own menu
432
+ * (`docs/studio/interface/canvas.md#the-right-click-context-menu`). The margin around the
433
+ * artboard is exactly where a reader reaches for View Source or Inspect.
433
434
  */
434
435
  if (!hit) {
435
436
  return;
@@ -71,7 +71,7 @@ export interface OverlayLayer {
71
71
  */
72
72
  setPresence: (items: { placement: OverlayPlacement; color: string; label: string }[]) => void;
73
73
  /**
74
- * Draw the NON-primary members of a multi-selection (§6.5). Replaces the whole set each call;
74
+ * Draw the NON-primary members of a multi-selection (§6.7). Replaces the whole set each call;
75
75
  * pass [] to clear, which is what a selection of one always does — one selected node draws the
76
76
  * selection box and nothing else, exactly as it did before the selection became a list.
77
77
  */
@@ -122,7 +122,7 @@ export type ParentToIframe =
122
122
  assets?: AssetContext | null;
123
123
  mapperCtx: WireMapperCtx;
124
124
  siteStyle: Record<string, unknown> | null;
125
- // Forced color-scheme preview (spec §9.5): "light"/"dark" sets data-color-scheme on the
125
+ // Forced color-scheme preview (spec.md §9.5): "light"/"dark" sets data-color-scheme on the
126
126
  // Iframe root, null clears it (auto — follow the OS).
127
127
  colorScheme: "light" | "dark" | null;
128
128
  /**
@@ -478,13 +478,13 @@ export type IframeToParent =
478
478
  // Not the parent, now resolves the scope). `gen` lets the parent drop a snapshot from a superseded
479
479
  // Render. Posted right after `renderComplete`; values are JSON-safe deep clones (see serialize-scope).
480
480
  | { kind: "dataScope"; gen: number; scope: Record<string, unknown> }
481
- // The frame's own quiescence, ANSWERED rather than polled (spec §13.5, plan §13.4 condition 5).
481
+ // The frame's own quiescence, ANSWERED rather than polled (studio.md §13.5).
482
482
  // Nothing in the parent realm can see inside a cross-origin frame, so a parent that wanted to
483
483
  // Know whether the canvas had settled could only sleep. The frame samples itself at its own rAF
484
484
  // And posts whenever the tuple changes, ending with the quiet one; the host holds the latest.
485
485
  //
486
486
  // `fonts` is `document.fonts.status === "loaded"` and is NOT sufficient alone — in a blank canvas
487
- // Frame it reports "loaded" against an EMPTY font set (measured in S0: `hero` drifted 0.150 RMSE
487
+ // Frame it reports "loaded" against an EMPTY font set (measured: `hero` drifted 0.150 RMSE
488
488
  // Because the first capture measured fallback metrics while Plus Jakarta Sans was still in
489
489
  // Flight). It is honest only together with `gen` (a frame that has not acked a render has not
490
490
  // Loaded its fonts either) and the runner's per-frame network count.
@@ -496,8 +496,9 @@ export type IframeToParent =
496
496
  // Pending retry is the one image fact only the app knows.
497
497
  | { kind: "idle"; gen: number; fonts: boolean; animations: number; images: number }
498
498
  // `additive` is Ctrl/Cmd being held at the moment of the click — the ACCUMULATE gesture, which
499
- // The parent turns into a toggle against `session.selection` (studio §6.5). Absent/false is a
500
- // Plain replace, which is what every canvas click was before the selection became a set.
499
+ // The parent turns into a toggle against `session.selection` (studio-ui-guidelines.md §8.1).
500
+ // Absent/false is a plain replace, which is what every canvas click was before the selection
501
+ // Became a set.
501
502
  | { kind: "hit"; hit: NodeHit; additive?: boolean }
502
503
  // A click on layout chrome (see {@link LayoutHit}). Distinct from `hit` because the target is not
503
504
  // In the page document at all: the parent adopts it as `view.layoutSelection` (which shows the
@@ -573,7 +573,7 @@ export function syncEditableRoot(container: HTMLElement, mode: CanvasMode): void
573
573
  * infer it from.
574
574
  *
575
575
  * Scoped deliberately: this describes the editing REGION. Per-block landmarks and a
576
- * keyboard-reachable block action bar are still missing (see specs/studio.md §4.5).
576
+ * keyboard-reachable block action bar are still missing (see specs/studio.md §4.4).
577
577
  */
578
578
  container.setAttribute("role", "textbox");
579
579
  container.setAttribute("aria-multiline", "true");
@@ -693,7 +693,7 @@ export function syncStylebookCss(doc: Document, mode: CanvasMode): void {
693
693
  * Apply the project's site style as a real stylesheet (replace-in-place): custom properties on
694
694
  * `:root`, plain properties on `body`, conditional blocks dual-emitted per the forced-scheme
695
695
  * contract. A stylesheet — not inline root properties — so `:root[data-color-scheme]` override
696
- * selectors can win (spec §9.5), and so removed tokens can't linger on reused iframes.
696
+ * selectors can win (spec.md §9.5), and so removed tokens can't linger on reused iframes.
697
697
  */
698
698
  export function applySiteStyle(
699
699
  siteStyle: Record<string, unknown> | null | undefined,
@@ -719,7 +719,7 @@ export function applySiteStyle(
719
719
 
720
720
  /**
721
721
  * Force or clear the color-scheme preview on the iframe's root element (the platform's
722
- * data-color-scheme contract, spec §9.5). Survives re-renders and patches — renders only replace
722
+ * data-color-scheme contract, spec.md §9.5). Survives re-renders and patches — renders only replace
723
723
  * the container's children, never the root element.
724
724
  */
725
725
  export function applyPreviewColorScheme(doc: Document, scheme: "light" | "dark" | null): void {
@@ -117,7 +117,7 @@ export function ensurePopoverRevealWatch(): () => void {
117
117
  const tab = activeTab.value;
118
118
  /* TRACKED: the selection, and the document its paths resolve against. Those are what the
119
119
  rule depends on, and a selection move is the only thing that should fire it. The whole
120
- SET is read joined, for the reason §6.5 gives: a bare property read would not re-trigger
120
+ SET is read joined, for the reason §6.7 gives: a bare property read would not re-trigger
121
121
  when the selection changes within the array. */
122
122
  void tab?.session.selection.map((path) => path.join("/")).join("|");
123
123
  void tab?.doc.document;
@@ -0,0 +1,112 @@
1
+ /// <reference lib="dom" />
2
+ /**
3
+ * Stage scrollbar — how wide the stage's own scrollbar is, published on the pane's cell.
4
+ *
5
+ * The zoom pod floats in the pane's chrome layer (`surfaces/pane-grid.json`, `[part="chrome"]`),
6
+ * and that layer spans the WHOLE lower cell, scrollbar gutter included. Edit's scroller is the
7
+ * stage document's `[part="edit-canvas"]`, which sits in the same cell with its native vertical
8
+ * scrollbar flush against the same edge. The pod was placed a fixed `--jx-space-4` (12px) in from
9
+ * that edge, and a classic scrollbar is 15px in Chromium on Linux, so the pod painted 3px into the
10
+ * track (more with its shadow) and swallowed clicks on it.
11
+ *
12
+ * **CSS cannot measure a scrollbar, and the pod's document cannot see the scroller.** The scroller
13
+ * belongs to `surfaces/canvas-stage.json`; the pod to `surfaces/pane-context.json`. So the
14
+ * renderer, which is the one module that knows which element scrolls in which mode, hands the
15
+ * scroller here, and this module writes its width onto the cell as {@link STAGE_SCROLLBAR_VAR}. The
16
+ * pod's margin adds it by cascade. It is the seam `--jump-bar-h` and `--pane-context-h` already
17
+ * use, run the other way: there each bar owns its answer and the stage reads it; here the stage
18
+ * owns its scroller and the chrome reads the answer.
19
+ *
20
+ * **On the CELL, never on `:root`**, for the reason `panels/jump-bar.ts` gives at the same seam:
21
+ * two panes have two stages, and one document-level number would push the other pane's pod in by a
22
+ * scrollbar it does not have. A stage outside any cell writes the root, which only happens in a
23
+ * test fixture.
24
+ *
25
+ * **Edit only, and 0 everywhere else by construction.** `canvas-render.ts` tracks the scroller from
26
+ * the Edit branch and releases it at every teardown (a mode transition, a full reset, the pane's
27
+ * own teardown), and a release writes `0px`. Design, Stylebook and a comparison clip their stage
28
+ * rather than scroll it, so the pod goes back to its plain inset there.
29
+ *
30
+ * The observer writes a custom property on the cell. That moves the pod, which is in the chrome
31
+ * layer, and never the observed scroller, so there is no resize loop.
32
+ */
33
+
34
+ import { PANE_SELECTOR } from "../surfaces/pane-grid";
35
+ import type { CanvasSurface } from "./surface-registry";
36
+
37
+ /**
38
+ * The custom property the cell carries: the stage scroller's scrollbar width, in px.
39
+ *
40
+ * `surfaces/pane-context.json`'s `[part="pod"]` margin reads it, and
41
+ * `tests/stage-scrollbar.test.ts` joins the two names so renaming either side goes red.
42
+ */
43
+ export const STAGE_SCROLLBAR_VAR = "--stage-scrollbar-w";
44
+
45
+ /**
46
+ * How much of `scroller`'s width its vertical scrollbar takes.
47
+ *
48
+ * `offsetWidth` is the border box and `clientWidth` the padding box minus the scrollbar, so the
49
+ * difference is exactly the scrollbar for a box that draws no border — which `[part="edit-canvas"]`
50
+ * does not (`surfaces/canvas-stage.json`). Give it one and this would count the border too.
51
+ *
52
+ * An OVERLAY scrollbar (macOS, GTK's overlay mode) reserves no width and correctly answers 0, so
53
+ * those platforms keep the pod at its plain inset. So does a DOM with no layout engine.
54
+ *
55
+ * @param {HTMLElement} scroller
56
+ * @returns {number}
57
+ */
58
+ export function scrollbarWidthOf(scroller: HTMLElement): number {
59
+ return Math.max(0, scroller.offsetWidth - scroller.clientWidth);
60
+ }
61
+
62
+ /** The cell a stage belongs to, or the document root when it is outside every cell. */
63
+ function cellOf(surface: CanvasSurface): HTMLElement {
64
+ return surface.wrap?.closest<HTMLElement>(PANE_SELECTOR) ?? document.documentElement;
65
+ }
66
+
67
+ /**
68
+ * Publish `scroller`'s scrollbar width on this stage's cell, and keep it current.
69
+ *
70
+ * **Observed, not measured once.** The scroller's content box shrinks when a scrollbar appears and
71
+ * grows when it goes, so one `ResizeObserver` hears the page growing past the pane and shrinking
72
+ * back as well as every window, dock and split resize.
73
+ *
74
+ * **Idempotent for the scroller it already holds**, because Edit's `.then` runs on every
75
+ * content-only repaint and the stage document keeps its scroller across them: re-observing it each
76
+ * time would churn an observer for nothing. A DIFFERENT scroller (the stage changed hands) releases
77
+ * the old one first, and `null` only releases.
78
+ *
79
+ * @param {CanvasSurface} surface
80
+ * @param {HTMLElement | null} scroller The element that scrolls this stage, or null for none.
81
+ */
82
+ export function trackStageScrollbar(surface: CanvasSurface, scroller: HTMLElement | null): void {
83
+ if (surface.stageScrollbar?.scroller === scroller) {
84
+ return;
85
+ }
86
+ releaseStageScrollbar(surface);
87
+ if (!scroller) {
88
+ return;
89
+ }
90
+ const cell = cellOf(surface);
91
+ const write = () => {
92
+ cell.style.setProperty(STAGE_SCROLLBAR_VAR, `${scrollbarWidthOf(scroller)}px`);
93
+ };
94
+ const observer = new ResizeObserver(write);
95
+ observer.observe(scroller);
96
+ surface.stageScrollbar = { observer, scroller };
97
+ write();
98
+ }
99
+
100
+ /**
101
+ * Stop observing this stage's scroller and put the cell's offset back to 0. Idempotent.
102
+ *
103
+ * Safe while the surface has no host yet, because a surface is addressable before its stage exists
104
+ * and the teardown paths run for it all the same.
105
+ *
106
+ * @param {CanvasSurface} surface
107
+ */
108
+ export function releaseStageScrollbar(surface: CanvasSurface): void {
109
+ surface.stageScrollbar?.observer.disconnect();
110
+ surface.stageScrollbar = null;
111
+ cellOf(surface).style.setProperty(STAGE_SCROLLBAR_VAR, "0px");
112
+ }
@@ -2,8 +2,8 @@
2
2
  /**
3
3
  * The surface REGISTRY — one stage record per pane, and nothing that knows what a pane contains.
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
  *
@@ -67,6 +67,12 @@ export interface CanvasSurface {
67
67
  stage: CanvasStageHandle | null;
68
68
  /** The observer that re-centres this stage until the author pans it. One per stage. */
69
69
  centerObserver: ResizeObserver | null;
70
+ /**
71
+ * The observer publishing this stage's scroller's scrollbar width to its cell, and the scroller
72
+ * it watches. Edit only: it is the one mode whose stage scrolls under the zoom pod. See
73
+ * `canvas/stage-scrollbar.ts`.
74
+ */
75
+ stageScrollbar: { observer: ResizeObserver; scroller: HTMLElement } | null;
70
76
  /** Whether this stage still wants re-centring — cleared by the first deliberate pan. */
71
77
  needsCenter: boolean;
72
78
  panX: number;
@@ -164,6 +170,7 @@ function freshSurface(paneId: string): CanvasSurface {
164
170
  prevStylebookFilter: null,
165
171
  renderGeneration: 0,
166
172
  sourceCollabCleanup: null,
173
+ stageScrollbar: null,
167
174
  wrap: null as unknown as HTMLElement,
168
175
  };
169
176
  }
@@ -1,14 +1,15 @@
1
1
  /**
2
- * The `Collaborate:` command family — the entry point and the exit co-editing has never had (§7.4).
2
+ * The `Collaborate:` command family — the entry point and the exit co-editing has never had
3
+ * (collab.md §4).
3
4
  *
4
5
  * Collaboration in Studio has been a thing that HAPPENS to a document: a session attaches because
5
6
  * the platform said the file was shared, presence chips appear in the toolbar, and there is no verb
6
7
  * anywhere in the app for starting it, inviting anybody, following someone, or stopping. The
7
- * palette had no `Collaborate:` rows at all, and `CATEGORIES` has carried the name since P2 with
8
- * nothing filed under it.
8
+ * palette had no `Collaborate:` rows at all, and `CATEGORIES` has carried the name since the
9
+ * command registry landed with nothing filed under it.
9
10
  *
10
- * Five records, each registered here — in the module that owns the machinery, per P2's rule — and
11
- * composed by `commands/app-commands.ts`. Every one is idempotent or refuses with a `requires`
11
+ * Five records, each registered here — in the module that owns the machinery, as §13.1 requires —
12
+ * and composed by `commands/app-commands.ts`. Every one is idempotent or refuses with a `requires`
12
13
  * sentence, and none of them is a `toggle*` without a `set*` beside it: `collab.setEnabled` is the
13
14
  * setter, `collab.stop` is the same setter's `false` and exists because "stop sharing" is what a
14
15
  * person actually looks for in a palette.
@@ -216,7 +217,8 @@ export function collabCommands(): AnyCommand[] {
216
217
  }
217
218
  const state = collabState(active);
218
219
  /* This is the honesty command: every one of these states used to be either invisible or
219
- spelled "detached", and the freeze in particular looked exactly like a bug (§7.4). */
220
+ spelled "detached", and the freeze in particular looked exactly like a bug
221
+ (collab.md §4). */
220
222
  const lines = [
221
223
  `Status: ${state.status}${state.attachError ? ` — ${state.attachError}` : ""}`,
222
224
  `People here: ${state.peers.length === 0 ? "just you" : state.peers.map((p) => p.state.user.login).join(", ")}`,