@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.
- package/data/locales/en/elements.json +434 -0
- package/data/webdata.json +116 -139
- package/dist/chunks/2020-rqm7qrje.js +4 -0
- package/dist/chunks/adapter-cyy8vbcx.js +4 -0
- package/dist/chunks/add-new-flow-8cgtejv2.js +4 -0
- package/dist/chunks/{cf-account-picker-xb8vvt2h.js → cf-account-picker-v3qd2dhw.js} +3 -3
- package/dist/chunks/cf-settings-1fnw874z.js +4 -0
- package/dist/chunks/{color-area-h8dbasp1.js → color-area-frd9y9q6.js} +2 -2
- package/dist/chunks/color-field-rebpbe33.js +4 -0
- package/dist/chunks/{color-slider-nz2mrzky.js → color-slider-6bzmk3s7.js} +2 -2
- package/dist/chunks/color-t2bnnj8y.js +4 -0
- package/dist/chunks/{combobox-6hs00996.js → combobox-c49x7gf1.js} +3 -3
- package/dist/chunks/components-y8yhdd7d.js +4 -0
- package/dist/chunks/content-source-9e7qvmsf.js +4 -0
- package/dist/chunks/convert-to-repeater-pd2w4z21.js +4 -0
- package/dist/chunks/editor-mb9jzdrw.js +4 -0
- package/dist/chunks/entry-commands-at0wnx3f.js +4 -0
- package/dist/chunks/extension-sections-4z9qq0gh.js +4 -0
- package/dist/chunks/file-ops-dtdvjmza.js +4 -0
- package/dist/chunks/files-8pf70854.js +4 -0
- package/dist/chunks/format-host-fd3m2y2a.js +4 -0
- package/dist/chunks/fs-events-hrk29vcv.js +4 -0
- package/dist/chunks/grid-open-bh74v82z.js +4 -0
- package/dist/chunks/{grid-view-2f03wcgw.js → grid-view-a51mgzmq.js} +17 -17
- package/dist/chunks/{icons-gb967qc8.js → icons-qp8fn1k8.js} +2 -2
- package/dist/chunks/{iframe-entry-btck0e9s.js → iframe-entry-mpwe5pwk.js} +4 -4
- package/dist/chunks/{iframe-entry-2x9bn5ng.js → iframe-entry-msd43x9h.js} +4 -4
- package/dist/chunks/iframe-entry-rt8jz5t8.js +5 -0
- package/dist/chunks/{iframe-entry-b5qgggyp.js → iframe-entry-v40cr813.js} +4 -4
- package/dist/chunks/iframe-host-q0s0wwy3.js +4 -0
- package/dist/chunks/{image-bake-4afr4y6a.js → image-bake-q9ajdjsj.js} +3 -3
- package/dist/chunks/{image-editor-x7t8jbjn.js → image-editor-ke9std9b.js} +3 -3
- package/dist/chunks/index-57ang7ma.js +4 -0
- package/dist/chunks/index-eytr7av4.js +17 -0
- package/dist/chunks/{index-yjrfeks8.js → index-x495reff.js} +3 -3
- package/dist/chunks/{javascript-hy2mws8v.js → javascript-qe34bvaz.js} +3 -3
- package/dist/chunks/jsonMode-m8rseh31.js +13 -0
- package/dist/chunks/jx-validate-bbd1mary.js +4 -0
- package/dist/chunks/layers-panel-wcjtdqsa.js +4 -0
- package/dist/chunks/layers-yscpys6q.js +4 -0
- package/dist/chunks/{listbox-7gka1h86.js → listbox-tktzq5xj.js} +2 -2
- package/dist/chunks/lit-html-qm5wbhqz.js +4 -0
- package/dist/chunks/media-library-2hfff88g.js +4 -0
- package/dist/chunks/media-open-4pb05cne.js +4 -0
- package/dist/chunks/media-picker-bzktmmwr.js +4 -0
- package/dist/chunks/menu-xc3cbgsd.js +4 -0
- package/dist/chunks/{monaco-binding-a51py33c.js → monaco-binding-7t38arh9.js} +3 -3
- package/dist/chunks/monaco-buffer-3vz94c9f.js +4 -0
- package/dist/chunks/monaco-setup-fstdew02.js +4 -0
- package/dist/chunks/notify-gbdcckrh.js +4 -0
- package/dist/chunks/parse-fpdzgk4c.js +4 -0
- package/dist/chunks/platform-9g72gdqr.js +4 -0
- package/dist/chunks/popover-cnmsav6b.js +4 -0
- package/dist/chunks/publish-panel-91x1cb5d.js +4 -0
- package/dist/chunks/publish-quick-e2dmf7eg.js +4 -0
- package/dist/chunks/publish-workspace-1x8mnzrj.js +4 -0
- package/dist/chunks/quick-search-2m4y6p1x.js +4 -0
- package/dist/chunks/references-yg1g9rwx.js +4 -0
- package/dist/chunks/right-panel-7mfyhzrk.js +4 -0
- package/dist/chunks/source-control-rrc4jgp5.js +4 -0
- package/dist/chunks/{split-m5rwn31z.js → split-d15yencd.js} +2 -2
- package/dist/chunks/studio-0jhhznxy.js +9 -0
- package/dist/chunks/studio-0nb57pjg.js +7 -0
- package/dist/chunks/{studio-s24mhq0z.js → studio-0wa4wv4s.js} +4 -4
- package/dist/chunks/studio-0z8kp4xk.js +5 -0
- package/dist/chunks/{studio-g0hxtcga.js → studio-18gc5t3v.js} +4 -4
- package/dist/chunks/{studio-4fnb5648.js → studio-1b79rpcb.js} +4 -4
- package/dist/chunks/studio-211q5j7m.js +4 -0
- package/dist/chunks/studio-213pyfxh.js +16 -0
- package/dist/chunks/studio-2b1smjvp.js +5 -0
- package/dist/chunks/{studio-nwf0c32p.js → studio-2v6tc9jp.js} +4 -4
- package/dist/chunks/studio-30j0wz99.js +5 -0
- package/dist/chunks/studio-3kmqhwtt.js +5 -0
- package/dist/chunks/studio-44drsmhw.js +5 -0
- package/dist/chunks/{studio-2pk7kc7z.js → studio-495b6f2r.js} +4 -4
- package/dist/chunks/{studio-jma2q8r9.js → studio-4gthb2fd.js} +4 -4
- package/dist/chunks/{studio-0gvvqj3h.js → studio-4xadhxj1.js} +4 -4
- package/dist/chunks/{studio-nvcn2ykc.js → studio-52mzbxqm.js} +4 -4
- package/dist/chunks/{studio-v7a7yb5w.js → studio-56mmnsw8.js} +7 -7
- package/dist/chunks/studio-59hz8f4r.js +5 -0
- package/dist/chunks/{studio-sce30r7b.js → studio-5k9mfafh.js} +3 -3
- package/dist/chunks/{studio-rr00ex23.js → studio-5qez462c.js} +6 -6
- package/dist/chunks/studio-6tnpg0vx.js +5 -0
- package/dist/chunks/studio-6w8mt734.js +6 -0
- package/dist/chunks/studio-7hm04x5z.js +5 -0
- package/dist/chunks/studio-8atmfjjm.js +7 -0
- package/dist/chunks/{studio-wmjmsw0p.js → studio-8n99z71s.js} +4 -4
- package/dist/chunks/studio-8vmad6x2.js +5 -0
- package/dist/chunks/studio-93p5k4r4.js +5 -0
- package/dist/chunks/{studio-2x972v58.js → studio-9gb0m81w.js} +4 -4
- package/dist/chunks/studio-a4ymg4vd.js +5 -0
- package/dist/chunks/studio-anc8067x.js +5 -0
- package/dist/chunks/{studio-2e4hry72.js → studio-dbdt67kr.js} +5 -5
- package/dist/chunks/studio-dbex091t.js +5 -0
- package/dist/chunks/{studio-amaz545p.js → studio-dj8ctxey.js} +4 -4
- package/dist/chunks/{studio-k2evngrj.js → studio-ed9bz68j.js} +5 -5
- package/dist/chunks/{studio-4bgvvzhv.js → studio-fe2d1fp5.js} +4 -4
- package/dist/chunks/{studio-45mj4jqe.js → studio-fgp4cyy3.js} +4 -4
- package/dist/chunks/studio-ft706q02.js +5 -0
- package/dist/chunks/studio-g79s1dyg.js +6 -0
- package/dist/chunks/{studio-pj216fmn.js → studio-gahswxp4.js} +4 -4
- package/dist/chunks/studio-gbqc12bk.js +5 -0
- package/dist/chunks/studio-gxphbetb.js +6 -0
- package/dist/chunks/studio-hdf01j7q.js +7 -0
- package/dist/chunks/studio-hf259xzd.js +48 -0
- package/dist/chunks/studio-jbva4wxw.js +5 -0
- package/dist/chunks/studio-jj9gf1qc.js +5 -0
- package/dist/chunks/studio-jsjg3mjv.js +5 -0
- package/dist/chunks/studio-jtvb8hd7.js +5 -0
- package/dist/chunks/{studio-g9brbewz.js → studio-kp4vzrct.js} +4 -4
- package/dist/chunks/studio-kpx02gaa.js +7 -0
- package/dist/chunks/{studio-3na1r48j.js → studio-kv1728ex.js} +8 -8
- package/dist/chunks/studio-m25ry0mf.js +5 -0
- package/dist/chunks/studio-mcbwmkyh.js +5 -0
- package/dist/chunks/studio-meajkr51.js +5 -0
- package/dist/chunks/studio-mkjrjpr2.js +6 -0
- package/dist/chunks/{studio-eqcddvc2.js → studio-mr084886.js} +201 -201
- package/dist/chunks/studio-mrb10hv3.js +5 -0
- package/dist/chunks/{studio-0939t1q1.js → studio-nhpg405j.js} +4 -4
- package/dist/chunks/studio-nsec0hxm.js +64 -0
- package/dist/chunks/{studio-xx3r3sb0.js → studio-nv0mh0p5.js} +4 -4
- package/dist/chunks/studio-pafmb06w.js +5 -0
- package/dist/chunks/{studio-pm17wdc4.js → studio-pgftd79b.js} +4 -4
- package/dist/chunks/{studio-4zd6an2b.js → studio-pk961xgz.js} +4 -4
- package/dist/chunks/studio-pzbr7g05.js +5 -0
- package/dist/chunks/studio-qcc5hsw2.js +10 -0
- package/dist/chunks/studio-qfq58c8n.js +5 -0
- package/dist/chunks/studio-qvb2jr83.js +5 -0
- package/dist/chunks/studio-r85zw6ay.js +5 -0
- package/dist/chunks/studio-s0kwce5w.js +9 -0
- package/dist/chunks/studio-s6ymkf3q.js +14 -0
- package/dist/chunks/{studio-0x6f3nk9.js → studio-s7p5ccxa.js} +4 -4
- package/dist/chunks/{studio-1410q5dw.js → studio-s9jjap3x.js} +4 -4
- package/dist/chunks/{studio-evq47j2z.js → studio-t5hde1ws.js} +4 -4
- package/dist/chunks/studio-vj9yv897.js +5 -0
- package/dist/chunks/studio-wmak8xjw.js +5 -0
- package/dist/chunks/{studio-bnc50f9h.js → studio-wqsmvtqj.js} +4 -4
- package/dist/chunks/studio-x3z3rhej.js +5 -0
- package/dist/chunks/{studio-bg8pzprg.js → studio-xb55kpmy.js} +4 -4
- package/dist/chunks/studio-xxtdajmv.js +12 -0
- package/dist/chunks/studio-xxttcym1.js +5 -0
- package/dist/chunks/{studio-90zkgne5.js → studio-y2zsnzye.js} +7 -7
- package/dist/chunks/{studio-3qr08sxq.js → studio-y3tjh2xb.js} +3 -3
- package/dist/chunks/studio-y4f1rdsj.js +5 -0
- package/dist/chunks/studio-ybkd5w8k.js +5 -0
- package/dist/chunks/{studio-tpxcryf2.js → studio-yetr4gg1.js} +4 -4
- package/dist/chunks/studio-z295anhs.js +6 -0
- package/dist/chunks/studio-z89e6wpg.js +9 -0
- package/dist/chunks/{studio-5tet77de.js → studio-zbwr6prc.js} +3 -3
- package/dist/chunks/{studio-daksw8kh.js → studio-zdbsv5q2.js} +4 -4
- package/dist/chunks/{studio-abh9kfdh.js → studio-zvthj8hg.js} +4 -4
- package/dist/chunks/toast-2kn2vrn4.js +4 -0
- package/dist/chunks/{toast-host-g3r2s0kn.js → toast-host-3rxeyj3d.js} +3 -3
- package/dist/chunks/{tooltip-dj1pvgz7.js → tooltip-ghkxkasp.js} +3 -3
- package/dist/chunks/{tree-j0ebwr5s.js → tree-vw4nc63s.js} +2 -2
- package/dist/chunks/tsMode-7gngmamr.js +14 -0
- package/dist/chunks/workspace-gv0hxgsm.js +4 -0
- package/dist/chunks/workspace-home-j7e0mjy5.js +4 -0
- package/dist/chunks/{ws-client-arz2qsy9.js → ws-client-vs11sxc5.js} +3 -3
- package/dist/iframe-entry.js +14 -14
- package/dist/manifest.json +84 -83
- package/dist/studio.js +99 -98
- package/dist/workers/ts.worker.js +3 -3
- package/package.json +12 -12
- package/src/browse/library-layouts.ts +9 -12
- package/src/browse/library-pane.ts +22 -16
- package/src/browse/library-source.ts +3 -4
- package/src/browse/preview-style.ts +2 -1
- package/src/canvas/canvas-live-render.ts +2 -5
- package/src/canvas/canvas-page-context.ts +5 -0
- package/src/canvas/canvas-render.ts +6 -1
- package/src/canvas/iframe-drop.ts +84 -1
- package/src/canvas/iframe-entry.ts +9 -6
- package/src/canvas/iframe-render.ts +13 -0
- package/src/collab/collab-session.ts +2 -2
- package/src/commands/levels.ts +1 -1
- package/src/content/entry-commands.ts +32 -27
- package/src/content/entry-editor.ts +46 -44
- package/src/content/entry-inspector.ts +15 -12
- package/src/content/entry-model.ts +3 -3
- package/src/content/entry-summaries.ts +5 -5
- package/src/content/hold-state.ts +172 -0
- package/src/content/page-summaries.ts +10 -14
- package/src/create/add-new-flow.ts +2 -1
- package/src/editor/convert-targets.ts +2 -23
- package/src/editor/inline-edit.ts +6 -1
- package/src/editor/repeater-scope.ts +333 -74
- package/src/editor/slash-menu.ts +35 -17
- package/src/files/files.ts +18 -0
- package/src/format/format-choices.ts +5 -5
- package/src/format/format-host.ts +35 -3
- package/src/grid/redirects-grid.ts +4 -4
- package/src/grid/sources/content-source.ts +42 -66
- package/src/locale/element-names.ts +131 -0
- package/src/panels/block-action-bar.ts +3 -4
- package/src/panels/dnd.ts +3 -1
- package/src/panels/elements-panel.ts +93 -36
- package/src/panels/events-panel.ts +6 -2
- package/src/panels/formula-workspace.ts +9 -0
- package/src/panels/imports-panel.ts +3 -2
- package/src/panels/navigator-panels.ts +10 -10
- package/src/panels/panel-registry.ts +3 -3
- package/src/panels/properties-panel.ts +127 -35
- package/src/panels/site-panel.ts +2 -1
- package/src/panels/statement-editor.ts +4 -0
- package/src/panels/style-panel.ts +2 -1
- package/src/panels/styles-panel.ts +2 -2
- package/src/panels/tab-strip.ts +29 -26
- package/src/platforms/cloud.ts +5 -2
- package/src/services/ai-command-tools.ts +2 -1
- package/src/services/ai-project-tools.ts +21 -8
- package/src/services/ai-tools.ts +5 -5
- package/src/services/ai-write-report.ts +22 -4
- package/src/services/context-manager.ts +110 -2
- package/src/services/document-assistant.ts +2 -2
- package/src/services/harness/chat-reducer.ts +9 -4
- package/src/services/jxn-language.ts +110 -0
- package/src/services/model-uri.ts +3 -0
- package/src/services/monaco-setup.ts +6 -2
- package/src/services/render-critic.ts +234 -12
- package/src/services/tool-executor.ts +16 -8
- package/src/settings/contributed-section.ts +2 -1
- package/src/settings/extension-commands.ts +2 -1
- package/src/settings/general-settings.ts +2 -2
- package/src/settings/settings-document.ts +5 -5
- package/src/shell.ts +1 -1
- package/src/studio.ts +28 -11
- package/src/style/variables-view.ts +2 -2
- package/src/surfaces/collections-panel.json +1 -1
- package/src/surfaces/collections-panel.ts +1 -1
- package/src/surfaces/entry-editor.json +10 -10
- package/src/surfaces/entry-editor.ts +22 -19
- package/src/surfaces/entry-inspector.json +11 -11
- package/src/surfaces/entry-inspector.ts +17 -10
- package/src/surfaces/panel-elements.json +466 -206
- package/src/surfaces/panel-elements.ts +61 -25
- package/src/surfaces/tab-strip.json +5 -5
- package/src/surfaces/tab-strip.ts +2 -2
- package/src/tabs/project-config.ts +13 -13
- package/src/tabs/tab.ts +7 -2
- package/src/types.ts +13 -2
- package/src/ui/expression-editor.ts +10 -5
- package/src/ui/schema-form.ts +18 -1
- package/src/utils/edit-display.ts +27 -1
- package/src/view.ts +9 -2
- package/src/workspace/tab-workspace.ts +2 -2
- package/src/workspace/workspace-back.ts +2 -2
- package/dist/chunks/2020-906vx2dq.js +0 -4
- package/dist/chunks/adapter-hnvad61x.js +0 -4
- package/dist/chunks/add-new-flow-v0pm96mr.js +0 -4
- package/dist/chunks/cf-settings-mkbcz4z2.js +0 -4
- package/dist/chunks/color-04361ttk.js +0 -4
- package/dist/chunks/color-field-qv5r2b16.js +0 -4
- package/dist/chunks/components-w5zqwva1.js +0 -4
- package/dist/chunks/content-source-g38w0d3f.js +0 -4
- package/dist/chunks/convert-to-repeater-r8sfxjjm.js +0 -4
- package/dist/chunks/editor-wvf5e6j9.js +0 -4
- package/dist/chunks/entry-commands-t13dyw3c.js +0 -4
- package/dist/chunks/extension-sections-vjzw46xb.js +0 -4
- package/dist/chunks/file-ops-1sghzgcr.js +0 -4
- package/dist/chunks/files-zrqtgt41.js +0 -4
- package/dist/chunks/format-host-9gy76erh.js +0 -4
- package/dist/chunks/fs-events-45rhg3me.js +0 -4
- package/dist/chunks/grid-open-t4fq6bbm.js +0 -4
- package/dist/chunks/iframe-entry-42hpmwz5.js +0 -5
- package/dist/chunks/iframe-host-9aq8ze3q.js +0 -4
- package/dist/chunks/index-f9qr7kzh.js +0 -4
- package/dist/chunks/jsonMode-vj30te2h.js +0 -13
- package/dist/chunks/jx-validate-r4ax11e3.js +0 -4
- package/dist/chunks/layers-m49nmwvx.js +0 -4
- package/dist/chunks/layers-panel-zr92pwdp.js +0 -4
- package/dist/chunks/lit-html-k2mb9k0r.js +0 -4
- package/dist/chunks/media-library-j74rt0w4.js +0 -4
- package/dist/chunks/media-open-mypr0k6b.js +0 -4
- package/dist/chunks/media-picker-pemtbsjv.js +0 -4
- package/dist/chunks/menu-d2jxpgma.js +0 -4
- package/dist/chunks/monaco-buffer-pa0g6ax7.js +0 -4
- package/dist/chunks/monaco-setup-0hw9f835.js +0 -4
- package/dist/chunks/notify-bwehrp7q.js +0 -4
- package/dist/chunks/parse-hee7ne9b.js +0 -4
- package/dist/chunks/platform-kv0k65cy.js +0 -4
- package/dist/chunks/popover-9tf4cjrk.js +0 -4
- package/dist/chunks/publish-panel-vg1n880b.js +0 -4
- package/dist/chunks/publish-quick-4fwngwrw.js +0 -4
- package/dist/chunks/publish-workspace-jv4w2bx2.js +0 -4
- package/dist/chunks/quick-search-nh29rt8a.js +0 -4
- package/dist/chunks/references-vqyr5s9x.js +0 -4
- package/dist/chunks/right-panel-3ws9f2w0.js +0 -4
- package/dist/chunks/source-control-h3hcs5gy.js +0 -4
- package/dist/chunks/studio-0khd8xtx.js +0 -4
- package/dist/chunks/studio-1ctnw3xv.js +0 -5
- package/dist/chunks/studio-1ks5d9y4.js +0 -5
- package/dist/chunks/studio-1y9mab9m.js +0 -5
- package/dist/chunks/studio-2kpxm0eh.js +0 -7
- package/dist/chunks/studio-2zz4qbv9.js +0 -5
- package/dist/chunks/studio-3pz4t5a2.js +0 -5
- package/dist/chunks/studio-4f7thq0p.js +0 -5
- package/dist/chunks/studio-61rg2cz5.js +0 -5
- package/dist/chunks/studio-7rrdbvex.js +0 -5
- package/dist/chunks/studio-9c6cbq2c.js +0 -4
- package/dist/chunks/studio-9dq7rwc7.js +0 -5
- package/dist/chunks/studio-9gr7xs9r.js +0 -6
- package/dist/chunks/studio-a88ej93p.js +0 -7
- package/dist/chunks/studio-acer0g8w.js +0 -5
- package/dist/chunks/studio-az3wvyh2.js +0 -5
- package/dist/chunks/studio-brh8d2pr.js +0 -5
- package/dist/chunks/studio-c2fa783p.js +0 -9
- package/dist/chunks/studio-cfnn70h1.js +0 -5
- package/dist/chunks/studio-cqxj4fcy.js +0 -5
- package/dist/chunks/studio-cw11h078.js +0 -5
- package/dist/chunks/studio-dn56z7sn.js +0 -5
- package/dist/chunks/studio-egnjpyde.js +0 -9
- package/dist/chunks/studio-ejrypvmq.js +0 -5
- package/dist/chunks/studio-frc16j8v.js +0 -10
- package/dist/chunks/studio-gnazwtv3.js +0 -5
- package/dist/chunks/studio-hfqpfmpk.js +0 -5
- package/dist/chunks/studio-hh394phe.js +0 -5
- package/dist/chunks/studio-j0zf5x2x.js +0 -5
- package/dist/chunks/studio-j5fcp6gj.js +0 -5
- package/dist/chunks/studio-jd1qb1st.js +0 -7
- package/dist/chunks/studio-k0xzrse2.js +0 -64
- package/dist/chunks/studio-k1xqrgm5.js +0 -5
- package/dist/chunks/studio-k3we3kqb.js +0 -5
- package/dist/chunks/studio-knsjyx7n.js +0 -5
- package/dist/chunks/studio-mrjtnm62.js +0 -5
- package/dist/chunks/studio-n9qba0gx.js +0 -5
- package/dist/chunks/studio-nzemfbkk.js +0 -14
- package/dist/chunks/studio-pwctzka3.js +0 -16
- package/dist/chunks/studio-q80fg1d6.js +0 -12
- package/dist/chunks/studio-q9hx2qp0.js +0 -5
- package/dist/chunks/studio-qrzmfaf6.js +0 -5
- package/dist/chunks/studio-r6mx7vm5.js +0 -7
- package/dist/chunks/studio-swedpd82.js +0 -5
- package/dist/chunks/studio-tmfcn347.js +0 -6
- package/dist/chunks/studio-v5wktj6f.js +0 -5
- package/dist/chunks/studio-vpevkfxw.js +0 -5
- package/dist/chunks/studio-wqfaqw4m.js +0 -5
- package/dist/chunks/studio-xpx91fby.js +0 -6
- package/dist/chunks/studio-xva3hzkv.js +0 -5
- package/dist/chunks/studio-z3fztxrp.js +0 -9
- package/dist/chunks/studio-z9453ypn.js +0 -6
- package/dist/chunks/toast-w11qpkyz.js +0 -4
- package/dist/chunks/tsMode-jg1dtphj.js +0 -14
- package/dist/chunks/workspace-6ztqcrsh.js +0 -4
- package/dist/chunks/workspace-home-rse9h8a0.js +0 -4
- 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 /
|
|
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 {
|
|
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(
|
|
91
|
+
const tab = workspace.tabs.get(projectConfigPath());
|
|
92
92
|
return (tab ? tab.doc.document : projectState?.projectConfig) ?? null;
|
|
93
93
|
}
|
|
94
94
|
|
package/src/panels/tab-strip.ts
CHANGED
|
@@ -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
|
|
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 {
|
|
61
|
-
import {
|
|
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
|
|
266
|
-
//
|
|
267
|
-
|
|
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
|
|
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 =
|
|
694
|
+
const pill = entryHoldPill(tab);
|
|
693
695
|
return [
|
|
694
696
|
{
|
|
695
697
|
dirty: tab.doc.dirty || imageEditorDirty(tab),
|
|
696
|
-
|
|
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.
|
|
1015
|
-
*
|
|
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
|
|
1020
|
-
*
|
|
1021
|
-
* `content.
|
|
1022
|
-
*
|
|
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/
|
|
1026
|
-
*
|
|
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
|
-
|
|
1031
|
-
|
|
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.
|
|
1044
|
-
*
|
|
1045
|
-
*
|
|
1046
|
-
*
|
|
1047
|
-
*
|
|
1048
|
-
*
|
|
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 ("
|
|
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,
|
package/src/platforms/cloud.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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 [
|
|
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
|
|
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?:
|
|
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
|
|
303
|
-
|
|
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
|
|
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
|
|
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.
|
package/src/services/ai-tools.ts
CHANGED
|
@@ -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?:
|
|
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?:
|
|
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?:
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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":
|