@jxsuite/studio 8.1.0 → 10.0.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/dist/chunks/{2020-dg097k3e.js → 2020-j56903f1.js} +3 -3
- package/dist/chunks/adapter-wc5zvg9a.js +4 -0
- package/dist/chunks/add-new-flow-snx1s4b0.js +4 -0
- package/dist/chunks/cf-account-picker-xyqw0jex.js +4 -0
- package/dist/chunks/cf-settings-hp1pjm0n.js +4 -0
- package/dist/chunks/color-04361ttk.js +4 -0
- package/dist/chunks/{color-area-vh9cew51.js → color-area-h8dbasp1.js} +2 -2
- package/dist/chunks/{color-field-e78f16bs.js → color-field-qv5r2b16.js} +3 -3
- package/dist/chunks/{color-slider-fg4yhvk6.js → color-slider-nz2mrzky.js} +2 -2
- package/dist/chunks/components-nkwphrx3.js +4 -0
- package/dist/chunks/content-source-6a253wmk.js +4 -0
- package/dist/chunks/{convert-to-repeater-grnmea6c.js → convert-to-repeater-fhdwyg14.js} +3 -3
- package/dist/chunks/editor-wqrc68ca.js +4 -0
- package/dist/chunks/entry-commands-nef0wrmp.js +4 -0
- package/dist/chunks/{extension-sections-rp7fkega.js → extension-sections-8ez2rrk8.js} +3 -3
- package/dist/chunks/file-ops-y3w71k1n.js +4 -0
- package/dist/chunks/files-de90apn9.js +4 -0
- package/dist/chunks/format-host-zfjz4rzm.js +4 -0
- package/dist/chunks/fs-events-77c0mr0x.js +4 -0
- package/dist/chunks/grid-open-4n4fjfpf.js +4 -0
- package/dist/chunks/{grid-view-x26rfpz1.js → grid-view-ka55z38f.js} +10 -10
- package/dist/chunks/{iframe-entry-pkgmcgp0.js → iframe-entry-2x9bn5ng.js} +4 -4
- package/dist/chunks/{iframe-entry-02wzcyvy.js → iframe-entry-btck0e9s.js} +4 -4
- package/dist/chunks/iframe-host-na2mywz4.js +4 -0
- package/dist/chunks/index-ewmgdb7c.js +4 -0
- package/dist/chunks/{index-203h71wy.js → index-qb98xhse.js} +3 -3
- package/dist/chunks/{javascript-ww5pv5ae.js → javascript-ces13wny.js} +2 -2
- package/dist/chunks/{jsonMode-vwbdwwq3.js → jsonMode-6md16d4v.js} +6 -6
- package/dist/chunks/jx-validate-b309c020.js +4 -0
- package/dist/chunks/layers-8rn8y6qv.js +4 -0
- package/dist/chunks/layers-panel-eyah7h9z.js +4 -0
- package/dist/chunks/lit-html-wyqw04y2.js +4 -0
- package/dist/chunks/media-library-gs3np47y.js +4 -0
- package/dist/chunks/media-picker-tt7dk4tb.js +4 -0
- package/dist/chunks/{monaco-binding-63tqze27.js → monaco-binding-p8j80s4w.js} +3 -3
- package/dist/chunks/monaco-buffer-wsf6cmnq.js +4 -0
- package/dist/chunks/{monaco-setup-zyyynnj8.js → monaco-setup-wasktzfg.js} +3 -3
- package/dist/chunks/notify-qwst108r.js +4 -0
- package/dist/chunks/parse-n8dfxnqf.js +4 -0
- package/dist/chunks/platform-c16mqvmp.js +4 -0
- package/dist/chunks/publish-panel-d34bhvwj.js +4 -0
- package/dist/chunks/{publish-quick-ajzdhhs6.js → publish-quick-vr695j26.js} +3 -3
- package/dist/chunks/publish-workspace-2yr60mt4.js +4 -0
- package/dist/chunks/quick-search-e6xsdv0d.js +4 -0
- package/dist/chunks/references-gsj6trdv.js +4 -0
- package/dist/chunks/right-panel-33jv1knc.js +4 -0
- package/dist/chunks/source-control-bj9bejjv.js +4 -0
- package/dist/chunks/studio-03mqh8ax.js +5 -0
- package/dist/chunks/{studio-957jg8g3.js → studio-06j4en5h.js} +4 -4
- package/dist/chunks/studio-0ydxv2mr.js +7 -0
- package/dist/chunks/{studio-v2ek69cb.js → studio-1e8dw2w2.js} +4 -4
- package/dist/chunks/studio-2hsqbq7b.js +7 -0
- package/dist/chunks/studio-2j86y34y.js +6 -0
- package/dist/chunks/studio-2ks6ka4g.js +5 -0
- package/dist/chunks/studio-34jrz12k.js +5 -0
- package/dist/chunks/{studio-1meqbxns.js → studio-35vrtpdq.js} +4 -4
- package/dist/chunks/studio-38g0jdz7.js +5 -0
- package/dist/chunks/{studio-25a5by3h.js → studio-396srjbk.js} +4 -4
- package/dist/chunks/{studio-zm7dnr9g.js → studio-3c8pp7pb.js} +4 -4
- package/dist/chunks/{studio-x3ge4rts.js → studio-3v5cagy9.js} +4 -4
- package/dist/chunks/studio-3w35qvay.js +5 -0
- package/dist/chunks/studio-3zge48z4.js +7 -0
- package/dist/chunks/studio-45gyxpcq.js +5 -0
- package/dist/chunks/{studio-hw60n5dh.js → studio-4kzbrr5t.js} +3 -3
- package/dist/chunks/{studio-533py3yy.js → studio-5tzgq6fx.js} +4 -4
- package/dist/chunks/studio-5wzdy1jc.js +16 -0
- package/dist/chunks/studio-732mw0g8.js +5 -0
- package/dist/chunks/{studio-3q6n9qvq.js → studio-771k2n12.js} +4 -4
- package/dist/chunks/{studio-z42kysg7.js → studio-7dzyz9n8.js} +137 -137
- package/dist/chunks/{studio-fqs1a22a.js → studio-7xag4na8.js} +4 -4
- package/dist/chunks/{studio-jqrg9xxg.js → studio-881z1xrx.js} +4 -4
- package/dist/chunks/studio-88bmt9rh.js +5 -0
- package/dist/chunks/{studio-8nyw10dp.js → studio-9b7177jq.js} +4 -4
- package/dist/chunks/{studio-nm9qev4p.js → studio-9cxmp61c.js} +5 -5
- package/dist/chunks/{studio-4dsdnan2.js → studio-9dz76yqc.js} +5 -5
- package/dist/chunks/{studio-a6x058ew.js → studio-agyvzfm4.js} +4 -4
- package/dist/chunks/{studio-2b8443ms.js → studio-ajw4k8hp.js} +3 -3
- package/dist/chunks/studio-c1pxszat.js +5 -0
- package/dist/chunks/studio-cpkamjdn.js +14 -0
- package/dist/chunks/studio-d038kqbs.js +5 -0
- package/dist/chunks/studio-d83m0wyq.js +5 -0
- package/dist/chunks/studio-dbxv7w4s.js +5 -0
- package/dist/chunks/studio-de1s2x9v.js +5 -0
- package/dist/chunks/{studio-94ppxeqy.js → studio-dx73md4n.js} +8 -8
- package/dist/chunks/studio-ekgtrndg.js +5 -0
- package/dist/chunks/studio-epenqmvk.js +7 -0
- package/dist/chunks/studio-et2q4s8n.js +5 -0
- package/dist/chunks/{studio-txkx6586.js → studio-ewarnzm4.js} +7 -7
- package/dist/chunks/{studio-bf0rgyf7.js → studio-fy7jzj9j.js} +4 -4
- package/dist/chunks/{studio-332hesdk.js → studio-gr36vnte.js} +4 -4
- package/dist/chunks/{studio-rdnezzkd.js → studio-gwrhcrss.js} +5 -5
- package/dist/chunks/{studio-9wyqyfqy.js → studio-h310mbqm.js} +4 -4
- package/dist/chunks/{studio-zht0x0v3.js → studio-h5tpz5ke.js} +4 -4
- package/dist/chunks/studio-h7weqf2q.js +5 -0
- package/dist/chunks/{studio-1gdfsr49.js → studio-hcsba201.js} +7 -7
- package/dist/chunks/studio-hwejq03c.js +5 -0
- package/dist/chunks/{studio-wcakp5bt.js → studio-hy4ye0dx.js} +7 -7
- package/dist/chunks/{studio-02phtmqn.js → studio-jttyqrmh.js} +4 -4
- package/dist/chunks/{studio-j5jqvyn3.js → studio-jw0q5j6k.js} +4 -4
- package/dist/chunks/studio-k3a8rp3d.js +5 -0
- package/dist/chunks/{studio-hnx1h1pr.js → studio-k3bcy02e.js} +4 -4
- package/dist/chunks/studio-m48v13s3.js +4 -0
- package/dist/chunks/{studio-bqgs90r8.js → studio-ndskgqef.js} +4 -4
- package/dist/chunks/studio-p1n2xjda.js +5 -0
- package/dist/chunks/studio-pgm83a2f.js +6 -0
- package/dist/chunks/studio-pqqq6v85.js +5 -0
- package/dist/chunks/studio-psh8mwnw.js +4 -0
- package/dist/chunks/{studio-cpdzrdgp.js → studio-r17asgs0.js} +4 -4
- package/dist/chunks/{studio-6vrxzkba.js → studio-ry81j4q1.js} +4 -4
- package/dist/chunks/studio-rzg8hnkf.js +5 -0
- package/dist/chunks/{studio-nsv7g1qn.js → studio-s0fg2wav.js} +4 -4
- package/dist/chunks/{studio-v0gsjav0.js → studio-tj2s0tks.js} +4 -4
- package/dist/chunks/studio-tyw7tk0x.js +5 -0
- package/dist/chunks/studio-v9ywz2se.js +6 -0
- package/dist/chunks/{studio-dmn7gpba.js → studio-vswcpzk7.js} +5 -5
- package/dist/chunks/studio-vzgmyt0v.js +5 -0
- package/dist/chunks/{studio-2dtc90zs.js → studio-wxdcd42g.js} +6 -6
- package/dist/chunks/{studio-c5dj3dzk.js → studio-wxhy1yh7.js} +4 -4
- package/dist/chunks/studio-x08a3m0a.js +5 -0
- package/dist/chunks/studio-x3mjr5cc.js +5 -0
- package/dist/chunks/{studio-xddgbwhe.js → studio-x97ay0v4.js} +4 -4
- package/dist/chunks/{studio-mmqr9awv.js → studio-xdpz9rpe.js} +33 -33
- package/dist/chunks/studio-xh683qzy.js +5 -0
- package/dist/chunks/{studio-hxq1ebp0.js → studio-xkxetm65.js} +6 -6
- package/dist/chunks/{studio-73wz63nn.js → studio-y26hx9re.js} +4 -4
- package/dist/chunks/{studio-kdseew9m.js → studio-yg3djpnm.js} +4 -4
- package/dist/chunks/studio-yx8hzqja.js +5 -0
- package/dist/chunks/studio-zg16eeq1.js +5 -0
- package/dist/chunks/studio-zgx7svjd.js +9 -0
- package/dist/chunks/studio-zkq3r91p.js +5 -0
- package/dist/chunks/{toast-host-1yg5ar8d.js → toast-host-g3r2s0kn.js} +2 -2
- package/dist/chunks/toast-w11qpkyz.js +4 -0
- package/dist/chunks/{tsMode-vra2hh7y.js → tsMode-h6tkz0ea.js} +5 -5
- package/dist/chunks/workspace-home-zkkrxq3r.js +4 -0
- package/dist/chunks/workspace-tdqqjqaa.js +4 -0
- package/dist/chunks/{ws-client-72x5n99j.js → ws-client-e951my95.js} +3 -3
- package/dist/iframe-entry.js +13 -13
- package/dist/manifest.json +77 -77
- package/dist/studio.js +91 -92
- package/package.json +11 -12
- package/src/canvas/iframe-editable-root.ts +32 -2
- package/src/collab/collab-session.ts +6 -2
- package/src/commands/context.ts +4 -0
- package/src/commands/defaults.ts +2 -0
- package/src/commands/live-context.ts +2 -0
- package/src/files/files.ts +6 -3
- package/src/format/format-host.ts +1 -1
- package/src/github/github-auth-status.ts +2 -2
- package/src/media/media-library.ts +73 -3
- package/src/new-project/new-project-modal.ts +1 -4
- package/src/packages/ensure-deps.ts +5 -5
- package/src/packages/pull-package-sync.ts +4 -4
- package/src/panels/activity-panel.ts +1 -1
- package/src/panels/ai-panel.ts +27 -34
- package/src/platform.ts +20 -0
- package/src/platforms/cloud.ts +48 -34
- package/src/platforms/devserver.ts +2 -120
- package/src/services/ai-ask.ts +29 -171
- package/src/services/ai-command-tools.ts +40 -50
- package/src/services/ai-import-tools.ts +4 -5
- package/src/services/ai-project-tools.ts +13 -3
- package/src/services/ai-tools.ts +26 -564
- package/src/services/ai-writes.ts +84 -2
- package/src/services/document-assistant.ts +29 -58
- package/src/services/harness/assistant-tools.ts +68 -0
- package/src/services/harness/interaction-store.ts +118 -0
- package/src/services/harness/studio-documents.ts +105 -0
- package/src/services/harness/turn-hooks.ts +1 -6
- package/src/services/project-adoption.ts +8 -16
- package/src/services/tool-executor.ts +25 -22
- package/src/settings/extension-commands.ts +1 -1
- package/src/settings/extension-rows.ts +1 -6
- package/src/settings/extensions-section.ts +3 -6
- package/src/state.ts +3 -13
- package/src/surfaces/block-action-bar.json +1 -3
- package/src/surfaces/files-panel.json +12 -0
- package/src/surfaces/files-panel.ts +5 -2
- package/src/surfaces/media-library.json +35 -0
- package/src/surfaces/media-library.ts +2 -0
- package/src/surfaces/menu.ts +62 -1
- package/src/surfaces/settings-packages.ts +1 -1
- package/src/surfaces/welcome.ts +4 -1
- package/src/tabs/selection.ts +14 -109
- package/src/tabs/tab.ts +10 -1
- package/src/tabs/transact.ts +316 -124
- package/src/types.ts +23 -16
- package/src/ui/layers.ts +1 -1
- package/src/ui/progress-modal.ts +3 -3
- package/dist/chunks/adapter-tck81zft.js +0 -4
- package/dist/chunks/add-new-flow-a1w0bnm6.js +0 -4
- package/dist/chunks/cf-account-picker-ce3kp3x1.js +0 -4
- package/dist/chunks/cf-settings-7ye6x7g2.js +0 -4
- package/dist/chunks/color-4gj49xq6.js +0 -4
- package/dist/chunks/components-qce0v93f.js +0 -4
- package/dist/chunks/content-source-jjx8kgzr.js +0 -4
- package/dist/chunks/editor-bkja5dbv.js +0 -4
- package/dist/chunks/entry-commands-1wh0btvg.js +0 -4
- package/dist/chunks/file-ops-6pe4z7an.js +0 -4
- package/dist/chunks/files-ybpp3j38.js +0 -4
- package/dist/chunks/format-host-3mtbhxk1.js +0 -4
- package/dist/chunks/fs-events-xfanfxds.js +0 -4
- package/dist/chunks/grid-open-dg5qmnaj.js +0 -4
- package/dist/chunks/iframe-host-j2bk2xkc.js +0 -4
- package/dist/chunks/index-fkrm7pdk.js +0 -4
- package/dist/chunks/jx-validate-w7v32f6a.js +0 -4
- package/dist/chunks/layers-g7j732cm.js +0 -4
- package/dist/chunks/layers-panel-gjrsxcck.js +0 -4
- package/dist/chunks/lit-html-jdfz9rhc.js +0 -4
- package/dist/chunks/media-library-9j0v4wmk.js +0 -4
- package/dist/chunks/media-picker-cpyyt2m0.js +0 -4
- package/dist/chunks/monaco-buffer-9wcme176.js +0 -4
- package/dist/chunks/notify-rahnnnv4.js +0 -4
- package/dist/chunks/parse-v77xtwv7.js +0 -4
- package/dist/chunks/platform-b2aqv7v5.js +0 -4
- package/dist/chunks/publish-panel-4yk2461b.js +0 -4
- package/dist/chunks/publish-workspace-mxestzcf.js +0 -4
- package/dist/chunks/quick-search-9g6z65b0.js +0 -4
- package/dist/chunks/references-c26enpxs.js +0 -4
- package/dist/chunks/right-panel-9d0e0yg7.js +0 -4
- package/dist/chunks/source-control-xyn3a8ds.js +0 -4
- package/dist/chunks/studio-0khd0xdw.js +0 -5
- package/dist/chunks/studio-1fvzq9vs.js +0 -5
- package/dist/chunks/studio-1yhz0s8p.js +0 -5
- package/dist/chunks/studio-2c2mjyzj.js +0 -6
- package/dist/chunks/studio-3pndhat0.js +0 -9
- package/dist/chunks/studio-4fjqdzrb.js +0 -6
- package/dist/chunks/studio-5abge3wy.js +0 -5
- package/dist/chunks/studio-74nzv280.js +0 -5
- package/dist/chunks/studio-7cg75gtj.js +0 -14
- package/dist/chunks/studio-89cwmnae.js +0 -5
- package/dist/chunks/studio-8m1t83kn.js +0 -7
- package/dist/chunks/studio-a3evp7f6.js +0 -6
- package/dist/chunks/studio-ana67wax.js +0 -5
- package/dist/chunks/studio-b4xhpcwy.js +0 -4
- package/dist/chunks/studio-c4rcaftm.js +0 -5
- package/dist/chunks/studio-cgjt1zce.js +0 -5
- package/dist/chunks/studio-cq87hree.js +0 -5
- package/dist/chunks/studio-d140sy8e.js +0 -5
- package/dist/chunks/studio-e1g64q43.js +0 -5
- package/dist/chunks/studio-e8pzjjhf.js +0 -5
- package/dist/chunks/studio-f8kz3krj.js +0 -7
- package/dist/chunks/studio-fw3jp9m7.js +0 -5
- package/dist/chunks/studio-g6ysm05p.js +0 -7
- package/dist/chunks/studio-gmhd0j3f.js +0 -5
- package/dist/chunks/studio-gqpty626.js +0 -5
- package/dist/chunks/studio-grp684wx.js +0 -5
- package/dist/chunks/studio-h98520qr.js +0 -5
- package/dist/chunks/studio-hvxt8z79.js +0 -5
- package/dist/chunks/studio-j4e627w1.js +0 -5
- package/dist/chunks/studio-kt2j51sa.js +0 -5
- package/dist/chunks/studio-m4w6sygj.js +0 -16
- package/dist/chunks/studio-nqxqf8p2.js +0 -4
- package/dist/chunks/studio-p89w12s2.js +0 -7
- package/dist/chunks/studio-pbagym4j.js +0 -5
- package/dist/chunks/studio-pr23qhrw.js +0 -5
- package/dist/chunks/studio-r4ya3sb2.js +0 -5
- package/dist/chunks/studio-rm9m3z60.js +0 -5
- package/dist/chunks/studio-tpmnbgmt.js +0 -5
- package/dist/chunks/studio-ttxhq5yt.js +0 -5
- package/dist/chunks/studio-y1x65h5r.js +0 -5
- package/dist/chunks/studio-zf188hnd.js +0 -5
- package/dist/chunks/toast-1gv0h53h.js +0 -4
- package/dist/chunks/workspace-3nt93k48.js +0 -4
- package/dist/chunks/workspace-home-zbgyqqws.js +0 -4
- package/src/services/directory-picker.ts +0 -187
- package/src/services/gated-registry.ts +0 -77
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/studio",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "10.0.0",
|
|
4
4
|
"description": "Jx Studio — visual builder for Jx documents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -31,7 +31,6 @@
|
|
|
31
31
|
"./platform": "./src/platform.ts",
|
|
32
32
|
"./types": "./src/types.ts",
|
|
33
33
|
"./base64": "./src/utils/base64.ts",
|
|
34
|
-
"./directory-picker": "./src/services/directory-picker.ts",
|
|
35
34
|
"./import-client": "./src/services/import-client.ts",
|
|
36
35
|
"./github-auth": "./src/github/github-auth.ts",
|
|
37
36
|
"./github-auth-status": "./src/github/github-auth-status.ts",
|
|
@@ -61,16 +60,16 @@
|
|
|
61
60
|
"dependencies": {
|
|
62
61
|
"@atlaskit/pragmatic-drag-and-drop": "^4.0.0",
|
|
63
62
|
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^3.0.0",
|
|
64
|
-
"@jxsuite/ai": "^0.
|
|
65
|
-
"@jxsuite/collab": "^0.11.
|
|
66
|
-
"@jxsuite/create": "^1.
|
|
67
|
-
"@jxsuite/formulas": "^0.0.
|
|
68
|
-
"@jxsuite/markup": "^1.0.
|
|
69
|
-
"@jxsuite/protocol": "^
|
|
70
|
-
"@jxsuite/runtime": "^
|
|
71
|
-
"@jxsuite/schema": "^
|
|
72
|
-
"@jxsuite/site": "^2.2.
|
|
73
|
-
"@jxsuite/ui": "^0.8.
|
|
63
|
+
"@jxsuite/ai": "^0.41.1",
|
|
64
|
+
"@jxsuite/collab": "^0.11.4",
|
|
65
|
+
"@jxsuite/create": "^1.5.0",
|
|
66
|
+
"@jxsuite/formulas": "^0.0.26",
|
|
67
|
+
"@jxsuite/markup": "^1.0.2",
|
|
68
|
+
"@jxsuite/protocol": "^6.0.0",
|
|
69
|
+
"@jxsuite/runtime": "^6.0.1",
|
|
70
|
+
"@jxsuite/schema": "^5.0.0",
|
|
71
|
+
"@jxsuite/site": "^2.2.2",
|
|
72
|
+
"@jxsuite/ui": "^0.8.2",
|
|
74
73
|
"@vue/reactivity": "3.5.44",
|
|
75
74
|
"ajv": "^8.20.0",
|
|
76
75
|
"ajv-formats": "^3.0.1",
|
|
@@ -445,8 +445,9 @@ export function startEditableRoot(
|
|
|
445
445
|
* something no caret can enter, so the caret went on blinking in the block clicked BEFORE, with
|
|
446
446
|
* the component now selected in the editor and every keystroke still typed into that block.
|
|
447
447
|
*/
|
|
448
|
-
const onPointerDownCapture = (e:
|
|
449
|
-
|
|
448
|
+
const onPointerDownCapture = (e: PointerEvent) => {
|
|
449
|
+
// A secondary press asks for the element menu, not an edit: see `onMouseDownCapture`.
|
|
450
|
+
if (!editingAllowed() || e.button === 2) {
|
|
450
451
|
return;
|
|
451
452
|
}
|
|
452
453
|
const target = e.target instanceof Element ? e.target : null;
|
|
@@ -478,6 +479,33 @@ export function startEditableRoot(
|
|
|
478
479
|
}
|
|
479
480
|
};
|
|
480
481
|
|
|
482
|
+
/**
|
|
483
|
+
* A secondary press outside the active block selects, it never starts an edit.
|
|
484
|
+
*
|
|
485
|
+
* The whole canvas is one `contenteditable`, and a right-button press places the caret where it
|
|
486
|
+
* lands, as a left one does. That caret activated the block through `selectionchange`, so every
|
|
487
|
+
* right-click on a block also entered inline editing: the bridge's edit start ran ahead of the
|
|
488
|
+
* menu's mount in the same task, which held the element menu back by hundreds of milliseconds on
|
|
489
|
+
* a real page, and the block was left in edit mode after the menu closed. Cancelling the press's
|
|
490
|
+
* default keeps the caret where it was; `contextmenu` still fires, so the menu is unaffected. The
|
|
491
|
+
* block being edited keeps its native press, because the caret placed there is what the native
|
|
492
|
+
* spelling and edit menu reads. A right-click elsewhere leaves that block, as a left click
|
|
493
|
+
* would.
|
|
494
|
+
*/
|
|
495
|
+
const onMouseDownCapture = (e: MouseEvent) => {
|
|
496
|
+
if (e.button !== 2 || !editingAllowed()) {
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
if (activeEl && e.target instanceof Node && activeEl.contains(e.target)) {
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
e.preventDefault();
|
|
503
|
+
if (activeEl) {
|
|
504
|
+
deactivate();
|
|
505
|
+
doc.getSelection()?.removeAllRanges();
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
|
|
481
509
|
/**
|
|
482
510
|
* Reordering is the block action bar's drag handle, full stop. A `contenteditable` region is
|
|
483
511
|
* natively draggable by its text, which would both fight text selection and move content behind
|
|
@@ -501,6 +529,7 @@ export function startEditableRoot(
|
|
|
501
529
|
container.addEventListener("compositionend", onCompositionEnd);
|
|
502
530
|
container.addEventListener("dragstart", onDragStart);
|
|
503
531
|
doc.addEventListener("pointerdown", onPointerDownCapture, true);
|
|
532
|
+
doc.addEventListener("mousedown", onMouseDownCapture, true);
|
|
504
533
|
|
|
505
534
|
return {
|
|
506
535
|
capture: () => captureDocSelection(container, deps.isEditableBlock),
|
|
@@ -542,6 +571,7 @@ export function startEditableRoot(
|
|
|
542
571
|
container.removeEventListener("compositionend", onCompositionEnd);
|
|
543
572
|
container.removeEventListener("dragstart", onDragStart);
|
|
544
573
|
doc.removeEventListener("pointerdown", onPointerDownCapture, true);
|
|
574
|
+
doc.removeEventListener("mousedown", onMouseDownCapture, true);
|
|
545
575
|
// Deactivating commits, so the pending tick is cancelled rather than fired.
|
|
546
576
|
deactivate();
|
|
547
577
|
},
|
|
@@ -26,7 +26,7 @@ import { jsonClone } from "../utils/studio-utils";
|
|
|
26
26
|
import { cloneSelection } from "../tabs/selection";
|
|
27
27
|
import {
|
|
28
28
|
applyExternalDocOps,
|
|
29
|
-
|
|
29
|
+
isBatched,
|
|
30
30
|
setBatchEndNotifier,
|
|
31
31
|
setHistoryDelegate,
|
|
32
32
|
setTransactGate,
|
|
@@ -318,7 +318,11 @@ function onBatchEnd(tab: Tab): void {
|
|
|
318
318
|
|
|
319
319
|
function publishRecord(session: ActiveSession, record: TransactionRecord): void {
|
|
320
320
|
const forward = record.docOps.map((pair) => pair.forward);
|
|
321
|
-
|
|
321
|
+
/* Buffered only while THIS tab has a lane segment open, which means the transaction just
|
|
322
|
+
observed was the lane owner's: anyone else's write closes the segment before it runs, and the
|
|
323
|
+
close flushes the buffer, so the Y document receives the owner's ops before the other writer's
|
|
324
|
+
and in local order. */
|
|
325
|
+
if (isBatched(session.tab)) {
|
|
322
326
|
session.batchOps ??= [];
|
|
323
327
|
if (forward.length === 0) {
|
|
324
328
|
session.batchNeedsDiff = true;
|
package/src/commands/context.ts
CHANGED
|
@@ -31,6 +31,9 @@ export const CAPABILITIES = [
|
|
|
31
31
|
"gitClone",
|
|
32
32
|
"importSite",
|
|
33
33
|
"openProjectInNewWindow",
|
|
34
|
+
/* Open Project's gate: a native dialog or the cloud's repository picker. A dev-server window opens
|
|
35
|
+
its project from `?project=`, so the command is not offered there. */
|
|
36
|
+
"openProject",
|
|
34
37
|
"dataRows",
|
|
35
38
|
"windowControls",
|
|
36
39
|
"findReferences",
|
|
@@ -339,6 +342,7 @@ export function emptyContext(): CommandContext {
|
|
|
339
342
|
gitClone: false,
|
|
340
343
|
importSite: false,
|
|
341
344
|
openProjectInNewWindow: false,
|
|
345
|
+
openProject: false,
|
|
342
346
|
dataRows: false,
|
|
343
347
|
windowControls: false,
|
|
344
348
|
findReferences: false,
|
package/src/commands/defaults.ts
CHANGED
|
@@ -563,6 +563,8 @@ export function defaultCommands(deps: CommandDeps): AnyCommand[] {
|
|
|
563
563
|
keybinding: "mod+o",
|
|
564
564
|
menus: ["commandbar/overflow", "statusbar/project", "palette"],
|
|
565
565
|
group: "1_file",
|
|
566
|
+
when: (ctx: CommandContext) => ctx.capability.openProject,
|
|
567
|
+
requires: "a host that can open a project from a dialog",
|
|
566
568
|
run: () => deps.openProject(),
|
|
567
569
|
},
|
|
568
570
|
{
|
|
@@ -38,6 +38,7 @@ import { collabState } from "../collab/collab-state";
|
|
|
38
38
|
|
|
39
39
|
import { canvasViewForMode, editorKindForMode, emptyContext } from "./context";
|
|
40
40
|
import type { CommandContext } from "./context";
|
|
41
|
+
import { canOpenProject } from "../platform";
|
|
41
42
|
import type { StudioPlatform } from "../types";
|
|
42
43
|
|
|
43
44
|
/** Elements that own the keyboard while focused. Matches the old listener's target test exactly. */
|
|
@@ -95,6 +96,7 @@ function capabilities(platform: StudioPlatform | null): CommandContext["capabili
|
|
|
95
96
|
findReferences: has("findReferences"),
|
|
96
97
|
gitClone: has("gitClone"),
|
|
97
98
|
importSite: has("importSite"),
|
|
99
|
+
openProject: canOpenProject(platform),
|
|
98
100
|
openProjectInNewWindow: has("openProjectInNewWindow"),
|
|
99
101
|
readFileBytes: has("readFileBytes"),
|
|
100
102
|
publish: has("cfApi"),
|
package/src/files/files.ts
CHANGED
|
@@ -39,7 +39,7 @@ import { errorMessage } from "@jxsuite/schema/parse";
|
|
|
39
39
|
import { localeLabel, localeOfPath, resolveI18n } from "@jxsuite/schema/locale";
|
|
40
40
|
import { showPromptDialog } from "../ui/layers";
|
|
41
41
|
import { projectState, requireProjectState, setProjectState } from "../store";
|
|
42
|
-
import { getPlatform } from "../platform";
|
|
42
|
+
import { canOpenProject, getPlatform } from "../platform";
|
|
43
43
|
import { disarmPreviewOverlay } from "../preview/preview-overlay";
|
|
44
44
|
import { notify } from "../services/notify";
|
|
45
45
|
import { loadComponentRegistry } from "./components";
|
|
@@ -309,7 +309,8 @@ export async function openProject({
|
|
|
309
309
|
}): Promise<boolean> {
|
|
310
310
|
try {
|
|
311
311
|
const platform = getPlatform();
|
|
312
|
-
|
|
312
|
+
// The command is gated on the capability, so a host without the member never gets here.
|
|
313
|
+
const result = await platform.openProject?.();
|
|
313
314
|
if (!result) {
|
|
314
315
|
return false;
|
|
315
316
|
} // User cancelled
|
|
@@ -864,7 +865,9 @@ function filesPanelValues(): FilesPanelValues {
|
|
|
864
865
|
}
|
|
865
866
|
if (!projectState.isSiteProject && projectState.projectRoot === ".") {
|
|
866
867
|
_fileRows = [];
|
|
867
|
-
|
|
868
|
+
// A host with no Open Project (the dev server) is told how its windows DO open one instead.
|
|
869
|
+
const view = canOpenProject(getPlatform()) ? "welcome" : "address";
|
|
870
|
+
const values: FilesPanelValues = { ...emptyFilesPanelValues(), view };
|
|
868
871
|
_lastValues = values;
|
|
869
872
|
return values;
|
|
870
873
|
}
|
|
@@ -217,7 +217,7 @@ async function shareProjectSchemas(
|
|
|
217
217
|
*/
|
|
218
218
|
export function refreshExtensionUi(platform: Pick<StudioPlatform, "fetchProjectSchemas">): void {
|
|
219
219
|
void shareProjectSchemas(platform);
|
|
220
|
-
// `installed`
|
|
220
|
+
// `installed` is a per-project fact, so an install or a project switch can move
|
|
221
221
|
// Them even when the shipped half has not changed.
|
|
222
222
|
void loadExtensionCatalog();
|
|
223
223
|
void import("../settings/extension-sections")
|
|
@@ -21,8 +21,8 @@ const STORAGE_KEY = "jx_github_token";
|
|
|
21
21
|
/**
|
|
22
22
|
* The GitHub OAuth app every Jx sign-in authorizes against: the browser's device flow in
|
|
23
23
|
* `github-auth.ts`, and the desktop launcher's RFC 8252 loopback redirect
|
|
24
|
-
* (`packages/desktop/src/github-signin.ts`). Here, in the module with no UI dependency,
|
|
25
|
-
* launcher's main process imports it too.
|
|
24
|
+
* (`packages/desktop/src/backend/github-signin.ts`). Here, in the module with no UI dependency,
|
|
25
|
+
* because the launcher's main process imports it too.
|
|
26
26
|
*/
|
|
27
27
|
export const GITHUB_OAUTH_CLIENT_ID = "Ov23liYVlMFpgjOEPXJH";
|
|
28
28
|
|
|
@@ -67,6 +67,7 @@ import { dirName, previewFileSrc } from "../files/media-paths";
|
|
|
67
67
|
import { forgetVersions } from "../files/asset-versions";
|
|
68
68
|
import { loadMediaUsages, peekMediaUsages } from "../files/media-usage";
|
|
69
69
|
import { onProjectMediaChange, projectMediaFiles } from "../ui/media-picker";
|
|
70
|
+
import { openMenu } from "../surfaces/menu";
|
|
70
71
|
import { openMediaTab } from "./media-open";
|
|
71
72
|
import { factsLine, mediaStageFor, specimenOf, usageOf } from "./media-pane";
|
|
72
73
|
import {
|
|
@@ -93,6 +94,7 @@ import type {
|
|
|
93
94
|
MediaTileRow,
|
|
94
95
|
} from "../surfaces/media-library";
|
|
95
96
|
import type { MediaPickDialogHandle } from "../surfaces/media-pick-dialog";
|
|
97
|
+
import type { MenuHandle, MenuRowProjection } from "../surfaces/menu";
|
|
96
98
|
import type { PreviewObserver } from "../browse/library-preview";
|
|
97
99
|
import type { Tab } from "../tabs/tab";
|
|
98
100
|
import type { CanvasSurface } from "../canvas/canvas-surface";
|
|
@@ -788,11 +790,11 @@ function actionsFor(mount: LibraryMount) {
|
|
|
788
790
|
activate(mount, selected);
|
|
789
791
|
}
|
|
790
792
|
},
|
|
793
|
+
contextMenu: (file: string, x: number, y: number) => showTileMenu(mount, file, x, y),
|
|
791
794
|
copyRef: () => {
|
|
792
795
|
const entry = selectedEntry(mount);
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
void navigator.clipboard.writeText(entry.ref);
|
|
796
|
+
if (entry) {
|
|
797
|
+
copySiteUrl(entry);
|
|
796
798
|
}
|
|
797
799
|
},
|
|
798
800
|
dragOut: () => {
|
|
@@ -913,6 +915,74 @@ function actionsFor(mount: LibraryMount) {
|
|
|
913
915
|
}
|
|
914
916
|
|
|
915
917
|
/** Enter or a double-click: open the viewer, or in pick mode, use the file. */
|
|
918
|
+
/** Put a file's site URL on the clipboard. */
|
|
919
|
+
function copySiteUrl(entry: ProjectMediaFile): void {
|
|
920
|
+
// `navigator.clipboard` is [SecureContext]: a plain-http host has none, whatever lib.dom says.
|
|
921
|
+
if ("clipboard" in navigator) {
|
|
922
|
+
void navigator.clipboard.writeText(entry.ref);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
/**
|
|
927
|
+
* The tile menu that is up, if any. One handle, because only one can be open: a second kit menu
|
|
928
|
+
* opened over the first is how two of them light-dismiss each other.
|
|
929
|
+
*/
|
|
930
|
+
let _tileMenu: MenuHandle | null = null;
|
|
931
|
+
|
|
932
|
+
/**
|
|
933
|
+
* A right-click on a tile: select it, then offer the detail pane's actions at the pointer.
|
|
934
|
+
*
|
|
935
|
+
* The same four verbs as the detail pane's (studio.md §22.6), in the order the pane draws them, and
|
|
936
|
+
* each calls what the pane's control calls, so the menu cannot drift from the pane. Selecting first
|
|
937
|
+
* is what makes the pane describe the file the menu acts on, as a right-click on a row does in the
|
|
938
|
+
* Content workspace.
|
|
939
|
+
*/
|
|
940
|
+
function showTileMenu(mount: LibraryMount, file: string, x: number, y: number): void {
|
|
941
|
+
select(mount, file);
|
|
942
|
+
const entry = selectedEntry(mount);
|
|
943
|
+
if (!entry) {
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
_tileMenu?.close();
|
|
947
|
+
const row = (
|
|
948
|
+
id: string,
|
|
949
|
+
title: string,
|
|
950
|
+
run: () => void,
|
|
951
|
+
extra: Partial<MenuRowProjection> = {},
|
|
952
|
+
): MenuRowProjection => ({
|
|
953
|
+
destructive: false,
|
|
954
|
+
disabled: false,
|
|
955
|
+
dividerAbove: false,
|
|
956
|
+
id,
|
|
957
|
+
run,
|
|
958
|
+
title,
|
|
959
|
+
...extra,
|
|
960
|
+
});
|
|
961
|
+
_tileMenu = openMenu({
|
|
962
|
+
label: `${entry.name} actions`,
|
|
963
|
+
onClosed: (handle) => {
|
|
964
|
+
if (_tileMenu === handle) {
|
|
965
|
+
_tileMenu = null;
|
|
966
|
+
}
|
|
967
|
+
},
|
|
968
|
+
origin: { x, y },
|
|
969
|
+
region: "media-tile",
|
|
970
|
+
rows: [
|
|
971
|
+
row("open", "Open in Viewer", () => openMediaTab(file)),
|
|
972
|
+
row("copy-url", "Copy Site URL", () => copySiteUrl(entry)),
|
|
973
|
+
row("replace", "Replace…", () => requestMediaReplace(file), { dividerAbove: true }),
|
|
974
|
+
row(
|
|
975
|
+
"delete",
|
|
976
|
+
"Delete",
|
|
977
|
+
() => {
|
|
978
|
+
void deleteMediaFile(file);
|
|
979
|
+
},
|
|
980
|
+
{ destructive: true, dividerAbove: true },
|
|
981
|
+
),
|
|
982
|
+
],
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
|
|
916
986
|
function activate(mount: LibraryMount, file: string): void {
|
|
917
987
|
if (mount.mode === "pick") {
|
|
918
988
|
const entry = selectedEntry(mount);
|
|
@@ -346,10 +346,7 @@ function finish(result: { root: string; config: ProjectConfig } | null) {
|
|
|
346
346
|
* runs this same sequence itself.
|
|
347
347
|
*/
|
|
348
348
|
async function finishCreated(result: { root: string; config: ProjectConfig }) {
|
|
349
|
-
const { adopted, error, openedElsewhere } = await adoptCreatedProject(result.root
|
|
350
|
-
// The wizard runs outside the agent loop — there is never a batch to flush and re-anchor.
|
|
351
|
-
getTab: () => null,
|
|
352
|
-
});
|
|
349
|
+
const { adopted, error, openedElsewhere } = await adoptCreatedProject(result.root);
|
|
353
350
|
if (!adopted && !openedElsewhere) {
|
|
354
351
|
notify.warn(
|
|
355
352
|
"Created the project, but could not open it here.",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
2
|
/**
|
|
3
3
|
* Ensure the active project's dependencies are installed before the editor loads. When the backend
|
|
4
|
-
* reports node_modules is missing,
|
|
5
|
-
*
|
|
4
|
+
* reports node_modules is missing, install them behind a blocking progress modal so component and
|
|
5
|
+
* format resolution see the real dependency tree. No-op on platforms without package support.
|
|
6
6
|
*
|
|
7
7
|
* It REPORTS whether an install actually ran, because "format resolution sees the real dependency
|
|
8
8
|
* tree" was only half true: project activation fetches the format registry before calling this, and
|
|
@@ -17,7 +17,7 @@ import { shouldInstallAutomation } from "../services/automation";
|
|
|
17
17
|
|
|
18
18
|
export async function ensureDependenciesInstalled(): Promise<boolean> {
|
|
19
19
|
// Automation/screenshot runs open projects read-only to drive the canvas — they must never mutate
|
|
20
|
-
// The project on disk (
|
|
20
|
+
// The project on disk (an install would litter node_modules and a lockfile into starter templates).
|
|
21
21
|
if (shouldInstallAutomation(location.search)) {
|
|
22
22
|
return false;
|
|
23
23
|
}
|
|
@@ -35,7 +35,7 @@ export async function ensureDependenciesInstalled(): Promise<boolean> {
|
|
|
35
35
|
return false;
|
|
36
36
|
}
|
|
37
37
|
const progress = showProgressModal({
|
|
38
|
-
status: "
|
|
38
|
+
status: "Installing dependencies…",
|
|
39
39
|
title: "Installing dependencies",
|
|
40
40
|
});
|
|
41
41
|
try {
|
|
@@ -44,7 +44,7 @@ export async function ensureDependenciesInstalled(): Promise<boolean> {
|
|
|
44
44
|
progress.done();
|
|
45
45
|
return true;
|
|
46
46
|
}
|
|
47
|
-
progress.fail(result.log ?? "
|
|
47
|
+
progress.fail(result.log ?? "Install failed");
|
|
48
48
|
} catch (error) {
|
|
49
49
|
progress.fail(error instanceof Error ? error.message : String(error));
|
|
50
50
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* updates. The on-open @jxsuite update rewrites package.json and bun.lock without committing, so a
|
|
5
5
|
* teammate pushing the same update makes a later `git pull` refuse to merge. Since those local
|
|
6
6
|
* changes are machine-generated and reproducible, we discard them, pull, and re-run the package
|
|
7
|
-
* sync (re-apply the update if the pulled project is still behind, or
|
|
7
|
+
* sync (re-apply the update if the pulled project is still behind, or an install if the pull
|
|
8
8
|
* brought it). Detection is state-based (git status + content comparison against `@{u}`), never git
|
|
9
9
|
* stderr parsing, so it works identically across backends and locales.
|
|
10
10
|
*/
|
|
@@ -188,7 +188,7 @@ async function applyDiscard(plan: PackagePullPlan): Promise<void> {
|
|
|
188
188
|
|
|
189
189
|
/**
|
|
190
190
|
* Bring packages back in line after a pull that touched (or followed a discard of) package files:
|
|
191
|
-
* re-apply the @jxsuite update if the pulled project is still behind, otherwise
|
|
191
|
+
* re-apply the @jxsuite update if the pulled project is still behind, otherwise an install so
|
|
192
192
|
* node_modules matches the pulled lockfile. Never throws — a successful pull must not turn into an
|
|
193
193
|
* error banner; failures surface through the progress modal.
|
|
194
194
|
*/
|
|
@@ -209,7 +209,7 @@ async function syncPackagesAfterPull(): Promise<void> {
|
|
|
209
209
|
return;
|
|
210
210
|
}
|
|
211
211
|
const progress = showProgressModal({
|
|
212
|
-
status: "
|
|
212
|
+
status: "Installing dependencies…",
|
|
213
213
|
title: "Syncing dependencies",
|
|
214
214
|
});
|
|
215
215
|
try {
|
|
@@ -217,7 +217,7 @@ async function syncPackagesAfterPull(): Promise<void> {
|
|
|
217
217
|
if (result.ok) {
|
|
218
218
|
progress.done();
|
|
219
219
|
} else {
|
|
220
|
-
progress.fail(result.log ?? "
|
|
220
|
+
progress.fail(result.log ?? "Install failed");
|
|
221
221
|
}
|
|
222
222
|
} catch (error) {
|
|
223
223
|
progress.fail(errorMessage(error));
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* What this replaces: `ui/progress-modal.ts` was **the only surface in Studio with a real error
|
|
6
6
|
* view** — a red headline, the captured log in a `<pre>`, an explicit Close — and it had four call
|
|
7
7
|
* sites, all package operations. It blocked the whole app, offered no cancel, and vanished
|
|
8
|
-
* completely on success, so
|
|
8
|
+
* completely on success, so an install that hung had no exit and one that failed had a log you
|
|
9
9
|
* could read exactly once. Every OTHER long operation — git clone, publish, media upload, site
|
|
10
10
|
* import, project open — got a three-second grey line or nothing at all.
|
|
11
11
|
*
|
package/src/panels/ai-panel.ts
CHANGED
|
@@ -28,13 +28,13 @@
|
|
|
28
28
|
|
|
29
29
|
import { effect, effectScope } from "../reactivity";
|
|
30
30
|
import { createDocumentAssistant } from "../services/document-assistant";
|
|
31
|
-
import {
|
|
31
|
+
import { restoreTurn } from "../services/ai-writes";
|
|
32
|
+
import { isString } from "@jxsuite/schema/guards";
|
|
32
33
|
import { notify } from "../services/notify";
|
|
33
|
-
import {
|
|
34
|
+
import type { NotifyOptions } from "../services/notify";
|
|
34
35
|
import { activeTab } from "../workspace/workspace";
|
|
35
36
|
import { setOpenAiKey } from "../services/ai-settings";
|
|
36
37
|
import { hasAiCredentials } from "../services/ai-models";
|
|
37
|
-
import { answerAsk, isAwaitingAnswer, pendingAsk, skipAsk } from "../services/ai-ask";
|
|
38
38
|
import { activeImportRun, importRun } from "../services/import-run";
|
|
39
39
|
import type { ImportBrief } from "../services/import-seed";
|
|
40
40
|
import type { ImportBreakpointPolicy } from "../types";
|
|
@@ -124,7 +124,7 @@ export function bindAiPanelHost(el: HTMLElement | null) {
|
|
|
124
124
|
run: runCommand,
|
|
125
125
|
send: () => composer.send(),
|
|
126
126
|
skip: () => {
|
|
127
|
-
|
|
127
|
+
assistant.interactions.skip();
|
|
128
128
|
renderAiPanel();
|
|
129
129
|
},
|
|
130
130
|
stop,
|
|
@@ -208,10 +208,10 @@ function watchAssistant() {
|
|
|
208
208
|
// The registry is composed AFTER the bootstrap mounts this, and it is a reactive holder —
|
|
209
209
|
// Reading it here is what turns the header's skeleton into its real buttons.
|
|
210
210
|
void activeRegistry();
|
|
211
|
-
// The question is not chat state — it lives in
|
|
212
|
-
//
|
|
213
|
-
// (and its ANSWER would never repaint the card at all).
|
|
214
|
-
void
|
|
211
|
+
// The question is not chat state — it lives in the assistant's interaction store — but it is
|
|
212
|
+
// Drawn into this panel, so the same effect has to track it or a question would appear a turn
|
|
213
|
+
// Late (and its ANSWER would never repaint the card at all).
|
|
214
|
+
void assistant.interactions.pending;
|
|
215
215
|
/* The run record, for the same reason: an import reports a line at a time from a store that
|
|
216
216
|
is not chat state, and its chip has to follow every one of them. Reading the ACTIVE run
|
|
217
217
|
tracks the whole record, so any field moving re-projects. */
|
|
@@ -268,7 +268,7 @@ async function handleAssistantSend(text: string) {
|
|
|
268
268
|
}
|
|
269
269
|
/* Answering comes first: a question is put by a turn that is still running, so a send while one
|
|
270
270
|
waits is its answer, never a second turn. */
|
|
271
|
-
if (
|
|
271
|
+
if (assistant.interactions.answer(text.trim())) {
|
|
272
272
|
surface?.pin();
|
|
273
273
|
renderAiPanel();
|
|
274
274
|
return;
|
|
@@ -319,38 +319,31 @@ async function handleRetry(): Promise<void> {
|
|
|
319
319
|
/**
|
|
320
320
|
* Undo everything one assistant turn changed — ai.md §3.2's "Restore to here".
|
|
321
321
|
*
|
|
322
|
-
* The
|
|
323
|
-
*
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
322
|
+
* The turn's batch lane left one step per document it wrote, and {@link restoreTurn} takes each
|
|
323
|
+
* back while it is still the top of its tab's history, or refuses the whole turn with a sentence.
|
|
324
|
+
* The button is offered by the projection only when every recorded change was transactional; the
|
|
325
|
+
* refusals are the second half of the same promise, because a ledger can be trimmed (MAX_TURNS) and
|
|
326
|
+
* a document edited between the projection and the click.
|
|
327
327
|
*
|
|
328
328
|
* @param {string} messageId
|
|
329
329
|
*/
|
|
330
330
|
/* Exported so the guard can be exercised directly: the button is not DRAWN for a turn that touched
|
|
331
331
|
disk, which would otherwise make the refusal path unreachable from the panel. */
|
|
332
332
|
export function handleRestore(messageId: string): void {
|
|
333
|
-
const
|
|
334
|
-
if (
|
|
335
|
-
notify.warn(
|
|
333
|
+
const restored = restoreTurn(messageId);
|
|
334
|
+
if (isString(restored)) {
|
|
335
|
+
notify.warn(restored, { source: "Assistant" });
|
|
336
336
|
return;
|
|
337
337
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
const tab = activeTab.value;
|
|
348
|
-
if (!tab) {
|
|
349
|
-
notify.warn("Open the document that turn edited to restore it.", { source: "Assistant" });
|
|
350
|
-
return;
|
|
338
|
+
/* Redo reaches the active tab only, so it is offered only when that is the one tab restored;
|
|
339
|
+
across documents it would bring back part of the turn. */
|
|
340
|
+
const [only, ...rest] = restored;
|
|
341
|
+
const redoable = rest.length === 0 && only === activeTab.value;
|
|
342
|
+
const options: NotifyOptions = { source: "Assistant" };
|
|
343
|
+
if (redoable) {
|
|
344
|
+
options.action = "edit.redo";
|
|
351
345
|
}
|
|
352
|
-
|
|
353
|
-
notify.success("Restored to before that turn.", { action: "edit.redo", source: "Assistant" });
|
|
346
|
+
notify.success("Restored to before that turn.", options);
|
|
354
347
|
renderAiPanel();
|
|
355
348
|
}
|
|
356
349
|
|
|
@@ -509,7 +502,7 @@ export function revealAssistant(): void {
|
|
|
509
502
|
* turn waiting forever on a question nobody wants to answer is exactly what Stop is for.
|
|
510
503
|
*/
|
|
511
504
|
export function isAssistantWaiting(): boolean {
|
|
512
|
-
return
|
|
505
|
+
return assistant.interactions.pending !== null;
|
|
513
506
|
}
|
|
514
507
|
|
|
515
508
|
/**
|
|
@@ -617,7 +610,7 @@ function projectPanel(): AiChatView {
|
|
|
617
610
|
const advice = error ? formatErrorAdvice(error) : "";
|
|
618
611
|
const newChatCommand = projectCommand("assistant.newChat", { icon: "plus" });
|
|
619
612
|
const rows = projectRows({
|
|
620
|
-
ask: { importRun, pendingId:
|
|
613
|
+
ask: { importRun, pendingId: assistant.interactions.pending?.callId ?? null },
|
|
621
614
|
messages: cs.messages,
|
|
622
615
|
status: cs.status,
|
|
623
616
|
});
|
package/src/platform.ts
CHANGED
|
@@ -50,6 +50,26 @@ declare global {
|
|
|
50
50
|
var __jxLauncher: LauncherSignal | undefined;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Whether this host offers Open Project at all: a native dialog (`openProject`), or the cloud's
|
|
55
|
+
* repository picker. The dev server has neither, because its windows open a project from the
|
|
56
|
+
* `?project=` URL parameter, so every surface that offers Open Project asks this first rather than
|
|
57
|
+
* drawing a control that cannot do anything.
|
|
58
|
+
*/
|
|
59
|
+
export function canOpenProject(platform: StudioPlatform | null | undefined): boolean {
|
|
60
|
+
if (!platform) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
if (isFunction(platform.openProject)) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
return (
|
|
67
|
+
platform.openProjectPicker === "repo-list" &&
|
|
68
|
+
isFunction(platform.listRepos) &&
|
|
69
|
+
isFunction(platform.importProject)
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
53
73
|
/** @param {StudioPlatform} platform */
|
|
54
74
|
export function registerPlatform(platform: StudioPlatform) {
|
|
55
75
|
globalThis.__jxPlatform = platform;
|