@jxsuite/studio 8.0.1 → 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 (282) hide show
  1. package/dist/chunks/{2020-5wtzce5g.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-6rbra5dy.js → color-area-h8dbasp1.js} +2 -2
  8. package/dist/chunks/{color-field-gy56c94g.js → color-field-qv5r2b16.js} +3 -3
  9. package/dist/chunks/{color-slider-sqqdyf6c.js → color-slider-nz2mrzky.js} +2 -2
  10. package/dist/chunks/{combobox-72h32ejk.js → combobox-6hs00996.js} +2 -2
  11. package/dist/chunks/components-3vf75n83.js +4 -0
  12. package/dist/chunks/content-source-4f2raf4a.js +4 -0
  13. package/dist/chunks/{convert-to-repeater-he70fc2b.js → convert-to-repeater-j9hfcj4f.js} +3 -3
  14. package/dist/chunks/editor-645krvtz.js +4 -0
  15. package/dist/chunks/entry-commands-3qmv8wkr.js +4 -0
  16. package/dist/chunks/{extension-sections-0egb88dm.js → extension-sections-thfke054.js} +3 -3
  17. package/dist/chunks/file-ops-kkhp9ma7.js +4 -0
  18. package/dist/chunks/files-yswdhxrf.js +4 -0
  19. package/dist/chunks/format-host-4g8ex6bz.js +4 -0
  20. package/dist/chunks/fs-events-z9gpvc6t.js +4 -0
  21. package/dist/chunks/grid-open-fm4j183c.js +4 -0
  22. package/dist/chunks/{grid-view-qg1b5eqx.js → grid-view-7d2nx83y.js} +16 -15
  23. package/dist/chunks/{icons-63bjtbs7.js → icons-853n6qt3.js} +2 -2
  24. package/dist/chunks/{iframe-entry-pkgmcgp0.js → iframe-entry-2x9bn5ng.js} +4 -4
  25. package/dist/chunks/{iframe-entry-99nr5zfb.js → iframe-entry-42hpmwz5.js} +4 -4
  26. package/dist/chunks/{iframe-entry-76j8vwwx.js → iframe-entry-b5qgggyp.js} +2 -2
  27. package/dist/chunks/{iframe-entry-2zggzajv.js → iframe-entry-btck0e9s.js} +4 -4
  28. package/dist/chunks/iframe-host-q7fedsmr.js +4 -0
  29. package/dist/chunks/{index-32eqzv1k.js → index-jxmt7vh8.js} +3 -3
  30. package/dist/chunks/index-pt4wh98x.js +4 -0
  31. package/dist/chunks/{javascript-pjp734q4.js → javascript-vmqp56ke.js} +2 -2
  32. package/dist/chunks/{jsonMode-njkvsa02.js → jsonMode-n3wmpant.js} +5 -5
  33. package/dist/chunks/jx-validate-19y62knt.js +4 -0
  34. package/dist/chunks/layers-panel-1qvc83y9.js +4 -0
  35. package/dist/chunks/layers-qf0bc7ze.js +4 -0
  36. package/dist/chunks/{listbox-c421cf8c.js → listbox-7gka1h86.js} +2 -2
  37. package/dist/chunks/lit-html-wyqw04y2.js +4 -0
  38. package/dist/chunks/media-library-f9d5qcjb.js +4 -0
  39. package/dist/chunks/media-picker-rsb1fmk2.js +4 -0
  40. package/dist/chunks/{menu-bdpzbfvs.js → menu-d2jxpgma.js} +2 -2
  41. package/dist/chunks/{monaco-binding-vq1hgnn0.js → monaco-binding-wfhmz89d.js} +3 -3
  42. package/dist/chunks/monaco-buffer-ak0raadw.js +4 -0
  43. package/dist/chunks/{monaco-setup-xrd2cbkp.js → monaco-setup-fty7a8vg.js} +3 -3
  44. package/dist/chunks/notify-4qj70q0x.js +4 -0
  45. package/dist/chunks/parse-n8dfxnqf.js +4 -0
  46. package/dist/chunks/platform-ehg6t9vd.js +4 -0
  47. package/dist/chunks/{popover-b89sfej7.js → popover-9tf4cjrk.js} +2 -2
  48. package/dist/chunks/publish-panel-yhmda3cd.js +4 -0
  49. package/dist/chunks/{publish-quick-qx2455cj.js → publish-quick-12hh8926.js} +3 -3
  50. package/dist/chunks/publish-workspace-hx8qdv9p.js +4 -0
  51. package/dist/chunks/quick-search-r955m3q1.js +4 -0
  52. package/dist/chunks/references-8gxv0x7t.js +4 -0
  53. package/dist/chunks/right-panel-jarwqjpy.js +4 -0
  54. package/dist/chunks/source-control-84qt25m9.js +4 -0
  55. package/dist/chunks/{split-c9zfdgxf.js → split-m5rwn31z.js} +2 -2
  56. package/dist/chunks/studio-0nefxhar.js +7 -0
  57. package/dist/chunks/studio-0rg30pvs.js +5 -0
  58. package/dist/chunks/{studio-s32578pn.js → studio-14hh60j8.js} +4 -4
  59. package/dist/chunks/studio-1cfyd1ys.js +5 -0
  60. package/dist/chunks/studio-2bq7hheq.js +5 -0
  61. package/dist/chunks/{studio-zz9qe3rw.js → studio-32d8wyxq.js} +37 -37
  62. package/dist/chunks/studio-3hpcawh8.js +4 -0
  63. package/dist/chunks/{studio-svb7yyve.js → studio-4j35hgsw.js} +4 -4
  64. package/dist/chunks/{studio-x1x167jh.js → studio-4qg61g9j.js} +4 -4
  65. package/dist/chunks/studio-4rgem12q.js +5 -0
  66. package/dist/chunks/studio-6k9djnxw.js +5 -0
  67. package/dist/chunks/studio-6rbaedw2.js +9 -0
  68. package/dist/chunks/studio-732mw0g8.js +5 -0
  69. package/dist/chunks/studio-79r8msnr.js +7 -0
  70. package/dist/chunks/studio-7q6jdd4h.js +5 -0
  71. package/dist/chunks/{studio-r5qy30vx.js → studio-8hf8qcm1.js} +4 -4
  72. package/dist/chunks/{studio-8tr6cfrc.js → studio-8pne0rx4.js} +4 -4
  73. package/dist/chunks/{studio-y0n7yxea.js → studio-9179bsm7.js} +7 -7
  74. package/dist/chunks/{studio-pbxa5fpc.js → studio-9ff0vqff.js} +3 -3
  75. package/dist/chunks/studio-9gzvypw4.js +5 -0
  76. package/dist/chunks/studio-9ndgd55w.js +5 -0
  77. package/dist/chunks/studio-9ra9e7m4.js +5 -0
  78. package/dist/chunks/studio-a6e5fzve.js +5 -0
  79. package/dist/chunks/studio-a9k86nq6.js +5 -0
  80. package/dist/chunks/studio-ae7a69jc.js +6 -0
  81. package/dist/chunks/studio-aghsg0ds.js +7 -0
  82. package/dist/chunks/{studio-k3xxbagr.js → studio-ajw4k8hp.js} +3 -3
  83. package/dist/chunks/{studio-qmn1xc24.js → studio-apapy17g.js} +4 -4
  84. package/dist/chunks/{studio-8m0y2qe7.js → studio-by8x0jvc.js} +4 -4
  85. package/dist/chunks/{studio-wbcjbhy1.js → studio-c88gq88j.js} +5 -5
  86. package/dist/chunks/{studio-n0c5h6mf.js → studio-cg54vtet.js} +4 -4
  87. package/dist/chunks/studio-dvhxxxbd.js +5 -0
  88. package/dist/chunks/{studio-3q40e3rj.js → studio-e4rq6x0q.js} +4 -4
  89. package/dist/chunks/{studio-fbhxxc5f.js → studio-ekzbgnjp.js} +4 -4
  90. package/dist/chunks/studio-fg4fj9ec.js +10 -0
  91. package/dist/chunks/studio-fj6jzgxw.js +5 -0
  92. package/dist/chunks/{studio-nh6d342n.js → studio-g45r5vm8.js} +7 -7
  93. package/dist/chunks/{studio-a2bvaptz.js → studio-gczve8zf.js} +4 -4
  94. package/dist/chunks/studio-ge08zwpv.js +5 -0
  95. package/dist/chunks/studio-gk9yk8dj.js +12 -0
  96. package/dist/chunks/{studio-bbe4c90a.js → studio-gskkgfe3.js} +4 -4
  97. package/dist/chunks/{studio-83mk4bgg.js → studio-h4jbgne2.js} +8 -8
  98. package/dist/chunks/{studio-b7y5mr30.js → studio-hcy9zeac.js} +4 -4
  99. package/dist/chunks/studio-ht81xjkr.js +5 -0
  100. package/dist/chunks/{studio-jaaz8ce9.js → studio-jr30g4ae.js} +4 -4
  101. package/dist/chunks/{studio-qgh13jtz.js → studio-k5n4es74.js} +4 -4
  102. package/dist/chunks/{studio-ccv2svc6.js → studio-k6hbx2fc.js} +4 -4
  103. package/dist/chunks/{studio-9tmebf0s.js → studio-kqh7fxn6.js} +4 -4
  104. package/dist/chunks/{studio-q0mvbaq6.js → studio-kyw47pet.js} +4 -4
  105. package/dist/chunks/studio-mkw17ngg.js +5 -0
  106. package/dist/chunks/{studio-x9hsze61.js → studio-myb4mf0h.js} +4 -4
  107. package/dist/chunks/studio-n4qq1n57.js +5 -0
  108. package/dist/chunks/studio-nfmpmkqb.js +5 -0
  109. package/dist/chunks/studio-nfwacw31.js +5 -0
  110. package/dist/chunks/{studio-rthyg42z.js → studio-nm86874b.js} +172 -172
  111. package/dist/chunks/studio-nqexty8x.js +16 -0
  112. package/dist/chunks/studio-nr0kcqc6.js +5 -0
  113. package/dist/chunks/{studio-jsgf2q5k.js → studio-nvfa137z.js} +5 -5
  114. package/dist/chunks/studio-psh8mwnw.js +4 -0
  115. package/dist/chunks/studio-qd0cycm4.js +9 -0
  116. package/dist/chunks/{studio-tfkznv39.js → studio-qevf5z9d.js} +4 -4
  117. package/dist/chunks/studio-qs5fmhtj.js +14 -0
  118. package/dist/chunks/studio-ry4789ng.js +7 -0
  119. package/dist/chunks/studio-s4fq9jht.js +5 -0
  120. package/dist/chunks/studio-sc35ttdk.js +5 -0
  121. package/dist/chunks/studio-sm2f0sc6.js +6 -0
  122. package/dist/chunks/{studio-3qeh9t0h.js → studio-spza69tb.js} +5 -5
  123. package/dist/chunks/studio-t6dqnnft.js +5 -0
  124. package/dist/chunks/{studio-43sd2mam.js → studio-tfmhnwyr.js} +3 -3
  125. package/dist/chunks/studio-tn7v8gey.js +6 -0
  126. package/dist/chunks/{studio-tft6r105.js → studio-trcfer2b.js} +4 -4
  127. package/dist/chunks/studio-tyw7tk0x.js +5 -0
  128. package/dist/chunks/{studio-jcnsdaep.js → studio-tyzgte21.js} +4 -4
  129. package/dist/chunks/studio-vtey9w98.js +5 -0
  130. package/dist/chunks/studio-ws2r1d7e.js +5 -0
  131. package/dist/chunks/studio-x2trkhk5.js +5 -0
  132. package/dist/chunks/{studio-mn0476ag.js → studio-xzgp0cp7.js} +4 -4
  133. package/dist/chunks/studio-y47090vq.js +5 -0
  134. package/dist/chunks/{studio-5g0rprdt.js → studio-yjh9gddd.js} +5 -5
  135. package/dist/chunks/studio-ywf9y1e2.js +5 -0
  136. package/dist/chunks/{studio-k7evxqh4.js → studio-z1p91z5a.js} +4 -4
  137. package/dist/chunks/studio-ztppbam0.js +5 -0
  138. package/dist/chunks/{toast-host-1yg5ar8d.js → toast-host-g3r2s0kn.js} +2 -2
  139. package/dist/chunks/toast-w11qpkyz.js +4 -0
  140. package/dist/chunks/{tooltip-yypz28gq.js → tooltip-dj1pvgz7.js} +2 -2
  141. package/dist/chunks/{tree-9xa4hm7n.js → tree-j0ebwr5s.js} +2 -2
  142. package/dist/chunks/{tsMode-epyfz1hv.js → tsMode-byje4a01.js} +5 -5
  143. package/dist/chunks/workspace-7q4arnt3.js +4 -0
  144. package/dist/chunks/workspace-home-ge027kr6.js +4 -0
  145. package/dist/chunks/{ws-client-bv552sfx.js → ws-client-h09h6sqg.js} +3 -3
  146. package/dist/iframe-entry.js +14 -14
  147. package/dist/manifest.json +76 -76
  148. package/dist/studio.js +88 -88
  149. package/package.json +15 -16
  150. package/src/browse/component-examples.ts +15 -224
  151. package/src/browse/library-preview.ts +6 -13
  152. package/src/browse/preview-root.ts +7 -4
  153. package/src/canvas/canvas-utils.ts +18 -4
  154. package/src/canvas/iframe-editable-root.ts +13 -4
  155. package/src/canvas/iframe-entry.ts +3 -1
  156. package/src/canvas/iframe-interaction.ts +11 -2
  157. package/src/canvas/iframe-render.ts +25 -3
  158. package/src/canvas/preview-placeholder.ts +120 -0
  159. package/src/collab/collab-session.ts +6 -2
  160. package/src/commands/context.ts +4 -0
  161. package/src/commands/defaults.ts +2 -0
  162. package/src/commands/live-context.ts +2 -0
  163. package/src/editor/shortcuts.ts +3 -0
  164. package/src/files/files.ts +6 -3
  165. package/src/new-project/new-project-modal.ts +1 -4
  166. package/src/panels/ai-panel.ts +27 -34
  167. package/src/panels/component-preview.ts +5 -0
  168. package/src/panels/pane-context.ts +43 -18
  169. package/src/panels/site-model.ts +7 -19
  170. package/src/panels/site-panel.ts +2 -25
  171. package/src/panels/stylebook-doc.ts +2 -53
  172. package/src/platform.ts +20 -0
  173. package/src/platforms/devserver.ts +2 -120
  174. package/src/services/ai-ask.ts +29 -171
  175. package/src/services/ai-command-tools.ts +40 -50
  176. package/src/services/ai-import-tools.ts +4 -5
  177. package/src/services/ai-project-tools.ts +13 -3
  178. package/src/services/ai-tools.ts +26 -564
  179. package/src/services/ai-writes.ts +84 -2
  180. package/src/services/document-assistant.ts +29 -58
  181. package/src/services/harness/assistant-tools.ts +68 -0
  182. package/src/services/harness/interaction-store.ts +118 -0
  183. package/src/services/harness/studio-documents.ts +105 -0
  184. package/src/services/harness/turn-hooks.ts +1 -6
  185. package/src/services/project-adoption.ts +8 -16
  186. package/src/services/references.ts +0 -0
  187. package/src/services/tool-executor.ts +25 -22
  188. package/src/state.ts +3 -13
  189. package/src/style/stylebook-specimens.ts +11 -15
  190. package/src/surfaces/files-panel.json +12 -0
  191. package/src/surfaces/files-panel.ts +5 -2
  192. package/src/surfaces/pane-context.json +286 -265
  193. package/src/surfaces/pane-context.ts +17 -8
  194. package/src/surfaces/welcome.ts +4 -1
  195. package/src/tabs/selection.ts +14 -109
  196. package/src/tabs/tab.ts +15 -7
  197. package/src/tabs/transact.ts +316 -124
  198. package/src/types.ts +13 -8
  199. package/src/utils/geometry.ts +49 -2
  200. package/dist/chunks/adapter-5tphg7xe.js +0 -4
  201. package/dist/chunks/add-new-flow-psym14sn.js +0 -4
  202. package/dist/chunks/cf-account-picker-haqazq5r.js +0 -4
  203. package/dist/chunks/cf-settings-h2wrh6hq.js +0 -4
  204. package/dist/chunks/color-m7r1vk54.js +0 -4
  205. package/dist/chunks/components-r22xca09.js +0 -4
  206. package/dist/chunks/content-source-mmppmcyg.js +0 -4
  207. package/dist/chunks/editor-ccd9jwsa.js +0 -4
  208. package/dist/chunks/entry-commands-6qxz9e8p.js +0 -4
  209. package/dist/chunks/file-ops-qnab36ek.js +0 -4
  210. package/dist/chunks/files-rcn56qvm.js +0 -4
  211. package/dist/chunks/format-host-yxz0kjjs.js +0 -4
  212. package/dist/chunks/fs-events-d1qxq4z4.js +0 -4
  213. package/dist/chunks/grid-open-4z1g062t.js +0 -4
  214. package/dist/chunks/iframe-host-ssv7kyj0.js +0 -4
  215. package/dist/chunks/index-f0jn7nnp.js +0 -4
  216. package/dist/chunks/jx-validate-ttasrbh7.js +0 -4
  217. package/dist/chunks/layers-bgyr11tr.js +0 -4
  218. package/dist/chunks/layers-panel-7ez66zeq.js +0 -4
  219. package/dist/chunks/lit-html-v9gh687x.js +0 -4
  220. package/dist/chunks/media-library-sq468wts.js +0 -4
  221. package/dist/chunks/media-picker-shfe59pt.js +0 -4
  222. package/dist/chunks/monaco-buffer-3afgwvbp.js +0 -4
  223. package/dist/chunks/notify-kzrjr3fv.js +0 -4
  224. package/dist/chunks/parse-v77xtwv7.js +0 -4
  225. package/dist/chunks/platform-7v4rtd7m.js +0 -4
  226. package/dist/chunks/publish-panel-2z9gw8ee.js +0 -4
  227. package/dist/chunks/publish-workspace-wrc1dgwx.js +0 -4
  228. package/dist/chunks/quick-search-n0t0eqxb.js +0 -4
  229. package/dist/chunks/references-9547rg5s.js +0 -4
  230. package/dist/chunks/right-panel-wsgnr9pn.js +0 -4
  231. package/dist/chunks/source-control-kwktr9y6.js +0 -4
  232. package/dist/chunks/studio-03ct6yfv.js +0 -9
  233. package/dist/chunks/studio-07rcfje3.js +0 -5
  234. package/dist/chunks/studio-3e54qh39.js +0 -7
  235. package/dist/chunks/studio-3exxhz5g.js +0 -5
  236. package/dist/chunks/studio-3qqjeep5.js +0 -6
  237. package/dist/chunks/studio-4hp8pkyt.js +0 -5
  238. package/dist/chunks/studio-58xg6j2q.js +0 -9
  239. package/dist/chunks/studio-6m380wp7.js +0 -5
  240. package/dist/chunks/studio-80jyh1xh.js +0 -4
  241. package/dist/chunks/studio-8vcvfs7r.js +0 -6
  242. package/dist/chunks/studio-bavb31r0.js +0 -5
  243. package/dist/chunks/studio-c05wdnt3.js +0 -5
  244. package/dist/chunks/studio-d5grtb9e.js +0 -16
  245. package/dist/chunks/studio-df0baea9.js +0 -5
  246. package/dist/chunks/studio-dgk4dqtx.js +0 -7
  247. package/dist/chunks/studio-e1g64q43.js +0 -5
  248. package/dist/chunks/studio-ed1hp6zf.js +0 -6
  249. package/dist/chunks/studio-ed3xx40h.js +0 -7
  250. package/dist/chunks/studio-f14e1zdj.js +0 -5
  251. package/dist/chunks/studio-fyh0jc96.js +0 -5
  252. package/dist/chunks/studio-gcxk20n7.js +0 -5
  253. package/dist/chunks/studio-kt3pjkwd.js +0 -5
  254. package/dist/chunks/studio-m8g16bgz.js +0 -5
  255. package/dist/chunks/studio-mx1smyq0.js +0 -5
  256. package/dist/chunks/studio-nqxqf8p2.js +0 -4
  257. package/dist/chunks/studio-p4nrkvqr.js +0 -5
  258. package/dist/chunks/studio-pgmvdv7z.js +0 -7
  259. package/dist/chunks/studio-ph84avkf.js +0 -5
  260. package/dist/chunks/studio-q0tccd43.js +0 -5
  261. package/dist/chunks/studio-q7rtx6zq.js +0 -14
  262. package/dist/chunks/studio-qwdqwkj0.js +0 -5
  263. package/dist/chunks/studio-rj9eamf8.js +0 -5
  264. package/dist/chunks/studio-rptgsk96.js +0 -5
  265. package/dist/chunks/studio-s19tp1xr.js +0 -5
  266. package/dist/chunks/studio-syreb644.js +0 -5
  267. package/dist/chunks/studio-tf9jryb1.js +0 -10
  268. package/dist/chunks/studio-txf2kysx.js +0 -5
  269. package/dist/chunks/studio-v8867ddv.js +0 -12
  270. package/dist/chunks/studio-vxq7fcvv.js +0 -5
  271. package/dist/chunks/studio-w4rz9hdk.js +0 -5
  272. package/dist/chunks/studio-wc649wss.js +0 -5
  273. package/dist/chunks/studio-wys23mxz.js +0 -5
  274. package/dist/chunks/studio-y3ahhdpx.js +0 -5
  275. package/dist/chunks/studio-z97c48fk.js +0 -5
  276. package/dist/chunks/studio-zat62m0c.js +0 -5
  277. package/dist/chunks/studio-zcd2e7yz.js +0 -5
  278. package/dist/chunks/toast-1gv0h53h.js +0 -4
  279. package/dist/chunks/workspace-dz7nxfhv.js +0 -4
  280. package/dist/chunks/workspace-home-xsd7hkjw.js +0 -4
  281. package/src/services/directory-picker.ts +0 -187
  282. package/src/services/gated-registry.ts +0 -77
@@ -21,15 +21,9 @@ import { isKeyframesAtRule } from "@jxsuite/runtime/css";
21
21
  import type { StylebookEntry } from "./stylebook-panel";
22
22
  import type { ComponentEntry } from "../files/components";
23
23
  import type { JxPath } from "../state";
24
- import { isNonNullObject, isRef } from "@jxsuite/schema/guards";
24
+ import { isNonNullObject } from "@jxsuite/schema/guards";
25
25
  import type { StylebookSpecimens } from "../style/stylebook-specimens";
26
- import type {
27
- JxDocument,
28
- JxHeadEntry,
29
- JxMutableNode,
30
- JxStateDefinition,
31
- JxStyle,
32
- } from "@jxsuite/schema/types";
26
+ import type { JxHeadEntry, JxMutableNode, JxStyle } from "@jxsuite/schema/types";
33
27
 
34
28
  export interface StylebookDocResult {
35
29
  /** The specimen document (plain JSON — safe to post over the bridge). */
@@ -210,40 +204,6 @@ export function catalogueRows(
210
204
  return out;
211
205
  }
212
206
 
213
- /**
214
- * A card's specimen for a component drawn as the first row of a list
215
- * (`style/stylebook-specimens.ts`): the list's repeater, reading its row from `rootState[key]` so
216
- * two cards' lists cannot collide, and the rest of the page state the row was drawn in, the first
217
- * card's value winning a shared name (`$page`, `$site`). Null when the sample is not a repeater
218
- * over a state list.
219
- */
220
- export function sampleRowSpecimen(
221
- sample: JxDocument,
222
- key: string,
223
- rootState: Record<string, JxStateDefinition>,
224
- ): JxMutableNode | null {
225
- const [list] = Array.isArray(sample.children) ? sample.children : [];
226
- if (!isNonNullObject(list) || Array.isArray(list) || !isRef(list.items)) {
227
- return null;
228
- }
229
- const { $ref } = list.items;
230
- const listKey = $ref.startsWith("#/state/") ? $ref.slice("#/state/".length) : "";
231
- const rows = sample.state?.[listKey];
232
- if (!Array.isArray(rows)) {
233
- return null;
234
- }
235
- rootState[key] = rows;
236
- for (const [name, value] of Object.entries(sample.state ?? {})) {
237
- if (name !== listKey && !(name in rootState)) {
238
- rootState[name] = value;
239
- }
240
- }
241
- /* SAFETY: `list` is the repeater object a page's `children` held; only its `items` is replaced,
242
- with a ref of the same shape. */
243
- const repeater = { ...list, items: { $ref: `#/state/${key}` } } as JxMutableNode;
244
- return { children: [repeater], tagName: "div" };
245
- }
246
-
247
207
  /** Options for {@link buildStylebookDoc}. */
248
208
  export interface BuildStylebookDocOpts {
249
209
  meta: { $sections: { label: string; elements: StylebookEntry[] }[] };
@@ -374,8 +334,6 @@ export function buildStylebookDoc(opts: BuildStylebookDocOpts): StylebookDocResu
374
334
 
375
335
  // Custom components from the registry — live custom elements registered via $elements.
376
336
  const $elements: (string | { $ref: string })[] = [];
377
- // The data the sample-row cards read: each row's list, and the page context it was drawn in.
378
- const rootState: Record<string, JxStateDefinition> = {};
379
337
  {
380
338
  let comps = opts.components;
381
339
  if (filter) {
@@ -404,12 +362,6 @@ export function buildStylebookDoc(opts: BuildStylebookDocOpts): StylebookDocResu
404
362
  const specimen: JxMutableNode = { $props: drawn.props, tagName: comp.tagName };
405
363
  return cardNode(comp.tagName, `<${comp.tagName}>`, specimen, cardPath, null);
406
364
  }
407
- if (drawn?.kind === "row") {
408
- const row = sampleRowSpecimen(drawn.sample.doc, `stylebookRow${ci}`, rootState);
409
- if (row) {
410
- return cardNode(comp.tagName, `<${comp.tagName}>`, row, cardPath, null);
411
- }
412
- }
413
365
  if (drawn?.kind !== "none") {
414
366
  const attributes: Record<string, string> = {};
415
367
  for (const p of comp.props ?? []) {
@@ -455,9 +407,6 @@ export function buildStylebookDoc(opts: BuildStylebookDocOpts): StylebookDocResu
455
407
  doc.$head = opts.effectiveHead;
456
408
  }
457
409
  doc.$media = opts.effectiveMedia;
458
- if (Object.keys(rootState).length > 0) {
459
- doc.state = rootState;
460
- }
461
410
  doc.attributes = { class: "sb-root" };
462
411
  doc.children = sections;
463
412
  doc.style = transposeStylebookStyle(opts.effectiveStyle);
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");
@@ -1,12 +1,10 @@
1
1
  /**
2
2
  * Ai-ask.ts — the assistant's one way to stop and put a question to the author.
3
3
  *
4
- * The agent loop `await`s `toolRegistry.execute` (`services/tool-executor.ts`), so a tool that
5
- * returns a pending promise suspends the turn. That is the whole mechanism: `ask_user` registers a
6
- * question, hands back a promise, and the panel resolves it when the human answers. No new chat
7
- * status, no pre-execution hook, and nothing in the published `@jxsuite/ai` changes —
8
- * `finishStream` has already set the state to `"idle"` by the time tools run, so a suspended turn
9
- * is idle by construction and this store is what "waiting" means.
4
+ * `ask_user` declares what it asks (`interaction`) and what its answer means (`settle`), and has no
5
+ * body that waits: the invoker answers `needs_input` before any tool code runs, the turn engine
6
+ * puts the question to the assistant's interaction store (`harness/interaction-store.ts`), and the
7
+ * person's answer comes back through `settle` as the call's result (specs/ai.md §3.4 and §3.11).
10
8
  *
11
9
  * Two decisions are load-bearing and easy to get wrong on a rewrite:
12
10
  *
@@ -17,29 +15,16 @@
17
15
  * between them is a 400. The answer travels as the tool result, and the chat view renders it into
18
16
  * the question's own chip.
19
17
  *
20
- * Exactly one question can be outstanding, because `runAgentLoop` executes a round's tool calls
21
- * serially. The guard below is defensive, not load-bearing.
22
- *
23
18
  * @license MIT
24
19
  */
25
20
 
21
+ import { JX_TOOL_FACTS } from "@jxsuite/ai/jx-tools";
26
22
  import { createToolDefinition, toolError, toolSuccess } from "@jxsuite/ai/tools";
27
23
  import { isString } from "@jxsuite/schema/guards";
28
- import { reactive } from "../reactivity";
29
-
30
- import type { ToolRegistry } from "@jxsuite/ai/tools";
31
24
 
32
- /** A question the assistant is waiting on. `id` is the tool-call id, so the chip and this agree. */
33
- export interface PendingAsk {
34
- id: string;
35
- question: string;
36
- /** Short choices rendered as buttons. Free text is always allowed alongside them. */
37
- options: string[];
38
- /** One line of why-I'm-asking, rendered under the question. */
39
- context: string;
40
- }
25
+ import type { InteractionResponse, ToolRegistry } from "@jxsuite/ai/tools";
41
26
 
42
- /** How a question was settled. `answer: null` with `skipped` is the author saying "you decide". */
27
+ /** What a settled question's result carries: the answer, or `null` with `skipped` for "you decide". */
43
28
  export interface AskAnswer {
44
29
  answer: string | null;
45
30
  skipped: boolean;
@@ -48,112 +33,6 @@ export interface AskAnswer {
48
33
  /** Max choices rendered as buttons; beyond this the question wants free text, not a menu. */
49
34
  const MAX_OPTIONS = 6;
50
35
 
51
- const state = reactive<{ pending: PendingAsk | null }>({ pending: null });
52
-
53
- /** The resolver for {@link state.pending}, kept out of the reactive object (it is not render data). */
54
- let settle: ((answer: AskAnswer) => void) | null = null;
55
-
56
- /** Torn down with the question so a stopped turn does not leave a listener on a dead signal. */
57
- let detachAbort: (() => void) | null = null;
58
-
59
- function clear() {
60
- state.pending = null;
61
- settle = null;
62
- detachAbort?.();
63
- detachAbort = null;
64
- }
65
-
66
- /**
67
- * The outstanding question, or null. Reactive — read it inside an `effect()` to repaint on it.
68
- *
69
- * @returns {PendingAsk | null}
70
- */
71
- export function pendingAsk(): PendingAsk | null {
72
- return state.pending;
73
- }
74
-
75
- /** Whether a turn is suspended on the author. The composer's Send becomes Answer on this. */
76
- export function isAwaitingAnswer(): boolean {
77
- return state.pending !== null;
78
- }
79
-
80
- /**
81
- * Register a question and suspend until it is settled.
82
- *
83
- * @param {PendingAsk} ask
84
- * @param {AbortSignal} [signal] - The asking call's signal. Without one the question can still be
85
- * settled by {@link cancelAsk}, but a Stop would leave the turn awaiting a promise nothing could
86
- * resolve.
87
- * @returns {Promise<AskAnswer>} Resolves on an answer, a skip, or the turn being stopped (`{
88
- * answer: null, skipped: false }` — the shape the tool reports as a failure).
89
- */
90
- export function askUser(ask: PendingAsk, signal?: AbortSignal): Promise<AskAnswer> {
91
- return new Promise<AskAnswer>((resolve) => {
92
- state.pending = ask;
93
- settle = resolve;
94
-
95
- if (signal?.aborted) {
96
- clear();
97
- resolve({ answer: null, skipped: false });
98
- return;
99
- }
100
- if (signal) {
101
- const onAbort = () => {
102
- cancelAsk();
103
- };
104
- signal.addEventListener("abort", onAbort, { once: true });
105
- detachAbort = () => signal.removeEventListener("abort", onAbort);
106
- }
107
- });
108
- }
109
-
110
- /**
111
- * Answer the outstanding question.
112
- *
113
- * @param {string} text
114
- * @returns {boolean} False when nothing was pending, so a caller can fall through to a normal send.
115
- */
116
- export function answerAsk(text: string): boolean {
117
- if (!settle) {
118
- return false;
119
- }
120
- const resolve = settle;
121
- clear();
122
- resolve({ answer: text, skipped: false });
123
- return true;
124
- }
125
-
126
- /**
127
- * Decline to answer — "you decide". Settles as a success, so the turn carries on.
128
- *
129
- * @returns {boolean} False when nothing was pending.
130
- */
131
- export function skipAsk(): boolean {
132
- if (!settle) {
133
- return false;
134
- }
135
- const resolve = settle;
136
- clear();
137
- resolve({ answer: null, skipped: true });
138
- return true;
139
- }
140
-
141
- /** Settle an outstanding question as unanswered. Called from `stop()` and from the turn's abort. */
142
- export function cancelAsk(): void {
143
- if (!settle) {
144
- return;
145
- }
146
- const resolve = settle;
147
- clear();
148
- resolve({ answer: null, skipped: false });
149
- }
150
-
151
- /** Drop any outstanding question without settling a turn. For New Chat and for tests. */
152
- export function resetAsk(): void {
153
- cancelAsk();
154
- clear();
155
- }
156
-
157
36
  /**
158
37
  * Register `ask_user` into a tool registry.
159
38
  *
@@ -163,24 +42,12 @@ export function resetAsk(): void {
163
42
  *
164
43
  * @param {Pick<ToolRegistry, "register">} registry
165
44
  */
166
- /** What the host that registers the question tool may hook. */
167
- export interface AskToolOptions {
168
- /**
169
- * Called once a question has been put to the author, before the turn waits on the answer. A host
170
- * saves the conversation here: a turn suspended on a person may wait for as long as they like,
171
- * and a reload in the meantime must still find the question to restore as open (`specs/ai.md`
172
- * §3.4).
173
- */
174
- onPending?: () => void;
175
- }
176
-
177
- export function registerAskTool(
178
- registry: Pick<ToolRegistry, "register">,
179
- { onPending }: AskToolOptions = {},
180
- ): void {
45
+ export function registerAskTool(registry: Pick<ToolRegistry, "register">): void {
181
46
  registry.register(
182
47
  createToolDefinition({
183
48
  name: "ask_user",
49
+ title: "Ask the user",
50
+ annotations: JX_TOOL_FACTS.ask_user.annotations,
184
51
  // It suspends the turn on the author, so a round that only asked spends no work budget.
185
52
  interactive: true,
186
53
  description:
@@ -212,43 +79,32 @@ export function registerAskTool(
212
79
  },
213
80
  required: ["question"],
214
81
  },
215
- execute: async (args, ctx) => {
82
+ interaction(args) {
216
83
  /* Every field is any JSON value, narrowed by a guard rather than by `?.`. The registry's
217
- `validate()` is "a lightweight structural check, not a JSON Schema validator",
218
- `strict: false` skips it entirely, and this tool may be registered into someone else's
219
- registry — so an argument that is a number is a case, not an impossibility. Optional
220
- chaining guards null, not type: `context?.trim()` threw on one. */
84
+ `validate()` is "a lightweight structural check, not a JSON Schema validator", and this
85
+ tool may be registered into someone else's registry, so an argument that is a number is
86
+ a case, not an impossibility. Optional chaining guards null, not type: `context?.trim()`
87
+ threw on one. */
221
88
  const { question, options, context } = args;
222
89
  if (!isString(question) || !question.trim()) {
223
- return toolError('Pass "question" — the question to put to the user.');
224
- }
225
- if (isAwaitingAnswer()) {
226
- return toolError("A question is already waiting for the user.");
90
+ return { refusal: 'Pass "question" — the question to put to the user.' };
227
91
  }
228
92
  const choices = Array.isArray(options)
229
93
  ? options
230
94
  .filter((o): o is string => isString(o) && o.trim().length > 0)
231
95
  .slice(0, MAX_OPTIONS)
232
96
  : [];
233
-
234
- const answering = askUser(
235
- {
236
- context: isString(context) ? context.trim() : "",
237
- /* The tool-call id, so the chip the transcript draws and the question this registers
238
- are the same thing. Without it a restored, permanently unanswered question would
239
- render identically to the live one. */
240
- id: ctx.callId,
241
- options: choices,
242
- question: question.trim(),
243
- },
244
- ctx.signal,
245
- );
246
- // The question is published by now (`askUser` sets it synchronously); the answer is not.
247
- if (isAwaitingAnswer()) {
248
- onPending?.();
249
- }
250
- const { answer, skipped } = await answering;
251
-
97
+ return {
98
+ context: isString(context) ? context.trim() : "",
99
+ kind: "question",
100
+ options: choices,
101
+ question: question.trim(),
102
+ };
103
+ },
104
+ settle(_args, response) {
105
+ /* SAFETY: `ask_user` only ever asks a question, so the response the invoker hands back is
106
+ the answer to one. */
107
+ const { answer, skipped } = response as Extract<InteractionResponse, { kind: "question" }>;
252
108
  if (skipped) {
253
109
  /* A success: the model asked, and "you decide" is an answer. Rendering it as a failure
254
110
  would have the model apologise for having asked a fair question. */
@@ -262,6 +118,8 @@ export function registerAskTool(
262
118
  }
263
119
  return toolSuccess({ answer, skipped: false }, `The user answered: "${answer}"`);
264
120
  },
121
+ // Never reached through the invoker, which settles an interaction instead of executing it.
122
+ execute: () => toolError("ask_user needs a person, and none is available here."),
265
123
  }),
266
124
  );
267
125
  }