@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
@@ -2,12 +2,12 @@
2
2
  /**
3
3
  * Dynamic route-param helpers for the tab-bar's $params picker.
4
4
  *
5
- * A page like `pages/products/[sku].json` declares its candidate values in a `$paths` block (spec
6
- * §4.3). At build time the compiler expands those into concrete routes (`resolvePathEntries` in
7
- * `@jxsuite/compiler`); in the studio the same shapes are resolved live so the author can pick one
8
- * value to render. The chosen values are substituted into the canvas render document
9
- * (`substitutePreviewParams`), which mirrors what the compiler's context injection produces for the
10
- * matching built page.
5
+ * A page like `pages/products/[sku].json` declares its candidate values in a `$paths` block
6
+ * (site-architecture.md §4.3). At build time the compiler expands those into concrete routes
7
+ * (`resolvePathEntries` in `@jxsuite/compiler`); in the studio the same shapes are resolved live so
8
+ * the author can pick one value to render. The chosen values are substituted into the canvas render
9
+ * document (`substitutePreviewParams`), which mirrors what the compiler's context injection
10
+ * produces for the matching built page.
11
11
  */
12
12
 
13
13
  import { documentUrlPattern as routePattern } from "@jxsuite/site/routes";
@@ -1,6 +1,6 @@
1
1
  /// <reference lib="dom" />
2
2
  /**
3
- * Activity — where a long operation lives while it runs, and what it leaves behind (plan §7.3).
3
+ * Activity — where a long operation lives while it runs, and what it leaves behind (§16.4).
4
4
  *
5
5
  * What this replaces: `ui/progress-modal.ts` was **the only surface in Studio with a real error
6
6
  * view** — a red headline, the captured log in a `<pre>`, an explicit Close — and it had four call
@@ -19,14 +19,14 @@
19
19
  * on its row, which is the affordance a hung install has never had;
20
20
  * - And it **fails into Problems** — {@link ActivityHandle.fail} raises a `notify.error` carrying the
21
21
  * log as its `detail`, so the failure outlives the run and carries a Retry command. That is
22
- * §7.3's "the progress modal's error view is promoted into Problems", in one function.
22
+ * §16.4's promotion of the progress modal's error view into Problems, in one function.
23
23
  *
24
24
  * **Nothing here blocks.** `showProgressModal` still exists for the four dependency-install sites
25
- * §7.3 keeps blocking, and it is now a thin front end over {@link beginActivity} — so even the
25
+ * §16.4 keeps blocking, and it is now a thin front end over {@link beginActivity} — so even the
26
26
  * blocking case leaves an inspectable entry behind, and closing the modal no longer loses it.
27
27
  *
28
28
  * `services/idle.ts` reads {@link activityIdleBlockers}: an open operation means the app is not
29
- * settled, which is the §13.6 S4 obligation every phase's checklist carries.
29
+ * settled, which is how §16.4 makes running activities a `probe.idle()` source.
30
30
  *
31
31
  * **The feed itself is a Jx document** (`surfaces/panel-activity.json`, mounted by
32
32
  * `surfaces/panel-activity.ts`). What stays here is the record and the vocabulary a row is written
@@ -154,7 +154,7 @@ export interface ActivityOptions {
154
154
  * Stop the operation.
155
155
  *
156
156
  * Its presence is what draws the Cancel button, so an operation that cannot honestly be stopped
157
- * must not pass one — a Cancel that does nothing is the failure §7 exists to end, restated as a
157
+ * must not pass one — a Cancel that does nothing is the failure §16 exists to end, restated as a
158
158
  * button.
159
159
  */
160
160
  cancel?: () => void;
@@ -176,7 +176,7 @@ export interface ActivityHandle {
176
176
  /**
177
177
  * Finish with a failure — and raise the Problem that outlives it.
178
178
  *
179
- * The log goes into the notification's `detail`, so the persistent, inspectable error view §7.3
179
+ * The log goes into the notification's `detail`, so the persistent, inspectable error view §16.4
180
180
  * asks for is the Problems row, not a modal that has to stay open to be read.
181
181
  */
182
182
  fail: (message: string, opts?: { action?: string; path?: string }) => void;
@@ -324,7 +324,7 @@ export function resetActivities(): void {
324
324
  /**
325
325
  * What is still running, as `services/idle.ts` phrases it.
326
326
  *
327
- * §13.6 S4 puts this on every phase's checklist: an app with an operation in flight has not
327
+ * §16.4 makes this a `probe.idle()` condition (§13.5): an app with an operation in flight has not
328
328
  * finished reacting, so a screenshot taken now photographs a half-done import. A FINISHED entry is
329
329
  * not a blocker — the list stays on screen forever and waiting for it to empty would never settle.
330
330
  */
@@ -449,8 +449,8 @@ const ACTIVITY_SURFACE: ActivitySurfaceDeps = {
449
449
  * Two documents, two hosts, and that is the seam rather than a layout: a document CLEARS the host
450
450
  * it is given and lit renders BESIDE foreign nodes, so a document and a lit template can never
451
451
  * share a container. The checklist belongs to `publish/deploy-checklist.ts` — a deploy is a long
452
- * operation with a log, which is why P4 folded it in here rather than giving it a fifth dock tab —
453
- * and it sits above the feed because its whole job is to say what is missing BEFORE you begin.
452
+ * operation with a log, which is why it was folded in here rather than given a fifth dock tab — and
453
+ * it sits above the feed because its whole job is to say what is missing BEFORE you begin.
454
454
  *
455
455
  * **`data-deploy-checklist` and `data-activity-surface` are the markers, and they are attributes
456
456
  * rather than classes for two reasons.** Nothing styles them, and a class no stylesheet defines is
@@ -500,7 +500,8 @@ export function registerActivityPanel(): void {
500
500
  dock: "bottom",
501
501
  icon: "clock-counter-clockwise",
502
502
  // No rail button: the Bottom dock's tabs are reached by ⌘J and `view.setBottomTab`, and a
503
- // Fifth rail slot for a surface with no steady state would spend chrome §2 principle 9 caps.
503
+ // Fifth rail slot for a surface with no steady state would spend chrome the budget caps
504
+ // (studio-ui-guidelines.md §12.2).
504
505
  rail: false,
505
506
  badge: () => (runningActivities().length > 0 ? runningActivities().length : null),
506
507
  render: () => renderActivityBody(),
@@ -4,17 +4,19 @@
4
4
  *
5
5
  * Message-row anatomy: user messages render as right-aligned bubbles (attached-context blocks
6
6
  * become chips), assistant messages render sanitized markdown plus tool-call chips, tool messages
7
- * surface failures only (ADR §11.3), the streaming tail renders as plain text with a cursor
7
+ * surface failures only, the streaming tail renders as plain text with a cursor
8
8
  * (markdown parses once on finalize), and chat errors get a danger row with recovery advice and a
9
9
  * Retry. **None of that is markup here any more** — `surfaces/ai-chat.json` draws it and this
10
10
  * module decides what it says, which is the same split the file always had with a document on the
11
11
  * other side of it instead of a lit template.
12
12
  *
13
- * §7.4 (AI honesty) is why three things here are not what they were:
13
+ * ai.md §3.2 (the accountable turn) is why three things here are not what they were:
14
14
  *
15
- * - **A chip renders its OUTCOME.** `ToolCallRecord.result` has always been populated by the loop
16
- * and ignored by this renderer, so a chip that said `update_style: ["children",0]` said exactly
17
- * as much when the edit had been refused as when it had landed.
15
+ * - **A chip renders its OUTCOME.** It reads `ToolCallRecord.result`, so a chip that said
16
+ * `update_style: ["children",0]` no longer says exactly as much when the edit was refused as when
17
+ * it landed. The live loop attaches each result as its call finishes (`appendToolResult`), and a
18
+ * restored chat backfills it from the tool message that answered the call
19
+ * (`services/tool-outcomes.ts`).
18
20
  * - **A turn renders what it CHANGED.** The changed-files summary comes off the write ledger
19
21
  * (`services/ai-writes.ts`), which records whether each change went through a transaction or
20
22
  * straight to disk — so the undo caveat is rendered to the human holding ⌘Z instead of being
@@ -22,8 +24,8 @@
22
24
  * - **An error offers Retry.** `chatState.retryLast()` has been implemented, exported and called by
23
25
  * nobody; the error row is where it belongs.
24
26
  *
25
- * §11.1 is why the three buttons here are not callbacks any more. History, New Chat and Retry were
26
- * closures this module received and invoked, so the capabilities existed ONLY as buttons: the
27
+ * ai.md §3.0 is why the three buttons here are not callbacks any more. History, New Chat and Retry
28
+ * were closures this module received and invoked, so the capabilities existed ONLY as buttons: the
27
29
  * `Assistant` category held zero records, and nothing could reach them from the palette, a chord,
28
30
  * the automation runner or the generated commands sheet. They are {@link projectCommand} now, in
29
31
  * the idiom `surfaces/statusbar.ts` established — the record is the definition site, and this file
@@ -116,6 +118,9 @@ export function formatErrorAdvice(error: string): string {
116
118
  if (lower.includes("500") || lower.includes("internal")) {
117
119
  return "The upstream API returned a server error. Try again in a moment.";
118
120
  }
121
+ if (lower.includes("empty reply")) {
122
+ return "Try again, or pick another model: some send back nothing for a request they cannot handle.";
123
+ }
119
124
  return "";
120
125
  }
121
126
 
@@ -191,10 +196,9 @@ export function tokenLabel(tokens: number): string {
191
196
  * The sentence behind the token readout.
192
197
  *
193
198
  * `services/context-manager.ts` has computed both numbers on every turn since it was written, and
194
- * `chat-state.ts` has stored them — with NO READER anywhere. Plan §11.6: "Context budget manager →
195
- * tokenCount / contextWarning actually rendered". So a conversation was silently trimmed, the
196
- * assistant forgot what you told it ten turns ago, and the two numbers that would have explained
197
- * why sat in the store.
199
+ * `chat-state.ts` has stored them — with NO READER anywhere. So a conversation was silently
200
+ * trimmed, the assistant forgot what you told it ten turns ago, and the two numbers that would have
201
+ * explained why sat in the store.
198
202
  *
199
203
  * @param {number} tokens
200
204
  * @param {boolean} overBudget
@@ -289,7 +293,16 @@ export function toolOutcomeText(tc: ToolCallRecord): string {
289
293
  if (!result) {
290
294
  return "";
291
295
  }
292
- return (result.success ? result.summary : result.error) ?? "";
296
+ const text = (result.success ? result.summary : result.error) ?? "";
297
+ /* An import's summary is written for the model: its first sentence says what happened ("Imported
298
+ <url> into <root> and opened it", or that it was not opened), and the rest instructs the model
299
+ what to read and ask next. The person reads the first sentence; the rest is not addressed to
300
+ them. The live chip draws the run's own log beside it. */
301
+ if (tc.name === IMPORT_TOOL && result.success) {
302
+ const end = /\.(?:\s|$)/.exec(text);
303
+ return end ? text.slice(0, end.index + 1) : text;
304
+ }
305
+ return text;
293
306
  }
294
307
 
295
308
  /** How a question card reaches the store that resolves it, and an import its record. */
@@ -535,8 +548,8 @@ export function projectRows(opts: MessageListOptions): ChatRowView[] {
535
548
  continue;
536
549
  }
537
550
  if (msg.role === "tool") {
538
- // Show only failed tool results so the user knows why an edit didn't land (ADR §11.3).
539
- // Successful tool results stay hidden to reduce noise.
551
+ /* Show only failed tool results so the user knows why an edit didn't land. Successful tool
552
+ results stay hidden to reduce noise; the chip on the call already states its outcome. */
540
553
  const parsed = tryParseToolResult(msg.content);
541
554
  if (parsed && !parsed.success) {
542
555
  rows.push({
@@ -40,6 +40,7 @@ import type { JxMutableNode } from "@jxsuite/schema/types";
40
40
  export interface ComposerOptions {
41
41
  /** Receives the full message content (typed text + serialized context). */
42
42
  onSend: (text: string) => void;
43
+ /** Whether a turn is in flight: the whole turn, tools included, not only its token stream. */
43
44
  isStreaming: () => boolean;
44
45
  /**
45
46
  * Whether the turn is suspended on a question. The composer becomes the answer field: Enter still
@@ -72,7 +73,10 @@ export interface Composer {
72
73
  view: () => ComposerView;
73
74
  /** The reader typed. */
74
75
  edit: (text: string) => void;
75
- /** Send what is typed, if anything is and nothing is already in flight. */
76
+ /**
77
+ * Send what is typed, if anything is. During a turn, only when a question waits: that send is its
78
+ * answer.
79
+ */
76
80
  send: () => void;
77
81
  clear: () => void;
78
82
  /** Open the attach menu under the button that was pressed. */
@@ -239,7 +243,8 @@ export function createComposer(opts: ComposerOptions): Composer {
239
243
  }
240
244
 
241
245
  function send() {
242
- if (!draft.trim() || opts.isStreaming()) {
246
+ // A send while a question waits is its answer, so a running turn refuses only an ordinary send.
247
+ if (!draft.trim() || (opts.isStreaming() && !(opts.isAwaiting?.() ?? false))) {
243
248
  return;
244
249
  }
245
250
  const text = buildMessageWithContext(draft, chips);
@@ -290,7 +295,9 @@ export function createComposer(opts: ComposerOptions): Composer {
290
295
  : "Ask the assistant… (Enter to send)",
291
296
  sendDisabled: draft.trim().length === 0,
292
297
  sendLabel: awaiting ? "Answer" : "Send",
293
- sendState: streaming ? "stop" : "send",
298
+ /* Answer, else Stop, else Send. A waiting question outranks Stop because the composer is its
299
+ answer field, and a turn in flight otherwise offers Stop for all of it, tools included. */
300
+ sendState: !awaiting && streaming ? "stop" : "send",
294
301
  };
295
302
  }
296
303
 
@@ -64,6 +64,7 @@ import type { AiChatSurface, AiChatView } from "../surfaces/ai-chat";
64
64
  import type { AnyCommand } from "../commands/registry";
65
65
  import type { CommandContext } from "../commands/context";
66
66
  import type { EffectScope } from "@vue/reactivity";
67
+ import type { Message } from "@jxsuite/ai/chat-state";
67
68
 
68
69
  // ─── State (module-level, persists across tab switches) ─────────────────────
69
70
 
@@ -136,11 +137,12 @@ let projectionQueued = false;
136
137
  * **Coalesced on a microtask, for two reasons that are not about paint.** A turn writes several
137
138
  * reactive facts in one tick — the message, the status, the token count — and the transcript
138
139
  * projection is O(messages), so running it once per write would rebuild the whole list three times
139
- * for one event. And the write LEDGER (`services/ai-writes.ts`) is a plain array: `endTurn` files
140
+ * for one event. And the write LEDGER (`services/ai-writes.ts`) is a plain array: `fileTurn` files
140
141
  * it immediately after the assistant message lands, so a projection that ran synchronously inside
141
- * the effect would read the turn's changed-files summary one write too early, every time. The old
142
- * frame loop hid both by accident of deferral; this states them. Precedent: `panels/overlays.ts`,
143
- * §9.3's second scheduler.
142
+ * the effect would read the turn's changed-files summary one write too early, every time. (When it
143
+ * lands later than that, after a Stop mid-stream, the send re-projects once the turn has settled.)
144
+ * The old frame loop hid both by accident of deferral; this states them. Precedent:
145
+ * `panels/overlays.ts`, studio-ui-guidelines.md §9.3's second scheduler.
144
146
  */
145
147
  export function renderAiPanel(): void {
146
148
  if (projectionQueued || !surface) {
@@ -187,8 +189,18 @@ function watchAssistant() {
187
189
  const last = cs.messages.at(-1);
188
190
  void last?.content;
189
191
  void last?.toolCalls?.length;
192
+ /* A call's result lands on its request's record once the tool has run, and the chip draws
193
+ it. By then the tail may be an earlier call's `tool` reply rather than the request, so the
194
+ request is found behind the replies, and every one of its records is tracked. */
195
+ for (const tc of latestRequest(cs.messages)?.toolCalls ?? []) {
196
+ void tc.result;
197
+ }
190
198
  void cs.status;
191
199
  void cs.error;
200
+ // The composer's Send/Stop follows the turn, which outlasts the stream's status.
201
+ void assistant.isTurnActive();
202
+ // A usage frame moves the token count and nothing else this effect reads.
203
+ void cs.tokenCount;
192
204
  // The registry is composed AFTER the bootstrap mounts this, and it is a reactive holder —
193
205
  // Reading it here is what turns the header's skeleton into its real buttons.
194
206
  void activeRegistry();
@@ -208,6 +220,22 @@ function watchAssistant() {
208
220
  });
209
221
  }
210
222
 
223
+ /**
224
+ * The assistant message the tool replies at the end of the transcript answer: the one whose chips a
225
+ * running tool's result is about to change.
226
+ *
227
+ * @param {readonly Message[]} messages
228
+ * @returns {Message | undefined}
229
+ */
230
+ function latestRequest(messages: readonly Message[]): Message | undefined {
231
+ for (let index = messages.length - 1; index >= 0; index--) {
232
+ if (messages[index]!.role !== "tool") {
233
+ return messages[index];
234
+ }
235
+ }
236
+ return undefined;
237
+ }
238
+
211
239
  // ─── Lifecycle ──────────────────────────────────────────────────────────────
212
240
 
213
241
  export function mountAiPanel() {
@@ -231,14 +259,20 @@ export function mountAiPanel() {
231
259
  * them is a 400. It travels as the tool result instead, and the question's own card renders it.
232
260
  */
233
261
  async function handleAssistantSend(text: string) {
234
- if (!text.trim() || assistant.chatState.status === "streaming") {
262
+ if (!text.trim()) {
235
263
  return;
236
264
  }
265
+ /* Answering comes first: a question is put by a turn that is still running, so a send while one
266
+ waits is its answer, never a second turn. */
237
267
  if (answerAsk(text.trim())) {
238
268
  surface?.pin();
239
269
  renderAiPanel();
240
270
  return;
241
271
  }
272
+ // One turn per window: a send during a turn, tools included, starts nothing.
273
+ if (isAssistantStreaming()) {
274
+ return;
275
+ }
242
276
  // A send always lands in the chat view, pinned to the newest message.
243
277
  view = "chat";
244
278
  surface?.pin();
@@ -248,11 +282,18 @@ async function handleAssistantSend(text: string) {
248
282
  } catch {
249
283
  // Synchronous failure (e.g. network unreachable) — the DocumentAssistant's
250
284
  // Own try/catch calls chatState.setError(), which the watcher renders.
285
+ } finally {
286
+ /* The turn's changes are filed in the loop's `finally`, and the ledger is not reactive. It is
287
+ usually filed in the same job as the turn's last reactive write, but not after a Stop
288
+ mid-stream: that write (`cancelStream`) lands microtasks before the aborted stream winds
289
+ down, so the coalesced projection has already drawn the reply without its summary. One
290
+ projection once the turn has settled is the one that is guaranteed to see it. */
291
+ renderAiPanel();
251
292
  }
252
293
  }
253
294
 
254
295
  /**
255
- * Re-send the last user message — §7.4's Retry.
296
+ * Re-send the last user message — ai.md §3.0's Retry.
256
297
  *
257
298
  * `chatState.retryLast()` pops the failed assistant turn AND the user message that caused it, on
258
299
  * the contract that the caller re-sends. It has been exported with zero callers since it was
@@ -272,7 +313,7 @@ async function handleRetry(): Promise<void> {
272
313
  }
273
314
 
274
315
  /**
275
- * Undo everything one assistant turn changed — §7.4's "Restore to here".
316
+ * Undo everything one assistant turn changed — ai.md §3.2's "Restore to here".
276
317
  *
277
318
  * The loop opens one batch per turn per document, so undoing the turn is undoing that batch. The
278
319
  * button is offered by the projection only when every recorded change was transactional; this guard
@@ -314,8 +355,19 @@ export function handleRestore(messageId: string): void {
314
355
  * project brief). Delegates to the same send path as the composer. Safe to call right after the
315
356
  * Assistant tab renders — the reactive watcher projects chat-state into the panel whenever it
316
357
  * mounts.
358
+ *
359
+ * **A seed waits for the window's turn, and is never refused.** One window runs one turn, and a
360
+ * turn outlives the call that ended it: New Chat stops the running one, but its loop unwinds
361
+ * afterwards, and a project switch does not stop it at all. A seed sent in that window was refused
362
+ * and lost without a word, taking the New Project hand-off with it. Waiting also keeps a seed from
363
+ * being taken as the answer to a question the old turn still had open.
317
364
  */
318
365
  export async function seedAssistantPrompt(text: string): Promise<void> {
366
+ /* One wait is enough: the turn's end settles in its loop's `finally`, and this resumes on the next
367
+ microtask, before any event could start another turn. */
368
+ if (assistant.isTurnActive()) {
369
+ await assistant.whenTurnEnds();
370
+ }
319
371
  await handleAssistantSend(text);
320
372
  }
321
373
 
@@ -461,15 +513,20 @@ export function isAssistantWaiting(): boolean {
461
513
  * Whether a turn is in flight — the probe `commands/live-context.ts` declares as `aiStreaming` and
462
514
  * projects onto `ctx.ai.streaming`.
463
515
  *
516
+ * The whole turn, not the token stream. The chat's `status` reads idle while a round's tools run,
517
+ * while a question waits and through a minutes-long import, so reading it here left Stop refused
518
+ * and Send offered in the middle of a turn, and a second send could start a second turn beside the
519
+ * first. The assistant's own turn fact is true from an accepted send until its loop ends.
520
+ *
464
521
  * That source was declared optional with the note "there is nothing to read yet; the caller passes
465
522
  * a probe when one exists", and no caller ever did — so `ctx.ai.streaming` read `false` forever and
466
- * `assistant.stop` would have been permanently refused. Reading the reactive chat state here is
467
- * what makes the fact LIVE: `createLiveContext` builds a fresh record per predicate evaluation, so
468
- * a surface projecting from an effect tracks this status and re-projects when the stream starts or
469
- * ends.
523
+ * `assistant.stop` would have been permanently refused. Reading the assistant's reactive turn flag
524
+ * here is what makes the fact LIVE: `createLiveContext` builds a fresh record per predicate
525
+ * evaluation, so a surface projecting from an effect tracks the flag and re-projects when a turn
526
+ * starts or ends.
470
527
  */
471
528
  export function isAssistantStreaming(): boolean {
472
- return assistant.chatState.status === "streaming";
529
+ return assistant.isTurnActive();
473
530
  }
474
531
 
475
532
  function stop() {
@@ -595,25 +652,24 @@ function projectPanel(): AiChatView {
595
652
  };
596
653
  }
597
654
 
598
- // ─── The `Assistant:` command family (§11.1) ────────────────────────────────
655
+ // ─── The `Assistant:` command family (ai.md §3.0) ───────────────────────────
599
656
 
600
657
  /**
601
658
  * Six records, beside the chat session they write.
602
659
  *
603
- * §11.1 pays for the chat column's demotion into an Inspector tab with a command family, and the
604
- * `Assistant` category — declared in `commands/levels.ts` since the taxonomy landed — held ZERO
605
- * records. Every capability below already existed and every one of them existed ONLY as a button in
606
- * this panel: not in the palette, not bindable, not reachable by `__jxAutomation` or by name, and
607
- * absent from the generated commands and shortcuts sheets. A capability that one surface can reach
608
- * and the registry cannot is the second definition site inverted.
660
+ * The chat column's demotion into an Inspector tab was paid for with a command family (ai.md §3.0),
661
+ * and the `Assistant` category — declared in `commands/levels.ts` since the taxonomy landed — held
662
+ * ZERO records. Every capability below already existed and every one of them existed ONLY as a
663
+ * button in this panel: not in the palette, not bindable, not reachable by `__jxAutomation` or by
664
+ * name, and absent from the generated commands and shortcuts sheets. A capability that one surface
665
+ * can reach and the registry cannot is the second definition site inverted.
609
666
  *
610
- * **All six are `level: "application"`, by principle 3 — a record is filed by the level of the
611
- * state it WRITES, not the state it reads.** The chat session is application state: it outlives the
612
- * open document, survives project close, and `assistant.attachSelection` is the case that proves
613
- * the rule — it READS the canvas selection and writes a chip into the composer, so it is
614
- * application, not selection, however selection-ish it looks. That is also why it may declare
615
- * `menus: ["palette"]` at all: `blockbar` and `context/element` admit selection-level records
616
- * only.
667
+ * **All six are `level: "application"`, by §13.2 — a record is filed by the level of the state it
668
+ * WRITES, not the state it reads.** The chat session is application state: it outlives the open
669
+ * document, survives project close, and `assistant.attachSelection` is the case that proves the
670
+ * rule — it READS the canvas selection and writes a chip into the composer, so it is application,
671
+ * not selection, however selection-ish it looks. That is also why it may declare `menus:
672
+ * ["palette"]` at all: `blockbar` and `context/element` admit selection-level records only.
617
673
  *
618
674
  * **No `aiTool` on any of them.** The assistant projecting "start a new chat" into its own tool
619
675
  * list would let a turn end its own conversation; the tier tables in `services/ai-tools.ts` gate
@@ -710,9 +766,11 @@ export function assistantCommands(): AnyCommand[] {
710
766
  menus: ["palette"],
711
767
  group: "2_turn",
712
768
  requires: "a turn in flight",
713
- /* The union, not just `streaming`. A turn suspended on `ask_user` moves no tokens, so
714
- `ctx.ai.streaming` reads false — and that is precisely the turn a reader who does not want
715
- to answer needs to end. */
769
+ /* `streaming` covers the whole turn, a waiting question included: a turn suspended on
770
+ `ask_user` moves no tokens, and it is precisely the turn a reader who does not want to
771
+ answer needs to end. A question is only ever put inside a turn, so `waiting` adds nothing
772
+ here; it stays in the union so a question could still be stopped if one were ever raised
773
+ without a turn. */
716
774
  enablement: (ctx: CommandContext) => ctx.ai.streaming || ctx.ai.waiting,
717
775
  run: stop,
718
776
  },
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * Block action bar — the floating toolbar above the selected element.
4
4
  *
5
- * The bar is a RENDERING of the command registry (plan §3.2 region ⑩, §5.5): its verb cluster is
5
+ * The bar is a RENDERING of the command registry (§4.4, §13): its verb cluster is
6
6
  * `registry.forPlacement("blockbar")`, sliced at {@link BLOCKBAR_MAX_ITEMS} with the remainder
7
7
  * behind a `⋮` overflow menu. Every button's accessible name is its record's `title` and its
8
8
  * tooltip carries `keymap.formatBinding(id)`, so no action is named twice.
@@ -19,8 +19,8 @@
19
19
  * whether each can act, where the bar belongs against the selection rect, when it steps aside, and
20
20
  * what a press does. The toolbar role, the roving caret and the ←/→/Home/End contract are
21
21
  * `jx-action-group`'s, so the hand-written `[data-toolbar-item]` ring is gone with the template
22
- * that needed it; the `⋮` menu is the kit menu every other Studio surface opens (§12.5), so there
23
- * is no second list of actions.
22
+ * that needed it; the `⋮` menu is the kit menu every other Studio surface opens
23
+ * (studio-ui-guidelines.md §12.5), so there is no second list of actions.
24
24
  *
25
25
  * This module is also the single definition site for the structural selection verbs — move
26
26
  * up/down/in/out and the component pair — because their implementations live here.
@@ -49,6 +49,7 @@ import {
49
49
  } from "../store";
50
50
  import { activeTab } from "../workspace/workspace";
51
51
  import { activeCanvasSurface, stageContaining } from "../canvas/canvas-surface";
52
+ import { panelOfSurface } from "../canvas/canvas-helpers";
52
53
  import { primarySelection, structuralBatch } from "../tabs/selection";
53
54
  import {
54
55
  mutateDuplicateNodes,
@@ -207,10 +208,13 @@ function repositionBlockActionBar(): void {
207
208
  });
208
209
  }
209
210
 
211
+ /** The bar's own height plus its 4px stand-off: the room it needs ABOVE an anchor to stand there. */
212
+ const BAR_CLEARANCE = 38;
213
+
210
214
  /**
211
- * The bar's position for `anchor` (flipping below the element near the top edge), or null when the
212
- * anchor's vertical extent has left the canvas area — the popover layer has no clipping, so a
213
- * clamped bar would float detached over the app toolbar / panel headers.
215
+ * The bar's position for `anchor` (flipping below the element when it cannot stand above it), or
216
+ * null when the anchor's vertical extent has left the canvas area — the popover layer has no
217
+ * clipping, so a clamped bar would float detached over the app toolbar / panel headers.
214
218
  *
215
219
  * @param {{ left: number; top: number; height: number }} anchor
216
220
  */
@@ -224,17 +228,39 @@ function barPosition(anchor: {
224
228
  stage — and the pane is the FOCUSED one, because that is where the caret is. This asked
225
229
  "the stage showing the active tab", which was the same answer only while a tab could be on
226
230
  screen in one place; with a document displayed in two panes it picked whichever came first in
227
- the grid, and clipped the bar against a stage the author is not typing in. */
228
- const stage = activeCanvasSurface().wrap;
229
- if (stage) {
230
- const wrap = rectOf(stage);
231
- if (anchor.top + anchor.height < wrap.top || anchor.top > wrap.bottom) {
232
- return null;
233
- }
231
+ the grid, and clipped the bar against a stage the author is not typing in.
232
+ …and against the box that SCROLLS, where the stage has one. Edit docks the Document Header
233
+ card above its scroller, inside the same stage cell, so a node scrolled up under the band is
234
+ still inside the stage while nobody can see it — and the bar would float over the card,
235
+ pointing at a node the card is covering. Everywhere else there is no scroller and the stage is
236
+ the viewport, as it always was. */
237
+ const surface = activeCanvasSurface();
238
+ const viewport = panelOfSurface(surface)?.scrollContainer ?? surface.wrap;
239
+ const box = viewport ? rectOf(viewport) : null;
240
+ if (box && (anchor.top + anchor.height < box.top || anchor.top > box.bottom)) {
241
+ return null;
242
+ }
243
+ /* ABOVE the anchor where the bar fits above it INSIDE that viewport, and BELOW it where it does
244
+ not. The flip used to be decided against a flat 80 in WINDOW coordinates, the app's own top
245
+ chrome, which is the same answer only while the viewport starts near the top of the window.
246
+ Edit's scroller starts under a band, so an anchor whose top had scrolled under the card while
247
+ its bottom was still on screen put the bar ON the card — over the fields it covers, taking
248
+ their clicks. Flipped is better than hidden: the element is visible, so its bar should be, and
249
+ past the clip above the anchor's bottom edge is inside the viewport, so the flipped bar is too.
250
+ The fallback is that same 80px of chrome, for a stage no pass has measured yet. */
251
+ const ceiling = box ? box.top : 80 - BAR_CLEARANCE;
252
+ const above = anchor.top - BAR_CLEARANCE;
253
+ if (above >= ceiling) {
254
+ return { left: anchor.left, top: above };
234
255
  }
256
+ /* Below it, and kept INSIDE the viewport: an element taller than what is left of the scroller has
257
+ its bottom edge off screen, and the bar would follow it there — over the status bar, or over
258
+ whatever the pane below is showing. Clamped, it stands on that element's last visible strip,
259
+ still pointing at it and still off the card. */
260
+ const below = anchor.top + anchor.height + 4;
235
261
  return {
236
262
  left: anchor.left,
237
- top: anchor.top < 80 ? anchor.top + anchor.height + 4 : anchor.top - 38,
263
+ top: box ? Math.max(box.top, Math.min(below, box.bottom - BAR_CLEARANCE)) : below,
238
264
  };
239
265
  }
240
266
 
@@ -276,16 +302,16 @@ function clampBarToWindow(bar: HTMLElement): void {
276
302
  * `formatBold` because Jx owns its markup. So ⌘B in the canvas did nothing at all, in an app whose
277
303
  * toolbar advertised it.
278
304
  *
279
- * They are the case UX-REDESIGN-PLAN §5.1 uses to justify two fields, so they are written the way
280
- * it says: `level: "selection"` — a range inside the selected node is still the selection, and a
281
- * fifth `range` level would demand a fifth region — with `keyScope: "caret"`, which is what makes
282
- * the chord live only where a caret is. They were, until this change, the only capability in Studio
283
- * with a chord and no record.
305
+ * They are the case that justifies the record's two fields (specs/studio.md §13.2–§13.3), so they
306
+ * are written the way it says: `level: "selection"` — a range inside the selected node is still the
307
+ * selection, and a fifth `range` level would demand a fifth region — with `keyScope: "caret"`,
308
+ * which is what makes the chord live only where a caret is. They were, until this change, the only
309
+ * capability in Studio with a chord and no record.
284
310
  *
285
311
  * **The set is the schema's, the verbs are the registry's.** `data/elements-meta.json` says which
286
312
  * of the eight a given tag accepts (`$inlineActions`), so the bar draws four on an `<h1>` and eight
287
313
  * on a `<p>`; what each one is CALLED, which chord it holds and what it does are on the record.
288
- * That is the same division P5.3 made for value sources — caps derive from the schema, behaviour
314
+ * That is the same division §6.6 makes for value sources — caps derive from the schema, behaviour
289
315
  * does not.
290
316
  *
291
317
  * `when` is {@link CommandContext.caret.inCanvas} and not `caret.active`: the caret stack is live
@@ -654,9 +680,9 @@ const hasTarget = (ctx: CommandContext) => ctx.selection.count > 0;
654
680
  * (Duplicate, from `commands/defaults.ts`) and `9_danger` (Delete, likewise), which is the order
655
681
  * the bar and the Outline rows both render in.
656
682
  *
657
- * No `keybinding` is claimed here on purpose. The chord table is `editor/shortcuts.ts`'s port (plan
658
- * P2 workstream 4); claiming chords from a panel would decide that port's outcome by accident. The
659
- * buttons still print the chords of the records that DO carry them.
683
+ * No `keybinding` is claimed here on purpose. The chord table belongs to `editor/shortcuts.ts`'s
684
+ * scope-stack dispatcher (§13.3); claiming chords from a panel would decide its contents by
685
+ * accident. The buttons still print the chords of the records that DO carry them.
660
686
  */
661
687
  export function registerSelectionCommands(
662
688
  registry: CommandRegistry,
@@ -828,7 +854,7 @@ export function selectionCommandRegistry(): CommandRegistry {
828
854
  return registry;
829
855
  }
830
856
 
831
- /** How many verb buttons the bar renders before the rest fold into `⋮` (plan §3.2 ⑩). */
857
+ /** How many verb buttons render before the rest fold into `⋮` (studio-ui-guidelines.md §12.1). */
832
858
  export const BLOCKBAR_MAX_ITEMS = 5;
833
859
 
834
860
  /**
@@ -996,10 +1022,10 @@ export function dismissBlockBarOverflow(): void {
996
1022
  * Show the `⋮` menu under `anchor`. Rows carry the same names, chords and refusals as the buttons.
997
1023
  *
998
1024
  * Shared by the block action bar and the Outline rows, and it is the KIT menu
999
- * (`surfaces/menu.json`, §12.5) rather than a list of this surface's own: one popover, one roving
1000
- * caret, one typeahead, one Escape. `target` (the Outline row's node) is captured per row and
1001
- * re-applied when a row is chosen, because a row action acts on the row's node and the hovered row
1002
- * is not the selected one.
1025
+ * (`surfaces/menu.json`, studio-ui-guidelines.md §12.5) rather than a list of this surface's own:
1026
+ * one popover, one roving caret, one typeahead, one Escape. `target` (the Outline row's node) is
1027
+ * captured per row and re-applied when a row is chosen, because a row action acts on the row's node
1028
+ * and the hovered row is not the selected one.
1003
1029
  */
1004
1030
  export function showCommandOverflow(
1005
1031
  anchor: HTMLElement,
@@ -1192,10 +1218,10 @@ export function isLinkPopoverOpen(): boolean {
1192
1218
  * snapshot; Apply/Remove post `applyFormat` link intents the iframe applies.
1193
1219
  *
1194
1220
  * By RECORD ID, not by rendered text. This queried `sp-action-button[title^="Link"]`, which is the
1195
- * addressing §13's first rule bans in the screenshot manifest — a match on a title the app DERIVES
1196
- * (it now reads "Link… (⌘K)", and on Windows it reads something else again). The id is the input
1197
- * the app accepts, and the button is one the surface ANNOUNCED as it drew it rather than one this
1198
- * module re-finds.
1221
+ * addressing the shot contract's R1 (`scripts/screenshots/README.md`) bans in the screenshot
1222
+ * manifest — a match on a title the app DERIVES (it now reads "Link… (⌘K)", and on Windows it reads
1223
+ * something else again). The id is the input the app accepts, and the button is one the surface
1224
+ * ANNOUNCED as it drew it rather than one this module re-finds.
1199
1225
  */
1200
1226
  export function openLinkPopoverFromShortcut(): void {
1201
1227
  const surface = _surface;
@@ -1413,7 +1439,7 @@ function projectBar(): BlockBarView | null {
1413
1439
  (editingProp ? ` · ${editingProp}` : "");
1414
1440
  // The handle is chrome, not a verb, so it renders on every selection; only a node that actually
1415
1441
  // Sits at a child index can be dragged, and at the root it is a disabled affordance rather than a
1416
- // Missing one (§8.6: ONE shape).
1442
+ // Missing one (studio-ui-guidelines.md §8.6: ONE shape).
1417
1443
  const canDrag = structuralTarget(selection) !== null;
1418
1444
 
1419
1445
  return {