@jxsuite/studio 10.4.0 → 10.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (346) hide show
  1. package/data/locales/en/elements.json +434 -0
  2. package/data/webdata.json +116 -139
  3. package/dist/chunks/2020-rqm7qrje.js +4 -0
  4. package/dist/chunks/adapter-cyy8vbcx.js +4 -0
  5. package/dist/chunks/add-new-flow-8cgtejv2.js +4 -0
  6. package/dist/chunks/{cf-account-picker-xb8vvt2h.js → cf-account-picker-v3qd2dhw.js} +3 -3
  7. package/dist/chunks/cf-settings-1fnw874z.js +4 -0
  8. package/dist/chunks/{color-area-h8dbasp1.js → color-area-frd9y9q6.js} +2 -2
  9. package/dist/chunks/color-field-rebpbe33.js +4 -0
  10. package/dist/chunks/{color-slider-nz2mrzky.js → color-slider-6bzmk3s7.js} +2 -2
  11. package/dist/chunks/color-t2bnnj8y.js +4 -0
  12. package/dist/chunks/{combobox-6hs00996.js → combobox-c49x7gf1.js} +3 -3
  13. package/dist/chunks/components-y8yhdd7d.js +4 -0
  14. package/dist/chunks/content-source-9e7qvmsf.js +4 -0
  15. package/dist/chunks/convert-to-repeater-pd2w4z21.js +4 -0
  16. package/dist/chunks/editor-mb9jzdrw.js +4 -0
  17. package/dist/chunks/entry-commands-at0wnx3f.js +4 -0
  18. package/dist/chunks/extension-sections-4z9qq0gh.js +4 -0
  19. package/dist/chunks/file-ops-dtdvjmza.js +4 -0
  20. package/dist/chunks/files-8pf70854.js +4 -0
  21. package/dist/chunks/format-host-fd3m2y2a.js +4 -0
  22. package/dist/chunks/fs-events-hrk29vcv.js +4 -0
  23. package/dist/chunks/grid-open-bh74v82z.js +4 -0
  24. package/dist/chunks/{grid-view-2f03wcgw.js → grid-view-a51mgzmq.js} +17 -17
  25. package/dist/chunks/{icons-gb967qc8.js → icons-qp8fn1k8.js} +2 -2
  26. package/dist/chunks/{iframe-entry-btck0e9s.js → iframe-entry-mpwe5pwk.js} +4 -4
  27. package/dist/chunks/{iframe-entry-2x9bn5ng.js → iframe-entry-msd43x9h.js} +4 -4
  28. package/dist/chunks/iframe-entry-rt8jz5t8.js +5 -0
  29. package/dist/chunks/{iframe-entry-b5qgggyp.js → iframe-entry-v40cr813.js} +4 -4
  30. package/dist/chunks/iframe-host-q0s0wwy3.js +4 -0
  31. package/dist/chunks/{image-bake-4afr4y6a.js → image-bake-q9ajdjsj.js} +3 -3
  32. package/dist/chunks/{image-editor-x7t8jbjn.js → image-editor-ke9std9b.js} +3 -3
  33. package/dist/chunks/index-57ang7ma.js +4 -0
  34. package/dist/chunks/index-eytr7av4.js +17 -0
  35. package/dist/chunks/{index-yjrfeks8.js → index-x495reff.js} +3 -3
  36. package/dist/chunks/{javascript-hy2mws8v.js → javascript-qe34bvaz.js} +3 -3
  37. package/dist/chunks/jsonMode-m8rseh31.js +13 -0
  38. package/dist/chunks/jx-validate-bbd1mary.js +4 -0
  39. package/dist/chunks/layers-panel-wcjtdqsa.js +4 -0
  40. package/dist/chunks/layers-yscpys6q.js +4 -0
  41. package/dist/chunks/{listbox-7gka1h86.js → listbox-tktzq5xj.js} +2 -2
  42. package/dist/chunks/lit-html-qm5wbhqz.js +4 -0
  43. package/dist/chunks/media-library-2hfff88g.js +4 -0
  44. package/dist/chunks/media-open-4pb05cne.js +4 -0
  45. package/dist/chunks/media-picker-bzktmmwr.js +4 -0
  46. package/dist/chunks/menu-xc3cbgsd.js +4 -0
  47. package/dist/chunks/{monaco-binding-a51py33c.js → monaco-binding-7t38arh9.js} +3 -3
  48. package/dist/chunks/monaco-buffer-3vz94c9f.js +4 -0
  49. package/dist/chunks/monaco-setup-fstdew02.js +4 -0
  50. package/dist/chunks/notify-gbdcckrh.js +4 -0
  51. package/dist/chunks/parse-fpdzgk4c.js +4 -0
  52. package/dist/chunks/platform-9g72gdqr.js +4 -0
  53. package/dist/chunks/popover-cnmsav6b.js +4 -0
  54. package/dist/chunks/publish-panel-91x1cb5d.js +4 -0
  55. package/dist/chunks/publish-quick-e2dmf7eg.js +4 -0
  56. package/dist/chunks/publish-workspace-1x8mnzrj.js +4 -0
  57. package/dist/chunks/quick-search-2m4y6p1x.js +4 -0
  58. package/dist/chunks/references-yg1g9rwx.js +4 -0
  59. package/dist/chunks/right-panel-7mfyhzrk.js +4 -0
  60. package/dist/chunks/source-control-rrc4jgp5.js +4 -0
  61. package/dist/chunks/{split-m5rwn31z.js → split-d15yencd.js} +2 -2
  62. package/dist/chunks/studio-0jhhznxy.js +9 -0
  63. package/dist/chunks/studio-0nb57pjg.js +7 -0
  64. package/dist/chunks/{studio-s24mhq0z.js → studio-0wa4wv4s.js} +4 -4
  65. package/dist/chunks/studio-0z8kp4xk.js +5 -0
  66. package/dist/chunks/{studio-g0hxtcga.js → studio-18gc5t3v.js} +4 -4
  67. package/dist/chunks/{studio-4fnb5648.js → studio-1b79rpcb.js} +4 -4
  68. package/dist/chunks/studio-211q5j7m.js +4 -0
  69. package/dist/chunks/studio-213pyfxh.js +16 -0
  70. package/dist/chunks/studio-2b1smjvp.js +5 -0
  71. package/dist/chunks/{studio-nwf0c32p.js → studio-2v6tc9jp.js} +4 -4
  72. package/dist/chunks/studio-30j0wz99.js +5 -0
  73. package/dist/chunks/studio-3kmqhwtt.js +5 -0
  74. package/dist/chunks/studio-44drsmhw.js +5 -0
  75. package/dist/chunks/{studio-2pk7kc7z.js → studio-495b6f2r.js} +4 -4
  76. package/dist/chunks/{studio-jma2q8r9.js → studio-4gthb2fd.js} +4 -4
  77. package/dist/chunks/{studio-0gvvqj3h.js → studio-4xadhxj1.js} +4 -4
  78. package/dist/chunks/{studio-nvcn2ykc.js → studio-52mzbxqm.js} +4 -4
  79. package/dist/chunks/{studio-v7a7yb5w.js → studio-56mmnsw8.js} +7 -7
  80. package/dist/chunks/studio-59hz8f4r.js +5 -0
  81. package/dist/chunks/{studio-sce30r7b.js → studio-5k9mfafh.js} +3 -3
  82. package/dist/chunks/{studio-rr00ex23.js → studio-5qez462c.js} +6 -6
  83. package/dist/chunks/studio-6tnpg0vx.js +5 -0
  84. package/dist/chunks/studio-6w8mt734.js +6 -0
  85. package/dist/chunks/studio-7hm04x5z.js +5 -0
  86. package/dist/chunks/studio-8atmfjjm.js +7 -0
  87. package/dist/chunks/{studio-wmjmsw0p.js → studio-8n99z71s.js} +4 -4
  88. package/dist/chunks/studio-8vmad6x2.js +5 -0
  89. package/dist/chunks/studio-93p5k4r4.js +5 -0
  90. package/dist/chunks/{studio-2x972v58.js → studio-9gb0m81w.js} +4 -4
  91. package/dist/chunks/studio-a4ymg4vd.js +5 -0
  92. package/dist/chunks/studio-anc8067x.js +5 -0
  93. package/dist/chunks/{studio-2e4hry72.js → studio-dbdt67kr.js} +5 -5
  94. package/dist/chunks/studio-dbex091t.js +5 -0
  95. package/dist/chunks/{studio-amaz545p.js → studio-dj8ctxey.js} +4 -4
  96. package/dist/chunks/{studio-k2evngrj.js → studio-ed9bz68j.js} +5 -5
  97. package/dist/chunks/{studio-4bgvvzhv.js → studio-fe2d1fp5.js} +4 -4
  98. package/dist/chunks/{studio-45mj4jqe.js → studio-fgp4cyy3.js} +4 -4
  99. package/dist/chunks/studio-ft706q02.js +5 -0
  100. package/dist/chunks/studio-g79s1dyg.js +6 -0
  101. package/dist/chunks/{studio-pj216fmn.js → studio-gahswxp4.js} +4 -4
  102. package/dist/chunks/studio-gbqc12bk.js +5 -0
  103. package/dist/chunks/studio-gxphbetb.js +6 -0
  104. package/dist/chunks/studio-hdf01j7q.js +7 -0
  105. package/dist/chunks/studio-hf259xzd.js +48 -0
  106. package/dist/chunks/studio-jbva4wxw.js +5 -0
  107. package/dist/chunks/studio-jj9gf1qc.js +5 -0
  108. package/dist/chunks/studio-jsjg3mjv.js +5 -0
  109. package/dist/chunks/studio-jtvb8hd7.js +5 -0
  110. package/dist/chunks/{studio-g9brbewz.js → studio-kp4vzrct.js} +4 -4
  111. package/dist/chunks/studio-kpx02gaa.js +7 -0
  112. package/dist/chunks/{studio-3na1r48j.js → studio-kv1728ex.js} +8 -8
  113. package/dist/chunks/studio-m25ry0mf.js +5 -0
  114. package/dist/chunks/studio-mcbwmkyh.js +5 -0
  115. package/dist/chunks/studio-meajkr51.js +5 -0
  116. package/dist/chunks/studio-mkjrjpr2.js +6 -0
  117. package/dist/chunks/{studio-eqcddvc2.js → studio-mr084886.js} +201 -201
  118. package/dist/chunks/studio-mrb10hv3.js +5 -0
  119. package/dist/chunks/{studio-0939t1q1.js → studio-nhpg405j.js} +4 -4
  120. package/dist/chunks/studio-nsec0hxm.js +64 -0
  121. package/dist/chunks/{studio-xx3r3sb0.js → studio-nv0mh0p5.js} +4 -4
  122. package/dist/chunks/studio-pafmb06w.js +5 -0
  123. package/dist/chunks/{studio-pm17wdc4.js → studio-pgftd79b.js} +4 -4
  124. package/dist/chunks/{studio-4zd6an2b.js → studio-pk961xgz.js} +4 -4
  125. package/dist/chunks/studio-pzbr7g05.js +5 -0
  126. package/dist/chunks/studio-qcc5hsw2.js +10 -0
  127. package/dist/chunks/studio-qfq58c8n.js +5 -0
  128. package/dist/chunks/studio-qvb2jr83.js +5 -0
  129. package/dist/chunks/studio-r85zw6ay.js +5 -0
  130. package/dist/chunks/studio-s0kwce5w.js +9 -0
  131. package/dist/chunks/studio-s6ymkf3q.js +14 -0
  132. package/dist/chunks/{studio-0x6f3nk9.js → studio-s7p5ccxa.js} +4 -4
  133. package/dist/chunks/{studio-1410q5dw.js → studio-s9jjap3x.js} +4 -4
  134. package/dist/chunks/{studio-evq47j2z.js → studio-t5hde1ws.js} +4 -4
  135. package/dist/chunks/studio-vj9yv897.js +5 -0
  136. package/dist/chunks/studio-wmak8xjw.js +5 -0
  137. package/dist/chunks/{studio-bnc50f9h.js → studio-wqsmvtqj.js} +4 -4
  138. package/dist/chunks/studio-x3z3rhej.js +5 -0
  139. package/dist/chunks/{studio-bg8pzprg.js → studio-xb55kpmy.js} +4 -4
  140. package/dist/chunks/studio-xxtdajmv.js +12 -0
  141. package/dist/chunks/studio-xxttcym1.js +5 -0
  142. package/dist/chunks/{studio-90zkgne5.js → studio-y2zsnzye.js} +7 -7
  143. package/dist/chunks/{studio-3qr08sxq.js → studio-y3tjh2xb.js} +3 -3
  144. package/dist/chunks/studio-y4f1rdsj.js +5 -0
  145. package/dist/chunks/studio-ybkd5w8k.js +5 -0
  146. package/dist/chunks/{studio-tpxcryf2.js → studio-yetr4gg1.js} +4 -4
  147. package/dist/chunks/studio-z295anhs.js +6 -0
  148. package/dist/chunks/studio-z89e6wpg.js +9 -0
  149. package/dist/chunks/{studio-5tet77de.js → studio-zbwr6prc.js} +3 -3
  150. package/dist/chunks/{studio-daksw8kh.js → studio-zdbsv5q2.js} +4 -4
  151. package/dist/chunks/{studio-abh9kfdh.js → studio-zvthj8hg.js} +4 -4
  152. package/dist/chunks/toast-2kn2vrn4.js +4 -0
  153. package/dist/chunks/{toast-host-g3r2s0kn.js → toast-host-3rxeyj3d.js} +3 -3
  154. package/dist/chunks/{tooltip-dj1pvgz7.js → tooltip-ghkxkasp.js} +3 -3
  155. package/dist/chunks/{tree-j0ebwr5s.js → tree-vw4nc63s.js} +2 -2
  156. package/dist/chunks/tsMode-7gngmamr.js +14 -0
  157. package/dist/chunks/workspace-gv0hxgsm.js +4 -0
  158. package/dist/chunks/workspace-home-j7e0mjy5.js +4 -0
  159. package/dist/chunks/{ws-client-arz2qsy9.js → ws-client-vs11sxc5.js} +3 -3
  160. package/dist/iframe-entry.js +14 -14
  161. package/dist/manifest.json +84 -83
  162. package/dist/studio.js +99 -98
  163. package/dist/workers/ts.worker.js +3 -3
  164. package/package.json +12 -12
  165. package/src/browse/library-layouts.ts +9 -12
  166. package/src/browse/library-pane.ts +22 -16
  167. package/src/browse/library-source.ts +3 -4
  168. package/src/browse/preview-style.ts +2 -1
  169. package/src/canvas/canvas-live-render.ts +2 -5
  170. package/src/canvas/canvas-page-context.ts +5 -0
  171. package/src/canvas/canvas-render.ts +6 -1
  172. package/src/canvas/iframe-drop.ts +84 -1
  173. package/src/canvas/iframe-entry.ts +9 -6
  174. package/src/canvas/iframe-render.ts +13 -0
  175. package/src/collab/collab-session.ts +2 -2
  176. package/src/commands/levels.ts +1 -1
  177. package/src/content/entry-commands.ts +32 -27
  178. package/src/content/entry-editor.ts +46 -44
  179. package/src/content/entry-inspector.ts +15 -12
  180. package/src/content/entry-model.ts +3 -3
  181. package/src/content/entry-summaries.ts +5 -5
  182. package/src/content/hold-state.ts +172 -0
  183. package/src/content/page-summaries.ts +10 -14
  184. package/src/create/add-new-flow.ts +2 -1
  185. package/src/editor/convert-targets.ts +2 -23
  186. package/src/editor/inline-edit.ts +6 -1
  187. package/src/editor/repeater-scope.ts +333 -74
  188. package/src/editor/slash-menu.ts +35 -17
  189. package/src/files/files.ts +18 -0
  190. package/src/format/format-choices.ts +5 -5
  191. package/src/format/format-host.ts +35 -3
  192. package/src/grid/redirects-grid.ts +4 -4
  193. package/src/grid/sources/content-source.ts +42 -66
  194. package/src/locale/element-names.ts +131 -0
  195. package/src/panels/block-action-bar.ts +3 -4
  196. package/src/panels/dnd.ts +3 -1
  197. package/src/panels/elements-panel.ts +93 -36
  198. package/src/panels/events-panel.ts +6 -2
  199. package/src/panels/formula-workspace.ts +9 -0
  200. package/src/panels/imports-panel.ts +3 -2
  201. package/src/panels/navigator-panels.ts +10 -10
  202. package/src/panels/panel-registry.ts +3 -3
  203. package/src/panels/properties-panel.ts +127 -35
  204. package/src/panels/site-panel.ts +2 -1
  205. package/src/panels/statement-editor.ts +4 -0
  206. package/src/panels/style-panel.ts +2 -1
  207. package/src/panels/styles-panel.ts +2 -2
  208. package/src/panels/tab-strip.ts +29 -26
  209. package/src/platforms/cloud.ts +5 -2
  210. package/src/services/ai-command-tools.ts +2 -1
  211. package/src/services/ai-project-tools.ts +21 -8
  212. package/src/services/ai-tools.ts +5 -5
  213. package/src/services/ai-write-report.ts +22 -4
  214. package/src/services/context-manager.ts +110 -2
  215. package/src/services/document-assistant.ts +2 -2
  216. package/src/services/harness/chat-reducer.ts +9 -4
  217. package/src/services/jxn-language.ts +110 -0
  218. package/src/services/model-uri.ts +3 -0
  219. package/src/services/monaco-setup.ts +6 -2
  220. package/src/services/render-critic.ts +234 -12
  221. package/src/services/tool-executor.ts +16 -8
  222. package/src/settings/contributed-section.ts +2 -1
  223. package/src/settings/extension-commands.ts +2 -1
  224. package/src/settings/general-settings.ts +2 -2
  225. package/src/settings/settings-document.ts +5 -5
  226. package/src/shell.ts +1 -1
  227. package/src/studio.ts +28 -11
  228. package/src/style/variables-view.ts +2 -2
  229. package/src/surfaces/collections-panel.json +1 -1
  230. package/src/surfaces/collections-panel.ts +1 -1
  231. package/src/surfaces/entry-editor.json +10 -10
  232. package/src/surfaces/entry-editor.ts +22 -19
  233. package/src/surfaces/entry-inspector.json +11 -11
  234. package/src/surfaces/entry-inspector.ts +17 -10
  235. package/src/surfaces/panel-elements.json +466 -206
  236. package/src/surfaces/panel-elements.ts +61 -25
  237. package/src/surfaces/tab-strip.json +5 -5
  238. package/src/surfaces/tab-strip.ts +2 -2
  239. package/src/tabs/project-config.ts +13 -13
  240. package/src/tabs/tab.ts +7 -2
  241. package/src/types.ts +13 -2
  242. package/src/ui/expression-editor.ts +10 -5
  243. package/src/ui/schema-form.ts +18 -1
  244. package/src/utils/edit-display.ts +27 -1
  245. package/src/view.ts +9 -2
  246. package/src/workspace/tab-workspace.ts +2 -2
  247. package/src/workspace/workspace-back.ts +2 -2
  248. package/dist/chunks/2020-906vx2dq.js +0 -4
  249. package/dist/chunks/adapter-hnvad61x.js +0 -4
  250. package/dist/chunks/add-new-flow-v0pm96mr.js +0 -4
  251. package/dist/chunks/cf-settings-mkbcz4z2.js +0 -4
  252. package/dist/chunks/color-04361ttk.js +0 -4
  253. package/dist/chunks/color-field-qv5r2b16.js +0 -4
  254. package/dist/chunks/components-w5zqwva1.js +0 -4
  255. package/dist/chunks/content-source-g38w0d3f.js +0 -4
  256. package/dist/chunks/convert-to-repeater-r8sfxjjm.js +0 -4
  257. package/dist/chunks/editor-wvf5e6j9.js +0 -4
  258. package/dist/chunks/entry-commands-t13dyw3c.js +0 -4
  259. package/dist/chunks/extension-sections-vjzw46xb.js +0 -4
  260. package/dist/chunks/file-ops-1sghzgcr.js +0 -4
  261. package/dist/chunks/files-zrqtgt41.js +0 -4
  262. package/dist/chunks/format-host-9gy76erh.js +0 -4
  263. package/dist/chunks/fs-events-45rhg3me.js +0 -4
  264. package/dist/chunks/grid-open-t4fq6bbm.js +0 -4
  265. package/dist/chunks/iframe-entry-42hpmwz5.js +0 -5
  266. package/dist/chunks/iframe-host-9aq8ze3q.js +0 -4
  267. package/dist/chunks/index-f9qr7kzh.js +0 -4
  268. package/dist/chunks/jsonMode-vj30te2h.js +0 -13
  269. package/dist/chunks/jx-validate-r4ax11e3.js +0 -4
  270. package/dist/chunks/layers-m49nmwvx.js +0 -4
  271. package/dist/chunks/layers-panel-zr92pwdp.js +0 -4
  272. package/dist/chunks/lit-html-k2mb9k0r.js +0 -4
  273. package/dist/chunks/media-library-j74rt0w4.js +0 -4
  274. package/dist/chunks/media-open-mypr0k6b.js +0 -4
  275. package/dist/chunks/media-picker-pemtbsjv.js +0 -4
  276. package/dist/chunks/menu-d2jxpgma.js +0 -4
  277. package/dist/chunks/monaco-buffer-pa0g6ax7.js +0 -4
  278. package/dist/chunks/monaco-setup-0hw9f835.js +0 -4
  279. package/dist/chunks/notify-bwehrp7q.js +0 -4
  280. package/dist/chunks/parse-hee7ne9b.js +0 -4
  281. package/dist/chunks/platform-kv0k65cy.js +0 -4
  282. package/dist/chunks/popover-9tf4cjrk.js +0 -4
  283. package/dist/chunks/publish-panel-vg1n880b.js +0 -4
  284. package/dist/chunks/publish-quick-4fwngwrw.js +0 -4
  285. package/dist/chunks/publish-workspace-jv4w2bx2.js +0 -4
  286. package/dist/chunks/quick-search-nh29rt8a.js +0 -4
  287. package/dist/chunks/references-vqyr5s9x.js +0 -4
  288. package/dist/chunks/right-panel-3ws9f2w0.js +0 -4
  289. package/dist/chunks/source-control-h3hcs5gy.js +0 -4
  290. package/dist/chunks/studio-0khd8xtx.js +0 -4
  291. package/dist/chunks/studio-1ctnw3xv.js +0 -5
  292. package/dist/chunks/studio-1ks5d9y4.js +0 -5
  293. package/dist/chunks/studio-1y9mab9m.js +0 -5
  294. package/dist/chunks/studio-2kpxm0eh.js +0 -7
  295. package/dist/chunks/studio-2zz4qbv9.js +0 -5
  296. package/dist/chunks/studio-3pz4t5a2.js +0 -5
  297. package/dist/chunks/studio-4f7thq0p.js +0 -5
  298. package/dist/chunks/studio-61rg2cz5.js +0 -5
  299. package/dist/chunks/studio-7rrdbvex.js +0 -5
  300. package/dist/chunks/studio-9c6cbq2c.js +0 -4
  301. package/dist/chunks/studio-9dq7rwc7.js +0 -5
  302. package/dist/chunks/studio-9gr7xs9r.js +0 -6
  303. package/dist/chunks/studio-a88ej93p.js +0 -7
  304. package/dist/chunks/studio-acer0g8w.js +0 -5
  305. package/dist/chunks/studio-az3wvyh2.js +0 -5
  306. package/dist/chunks/studio-brh8d2pr.js +0 -5
  307. package/dist/chunks/studio-c2fa783p.js +0 -9
  308. package/dist/chunks/studio-cfnn70h1.js +0 -5
  309. package/dist/chunks/studio-cqxj4fcy.js +0 -5
  310. package/dist/chunks/studio-cw11h078.js +0 -5
  311. package/dist/chunks/studio-dn56z7sn.js +0 -5
  312. package/dist/chunks/studio-egnjpyde.js +0 -9
  313. package/dist/chunks/studio-ejrypvmq.js +0 -5
  314. package/dist/chunks/studio-frc16j8v.js +0 -10
  315. package/dist/chunks/studio-gnazwtv3.js +0 -5
  316. package/dist/chunks/studio-hfqpfmpk.js +0 -5
  317. package/dist/chunks/studio-hh394phe.js +0 -5
  318. package/dist/chunks/studio-j0zf5x2x.js +0 -5
  319. package/dist/chunks/studio-j5fcp6gj.js +0 -5
  320. package/dist/chunks/studio-jd1qb1st.js +0 -7
  321. package/dist/chunks/studio-k0xzrse2.js +0 -64
  322. package/dist/chunks/studio-k1xqrgm5.js +0 -5
  323. package/dist/chunks/studio-k3we3kqb.js +0 -5
  324. package/dist/chunks/studio-knsjyx7n.js +0 -5
  325. package/dist/chunks/studio-mrjtnm62.js +0 -5
  326. package/dist/chunks/studio-n9qba0gx.js +0 -5
  327. package/dist/chunks/studio-nzemfbkk.js +0 -14
  328. package/dist/chunks/studio-pwctzka3.js +0 -16
  329. package/dist/chunks/studio-q80fg1d6.js +0 -12
  330. package/dist/chunks/studio-q9hx2qp0.js +0 -5
  331. package/dist/chunks/studio-qrzmfaf6.js +0 -5
  332. package/dist/chunks/studio-r6mx7vm5.js +0 -7
  333. package/dist/chunks/studio-swedpd82.js +0 -5
  334. package/dist/chunks/studio-tmfcn347.js +0 -6
  335. package/dist/chunks/studio-v5wktj6f.js +0 -5
  336. package/dist/chunks/studio-vpevkfxw.js +0 -5
  337. package/dist/chunks/studio-wqfaqw4m.js +0 -5
  338. package/dist/chunks/studio-xpx91fby.js +0 -6
  339. package/dist/chunks/studio-xva3hzkv.js +0 -5
  340. package/dist/chunks/studio-z3fztxrp.js +0 -9
  341. package/dist/chunks/studio-z9453ypn.js +0 -6
  342. package/dist/chunks/toast-w11qpkyz.js +0 -4
  343. package/dist/chunks/tsMode-jg1dtphj.js +0 -14
  344. package/dist/chunks/workspace-6ztqcrsh.js +0 -4
  345. package/dist/chunks/workspace-home-rse9h8a0.js +0 -4
  346. package/src/content/draft-state.ts +0 -108
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * What the Content workspace's Pages list says about each page beyond its file: the template it is
3
- * drawn in, the collection a dynamic page expands over, and whether it is marked a draft
4
- * (`specs/studio.md` §22.5, the Content-Pages board: Title, URL, Template, Status, Edited).
3
+ * drawn in, the collection a dynamic page expands over (`specs/studio.md` §22.5, the Content-Pages
4
+ * board: Title, URL, Template, Status, Edited).
5
5
  *
6
- * The scan behind the Content home knows each page's FILE; its `$layout`, `$paths` and `draft` are
7
- * inside it. So this reads each page once, through the same parse the editor uses, and keeps the
8
- * answer until a file event drops it ({@link invalidatePageSummaries}, wired beside the
9
- * template-page index in `studio.ts`). The read never rejects: a page that cannot be read states
10
- * nothing, and is listed by its route alone.
6
+ * The scan behind the Content home knows each page's FILE; its `$layout` and `$paths` are inside
7
+ * it. So this reads each page once, through the same parse the editor uses, and keeps the answer
8
+ * until a file event drops it ({@link invalidatePageSummaries}, wired beside the template-page
9
+ * index in `studio.ts`). The read never rejects: a page that cannot be read states nothing, and is
10
+ * listed by its route alone.
11
11
  *
12
12
  * @docs studio/projects/browse
13
13
  */
@@ -15,7 +15,6 @@
15
15
  import { isNonNullObject } from "@jxsuite/schema/guards";
16
16
  import { getPlatform, hasPlatform } from "../platform";
17
17
  import { pageFactsOf } from "./collection-pages";
18
- import { DRAFT_FIELD } from "./draft-state";
19
18
  import type { FormatFrontmatter } from "../format/format-host";
20
19
  import type { JxMutableNode } from "@jxsuite/schema/types";
21
20
 
@@ -26,13 +25,11 @@ export interface PageSummary {
26
25
  layout: string | false | undefined;
27
26
  /** The collection its `$paths` expands over, or `null` for a page of its own. */
28
27
  contentType: string | null;
29
- /** Whether the page carries `draft: true`, the one draft marker a page can have. */
30
- draft: boolean;
31
28
  }
32
29
 
33
30
  /** What a page that could not be read states: nothing. */
34
31
  function unread(path: string): PageSummary {
35
- return { contentType: null, draft: false, layout: undefined, path };
32
+ return { contentType: null, layout: undefined, path };
36
33
  }
37
34
 
38
35
  /** The summary a parsed page states. Exported so the rule is assertable without a platform. */
@@ -42,8 +39,7 @@ export function pageSummaryOf(
42
39
  frontmatter: FormatFrontmatter | null,
43
40
  ): PageSummary {
44
41
  const { contentType, layout } = pageFactsOf(document, frontmatter);
45
- const draft = frontmatter?.[DRAFT_FIELD] === true || document?.[DRAFT_FIELD] === true;
46
- return { contentType, draft, layout, path };
42
+ return { contentType, layout, path };
47
43
  }
48
44
 
49
45
  /** Read one page. */
@@ -61,7 +57,7 @@ async function readSummary(path: string): Promise<PageSummary> {
61
57
  const { parseSourceForPath } = await import("../files/file-ops");
62
58
  const parsed = await parseSourceForPath(path, source);
63
59
  /* SAFETY: a format class's parser returns its file as a Jx element tree (the format contract);
64
- only `$layout`, `$paths` and `draft` are read off it. */
60
+ only `$layout` and `$paths` are read off it. */
65
61
  return pageSummaryOf(path, parsed.document as JxMutableNode, parsed.frontmatter);
66
62
  } catch {
67
63
  return unread(path);
@@ -101,6 +101,7 @@ import type {
101
101
  AddNewView,
102
102
  } from "../surfaces/add-new";
103
103
  import type { JsonObject, JxMutableNode } from "@jxsuite/schema/types";
104
+ import { projectConfigPath } from "../tabs/tab";
104
105
 
105
106
  /** The `source` every Problem this flow files carries. */
106
107
  const SOURCE = "Add new";
@@ -385,7 +386,7 @@ async function performCollection(creation: Creation): Promise<AddNewResult | nul
385
386
  if (!committed.ok) {
386
387
  return null;
387
388
  }
388
- const wrote = ["project.json"];
389
+ const wrote = [projectConfigPath()];
389
390
  const missed: string[] = [];
390
391
  for (const write of [{ body: creation.content, path: creation.path }, ...creation.extraWrites]) {
391
392
  try {
@@ -1,32 +1,11 @@
1
1
  import elementsMeta from "../../data/elements-meta.json";
2
2
 
3
+ import { elementName } from "../locale/element-names";
3
4
  import { ELEMENT_DEFS } from "./editable-tags";
4
5
  import type { SlashCommand } from "./inline-edit";
5
6
 
6
7
  export type { SlashCommand } from "./inline-edit";
7
8
 
8
- const TAG_LABELS = new Map<string, string>([
9
- ["article", "Article"],
10
- ["aside", "Aside"],
11
- ["blockquote", "Blockquote"],
12
- ["div", "Div"],
13
- ["footer", "Footer"],
14
- ["h1", "Heading 1"],
15
- ["h2", "Heading 2"],
16
- ["h3", "Heading 3"],
17
- ["h4", "Heading 4"],
18
- ["h5", "Heading 5"],
19
- ["h6", "Heading 6"],
20
- ["header", "Header"],
21
- ["main", "Main"],
22
- ["nav", "Nav"],
23
- ["ol", "Numbered List"],
24
- ["p", "Paragraph"],
25
- ["search", "Search"],
26
- ["section", "Section"],
27
- ["ul", "Bulleted List"],
28
- ]);
29
-
30
9
  const groups: Readonly<Record<string, string[]>> = elementsMeta.$convertGroups;
31
10
 
32
11
  /**
@@ -56,7 +35,7 @@ export function getConvertTargets(currentTag: string, isEmpty: boolean): SlashCo
56
35
 
57
36
  return [...tags].map((tag) => ({
58
37
  description: "",
59
- label: TAG_LABELS.get(tag) || tag.charAt(0).toUpperCase() + tag.slice(1),
38
+ label: elementName(tag),
60
39
  tag,
61
40
  }));
62
41
  }
@@ -234,7 +234,12 @@ export function setEditableVerdicts(verdicts: EditableVerdicts): void {
234
234
  * {@link file://./editable-tags.ts}.
235
235
  */
236
236
  export function isEditableBlock(el: HTMLElement) {
237
- return isEditableTag(el.tagName, editableVerdicts);
237
+ /* Closed explicitly, whatever its tag. Edit mode closes the label it draws for computed children
238
+ (`prepareForEditMode`), because activating that label commits its words as the element's
239
+ `textContent` when the caret leaves, and the binding is gone without a key being pressed. */
240
+ return (
241
+ el.getAttribute("contenteditable") !== "false" && isEditableTag(el.tagName, editableVerdicts)
242
+ );
238
243
  }
239
244
 
240
245
  /**
@@ -1,6 +1,12 @@
1
1
  /**
2
- * Repeater scope resolution — enumerate the local `item` / `index` / field tokens available when
3
- * the author inline-edits text INSIDE a repeater (`$prototype: "Array"`) on the design canvas.
2
+ * Repeater scope resolution: what the item of a repeater (`$prototype: "Array"`) holds, deduced
3
+ * from the document and the project without running either.
4
+ *
5
+ * Two surfaces ask. Inline text edited INSIDE a repeater inserts a `${item.data.title}` token, and
6
+ * the `From data…` rung binds a `$map/item/data/title` pointer. They are the same field spelled for
7
+ * two grammars, so there is one walk and each field carries both spellings. A third surface asks
8
+ * for the one field that is neither text nor a value: `$children`, which an element draws by
9
+ * holding it as computed children (spec.md §8.4).
4
10
  *
5
11
  * There is no live `$map` in edit mode: `prepareForEditMode` renders a mapped array as a
6
12
  * `repeater-perimeter` wrapping one glyph-ified template instance, so `renderMappedArrayInto`
@@ -9,23 +15,30 @@
9
15
  * document path (which carries a real `"map"` segment — see canvas/path-mapping.ts
10
16
  * `classifyRenderNode`).
11
17
  *
12
- * Pure and DOM-free: deps limited to `@jxsuite/schema` guards/types + {@link getNodeAtPath}.
18
+ * Pure and DOM-free: deps limited to `@jxsuite/schema` guards/types, the runtime's pointer grammar
19
+ *
20
+ * - {@link getNodeAtPath}.
21
+ *
22
+ * @docs studio/design/repeaters
13
23
  */
14
24
 
25
+ import { escapeToken, refSegments } from "@jxsuite/runtime/pointer";
15
26
  import { getNodeAtPath } from "@jxsuite/schema/doc-ops";
16
27
  import {
17
28
  isJsonObject,
18
29
  isMappedArray,
19
- isNonNullObject,
20
30
  isRef,
31
+ isSchemaOnlyDef,
21
32
  isString,
22
33
  } from "@jxsuite/schema/guards";
23
34
 
24
35
  import type { JxPath } from "../state";
25
36
  import type {
37
+ JsonObject,
38
+ JsonValue,
39
+ JxDocumentValue,
26
40
  JxMappedArray,
27
41
  JxMutableNode,
28
- JxStateDefinition,
29
42
  ProjectConfig,
30
43
  } from "@jxsuite/schema/types";
31
44
 
@@ -58,93 +71,339 @@ export function findEnclosingRepeater(
58
71
  return isMappedArray(node) ? node : null;
59
72
  }
60
73
 
61
- /** Strip a `#/state/<name>` JSON-Pointer ref to its bare state name (null when it is not one). */
62
- function refToStateName(binding: JxMappedArray["items"]): string | null {
74
+ /** One field of a repeater's item, in each grammar that can name it. */
75
+ export interface RepeaterField {
76
+ /** The members walked from the item, unescaped. */
77
+ path: string[];
78
+ /** What a template reads and a person is shown: `item.data.title`. */
79
+ token: string;
80
+ /** What a `$ref` holds: `$map/item/data/title`, with each member escaped (RFC 6901 §3). */
81
+ pointer: string;
82
+ /** Whether the field holds a list, which is all a nested repeater's Items can read. */
83
+ isArray: boolean;
84
+ /** Whether the field holds child nodes, which only an element's computed children can draw. */
85
+ holdsNodes: boolean;
86
+ }
87
+
88
+ /** The one `$`-prefixed member an item offers: the Jx tree a parsed body came out as. */
89
+ const NODES_MEMBER = "$children";
90
+
91
+ /** The content format whose loader parses an entry's body into {@link NODES_MEMBER}. */
92
+ const NODES_FORMAT = "Markdown";
93
+
94
+ /** What every step of the walk reads from. */
95
+ interface ItemSources {
96
+ doc: JxMutableNode;
97
+ config: ProjectConfig | null | undefined;
98
+ }
99
+
100
+ /** Marks a schema as an entry of this content type, so a relationship cycle can be stopped. */
101
+ const ENTRY_TYPE = "$entryType";
102
+
103
+ /** Marks an entry's `data` member, which is a level of the path rather than a field to offer. */
104
+ const ENTRY_DATA = "$entryData";
105
+
106
+ /** How many members a sample list contributes to the shape it implies. */
107
+ const SAMPLE_CAP = 20;
108
+
109
+ /** How many members deep a field path may run, so a walk of a deep schema stays a short list. */
110
+ const DEPTH_CAP = 6;
111
+
112
+ /** How many pointers a `$ref` chain may follow, so `a → b → a` ends. */
113
+ const HOP_CAP = 8;
114
+
115
+ /** The shape of what a content type's loader hands back (`ContentLoaderEntry`). */
116
+ function entrySchema(contentType: string, ctx: ItemSources): JsonObject {
117
+ /* The `content` section is contributed by an extension and absorbed by the project config's index
118
+ signature, so each level is checked before it is read. */
119
+ const content: unknown = ctx.config?.content;
120
+ const type = isJsonObject(content) ? content[contentType] : undefined;
121
+ const data = isJsonObject(type) && isJsonObject(type.schema) ? type.schema : {};
122
+ // A CSV row has no body to parse. A type naming no format is not known to lack one.
123
+ const format = isJsonObject(type) ? type.format : undefined;
124
+ const hasNodes = !isString(format) || format === NODES_FORMAT;
125
+ // Built from entries because the order is what the picker lists: id and body, then the fields.
126
+ const members: [string, JsonValue][] = [
127
+ ["id", {}],
128
+ ["body", {}],
129
+ ...(hasNodes ? ([[NODES_MEMBER, {}]] satisfies [string, JsonValue][]) : []),
130
+ ["data", { ...data, [ENTRY_DATA]: true }],
131
+ ];
132
+ return { [ENTRY_TYPE]: contentType, properties: Object.fromEntries(members) };
133
+ }
134
+
135
+ /** Whether a schema's own keys say what is inside it, so a sample has nothing to add. */
136
+ function describesFields(schema: JsonObject | null, ctx: ItemSources): boolean {
137
+ return (
138
+ isJsonObject(schema?.properties) || isJsonObject(followPointers(schema?.items, ctx)?.properties)
139
+ );
140
+ }
141
+
142
+ /**
143
+ * Follow a schema's pointers to the schema they name, or `null` for one this walk cannot read.
144
+ *
145
+ * Three spellings reach another schema: `{ "$ref": "#/content/authors" }` is a relationship, whose
146
+ * value the loader resolves to the full entry; `{ "$ref": "#/$defs/Member" }` is a shared type; and
147
+ * `{ "type": { "$ref": … } }` is a typed value naming one.
148
+ */
149
+ function followPointers(
150
+ schema: JxDocumentValue | undefined,
151
+ ctx: ItemSources,
152
+ hops = 0,
153
+ ): JsonObject | null {
154
+ if (!isJsonObject(schema) || hops > HOP_CAP) {
155
+ return null;
156
+ }
157
+ const pointer = isRef(schema) ? schema.$ref : isRef(schema.type) ? schema.type.$ref : null;
158
+ if (pointer === null) {
159
+ return schema;
160
+ }
161
+ if (pointer.startsWith("#/content/")) {
162
+ return entrySchema(pointer.slice("#/content/".length), ctx);
163
+ }
164
+ if (pointer.startsWith("#/$defs/")) {
165
+ const [name, ...rest] = refSegments(pointer.slice("#/$defs/".length));
166
+ const projectDefs: unknown = ctx.config?.$defs;
167
+ const declared =
168
+ name === undefined
169
+ ? undefined
170
+ : (ctx.doc.$defs?.[name] ?? (isJsonObject(projectDefs) ? projectDefs[name] : undefined));
171
+ return walkSchema(followPointers(declared, ctx, hops + 1), rest, ctx);
172
+ }
173
+ return null;
174
+ }
175
+
176
+ /** The union of two implied shapes, so an optional field the first row lacks is still found. */
177
+ function mergeSamples(a: JsonObject, b: JsonObject): JsonObject {
178
+ const merged: JsonObject = { ...a, ...b };
179
+ if (isJsonObject(a.items) && isJsonObject(b.items)) {
180
+ merged.items = mergeSamples(a.items, b.items);
181
+ }
182
+ if (isJsonObject(a.properties) && isJsonObject(b.properties)) {
183
+ const properties: JsonObject = { ...a.properties, ...b.properties };
184
+ for (const [key, left] of Object.entries(a.properties)) {
185
+ const right = b.properties[key];
186
+ if (isJsonObject(left) && isJsonObject(right)) {
187
+ properties[key] = mergeSamples(left, right);
188
+ }
189
+ }
190
+ merged.properties = properties;
191
+ }
192
+ return merged;
193
+ }
194
+
195
+ /** The shape a sample value implies. */
196
+ function sampleSchema(value: JsonValue | undefined): JsonObject {
197
+ if (Array.isArray(value)) {
198
+ let items: JsonObject = {};
199
+ for (const member of value.slice(0, SAMPLE_CAP)) {
200
+ items = mergeSamples(items, sampleSchema(member));
201
+ }
202
+ return { items };
203
+ }
204
+ if (isJsonObject(value)) {
205
+ return {
206
+ properties: Object.fromEntries(
207
+ Object.entries(value).map(([key, member]) => [key, sampleSchema(member)]),
208
+ ),
209
+ };
210
+ }
211
+ return {};
212
+ }
213
+
214
+ /** The shape of a state entry's value, or `null` when only running it would say. */
215
+ function stateSchema(def: JxDocumentValue | undefined, ctx: ItemSources): JsonObject | null {
216
+ if (Array.isArray(def)) {
217
+ // SAFETY: a naked array state entry is the JSON value it reads as (schema/types `JxStateDefinition`).
218
+ return sampleSchema(def as JsonValue[]);
219
+ }
220
+ if (!isJsonObject(def)) {
221
+ return null;
222
+ }
223
+ /* Detected on the `contentType` string, not the prototype's name, so an import-aliased
224
+ `$prototype` still matches. An `id` is what asks for one entry rather than the list. */
225
+ if (isString(def.contentType)) {
226
+ const entry = entrySchema(def.contentType, ctx);
227
+ return def.$prototype === "ContentEntry" || def.id !== undefined ? entry : { items: entry };
228
+ }
229
+ const declared = followPointers(def, ctx);
230
+ if (describesFields(declared, ctx)) {
231
+ return declared;
232
+ }
233
+ if ("default" in def) {
234
+ return sampleSchema(def.default);
235
+ }
236
+ const isNakedObject = !isSchemaOnlyDef(def) && !Object.keys(def).some((k) => k.startsWith("$"));
237
+ return isNakedObject ? sampleSchema(def) : declared;
238
+ }
239
+
240
+ /** Follow members down a schema, or `null` where the schema stops saying. */
241
+ function walkSchema(
242
+ schema: JsonObject | null,
243
+ segments: readonly string[],
244
+ ctx: ItemSources,
245
+ ): JsonObject | null {
246
+ let current = followPointers(schema, ctx);
247
+ for (const segment of segments) {
248
+ const member = isJsonObject(current?.properties) ? current.properties[segment] : undefined;
249
+ current = followPointers(member ?? (/^\d+$/.test(segment) ? current?.items : undefined), ctx);
250
+ }
251
+ return current;
252
+ }
253
+
254
+ /** The shape of what a repeater's `items` binding reads, in the scope of the repeater itself. */
255
+ function bindingSchema(
256
+ binding: JxMappedArray["items"],
257
+ repeaterPath: JxPath,
258
+ ctx: ItemSources,
259
+ ): JsonObject | null {
260
+ if (Array.isArray(binding)) {
261
+ // SAFETY: an inline `items` list is read straight out of the JSON document.
262
+ return sampleSchema(binding as JsonValue[]);
263
+ }
63
264
  if (!isRef(binding)) {
64
265
  return null;
65
266
  }
66
- const { $ref: target } = binding;
67
- return target.startsWith("#/state/") ? target.slice("#/state/".length) : null;
267
+ const ref = binding.$ref.replace(/^#\/\$map\//, "$map/");
268
+ if (ref.startsWith("#/state/")) {
269
+ const [name, ...rest] = refSegments(ref.slice("#/state/".length));
270
+ const def = name === undefined ? undefined : ctx.doc.state?.[name];
271
+ return walkSchema(stateSchema(def, ctx), rest, ctx);
272
+ }
273
+ // A nested repeater reads a member of the repeater around it, so the walk starts from that item.
274
+ if (ref === "$map/item" || ref.startsWith("$map/item/")) {
275
+ const rest = refSegments(ref.slice("$map/item/".length));
276
+ return walkSchema(itemSchemaAt(repeaterPath, ctx), rest, ctx);
277
+ }
278
+ return null;
279
+ }
280
+
281
+ /** The shape of the item in scope at `path`, or `null` outside a repeater or past what is known. */
282
+ function itemSchemaAt(path: JxPath, ctx: ItemSources): JsonObject | null {
283
+ const repeater = findEnclosingRepeater(ctx.doc, path);
284
+ if (!repeater) {
285
+ return null;
286
+ }
287
+ const list = bindingSchema(repeater.items, path.slice(0, path.lastIndexOf("map")), ctx);
288
+ return followPointers(list?.items, ctx);
68
289
  }
69
290
 
70
- /** Cap + $-filter a field-name list to a stable, author-facing subset. */
71
- function safeFields(names: string[]): string[] {
72
- return names.filter((k) => !k.startsWith("$")).slice(0, FIELD_CAP);
291
+ /** A field name as a template reads it: a member access where a name allows one, else a bracket. */
292
+ function fieldToken(path: readonly string[]): string {
293
+ let token = "item";
294
+ for (const member of path) {
295
+ token += /^[A-Za-z_$][\w$]*$/.test(member) ? `.${member}` : `[${JSON.stringify(member)}]`;
296
+ }
297
+ return token;
73
298
  }
74
299
 
75
300
  /**
76
- * Resolve the local scope tokens for text edited inside `arrayNode`. Always leads with `item` and
77
- * `index`; appends field tokens resolved from the repeater's binding:
301
+ * Every field the item in scope at `path` is known to hold, nearest the item first.
78
302
  *
79
- * - **Content collection** — `items` refs a `{ contentType }` state def. Item shape is `{ id, data: {
80
- * …fields }, body }`, so fields come from `projectConfig.content[contentType].schema.properties`
81
- * and emit as `item.data.<f>` (the `.data.` nesting is LOAD-BEARING — runtime templates use
82
- * `${item.data.title}`), plus `item.id` / `item.body`.
83
- * - **State array** — `items` refs a state def that is an array of objects. Fields come from a
84
- * declared `items.properties`, else are inferred from a sample `default[0]`. Emitted FLAT
85
- * (`item.<f>`, no `.data.`).
86
- * - **Inline array** — `items` is a literal array; fields inferred from `items[0]` object keys.
87
- * - **Fallback** — anything unresolvable yields exactly `["item", "index"]`.
303
+ * Walked a level at a time so {@link FIELD_CAP} cuts the deepest fields and never a top-level one.
304
+ * An object is offered as well as its members, because a component can take the whole value. A list
305
+ * is offered and not entered: one of its members has no single value to bind. A relationship is
306
+ * entered unless the branch already passed through that content type, which ends a post → author →
307
+ * post cycle.
88
308
  *
89
- * @param arrayNode - The repeater node from {@link findEnclosingRepeater}.
90
- * @param state - The document's `state` definitions (`tab.doc.document.state`).
309
+ * `$children` is left out unless asked for: it is a tree of nodes, so a text position, an attribute
310
+ * or a formula operand that read it would draw `[object Object]`.
311
+ *
312
+ * @param doc - The tab's document root (`tab.doc.document`).
313
+ * @param path - The path of the node asking.
91
314
  * @param projectConfig - Parsed `project.json` (`projectState?.projectConfig`).
92
- * @returns Token strings, e.g. `["item", "index", "item.id", "item.body", "item.data.title"]`.
315
+ * @param options - `withNodes` also lists the fields that hold child nodes.
316
+ * @returns The fields, empty outside a repeater and wherever the item's shape cannot be deduced.
93
317
  */
94
- export function resolveRepeaterItemFields(
95
- arrayNode: JxMappedArray,
96
- state: Readonly<Record<string, JxStateDefinition>> | null | undefined,
318
+ export function repeaterItemFields(
319
+ doc: JxMutableNode,
320
+ path: JxPath | null | undefined,
97
321
  projectConfig: ProjectConfig | null | undefined,
98
- ): string[] {
99
- const base = ["item", "index"];
100
- const { items } = arrayNode;
101
- const defs = state ?? {};
102
-
103
- const stateName = refToStateName(items);
104
- if (stateName) {
105
- const def = defs[stateName];
106
- if (isJsonObject(def)) {
107
- // (a) Content collection — `{ contentType: "<name>" }`. Import-aliased `$prototype` still
108
- // Matches because we detect on the `contentType` string, not the prototype name.
109
- if (isString(def.contentType)) {
110
- /* The `content` section is contributed by an extension and absorbed by the project
111
- config's index signature, so each level is checked before it is read. */
112
- const content = projectConfig?.content;
113
- const ct = isJsonObject(content) ? content[def.contentType] : undefined;
114
- const ctSchema = isJsonObject(ct) ? ct.schema : undefined;
115
- const props = isJsonObject(ctSchema) ? ctSchema.properties : undefined;
116
- const out = [...base, "item.id", "item.body"];
117
- if (isNonNullObject(props)) {
118
- for (const f of safeFields(Object.keys(props))) {
119
- out.push(`item.data.${f}`);
120
- }
121
- }
122
- return out;
322
+ { withNodes = false }: { withNodes?: boolean } = {},
323
+ ): RepeaterField[] {
324
+ if (!Array.isArray(path)) {
325
+ return [];
326
+ }
327
+ const ctx: ItemSources = { config: projectConfig, doc };
328
+ const fields: RepeaterField[] = [];
329
+ const pending: { at: string[]; schema: JsonObject | null; via: string[] }[] = [
330
+ { at: [], schema: itemSchemaAt(path, ctx), via: [] },
331
+ ];
332
+ for (const { at, schema, via } of pending) {
333
+ const entryType = isString(schema?.[ENTRY_TYPE]) ? schema[ENTRY_TYPE] : null;
334
+ const passed = entryType === null ? via : [...via, entryType];
335
+ const members = isJsonObject(schema?.properties) ? Object.entries(schema.properties) : [];
336
+ for (const [key, member] of members) {
337
+ const holdsNodes = key === NODES_MEMBER;
338
+ if ((key.startsWith("$") && !(holdsNodes && withNodes)) || fields.length >= FIELD_CAP) {
339
+ continue;
123
340
  }
124
-
125
- // (b) State array — declared object item-schema, or inferred from a sample default[0].
126
- const itemsSchema = def.items;
127
- const declared = isJsonObject(itemsSchema) ? itemsSchema.properties : undefined;
128
- if (isNonNullObject(declared)) {
129
- return [...base, ...safeFields(Object.keys(declared)).map((f) => `item.${f}`)];
341
+ const memberPath = [...at, key];
342
+ const resolved = followPointers(member, ctx);
343
+ if (resolved?.[ENTRY_DATA] !== true) {
344
+ fields.push({
345
+ holdsNodes,
346
+ isArray: resolved?.type === "array" || isJsonObject(resolved?.items),
347
+ path: memberPath,
348
+ pointer: `$map/item/${memberPath.map((m) => escapeToken(m)).join("/")}`,
349
+ token: fieldToken(memberPath),
350
+ });
130
351
  }
131
- const defaults = def.default;
132
- const sample = Array.isArray(defaults) ? defaults[0] : undefined;
133
- if (isJsonObject(sample)) {
134
- return [...base, ...safeFields(Object.keys(sample)).map((f) => `item.${f}`)];
352
+ const type = isString(resolved?.[ENTRY_TYPE]) ? resolved[ENTRY_TYPE] : null;
353
+ if (memberPath.length < DEPTH_CAP && !(type !== null && passed.includes(type))) {
354
+ pending.push({ at: memberPath, schema: resolved, via: passed });
135
355
  }
136
356
  }
137
- return base;
138
357
  }
358
+ return fields;
359
+ }
139
360
 
140
- // (c) Inline literal array — infer fields from the first object element.
141
- if (Array.isArray(items)) {
142
- const [first] = items;
143
- if (isJsonObject(first)) {
144
- return [...base, ...safeFields(Object.keys(first)).map((f) => `item.${f}`)];
145
- }
146
- }
361
+ /**
362
+ * The local scope tokens for text edited inside a repeater: `item` and `index`, then the fields of
363
+ * the item in scope (see {@link repeaterItemFields}).
364
+ *
365
+ * @returns Token strings, e.g. `["item", "index", "item.id", "item.body", "item.data.title"]`.
366
+ */
367
+ export function resolveRepeaterItemFields(
368
+ doc: JxMutableNode,
369
+ path: JxPath | null | undefined,
370
+ projectConfig: ProjectConfig | null | undefined,
371
+ ): string[] {
372
+ return ["item", "index", ...repeaterItemFields(doc, path, projectConfig).map((f) => f.token)];
373
+ }
147
374
 
148
- // (d) Fallback — unresolvable binding, primitive/empty items, or missing schema.
149
- return base;
375
+ /**
376
+ * The `children` template that draws a field's nodes. The fallback is part of it because the field
377
+ * is only known to be possible: an entry loaded without a body has no `$children` to read.
378
+ */
379
+ export function childrenTemplate(field: RepeaterField): string {
380
+ return `\${${field.token} ?? []}`;
381
+ }
382
+
383
+ /**
384
+ * The field a `children` template reads, with or without the fallback {@link childrenTemplate}
385
+ * writes. Anything more than the bare field is the author's own expression and is left as theirs.
386
+ */
387
+ export function fieldOfChildrenTemplate(
388
+ template: string,
389
+ fields: readonly RepeaterField[],
390
+ ): RepeaterField | undefined {
391
+ const token = /^\$\{\s*(.+?)\s*(?:\?\?\s*\[\s*\]\s*)?\}$/u.exec(template)?.[1];
392
+ return fields.find((field) => field.token === token);
393
+ }
394
+
395
+ /**
396
+ * A `$map/…` pointer in the words {@link repeaterItemFields} offers it under, or `null` for any
397
+ * other pointer. A bound chip says the same name the picker did: `item.data.title`.
398
+ */
399
+ export function mapRefLabel(ref: string): string | null {
400
+ if (ref === "$map/index") {
401
+ return "index";
402
+ }
403
+ if (ref === "$map/item") {
404
+ return "item";
405
+ }
406
+ return ref.startsWith("$map/item/")
407
+ ? fieldToken(refSegments(ref.slice("$map/item/".length)))
408
+ : null;
150
409
  }