@jxsuite/studio 5.2.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 (398) hide show
  1. package/data/css-meta.json +28 -27
  2. package/dist/chunks/{cf-account-picker-h4dab9bq.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-sgg1sv6n.js → content-source-jt7rbcbc.js} +22 -22
  6. package/dist/chunks/{convert-to-repeater-mf1cxgfn.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-c6zwzbre.js → extension-sections-r73an5s6.js} +31 -30
  10. package/dist/chunks/{file-ops-vrpq1jjk.js → file-ops-3xan8mnj.js} +21 -21
  11. package/dist/chunks/{files-msaf95y6.js → files-egsb0ry8.js} +30 -29
  12. package/dist/chunks/{format-host-f64fnesp.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-bv5e22j0.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-paj67nr5.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-dh5np8vk.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/{jx-validate-j0wxk72w.js → jx-validate-epp2z3s3.js} +2 -2
  25. package/dist/chunks/layers-panel-83a16ytx.js +41 -0
  26. package/dist/chunks/{layers-1vcfdbet.js → layers-s6mc01ry.js} +3 -3
  27. package/dist/chunks/{listbox-6v2ac08j.js → listbox-b7jxnhq1.js} +19 -3
  28. package/dist/chunks/{media-picker-p49nhqhs.js → media-picker-nhn6rv4w.js} +11 -11
  29. package/dist/chunks/{menu-qht48vnc.js → menu-8j4qxjrp.js} +4 -4
  30. package/dist/chunks/{monaco-binding-sw6en4dw.js → monaco-binding-e8d9wzyk.js} +3 -3
  31. package/dist/chunks/{monaco-buffer-pykwbm92.js → monaco-buffer-jaq1t9zj.js} +2 -2
  32. package/dist/chunks/{monaco-setup-8vbyqnhr.css → monaco-setup-ey9cjcm3.css} +954 -137
  33. package/dist/chunks/{monaco-setup-8r62fj6y.js → monaco-setup-gsjdysze.js} +7 -7
  34. package/dist/chunks/{platform-ts9cg1a4.js → platform-fnchmhy7.js} +5 -3
  35. package/dist/chunks/publish-panel-ak6hxfma.js +27 -0
  36. package/dist/chunks/quick-search-c63mkt3b.js +47 -0
  37. package/dist/chunks/{references-h69xp9zz.js → references-f7bdv3z5.js} +3 -3
  38. package/dist/chunks/right-panel-t5n8wttg.js +45 -0
  39. package/dist/chunks/{split-g7nm1rgb.js → split-4cz4b6ef.js} +14 -3
  40. package/dist/chunks/{studio-sar8g54p.js → studio-01269reb.js} +8 -8
  41. package/dist/chunks/{studio-rnyf3f8e.js → studio-0a4gncx0.js} +3 -3
  42. package/dist/chunks/{studio-e1f24eff.js → studio-0d4p4wxj.js} +6778 -2993
  43. package/dist/chunks/{studio-4wbb9ct0.js → studio-1tejxvba.js} +3006 -1855
  44. package/dist/chunks/{studio-p6pmwkvh.js → studio-3nprwbd9.js} +147 -28
  45. package/dist/chunks/{studio-4c041r8y.js → studio-40z39dme.js} +3 -3
  46. package/dist/chunks/{studio-68fydsrj.js → studio-482eeazk.js} +20 -20
  47. package/dist/chunks/{studio-y4f9fdge.js → studio-4998spet.js} +10 -9
  48. package/dist/chunks/{studio-nq5yf5mp.js → studio-557bmn0x.js} +2 -2
  49. package/dist/chunks/{studio-hdc2jt3z.js → studio-5w26wza5.js} +3 -3
  50. package/dist/chunks/{studio-6nzaktvy.js → studio-6pwn9j8c.js} +6 -3
  51. package/dist/chunks/{studio-wnsyffg1.js → studio-6vg0x5fz.js} +3 -2
  52. package/dist/chunks/{studio-vn5hske5.js → studio-7d3c1m59.js} +12 -5
  53. package/dist/chunks/{studio-28vs5k2y.js → studio-9vsvz7w7.js} +7 -7
  54. package/dist/chunks/{studio-t3adrr56.js → studio-9wm0xzpf.js} +19 -19
  55. package/dist/chunks/{studio-38emantv.js → studio-a5h5j33a.js} +19 -5
  56. package/dist/chunks/{studio-ympe0n7a.js → studio-ag3z6shb.js} +10 -4
  57. package/dist/chunks/studio-aj9drheq.js +177 -0
  58. package/dist/chunks/{studio-vt0tj32j.js → studio-apv9x70c.js} +189 -130
  59. package/dist/chunks/{studio-c04nzbby.js → studio-b9q3znz8.js} +2 -2
  60. package/dist/chunks/{studio-2mdbbc76.js → studio-bh47w0cy.js} +9 -10
  61. package/dist/chunks/{studio-ez11dmdh.js → studio-dgdm6vvt.js} +264 -52
  62. package/dist/chunks/{studio-5nz00bhh.js → studio-dwdcr2e8.js} +4 -4
  63. package/dist/chunks/{studio-nqgawfks.js → studio-fbcyz234.js} +146 -139
  64. package/dist/chunks/{studio-kcgff4r0.js → studio-fgmfksz8.js} +2 -2
  65. package/dist/chunks/{studio-x72j58vw.js → studio-fqpkpq1t.js} +7 -4
  66. package/dist/chunks/{studio-15f3kxqe.js → studio-fy320dfc.js} +62 -68
  67. package/dist/chunks/{studio-zjjde71a.js → studio-gjj4bbfx.js} +2 -2
  68. package/dist/chunks/{studio-66arbpc2.js → studio-jfhxy8h3.js} +16 -15
  69. package/dist/chunks/{studio-d0300636.js → studio-ks2zzth2.js} +17 -2
  70. package/dist/chunks/{studio-c07s5ckd.js → studio-kwewmhhq.js} +4 -4
  71. package/dist/chunks/{studio-01jeck2y.js → studio-m58xn8at.js} +26 -25
  72. package/dist/chunks/{studio-mpzwze9x.js → studio-mfkh1q8d.js} +17 -17
  73. package/dist/chunks/{studio-etvx9s2g.js → studio-mrh0a92f.js} +17 -17
  74. package/dist/chunks/{studio-qa7fzxan.js → studio-mt79tn56.js} +3 -3
  75. package/dist/chunks/{studio-hhskq2mr.js → studio-ne593z1j.js} +39 -32
  76. package/dist/chunks/{studio-h4nvnjvz.js → studio-ptfcxkz5.js} +418 -232
  77. package/dist/chunks/{studio-4xmjcrg3.js → studio-qxye31h7.js} +7 -7
  78. package/dist/chunks/{studio-a1pswps3.js → studio-rav5bpmp.js} +81 -59
  79. package/dist/chunks/{studio-ncn9e189.js → studio-rwmmbrth.js} +6 -6
  80. package/dist/chunks/{studio-en82c639.js → studio-sceg8ws0.js} +8 -8
  81. package/dist/chunks/{studio-wjpgxywa.js → studio-sjas5b3a.js} +19 -13
  82. package/dist/chunks/{studio-4r14yzyy.js → studio-vd67b66d.js} +3 -3
  83. package/dist/chunks/{studio-14d9tq4d.js → studio-w475cbf9.js} +10 -10
  84. package/dist/chunks/{studio-ctm7bhx5.js → studio-w7gpcvfj.js} +9 -9
  85. package/dist/chunks/{studio-9j6sfeba.js → studio-xrbfdxgk.js} +7 -7
  86. package/dist/chunks/{studio-x10fp4tp.js → studio-y49b6mjv.js} +35 -35
  87. package/dist/chunks/{studio-5t7ena5z.js → studio-ygvr9e4q.js} +10 -10
  88. package/dist/chunks/{studio-z2smx5ce.js → studio-yxmmjy2a.js} +9 -6
  89. package/dist/chunks/{tsMode-jy4m86rv.js → tsMode-ay4c78ne.js} +4 -4
  90. package/dist/chunks/{tsMode-hnxxb73h.css → tsMode-c72wyz27.css} +954 -137
  91. package/dist/chunks/{workspace-bnw54cha.js → workspace-b9k2frks.js} +5 -5
  92. package/dist/chunks/{ws-client-ny4zc51r.js → ws-client-erze4czt.js} +356 -20
  93. package/dist/codicon.ttf +0 -0
  94. package/dist/iframe-entry.js +20 -18
  95. package/dist/studio.css +956 -139
  96. package/dist/studio.js +1511 -851
  97. package/dist/workers/editor.worker.js +196 -107
  98. package/dist/workers/json.worker.js +196 -107
  99. package/dist/workers/ts.worker.js +196 -107
  100. package/package.json +18 -17
  101. package/src/about/about-modal.ts +4 -3
  102. package/src/browse/library-commands.ts +15 -13
  103. package/src/browse/library-pane.ts +2 -2
  104. package/src/canvas/canvas-render.ts +97 -83
  105. package/src/canvas/canvas-surface.ts +2 -2
  106. package/src/canvas/canvas-utils.ts +42 -32
  107. package/src/canvas/diff-toolbar.ts +21 -2
  108. package/src/canvas/diff-view.ts +30 -6
  109. package/src/canvas/iframe-entry.ts +2 -2
  110. package/src/canvas/iframe-host.ts +5 -5
  111. package/src/canvas/iframe-interaction.ts +7 -6
  112. package/src/canvas/iframe-overlay.ts +1 -1
  113. package/src/canvas/iframe-protocol.ts +6 -5
  114. package/src/canvas/iframe-render.ts +3 -3
  115. package/src/canvas/popover-state.ts +1 -1
  116. package/src/canvas/stage-scrollbar.ts +112 -0
  117. package/src/canvas/surface-registry.ts +9 -2
  118. package/src/collab/collab-commands.ts +8 -6
  119. package/src/collab/collab-session.ts +4 -4
  120. package/src/collab/collab-state.ts +4 -4
  121. package/src/collab/presence-chips.ts +3 -3
  122. package/src/commands/app-commands.ts +7 -7
  123. package/src/commands/budget.ts +17 -16
  124. package/src/commands/command-args.ts +10 -9
  125. package/src/commands/context.ts +20 -10
  126. package/src/commands/defaults.ts +30 -29
  127. package/src/commands/levels.ts +8 -6
  128. package/src/commands/live-context.ts +12 -10
  129. package/src/commands/reference.ts +5 -5
  130. package/src/commands/registry.ts +2 -2
  131. package/src/content/draft-state.ts +9 -8
  132. package/src/content/entry-commands.ts +16 -14
  133. package/src/content/entry-editor.ts +2 -2
  134. package/src/content/entry-fields.ts +1 -1
  135. package/src/editor/context-menu.ts +4 -4
  136. package/src/editor/convert-to-component.ts +6 -5
  137. package/src/editor/inline-edit.ts +3 -3
  138. package/src/editor/shortcuts.ts +88 -20
  139. package/src/editor/slash-menu.ts +11 -10
  140. package/src/files/elements.ts +4 -5
  141. package/src/files/file-ops.ts +1 -1
  142. package/src/files/files.ts +21 -17
  143. package/src/files/serialize-document.ts +1 -1
  144. package/src/format/convert-file.ts +5 -5
  145. package/src/github/github-auth-status.ts +107 -0
  146. package/src/github/github-auth.ts +18 -82
  147. package/src/github/github-publish.ts +7 -6
  148. package/src/grid/cell-editors.ts +8 -7
  149. package/src/grid/grid-layout.ts +1 -1
  150. package/src/grid/grid-open.ts +6 -5
  151. package/src/grid/grid-panel.ts +13 -12
  152. package/src/grid/redirects-grid.ts +14 -11
  153. package/src/grid/redirects.ts +10 -5
  154. package/src/grid/sources/content-source.ts +10 -9
  155. package/src/hosting/document.ts +28 -1
  156. package/src/hosting/stage.ts +15 -4
  157. package/src/i18n/i18n-commands.ts +9 -7
  158. package/src/new-project/location-fields.ts +16 -3
  159. package/src/new-project/new-project-modal.ts +23 -7
  160. package/src/page-params.ts +6 -6
  161. package/src/panels/activity-panel.ts +11 -10
  162. package/src/panels/ai-chat/chat-view.ts +27 -14
  163. package/src/panels/ai-chat/composer.ts +10 -3
  164. package/src/panels/ai-panel.ts +87 -29
  165. package/src/panels/block-action-bar.ts +59 -33
  166. package/src/panels/bottom-dock.ts +22 -22
  167. package/src/panels/chat-panel.ts +40 -5
  168. package/src/panels/data-explorer.ts +9 -9
  169. package/src/panels/data-grid.ts +1 -1
  170. package/src/panels/editors.ts +8 -8
  171. package/src/panels/elements-panel.ts +7 -8
  172. package/src/panels/empty-state.ts +1 -1
  173. package/src/panels/events-panel.ts +13 -12
  174. package/src/panels/formula-workspace.ts +8 -5
  175. package/src/panels/frontmatter-fields.ts +4 -1
  176. package/src/panels/frontmatter-panel.ts +18 -18
  177. package/src/panels/git-panel.ts +19 -13
  178. package/src/panels/head-panel.ts +9 -10
  179. package/src/panels/i18n-panel.ts +6 -4
  180. package/src/panels/imports-panel.ts +1 -1
  181. package/src/panels/jump-bar.ts +20 -18
  182. package/src/panels/layers-panel.ts +19 -17
  183. package/src/panels/left-panel.ts +3 -3
  184. package/src/panels/navigator-panels.ts +8 -8
  185. package/src/panels/overlays.ts +1 -1
  186. package/src/panels/pane-context.ts +56 -28
  187. package/src/panels/pane-grid.ts +9 -8
  188. package/src/panels/panel-registry.ts +47 -19
  189. package/src/panels/problems-panel.ts +11 -12
  190. package/src/panels/properties-panel.ts +21 -20
  191. package/src/panels/provenance.ts +3 -3
  192. package/src/panels/quick-search.ts +15 -11
  193. package/src/panels/right-panel.ts +4 -3
  194. package/src/panels/seo-modal.ts +5 -4
  195. package/src/panels/settings-menu.ts +9 -9
  196. package/src/panels/settings-pane.ts +3 -3
  197. package/src/panels/signals-panel.ts +16 -9
  198. package/src/panels/statement-editor.ts +12 -9
  199. package/src/panels/style-panel.ts +45 -41
  200. package/src/panels/stylebook-panel.ts +0 -1
  201. package/src/panels/tab-strip.ts +26 -23
  202. package/src/platform-errors.ts +2 -2
  203. package/src/platform.ts +44 -4
  204. package/src/platforms/cloud.ts +11 -1
  205. package/src/platforms/default-platform.ts +141 -4
  206. package/src/platforms/devserver.ts +23 -3
  207. package/src/publish/deploy-checklist.ts +11 -10
  208. package/src/publish/publish-commands.ts +9 -8
  209. package/src/services/a11y-report.ts +1 -1
  210. package/src/services/ai-ask.ts +39 -16
  211. package/src/services/ai-command-tools.ts +28 -14
  212. package/src/services/ai-import-tools.ts +43 -29
  213. package/src/services/ai-project-tools.ts +20 -38
  214. package/src/services/ai-settings.ts +55 -2
  215. package/src/services/ai-tools.ts +69 -58
  216. package/src/services/ai-write-report.ts +4 -4
  217. package/src/services/ai-writes.ts +74 -55
  218. package/src/services/automation.ts +12 -12
  219. package/src/services/clock.ts +2 -1
  220. package/src/services/connection.ts +2 -2
  221. package/src/services/context-manager.ts +69 -6
  222. package/src/services/directory-picker.ts +93 -27
  223. package/src/services/document-assistant.ts +86 -17
  224. package/src/services/gated-registry.ts +4 -3
  225. package/src/services/idle.ts +5 -4
  226. package/src/services/live-surfaces.ts +2 -1
  227. package/src/services/module-preloads.ts +67 -0
  228. package/src/services/notify.ts +9 -7
  229. package/src/services/popover-report.ts +3 -3
  230. package/src/services/preview-eval.ts +1 -1
  231. package/src/services/project-adoption.ts +34 -1
  232. package/src/services/project-path.ts +53 -0
  233. package/src/services/settings/kernel.ts +13 -2
  234. package/src/services/tool-executor.ts +135 -30
  235. package/src/services/tool-outcomes.ts +143 -0
  236. package/src/settings/contexts-section.ts +8 -8
  237. package/src/settings/contributed-section.ts +6 -6
  238. package/src/settings/css-vars-editor.ts +6 -5
  239. package/src/settings/extension-commands.ts +19 -18
  240. package/src/settings/extension-sections.ts +7 -7
  241. package/src/settings/extensions-section.ts +3 -3
  242. package/src/settings/general-settings.ts +21 -18
  243. package/src/settings/preferences-accounts.ts +3 -3
  244. package/src/settings/preferences-dialog.ts +9 -8
  245. package/src/settings/preferences-sections.ts +1 -1
  246. package/src/settings/project-sections.ts +2 -2
  247. package/src/settings/section-registry.ts +7 -6
  248. package/src/settings/settings-document.ts +41 -43
  249. package/src/shell/tree.ts +29 -0
  250. package/src/shell.ts +63 -55
  251. package/src/site-context.ts +1 -1
  252. package/src/store.ts +2 -2
  253. package/src/studio.ts +52 -28
  254. package/src/style/live-preview.ts +8 -8
  255. package/src/style/project-styles.ts +8 -9
  256. package/src/style/token-ref.ts +2 -2
  257. package/src/surfaces/about.json +6 -2
  258. package/src/surfaces/add-repo.json +2 -2
  259. package/src/surfaces/add-repo.ts +2 -2
  260. package/src/surfaces/ai-chat.json +14 -6
  261. package/src/surfaces/ai-chat.ts +4 -2
  262. package/src/surfaces/ai-credentials-form.json +53 -49
  263. package/src/surfaces/ai-credentials-form.ts +18 -4
  264. package/src/surfaces/ai-managed-connect.json +4 -4
  265. package/src/surfaces/block-action-bar.json +4 -4
  266. package/src/surfaces/block-action-bar.ts +3 -3
  267. package/src/surfaces/boot-failure.json +228 -0
  268. package/src/surfaces/boot-failure.ts +156 -0
  269. package/src/surfaces/bottom-dock.json +1 -1
  270. package/src/surfaces/canvas-stage.json +9 -40
  271. package/src/surfaces/canvas-stage.ts +9 -9
  272. package/src/surfaces/cf-account-picker.json +11 -8
  273. package/src/surfaces/commandbar.ts +11 -10
  274. package/src/surfaces/convert-repeater.ts +3 -2
  275. package/src/surfaces/dialog.json +7 -1
  276. package/src/surfaces/dialog.ts +2 -2
  277. package/src/surfaces/doc-header.json +6 -13
  278. package/src/surfaces/doc-header.ts +4 -5
  279. package/src/surfaces/entry-editor.json +1 -1
  280. package/src/surfaces/entry-editor.ts +2 -1
  281. package/src/surfaces/expression-editor.json +5 -5
  282. package/src/surfaces/files-panel.json +4 -3
  283. package/src/surfaces/files-panel.ts +1 -1
  284. package/src/surfaces/formula-palette.json +10 -9
  285. package/src/surfaces/formula-palette.ts +11 -7
  286. package/src/surfaces/git-panel.json +167 -109
  287. package/src/surfaces/git-panel.ts +2 -2
  288. package/src/surfaces/github-auth.json +2 -1
  289. package/src/surfaces/github-publish.ts +3 -3
  290. package/src/surfaces/grid-open.json +1 -1
  291. package/src/surfaces/grid-panel.json +2 -2
  292. package/src/surfaces/grid-views.json +1 -1
  293. package/src/surfaces/inspector-dock.json +2 -2
  294. package/src/surfaces/jump-bar.json +1 -1
  295. package/src/surfaces/jump-bar.ts +1 -1
  296. package/src/surfaces/library-pane.ts +2 -1
  297. package/src/surfaces/logic-panel.json +7 -7
  298. package/src/surfaces/logic-panel.ts +6 -4
  299. package/src/surfaces/logic-workspace.json +2 -2
  300. package/src/surfaces/logic-workspace.ts +2 -2
  301. package/src/surfaces/media-browser.ts +3 -3
  302. package/src/surfaces/media-pane.ts +2 -1
  303. package/src/surfaces/menu.ts +5 -5
  304. package/src/surfaces/new-project.json +1 -1
  305. package/src/surfaces/palette.json +8 -7
  306. package/src/surfaces/pane-context.json +86 -57
  307. package/src/surfaces/pane-context.ts +2 -1
  308. package/src/surfaces/pane-grid.json +3 -3
  309. package/src/surfaces/panel-activity.json +2 -1
  310. package/src/surfaces/panel-deploy-checklist.json +1 -1
  311. package/src/surfaces/panel-elements.ts +2 -1
  312. package/src/surfaces/panel-i18n.ts +2 -1
  313. package/src/surfaces/panel-outline.json +1 -1
  314. package/src/surfaces/panel-outline.ts +4 -3
  315. package/src/surfaces/panel-page.json +2 -2
  316. package/src/surfaces/panel-page.ts +4 -1
  317. package/src/surfaces/panel-problems.json +4 -3
  318. package/src/surfaces/panel-problems.ts +2 -2
  319. package/src/surfaces/panel-signals.json +23 -8
  320. package/src/surfaces/panel-signals.ts +5 -1
  321. package/src/surfaces/panel-stylebook-layers.json +1 -1
  322. package/src/surfaces/panel-stylebook-layers.ts +2 -2
  323. package/src/surfaces/preferences.json +21 -8
  324. package/src/surfaces/preferences.ts +9 -1
  325. package/src/surfaces/properties-panel.json +4 -4
  326. package/src/surfaces/properties-panel.ts +2 -2
  327. package/src/surfaces/publish.json +1 -1
  328. package/src/surfaces/publish.ts +2 -1
  329. package/src/surfaces/push-plan.json +4 -3
  330. package/src/surfaces/rail.json +2 -2
  331. package/src/surfaces/rail.ts +10 -4
  332. package/src/surfaces/reference-field.json +1 -1
  333. package/src/surfaces/schema-form.json +2 -2
  334. package/src/surfaces/schema-form.ts +1 -1
  335. package/src/surfaces/seo.json +1 -1
  336. package/src/surfaces/seo.ts +2 -2
  337. package/src/surfaces/settings-contributed.ts +2 -1
  338. package/src/surfaces/settings-css-vars.json +4 -4
  339. package/src/surfaces/settings-deploy.json +4 -4
  340. package/src/surfaces/settings-deploy.ts +2 -1
  341. package/src/surfaces/settings-extensions.json +6 -6
  342. package/src/surfaces/settings-extensions.ts +6 -2
  343. package/src/surfaces/settings-locales.json +4 -4
  344. package/src/surfaces/settings-overview.ts +2 -1
  345. package/src/surfaces/settings-pane.ts +3 -2
  346. package/src/surfaces/settings-rawjson.ts +1 -1
  347. package/src/surfaces/slash-menu.ts +4 -4
  348. package/src/surfaces/statements.json +3 -3
  349. package/src/surfaces/statusbar.ts +23 -23
  350. package/src/surfaces/style-panel.json +9 -5
  351. package/src/surfaces/style-panel.ts +3 -3
  352. package/src/surfaces/tab-strip.json +2 -2
  353. package/src/surfaces/target-line.json +1 -1
  354. package/src/surfaces/target-line.ts +12 -10
  355. package/src/surfaces/welcome.json +7 -2
  356. package/src/tabs/doc-op-apply.ts +5 -5
  357. package/src/tabs/patch-ops.ts +3 -3
  358. package/src/tabs/project-config.ts +10 -10
  359. package/src/tabs/selection.ts +1 -1
  360. package/src/tabs/tab.ts +13 -14
  361. package/src/tabs/transact.ts +18 -24
  362. package/src/types.ts +27 -3
  363. package/src/ui/ai-credentials-form.ts +74 -16
  364. package/src/ui/ai-managed-connect.ts +68 -19
  365. package/src/ui/color-selector.ts +1 -1
  366. package/src/ui/expression-editor.ts +3 -3
  367. package/src/ui/form-controls.ts +6 -5
  368. package/src/ui/formula-catalog.ts +4 -4
  369. package/src/ui/formula-chips.ts +2 -2
  370. package/src/ui/layers.ts +20 -18
  371. package/src/ui/panel-resize.ts +8 -28
  372. package/src/ui/popover-surface.ts +2 -2
  373. package/src/ui/progress-modal.ts +3 -3
  374. package/src/ui/regions.ts +14 -9
  375. package/src/ui/schema-form.ts +10 -9
  376. package/src/ui/timing.ts +9 -7
  377. package/src/ui/value-source.ts +4 -4
  378. package/src/ui/virtual-window.ts +58 -5
  379. package/src/utils/base64.ts +20 -1
  380. package/src/utils/canvas-media.ts +1 -1
  381. package/src/utils/studio-utils.ts +0 -31
  382. package/src/view.ts +4 -3
  383. package/src/workspace/pane-derive.ts +14 -13
  384. package/src/workspace/session.ts +7 -6
  385. package/src/workspace/workspace.ts +49 -39
  386. package/styles/canvas.css +1 -27
  387. package/styles/inspector.css +4 -3
  388. package/styles/overlays.css +1 -1
  389. package/styles/tokens.css +17 -1
  390. package/styles/tokens.json +6 -1
  391. package/dist/chunks/entry-commands-w9qsxa6q.js +0 -42
  392. package/dist/chunks/layers-panel-fksbdg4q.js +0 -40
  393. package/dist/chunks/publish-panel-vz23wwmg.js +0 -27
  394. package/dist/chunks/quick-search-e9rp74m8.js +0 -46
  395. package/dist/chunks/right-panel-j97t2yvy.js +0 -45
  396. package/dist/chunks/studio-727xh0e3.js +0 -76
  397. package/src/files/json-layout.ts +0 -474
  398. package/src/services/ai-turn-signal.ts +0 -70
@@ -9,19 +9,19 @@
9
9
  * different command owned by a different module. Nothing in Studio held the chain, so no surface
10
10
  * could say which link was missing.
11
11
  *
12
- * This module is the chain (plan §9.5):
12
+ * This module is the chain (`docs/studio/publish.md`):
13
13
  *
14
14
  * - {@link deployChecklist} is the ordered prerequisite list, each step carrying **the command that
15
15
  * satisfies it** rather than a sentence telling the reader to go and find one;
16
16
  * - {@link deployChecklistView} is its projection and {@link syncDeployChecklist} mounts the document
17
17
  * that draws it (`surfaces/panel-deploy-checklist.json`), in the Bottom dock's **Activity** tab
18
- * because a deploy is a long operation with a log and the dock cap is four (§2 principle 9). It
19
- * draws in the Activity tab's vocabulary — the same row, the same ordered steps, the same glyphs
20
- * — for the same reason: a fifth visual idiom for "an ordered list of stages that finish" would
21
- * be a second design of a thing the tab already has;
18
+ * because a deploy is a long operation with a log and the dock cap is four
19
+ * (`studio-ui-guidelines.md` §12.2). It draws in the Activity tab's vocabulary — the same row,
20
+ * the same ordered steps, the same glyphs — for the same reason: a fifth visual idiom for "an
21
+ * ordered list of stages that finish" would be a second design of a thing the tab already has;
22
22
  * - {@link deployStatusItem} is the status bar's project-field item, whose **label is the next
23
23
  * blocking prerequisite** — the shortcut and the explanation in one 24px item, and ambient state
24
- * rather than a transient message, which is the only thing that bar carries (§3.2 ⑫).
24
+ * rather than a transient message, which is the only thing that bar carries (§16.2).
25
25
  *
26
26
  * **A step it cannot answer says `unknown`.** `services/references.ts` established the rule and it
27
27
  * matters more here: "no deployment" and "we have not asked Cloudflare" look identical from inside
@@ -81,8 +81,9 @@ function currentConfig(): ProjectConfig | null {
81
81
  /**
82
82
  * The connected provider block, or `undefined`.
83
83
  *
84
- * Exported because the Publish panel asks the same question and P6's lesson was that two copies of
85
- * one derivation drift apart in silence — there is one reader of `build.deploy` in the app.
84
+ * Exported because the Publish panel asks the same question and the shell redesign's configuration
85
+ * phase taught that two copies of one derivation drift apart in silence — there is one reader of
86
+ * `build.deploy` in the app.
86
87
  */
87
88
  export function currentDeploy(): DeployConfig | undefined {
88
89
  return currentConfig()?.build?.deploy;
@@ -336,7 +337,7 @@ function stepView(step: DeployStep): DeployStepRowView {
336
337
  *
337
338
  * A command the registry does not have, or whose `when` is false, draws NOTHING rather than a dead
338
339
  * label. The status bar's projection takes the same position for the same reason: a surface may
339
- * choose whether to show a command, never whether it exists (§12.4).
340
+ * choose whether to show a command, never whether it exists (`studio-ui-guidelines.md` §12.3).
340
341
  */
341
342
  function actionView(
342
343
  step: DeployStep | null,
@@ -415,7 +416,7 @@ const CHECKLIST_SURFACE = { project: deployChecklistView, runAction: runNextStep
415
416
  * Rendered ABOVE the operation feed rather than as an entry in it: an activity is something that
416
417
  * happened, and this is something that has not. It carries no `data-jx-region` stamp — the region
417
418
  * is `dock.bottom/panel:activity`, which the dock derives, and a hand-stamped leaf is a committed
418
- * budget (§13.2) that this does not need to spend.
419
+ * budget (`scripts/screenshots/README.md`, "The region grammar") that this does not need to spend.
419
420
  *
420
421
  * @param host The dock body element the tab was rendered into.
421
422
  */
@@ -1,5 +1,6 @@
1
1
  /**
2
- * Publish-commands.ts — the `Publish:` family (plan §9.5), defined beside the deploy it drives.
2
+ * Publish-commands.ts — the `Publish:` family (`docs/studio/publish.md`), defined beside the deploy
3
+ * it drives.
3
4
  *
4
5
  * Two unrelated operations were called "Publish" and neither was a command. `publishToGithub`
5
6
  * created a git repository; `openPublishPanel` connected Cloudflare Pages — and it had **zero call
@@ -16,7 +17,7 @@
16
17
  * | `publish.openDashboard` | opens the connected Pages project on Cloudflare |
17
18
  *
18
19
  * **`runDeploy` is an Activity, not a toast.** A deploy pushes a branch and then waits on a build
19
- * that is happening somewhere else; that is the definition of the long operation §7.3 gave the
20
+ * that is happening somewhere else; that is the definition of the long operation §16.4 gave the
20
21
  * Activity tab, and `fail()` raises the Problem so the failure outlives the run and carries a
21
22
  * Retry. The caller therefore never also notifies (§16).
22
23
  *
@@ -42,7 +43,7 @@ import type { AnyCommand, CommandRegistry } from "../commands/registry";
42
43
  * Six attempts five seconds apart: a Pages build takes minutes, so this is not "wait for the build"
43
44
  * — it is "wait for the deployment RECORD", which appears within seconds of the push. The activity
44
45
  * says which of the two it is waiting for, because a progress line that implies the former and
45
- * delivers the latter is the dishonesty §7.3 exists to end.
46
+ * delivers the latter is the dishonesty §16.4 exists to end.
46
47
  */
47
48
  export const DEPLOY_POLL = { attempts: 6, delayMs: 5000 } as const;
48
49
 
@@ -135,7 +136,7 @@ export async function runDeploy(
135
136
  *
136
137
  * `enablement` reads `platformSupportsPublish()` rather than a `capability.*` key because there is
137
138
  * no `publish` capability yet — adding one is a `commands/context.ts` + `commands/live-context.ts`
138
- * change, and §5.2 is explicit that PAL differences belong there. Until it exists this is the one
139
+ * change, and §13.4 is explicit that PAL differences belong there. Until it exists this is the one
139
140
  * `if (platform.x)` the family carries, in the one place a reader looks for it.
140
141
  */
141
142
  export function publishCommands(): AnyCommand[] {
@@ -157,8 +158,8 @@ export function publishCommands(): AnyCommand[] {
157
158
  when: (ctx) => ctx.project.open,
158
159
  // `when` already asked about the project.
159
160
  enablement: () => platformSupportsPublish(),
160
- /* No `aiTool`, by §12.4's second deletion rule: an auth flow that waits on a person, with
161
- credentials. */
161
+ /* No `aiTool`, by `studio-ui-guidelines.md` §12.4's second rule: an auth flow that waits on a
162
+ person, with credentials. */
162
163
  run: async () => {
163
164
  const { openPublishPanel } = await import("./publish-panel");
164
165
  openPublishPanel();
@@ -177,8 +178,8 @@ export function publishCommands(): AnyCommand[] {
177
178
  when: (ctx) => ctx.project.open,
178
179
  enablement: (ctx) =>
179
180
  ctx.project.isRepo && currentDeploy() !== undefined && platformSupportsPublish(),
180
- /* No `aiTool`, by §12.4's third deletion rule: remote and irreversible, the same class as
181
- `packages.remove`. */
181
+ /* No `aiTool`, by `studio-ui-guidelines.md` §12.4's third rule: remote and irreversible, the
182
+ same class as `packages.remove`. */
182
183
  run: async () => {
183
184
  await runDeploy();
184
185
  },
@@ -29,7 +29,7 @@
29
29
  * alt, an aria reference to an id nothing has, a tab, menu item or option outside its container, a
30
30
  * tablist with no selected tab, an unnamed dialog, aria-activedescendant on an element that cannot
31
31
  * take focus — so `jx validate`, the kit's conformance tests and this report judge a document alike
32
- * (spec §8.8). What stays here is what needs the whole page rather than a node: the heading
32
+ * (`spec.md` §8.8). What stays here is what needs the whole page rather than a node: the heading
33
33
  * outline, duplicate ids, the document language, link wording, autocomplete purposes and media.
34
34
  *
35
35
  * @docs studio/interface/problems-and-progress
@@ -25,7 +25,6 @@
25
25
 
26
26
  import { createToolDefinition, toolError, toolSuccess } from "@jxsuite/ai/tools";
27
27
  import { reactive } from "../reactivity";
28
- import { currentToolCallId, turnSignal } from "./ai-turn-signal";
29
28
 
30
29
  import type { ToolRegistry } from "@jxsuite/ai/tools";
31
30
 
@@ -81,17 +80,17 @@ export function isAwaitingAnswer(): boolean {
81
80
  * Register a question and suspend until it is settled.
82
81
  *
83
82
  * @param {PendingAsk} ask
83
+ * @param {AbortSignal} [signal] - The asking call's signal. Without one the question can still be
84
+ * settled by {@link cancelAsk}, but a Stop would leave the turn awaiting a promise nothing could
85
+ * resolve.
84
86
  * @returns {Promise<AskAnswer>} Resolves on an answer, a skip, or the turn being stopped (`{
85
87
  * answer: null, skipped: false }` — the shape the tool reports as a failure).
86
88
  */
87
- export function askUser(ask: PendingAsk): Promise<AskAnswer> {
89
+ export function askUser(ask: PendingAsk, signal?: AbortSignal): Promise<AskAnswer> {
88
90
  return new Promise<AskAnswer>((resolve) => {
89
91
  state.pending = ask;
90
92
  settle = resolve;
91
93
 
92
- /* The turn's signal, not a parameter: `ToolRegistry.execute` has nowhere to pass one. Without
93
- this, Stop would leave the loop awaiting a promise nothing could ever resolve. */
94
- const signal = turnSignal();
95
94
  if (signal?.aborted) {
96
95
  clear();
97
96
  resolve({ answer: null, skipped: false });
@@ -163,10 +162,26 @@ export function resetAsk(): void {
163
162
  *
164
163
  * @param {Pick<ToolRegistry, "register">} registry
165
164
  */
166
- export function registerAskTool(registry: Pick<ToolRegistry, "register">): void {
165
+ /** What the host that registers the question tool may hook. */
166
+ export interface AskToolOptions {
167
+ /**
168
+ * Called once a question has been put to the author, before the turn waits on the answer. A host
169
+ * saves the conversation here: a turn suspended on a person may wait for as long as they like,
170
+ * and a reload in the meantime must still find the question to restore as open (`specs/ai.md`
171
+ * §3.4).
172
+ */
173
+ onPending?: () => void;
174
+ }
175
+
176
+ export function registerAskTool(
177
+ registry: Pick<ToolRegistry, "register">,
178
+ { onPending }: AskToolOptions = {},
179
+ ): void {
167
180
  registry.register(
168
181
  createToolDefinition({
169
182
  name: "ask_user",
183
+ // It suspends the turn on the author, so a round that only asked spends no work budget.
184
+ interactive: true,
170
185
  description:
171
186
  "Pause and put a question to the user; the turn waits for their reply and resumes with " +
172
187
  "it. Use it ONLY for a judgement that is genuinely theirs — which pages matter, whether to " +
@@ -196,7 +211,7 @@ export function registerAskTool(registry: Pick<ToolRegistry, "register">): void
196
211
  },
197
212
  required: ["question"],
198
213
  },
199
- execute: async (args) => {
214
+ execute: async (args, ctx) => {
200
215
  /* Everything `unknown`, and every field narrowed by `typeof` rather than by `?.`. The
201
216
  registry's `validate()` is "a lightweight structural check, not a JSON Schema validator",
202
217
  `strict: false` skips it entirely, and this tool may be registered into someone else's
@@ -219,15 +234,23 @@ export function registerAskTool(registry: Pick<ToolRegistry, "register">): void
219
234
  .slice(0, MAX_OPTIONS)
220
235
  : [];
221
236
 
222
- const { answer, skipped } = await askUser({
223
- context: typeof context === "string" ? context.trim() : "",
224
- /* The tool-call id, so the chip the transcript draws and the question this registers are
225
- the same thing. Without it a restored, permanently unanswered question would render
226
- identically to the live one. */
227
- id: currentToolCallId(),
228
- options: choices,
229
- question: question.trim(),
230
- });
237
+ const answering = askUser(
238
+ {
239
+ context: typeof context === "string" ? context.trim() : "",
240
+ /* The tool-call id, so the chip the transcript draws and the question this registers
241
+ are the same thing. Without it a restored, permanently unanswered question would
242
+ render identically to the live one. */
243
+ id: ctx.callId,
244
+ options: choices,
245
+ question: question.trim(),
246
+ },
247
+ ctx.signal,
248
+ );
249
+ // The question is published by now (`askUser` sets it synchronously); the answer is not.
250
+ if (isAwaitingAnswer()) {
251
+ onPending?.();
252
+ }
253
+ const { answer, skipped } = await answering;
231
254
 
232
255
  if (skipped) {
233
256
  /* A success: the model asked, and "you decide" is an answer. Rendering it as a failure
@@ -59,14 +59,19 @@
59
59
  * @license MIT
60
60
  */
61
61
 
62
- import { toolError } from "@jxsuite/ai/tools";
63
- import type { ToolDefinition, ToolRegistry, ToolResult } from "@jxsuite/ai/tools";
62
+ import { createToolContext, toolError } from "@jxsuite/ai/tools";
63
+ import type {
64
+ ToolContext,
65
+ ToolDefinition,
66
+ ToolRegistry,
67
+ ToolResult,
68
+ WriteLedger,
69
+ } from "@jxsuite/ai/tools";
64
70
  import { activeRegistry } from "../commands/active-registry";
65
71
  import { argsSchema } from "../commands/command-args";
66
72
  import { inCanvas } from "../commands/context";
67
73
  import { toRaw } from "../reactivity";
68
74
  import { projectState } from "../state";
69
- import { recordWrite } from "./ai-writes";
70
75
  import { reportDocumentWrite, snapshotBeforeWrite } from "./ai-write-report";
71
76
  import type { WriteReportDeps, WriteSnapshot } from "./ai-write-report";
72
77
  import type { CommandContext } from "../commands/context";
@@ -198,7 +203,7 @@ function projected(registry: CommandRegistry): AnyCommand[] {
198
203
  function definitionOf(
199
204
  registry: CommandRegistry,
200
205
  command: AnyCommand,
201
- execute: (name: string, args: object) => Promise<ToolResult>,
206
+ execute: (name: string, args: object, ctx: ToolContext) => Promise<ToolResult>,
202
207
  ): ToolDefinition | undefined {
203
208
  const tool = command.aiTool!;
204
209
  let parameters: object;
@@ -217,7 +222,7 @@ function definitionOf(
217
222
  parameters: parameters as ToolDefinition["parameters"],
218
223
  strict: false,
219
224
  llmStrict: false,
220
- execute: (args) => execute(tool.name, args),
225
+ execute: (args, ctx) => execute(tool.name, args, ctx),
221
226
  };
222
227
  }
223
228
 
@@ -235,7 +240,7 @@ function definitionOf(
235
240
  */
236
241
  export function advertisedCommandTools(
237
242
  registry: CommandRegistry,
238
- execute: (name: string, args: object) => Promise<ToolResult> = async (name) =>
243
+ execute: (name: string, args: object, ctx: ToolContext) => Promise<ToolResult> = async (name) =>
239
244
  toolError(`Tool "${name}" was listed without an executor.`),
240
245
  ): ToolDefinition[] {
241
246
  const definitions: ToolDefinition[] = [];
@@ -324,13 +329,18 @@ function defaultPaths(command: AnyCommand, tab: Tab | null): string[] {
324
329
  * had — and files nothing, because the ledger names files that changed; `undefined` is a report
325
330
  * that named none and takes the record's defaults.
326
331
  */
327
- function fileLedger(command: AnyCommand, tab: Tab | null, wrote?: readonly string[]): void {
332
+ function fileLedger(
333
+ ledger: WriteLedger,
334
+ command: AnyCommand,
335
+ tab: Tab | null,
336
+ wrote?: readonly string[],
337
+ ): void {
328
338
  if (command.undo === undefined) {
329
339
  return;
330
340
  }
331
341
  const disk = command.undo === "none";
332
342
  for (const path of wrote ?? defaultPaths(command, tab)) {
333
- recordWrite({ disk, ok: true, path, tool: command.title });
343
+ ledger.record({ disk, ok: true, path, tool: command.title });
334
344
  }
335
345
  }
336
346
 
@@ -349,13 +359,17 @@ export function createCommandToolRegistry(deps: CommandToolDeps): ToolRegistry {
349
359
  }
350
360
 
351
361
  /**
352
- * Run one projected record as the agent — the whole of §3 of the design.
362
+ * Run one projected record as the agent — the whole of `ai.md` §3.6.
353
363
  *
354
364
  * @param {string} name
355
365
  * @param {object} received
356
366
  * @returns {Promise<ToolResult>}
357
367
  */
358
- async function execute(name: string, received: object): Promise<ToolResult> {
368
+ async function execute(
369
+ name: string,
370
+ received: object,
371
+ ctx: ToolContext = createToolContext(),
372
+ ): Promise<ToolResult> {
359
373
  const registry = activeRegistry();
360
374
  const command = commandFor(name);
361
375
  if (!registry || !command) {
@@ -426,7 +440,7 @@ export function createCommandToolRegistry(deps: CommandToolDeps): ToolRegistry {
426
440
  Left to the loop, the throw would be labelled "Failed to parse arguments" and the ledger
427
441
  would never be filed. So the ledger is filed from `undo`'s defaults — the report that would
428
442
  have named the paths is the thing that failed — and the model is told both halves. */
429
- fileLedger(command, tab, projectUnchanged ? [] : undefined);
443
+ fileLedger(ctx.ledger, command, tab, projectUnchanged ? [] : undefined);
430
444
  const message = error instanceof Error ? error.message : String(error);
431
445
  return { error: `${command.title} ran, but its report failed: ${message}`, success: false };
432
446
  }
@@ -442,7 +456,7 @@ export function createCommandToolRegistry(deps: CommandToolDeps): ToolRegistry {
442
456
  summary = `${command.title} changed nothing: project.json is exactly as it was.`;
443
457
  }
444
458
  }
445
- fileLedger(command, tab, wrote);
459
+ fileLedger(ctx.ledger, command, tab, wrote);
446
460
  if (tab && snapshot) {
447
461
  const verdict = await reportDocumentWrite(tab, snapshot, summary, deps);
448
462
  if (!verdict.success) {
@@ -530,9 +544,9 @@ export function composeToolRegistries(hand: ToolRegistry, commands: ToolRegistry
530
544
  ? side.validate(name, args)
531
545
  : { valid: false, errors: [`Unknown tool: "${name}"`] };
532
546
  },
533
- async execute(name, args) {
547
+ async execute(name, args, ctx) {
534
548
  const side = sideOf(name);
535
- return side ? side.execute(name, args) : toolError(`Unknown tool: "${name}"`);
549
+ return side ? side.execute(name, args, ctx) : toolError(`Unknown tool: "${name}"`);
536
550
  },
537
551
  };
538
552
  }
@@ -28,18 +28,12 @@ import { workspace } from "../workspace/workspace";
28
28
  import { getBaseUrl, getOpenAiKey } from "./ai-settings";
29
29
  import { preferredModel } from "./ai-models";
30
30
  import { clearPendingImportBrief, pendingImportBrief } from "./import-seed";
31
- import {
32
- abortImportRun,
33
- beginImportRun,
34
- finishImportRun,
35
- importRun,
36
- recordImportProgress,
37
- } from "./import-run";
31
+ import type { ImportBrief } from "./import-seed";
32
+ import { abortImportRun, beginImportRun, finishImportRun, importRun } from "./import-run";
38
33
  import { adoptCreatedProject } from "./project-adoption";
39
34
  import type { AdoptOutcome } from "./project-adoption";
40
- import { currentToolCallId, turnSignal } from "./ai-turn-signal";
41
35
 
42
- import type { ToolRegistry } from "@jxsuite/ai/tools";
36
+ import type { JsonValue, ToolRegistry } from "@jxsuite/ai/tools";
43
37
  import type { Tab } from "../tabs/tab";
44
38
  import type { ImportBreakpointPolicy, ImportReadyEvent, ImportSiteSummary } from "../types";
45
39
 
@@ -72,6 +66,11 @@ export interface ImportToolsCtx {
72
66
  adoptProject?: (root: string) => Promise<void>;
73
67
  /** Fired once adoption is VERIFIED — the session store re-keys the live chat here. */
74
68
  onProjectAdopted?: (root: string) => void;
69
+ /**
70
+ * The New Project Import form's brief, when the run started there. Defaults to the one the form
71
+ * stores (`services/import-seed.ts`); a host with no form binds its own.
72
+ */
73
+ brief?: { read: () => ImportBrief | null; clear: () => void };
75
74
  }
76
75
 
77
76
  function clamp(value: number, min: number, max: number): number {
@@ -151,19 +150,15 @@ function destinationProblem(destination: string): string | null {
151
150
  }
152
151
 
153
152
  /**
154
- * One run has already succeeded in this session.
153
+ * The session fact that one run has already succeeded in this conversation.
155
154
  *
156
155
  * Not the same question as `workspace.projectRoot`, and that is the whole reason it exists: on
157
156
  * desktop, adoption may open the project in ANOTHER window, leaving this one's root empty — so the
158
157
  * `no-project` tier would happily advertise a second import over the top of the first.
159
- * `create_project` has the identical hazard today.
158
+ * `create_project` has the identical hazard today. A session fact rather than a module flag, and it
159
+ * lasts as the flag did: New Chat starts with none, and opening another chat keeps it.
160
160
  */
161
- let imported = false;
162
-
163
- /** Forget that an import ran. New Chat, and tests. */
164
- export function resetImportGuard(): void {
165
- imported = false;
166
- }
161
+ const IMPORT_DONE = "import.done";
167
162
 
168
163
  /**
169
164
  * A compact, factual account of the run for the model to reason about — and to ask about.
@@ -258,7 +253,12 @@ function describeRun(id: string, root: string, summary?: ImportSiteSummary): str
258
253
  */
259
254
  export function registerImportTools(
260
255
  registry: Pick<ToolRegistry, "register">,
261
- { getTab, adoptProject, onProjectAdopted }: ImportToolsCtx,
256
+ {
257
+ getTab,
258
+ adoptProject,
259
+ onProjectAdopted,
260
+ brief: briefStore = { clear: clearPendingImportBrief, read: pendingImportBrief },
261
+ }: ImportToolsCtx,
262
262
  ): void {
263
263
  registry.register(
264
264
  createToolDefinition({
@@ -334,7 +334,7 @@ export function registerImportTools(
334
334
  },
335
335
  required: ["url"],
336
336
  },
337
- async execute(args) {
337
+ async execute(args, ctx) {
338
338
  const {
339
339
  url,
340
340
  directory,
@@ -364,7 +364,7 @@ export function registerImportTools(
364
364
  "A project is already open in this window — import_site is only for bootstrapping.",
365
365
  );
366
366
  }
367
- if (imported) {
367
+ if (ctx.session.get(IMPORT_DONE) === true) {
368
368
  return toolError(
369
369
  "A site has already been imported in this conversation. Start a new chat to import " +
370
370
  "another, or use the file tools on the project that was created.",
@@ -385,7 +385,7 @@ export function registerImportTools(
385
385
  return toolError("The url must start with http:// or https://.");
386
386
  }
387
387
 
388
- const brief = pendingImportBrief();
388
+ const brief = briefStore.read();
389
389
  const requested = typeof directory === "string" ? directory.trim() : "";
390
390
  if (requested && brief && requested !== brief.directory) {
391
391
  /* The wizard's Location field is the user's answer to "where does this go". A model that
@@ -410,16 +410,23 @@ export function registerImportTools(
410
410
  return toolError(problem);
411
411
  }
412
412
 
413
- const id = currentToolCallId();
413
+ const id = ctx.callId;
414
414
  const signal = beginImportRun(id, { directory: destination, url: target.href });
415
- /* The turn's own signal too. `assistant.stop` aborts the request through `abortImportRun`,
415
+ /* The call's own signal too. `assistant.stop` aborts the request through `abortImportRun`,
416
416
  but that is not the only way a turn ends: the loop's signal can be aborted directly, and
417
417
  a run left going after its turn died would keep a headless browser open for minutes with
418
- nothing left to receive the result. */
419
- turnSignal()?.addEventListener("abort", () => {
418
+ nothing left to receive the result.
419
+
420
+ It stops THIS run and nothing after it, so it is removed the moment the run settles: left
421
+ on the signal, a Stop later in the same turn (a question after the import, say) rewrote
422
+ the finished run as stopped. The loop also unlinks the call's signal once the call
423
+ settles, so a later Stop reaches it no more than it reaches the listener. */
424
+ const callSignal = ctx.signal;
425
+ const stopRun = () => {
420
426
  abortImportRun();
421
427
  finishImportRun(id, { status: "stopped" });
422
- });
428
+ };
429
+ callSignal.addEventListener("abort", stopRun, { once: true });
423
430
 
424
431
  const apiKey = getOpenAiKey();
425
432
  const baseUrl = getBaseUrl();
@@ -479,11 +486,14 @@ export function registerImportTools(
479
486
  ...(baseUrl ? { baseUrl } : {}),
480
487
  ...(model ? { model } : {}),
481
488
  },
482
- (evt) => recordImportProgress(id, evt),
489
+ (evt) => {
490
+ ctx.progress(evt as unknown as JsonValue);
491
+ },
483
492
  signal,
484
493
  onReady,
485
494
  );
486
495
  } catch (error) {
496
+ callSignal.removeEventListener("abort", stopRun);
487
497
  const message = error instanceof Error ? error.message : String(error);
488
498
  if (signal.aborted) {
489
499
  finishImportRun(id, { error: message, status: "stopped" });
@@ -502,9 +512,13 @@ export function registerImportTools(
502
512
  );
503
513
  }
504
514
 
515
+ callSignal.removeEventListener("abort", stopRun);
505
516
  finishImportRun(id, { status: "done" });
506
- imported = true;
507
- clearPendingImportBrief();
517
+ /* The imported project is on disk from here, and no undo reaches it: it is what the turn
518
+ changed, whichever window ends up opening it. */
519
+ ctx.ledger.record({ disk: true, ok: true, path: result.root, tool: "import_site" });
520
+ ctx.session.set(IMPORT_DONE, true);
521
+ briefStore.clear();
508
522
 
509
523
  /* The early adoption if there was one, otherwise adopt now: a backend that sends no `ready`
510
524
  line is not broken, it is older, and the project must still open. */
@@ -16,7 +16,7 @@
16
16
 
17
17
  import { createToolDefinition } from "@jxsuite/ai/tools";
18
18
  import type { CreateProjectDestination } from "../types";
19
- import type { ToolRegistry, ToolResult } from "@jxsuite/ai/tools";
19
+ import type { ToolRegistry } from "@jxsuite/ai/tools";
20
20
  import type { JxMutableNode, ProjectConfig } from "@jxsuite/schema/types";
21
21
  import { getPlatform } from "../platform";
22
22
  import { workspace } from "../workspace/workspace";
@@ -25,8 +25,8 @@ import type { Tab } from "../tabs/tab";
25
25
  import { adoptCreatedProject } from "./project-adoption";
26
26
  import { translateValidationError } from "./ai-tools";
27
27
  import { validateDoc, validateProjectConfig } from "./jx-validate";
28
- import { recordWrite } from "./ai-writes";
29
28
  import { flagHardcodedTokens, formatTokenHints } from "./token-lint";
29
+ import { invalidPathResult, normalizeRelPath } from "./project-path";
30
30
 
31
31
  /** Directories the file tools never descend into or report. */
32
32
  const EXCLUDED_DIRS = new Set(["node_modules", "dist", ".git", ".jx-cache"]);
@@ -39,34 +39,6 @@ const WRITE_CAP_BYTES = 256 * 1024;
39
39
 
40
40
  const NOT_UNDOABLE = "(saved to disk; not undoable with Cmd+Z)";
41
41
 
42
- /**
43
- * Normalize a project-relative path, or return null when it escapes the project (absolute paths,
44
- * `..` segments, drive letters). The server re-checks; this keeps the error actionable.
45
- */
46
- export function normalizeRelPath(path: unknown): string | null {
47
- if (typeof path !== "string" || !path.trim()) {
48
- return null;
49
- }
50
- let p = path.trim().replaceAll("\\", "/");
51
- while (p.startsWith("./")) {
52
- p = p.slice(2);
53
- }
54
- if (p.startsWith("/") || p.startsWith("~") || /^[A-Za-z]:/.test(p)) {
55
- return null;
56
- }
57
- if (p.split("/").includes("..")) {
58
- return null;
59
- }
60
- return p;
61
- }
62
-
63
- function pathError(path: unknown): ToolResult {
64
- return {
65
- success: false,
66
- error: `Invalid path ${JSON.stringify(path)} — use a path relative to the project root (no leading "/", no "..").`,
67
- };
68
- }
69
-
70
42
  /**
71
43
  * Whether a project-relative path conventionally holds a Jx document. Kept in step with
72
44
  * monaco-setup.ts's DOCUMENT_FILE_MATCH so the assistant's gate and the editor's diagnostics cover
@@ -170,7 +142,7 @@ export function registerProjectTools(
170
142
  const { dir } = args as { dir?: string };
171
143
  const start = dir === undefined || dir === "" ? "." : normalizeRelPath(dir);
172
144
  if (start === null) {
173
- return pathError(dir);
145
+ return invalidPathResult(dir);
174
146
  }
175
147
  const platform = getPlatform();
176
148
  const entries: { path: string; type: string; size?: number }[] = [];
@@ -234,7 +206,7 @@ export function registerProjectTools(
234
206
  async execute(args) {
235
207
  const relPath = normalizeRelPath((args as { path: string }).path);
236
208
  if (relPath === null) {
237
- return pathError((args as { path: string }).path);
209
+ return invalidPathResult((args as { path: string }).path);
238
210
  }
239
211
  try {
240
212
  const content = await getPlatform().readFile(relPath);
@@ -282,11 +254,11 @@ export function registerProjectTools(
282
254
  },
283
255
  required: ["path", "content"],
284
256
  },
285
- async execute(args) {
257
+ async execute(args, ctx) {
286
258
  const { path, content } = args as { path: string; content: string };
287
259
  const relPath = normalizeRelPath(path);
288
260
  if (relPath === null) {
289
- return pathError(path);
261
+ return invalidPathResult(path);
290
262
  }
291
263
  if (content.length > WRITE_CAP_BYTES) {
292
264
  return {
@@ -389,11 +361,17 @@ export function registerProjectTools(
389
361
  await getPlatform().writeFile(relPath, content);
390
362
  /* Disk, not transaction: there is no undo behind this and there never was. Recorded as
391
363
  `disk: true` so the panel can say so to the person holding ⌘Z — the caveat used to be
392
- appended to the model-facing summary only (§7.4). */
393
- recordWrite({ disk: true, ok: true, path: relPath, tool: "write_file" });
364
+ appended to the model-facing summary only (`ai.md` §3.2). */
365
+ ctx.ledger.record({ disk: true, ok: true, path: relPath, tool: "write_file" });
394
366
  } catch (error) {
395
367
  const message = error instanceof Error ? error.message : String(error);
396
- recordWrite({ disk: true, error: message, ok: false, path: relPath, tool: "write_file" });
368
+ ctx.ledger.record({
369
+ disk: true,
370
+ error: message,
371
+ ok: false,
372
+ path: relPath,
373
+ tool: "write_file",
374
+ });
397
375
  return {
398
376
  success: false,
399
377
  error: `Failed to write "${relPath}": ${message}`,
@@ -511,7 +489,7 @@ export function registerProjectTools(
511
489
  },
512
490
  required: ["name"],
513
491
  },
514
- async execute(args) {
492
+ async execute(args, ctx) {
515
493
  const {
516
494
  name,
517
495
  description,
@@ -598,6 +576,10 @@ export function registerProjectTools(
598
576
  error: `Failed to create project: ${error instanceof Error ? error.message : String(error)}`,
599
577
  };
600
578
  }
579
+ /* The project is on disk from here, whether or not this window goes on to open it, and no
580
+ undo reaches it. Recorded, it is what the turn changed: without it a turn that bootstrapped
581
+ a project and then only read reported that it had applied nothing. */
582
+ ctx.ledger.record({ disk: true, ok: true, path: result.root, tool: "create_project" });
601
583
 
602
584
  /*
603
585
  * Git init, then the full project-open flow, then a check that it landed — all three in