@jxsuite/studio 8.1.0 → 9.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.
Files changed (247) hide show
  1. package/dist/chunks/{2020-dg097k3e.js → 2020-89fz3pfy.js} +3 -3
  2. package/dist/chunks/adapter-ay13gema.js +4 -0
  3. package/dist/chunks/add-new-flow-nmkqpjym.js +4 -0
  4. package/dist/chunks/cf-account-picker-2e06qxey.js +4 -0
  5. package/dist/chunks/cf-settings-5y04810c.js +4 -0
  6. package/dist/chunks/color-04361ttk.js +4 -0
  7. package/dist/chunks/{color-area-vh9cew51.js → color-area-h8dbasp1.js} +2 -2
  8. package/dist/chunks/{color-field-e78f16bs.js → color-field-qv5r2b16.js} +3 -3
  9. package/dist/chunks/{color-slider-fg4yhvk6.js → color-slider-nz2mrzky.js} +2 -2
  10. package/dist/chunks/components-3vf75n83.js +4 -0
  11. package/dist/chunks/content-source-4f2raf4a.js +4 -0
  12. package/dist/chunks/{convert-to-repeater-grnmea6c.js → convert-to-repeater-j9hfcj4f.js} +3 -3
  13. package/dist/chunks/editor-645krvtz.js +4 -0
  14. package/dist/chunks/entry-commands-3qmv8wkr.js +4 -0
  15. package/dist/chunks/{extension-sections-rp7fkega.js → extension-sections-thfke054.js} +3 -3
  16. package/dist/chunks/file-ops-kkhp9ma7.js +4 -0
  17. package/dist/chunks/files-yswdhxrf.js +4 -0
  18. package/dist/chunks/format-host-4g8ex6bz.js +4 -0
  19. package/dist/chunks/fs-events-z9gpvc6t.js +4 -0
  20. package/dist/chunks/grid-open-fm4j183c.js +4 -0
  21. package/dist/chunks/{grid-view-x26rfpz1.js → grid-view-7d2nx83y.js} +10 -10
  22. package/dist/chunks/{iframe-entry-pkgmcgp0.js → iframe-entry-2x9bn5ng.js} +4 -4
  23. package/dist/chunks/{iframe-entry-02wzcyvy.js → iframe-entry-btck0e9s.js} +4 -4
  24. package/dist/chunks/iframe-host-q7fedsmr.js +4 -0
  25. package/dist/chunks/{index-203h71wy.js → index-jxmt7vh8.js} +3 -3
  26. package/dist/chunks/index-pt4wh98x.js +4 -0
  27. package/dist/chunks/{javascript-ww5pv5ae.js → javascript-vmqp56ke.js} +2 -2
  28. package/dist/chunks/{jsonMode-vwbdwwq3.js → jsonMode-n3wmpant.js} +6 -6
  29. package/dist/chunks/jx-validate-19y62knt.js +4 -0
  30. package/dist/chunks/layers-panel-1qvc83y9.js +4 -0
  31. package/dist/chunks/layers-qf0bc7ze.js +4 -0
  32. package/dist/chunks/lit-html-wyqw04y2.js +4 -0
  33. package/dist/chunks/media-library-f9d5qcjb.js +4 -0
  34. package/dist/chunks/media-picker-rsb1fmk2.js +4 -0
  35. package/dist/chunks/{monaco-binding-63tqze27.js → monaco-binding-wfhmz89d.js} +3 -3
  36. package/dist/chunks/monaco-buffer-ak0raadw.js +4 -0
  37. package/dist/chunks/{monaco-setup-zyyynnj8.js → monaco-setup-fty7a8vg.js} +3 -3
  38. package/dist/chunks/notify-4qj70q0x.js +4 -0
  39. package/dist/chunks/parse-n8dfxnqf.js +4 -0
  40. package/dist/chunks/platform-ehg6t9vd.js +4 -0
  41. package/dist/chunks/publish-panel-yhmda3cd.js +4 -0
  42. package/dist/chunks/{publish-quick-ajzdhhs6.js → publish-quick-12hh8926.js} +3 -3
  43. package/dist/chunks/publish-workspace-hx8qdv9p.js +4 -0
  44. package/dist/chunks/quick-search-r955m3q1.js +4 -0
  45. package/dist/chunks/references-8gxv0x7t.js +4 -0
  46. package/dist/chunks/right-panel-jarwqjpy.js +4 -0
  47. package/dist/chunks/source-control-84qt25m9.js +4 -0
  48. package/dist/chunks/studio-0nefxhar.js +7 -0
  49. package/dist/chunks/studio-0rg30pvs.js +5 -0
  50. package/dist/chunks/{studio-3q6n9qvq.js → studio-14hh60j8.js} +4 -4
  51. package/dist/chunks/studio-1cfyd1ys.js +5 -0
  52. package/dist/chunks/studio-2bq7hheq.js +5 -0
  53. package/dist/chunks/{studio-mmqr9awv.js → studio-32d8wyxq.js} +33 -33
  54. package/dist/chunks/studio-3hpcawh8.js +4 -0
  55. package/dist/chunks/{studio-zm7dnr9g.js → studio-4j35hgsw.js} +4 -4
  56. package/dist/chunks/{studio-bqgs90r8.js → studio-4qg61g9j.js} +4 -4
  57. package/dist/chunks/studio-4rgem12q.js +5 -0
  58. package/dist/chunks/studio-6k9djnxw.js +5 -0
  59. package/dist/chunks/studio-6rbaedw2.js +9 -0
  60. package/dist/chunks/studio-732mw0g8.js +5 -0
  61. package/dist/chunks/studio-79r8msnr.js +7 -0
  62. package/dist/chunks/studio-7q6jdd4h.js +5 -0
  63. package/dist/chunks/{studio-jqrg9xxg.js → studio-881z1xrx.js} +4 -4
  64. package/dist/chunks/{studio-9wyqyfqy.js → studio-8hf8qcm1.js} +4 -4
  65. package/dist/chunks/{studio-6vrxzkba.js → studio-8pne0rx4.js} +4 -4
  66. package/dist/chunks/{studio-wcakp5bt.js → studio-9179bsm7.js} +7 -7
  67. package/dist/chunks/{studio-xddgbwhe.js → studio-9ff0vqff.js} +3 -3
  68. package/dist/chunks/{studio-25a5by3h.js → studio-9gzvypw4.js} +4 -4
  69. package/dist/chunks/studio-9ndgd55w.js +5 -0
  70. package/dist/chunks/studio-9ra9e7m4.js +5 -0
  71. package/dist/chunks/studio-a6e5fzve.js +5 -0
  72. package/dist/chunks/studio-a9k86nq6.js +5 -0
  73. package/dist/chunks/studio-ae7a69jc.js +6 -0
  74. package/dist/chunks/studio-aghsg0ds.js +7 -0
  75. package/dist/chunks/{studio-2b8443ms.js → studio-ajw4k8hp.js} +3 -3
  76. package/dist/chunks/{studio-957jg8g3.js → studio-apapy17g.js} +4 -4
  77. package/dist/chunks/{studio-hnx1h1pr.js → studio-by8x0jvc.js} +4 -4
  78. package/dist/chunks/{studio-hxq1ebp0.js → studio-c88gq88j.js} +6 -6
  79. package/dist/chunks/{studio-a6x058ew.js → studio-cg54vtet.js} +4 -4
  80. package/dist/chunks/studio-dvhxxxbd.js +5 -0
  81. package/dist/chunks/{studio-1meqbxns.js → studio-e4rq6x0q.js} +4 -4
  82. package/dist/chunks/{studio-v2ek69cb.js → studio-ekzbgnjp.js} +4 -4
  83. package/dist/chunks/{studio-2dtc90zs.js → studio-fg4fj9ec.js} +6 -6
  84. package/dist/chunks/studio-fj6jzgxw.js +5 -0
  85. package/dist/chunks/{studio-txkx6586.js → studio-g45r5vm8.js} +7 -7
  86. package/dist/chunks/{studio-v0gsjav0.js → studio-gczve8zf.js} +4 -4
  87. package/dist/chunks/studio-ge08zwpv.js +5 -0
  88. package/dist/chunks/{studio-1gdfsr49.js → studio-gk9yk8dj.js} +7 -7
  89. package/dist/chunks/{studio-cpdzrdgp.js → studio-gskkgfe3.js} +4 -4
  90. package/dist/chunks/{studio-94ppxeqy.js → studio-h4jbgne2.js} +8 -8
  91. package/dist/chunks/{studio-kdseew9m.js → studio-hcy9zeac.js} +4 -4
  92. package/dist/chunks/studio-ht81xjkr.js +5 -0
  93. package/dist/chunks/{studio-73wz63nn.js → studio-jr30g4ae.js} +4 -4
  94. package/dist/chunks/{studio-fqs1a22a.js → studio-k5n4es74.js} +4 -4
  95. package/dist/chunks/{studio-j5jqvyn3.js → studio-k6hbx2fc.js} +4 -4
  96. package/dist/chunks/{studio-332hesdk.js → studio-kqh7fxn6.js} +4 -4
  97. package/dist/chunks/{studio-8nyw10dp.js → studio-kyw47pet.js} +4 -4
  98. package/dist/chunks/studio-mkw17ngg.js +5 -0
  99. package/dist/chunks/{studio-02phtmqn.js → studio-myb4mf0h.js} +4 -4
  100. package/dist/chunks/{studio-d140sy8e.js → studio-n4qq1n57.js} +4 -4
  101. package/dist/chunks/{studio-bf0rgyf7.js → studio-nfmpmkqb.js} +4 -4
  102. package/dist/chunks/studio-nfwacw31.js +5 -0
  103. package/dist/chunks/{studio-z42kysg7.js → studio-nm86874b.js} +136 -136
  104. package/dist/chunks/studio-nqexty8x.js +16 -0
  105. package/dist/chunks/studio-nr0kcqc6.js +5 -0
  106. package/dist/chunks/{studio-dmn7gpba.js → studio-nvfa137z.js} +5 -5
  107. package/dist/chunks/studio-psh8mwnw.js +4 -0
  108. package/dist/chunks/{studio-4dsdnan2.js → studio-qd0cycm4.js} +5 -5
  109. package/dist/chunks/{studio-zht0x0v3.js → studio-qevf5z9d.js} +4 -4
  110. package/dist/chunks/studio-qs5fmhtj.js +14 -0
  111. package/dist/chunks/studio-ry4789ng.js +7 -0
  112. package/dist/chunks/studio-s4fq9jht.js +5 -0
  113. package/dist/chunks/studio-sc35ttdk.js +5 -0
  114. package/dist/chunks/studio-sm2f0sc6.js +6 -0
  115. package/dist/chunks/{studio-rdnezzkd.js → studio-spza69tb.js} +5 -5
  116. package/dist/chunks/studio-t6dqnnft.js +5 -0
  117. package/dist/chunks/{studio-hw60n5dh.js → studio-tfmhnwyr.js} +3 -3
  118. package/dist/chunks/studio-tn7v8gey.js +6 -0
  119. package/dist/chunks/{studio-nsv7g1qn.js → studio-trcfer2b.js} +4 -4
  120. package/dist/chunks/studio-tyw7tk0x.js +5 -0
  121. package/dist/chunks/{studio-533py3yy.js → studio-tyzgte21.js} +4 -4
  122. package/dist/chunks/studio-vtey9w98.js +5 -0
  123. package/dist/chunks/studio-ws2r1d7e.js +5 -0
  124. package/dist/chunks/studio-x2trkhk5.js +5 -0
  125. package/dist/chunks/{studio-c5dj3dzk.js → studio-xzgp0cp7.js} +4 -4
  126. package/dist/chunks/studio-y47090vq.js +5 -0
  127. package/dist/chunks/{studio-nm9qev4p.js → studio-yjh9gddd.js} +5 -5
  128. package/dist/chunks/studio-ywf9y1e2.js +5 -0
  129. package/dist/chunks/{studio-x3ge4rts.js → studio-z1p91z5a.js} +4 -4
  130. package/dist/chunks/studio-ztppbam0.js +5 -0
  131. package/dist/chunks/{toast-host-1yg5ar8d.js → toast-host-g3r2s0kn.js} +2 -2
  132. package/dist/chunks/toast-w11qpkyz.js +4 -0
  133. package/dist/chunks/{tsMode-vra2hh7y.js → tsMode-byje4a01.js} +5 -5
  134. package/dist/chunks/workspace-7q4arnt3.js +4 -0
  135. package/dist/chunks/workspace-home-ge027kr6.js +4 -0
  136. package/dist/chunks/{ws-client-72x5n99j.js → ws-client-h09h6sqg.js} +3 -3
  137. package/dist/iframe-entry.js +13 -13
  138. package/dist/manifest.json +77 -77
  139. package/dist/studio.js +89 -89
  140. package/package.json +11 -12
  141. package/src/collab/collab-session.ts +6 -2
  142. package/src/commands/context.ts +4 -0
  143. package/src/commands/defaults.ts +2 -0
  144. package/src/commands/live-context.ts +2 -0
  145. package/src/files/files.ts +6 -3
  146. package/src/new-project/new-project-modal.ts +1 -4
  147. package/src/panels/ai-panel.ts +27 -34
  148. package/src/platform.ts +20 -0
  149. package/src/platforms/devserver.ts +2 -120
  150. package/src/services/ai-ask.ts +29 -171
  151. package/src/services/ai-command-tools.ts +40 -50
  152. package/src/services/ai-import-tools.ts +4 -5
  153. package/src/services/ai-project-tools.ts +13 -3
  154. package/src/services/ai-tools.ts +26 -564
  155. package/src/services/ai-writes.ts +84 -2
  156. package/src/services/document-assistant.ts +29 -58
  157. package/src/services/harness/assistant-tools.ts +68 -0
  158. package/src/services/harness/interaction-store.ts +118 -0
  159. package/src/services/harness/studio-documents.ts +105 -0
  160. package/src/services/harness/turn-hooks.ts +1 -6
  161. package/src/services/project-adoption.ts +8 -16
  162. package/src/services/tool-executor.ts +25 -22
  163. package/src/state.ts +3 -13
  164. package/src/surfaces/files-panel.json +12 -0
  165. package/src/surfaces/files-panel.ts +5 -2
  166. package/src/surfaces/welcome.ts +4 -1
  167. package/src/tabs/selection.ts +14 -109
  168. package/src/tabs/tab.ts +10 -1
  169. package/src/tabs/transact.ts +316 -124
  170. package/src/types.ts +13 -8
  171. package/dist/chunks/adapter-tck81zft.js +0 -4
  172. package/dist/chunks/add-new-flow-a1w0bnm6.js +0 -4
  173. package/dist/chunks/cf-account-picker-ce3kp3x1.js +0 -4
  174. package/dist/chunks/cf-settings-7ye6x7g2.js +0 -4
  175. package/dist/chunks/color-4gj49xq6.js +0 -4
  176. package/dist/chunks/components-qce0v93f.js +0 -4
  177. package/dist/chunks/content-source-jjx8kgzr.js +0 -4
  178. package/dist/chunks/editor-bkja5dbv.js +0 -4
  179. package/dist/chunks/entry-commands-1wh0btvg.js +0 -4
  180. package/dist/chunks/file-ops-6pe4z7an.js +0 -4
  181. package/dist/chunks/files-ybpp3j38.js +0 -4
  182. package/dist/chunks/format-host-3mtbhxk1.js +0 -4
  183. package/dist/chunks/fs-events-xfanfxds.js +0 -4
  184. package/dist/chunks/grid-open-dg5qmnaj.js +0 -4
  185. package/dist/chunks/iframe-host-j2bk2xkc.js +0 -4
  186. package/dist/chunks/index-fkrm7pdk.js +0 -4
  187. package/dist/chunks/jx-validate-w7v32f6a.js +0 -4
  188. package/dist/chunks/layers-g7j732cm.js +0 -4
  189. package/dist/chunks/layers-panel-gjrsxcck.js +0 -4
  190. package/dist/chunks/lit-html-jdfz9rhc.js +0 -4
  191. package/dist/chunks/media-library-9j0v4wmk.js +0 -4
  192. package/dist/chunks/media-picker-cpyyt2m0.js +0 -4
  193. package/dist/chunks/monaco-buffer-9wcme176.js +0 -4
  194. package/dist/chunks/notify-rahnnnv4.js +0 -4
  195. package/dist/chunks/parse-v77xtwv7.js +0 -4
  196. package/dist/chunks/platform-b2aqv7v5.js +0 -4
  197. package/dist/chunks/publish-panel-4yk2461b.js +0 -4
  198. package/dist/chunks/publish-workspace-mxestzcf.js +0 -4
  199. package/dist/chunks/quick-search-9g6z65b0.js +0 -4
  200. package/dist/chunks/references-c26enpxs.js +0 -4
  201. package/dist/chunks/right-panel-9d0e0yg7.js +0 -4
  202. package/dist/chunks/source-control-xyn3a8ds.js +0 -4
  203. package/dist/chunks/studio-0khd0xdw.js +0 -5
  204. package/dist/chunks/studio-1fvzq9vs.js +0 -5
  205. package/dist/chunks/studio-1yhz0s8p.js +0 -5
  206. package/dist/chunks/studio-2c2mjyzj.js +0 -6
  207. package/dist/chunks/studio-3pndhat0.js +0 -9
  208. package/dist/chunks/studio-4fjqdzrb.js +0 -6
  209. package/dist/chunks/studio-5abge3wy.js +0 -5
  210. package/dist/chunks/studio-74nzv280.js +0 -5
  211. package/dist/chunks/studio-7cg75gtj.js +0 -14
  212. package/dist/chunks/studio-89cwmnae.js +0 -5
  213. package/dist/chunks/studio-8m1t83kn.js +0 -7
  214. package/dist/chunks/studio-a3evp7f6.js +0 -6
  215. package/dist/chunks/studio-ana67wax.js +0 -5
  216. package/dist/chunks/studio-b4xhpcwy.js +0 -4
  217. package/dist/chunks/studio-c4rcaftm.js +0 -5
  218. package/dist/chunks/studio-cgjt1zce.js +0 -5
  219. package/dist/chunks/studio-cq87hree.js +0 -5
  220. package/dist/chunks/studio-e1g64q43.js +0 -5
  221. package/dist/chunks/studio-e8pzjjhf.js +0 -5
  222. package/dist/chunks/studio-f8kz3krj.js +0 -7
  223. package/dist/chunks/studio-fw3jp9m7.js +0 -5
  224. package/dist/chunks/studio-g6ysm05p.js +0 -7
  225. package/dist/chunks/studio-gmhd0j3f.js +0 -5
  226. package/dist/chunks/studio-gqpty626.js +0 -5
  227. package/dist/chunks/studio-grp684wx.js +0 -5
  228. package/dist/chunks/studio-h98520qr.js +0 -5
  229. package/dist/chunks/studio-hvxt8z79.js +0 -5
  230. package/dist/chunks/studio-j4e627w1.js +0 -5
  231. package/dist/chunks/studio-kt2j51sa.js +0 -5
  232. package/dist/chunks/studio-m4w6sygj.js +0 -16
  233. package/dist/chunks/studio-nqxqf8p2.js +0 -4
  234. package/dist/chunks/studio-p89w12s2.js +0 -7
  235. package/dist/chunks/studio-pbagym4j.js +0 -5
  236. package/dist/chunks/studio-pr23qhrw.js +0 -5
  237. package/dist/chunks/studio-r4ya3sb2.js +0 -5
  238. package/dist/chunks/studio-rm9m3z60.js +0 -5
  239. package/dist/chunks/studio-tpmnbgmt.js +0 -5
  240. package/dist/chunks/studio-ttxhq5yt.js +0 -5
  241. package/dist/chunks/studio-y1x65h5r.js +0 -5
  242. package/dist/chunks/studio-zf188hnd.js +0 -5
  243. package/dist/chunks/toast-1gv0h53h.js +0 -4
  244. package/dist/chunks/workspace-3nt93k48.js +0 -4
  245. package/dist/chunks/workspace-home-zbgyqqws.js +0 -4
  246. package/src/services/directory-picker.ts +0 -187
  247. 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": "8.1.0",
3
+ "version": "9.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.40.0",
65
- "@jxsuite/collab": "^0.11.2",
66
- "@jxsuite/create": "^1.4.0",
67
- "@jxsuite/formulas": "^0.0.24",
68
- "@jxsuite/markup": "^1.0.0",
69
- "@jxsuite/protocol": "^4.0.0",
70
- "@jxsuite/runtime": "^5.2.0",
71
- "@jxsuite/schema": "^4.0.0",
72
- "@jxsuite/site": "^2.2.0",
73
- "@jxsuite/ui": "^0.8.0",
63
+ "@jxsuite/ai": "^0.41.0",
64
+ "@jxsuite/collab": "^0.11.3",
65
+ "@jxsuite/create": "^1.4.1",
66
+ "@jxsuite/formulas": "^0.0.25",
67
+ "@jxsuite/markup": "^1.0.1",
68
+ "@jxsuite/protocol": "^5.0.0",
69
+ "@jxsuite/runtime": "^6.0.0",
70
+ "@jxsuite/schema": "^4.1.0",
71
+ "@jxsuite/site": "^2.2.1",
72
+ "@jxsuite/ui": "^0.8.1",
74
73
  "@vue/reactivity": "3.5.44",
75
74
  "ajv": "^8.20.0",
76
75
  "ajv-formats": "^3.0.1",
@@ -26,7 +26,7 @@ import { jsonClone } from "../utils/studio-utils";
26
26
  import { cloneSelection } from "../tabs/selection";
27
27
  import {
28
28
  applyExternalDocOps,
29
- isBatching,
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
- if (isBatching()) {
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;
@@ -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,
@@ -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"),
@@ -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
- const result = await platform.openProject();
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
- const values: FilesPanelValues = { ...emptyFilesPanelValues(), view: "welcome" };
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
  }
@@ -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.",
@@ -28,13 +28,13 @@
28
28
 
29
29
  import { effect, effectScope } from "../reactivity";
30
30
  import { createDocumentAssistant } from "../services/document-assistant";
31
- import { writesForTurn } from "../services/ai-writes";
31
+ import { restoreTurn } from "../services/ai-writes";
32
+ import { isString } from "@jxsuite/schema/guards";
32
33
  import { notify } from "../services/notify";
33
- import { undo } from "../tabs/transact";
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
- skipAsk();
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 `services/ai-ask.ts` — but it is drawn into
212
- // This panel, so the same effect has to track it or a question would appear a turn late
213
- // (and its ANSWER would never repaint the card at all).
214
- void pendingAsk();
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 (answerAsk(text.trim())) {
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 loop opens one batch per turn per document, so undoing the turn is undoing that batch. The
323
- * button is offered by the projection only when every recorded change was transactional; this guard
324
- * is the second half of the same promise, because a ledger can be trimmed (MAX_TURNS) between the
325
- * projection and the click and a Restore that silently restored SOME of a turn would be worse than
326
- * one that refused.
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 writes = writesForTurn(messageId);
334
- if (writes.length === 0) {
335
- notify.warn("There is no longer a record of what that turn changed.", { source: "Assistant" });
333
+ const restored = restoreTurn(messageId);
334
+ if (isString(restored)) {
335
+ notify.warn(restored, { source: "Assistant" });
336
336
  return;
337
337
  }
338
- const disk = writes.filter((w) => w.disk).map((w) => w.path);
339
- if (disk.length > 0) {
340
- notify.warn(
341
- `Cannot restore: ${disk.join(", ")} ${disk.length === 1 ? "was" : "were"} written straight ` +
342
- "to disk, which undo cannot reach.",
343
- { source: "Assistant" },
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
- undo(tab);
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 isAwaitingAnswer();
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: pendingAsk()?.id ?? null },
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;
@@ -2,18 +2,16 @@
2
2
  * Devserver.js — Dev Server Platform Adapter
3
3
  *
4
4
  * Implements the StudioPlatform interface for the @jxsuite/server development workflow. All file
5
- * I/O goes through /__studio/* REST endpoints. Project opening uses the Chrome File System Access
6
- * API (showDirectoryPicker).
5
+ * I/O goes through /__studio/* REST endpoints. There is no Open Project: a dev-server window opens
6
+ * a project from its `?project=` URL parameter (specs/desktop.md §8.2).
7
7
  *
8
8
  * See spec/desktop.md §8 for the full specification.
9
9
  */
10
10
 
11
11
  import { streamImport } from "../services/import-client";
12
- import { canPickDirectory, pickDirectoryPath } from "../services/directory-picker";
13
12
  import { negotiateCollab } from "@jxsuite/collab/negotiate";
14
13
  import type { CollabNegotiation } from "@jxsuite/collab/negotiate";
15
14
  import type { WsCollabConnection } from "@jxsuite/collab/client";
16
- import { isFunction } from "@jxsuite/schema/guards";
17
15
  import type { JsonObject, JsonValue, JxMutableNode, ProjectConfig } from "@jxsuite/schema/types";
18
16
  import type {
19
17
  DataConnectionsResponse,
@@ -80,16 +78,6 @@ interface FsEventBatch {
80
78
  events?: FsEvent[];
81
79
  }
82
80
 
83
- /** `showDirectoryPicker()` (WICG File System Access), which only Chromium-based browsers expose. */
84
- type ShowDirectoryPicker = (options: { mode: string }) => Promise<FileSystemDirectoryHandle>;
85
-
86
- /** True when the window implements the File System Access directory picker. */
87
- function hasDirectoryPicker(
88
- host: Window,
89
- ): host is Window & { showDirectoryPicker: ShowDirectoryPicker } {
90
- return "showDirectoryPicker" in host && isFunction(host.showDirectoryPicker);
91
- }
92
-
93
81
  /** The options object {@link StudioPlatform.createProject} takes. */
94
82
  type CreateProjectOptions = Parameters<StudioPlatform["createProject"]>[0];
95
83
 
@@ -225,36 +213,6 @@ export function createDevServerPlatform() {
225
213
  return p.startsWith(`${_projectRoot}/`) ? p.slice(_projectRoot.length + 1) : p;
226
214
  }
227
215
 
228
- /**
229
- * Native folder chooser for the New Project modal's Browse… button. Present only when the browser
230
- * implements `showDirectoryPicker` (Chromium-based); elsewhere the member stays undefined and the
231
- * modal falls back to a typed Location field.
232
- *
233
- * The picked handle carries no filesystem path, so it is resolved by the id the picker tags the
234
- * folder with (see `@jxsuite/studio/directory-picker`). Resolves `null` only when the user
235
- * dismisses the chooser; every other failure rejects with a sentence the modal shows under
236
- * Location. So `locate` keeps the helper's contract: `null` is "no folder holds this id" (the
237
- * route's 200 `{ path: null }`), and a non-ok answer throws the problem the server sent rather
238
- * than posing as a miss. A network failure (`TypeError: Failed to fetch`, e.g. a page that is not
239
- * being served by the dev server at all) propagates as is, and the picker names it.
240
- */
241
- const directoryPicking: Pick<StudioPlatform, "pickDirectory"> = {};
242
- if (canPickDirectory()) {
243
- directoryPicking.pickDirectory = async (): Promise<string | null> =>
244
- pickDirectoryPath(async ({ id, name }) => {
245
- const res = await fetch(
246
- `/__studio/locate-directory?name=${encodeURIComponent(name)}&id=${encodeURIComponent(id)}`,
247
- );
248
- if (!res.ok) {
249
- // A proxy, or a `views://` host with no dev server behind it, may answer in HTML.
250
- const body = await readJson<JsonValue>(res).catch(() => null);
251
- throw new Error(problemMessage(body, res.status));
252
- }
253
- const found = await readJson<{ path?: string | null }>(res);
254
- return found.path ?? null;
255
- });
256
- }
257
-
258
216
  return {
259
217
  id: "devserver",
260
218
 
@@ -293,80 +251,6 @@ export function createDevServerPlatform() {
293
251
  await postActivate(root ?? _projectRoot);
294
252
  },
295
253
 
296
- // ─── Project opening ──────────────────────────────────────────────────
297
-
298
- async openProject() {
299
- // Use Chrome's showDirectoryPicker API
300
- const host = window;
301
- if (!hasDirectoryPicker(host)) {
302
- throw new Error("showDirectoryPicker not available — use a Chromium-based browser");
303
- }
304
-
305
- let dirHandle;
306
- try {
307
- dirHandle = await host.showDirectoryPicker({ mode: "readwrite" });
308
- } catch (error) {
309
- // User cancelled the picker
310
- if (error instanceof Error && error.name === "AbortError") {
311
- return null;
312
- }
313
- throw error;
314
- }
315
-
316
- // Read project.json from the chosen directory
317
- let siteHandle;
318
- try {
319
- siteHandle = await dirHandle.getFileHandle("project.json");
320
- } catch {
321
- throw new Error("No project.json found in selected folder");
322
- }
323
-
324
- const file = await siteHandle.getFile();
325
- /* SAFETY: the user picked this folder as a project, and project.json is that project's own
326
- config file; it is matched below against the server's parse of the same file, and every
327
- adapter hands project.json to Studio as the ProjectConfig the session edits. */
328
- const config = JSON.parse(await file.text()) as ProjectConfig;
329
-
330
- // Resolve server-relative path by matching against known sites
331
- const sitesRes = await fetch("/__studio/sites");
332
- if (!sitesRes.ok) {
333
- throw new Error("Failed to fetch site list from server");
334
- }
335
- const sites = await readJson<SiteEntry[]>(sitesRes);
336
- const match = sites.find(
337
- (s: SiteEntry) => JSON.stringify(s.config) === JSON.stringify(config),
338
- );
339
-
340
- if (!match) {
341
- // Project is outside dev server root — ask the server to find it by directory name
342
- const findRes = await fetch(
343
- `/__studio/find-project?name=${encodeURIComponent(dirHandle.name)}`,
344
- );
345
- if (!findRes.ok) {
346
- throw new Error("Could not locate project on disk");
347
- }
348
- const found = await readJson<{ path?: string }>(findRes);
349
- if (!found.path) {
350
- throw new Error(`Could not find project directory "${dirHandle.name}"`);
351
- }
352
- _projectRoot = found.path;
353
- } else {
354
- _projectRoot = match.path;
355
- }
356
-
357
- // Notify server of active project for static file resolution
358
- await this.activate();
359
-
360
- return {
361
- config,
362
- handle: {
363
- name: config.name || _projectRoot.split("/").pop()!,
364
- projectConfig: config,
365
- root: _projectRoot,
366
- },
367
- };
368
- },
369
-
370
254
  /**
371
255
  * Probe the server root to see if it is itself a site project. Used at startup to auto-detect
372
256
  * projects.
@@ -408,8 +292,6 @@ export function createDevServerPlatform() {
408
292
  return await res.json();
409
293
  },
410
294
 
411
- ...directoryPicking,
412
-
413
295
  /** List starter templates from the dev server. */
414
296
  async listStarters(): Promise<StarterInfo[]> {
415
297
  const res = await fetch("/__studio/starters");