@jxsuite/studio 10.4.0 → 10.6.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 (346) hide show
  1. package/data/locales/en/elements.json +434 -0
  2. package/data/webdata.json +116 -139
  3. package/dist/chunks/2020-rqm7qrje.js +4 -0
  4. package/dist/chunks/adapter-cyy8vbcx.js +4 -0
  5. package/dist/chunks/add-new-flow-8cgtejv2.js +4 -0
  6. package/dist/chunks/{cf-account-picker-xb8vvt2h.js → cf-account-picker-v3qd2dhw.js} +3 -3
  7. package/dist/chunks/cf-settings-1fnw874z.js +4 -0
  8. package/dist/chunks/{color-area-h8dbasp1.js → color-area-frd9y9q6.js} +2 -2
  9. package/dist/chunks/color-field-rebpbe33.js +4 -0
  10. package/dist/chunks/{color-slider-nz2mrzky.js → color-slider-6bzmk3s7.js} +2 -2
  11. package/dist/chunks/color-t2bnnj8y.js +4 -0
  12. package/dist/chunks/{combobox-6hs00996.js → combobox-c49x7gf1.js} +3 -3
  13. package/dist/chunks/components-y8yhdd7d.js +4 -0
  14. package/dist/chunks/content-source-9e7qvmsf.js +4 -0
  15. package/dist/chunks/convert-to-repeater-pd2w4z21.js +4 -0
  16. package/dist/chunks/editor-mb9jzdrw.js +4 -0
  17. package/dist/chunks/entry-commands-at0wnx3f.js +4 -0
  18. package/dist/chunks/extension-sections-4z9qq0gh.js +4 -0
  19. package/dist/chunks/file-ops-dtdvjmza.js +4 -0
  20. package/dist/chunks/files-8pf70854.js +4 -0
  21. package/dist/chunks/format-host-fd3m2y2a.js +4 -0
  22. package/dist/chunks/fs-events-hrk29vcv.js +4 -0
  23. package/dist/chunks/grid-open-bh74v82z.js +4 -0
  24. package/dist/chunks/{grid-view-2f03wcgw.js → grid-view-a51mgzmq.js} +17 -17
  25. package/dist/chunks/{icons-gb967qc8.js → icons-qp8fn1k8.js} +2 -2
  26. package/dist/chunks/{iframe-entry-btck0e9s.js → iframe-entry-mpwe5pwk.js} +4 -4
  27. package/dist/chunks/{iframe-entry-2x9bn5ng.js → iframe-entry-msd43x9h.js} +4 -4
  28. package/dist/chunks/iframe-entry-rt8jz5t8.js +5 -0
  29. package/dist/chunks/{iframe-entry-b5qgggyp.js → iframe-entry-v40cr813.js} +4 -4
  30. package/dist/chunks/iframe-host-q0s0wwy3.js +4 -0
  31. package/dist/chunks/{image-bake-4afr4y6a.js → image-bake-q9ajdjsj.js} +3 -3
  32. package/dist/chunks/{image-editor-x7t8jbjn.js → image-editor-ke9std9b.js} +3 -3
  33. package/dist/chunks/index-57ang7ma.js +4 -0
  34. package/dist/chunks/index-eytr7av4.js +17 -0
  35. package/dist/chunks/{index-yjrfeks8.js → index-x495reff.js} +3 -3
  36. package/dist/chunks/{javascript-hy2mws8v.js → javascript-qe34bvaz.js} +3 -3
  37. package/dist/chunks/jsonMode-m8rseh31.js +13 -0
  38. package/dist/chunks/jx-validate-bbd1mary.js +4 -0
  39. package/dist/chunks/layers-panel-wcjtdqsa.js +4 -0
  40. package/dist/chunks/layers-yscpys6q.js +4 -0
  41. package/dist/chunks/{listbox-7gka1h86.js → listbox-tktzq5xj.js} +2 -2
  42. package/dist/chunks/lit-html-qm5wbhqz.js +4 -0
  43. package/dist/chunks/media-library-2hfff88g.js +4 -0
  44. package/dist/chunks/media-open-4pb05cne.js +4 -0
  45. package/dist/chunks/media-picker-bzktmmwr.js +4 -0
  46. package/dist/chunks/menu-xc3cbgsd.js +4 -0
  47. package/dist/chunks/{monaco-binding-a51py33c.js → monaco-binding-7t38arh9.js} +3 -3
  48. package/dist/chunks/monaco-buffer-3vz94c9f.js +4 -0
  49. package/dist/chunks/monaco-setup-fstdew02.js +4 -0
  50. package/dist/chunks/notify-gbdcckrh.js +4 -0
  51. package/dist/chunks/parse-fpdzgk4c.js +4 -0
  52. package/dist/chunks/platform-9g72gdqr.js +4 -0
  53. package/dist/chunks/popover-cnmsav6b.js +4 -0
  54. package/dist/chunks/publish-panel-91x1cb5d.js +4 -0
  55. package/dist/chunks/publish-quick-e2dmf7eg.js +4 -0
  56. package/dist/chunks/publish-workspace-1x8mnzrj.js +4 -0
  57. package/dist/chunks/quick-search-2m4y6p1x.js +4 -0
  58. package/dist/chunks/references-yg1g9rwx.js +4 -0
  59. package/dist/chunks/right-panel-7mfyhzrk.js +4 -0
  60. package/dist/chunks/source-control-rrc4jgp5.js +4 -0
  61. package/dist/chunks/{split-m5rwn31z.js → split-d15yencd.js} +2 -2
  62. package/dist/chunks/studio-0jhhznxy.js +9 -0
  63. package/dist/chunks/studio-0nb57pjg.js +7 -0
  64. package/dist/chunks/{studio-s24mhq0z.js → studio-0wa4wv4s.js} +4 -4
  65. package/dist/chunks/studio-0z8kp4xk.js +5 -0
  66. package/dist/chunks/{studio-g0hxtcga.js → studio-18gc5t3v.js} +4 -4
  67. package/dist/chunks/{studio-4fnb5648.js → studio-1b79rpcb.js} +4 -4
  68. package/dist/chunks/studio-211q5j7m.js +4 -0
  69. package/dist/chunks/studio-213pyfxh.js +16 -0
  70. package/dist/chunks/studio-2b1smjvp.js +5 -0
  71. package/dist/chunks/{studio-nwf0c32p.js → studio-2v6tc9jp.js} +4 -4
  72. package/dist/chunks/studio-30j0wz99.js +5 -0
  73. package/dist/chunks/studio-3kmqhwtt.js +5 -0
  74. package/dist/chunks/studio-44drsmhw.js +5 -0
  75. package/dist/chunks/{studio-2pk7kc7z.js → studio-495b6f2r.js} +4 -4
  76. package/dist/chunks/{studio-jma2q8r9.js → studio-4gthb2fd.js} +4 -4
  77. package/dist/chunks/{studio-0gvvqj3h.js → studio-4xadhxj1.js} +4 -4
  78. package/dist/chunks/{studio-nvcn2ykc.js → studio-52mzbxqm.js} +4 -4
  79. package/dist/chunks/{studio-v7a7yb5w.js → studio-56mmnsw8.js} +7 -7
  80. package/dist/chunks/studio-59hz8f4r.js +5 -0
  81. package/dist/chunks/{studio-sce30r7b.js → studio-5k9mfafh.js} +3 -3
  82. package/dist/chunks/{studio-rr00ex23.js → studio-5qez462c.js} +6 -6
  83. package/dist/chunks/studio-6tnpg0vx.js +5 -0
  84. package/dist/chunks/studio-6w8mt734.js +6 -0
  85. package/dist/chunks/studio-7hm04x5z.js +5 -0
  86. package/dist/chunks/studio-8atmfjjm.js +7 -0
  87. package/dist/chunks/{studio-wmjmsw0p.js → studio-8n99z71s.js} +4 -4
  88. package/dist/chunks/studio-8vmad6x2.js +5 -0
  89. package/dist/chunks/studio-93p5k4r4.js +5 -0
  90. package/dist/chunks/{studio-2x972v58.js → studio-9gb0m81w.js} +4 -4
  91. package/dist/chunks/studio-a4ymg4vd.js +5 -0
  92. package/dist/chunks/studio-anc8067x.js +5 -0
  93. package/dist/chunks/{studio-2e4hry72.js → studio-dbdt67kr.js} +5 -5
  94. package/dist/chunks/studio-dbex091t.js +5 -0
  95. package/dist/chunks/{studio-amaz545p.js → studio-dj8ctxey.js} +4 -4
  96. package/dist/chunks/{studio-k2evngrj.js → studio-ed9bz68j.js} +5 -5
  97. package/dist/chunks/{studio-4bgvvzhv.js → studio-fe2d1fp5.js} +4 -4
  98. package/dist/chunks/{studio-45mj4jqe.js → studio-fgp4cyy3.js} +4 -4
  99. package/dist/chunks/studio-ft706q02.js +5 -0
  100. package/dist/chunks/studio-g79s1dyg.js +6 -0
  101. package/dist/chunks/{studio-pj216fmn.js → studio-gahswxp4.js} +4 -4
  102. package/dist/chunks/studio-gbqc12bk.js +5 -0
  103. package/dist/chunks/studio-gxphbetb.js +6 -0
  104. package/dist/chunks/studio-hdf01j7q.js +7 -0
  105. package/dist/chunks/studio-hf259xzd.js +48 -0
  106. package/dist/chunks/studio-jbva4wxw.js +5 -0
  107. package/dist/chunks/studio-jj9gf1qc.js +5 -0
  108. package/dist/chunks/studio-jsjg3mjv.js +5 -0
  109. package/dist/chunks/studio-jtvb8hd7.js +5 -0
  110. package/dist/chunks/{studio-g9brbewz.js → studio-kp4vzrct.js} +4 -4
  111. package/dist/chunks/studio-kpx02gaa.js +7 -0
  112. package/dist/chunks/{studio-3na1r48j.js → studio-kv1728ex.js} +8 -8
  113. package/dist/chunks/studio-m25ry0mf.js +5 -0
  114. package/dist/chunks/studio-mcbwmkyh.js +5 -0
  115. package/dist/chunks/studio-meajkr51.js +5 -0
  116. package/dist/chunks/studio-mkjrjpr2.js +6 -0
  117. package/dist/chunks/{studio-eqcddvc2.js → studio-mr084886.js} +201 -201
  118. package/dist/chunks/studio-mrb10hv3.js +5 -0
  119. package/dist/chunks/{studio-0939t1q1.js → studio-nhpg405j.js} +4 -4
  120. package/dist/chunks/studio-nsec0hxm.js +64 -0
  121. package/dist/chunks/{studio-xx3r3sb0.js → studio-nv0mh0p5.js} +4 -4
  122. package/dist/chunks/studio-pafmb06w.js +5 -0
  123. package/dist/chunks/{studio-pm17wdc4.js → studio-pgftd79b.js} +4 -4
  124. package/dist/chunks/{studio-4zd6an2b.js → studio-pk961xgz.js} +4 -4
  125. package/dist/chunks/studio-pzbr7g05.js +5 -0
  126. package/dist/chunks/studio-qcc5hsw2.js +10 -0
  127. package/dist/chunks/studio-qfq58c8n.js +5 -0
  128. package/dist/chunks/studio-qvb2jr83.js +5 -0
  129. package/dist/chunks/studio-r85zw6ay.js +5 -0
  130. package/dist/chunks/studio-s0kwce5w.js +9 -0
  131. package/dist/chunks/studio-s6ymkf3q.js +14 -0
  132. package/dist/chunks/{studio-0x6f3nk9.js → studio-s7p5ccxa.js} +4 -4
  133. package/dist/chunks/{studio-1410q5dw.js → studio-s9jjap3x.js} +4 -4
  134. package/dist/chunks/{studio-evq47j2z.js → studio-t5hde1ws.js} +4 -4
  135. package/dist/chunks/studio-vj9yv897.js +5 -0
  136. package/dist/chunks/studio-wmak8xjw.js +5 -0
  137. package/dist/chunks/{studio-bnc50f9h.js → studio-wqsmvtqj.js} +4 -4
  138. package/dist/chunks/studio-x3z3rhej.js +5 -0
  139. package/dist/chunks/{studio-bg8pzprg.js → studio-xb55kpmy.js} +4 -4
  140. package/dist/chunks/studio-xxtdajmv.js +12 -0
  141. package/dist/chunks/studio-xxttcym1.js +5 -0
  142. package/dist/chunks/{studio-90zkgne5.js → studio-y2zsnzye.js} +7 -7
  143. package/dist/chunks/{studio-3qr08sxq.js → studio-y3tjh2xb.js} +3 -3
  144. package/dist/chunks/studio-y4f1rdsj.js +5 -0
  145. package/dist/chunks/studio-ybkd5w8k.js +5 -0
  146. package/dist/chunks/{studio-tpxcryf2.js → studio-yetr4gg1.js} +4 -4
  147. package/dist/chunks/studio-z295anhs.js +6 -0
  148. package/dist/chunks/studio-z89e6wpg.js +9 -0
  149. package/dist/chunks/{studio-5tet77de.js → studio-zbwr6prc.js} +3 -3
  150. package/dist/chunks/{studio-daksw8kh.js → studio-zdbsv5q2.js} +4 -4
  151. package/dist/chunks/{studio-abh9kfdh.js → studio-zvthj8hg.js} +4 -4
  152. package/dist/chunks/toast-2kn2vrn4.js +4 -0
  153. package/dist/chunks/{toast-host-g3r2s0kn.js → toast-host-3rxeyj3d.js} +3 -3
  154. package/dist/chunks/{tooltip-dj1pvgz7.js → tooltip-ghkxkasp.js} +3 -3
  155. package/dist/chunks/{tree-j0ebwr5s.js → tree-vw4nc63s.js} +2 -2
  156. package/dist/chunks/tsMode-7gngmamr.js +14 -0
  157. package/dist/chunks/workspace-gv0hxgsm.js +4 -0
  158. package/dist/chunks/workspace-home-j7e0mjy5.js +4 -0
  159. package/dist/chunks/{ws-client-arz2qsy9.js → ws-client-vs11sxc5.js} +3 -3
  160. package/dist/iframe-entry.js +14 -14
  161. package/dist/manifest.json +84 -83
  162. package/dist/studio.js +99 -98
  163. package/dist/workers/ts.worker.js +3 -3
  164. package/package.json +12 -12
  165. package/src/browse/library-layouts.ts +9 -12
  166. package/src/browse/library-pane.ts +22 -16
  167. package/src/browse/library-source.ts +3 -4
  168. package/src/browse/preview-style.ts +2 -1
  169. package/src/canvas/canvas-live-render.ts +2 -5
  170. package/src/canvas/canvas-page-context.ts +5 -0
  171. package/src/canvas/canvas-render.ts +6 -1
  172. package/src/canvas/iframe-drop.ts +84 -1
  173. package/src/canvas/iframe-entry.ts +9 -6
  174. package/src/canvas/iframe-render.ts +13 -0
  175. package/src/collab/collab-session.ts +2 -2
  176. package/src/commands/levels.ts +1 -1
  177. package/src/content/entry-commands.ts +32 -27
  178. package/src/content/entry-editor.ts +46 -44
  179. package/src/content/entry-inspector.ts +15 -12
  180. package/src/content/entry-model.ts +3 -3
  181. package/src/content/entry-summaries.ts +5 -5
  182. package/src/content/hold-state.ts +172 -0
  183. package/src/content/page-summaries.ts +10 -14
  184. package/src/create/add-new-flow.ts +2 -1
  185. package/src/editor/convert-targets.ts +2 -23
  186. package/src/editor/inline-edit.ts +6 -1
  187. package/src/editor/repeater-scope.ts +333 -74
  188. package/src/editor/slash-menu.ts +35 -17
  189. package/src/files/files.ts +18 -0
  190. package/src/format/format-choices.ts +5 -5
  191. package/src/format/format-host.ts +35 -3
  192. package/src/grid/redirects-grid.ts +4 -4
  193. package/src/grid/sources/content-source.ts +42 -66
  194. package/src/locale/element-names.ts +131 -0
  195. package/src/panels/block-action-bar.ts +3 -4
  196. package/src/panels/dnd.ts +3 -1
  197. package/src/panels/elements-panel.ts +93 -36
  198. package/src/panels/events-panel.ts +6 -2
  199. package/src/panels/formula-workspace.ts +9 -0
  200. package/src/panels/imports-panel.ts +3 -2
  201. package/src/panels/navigator-panels.ts +10 -10
  202. package/src/panels/panel-registry.ts +3 -3
  203. package/src/panels/properties-panel.ts +127 -35
  204. package/src/panels/site-panel.ts +2 -1
  205. package/src/panels/statement-editor.ts +4 -0
  206. package/src/panels/style-panel.ts +2 -1
  207. package/src/panels/styles-panel.ts +2 -2
  208. package/src/panels/tab-strip.ts +29 -26
  209. package/src/platforms/cloud.ts +5 -2
  210. package/src/services/ai-command-tools.ts +2 -1
  211. package/src/services/ai-project-tools.ts +21 -8
  212. package/src/services/ai-tools.ts +5 -5
  213. package/src/services/ai-write-report.ts +22 -4
  214. package/src/services/context-manager.ts +110 -2
  215. package/src/services/document-assistant.ts +2 -2
  216. package/src/services/harness/chat-reducer.ts +9 -4
  217. package/src/services/jxn-language.ts +110 -0
  218. package/src/services/model-uri.ts +3 -0
  219. package/src/services/monaco-setup.ts +6 -2
  220. package/src/services/render-critic.ts +234 -12
  221. package/src/services/tool-executor.ts +16 -8
  222. package/src/settings/contributed-section.ts +2 -1
  223. package/src/settings/extension-commands.ts +2 -1
  224. package/src/settings/general-settings.ts +2 -2
  225. package/src/settings/settings-document.ts +5 -5
  226. package/src/shell.ts +1 -1
  227. package/src/studio.ts +28 -11
  228. package/src/style/variables-view.ts +2 -2
  229. package/src/surfaces/collections-panel.json +1 -1
  230. package/src/surfaces/collections-panel.ts +1 -1
  231. package/src/surfaces/entry-editor.json +10 -10
  232. package/src/surfaces/entry-editor.ts +22 -19
  233. package/src/surfaces/entry-inspector.json +11 -11
  234. package/src/surfaces/entry-inspector.ts +17 -10
  235. package/src/surfaces/panel-elements.json +466 -206
  236. package/src/surfaces/panel-elements.ts +61 -25
  237. package/src/surfaces/tab-strip.json +5 -5
  238. package/src/surfaces/tab-strip.ts +2 -2
  239. package/src/tabs/project-config.ts +13 -13
  240. package/src/tabs/tab.ts +7 -2
  241. package/src/types.ts +13 -2
  242. package/src/ui/expression-editor.ts +10 -5
  243. package/src/ui/schema-form.ts +18 -1
  244. package/src/utils/edit-display.ts +27 -1
  245. package/src/view.ts +9 -2
  246. package/src/workspace/tab-workspace.ts +2 -2
  247. package/src/workspace/workspace-back.ts +2 -2
  248. package/dist/chunks/2020-906vx2dq.js +0 -4
  249. package/dist/chunks/adapter-hnvad61x.js +0 -4
  250. package/dist/chunks/add-new-flow-v0pm96mr.js +0 -4
  251. package/dist/chunks/cf-settings-mkbcz4z2.js +0 -4
  252. package/dist/chunks/color-04361ttk.js +0 -4
  253. package/dist/chunks/color-field-qv5r2b16.js +0 -4
  254. package/dist/chunks/components-w5zqwva1.js +0 -4
  255. package/dist/chunks/content-source-g38w0d3f.js +0 -4
  256. package/dist/chunks/convert-to-repeater-r8sfxjjm.js +0 -4
  257. package/dist/chunks/editor-wvf5e6j9.js +0 -4
  258. package/dist/chunks/entry-commands-t13dyw3c.js +0 -4
  259. package/dist/chunks/extension-sections-vjzw46xb.js +0 -4
  260. package/dist/chunks/file-ops-1sghzgcr.js +0 -4
  261. package/dist/chunks/files-zrqtgt41.js +0 -4
  262. package/dist/chunks/format-host-9gy76erh.js +0 -4
  263. package/dist/chunks/fs-events-45rhg3me.js +0 -4
  264. package/dist/chunks/grid-open-t4fq6bbm.js +0 -4
  265. package/dist/chunks/iframe-entry-42hpmwz5.js +0 -5
  266. package/dist/chunks/iframe-host-9aq8ze3q.js +0 -4
  267. package/dist/chunks/index-f9qr7kzh.js +0 -4
  268. package/dist/chunks/jsonMode-vj30te2h.js +0 -13
  269. package/dist/chunks/jx-validate-r4ax11e3.js +0 -4
  270. package/dist/chunks/layers-m49nmwvx.js +0 -4
  271. package/dist/chunks/layers-panel-zr92pwdp.js +0 -4
  272. package/dist/chunks/lit-html-k2mb9k0r.js +0 -4
  273. package/dist/chunks/media-library-j74rt0w4.js +0 -4
  274. package/dist/chunks/media-open-mypr0k6b.js +0 -4
  275. package/dist/chunks/media-picker-pemtbsjv.js +0 -4
  276. package/dist/chunks/menu-d2jxpgma.js +0 -4
  277. package/dist/chunks/monaco-buffer-pa0g6ax7.js +0 -4
  278. package/dist/chunks/monaco-setup-0hw9f835.js +0 -4
  279. package/dist/chunks/notify-bwehrp7q.js +0 -4
  280. package/dist/chunks/parse-hee7ne9b.js +0 -4
  281. package/dist/chunks/platform-kv0k65cy.js +0 -4
  282. package/dist/chunks/popover-9tf4cjrk.js +0 -4
  283. package/dist/chunks/publish-panel-vg1n880b.js +0 -4
  284. package/dist/chunks/publish-quick-4fwngwrw.js +0 -4
  285. package/dist/chunks/publish-workspace-jv4w2bx2.js +0 -4
  286. package/dist/chunks/quick-search-nh29rt8a.js +0 -4
  287. package/dist/chunks/references-vqyr5s9x.js +0 -4
  288. package/dist/chunks/right-panel-3ws9f2w0.js +0 -4
  289. package/dist/chunks/source-control-h3hcs5gy.js +0 -4
  290. package/dist/chunks/studio-0khd8xtx.js +0 -4
  291. package/dist/chunks/studio-1ctnw3xv.js +0 -5
  292. package/dist/chunks/studio-1ks5d9y4.js +0 -5
  293. package/dist/chunks/studio-1y9mab9m.js +0 -5
  294. package/dist/chunks/studio-2kpxm0eh.js +0 -7
  295. package/dist/chunks/studio-2zz4qbv9.js +0 -5
  296. package/dist/chunks/studio-3pz4t5a2.js +0 -5
  297. package/dist/chunks/studio-4f7thq0p.js +0 -5
  298. package/dist/chunks/studio-61rg2cz5.js +0 -5
  299. package/dist/chunks/studio-7rrdbvex.js +0 -5
  300. package/dist/chunks/studio-9c6cbq2c.js +0 -4
  301. package/dist/chunks/studio-9dq7rwc7.js +0 -5
  302. package/dist/chunks/studio-9gr7xs9r.js +0 -6
  303. package/dist/chunks/studio-a88ej93p.js +0 -7
  304. package/dist/chunks/studio-acer0g8w.js +0 -5
  305. package/dist/chunks/studio-az3wvyh2.js +0 -5
  306. package/dist/chunks/studio-brh8d2pr.js +0 -5
  307. package/dist/chunks/studio-c2fa783p.js +0 -9
  308. package/dist/chunks/studio-cfnn70h1.js +0 -5
  309. package/dist/chunks/studio-cqxj4fcy.js +0 -5
  310. package/dist/chunks/studio-cw11h078.js +0 -5
  311. package/dist/chunks/studio-dn56z7sn.js +0 -5
  312. package/dist/chunks/studio-egnjpyde.js +0 -9
  313. package/dist/chunks/studio-ejrypvmq.js +0 -5
  314. package/dist/chunks/studio-frc16j8v.js +0 -10
  315. package/dist/chunks/studio-gnazwtv3.js +0 -5
  316. package/dist/chunks/studio-hfqpfmpk.js +0 -5
  317. package/dist/chunks/studio-hh394phe.js +0 -5
  318. package/dist/chunks/studio-j0zf5x2x.js +0 -5
  319. package/dist/chunks/studio-j5fcp6gj.js +0 -5
  320. package/dist/chunks/studio-jd1qb1st.js +0 -7
  321. package/dist/chunks/studio-k0xzrse2.js +0 -64
  322. package/dist/chunks/studio-k1xqrgm5.js +0 -5
  323. package/dist/chunks/studio-k3we3kqb.js +0 -5
  324. package/dist/chunks/studio-knsjyx7n.js +0 -5
  325. package/dist/chunks/studio-mrjtnm62.js +0 -5
  326. package/dist/chunks/studio-n9qba0gx.js +0 -5
  327. package/dist/chunks/studio-nzemfbkk.js +0 -14
  328. package/dist/chunks/studio-pwctzka3.js +0 -16
  329. package/dist/chunks/studio-q80fg1d6.js +0 -12
  330. package/dist/chunks/studio-q9hx2qp0.js +0 -5
  331. package/dist/chunks/studio-qrzmfaf6.js +0 -5
  332. package/dist/chunks/studio-r6mx7vm5.js +0 -7
  333. package/dist/chunks/studio-swedpd82.js +0 -5
  334. package/dist/chunks/studio-tmfcn347.js +0 -6
  335. package/dist/chunks/studio-v5wktj6f.js +0 -5
  336. package/dist/chunks/studio-vpevkfxw.js +0 -5
  337. package/dist/chunks/studio-wqfaqw4m.js +0 -5
  338. package/dist/chunks/studio-xpx91fby.js +0 -6
  339. package/dist/chunks/studio-xva3hzkv.js +0 -5
  340. package/dist/chunks/studio-z3fztxrp.js +0 -9
  341. package/dist/chunks/studio-z9453ypn.js +0 -6
  342. package/dist/chunks/toast-w11qpkyz.js +0 -4
  343. package/dist/chunks/tsMode-jg1dtphj.js +0 -14
  344. package/dist/chunks/workspace-6ztqcrsh.js +0 -4
  345. package/dist/chunks/workspace-home-rse9h8a0.js +0 -4
  346. package/src/content/draft-state.ts +0 -108
@@ -62,6 +62,8 @@ export interface StatementEditorOpts {
62
62
  stateDefs: string[];
63
63
  stateEntries?: Record<string, JxStateDefinition> | null;
64
64
  allowEventRef: boolean;
65
+ /** Pointers beyond the document's own signals: inside a repeater, its item and that item's fields. */
66
+ extraRefs?: readonly SurfaceOption[] | null | undefined;
65
67
  /** The entry's declared CEM events — offered as dispatchEvent name completions. */
66
68
  emits?: CemEvent[];
67
69
  /**
@@ -342,6 +344,7 @@ function operandOpts(opts: StatementEditorOpts) {
342
344
  return {
343
345
  allowEventRef: opts.allowEventRef,
344
346
  depth: 0,
347
+ extraRefs: opts.extraRefs,
345
348
  stateDefs: opts.stateDefs,
346
349
  stateEntries: opts.stateEntries ?? null,
347
350
  };
@@ -578,6 +581,7 @@ export function flattenStatements(
578
581
  (n) => commit(n as JxStatement),
579
582
  {
580
583
  allowEventRef: opts.allowEventRef,
584
+ extraRefs: opts.extraRefs,
581
585
  stateDefs: opts.stateDefs,
582
586
  stateEntries: opts.stateEntries ?? null,
583
587
  },
@@ -54,6 +54,7 @@ import {
54
54
  isNumber,
55
55
  isString,
56
56
  } from "@jxsuite/schema/guards";
57
+ import { PROJECT_FILE_NAME } from "@jxsuite/schema/project-file";
57
58
  import { effect, effectScope, reactive } from "../reactivity";
58
59
  import {
59
60
  cancelStyleDebounce,
@@ -374,7 +375,7 @@ function stylebookTagOf(selector: string | null): string | null {
374
375
  * elements of its own for "in this document" to be about.
375
376
  */
376
377
  function isProjectStylesheet(path: string): boolean {
377
- return /(?:^|\/)project\.json$/.test(path);
378
+ return path.split("/").pop() === PROJECT_FILE_NAME;
378
379
  }
379
380
 
380
381
  /**
@@ -34,7 +34,7 @@ import { effect, effectScope } from "../reactivity";
34
34
  import { projectState } from "../store";
35
35
  import { setDockCollapsed, shell } from "../shell";
36
36
  import { workspace } from "../workspace/workspace";
37
- import { PROJECT_CONFIG_PATH } from "../tabs/tab";
37
+ import { projectConfigPath } from "../tabs/tab";
38
38
  import { componentRegistry } from "../files/components";
39
39
  import { runActiveReported } from "../commands/run-reported";
40
40
  import { showSettingsDocument } from "../settings/settings-document";
@@ -88,7 +88,7 @@ export interface StylesPanelInput {
88
88
  * opened with.
89
89
  */
90
90
  export function stylesConfig(): StylesPanelInput["config"] {
91
- const tab = workspace.tabs.get(PROJECT_CONFIG_PATH);
91
+ const tab = workspace.tabs.get(projectConfigPath());
92
92
  return (tab ? tab.doc.document : projectState?.projectConfig) ?? null;
93
93
  }
94
94
 
@@ -16,7 +16,7 @@
16
16
  * **The chip is a `jx-tab` now, and three of the things it used to draw by hand are the kit's**:
17
17
  * the selected state (`aria-selected` on a real `tab` inside a real `tablist`, where an `.active`
18
18
  * class used to be), the close button, and the dirty dot. The three that are Studio's own live in
19
- * `jx-tab`'s slots — the drill-in marker in `icon`, the draft pill in `status`, the pin toggle in
19
+ * `jx-tab`'s slots — the drill-in marker in `icon`, the hold pill in `status`, the pin toggle in
20
20
  * `actions` — which is the gap that kept this strip out of the tab pattern (`ui.md` §5.4). With it
21
21
  * closed the strip is one stop in the tab order, the arrows walk it, Home and End reach its ends,
22
22
  * and Delete closes a tab: a keyboard contract the × has never had.
@@ -57,8 +57,8 @@ import { gridTabLabel } from "../grid/grid-source";
57
57
  import { workspaceTabOrder } from "../workspace/tab-workspace";
58
58
  import { workspaceHomeLabel } from "../workspace/workspace-ids";
59
59
  import { shell, workspaceDrawsTabStrip } from "../shell";
60
- import { entryDraftPill } from "../content/entry-editor";
61
- import { DRAFT_FIELD, isDraftEntry } from "../content/draft-state";
60
+ import { entryHoldPill } from "../content/entry-editor";
61
+ import { holdFields, holdSwitch } from "../content/hold-state";
62
62
  import { entryFields } from "../content/entry-fields";
63
63
  import { collectionOfPath } from "../content/entry-model";
64
64
  import { activeRegistry } from "../commands/active-registry";
@@ -262,9 +262,11 @@ export function mount(host: HTMLElement) {
262
262
  void tab.pinned;
263
263
  void tab.preview;
264
264
  void tab.session.openedFrom;
265
- // The draft pill is on the chip, so the chip has to repaint when the flag changes — reading
266
- // The absent key tracks its ADDITION too, which is the transition that matters most.
267
- void tab.doc.content.frontmatter[DRAFT_FIELD];
265
+ // The hold pill is on the chip, so the chip has to repaint when a field its rules read
266
+ // Changes. Reading an absent key tracks its ADDITION too, which matters most.
267
+ for (const field of holdFields(collectionOfPath(tab.documentPath)?.def)) {
268
+ void tab.doc.content.frontmatter[field];
269
+ }
268
270
  }
269
271
  // An edit is a commitment: a preview tab with unsaved changes is no longer disposable, and
270
272
  // The dirty flags this effect already tracks are exactly the signal.
@@ -685,15 +687,15 @@ function chipView(
685
687
  if (!tab) {
686
688
  return [];
687
689
  }
688
- /* The draft pill (`site-architecture.md` §7.6). On the CHIP, not only inside the entry editor:
690
+ /* The hold pill (`site-architecture.md` §7.6). On the CHIP, not only inside the entry editor:
689
691
  the mistake this prevents — publishing something you believed was private — is made while
690
692
  glancing at a row of tabs, and it is made about a document that may not even be the active
691
693
  one. */
692
- const pill = entryDraftPill(tab);
694
+ const pill = entryHoldPill(tab);
693
695
  return [
694
696
  {
695
697
  dirty: tab.doc.dirty || imageEditorDirty(tab),
696
- draft: pill?.draft ?? false,
698
+ held: pill?.held ?? false,
697
699
  index,
698
700
  key: id,
699
701
  label: labels.get(id) ?? "Untitled",
@@ -1011,24 +1013,25 @@ export function dismissTabContextMenu() {
1011
1013
  * A tab addresses ONE document, and this is everything the chip can say about it — keyed by the
1012
1014
  * ARGUMENT NAME that asks for it.
1013
1015
  *
1014
- * The whole `context/tab` placement asks for exactly one argument today: `content.setDraft
1015
- * {draft}`. The other five records take none, because they read the ACTIVE document — which is why
1016
+ * The whole `context/tab` placement asks for exactly one argument today: `content.setHold {hold}`.
1017
+ * The other five records take none, because they read the ACTIVE document — which is why
1016
1018
  * {@link openTabContextMenu} activates the chip before it builds a single row.
1017
1019
  *
1018
1020
  * A fact is stated ONLY when it is true of this tab, and that is what decides whether a command
1019
- * appears at all: `styles/main.css` is an entry of no collection, so it states no `draft`, so "Set
1020
- * Draft" is not offered on it. The condition is `collectionOfPath` — the same question
1021
- * `content.setDraft`'s own `enablement` asks — so the menu never invents a rule the command does
1022
- * not have, and never renders a row into a refusal.
1021
+ * appears at all: `styles/main.css` is an entry of no collection, so it has no hold rules, so "Set
1022
+ * Hold" is not offered on it. The condition is `collectionOfPath` — the same question
1023
+ * `content.setHold`'s own `enablement` asks — so the menu never invents a rule the command does not
1024
+ * have, and never renders a row into a refusal.
1023
1025
  *
1024
1026
  * The VALUE is the state the row would reach, not the state the tab is in: a setter is named for
1025
- * where it lands (`content/draft-state.ts` says why), so the row offers the flip of what is true
1026
- * now and {@link statedChecked} reads the current state back out of it for the checkmark.
1027
+ * where it lands (`content/hold-state.ts` says why), so the row offers the flip of what is true now
1028
+ * and {@link statedChecked} reads the current state back out of it for the checkmark.
1027
1029
  */
1028
1030
  function tabRowFacts(tab: Tab): CommandArgValues {
1029
1031
  const facts: CommandArgValues = {};
1030
- if (collectionOfPath(tab.documentPath)) {
1031
- facts.draft = !isDraftEntry(entryFields(tab));
1032
+ const toggle = holdSwitch(collectionOfPath(tab.documentPath)?.def);
1033
+ if (toggle) {
1034
+ facts.hold = entryFields(tab)[toggle.field] !== true;
1032
1035
  }
1033
1036
  return facts;
1034
1037
  }
@@ -1040,16 +1043,16 @@ function tabRowFacts(tab: Tab): CommandArgValues {
1040
1043
  * A menu is the one surface that is READ before it is used, so it is the one surface that can show
1041
1044
  * a boolean instead of asking the author to remember it. That is an argument for the SETTER over
1042
1045
  * the toggle, and it is why this is derived from the args rather than from a list of command ids:
1043
- * `content.setDraft {draft}` names the state it reaches, so the row can say which state the tab is
1044
- * in now and still land somewhere definite when clicked. `document.togglePinned` names no state —
1045
- * "Pin / Unpin Document" is a title that admits it does not know which it will do — so it renders
1046
- * as a plain row here. Its idempotent sibling `document.setPinned {pinned}` would state its own
1047
- * value, with no edit to this file, on the day its record declares `context/tab`; that declaration
1048
- * lives in `workspace/workspace.ts`, which is the only place it can be made.
1046
+ * `content.setHold {hold}` names the state it reaches, so the row can say which state the tab is in
1047
+ * now and still land somewhere definite when clicked. `document.togglePinned` names no state — "Pin
1048
+ * / Unpin Document" is a title that admits it does not know which it will do — so it renders as a
1049
+ * plain row here. Its idempotent sibling `document.setPinned {pinned}` would state its own value,
1050
+ * with no edit to this file, on the day its record declares `context/tab`; that declaration lives
1051
+ * in `workspace/workspace.ts`, which is the only place it can be made.
1049
1052
  *
1050
1053
  * **It is a real checkbox row now, which it could not be under Spectrum.** `sp-menu` reassigned
1051
1054
  * every item's role one frame after connect whenever the menu declared no `selects`, so
1052
- * `role="menuitemcheckbox"` did not survive and the state had to be printed as a sentence ("Draft:
1055
+ * `role="menuitemcheckbox"` did not survive and the state had to be printed as a sentence ("Held:
1053
1056
  * no") in the description line instead. `jx-menu-item` derives `role` and `aria-checked` from
1054
1057
  * `checked` and nothing rewrites them, so the row states the boolean the way a screen reader
1055
1058
  * already knows how to announce — and the five rows that carry no state stay plain `menuitem`s,
@@ -20,6 +20,7 @@ import type { WsCollabConnection } from "@jxsuite/collab/client";
20
20
  import { isBoolean, isJsonObject, isNonNullObject, isString } from "@jxsuite/schema/guards";
21
21
  import type { JsonObject, JsonValue, JxMutableNode, ProjectConfig } from "@jxsuite/schema/types";
22
22
  import { componentMetaFrom } from "@jxsuite/schema/component-meta";
23
+ import { isJxnPath, isNativeDocumentPath, parseJxn } from "@jxsuite/schema/jxn";
23
24
  import { streamImport } from "../services/import-client";
24
25
  import type {
25
26
  AccountEntry,
@@ -1403,7 +1404,7 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
1403
1404
  if (!SKIP.has(entry.name) && !entry.name.startsWith(".")) {
1404
1405
  dirs.push(entry.path);
1405
1406
  }
1406
- } else if (entry.name.endsWith(".json") && jsonPaths.length < MAX_FILES) {
1407
+ } else if (isNativeDocumentPath(entry.name) && jsonPaths.length < MAX_FILES) {
1407
1408
  jsonPaths.push(entry.path);
1408
1409
  }
1409
1410
  }
@@ -1414,7 +1415,9 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
1414
1415
  const metas = await Promise.all(
1415
1416
  jsonPaths.map(async (path) => {
1416
1417
  try {
1417
- return componentMetaFrom(parseJson(await platform.readFile(path)), path);
1418
+ const text = await platform.readFile(path);
1419
+ const document = isJxnPath(path) ? parseJxn(text) : parseJson(text);
1420
+ return componentMetaFrom(isJsonObject(document) ? document : null, path);
1418
1421
  } catch {
1419
1422
  return null; // Not JSON, not readable, or not a component — all the same answer here.
1420
1423
  }
@@ -84,6 +84,7 @@ import type { CommandContext } from "../commands/context";
84
84
  import type { AiToolReport, AnyCommand, CommandRegistry } from "../commands/registry";
85
85
  import type { Tab } from "../tabs/tab";
86
86
  import { runAsActor } from "../tabs/transact";
87
+ import { projectConfigPath } from "../tabs/tab";
87
88
 
88
89
  /**
89
90
  * The record a selection-level projection composes through.
@@ -328,7 +329,7 @@ function defaultPaths(command: AnyCommand, tab: Tab | null): string[] {
328
329
  return [tab?.documentPath ?? "(untitled)"];
329
330
  }
330
331
  if (command.undo === "project") {
331
- return ["project.json"];
332
+ return [projectConfigPath()];
332
333
  }
333
334
  return ["(unknown file)"];
334
335
  }
@@ -17,6 +17,7 @@
17
17
  import { JX_TOOL_FACTS } from "@jxsuite/ai/jx-tools";
18
18
  import { createToolDefinition } from "@jxsuite/ai/tools";
19
19
  import { isJsonObject, isNumber } from "@jxsuite/schema/guards";
20
+ import { classifyJxn, isJxnPath, isNativeDocumentPath, parseJxn } from "@jxsuite/schema/jxn";
20
21
  import type { CreateProjectDestination, StudioPlatform } from "../types";
21
22
  import type { ToolRegistry } from "@jxsuite/ai/tools";
22
23
  import type { JsonValue, JxMutableNode, ProjectConfig } from "@jxsuite/schema/types";
@@ -28,8 +29,9 @@ import { adoptCreatedProject } from "./project-adoption";
28
29
  import { translateValidationError } from "./ai-tools";
29
30
  import { validateDoc, validateProjectConfig } from "./jx-validate";
30
31
  import { flagHardcodedTokens, formatTokenHints } from "./token-lint";
31
- import type { CandidateDocument } from "./ai-write-report";
32
+ import type { CandidateDocument, RenderProbe } from "./ai-write-report";
32
33
  import { invalidPathResult, normalizeRelPath } from "./project-path";
34
+ import { isProjectConfigPath } from "@jxsuite/schema/project-file";
33
35
 
34
36
  /** Directories the file tools never descend into or report. */
35
37
  const EXCLUDED_DIRS = new Set(["node_modules", "dist", ".git", ".jx-cache"]);
@@ -49,7 +51,7 @@ const NOT_UNDOABLE = "(saved to disk; not undoable with Cmd+Z)";
49
51
  * {@link looksLikeJxDoc} sniff.
50
52
  */
51
53
  function isJxDocPath(path: string): boolean {
52
- return /^(pages|layouts|components|elements)\//.test(path) && path.endsWith(".json");
54
+ return /^(pages|layouts|components|elements)\//.test(path) && isNativeDocumentPath(path);
53
55
  }
54
56
 
55
57
  /** Structural sniff for Jx-document-shaped JSON values. */
@@ -71,7 +73,7 @@ export interface ProjectToolsCtx {
71
73
  * judges the live configuration and a candidate parsed from the model's text alike.
72
74
  */
73
75
  validateProject?: (config: ProjectConfig | JsonValue) => Promise<string[]>;
74
- renderCheck?: (doc: CandidateDocument) => Promise<{ ok: true } | { ok: false; error: string }>;
76
+ renderCheck?: RenderProbe;
75
77
  /** Live getter — the project style appears only after a project is open/bootstrapped. */
76
78
  getProjectStyle?: () => Record<string, string> | undefined;
77
79
  /** The open tab whose `documentPath` equals the given project-relative path, if any. */
@@ -299,8 +301,19 @@ export function registerProjectTools(
299
301
 
300
302
  // Pre-validate Jx documents — disk writes have no undo to lean on.
301
303
  let parsed: JsonValue | undefined;
302
- const isJson = relPath.endsWith(".json") && relPath !== "project.json";
303
- if (isJson) {
304
+ const isJxn = isJxnPath(relPath) && classifyJxn(content) !== "project";
305
+ const isJson = relPath.endsWith(".json") && !isProjectConfigPath(relPath);
306
+ if (isJxn) {
307
+ try {
308
+ const document = parseJxn(content);
309
+ parsed = isJsonObject(document) ? document : null;
310
+ } catch (error) {
311
+ return {
312
+ success: false,
313
+ error: `Content is not valid Jx Notation: ${error instanceof Error ? error.message : String(error)}`,
314
+ };
315
+ }
316
+ } else if (isJson) {
304
317
  try {
305
318
  // SAFETY: `JSON.parse` without a reviver yields nothing but JSON values.
306
319
  parsed = JSON.parse(content) as JsonValue;
@@ -312,7 +325,7 @@ export function registerProjectTools(
312
325
  }
313
326
  }
314
327
  let tokenHints = "";
315
- // The parse above sets `parsed` exactly when `isJson` holds: it never yields `undefined`.
328
+ // The parse above sets `parsed` exactly when one of those holds: it never yields `undefined`.
316
329
  if (parsed !== undefined && (isJxDocPath(relPath) || looksLikeJxDoc(parsed))) {
317
330
  const errors = await validate(parsed);
318
331
  if (errors.length > 0) {
@@ -323,7 +336,7 @@ export function registerProjectTools(
323
336
  };
324
337
  }
325
338
  if (renderCheck) {
326
- const renderResult = await renderCheck(parsed);
339
+ const renderResult = await renderCheck(parsed, { documentPath: relPath });
327
340
  if (!renderResult.ok) {
328
341
  return {
329
342
  success: false,
@@ -345,7 +358,7 @@ export function registerProjectTools(
345
358
  /* Parsed as `unknown` because that is what it is until the schema says otherwise — this is
346
359
  model-authored text, and the gate below is the whole reason we do not trust its shape. */
347
360
  let projectConfig: ProjectConfig | undefined;
348
- if (relPath === "project.json") {
361
+ if (isProjectConfigPath(relPath)) {
349
362
  let parsedConfig: JsonValue;
350
363
  try {
351
364
  // SAFETY: `JSON.parse` without a reviver yields nothing but JSON values.
@@ -18,7 +18,7 @@ import { validateDoc } from "./jx-validate";
18
18
  import { invalidPathResult, normalizeRelPath } from "./project-path";
19
19
  import { serializeJson } from "@jxsuite/schema/json-layout";
20
20
  import { translateValidationError } from "./ai-write-report";
21
- import type { CandidateDocument } from "./ai-write-report";
21
+ import type { CandidateDocument, RenderProbe } from "./ai-write-report";
22
22
  import { studioDocumentHost, studioWriteReporter } from "./harness/studio-documents";
23
23
 
24
24
  /* Re-exported from its new home so `ai-project-tools.ts` and the tests keep their import: the
@@ -34,7 +34,7 @@ export { translateValidationError } from "./ai-write-report";
34
34
  * getTab: () => import("../tabs/tab").Tab | null;
35
35
  * validate?: (doc: CandidateDocument) => Promise<string[]>;
36
36
  * saveFile?: (relPath: string, content: string) => Promise<void>;
37
- * renderCheck?: (doc: CandidateDocument) => Promise<{ ok: true } | { ok: false; error: string }>;
37
+ * renderCheck?: RenderProbe;
38
38
  * projectStyle?: Record<string, string>;
39
39
  * getProjectStyle?: () => Record<string, string> | undefined;
40
40
  * findOpenTab?: (path: string) => import("../tabs/tab").Tab | null;
@@ -56,7 +56,7 @@ export function registerAiTools(
56
56
  getTab: () => Tab | null;
57
57
  validate?: (doc: CandidateDocument) => Promise<string[]>;
58
58
  saveFile?: (relPath: string, content: string) => Promise<void>;
59
- renderCheck?: (doc: CandidateDocument) => Promise<{ ok: true } | { ok: false; error: string }>;
59
+ renderCheck?: RenderProbe;
60
60
  projectStyle?: Record<string, string> | undefined;
61
61
  /**
62
62
  * Live variant of `projectStyle` — takes precedence; re-read per tool call so a project
@@ -164,7 +164,7 @@ export function registerAiTools(
164
164
  };
165
165
  }
166
166
  if (renderCheck) {
167
- const renderResult = await renderCheck(content);
167
+ const renderResult = await renderCheck(content, { documentPath: relPath });
168
168
  if (!renderResult.ok) {
169
169
  return {
170
170
  success: false,
@@ -252,7 +252,7 @@ export function registerAiTools(
252
252
  };
253
253
  }
254
254
  if (renderCheck) {
255
- const renderResult = await renderCheck(content);
255
+ const renderResult = await renderCheck(content, { documentPath: relPath });
256
256
  if (!renderResult.ok) {
257
257
  return {
258
258
  success: false,
@@ -22,7 +22,7 @@
22
22
  */
23
23
 
24
24
  import type { ToolResult } from "@jxsuite/ai/tools";
25
- import type { JsonValue, JxMutableNode } from "@jxsuite/schema/types";
25
+ import type { JsonValue, JxDocumentValue, JxMutableNode } from "@jxsuite/schema/types";
26
26
  import { toRaw } from "../reactivity";
27
27
  import type { Tab } from "../tabs/tab";
28
28
  import { flagHardcodedTokens, formatTokenHints } from "./token-lint";
@@ -37,12 +37,28 @@ export type RenderVerdict = { ok: true } | { ok: false; error: string };
37
37
  */
38
38
  export type CandidateDocument = JxMutableNode | JsonValue;
39
39
 
40
+ /** Where a document under a render check lives: what its route and relative references resolve from. */
41
+ export interface RenderSource {
42
+ /** Its project-relative path, once it has one. */
43
+ documentPath?: string | null | undefined;
44
+ /** A format-class page's frontmatter, which is where its `$paths` is. */
45
+ frontmatter?: Record<string, JxDocumentValue | undefined> | null | undefined;
46
+ }
47
+
48
+ /** A detached-DOM render probe. */
49
+ export type RenderProbe = (doc: CandidateDocument, source?: RenderSource) => Promise<RenderVerdict>;
50
+
51
+ /** Where an open tab's document lives, as a render check reads it. */
52
+ function renderSourceOf(tab: Tab): RenderSource {
53
+ return { documentPath: tab.documentPath, frontmatter: tab.doc.content.frontmatter };
54
+ }
55
+
40
56
  /** What a write reporter needs. Every member is what `registerAiTools` already takes by injection. */
41
57
  export interface WriteReportDeps {
42
58
  /** Schema errors for a document, as raw ajv sentences. */
43
59
  validate: (doc: CandidateDocument) => Promise<string[]>;
44
60
  /** The detached-DOM render probe, when the host has one. */
45
- renderCheck?: ((doc: CandidateDocument) => Promise<RenderVerdict>) | undefined;
61
+ renderCheck?: RenderProbe | undefined;
46
62
  /** The project's design tokens, read per call so a project opened mid-session contributes. */
47
63
  getProjectStyle?: (() => Record<string, string> | undefined) | undefined;
48
64
  }
@@ -122,7 +138,9 @@ export function translateValidationError(rawError: string): string {
122
138
  export async function snapshotBeforeWrite(tab: Tab, deps: WriteReportDeps): Promise<WriteSnapshot> {
123
139
  const rawBefore = toRaw(tab.doc.document);
124
140
  const errors = new Set(await deps.validate(rawBefore));
125
- const rendered = deps.renderCheck ? await deps.renderCheck(rawBefore) : { ok: true };
141
+ const rendered = deps.renderCheck
142
+ ? await deps.renderCheck(rawBefore, renderSourceOf(tab))
143
+ : { ok: true };
126
144
  return { errors, renderOk: rendered.ok };
127
145
  }
128
146
 
@@ -159,7 +177,7 @@ export async function reportDocumentWrite(
159
177
  }
160
178
 
161
179
  if (deps.renderCheck && before.renderOk) {
162
- const renderResult = await deps.renderCheck(rawAfter);
180
+ const renderResult = await deps.renderCheck(rawAfter, renderSourceOf(tab));
163
181
  if (!renderResult.ok) {
164
182
  return {
165
183
  success: false,
@@ -19,7 +19,8 @@ import {
19
19
  toChatMessages,
20
20
  toLiveMessages,
21
21
  } from "@jxsuite/ai/messages";
22
- import { isString } from "@jxsuite/schema/guards";
22
+ import { isJsonObject, isString } from "@jxsuite/schema/guards";
23
+ import type { JsonObject, JsonValue } from "@jxsuite/schema/types";
23
24
  import { modelContextWindow } from "./ai-models";
24
25
 
25
26
  /** How many tool messages {@link pruneOrphanToolMessages} dropped, moved and sealed. */
@@ -161,6 +162,17 @@ function contextTokens(
161
162
  chatState: ReturnType<typeof createChatState>,
162
163
  systemTokens: number,
163
164
  ): number {
165
+ return (
166
+ countedTokens(chatState, systemTokens) ??
167
+ systemTokens + estimateTokens(chatState.toMessagesArray())
168
+ );
169
+ }
170
+
171
+ /** The provider's count carried forward to this transcript, when there is one it still covers. */
172
+ function countedTokens(
173
+ chatState: ReturnType<typeof createChatState>,
174
+ systemTokens: number,
175
+ ): number | null {
164
176
  const { usage, messages } = chatState;
165
177
  const boundary = usage ? messages[usage.messageCount - 1] : undefined;
166
178
  if (usage && boundary && boundary.id === usage.lastMessageId) {
@@ -180,7 +192,7 @@ function contextTokens(
180
192
  return reused;
181
193
  }
182
194
  }
183
- return systemTokens + estimateTokens(chatState.toMessagesArray());
195
+ return null;
184
196
  }
185
197
 
186
198
  /**
@@ -282,6 +294,102 @@ export function trimContext(
282
294
  return { estimatedTokens: newTotal, droppedCount };
283
295
  }
284
296
 
297
+ // ─── Shed ───────────────────────────────────────────────────────────────────
298
+
299
+ /** What {@link shedToolResults} sheds down to, as a fraction of the window. */
300
+ const SHED_TARGET_FRACTION = 0.6;
301
+
302
+ /** A result smaller than this is mostly its stub already, and is often an answer worth keeping. */
303
+ const SHED_MIN_CHARS = 1024;
304
+
305
+ /** What a shed result tells the model. */
306
+ const SHED_NOTE =
307
+ "This result was removed to save space. Call the tool again if you still need it.";
308
+
309
+ /** A tool's reply as an object, or an empty one when it is not a result this module can read. */
310
+ function parsedReply(content: string): JsonObject {
311
+ try {
312
+ /* SAFETY: JSON.parse without a reviver turns text into nothing but JSON values. */
313
+ const parsed = JSON.parse(content) as JsonValue;
314
+ return isJsonObject(parsed) ? parsed : {};
315
+ } catch {
316
+ return {};
317
+ }
318
+ }
319
+
320
+ /**
321
+ * What is left of a tool's reply once its output is shed: how it ended and what it said it did, so
322
+ * the model and a restored chip keep the outcome and lose only the bulk.
323
+ */
324
+ function shedReply(content: string): string {
325
+ const { success = true, summary, error } = parsedReply(content);
326
+ return JSON.stringify({ success, summary, error, shed: SHED_NOTE });
327
+ }
328
+
329
+ /**
330
+ * Keep a turn inside the model's window between its rounds (specs/ai.md §3.8).
331
+ *
332
+ * {@link trimContext} runs when a message is sent, so nothing else bounds what one turn's own tool
333
+ * results add, and a turn with room to work could fill the window and have its request refused.
334
+ * Past the same budget the trim uses, this replaces the oldest large tool results with a stub until
335
+ * the context is back under {@link SHED_TARGET_FRACTION}. The results of the latest round are never
336
+ * touched, because the model has not read them yet, and the pairs are never broken, because only a
337
+ * reply's content changes.
338
+ *
339
+ * It changes the transcript rather than one request, so a shed result stays shed: the next send
340
+ * measures what it will really send, and the provider's prompt cache breaks once for a whole batch
341
+ * rather than on every round after the budget is reached.
342
+ *
343
+ * @param {ReturnType<typeof createChatState>} chatState
344
+ * @param {string} systemPrompt
345
+ * @returns {number} How many results it shed.
346
+ */
347
+ export function shedToolResults(
348
+ chatState: ReturnType<typeof createChatState>,
349
+ systemPrompt: string,
350
+ ): number {
351
+ const window = contextWindowFor(chatState.model);
352
+ const systemTokens = estimateTokens(systemPrompt);
353
+ const { messages } = chatState;
354
+ let total =
355
+ countedTokens(chatState, systemTokens) ??
356
+ systemTokens + estimateTokens(messages.map((message) => wireEntry(message)));
357
+ if (total <= Math.floor(window * BUDGET_FRACTION)) {
358
+ return 0;
359
+ }
360
+ const target = Math.floor(window * SHED_TARGET_FRACTION);
361
+ /* The latest round is the trailing run of replies, with the placeholder of the round about to
362
+ stream after it when there is one. */
363
+ let latest = messages.length;
364
+ while (
365
+ latest > 0 &&
366
+ messages[latest - 1]!.role === "assistant" &&
367
+ !messages[latest - 1]!.toolCalls
368
+ ) {
369
+ latest -= 1;
370
+ }
371
+ while (latest > 0 && messages[latest - 1]!.role === "tool") {
372
+ latest -= 1;
373
+ }
374
+ let shed = 0;
375
+ for (let i = 0; i < latest && total > target; i++) {
376
+ const message = messages[i]!;
377
+ if (message.role === "tool" && message.content.length >= SHED_MIN_CHARS) {
378
+ const before = estimateTokens(message.content);
379
+ message.content = shedReply(message.content);
380
+ total -= before - estimateTokens(message.content);
381
+ shed += 1;
382
+ }
383
+ }
384
+ if (shed > 0) {
385
+ // The provider counted the longer transcript; the estimate is all there is until it next counts.
386
+ chatState.clearUsage();
387
+ chatState.setTokenCount(total);
388
+ chatState.setContextWarning(true);
389
+ }
390
+ return shed;
391
+ }
392
+
285
393
  // ─── Orphaned tool calls ────────────────────────────────────────────────────
286
394
 
287
395
  /**
@@ -38,7 +38,7 @@ import { getAiProvider, getBaseUrl, getOpenAiKey } from "./ai-settings";
38
38
  import { preferredModel } from "./ai-models";
39
39
  import { pruneOrphanToolMessages, trimContext } from "./context-manager";
40
40
  import { renderCheck } from "./render-critic";
41
- import type { CandidateDocument, RenderVerdict } from "./ai-write-report";
41
+ import type { RenderProbe } from "./ai-write-report";
42
42
  import { validateDoc } from "./jx-validate";
43
43
  import { reloadFileInTab } from "../files/files";
44
44
  import { getExtensionCatalog, refreshExtensionUi } from "../format/format-host";
@@ -68,7 +68,7 @@ function projectConfig(): ProjectConfig | null {
68
68
  /* SAFETY: renderCheck runs the whole render inside a try and answers `{ ok: false, error }` for
69
69
  anything it cannot render, so JSON that is not (yet) a valid document is exactly what it is safe
70
70
  to hand; its JxDocument parameter names what renders, not what may be probed. */
71
- const probeRender = renderCheck as (doc: CandidateDocument) => Promise<RenderVerdict>;
71
+ const probeRender = renderCheck as RenderProbe;
72
72
 
73
73
  /**
74
74
  * Create a document-assistant session bound to the currently active tab.
@@ -39,10 +39,15 @@ export function isImportProgressEvent(value: unknown): value is ImportProgressEv
39
39
  function applyOutcome(chat: ChatStore, outcome: TurnOutcome): void {
40
40
  switch (outcome.kind) {
41
41
  case "cap_partial": {
42
- // A partial success is said as the assistant, not as a failure (specs/ai.md §3.2).
43
- chat.beginAssistantTurn();
44
- chat.appendDelta(outcome.cap?.text ?? "");
45
- chat.finishStream("length");
42
+ /* The model's closing reply has already been drawn, and there is nothing to add. Otherwise a
43
+ partial success is said as the assistant, not as a failure (specs/ai.md §3.2), after
44
+ removing whatever the closing round that gave no reply left behind. */
45
+ if (outcome.cap) {
46
+ chat.cancelStream();
47
+ chat.beginAssistantTurn();
48
+ chat.appendDelta(outcome.cap.text);
49
+ chat.finishStream("length");
50
+ }
46
51
  break;
47
52
  }
48
53
  case "cap_failed":