@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
@@ -24,6 +24,7 @@ import {
24
24
  setCanvasDelinkCommands,
25
25
  setCanvasDelinkPopovers,
26
26
  setCanvasViewportTranspose,
27
+ setDocumentParser,
27
28
  setRootMedia,
28
29
  setSkipAutoRequests,
29
30
  setSkipServerFunctions,
@@ -43,6 +44,17 @@ import { isNonNullObject, isString } from "@jxsuite/schema/guards";
43
44
  import type { JxDocument, JxElement, JxStyle } from "@jxsuite/schema/types";
44
45
  import type { PathMapCtx } from "./path-mapping";
45
46
 
47
+ /**
48
+ * Read a `.jxn` `$ref`, which the editing server answers as its text.
49
+ *
50
+ * Loaded when one turns up, because the parser is a third of this entry's size budget and most
51
+ * canvases never see a `.jxn` reference.
52
+ */
53
+ async function parseDocumentText(text: string, source: string): Promise<JxDocument> {
54
+ const { parseJxn } = await import("@jxsuite/schema/jxn");
55
+ return parseJxn(text, source);
56
+ }
57
+
46
58
  /**
47
59
  * The retained render context a full render leaves behind so the surgical patcher can re-render an
48
60
  * individual subtree (insert/replace/attr edits) with the SAME scope, doc base, path mapping, and
@@ -1070,6 +1082,7 @@ export async function renderResolvedDocument(opts: {
1070
1082
  const assets = opts.assets ?? null;
1071
1083
  setCanvasAssetResolver(assets ? (value) => resolveAssetRef(value, assets) : null);
1072
1084
  setSkipServerFunctions(opts.mode !== "preview");
1085
+ setDocumentParser(parseDocumentText);
1073
1086
  // Same gate for automatic `$prototype: "Request"` state entries. `buildScope` re-resolves every
1074
1087
  // State entry on each full render, so without this an escalating authoring action (a signals-panel
1075
1088
  // Edit, or Enter inside component-wrapped content) issued an HTTP request per render. Edit/design
@@ -33,7 +33,7 @@ import {
33
33
  setTransactObserver,
34
34
  transactDoc,
35
35
  } from "../tabs/transact";
36
- import { PROJECT_CONFIG_PATH } from "../tabs/tab";
36
+ import { isProjectConfigPath } from "../tabs/tab";
37
37
  import type { TransactOrigin } from "../tabs/transact";
38
38
  import type { TransactionRecord } from "../tabs/patch-ops";
39
39
  import type { Tab } from "../tabs/tab";
@@ -672,7 +672,7 @@ const liveTabs = new Set<Tab>();
672
672
  */
673
673
  export function ensureCollab(tab: Tab): void {
674
674
  const platform = maybePlatform();
675
- if (!platform?.collab || !tab.documentPath || tab.documentPath === PROJECT_CONFIG_PATH) {
675
+ if (!platform?.collab || !tab.documentPath || isProjectConfigPath(tab.documentPath)) {
676
676
  return;
677
677
  }
678
678
  const runtime = runtimeFor(tab);
@@ -180,7 +180,7 @@ export const PANEL_PLACEMENT_MATRIX: Readonly<Record<PanelPlacement, PlacementRu
180
180
  },
181
181
  "rail/document": {
182
182
  admits: ["document"],
183
- note: "the rail's lower group — Outline, Data, and Packages with the developer panels on",
183
+ note: "the rail's lower group — Insert, Outline, Data, and Packages with the developer panels on",
184
184
  },
185
185
  navigator: {
186
186
  admits: ["project", "document"],
@@ -1,6 +1,6 @@
1
1
  /**
2
- * The content-entry verbs: open one where it is written, mark one a draft, and choose whether
3
- * drafts are listed at all — and {@link createEntry}, the New File tree's route into a collection
2
+ * The content-entry verbs: open one where it is written, hold one back, and choose whether held
3
+ * entries are listed at all — and {@link createEntry}, the New File tree's route into a collection
4
4
  * folder.
5
5
  *
6
6
  * Creating an entry from anywhere else is Add new's (`file.new { kind: "entry", collection }`,
@@ -9,10 +9,10 @@
9
9
  *
10
10
  * Each is a record in its owning module, so the palette row, the assistant's tool, the automation
11
11
  * step and (for the two that declare one) the context-menu item are renderings of the same
12
- * declaration. Two of them are **setters and not toggles** — `content.setDraft {draft}` and
13
- * `content.setIncludeDrafts {include}` name the state they reach, because "toggle the draft flag"
14
- * run twice by a script leaves the entry exactly where it started and run once against an unknown
15
- * default publishes something private.
12
+ * declaration. Two of them are **setters and not toggles** — `content.setHold {hold}` and
13
+ * `content.setIncludeHeld {include}` name the state they reach, because "toggle the hold" run twice
14
+ * by a script leaves the entry exactly where it started and run once against an unknown default
15
+ * publishes something private.
16
16
  */
17
17
 
18
18
  import {
@@ -33,13 +33,13 @@ import { notify } from "../services/notify";
33
33
  import { activeTab } from "../workspace/workspace";
34
34
  import { serializeJson } from "@jxsuite/schema/json-layout";
35
35
  import { invalidateReferenceEntries } from "../ui/form-controls";
36
- import { reloadDraftAwareGrids } from "../grid/sources/content-source";
37
- import { setIncludeDrafts } from "./draft-state";
36
+ import { reloadHoldAwareGrids } from "../grid/sources/content-source";
37
+ import { holdSwitch, setIncludeHeld } from "./hold-state";
38
38
  import { collectionOfPath, entryCollection } from "./entry-model";
39
39
  import { entryBody } from "../create/add-new";
40
40
  import { collectionForDirectory } from "./collection-match";
41
41
  import { LOCALE_PLACEHOLDER } from "../canvas/asset-resolve";
42
- import { setEntryDraft } from "./entry-editor";
42
+ import { setEntryHold } from "./entry-editor";
43
43
  import { openEntry } from "./entry-open";
44
44
  import { errorMessage } from "@jxsuite/schema/parse";
45
45
  import type { EntryCollection } from "./entry-model";
@@ -188,6 +188,11 @@ function activeEntryTab(): Tab | null {
188
188
  return tab && collectionOfPath(tab.documentPath) ? tab : null;
189
189
  }
190
190
 
191
+ /** The collection of the entry in front of the author, or null. */
192
+ function activeEntryCollection(): EntryCollection | null {
193
+ return collectionOfPath(activeEntryTab()?.documentPath ?? null);
194
+ }
195
+
191
196
  /** The content-entry command records. */
192
197
  export function contentCommands(): AnyCommand[] {
193
198
  return [
@@ -212,41 +217,41 @@ export function contentCommands(): AnyCommand[] {
212
217
  },
213
218
  {
214
219
  args: argsSchema({
215
- draft: booleanProperty(
216
- "True marks the open entry a draft; false marks it published. Written as a `draft` " +
217
- "field on the entry.",
220
+ hold: booleanProperty(
221
+ "True holds the open entry back from production; false releases it. Written to the " +
222
+ "one-field `hold` rule its collection declares.",
218
223
  ),
219
224
  }),
220
225
  category: "Document",
221
- id: "content.setDraft",
226
+ id: "content.setHold",
222
227
  level: "document",
223
228
  menus: ["context/tab", "palette"],
224
229
  group: "3_document",
225
- requires: "a content entry open",
230
+ requires: "a content entry whose collection has a one-field hold rule",
226
231
  when: (ctx) => ctx.document.open,
227
- enablement: () => activeEntryTab() !== null,
232
+ enablement: () => holdSwitch(activeEntryCollection()?.def) !== null,
228
233
  undo: "document",
229
- /* No `aiTool`, by studio-ui-guidelines.md §12.4's fourth deletion rule: the draft flag is ONE
234
+ /* No `aiTool`, by studio-ui-guidelines.md §12.4's fourth deletion rule: the hold switch is ONE
230
235
  FIELD on the entry, and the model already writes fields — `set_property` at the root path
231
236
  for a JSON entry (`mutateDocumentField` is the same `set-key` at `[]`), `write_file` for a
232
237
  frontmatter one. The state is reachable — `open_document` opens a collection file as
233
238
  readily as `content.openEntry` does — so the reason is redundancy, not reach. Six lines to
234
- re-enter should a task want the flag by name rather than by key. */
239
+ re-enter should a task want the state by name rather than by key. */
235
240
  run: (_ctx, args) => {
236
241
  const tab = activeEntryTab();
237
242
  if (!tab) {
238
- throw new Error("content.setDraft: the active document is not a content entry");
243
+ throw new Error("content.setHold: the active document is not a content entry");
239
244
  }
240
- setEntryDraft(tab, booleanArg("content.setDraft", args, "draft"));
245
+ setEntryHold(tab, booleanArg("content.setHold", args, "hold"));
241
246
  },
242
- title: "Set Draft",
247
+ title: "Set Hold",
243
248
  },
244
249
  {
245
250
  args: argsSchema({
246
- include: booleanProperty("True lists entries marked `draft: true` alongside the rest."),
251
+ include: booleanProperty("True lists entries a `hold` rule matches alongside the rest."),
247
252
  }),
248
253
  category: "View",
249
- id: "content.setIncludeDrafts",
254
+ id: "content.setIncludeHeld",
250
255
  level: "project",
251
256
  menus: ["palette"],
252
257
  group: "2_view",
@@ -258,16 +263,16 @@ export function contentCommands(): AnyCommand[] {
258
263
  * Set the flag, then repaint what reads it.
259
264
  *
260
265
  * The second half is the wiring, and without it this command sets a value nobody looks at
261
- * again: a collection grid holds the rows it loaded once, so an author who asks to see their
262
- * drafts watches an unchanged table. `draft-state.ts` cannot do it — it is imported BY the
266
+ * again: a collection grid holds the rows it loaded once, so an author who asks to see held
267
+ * entries watches an unchanged table. `hold-state.ts` cannot do it — it is imported BY the
263
268
  * listings and reaching back into the workspace from there is the cycle. The command can,
264
269
  * because a command is exactly the place where "the state changed" becomes "the app changed".
265
270
  */
266
271
  run: async (_ctx, args) => {
267
- setIncludeDrafts(booleanArg("content.setIncludeDrafts", args, "include"));
268
- await reloadDraftAwareGrids();
272
+ setIncludeHeld(booleanArg("content.setIncludeHeld", args, "include"));
273
+ await reloadHoldAwareGrids();
269
274
  },
270
- title: "Include Drafts",
275
+ title: "Include Held Entries",
271
276
  },
272
277
  ];
273
278
  }
@@ -16,7 +16,7 @@
16
16
  *
17
17
  * **This module is the FLOW; `surfaces/entry-editor.json` is the markup.** Everything below decides
18
18
  * something — which pane holds which tab, which collection a path belongs to, which record the
19
- * fields live in, which required keys are absent, what a draft flag commits into — and hands the
19
+ * fields live in, which required keys are absent, what a hold switch commits into — and hands the
20
20
  * surface a projection with no decisions left in it. The form itself is an island the surface
21
21
  * renders empty: `ui/schema-form.ts` hands back a host ELEMENT, and {@link renderEntryMode} places
22
22
  * it in the node the document announces.
@@ -51,7 +51,7 @@ import { paneRegion } from "../ui/regions";
51
51
  import { activateTab, workspace } from "../workspace/workspace";
52
52
  import { commitEntryFields, entryFields, mutateEntryField } from "./entry-fields";
53
53
  import type { EntryFields } from "./entry-fields";
54
- import { DRAFT_FIELD, DRAFT_MEANING, hasDraftAxis, isDraftEntry } from "./draft-state";
54
+ import { hasHold, holdMeaning, holdOf, holdStatus, holdSwitch } from "./hold-state";
55
55
  import { collectionOfPath, missingRequired, recordCollectionOfPath } from "./entry-model";
56
56
  import { ENTRY_MODE, isRecordTab, recordPlace, showRecord } from "./record-entry";
57
57
  import { requestGridReveal } from "../grid/grid-reveal";
@@ -115,56 +115,55 @@ export function detachEntryPane(paneId: string): void {
115
115
  _active.delete(paneId);
116
116
  }
117
117
 
118
- // ─── Drafts ──────────────────────────────────────────────────────────────────
118
+ // ─── Holds ───────────────────────────────────────────────────────────────────
119
119
 
120
120
  /**
121
- * Set this entry's draft flag. A setter, never a toggle — the pill, the command and the assistant
122
- * all reach the same state whatever it was before.
121
+ * Flip the switch this entry's collection offers (`holdSwitch`). A setter, never a toggle: the
122
+ * pill, the command and the assistant all reach the same state whatever it was before. Does nothing
123
+ * on a collection whose rules offer no switch.
123
124
  *
124
- * `draft: false` is written rather than removed. The absence of the key and the value `false` mean
125
- * the same thing to a reader, but only one of them survives a glance at the file, and an author who
126
- * has explicitly published something should be able to see that they did.
125
+ * `false` is written rather than removed. The absence of the key and the value `false` mean the
126
+ * same thing to a reader, but only one of them survives a glance at the file, and an author who has
127
+ * explicitly released something should be able to see that they did.
127
128
  */
128
- export function setEntryDraft(tab: Tab, draft: boolean): void {
129
- transactDoc(tab, (t) => mutateEntryField(t, DRAFT_FIELD, draft));
129
+ export function setEntryHold(tab: Tab, on: boolean): void {
130
+ const toggle = holdSwitch(entryCollectionOf(tab)?.def);
131
+ if (toggle) {
132
+ transactDoc(tab, (t) => mutateEntryField(t, toggle.field, on));
133
+ }
130
134
  }
131
135
 
132
- /** What the tab chip's draft pill says. A value, drawn by `surfaces/tab-strip.json`. */
133
- export interface DraftPill {
134
- /** The word on the pill: "Draft" or "Published". */
136
+ /** What the tab chip's pill says. A value, drawn by `surfaces/tab-strip.json`. */
137
+ export interface HoldPill {
138
+ /** The word on the pill: the rule's label, or "Published". */
135
139
  text: string;
136
- /** Its tooltip: what being a draft actually excludes, or that it is not one. */
140
+ /** Its tooltip: what being held actually does, or that it is not. */
137
141
  title: string;
138
- draft: boolean;
142
+ held: boolean;
139
143
  }
140
144
 
141
145
  /**
142
- * The draft pill for a tab, or `null` when its document has no draft axis at all.
146
+ * The pill for a tab, or `null` when its collection states no rules.
143
147
  *
144
148
  * Drawn on the pane's tab chip (`panels/tab-strip.ts`) rather than only inside the editor, because
145
- * the failure this exists to prevent is publishing something you thought was private — and that
146
- * mistake is made while looking at a tab, not while looking at a form. It states its two words for
147
- * any entry whose collection declares the field, so "Published" is as visible as "Draft"; a
148
- * collection with no draft workflow states neither, which is what `null` means.
149
+ * the failure this exists to prevent is publishing something you thought was private, and that
150
+ * mistake is made while looking at a tab, not while looking at a form. It states its state for any
151
+ * entry whose collection has rules, so "Published" is as visible as "Draft"; a collection with none
152
+ * states neither, which is what `null` means.
149
153
  *
150
154
  * **It was the ONE piece of markup left in this file, and it was not this surface's.** It was a
151
- * fragment of the tab CHIP — a `<span>` lit interpolated into a `repeat()` that rebuilt on every
152
- * strip repaint — and it said so, and that it would convert when `panels/tab-strip.ts` converted.
153
- * The strip is a Jx document now, so this is a projection: two strings and a flag, drawn by the
154
- * chip's `status` slot, and `.entry-pill`'s rules in `styles/overlays.css` are dead with it.
155
+ * fragment of the tab CHIP, a `<span>` lit interpolated into a `repeat()` that rebuilt on every
156
+ * strip repaint. The strip is a Jx document now, so this is a projection: two strings and a flag,
157
+ * drawn by the chip's `status` slot.
155
158
  */
156
- export function entryDraftPill(tab: Tab): DraftPill | null {
157
- const collection = entryCollectionOf(tab);
158
- const fields = entryFields(tab);
159
- if (!collection || !hasDraftAxis(collection.schema, fields)) {
159
+ export function entryHoldPill(tab: Tab): HoldPill | null {
160
+ const def = entryCollectionOf(tab)?.def;
161
+ if (!hasHold(def)) {
160
162
  return null;
161
163
  }
162
- const draft = isDraftEntry(fields);
163
- return {
164
- draft,
165
- text: draft ? "Draft" : "Published",
166
- title: draft ? DRAFT_MEANING : "Not marked a draft.",
167
- };
164
+ const fields = entryFields(tab);
165
+ const label = holdOf(def, fields);
166
+ return { held: label !== null, text: holdStatus(def, fields), title: holdMeaning(label) };
168
167
  }
169
168
 
170
169
  // ─── The form ────────────────────────────────────────────────────────────────
@@ -255,11 +254,12 @@ function entryView(paneId: string, tab: Tab, collection: EntryCollection | null)
255
254
  if (!collection) {
256
255
  return {
257
256
  collection: "",
258
- draft: false,
259
- draftAxis: "hidden",
260
- draftHint: DRAFT_MEANING,
261
257
  emptyLine: emptyLine(tab),
262
258
  fieldsRegion,
259
+ holdHint: holdMeaning(null),
260
+ holdLabel: "",
261
+ holdOn: false,
262
+ holdSwitch: "hidden",
263
263
  name: "",
264
264
  nextDisabled: true,
265
265
  note: "",
@@ -272,23 +272,25 @@ function entryView(paneId: string, tab: Tab, collection: EntryCollection | null)
272
272
  };
273
273
  }
274
274
  const fields = entryFields(tab);
275
- const draft = isDraftEntry(fields);
275
+ const label = holdOf(collection.def, fields);
276
+ const toggle = holdSwitch(collection.def);
276
277
  const record = isRecordTab(tab);
277
278
  const place = recordPlace(tab);
278
279
  return {
279
280
  collection: collection.name,
280
- draft,
281
- draftAxis: hasDraftAxis(collection.schema, fields) ? "shown" : "hidden",
282
- draftHint: DRAFT_MEANING,
283
281
  emptyLine: "",
284
282
  fieldsRegion,
283
+ holdHint: holdMeaning(label),
284
+ holdLabel: toggle?.label ?? "",
285
+ holdOn: toggle !== null && fields[toggle.field] === true,
286
+ holdSwitch: toggle ? "shown" : "hidden",
285
287
  // A record is called what the Content home calls it: its title field, else its row.
286
288
  name: record
287
289
  ? recordName(collection, fields, place.index)
288
290
  : (tab.documentPath?.split("/").pop() ?? "Untitled"),
289
291
  nextDisabled: place.next === null,
290
- note: DRAFT_MEANING,
291
- noteState: draft ? "shown" : "hidden",
292
+ note: holdMeaning(label),
293
+ noteState: label === null ? "hidden" : "shown",
292
294
  place: place.index === -1 ? "" : `${place.index + 1} of ${place.total}`,
293
295
  previousDisabled: place.previous === null,
294
296
  recordNav: record ? "shown" : "hidden",
@@ -401,7 +403,7 @@ export function renderEntryMode(surface: CanvasSurface, tab: Tab): void {
401
403
  }
402
404
  tab.session.ui.canvasMode = "grid";
403
405
  },
404
- setDraft: (draft) => setEntryDraft(tab, draft),
406
+ setHold: (on) => setEntryHold(tab, on),
405
407
  showNext: () => {
406
408
  const { next } = recordPlace(tab);
407
409
  if (next !== null) {
@@ -3,9 +3,9 @@
3
3
  * The Inspector's Entry tab: what the Page tab draws while a collection entry is the focused
4
4
  * document (`specs/studio.md` §6, §22.5; the Content-Entry board's sidebar).
5
5
  *
6
- * Four things, in the board's order: the entry's **status** (its draft flag, when the collection
7
- * has a draft workflow, `content/draft-state.ts`), its **fields** as the collection's schema types
8
- * them (`ui/schema-form.ts` over `content/entry-fields.ts`, the same form and the same transacted
6
+ * Four things, in the board's order: the entry's **status** (what its collection's `hold` rules
7
+ * say, `content/hold-state.ts`), its **fields** as the collection's schema types them
8
+ * (`ui/schema-form.ts` over `content/entry-fields.ts`, the same form and the same transacted
9
9
  * commits as the form-only editor, so ⌘Z undoes a field here exactly as it does there), the **URL**
10
10
  * its collection page publishes it at (`content/collection-pages.ts`), and the **file** it is
11
11
  * stored in, with what that file holds.
@@ -30,13 +30,13 @@ import {
30
30
  peekCollectionPage,
31
31
  } from "./collection-pages";
32
32
  import { commitEntryFields, entryFields } from "./entry-fields";
33
- import { DRAFT_MEANING, hasDraftAxis, isDraftEntry } from "./draft-state";
33
+ import { holdMeaning, holdOf, holdStatus, holdSwitch } from "./hold-state";
34
34
  import { entryCollection } from "./entry-model";
35
35
  import {
36
36
  absentRequiredErrors,
37
37
  fieldLabels,
38
38
  formContext,
39
- setEntryDraft,
39
+ setEntryHold,
40
40
  titleFirst,
41
41
  } from "./entry-editor";
42
42
  import { mountEntryInspectorSurface } from "../surfaces/entry-inspector";
@@ -64,20 +64,23 @@ export function entryInspectorView(tab: Tab): EntryInspectorView {
64
64
  const name = match?.name ?? "";
65
65
  const collection = entryCollection(name);
66
66
  const fields = entryFields(tab);
67
- const draftAxis = hasDraftAxis(collection?.schema, fields);
68
- const draft = isDraftEntry(fields);
67
+ const def = collection?.def;
68
+ const label = holdOf(def, fields);
69
+ const toggle = holdSwitch(def);
69
70
  const page = name === "" ? null : peekCollectionPage(name, entryLocale(path));
70
71
  const url = page ? entryUrl(page, path, fields) : null;
71
72
  const isJson = path.endsWith(".json");
72
73
  return {
73
- draft,
74
- draftAxis: draftAxis ? "shown" : "hidden",
75
- draftHint: DRAFT_MEANING,
76
74
  fieldsState: collection?.schema ? "form" : "none",
75
+ held: label !== null,
76
+ holdHint: holdMeaning(label),
77
+ holdLabel: toggle?.label ?? "",
78
+ holdOn: toggle !== null && fields[toggle.field] === true,
79
+ holdSwitch: toggle ? "shown" : "hidden",
77
80
  kind: capitalise(contentCollection(name)?.item ?? "entry"),
78
81
  path,
79
82
  region: ENTRY_INSPECTOR_REGION,
80
- status: draftAxis ? (draft ? "Draft" : "Published") : "",
83
+ status: holdStatus(def, fields),
81
84
  storage: isJson
82
85
  ? ". Its fields are the file itself."
83
86
  : ". Fields are its frontmatter, and the body is its Markdown.",
@@ -194,7 +197,7 @@ export function renderEntryInspector(host: HTMLElement, tab: Tab): void {
194
197
  openSeo: () => {
195
198
  void runActiveReported("document.openSeo", undefined, "Entry");
196
199
  },
197
- setDraft: (draft) => setEntryDraft(tab, draft),
200
+ setHold: (on) => setEntryHold(tab, on),
198
201
  },
199
202
  {
200
203
  fieldsSlot: (element) => {
@@ -18,9 +18,9 @@
18
18
  * `grid/sources/content-source.ts`'s `collectionInfo`, and `browse/library-model.ts`'s
19
19
  * `contentTypeFor`, since deleted); this module adds no fourth and calls the exported ones.
20
20
  *
21
- * **The draft axis is NOT here.** `content/draft-state.ts` owns what a draft is and whether drafts
22
- * are listed, because this module imports `content-source.ts` and the grid source must be able to
23
- * ask what a draft is — the reverse edge would be a cycle, and `import/no-cycle` is an error here.
21
+ * **The hold axis is NOT here.** `content/hold-state.ts` owns what a hold is and whether hrafts are
22
+ * listed, because this module imports `content-source.ts` and the grid source must be able to ask
23
+ * what a draft is — the reverse edge would be a cycle, and `import/no-cycle` is an error here.
24
24
  */
25
25
 
26
26
  import { collectionInfo } from "../grid/sources/content-source";
@@ -25,7 +25,7 @@ import {
25
25
  import { getPlatform, hasPlatform } from "../platform";
26
26
  import { isImage } from "../files/media-upload";
27
27
  import { previewFileSrc } from "../files/media-paths";
28
- import { hasDraftAxis, isDraftEntry } from "./draft-state";
28
+ import { holdStatus } from "./hold-state";
29
29
  import { entryCollection } from "./entry-model";
30
30
  import { PAGES_COLLECTION, collectionKeyOfPath } from "./content-collections";
31
31
  import type { EntryFields } from "./entry-fields";
@@ -36,7 +36,7 @@ export interface EntrySummary {
36
36
  path: string;
37
37
  /** The entry's `title`, or its file name without the extension. */
38
38
  title: string;
39
- /** "Draft", "Published", or "" for a collection with no draft workflow. */
39
+ /** A `hold` rule's label, "Published", or "" for a collection with no rules. */
40
40
  status: string;
41
41
  /** A preview URL for the entry's first image field, or "" for none. */
42
42
  thumb: string;
@@ -278,16 +278,16 @@ export function entryThumb(
278
278
 
279
279
  /** One entry's summary, from its fields. Pure. */
280
280
  export function summarize(path: string, collection: string, fields: EntryFields): EntrySummary {
281
- const schema = entryCollection(collection)?.schema ?? null;
281
+ const definition = entryCollection(collection);
282
+ const schema = definition?.schema ?? null;
282
283
  const titleField = titleFieldOf(schema, fields);
283
284
  const title = titleField === null ? undefined : fields[titleField];
284
- const draft = isDraftEntry(fields);
285
285
  const dateField = dateFieldOf(schema, fields);
286
286
  return {
287
287
  date: dateField === null ? "" : entryDay(fields[dateField]),
288
288
  fields: fieldTexts(schema, fields),
289
289
  path,
290
- status: hasDraftAxis(schema, fields) ? (draft ? "Draft" : "Published") : "",
290
+ status: holdStatus(definition?.def, fields),
291
291
  thumb: entryThumb(path, fields, schema),
292
292
  title: isString(title) && title.trim() !== "" ? title : entryStem(path),
293
293
  };
@@ -0,0 +1,172 @@
1
+ /**
2
+ * The hold axis (site-architecture.md §7.6): what a project says keeps an entry back, what state an
3
+ * entry is in by those rules, and whether held entries are listed. One place, every list.
4
+ *
5
+ * Jx gives no field a meaning of its own. A project names its fields and writes `hold` rules on a
6
+ * content type, so everything here reads those rules and nothing here knows the word "draft". The
7
+ * loader answers the same rules with the same code (`@jxsuite/schema/content-query`), which is what
8
+ * lets a badge here say what the build will actually do.
9
+ *
10
+ * It is deliberately its own module rather than a field on a pane's state and rather than a section
11
+ * of `entry-model.ts`. Two reasons, and the second is why the definitions live here beside the
12
+ * flag:
13
+ *
14
+ * 1. **The perspective is a fact about the AUTHOR, not the surface.** A filter that belonged to the
15
+ * Library would answer for the Library only, and the same author would then be shown a held
16
+ * entry in the collection grid, in the pages grid and in Quick Open a second later.
17
+ * 2. **Every list that must obey it has to be able to import it.** `entry-model.ts` asks
18
+ * `grid/sources/content-source.ts` where a collection's entries live, so the grid source cannot
19
+ * ask `entry-model.ts` back what a hold is: `import/no-cycle` is an error in this repo, and the
20
+ * cycle would be real.
21
+ *
22
+ * @docs studio/projects/content-types
23
+ */
24
+
25
+ import { compileHold } from "@jxsuite/schema/content-query";
26
+ import type { HoldPredicate } from "@jxsuite/schema/content-query";
27
+ import { isJsonObject, isString } from "@jxsuite/schema/guards";
28
+ import type { JsonValue } from "@jxsuite/schema/types";
29
+ import { reactive } from "../reactivity";
30
+ import type { ContentSectionEntry } from "../types";
31
+ import type { EntryFields } from "./entry-fields";
32
+
33
+ /** What this module reads off a content type's definition. */
34
+ export type HoldBearer = Pick<ContentSectionEntry, "hold">;
35
+
36
+ /** The word for an entry no rule holds, on a collection that has rules. */
37
+ export const NOT_HELD = "Published";
38
+
39
+ /** The rules a definition states, or none: a value that is not a list has no rules to read. */
40
+ function rulesOf(def: HoldBearer | null | undefined): JsonValue[] {
41
+ /* SAFETY: `hold` is read straight out of `project.json`, so whatever it holds is JSON. */
42
+ return Array.isArray(def?.hold) ? (def.hold as JsonValue[]) : [];
43
+ }
44
+
45
+ /** Each rules list's compiled answer, kept for as long as the list itself is. */
46
+ const compiled = new WeakMap<object, HoldPredicate>();
47
+
48
+ /** The compiled rules, answering nothing if they cannot compile: the build is who reports those. */
49
+ function predicateOf(def: HoldBearer | null | undefined): HoldPredicate {
50
+ const rules = rulesOf(def);
51
+ const known = compiled.get(rules);
52
+ if (known) {
53
+ return known;
54
+ }
55
+ let predicate: HoldPredicate;
56
+ try {
57
+ predicate = compileHold(rules);
58
+ } catch {
59
+ predicate = () => {};
60
+ }
61
+ compiled.set(rules, predicate);
62
+ return predicate;
63
+ }
64
+
65
+ /** Whether this collection has rules at all. With none, it has no state to show. */
66
+ export function hasHold(def: HoldBearer | null | undefined): boolean {
67
+ return rulesOf(def).length > 0;
68
+ }
69
+
70
+ /**
71
+ * The label of the rule holding this entry, or null. Takes the entry's FIELDS, whichever record
72
+ * they live in: `content/entry-fields.ts` answers that, because a JSON collection's entry has no
73
+ * frontmatter and its fields are the document.
74
+ */
75
+ export function holdOf(
76
+ def: HoldBearer | null | undefined,
77
+ fields: EntryFields | null | undefined,
78
+ ): string | null {
79
+ if (!hasHold(def)) {
80
+ return null;
81
+ }
82
+ /* SAFETY: an entry's fields are its parsed frontmatter or its JSON, which is what a query reads
83
+ from the loader too. */
84
+ return predicateOf(def)((fields ?? {}) as Parameters<HoldPredicate>[0]) ?? null;
85
+ }
86
+
87
+ /** What an entry's status reads: its rule's label, {@link NOT_HELD}, or "" with no rules. */
88
+ export function holdStatus(
89
+ def: HoldBearer | null | undefined,
90
+ fields: EntryFields | null | undefined,
91
+ ): string {
92
+ return hasHold(def) ? (holdOf(def, fields) ?? NOT_HELD) : "";
93
+ }
94
+
95
+ /** A rule an author can flip: one field, equal to `true`. */
96
+ export interface HoldSwitch {
97
+ field: string;
98
+ label: string;
99
+ }
100
+
101
+ /**
102
+ * The switch a collection's rules offer, or null. A rule on one boolean field is a thing an author
103
+ * can flip. A rule on a date or a list of values has no single other state to flip to, so it is
104
+ * shown as a status and set by editing its field.
105
+ */
106
+ export function holdSwitch(def: HoldBearer | null | undefined): HoldSwitch | null {
107
+ for (const rule of rulesOf(def)) {
108
+ if (isJsonObject(rule) && isString(rule.label) && isJsonObject(rule.when)) {
109
+ const fields = Object.entries(rule.when);
110
+ const [first] = fields;
111
+ if (fields.length === 1 && first?.[1] === true) {
112
+ return { field: first[0], label: rule.label };
113
+ }
114
+ }
115
+ }
116
+ return null;
117
+ }
118
+
119
+ /** The fields a collection's rules read, which is what a surface must repaint on. */
120
+ export function holdFields(def: HoldBearer | null | undefined): string[] {
121
+ return rulesOf(def).flatMap((rule) =>
122
+ isJsonObject(rule) && isJsonObject(rule.when) ? Object.keys(rule.when) : [],
123
+ );
124
+ }
125
+
126
+ /** What being held means, in one sentence, for every surface that shows the state. */
127
+ export function holdMeaning(label: string | null): string {
128
+ return label === null
129
+ ? "No hold rule matches this entry, so a production build publishes it."
130
+ : `Held: ${label}. A production build leaves it out; jx dev, Preview and preview builds keep it.`;
131
+ }
132
+
133
+ // ─── The perspective ─────────────────────────────────────────────────────────
134
+
135
+ /** The perspective. `includeHeld` false, the default, hides entries a rule holds. */
136
+ export const holdView = reactive({ includeHeld: false });
137
+
138
+ /** Whether held entries are currently listed. */
139
+ export function includingHeld(): boolean {
140
+ return holdView.includeHeld;
141
+ }
142
+
143
+ /**
144
+ * Set the perspective.
145
+ *
146
+ * A setter, never a toggle: `content.setIncludeHeld` names the STATE it reaches, so a manifest
147
+ * step, an assistant call and a checkbox all land on the same value however many times they run
148
+ * (§13.5's idempotence rule, which `tests/app-commands.test.ts` enforces).
149
+ *
150
+ * Setting the flag is all this does. Repainting the lists that read it belongs to the command, in
151
+ * `content/entry-commands.ts`, because a data module may not reach into the workspace.
152
+ */
153
+ export function setIncludeHeld(include: boolean): void {
154
+ holdView.includeHeld = include;
155
+ }
156
+
157
+ /**
158
+ * Apply the perspective to a list.
159
+ *
160
+ * `includeHeld` true, or a collection with no rules, returns the input array unchanged (identity,
161
+ * not a copy), so a caller can cheaply tell that nothing was filtered.
162
+ */
163
+ export function applyHoldFilter<T>(
164
+ items: readonly T[],
165
+ def: HoldBearer | null | undefined,
166
+ fieldsOf: (item: T) => EntryFields | null | undefined,
167
+ includeHeld: boolean,
168
+ ): readonly T[] {
169
+ return includeHeld || !hasHold(def)
170
+ ? items
171
+ : items.filter((item) => holdOf(def, fieldsOf(item)) === null);
172
+ }