@panphora/clayjs 1.16.0 → 1.18.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 (511) hide show
  1. package/dist/all.js +1 -1
  2. package/dist/clay-dom.js +1 -1
  3. package/dist/clay-events.js +1 -1
  4. package/dist/clay-internals.js +1 -1
  5. package/dist/clay-options.js +1 -1
  6. package/dist/clay-router.js +1 -1
  7. package/dist/clay-ui.js +1 -1
  8. package/dist/clay-utils.js +1 -1
  9. package/dist/clay.js +1 -1
  10. package/dist/clay.standalone.js +72519 -59839
  11. package/dist/m/attrs/refetch-on-save-ZUE7PSCI.js +1 -0
  12. package/dist/m/attrs/save-freeze-LWGF6XUN.js +1 -0
  13. package/dist/m/c-2GRUM77Q.js +30 -0
  14. package/dist/m/c-2KIEGNC4.js +1 -0
  15. package/dist/m/{c-5R4PTRG2.js → c-2KL3BD52.js} +1 -1
  16. package/dist/m/c-2YYDMLNZ.js +1 -0
  17. package/dist/m/c-2ZZBC45O.js +1 -0
  18. package/dist/m/c-372N6H3B.js +1 -0
  19. package/dist/m/{c-4WKFKEKF.js → c-3G2DGZUP.js} +1 -1
  20. package/dist/m/c-3LDGIBMN.js +1 -0
  21. package/dist/m/c-3O7OBDCM.js +1 -0
  22. package/dist/m/c-4ASQPRQL.js +1 -0
  23. package/dist/m/c-4TXNLX4K.js +6 -0
  24. package/dist/m/c-4YU3BR4D.js +6 -0
  25. package/dist/m/c-5734MF2P.js +1 -0
  26. package/dist/m/c-57XDY6HC.js +1 -0
  27. package/dist/m/{c-VFX45IZQ.js → c-5B4YBZXC.js} +1 -1
  28. package/dist/m/c-5ONTYIUI.js +1 -0
  29. package/dist/m/c-5OOVFCJM.js +1 -0
  30. package/dist/m/c-5T6NZHNM.js +1 -0
  31. package/dist/m/{c-YDH7Q3NY.js → c-5ZQV7KC4.js} +1 -1
  32. package/dist/m/{c-Z36J63PP.js → c-6DB2OLRJ.js} +1 -1
  33. package/dist/m/c-6DNRCPNM.js +1 -0
  34. package/dist/m/c-6UGWFMQC.js +2 -0
  35. package/dist/m/c-7K4SCQB7.js +1 -0
  36. package/dist/m/c-7MG5RU63.js +1 -0
  37. package/dist/m/c-7PRJCA3E.js +1 -0
  38. package/dist/m/c-A6L3FMUS.js +1 -0
  39. package/dist/m/c-AMU3BPPR.js +4 -0
  40. package/dist/m/c-AMYEFA2E.js +1 -0
  41. package/dist/m/c-AQYCG7QW.js +1 -0
  42. package/dist/m/c-ATKAOH23.js +1 -0
  43. package/dist/m/c-B3JM5ZXC.js +1 -0
  44. package/dist/m/c-B5CS55WR.js +1 -0
  45. package/dist/m/c-B5UJQT7W.js +1 -0
  46. package/dist/m/c-B7J4K2AE.js +3 -0
  47. package/dist/m/c-BHZYSHW4.js +1 -0
  48. package/dist/m/c-BIJALSRD.js +1 -0
  49. package/dist/m/c-BJ7YQ5QZ.js +1 -0
  50. package/dist/m/c-BPF2NSPV.js +1 -0
  51. package/dist/m/{c-KIPQJX4B.js → c-BRLCTNMC.js} +1 -1
  52. package/dist/m/c-BUAYFEV5.js +1 -0
  53. package/dist/m/c-C6IPRG34.js +1 -0
  54. package/dist/m/{c-2JL6E75N.js → c-CAS3NND4.js} +1 -1
  55. package/dist/m/{c-4DRMS4QG.js → c-CCQ25GFY.js} +1 -1
  56. package/dist/m/c-CJFDLD43.js +1 -0
  57. package/dist/m/c-COPTL5WB.js +1 -0
  58. package/dist/m/c-D3E4WJ4M.js +166 -0
  59. package/dist/m/c-D5IKEKXW.js +7 -0
  60. package/dist/m/c-DHBRA6DL.js +1 -0
  61. package/dist/m/c-DKLS74XB.js +72 -0
  62. package/dist/m/c-DVYGFTSK.js +3 -0
  63. package/dist/m/c-E3ZXARWA.js +1 -0
  64. package/dist/m/c-EK63G3CG.js +1 -0
  65. package/dist/m/c-ERQSNR6K.js +13 -0
  66. package/dist/m/c-EVYAISP3.js +1 -0
  67. package/dist/m/c-EXKHPOVC.js +3 -0
  68. package/dist/m/{c-7VGZ7RU5.js → c-FWKHM4E7.js} +1 -1
  69. package/dist/m/{c-4Q2XTQ2N.js → c-GBKB5WDL.js} +1 -1
  70. package/dist/m/c-GNDL3TKB.js +1 -0
  71. package/dist/m/c-GRWMD6O5.js +1 -0
  72. package/dist/m/c-GRXHHFPO.js +1 -0
  73. package/dist/m/{c-OYTFP5QN.js → c-GY422N3Q.js} +1 -1
  74. package/dist/m/c-HAWFNBYG.js +1 -0
  75. package/dist/m/c-HG2C77BM.js +4 -0
  76. package/dist/m/c-HHGJBU4D.js +1 -0
  77. package/dist/m/c-HMV6P4RX.js +1 -0
  78. package/dist/m/c-HMYWIHFG.js +1 -0
  79. package/dist/m/c-HT2DKBV2.js +1 -0
  80. package/dist/m/c-I2SCCUEW.js +1 -0
  81. package/dist/m/{c-HRDCD3WN.js → c-ICJYHABF.js} +2 -2
  82. package/dist/m/c-IMZCWYHR.js +7 -0
  83. package/dist/m/c-IVMD3FEM.js +1 -0
  84. package/dist/m/c-IVTRVENZ.js +1 -0
  85. package/dist/m/c-IW6GGOU2.js +1 -0
  86. package/dist/m/c-JKZJIUIP.js +1 -0
  87. package/dist/m/{c-NZMNXVF6.js → c-JNLX3RJB.js} +1 -1
  88. package/dist/m/c-JRKY44DK.js +1 -0
  89. package/dist/m/c-K3ADYNLU.js +1 -0
  90. package/dist/m/c-KBS3UV6B.js +1 -0
  91. package/dist/m/c-KGHVFRR3.js +1 -0
  92. package/dist/m/c-KJJPV77R.js +1 -0
  93. package/dist/m/{c-CZUSOB7V.js → c-KNPDOMDY.js} +1 -1
  94. package/dist/m/{c-5Y6TNX5W.js → c-KP64LT2A.js} +2 -2
  95. package/dist/m/c-KQ7E3PYY.js +1 -0
  96. package/dist/m/c-L3F5ZLIV.js +1 -0
  97. package/dist/m/{c-LR4CGEPT.js → c-L42OLXWM.js} +3 -2
  98. package/dist/m/c-L5VFATT6.js +1 -0
  99. package/dist/m/c-L7JL5TL3.js +3 -0
  100. package/dist/m/c-LKSL4AUJ.js +1 -0
  101. package/dist/m/c-M6FNQCTQ.js +1 -0
  102. package/dist/m/c-MCOOHCSG.js +1 -0
  103. package/dist/m/c-MHI3GYKF.js +1 -0
  104. package/dist/m/c-MWXFLW4J.js +1 -0
  105. package/dist/m/c-NEL7HQW3.js +1 -0
  106. package/dist/m/c-NQJRVJ56.js +1 -0
  107. package/dist/m/c-NWX3MFT4.js +1 -0
  108. package/dist/m/{c-HPURYVZS.js → c-OJYM642Q.js} +1 -1
  109. package/dist/m/c-OKXI52N7.js +1 -0
  110. package/dist/m/{c-MMUIUUZG.js → c-OPLUZR3A.js} +1 -1
  111. package/dist/m/{c-J7CA54IP.js → c-OTMKN2OO.js} +1 -1
  112. package/dist/m/{c-LTGMXXKO.js → c-OYRZ7XG2.js} +1 -1
  113. package/dist/m/c-P3OFYUMA.js +1 -0
  114. package/dist/m/c-PLN76GF3.js +6 -0
  115. package/dist/m/c-PLP4JKFC.js +1 -0
  116. package/dist/m/c-Q26MME6N.js +1 -0
  117. package/dist/m/{c-S7H2DRCB.js → c-Q3XOWZEV.js} +1 -1
  118. package/dist/m/{c-SWRJFCNN.js → c-QIKT6SKM.js} +1 -1
  119. package/dist/m/c-QIXT3Q7C.js +28 -0
  120. package/dist/m/c-QO4HNGCQ.js +1 -0
  121. package/dist/m/c-R27DWQKX.js +1 -0
  122. package/dist/m/{c-HVYXIS72.js → c-R2XUODVY.js} +1 -1
  123. package/dist/m/{c-QNKREOL6.js → c-R7LBFMF5.js} +1 -1
  124. package/dist/m/c-RND3G2S2.js +1 -0
  125. package/dist/m/c-S2U6NJU4.js +1 -0
  126. package/dist/m/c-SPZPGD3R.js +1 -0
  127. package/dist/m/c-SW4YEG2D.js +8 -0
  128. package/dist/m/c-SZGYW5HT.js +1 -0
  129. package/dist/m/c-T637T7SN.js +1 -0
  130. package/dist/m/c-TJAXGIRV.js +1 -0
  131. package/dist/m/{c-KKWLDOPM.js → c-TNEM3RNM.js} +1 -1
  132. package/dist/m/c-TTB5EK4S.js +32 -0
  133. package/dist/m/c-U7SHNWDH.js +1 -0
  134. package/dist/m/c-UDHIVLYH.js +20 -0
  135. package/dist/m/{c-BBVGTQG7.js → c-UGNV5MFB.js} +1 -1
  136. package/dist/m/{c-TQZ2JYHD.js → c-UORXLSD3.js} +1 -1
  137. package/dist/m/c-UPD33ZPE.js +1 -0
  138. package/dist/m/c-URSMSFCB.js +1 -0
  139. package/dist/m/c-UY4QX42B.js +1 -0
  140. package/dist/m/c-V44KWI2L.js +4 -0
  141. package/dist/m/c-VG5XIN4T.js +1 -0
  142. package/dist/m/c-VILENZTD.js +1 -0
  143. package/dist/m/{c-33HUYEDI.js → c-VJYEVRYK.js} +1 -1
  144. package/dist/m/c-VR2BJ4AN.js +1 -0
  145. package/dist/m/c-W4JCVLAB.js +1 -0
  146. package/dist/m/c-W6DQSHXI.js +135 -0
  147. package/dist/m/c-WBGWGOM7.js +1 -0
  148. package/dist/m/{c-4L3BTFRK.js → c-WCXLG3MV.js} +1 -1
  149. package/dist/m/c-WGW764WX.js +1 -0
  150. package/dist/m/c-WMRJKQ2V.js +5 -0
  151. package/dist/m/c-WNTYKCDG.js +1 -0
  152. package/dist/m/c-WQWIRWO3.js +72 -0
  153. package/dist/m/c-X5LNHET5.js +1 -0
  154. package/dist/m/c-X5TSXZYJ.js +2 -0
  155. package/dist/m/c-XGH4GS2T.js +1 -0
  156. package/dist/m/c-Y6F2FORL.js +1 -0
  157. package/dist/m/c-YBKHUAK7.js +10 -0
  158. package/dist/m/c-YBSS55KU.js +20 -0
  159. package/dist/m/c-YCLU4UNT.js +2 -0
  160. package/dist/m/c-YVZ2L7CE.js +1 -0
  161. package/dist/m/c-ZK4GIZH2.js +1 -0
  162. package/dist/m/c-ZRXJD4G6.js +1 -0
  163. package/dist/m/c-ZWBI4HYK.js +1 -0
  164. package/dist/m/{c-HT33EQRN.js → c-ZWIOJGMT.js} +1 -1
  165. package/dist/m/core/admin-attrs-YY7T5FGU.js +1 -0
  166. package/dist/m/core/autosave-S6OMNDCK.js +1 -0
  167. package/dist/m/core/conflict-notice-IWHMHH43.js +1 -0
  168. package/dist/m/core/host-permission-6EVJGNPL.js +1 -0
  169. package/dist/m/core/is-edit-mode-GCX6767E.js +1 -0
  170. package/dist/m/core/{persist-HKHA66IY.js → persist-UKXV5KLA.js} +1 -1
  171. package/dist/m/core/recovery-DRFZRJED.js +1 -0
  172. package/dist/m/core/save-LYY7K6U7.js +1 -0
  173. package/dist/m/core/save-core-MKRPSEEO.js +1 -0
  174. package/dist/m/core/snapshot-NU3YM6GR.js +1 -0
  175. package/dist/m/core/unsaved-warning-7QTKEN7Z.js +1 -0
  176. package/dist/m/events/index-RN2JNGBV.js +1 -0
  177. package/dist/m/internals/index-TGLQINN3.js +1 -0
  178. package/dist/m/lib/cache-bust-MUMFXZK6.js +1 -0
  179. package/dist/m/loader-3BBNZZGM.js +1 -0
  180. package/dist/m/manifest-F449FF98.js +1 -0
  181. package/dist/m/manifest-edit-4396A009.js +1 -0
  182. package/dist/m/manifest.json +2509 -1474
  183. package/dist/m/options/options-EJMS3K24.js +1 -0
  184. package/dist/m/page/editor-6LRB3GE4.js +304 -0
  185. package/dist/m/plugins/ai-edit-UTV7YO2I.js +6 -0
  186. package/dist/m/plugins/{cms-HP3LSTPN.js → cms-LRAOZHT7.js} +9 -9
  187. package/dist/m/plugins/demo-OYO5GCAO.js +1 -0
  188. package/dist/m/plugins/{derive-UAAA4V4S.js → derive-FRE5JJQE.js} +1 -1
  189. package/dist/m/plugins/indicator-4VDXWYTD.js +1 -0
  190. package/dist/m/plugins/live-AD4H6XSL.js +1 -0
  191. package/dist/m/plugins/menu-M5WW7DBF.js +1 -0
  192. package/dist/m/plugins/page-MD3T2ZX3.js +2 -0
  193. package/dist/m/plugins/page-view-PAWVZNFY.js +1 -0
  194. package/dist/m/plugins/people-JCHVPF7P.js +1 -0
  195. package/dist/m/plugins/quickcrop-BQ5YYFCH.js +26 -0
  196. package/dist/m/plugins/{richclay-RC3L2F6X.js → richclay-KLGETK77.js} +2 -2
  197. package/dist/m/plugins/sortable-4S6SQC3D.js +1 -0
  198. package/dist/m/plugins/source-TJXOIOYY.js +1 -0
  199. package/dist/m/plugins/undo-NLPGCJQY.js +1 -0
  200. package/dist/m/plugins/{unsent-HJFLQJ2O.js → unsent-OA4RGBUT.js} +1 -1
  201. package/dist/m/plugins/upgrade-HSUNDNJD.js +1 -0
  202. package/dist/m/plugins/upload-NHGJXZK2.js +1 -0
  203. package/dist/m/plugins/visitor-drafts-BW43DGY3.js +20 -0
  204. package/dist/m/plugins/visitor-drafts-owner-7ODQG7JX.js +1 -0
  205. package/dist/m/plugins/wire-VBWXEXU2.js +1 -0
  206. package/dist/m/reader-core-4W2KYEWQ.js +77 -0
  207. package/dist/m/route/route-62CHXL5B.js +1 -0
  208. package/dist/m/sync/live-sync-MO5DH33W.js +1 -0
  209. package/dist/m/ui/index-IVWN2QC4.js +1 -0
  210. package/dist/m/vendor/sap-core.vendor-RN4ZVY4X.js +6 -0
  211. package/dist/offline-manifest.json +1560 -889
  212. package/dist/page/themes/light-1.css +33 -8
  213. package/dist/server/core.cjs +1554 -311
  214. package/dist/server/offline-runtime.cjs +63 -63
  215. package/dist/server/offline-runtime.mjs +63 -63
  216. package/dist/server/page-compaction-worker.cjs +3710 -0
  217. package/dist/server/rooms.mjs +5 -0
  218. package/docs/guide.md +653 -101
  219. package/entries/sap.js +5 -5
  220. package/package.json +1 -1
  221. package/src/core/admin-contenteditable.js +5 -2
  222. package/src/core/admin-inputs.js +1 -0
  223. package/src/core/admin-onclick.js +2 -1
  224. package/src/core/boot-phase.js +2 -1
  225. package/src/core/conflict-download.js +3 -29
  226. package/src/core/conflict-notice-ui.js +4 -7
  227. package/src/core/download.js +32 -0
  228. package/src/core/host-meta.js +56 -0
  229. package/src/core/is-edit-mode.js +17 -1
  230. package/src/core/mode-key.js +125 -19
  231. package/src/core/mode-support.js +5 -0
  232. package/src/core/mode-transition.js +20 -6
  233. package/src/core/preserve-authored.js +4 -4
  234. package/src/core/recovery.js +121 -33
  235. package/src/core/save-accepted.js +33 -0
  236. package/src/core/save-core.js +111 -32
  237. package/src/core/save-job.js +108 -0
  238. package/src/core/save-prepare.js +53 -0
  239. package/src/core/save-size-notice.js +3 -1
  240. package/src/core/save-slices.js +15 -0
  241. package/src/core/save.js +557 -127
  242. package/src/core/serialize-steps.js +109 -0
  243. package/src/core/snapshot.js +209 -40
  244. package/src/core/source-map.js +83 -38
  245. package/src/core/stale-host-notice.js +1 -0
  246. package/src/core/upgrade-run.js +59 -8
  247. package/src/dom/active-element.js +5 -0
  248. package/src/lib/assets.js +1 -0
  249. package/src/lib/autosave-scheduler.js +3 -4
  250. package/src/lib/slice.js +65 -0
  251. package/src/lib/uimotion.js +774 -0
  252. package/src/lib/unsaved-state.js +2 -2
  253. package/src/live/fast-lane.js +10 -3
  254. package/src/live/local-lane.js +202 -11
  255. package/src/live/ops.js +16 -0
  256. package/src/live/page-compaction.js +162 -0
  257. package/src/live/server-lane.js +1 -0
  258. package/src/live/store.js +11 -3
  259. package/src/loader-logic.js +3 -1
  260. package/src/loader.js +14 -4
  261. package/src/page/adopt-boot.js +2 -1
  262. package/src/page/awareness.js +33 -2
  263. package/src/page/blocks.js +122 -27
  264. package/src/page/board-views.js +2 -16
  265. package/src/page/boot-pack.js +1 -1
  266. package/src/page/chart-tables.js +29 -0
  267. package/src/page/chart.js +411 -72
  268. package/src/page/checklist.js +10 -135
  269. package/src/page/checkpoint-proof.js +295 -0
  270. package/src/page/chrome.js +257 -40
  271. package/src/page/clipboard-shape.js +126 -0
  272. package/src/page/document-title-dom.js +12 -0
  273. package/src/page/document-title-runtime.js +15 -1
  274. package/src/page/document-title.js +4 -1
  275. package/src/page/drag.js +33 -3
  276. package/src/page/editor.js +207 -31
  277. package/src/page/emoji-data.js +162 -0
  278. package/src/page/export-assets.js +357 -0
  279. package/src/page/export-html.js +209 -0
  280. package/src/page/export-markdown.js +361 -0
  281. package/src/page/features/board.js +12 -3
  282. package/src/page/features/chart.js +11 -0
  283. package/src/page/features/checklist.js +428 -0
  284. package/src/page/features/dates.js +180 -0
  285. package/src/page/features/emoji.js +187 -0
  286. package/src/page/features/export-html.js +189 -0
  287. package/src/page/features/export-markdown.js +169 -0
  288. package/src/page/features/import-markdown.js +349 -0
  289. package/src/page/features/media.js +61 -5
  290. package/src/page/features/mentions.js +188 -0
  291. package/src/page/features/registry.js +24 -1
  292. package/src/page/features/table.js +375 -85
  293. package/src/page/features/title-cover.js +331 -0
  294. package/src/page/features/title-icon.js +213 -0
  295. package/src/page/features/toc.js +87 -0
  296. package/src/page/format-command.js +33 -0
  297. package/src/page/format-gate.js +68 -18
  298. package/src/page/format-host.js +71 -0
  299. package/src/page/format-native-commands.js +158 -0
  300. package/src/page/format-native.js +42 -0
  301. package/src/page/image-actions.js +64 -0
  302. package/src/page/import-markdown.js +262 -0
  303. package/src/page/inline.js +32 -8
  304. package/src/page/items.js +141 -11
  305. package/src/page/markdown.js +30 -2
  306. package/src/page/media.js +35 -119
  307. package/src/page/mention-picker.js +99 -0
  308. package/src/page/merge.js +1 -1
  309. package/src/page/placeholder.js +14 -1
  310. package/src/page/projection.js +156 -25
  311. package/src/page/recover.js +10 -12
  312. package/src/page/recovery-ui.js +464 -0
  313. package/src/page/rich/chart.js +63 -7
  314. package/src/page/save-projection.js +67 -0
  315. package/src/page/save-states.js +14 -4
  316. package/src/page/schema.js +52 -3
  317. package/src/page/table-paste.js +3 -1
  318. package/src/page/table-style.js +4 -4
  319. package/src/page/table.js +44 -2
  320. package/src/page/themes/content.css +25 -1
  321. package/src/page/themes/light-1.css +33 -8
  322. package/src/page/themes/light-1.source.css +8 -7
  323. package/src/page/toc-link.js +31 -0
  324. package/src/page/toc.js +5 -0
  325. package/src/page/view-hooks.js +58 -0
  326. package/src/page/view.js +13 -0
  327. package/src/page/ybind/binding.js +116 -6
  328. package/src/page/ybind/bridge.js +187 -20
  329. package/src/page/ybind/containment.js +162 -0
  330. package/src/page/ybind/doc.js +17 -9
  331. package/src/page/ybind/file-debt.js +63 -0
  332. package/src/page/ybind/layout.js +7 -2
  333. package/src/page/ybind/marks.js +2 -1
  334. package/src/page/ybind/moves.js +5 -4
  335. package/src/page/ybind/origins.js +106 -4
  336. package/src/page/ybind/parts.js +8 -1
  337. package/src/page/ybind/provider.js +125 -18
  338. package/src/page/ybind/recovery-proof.js +32 -9
  339. package/src/page/ybind/recovery-replay.js +905 -4
  340. package/src/page/ybind/recovery.js +174 -45
  341. package/src/page/ybind/table.js +67 -11
  342. package/src/page/ybind/walk-delta.js +258 -0
  343. package/src/page/ybind/walk.js +54 -29
  344. package/src/page/ybind/writer.js +1 -1
  345. package/src/plugins/ai-edit.js +13 -9
  346. package/src/plugins/indicator-chip.js +91 -0
  347. package/src/plugins/indicator.js +13 -80
  348. package/src/plugins/live.js +126 -15
  349. package/src/plugins/menu/actions.js +196 -0
  350. package/src/plugins/menu/people.js +279 -0
  351. package/src/plugins/menu/placement.js +121 -0
  352. package/src/plugins/menu/preferences.js +9 -0
  353. package/src/plugins/menu/seo.js +316 -397
  354. package/src/plugins/menu/style.js +171 -0
  355. package/src/plugins/menu/variants.js +296 -0
  356. package/src/plugins/menu.js +186 -183
  357. package/src/plugins/page-view.js +3 -2
  358. package/src/plugins/page.js +362 -43
  359. package/src/plugins/sortable.js +8 -1
  360. package/src/plugins/source.js +6 -0
  361. package/src/plugins/upgrade.js +51 -17
  362. package/src/plugins/upload-ui.js +12 -17
  363. package/src/plugins/visitor-drafts/bottom-ui.js +12 -4
  364. package/src/plugins/visitor-drafts/intro-style.js +7 -2
  365. package/src/plugins/visitor-drafts/intro.js +7 -4
  366. package/src/plugins/visitor-drafts/owner-chip.js +8 -12
  367. package/src/plugins/visitor-drafts/prompt.js +11 -9
  368. package/src/plugins/visitor-drafts/source.js +4 -3
  369. package/src/plugins/visitor-drafts/store.js +138 -9
  370. package/src/plugins/visitor-drafts/ui-memory.js +2 -2
  371. package/src/plugins/visitor-drafts-owner.js +4 -6
  372. package/src/plugins/visitor-drafts.js +5 -3
  373. package/src/recovery/store.js +1 -1
  374. package/src/route/route.js +4 -0
  375. package/src/server/checkpoint/index.js +2 -2
  376. package/src/server/rooms/index.js +5 -1
  377. package/src/server/rooms/live-data-rooms.js +302 -50
  378. package/src/server/rooms/page-compaction-worker.js +12 -0
  379. package/src/server/rooms/page-compactor.js +92 -0
  380. package/src/server/rooms/page-room-conformance.js +461 -0
  381. package/src/source/parse-steps.js +94 -0
  382. package/src/source/session.js +233 -57
  383. package/src/sync/editing-sender.js +12 -0
  384. package/src/sync/live-sync-edit.js +22 -2
  385. package/src/sync/live-sync.js +150 -23
  386. package/src/sync/page-relay.js +52 -0
  387. package/src/sync/presence-store.js +44 -4
  388. package/src/sync/presence.js +6 -3
  389. package/src/sync/saved-stream.js +28 -14
  390. package/src/ui/bevel-controls.js +1 -0
  391. package/src/ui/bevel-dialog.js +5 -2
  392. package/src/ui/editor-ui.js +77 -0
  393. package/src/ui/page-colour.js +281 -0
  394. package/src/ui/toast.js +16 -7
  395. package/src/vendor/hyper-html-api-upgrade.vendor.js +2 -2
  396. package/src/vendor/hyper-undo.vendor.js +1 -1
  397. package/src/vendor/hypercms.vendor.js +9 -9
  398. package/src/vendor/parse5.vendor.js +2 -2
  399. package/src/vendor/sap-core.vendor.js +5 -5
  400. package/dist/m/attrs/refetch-on-save-NWPPXOCZ.js +0 -1
  401. package/dist/m/attrs/save-freeze-FUP4Q4X3.js +0 -1
  402. package/dist/m/c-2M73252O.js +0 -1
  403. package/dist/m/c-2SPVQSHC.js +0 -1
  404. package/dist/m/c-35NBGMZB.js +0 -1
  405. package/dist/m/c-3IQRECYS.js +0 -1
  406. package/dist/m/c-4B6H4G5V.js +0 -1
  407. package/dist/m/c-4FFHZM32.js +0 -1
  408. package/dist/m/c-4YSCRICO.js +0 -1
  409. package/dist/m/c-54RCDCBU.js +0 -1
  410. package/dist/m/c-5M4YEKCX.js +0 -4
  411. package/dist/m/c-5V3MOVJT.js +0 -1
  412. package/dist/m/c-66O32MV3.js +0 -28
  413. package/dist/m/c-6HMB24EV.js +0 -1
  414. package/dist/m/c-7O6AFR65.js +0 -1
  415. package/dist/m/c-ANQTJ3B5.js +0 -2
  416. package/dist/m/c-AYVOREBE.js +0 -1
  417. package/dist/m/c-B2ZXPOS3.js +0 -3
  418. package/dist/m/c-BMIZKMPV.js +0 -1
  419. package/dist/m/c-BTDI4Y23.js +0 -1
  420. package/dist/m/c-CJZSLMVQ.js +0 -1
  421. package/dist/m/c-DBE6ORSI.js +0 -1
  422. package/dist/m/c-EDHUR4MX.js +0 -1
  423. package/dist/m/c-EY6UOM54.js +0 -1
  424. package/dist/m/c-F3CKJTJS.js +0 -1
  425. package/dist/m/c-F7Y6FLPY.js +0 -1
  426. package/dist/m/c-FA4DZVTC.js +0 -1
  427. package/dist/m/c-G2BUZITR.js +0 -1
  428. package/dist/m/c-GB3JTV6E.js +0 -1
  429. package/dist/m/c-GBGWXTX4.js +0 -1
  430. package/dist/m/c-GHBCCVG4.js +0 -1
  431. package/dist/m/c-GJAEGGXY.js +0 -1
  432. package/dist/m/c-GOWBDM6J.js +0 -1
  433. package/dist/m/c-IB52R237.js +0 -1
  434. package/dist/m/c-IKWNE4R4.js +0 -1
  435. package/dist/m/c-IU3IYEU6.js +0 -1
  436. package/dist/m/c-IZ2EBRAJ.js +0 -1
  437. package/dist/m/c-KR4UW4KW.js +0 -1
  438. package/dist/m/c-KY3VJGUA.js +0 -8
  439. package/dist/m/c-LFPHCXY3.js +0 -1
  440. package/dist/m/c-LRPUO2MX.js +0 -1
  441. package/dist/m/c-LXENEBV7.js +0 -1
  442. package/dist/m/c-NMJRXKPC.js +0 -8
  443. package/dist/m/c-OIBUTDXI.js +0 -1
  444. package/dist/m/c-PPOSCAQD.js +0 -1
  445. package/dist/m/c-QIX5BJYN.js +0 -1
  446. package/dist/m/c-QQHEX6HA.js +0 -1
  447. package/dist/m/c-R7RMANJI.js +0 -9
  448. package/dist/m/c-RI4NT75Y.js +0 -1
  449. package/dist/m/c-RM7J7BV2.js +0 -1
  450. package/dist/m/c-RUJKSUQW.js +0 -1
  451. package/dist/m/c-SGCCC4FW.js +0 -25
  452. package/dist/m/c-T4VO4OCB.js +0 -1
  453. package/dist/m/c-TQEMVJCS.js +0 -1
  454. package/dist/m/c-TR6RMDQ7.js +0 -3
  455. package/dist/m/c-UKHTLWUW.js +0 -1
  456. package/dist/m/c-UNN7LWCT.js +0 -1
  457. package/dist/m/c-UXBMRDZB.js +0 -235
  458. package/dist/m/c-V7PZZRG7.js +0 -1
  459. package/dist/m/c-VL52ADDR.js +0 -1
  460. package/dist/m/c-WNYBLIOZ.js +0 -1
  461. package/dist/m/c-WWDBYXLK.js +0 -1
  462. package/dist/m/c-XNNW2CWK.js +0 -1
  463. package/dist/m/c-Y6USGTS5.js +0 -5
  464. package/dist/m/c-YG2NJYS6.js +0 -135
  465. package/dist/m/c-Z4VRC7GU.js +0 -7
  466. package/dist/m/c-Z6K52OLP.js +0 -19
  467. package/dist/m/c-ZDY2MDAA.js +0 -21
  468. package/dist/m/c-ZUCOKOHT.js +0 -1
  469. package/dist/m/core/admin-attrs-VGNLAUDK.js +0 -1
  470. package/dist/m/core/autosave-E4PEFPZ6.js +0 -1
  471. package/dist/m/core/conflict-notice-VQ6Y64PH.js +0 -1
  472. package/dist/m/core/host-permission-XN3K2IW3.js +0 -1
  473. package/dist/m/core/is-edit-mode-JXHDVGUL.js +0 -1
  474. package/dist/m/core/recovery-TBBSICE2.js +0 -1
  475. package/dist/m/core/save-WUCHTQBE.js +0 -1
  476. package/dist/m/core/save-core-FJVHT5M3.js +0 -1
  477. package/dist/m/core/snapshot-F3SWDZMM.js +0 -1
  478. package/dist/m/core/unsaved-warning-STKBHE6G.js +0 -1
  479. package/dist/m/events/index-23DPTUT4.js +0 -1
  480. package/dist/m/internals/index-WOVAP3RU.js +0 -1
  481. package/dist/m/lib/cache-bust-CGWT6ZJI.js +0 -1
  482. package/dist/m/loader-HXX6YNH5.js +0 -1
  483. package/dist/m/manifest-F22DEDAC.js +0 -1
  484. package/dist/m/manifest-edit-C3C7376B.js +0 -1
  485. package/dist/m/options/options-2LH76JQ3.js +0 -1
  486. package/dist/m/page/editor-XUTB2HB2.js +0 -189
  487. package/dist/m/plugins/ai-edit-ZQ4CGWIP.js +0 -6
  488. package/dist/m/plugins/demo-ZIBFAA3R.js +0 -1
  489. package/dist/m/plugins/indicator-PV4YW2ID.js +0 -1
  490. package/dist/m/plugins/live-NUTTXSBS.js +0 -1
  491. package/dist/m/plugins/menu-HCKO3DSP.js +0 -138
  492. package/dist/m/plugins/page-KSUH7G6J.js +0 -2
  493. package/dist/m/plugins/page-view-W73OW7WI.js +0 -1
  494. package/dist/m/plugins/people-AJJ32YCG.js +0 -1
  495. package/dist/m/plugins/quickcrop-QNY3PKLD.js +0 -26
  496. package/dist/m/plugins/sortable-YRMQX3ZU.js +0 -1
  497. package/dist/m/plugins/source-KHK5UUSK.js +0 -1
  498. package/dist/m/plugins/undo-OF2L3CXU.js +0 -1
  499. package/dist/m/plugins/upgrade-ZNYODP5Y.js +0 -1
  500. package/dist/m/plugins/upload-XSGNILUY.js +0 -1
  501. package/dist/m/plugins/visitor-drafts-XK5GW55Q.js +0 -21
  502. package/dist/m/plugins/visitor-drafts-owner-KSSH75EV.js +0 -1
  503. package/dist/m/plugins/wire-ZWVWKF76.js +0 -1
  504. package/dist/m/reader-core-ILWLXVMP.js +0 -68
  505. package/dist/m/route/route-KOCZSEGN.js +0 -1
  506. package/dist/m/sync/live-sync-4AUVXVJU.js +0 -1
  507. package/dist/m/ui/index-EVHKMKBL.js +0 -1
  508. package/dist/m/vendor/sap-core.vendor-A7BMUYZT.js +0 -6
  509. /package/dist/m/{c-FILLTJA7.js → c-QTTJYJMA.js} +0 -0
  510. /package/dist/m/plugins/{quickcrop-QNY3PKLD.js.LEGAL.txt → quickcrop-BQ5YYFCH.js.LEGAL.txt} +0 -0
  511. /package/dist/m/plugins/{richclay-RC3L2F6X.js.LEGAL.txt → richclay-KLGETK77.js.LEGAL.txt} +0 -0
@@ -1,143 +1,18 @@
1
- import { state, view as pmView } from "../vendor/prosemirror.vendor.js";
2
- import { schema } from "./schema.js";
3
- import { asStep } from "./blocksel.js";
4
- import { el, cardFace } from "./board-face.js";
1
+ // The checklist's keys: `Mod-Shift-o` and `Mod-Shift-d` open an item's owner and
2
+ // due popovers. The controls are a decoration the view part draws and opens, so
3
+ // this asks the parts it finds for a loaded editor and stays out of the rest.
4
+ import { state } from "../vendor/prosemirror.vendor.js";
5
+ import { viewHooks } from "./view-hooks.js";
5
6
 
6
- const { Plugin } = state;
7
- const { Decoration, DecorationSet } = pmView;
8
-
9
- function itemAt(view, id) {
10
- let found = null;
11
- view.state.doc.forEach((node, pos) => { if (node.attrs.id === id && node.type === schema.nodes.list_item && node.attrs.kind === "check") found = { node, pos }; });
12
- return found;
13
- }
14
-
15
- function setItem(view, id, attr, value) {
16
- const found = itemAt(view, id);
17
- if (!found) return;
18
- view.dispatch(asStep(view.state.tr.setNodeMarkup(found.pos, null, { ...found.node.attrs, [attr]: value || null })));
19
- }
20
-
21
- function chip(doc, label, aria, attr) {
22
- const button = doc.createElement("button");
23
- button.type = "button";
24
- button.className = "page-check-chip";
25
- button.dataset.check = attr;
26
- button.textContent = label;
27
- button.setAttribute("aria-label", aria);
28
- return button;
29
- }
30
-
31
- // The same owner field a board card's panel shows: people as chips, "No owner",
32
- // then a name box for anyone not in the list.
33
- function ownerPicker(view, id, anchor) {
34
- const doc = anchor.ownerDocument;
35
- const current = itemAt(view, id)?.node.attrs.owner || "";
36
- const popup = doc.createElement("span");
37
- popup.className = "page-check-popup page-check-owner";
38
- popup.contentEditable = "false";
39
- const box = el(doc, "span", "page-board-owners");
40
- const people = el(doc, "span", "page-board-people", { role: "group", "aria-label": "Owner" });
41
- const input = el(doc, "input", "page-board-owner-input", { type: "text", "aria-label": "Owner name", placeholder: "Or type a name…" });
42
- input.value = current;
43
- const choose = (name) => setItem(view, id, "owner", name);
44
- const person = (name, label, attr, pressed) => {
45
- const button = el(doc, "button", `page-board-person${name ? "" : " is-none"}`, { type: "button", "aria-pressed": String(pressed), "data-check": attr });
46
- if (name) button.append(cardFace(doc, name), el(doc, "span", null, {}, name));
47
- else button.textContent = label;
48
- button.addEventListener("click", () => choose(name));
49
- return button;
50
- };
51
- const draw = (available) => {
52
- const typed = input.value.trim().toLowerCase();
53
- const shown = (available || []).filter((p) => !typed || typed === current.toLowerCase() || p.name.toLowerCase().includes(typed));
54
- people.replaceChildren(...shown.map((p) => person(p.name, p.name, "owner-option", p.name === current)), person(null, "No owner", "clear-owner", !current));
55
- };
56
- input.addEventListener("keydown", (event) => {
57
- if (event.key === "Enter") { event.preventDefault(); choose(input.value.trim()); }
58
- if (event.key === "Escape") { event.preventDefault(); popup.remove(); anchor.focus(); }
59
- event.stopPropagation();
60
- });
61
- box.append(people, input);
62
- popup.append(box);
63
- anchor.after(popup);
64
- input.focus();
65
- let available = [];
66
- draw(available);
67
- input.addEventListener("input", () => draw(available));
68
- const source = globalThis.clay?.people?.available;
69
- if (source) Promise.resolve().then(() => source()).then((list) => { if (popup.isConnected) { available = list || []; draw(available); } }).catch(() => {});
70
- }
71
-
72
- function datePicker(view, id, anchor) {
73
- const doc = anchor.ownerDocument;
74
- const popup = doc.createElement("span");
75
- popup.className = "page-check-popup";
76
- popup.contentEditable = "false";
77
- const input = doc.createElement("input");
78
- input.type = "date";
79
- input.setAttribute("aria-label", "Due date");
80
- input.value = itemAt(view, id)?.node.attrs.due || "";
81
- input.addEventListener("change", () => setItem(view, id, "due", input.value));
82
- input.addEventListener("keydown", (event) => event.stopPropagation());
83
- const clear = chip(doc, "Clear", "Clear due date", "clear-date");
84
- clear.addEventListener("click", () => setItem(view, id, "due", null));
85
- popup.append(input, clear);
86
- anchor.after(popup);
87
- input.focus();
88
- }
89
-
90
- function controls(view, node) {
91
- const doc = view.dom.ownerDocument;
92
- const box = doc.createElement("span");
93
- box.className = "page-check-controls";
94
- box.contentEditable = "false";
95
- box.dataset.empty = String(!node.attrs.owner && !node.attrs.due);
96
- const owner = chip(doc, node.attrs.owner || "Assign", node.attrs.owner ? `Owner: ${node.attrs.owner}` : "Assign owner", "owner");
97
- const dueLabel = node.attrs.due ? new Date(`${node.attrs.due}T00:00:00Z`).toLocaleDateString("en-US", { month: "short", day: "numeric", timeZone: "UTC" }) : "Date";
98
- const date = chip(doc, dueLabel, node.attrs.due ? `Due ${dueLabel}` : "Set due date", "date");
99
- owner.addEventListener("click", () => ownerPicker(view, node.attrs.id, owner));
100
- date.addEventListener("click", () => datePicker(view, node.attrs.id, date));
101
- box.append(owner, date);
102
- return box;
103
- }
104
-
105
- function focusControl(view, name) {
106
- const $pos = view.state.selection.$head;
107
- if ($pos.depth !== 1 || $pos.parent.type !== schema.nodes.list_item || $pos.parent.attrs.kind !== "check") return false;
108
- const id = $pos.parent.attrs.id;
109
- const widget = [...view.dom.querySelectorAll(".page-check-controls")].find((el) => el.dataset.item === id);
110
- const button = widget?.querySelector(`[data-check="${name}"]`);
111
- if (!button) return false;
112
- button.focus();
113
- return true;
114
- }
115
-
116
- export const checklistKeys = new Plugin({
7
+ export const checklistKeys = new state.Plugin({
117
8
  props: {
118
9
  handleKeyDown(view, event) {
119
10
  if (!(event.metaKey || event.ctrlKey) || !event.shiftKey || event.altKey) return false;
120
11
  const name = event.key.toLowerCase() === "o" ? "owner" : event.key.toLowerCase() === "d" ? "date" : null;
121
- return name ? focusControl(view, name) : false;
12
+ if (!name) return false;
13
+ if (view.dom.getAttribute(name === "owner" ? "data-assignees" : "data-dates") === "false") return false;
14
+ for (const hook of viewHooks.get(view) ?? []) if (hook.focusControl?.(view, name)) return true;
15
+ return false;
122
16
  },
123
17
  },
124
18
  });
125
-
126
- export function checklistPlugin() {
127
- return new Plugin({
128
- props: {
129
- decorations(st) {
130
- const widgets = [];
131
- st.doc.forEach((node, pos) => {
132
- if (node.type !== schema.nodes.list_item || node.attrs.kind !== "check") return;
133
- widgets.push(Decoration.widget(pos + node.nodeSize - 1, (view) => {
134
- const box = controls(view, node);
135
- box.dataset.item = node.attrs.id;
136
- return box;
137
- }, { key: `${node.attrs.id}:${node.attrs.owner}:${node.attrs.due}`, side: 1, stopEvent: () => true, ignoreSelection: true }));
138
- });
139
- return DecorationSet.create(st.doc, widgets);
140
- },
141
- },
142
- });
143
- }
@@ -0,0 +1,295 @@
1
+ /**
2
+ * checkpoint-proof.js — the proof one accepted save produces, and the conditional read
3
+ * that answers for bytes this tab did not write.
4
+ *
5
+ * Step 2 built the proof itself: the recorded revisions, the accepted page state, the
6
+ * final article and the receipt, checked by `validatePageCheckpoint` before a store
7
+ * retires anything. What it left here is the producer's half. A proof is made at the
8
+ * accepted-save event, from the page state pinned to THAT capture, never from the live
9
+ * store: an edit made after the capture is in the store and is not in the bytes the host
10
+ * took, and reading the store when the response lands would cover it and clear it.
11
+ *
12
+ * Checklist item 2 lives here too. A state that no longer projects to the article that
13
+ * went to disk proves nothing about those bytes, so an authored transform that changed
14
+ * meaning, a page rebuilt from another article or a stale tag all produce no proof, and
15
+ * the records stay.
16
+ *
17
+ * The second half is the conditional page-state client: the host's own copy of the page
18
+ * state, asked for by the accepted ETag, for the cases a local capture cannot answer —
19
+ * the server's checkpoint, another writer's, and a lost reply that only a verified
20
+ * refetch can reconcile. Nothing here reads a cookie, a room sequence or a version
21
+ * string: a 412, a 428 or a 404 keeps every record, and so does any state whose
22
+ * identity, generation, digest or projection disagrees with what was accepted.
23
+ *
24
+ * It loads only when a recovery channel is registered: nothing else imports it.
25
+ */
26
+
27
+ import { saveToken } from "../core/host-attrs.js";
28
+ import { setCapturePageState } from "../core/snapshot.js";
29
+ import { canonicalPageBytes, digestHex, pageProof, validatePageCheckpoint } from "./ybind/recovery-proof.js";
30
+ import { articleOfPageState, pageStateOfClone } from "./save-projection.js";
31
+
32
+ // The capture half. A save capture carries its page state only once something can prove
33
+ // it, so the reader is installed here rather than from the page bundle: a page that
34
+ // journals nothing never pays for it, and a page that does gets the state of the exact
35
+ // clone whose bytes it sends, after every transform.
36
+ setCapturePageState(pageStateOfClone);
37
+
38
+ /** The only page-state resource version this client reads. */
39
+ export const PAGE_STATE_VERSION = 1;
40
+
41
+ export class PageCheckpointError extends Error {
42
+ constructor(code, message) {
43
+ super(message);
44
+ this.name = "PageCheckpointError";
45
+ this.code = code;
46
+ }
47
+ }
48
+
49
+ const fail = (code, message) => { throw new PageCheckpointError(code, message); };
50
+ const refused = (code, message) => ({ ok: false, code, message });
51
+ const HEX64 = /^[0-9a-f]{64}$/;
52
+ const GENERATION = /^[0-9a-f]{32}$/;
53
+
54
+ const isObject = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
55
+
56
+ // The state as this tab holds it, copied: a pinned capture's value shares its `y` map
57
+ // with the store's at the moment of the copy and must not share it after.
58
+ function copyState(state) {
59
+ return { v: state.v, g: state.g, y: { ...(state.y ?? {}) } };
60
+ }
61
+
62
+ // The state the bytes are decoded from, strictly, and its canonical form.
63
+ function stateOfTag(tag) {
64
+ let state;
65
+ try { state = JSON.parse(tag); }
66
+ catch (_) { fail("invalid-state", "The page state is not JSON"); }
67
+ if (!isObject(state) || state.v !== 1) fail("invalid-state", "Expected a version 1 Yjs page value");
68
+ return { state: copyState(state), bytes: canonicalPageBytes(state) };
69
+ }
70
+
71
+ const idKey = (value) => (value === null || value === undefined ? "" : String(value));
72
+
73
+ /**
74
+ * Ask the host for the page state of one accepted version, conditionally.
75
+ *
76
+ * The ETag is the one the accepted save carried, so the answer is about those exact
77
+ * bytes or about nothing. 412 (another version is stored), 428 (no condition was sent,
78
+ * which no answer of ours can be), 404 (this host keeps no page state) and every other
79
+ * failure are refusals with a code: the caller keeps its records and asks again later.
80
+ *
81
+ * @param {Object} options
82
+ * @param {string} options.href - the resource discovery named in `document.pageState.href`
83
+ * @param {string} options.etag - the ETag the accepted bytes were stored under
84
+ * @param {Function} [options.fetch]
85
+ * @param {AbortSignal} [options.signal]
86
+ * @returns {Promise<{ok: true, version: number, documentId: *, generation: string, etag: string, stateHash: string, tag: string}|{ok: false, code: string, message: string}>}
87
+ */
88
+ export async function readPageState({ href, etag, fetch: doFetch = globalThis.fetch, signal = null } = {}) {
89
+ if (typeof etag !== "string" || !etag) return refused("missing-etag", "A conditional read needs the accepted ETag");
90
+ if (typeof href !== "string" || !href) return refused("no-page-state", "This host names no page-state resource");
91
+ if (typeof doFetch !== "function") return refused("unavailable", "This tab cannot read the page state");
92
+ const base = globalThis.location?.href ?? "http://localhost/";
93
+ let url;
94
+ try { url = new URL(href, base).href; }
95
+ catch (_) { return refused("no-page-state", "The page-state resource is not a URL"); }
96
+ const token = saveToken();
97
+ let res;
98
+ try {
99
+ res = await doFetch(url, {
100
+ method: "GET",
101
+ credentials: token ? "omit" : "same-origin",
102
+ headers: { "If-Match": etag, "Document-URL": globalThis.location?.href ?? base },
103
+ cache: "no-store",
104
+ redirect: "manual",
105
+ signal,
106
+ });
107
+ } catch (_) {
108
+ return refused("unavailable", "The page-state request did not complete");
109
+ }
110
+ if (!res || typeof res.status !== "number") return refused("unavailable", "The page-state request did not answer");
111
+ if (res.status === 412) return refused("etag-mismatch", "Another version of this document is stored");
112
+ if (res.status === 428) return refused("etag-required", "The host needs a condition this read cannot send");
113
+ if (res.status === 404) return refused("no-page-state", "This host keeps no page state");
114
+ if (!res.ok) return refused("unavailable", `The host answered ${res.status}`);
115
+
116
+ let body;
117
+ try { body = JSON.parse(await res.text()); }
118
+ catch (_) { return refused("invalid-state", "The page-state answer is not JSON"); }
119
+ if (!isObject(body) || body.version !== PAGE_STATE_VERSION) return refused("unsupported-version", "Unknown page-state version");
120
+ if (typeof body.tag !== "string" || !body.tag) return refused("invalid-state", "The page-state answer carries no page tag");
121
+ if (typeof body.stateHash !== "string" || !HEX64.test(body.stateHash)) return refused("invalid-state", "The page-state answer carries no state digest");
122
+ if (typeof body.generation !== "string" || !GENERATION.test(body.generation)) return refused("invalid-state", "The page-state answer carries no generation");
123
+ if (typeof body.etag !== "string" || !body.etag) return refused("invalid-state", "The page-state answer carries no ETag");
124
+ if (!Object.hasOwn(body, "documentId")) return refused("invalid-state", "The page-state answer carries no document identity");
125
+ return {
126
+ ok: true,
127
+ version: body.version,
128
+ documentId: body.documentId,
129
+ generation: body.generation,
130
+ etag: body.etag,
131
+ stateHash: body.stateHash,
132
+ tag: body.tag,
133
+ };
134
+ }
135
+
136
+ /**
137
+ * Whether a conditional read answers for the version this tab accepted.
138
+ *
139
+ * Identity, generation, ETag, digest and projection, in that order: a state for another
140
+ * document, another generation, another version of the same document or another article
141
+ * is refused rather than used, and the receipt names the pair the host persisted.
142
+ *
143
+ * A host that gives the document no identity of its own answers null, and a page pinned
144
+ * by path accepts that answer as its own: there is no id it could have answered instead.
145
+ * Every other mismatch is still another document.
146
+ *
147
+ * @param {Object} read - an answer from readPageState()
148
+ * @param {Object} options
149
+ * @param {{authority: Object, lineage: Object}} options.accepted
150
+ * @param {string} options.article - the final outgoing canonical article
151
+ * @param {string} options.etag - the ETag the accepted save carried
152
+ * @returns {Promise<{ok: true, state: Object, receipt: {file: string, pageState: string}}|{ok: false, code: string, message: string}>}
153
+ */
154
+ export async function verifyPageState(read, { accepted, article, etag } = {}) {
155
+ if (!read || read.ok !== true) return refused(read?.code ?? "unavailable", read?.message ?? "The page state was not read");
156
+ const authority = accepted?.authority;
157
+ const byPath = authority?.scope === "path" && read.documentId == null;
158
+ if (!byPath && idKey(read.documentId) !== idKey(authority?.documentId)) return refused("foreign-document", "The page state belongs to another document");
159
+ if (typeof accepted?.lineage?.generation !== "string" || read.generation !== accepted.lineage.generation) return refused("foreign-generation", "The page state belongs to another generation");
160
+ if (read.etag !== etag) return refused("etag-mismatch", "The page state belongs to another version of this document");
161
+ let stateBytes, state;
162
+ try { ({ state, bytes: stateBytes } = stateOfTag(read.tag)); }
163
+ catch (error) { return refused(error?.code ?? "invalid-state", error?.message ?? "The page state does not decode"); }
164
+ if (await digestHex(stateBytes) !== read.stateHash) return refused("state-digest-mismatch", "The page state is not the one the host hashed");
165
+ if (typeof article !== "string") return refused("missing-article", "A proof names the final outgoing article");
166
+ let projected;
167
+ try { projected = articleOfPageState(state); }
168
+ catch (error) { return refused("invalid-state", error?.message ?? "The page state does not project"); }
169
+ if (projected !== article) return refused("article-mismatch", "The page state does not project to the accepted article");
170
+ return { ok: true, state, receipt: { file: read.etag, pageState: read.stateHash } };
171
+ }
172
+
173
+ // A proof is only worth the records the validator confirms, over the revisions the
174
+ // caller observed: a proof that covers nothing keeps everything.
175
+ async function covered(proof, records) {
176
+ const observed = (records || []).map(({ id, revision }) => ({ id, revision }));
177
+ try {
178
+ return { ok: true, proof, covered: await validatePageCheckpoint(proof, observed) };
179
+ } catch (error) {
180
+ return refused(error?.code ?? "invalid-proof", error?.message ?? "The proof does not validate");
181
+ }
182
+ }
183
+
184
+ /**
185
+ * The proof of one accepted save, from the page state pinned to its capture.
186
+ *
187
+ * The receipt is the accepted ETag and the digest of the state the proof is made over,
188
+ * which is this tab's own account that this capture was written and hashed. Nothing
189
+ * about a later edit, a store value or an acknowledgement reaches this function.
190
+ *
191
+ * @param {Object} options
192
+ * @param {string} options.capture - the capture the save belongs to
193
+ * @param {Array<Object>} options.records - {id, revision, update, context} the caller observed
194
+ * @param {{authority: Object, lineage: Object}} options.accepted
195
+ * @param {string} options.article - the final outgoing canonical article
196
+ * @param {Object} options.state - the page state pinned to that capture
197
+ * @param {string} options.etag - the ETag the accepted save carried
198
+ * @returns {Promise<{ok: true, proof: Object, covered: Array}|{ok: false, code: string, message: string}>}
199
+ */
200
+ export async function captureProof({ capture, records = [], accepted, article, state = null, etag = null } = {}) {
201
+ if (!state) return refused("no-capture-state", "No page state was pinned to this capture");
202
+ if (typeof etag !== "string" || !etag) return refused("missing-etag", "An accepted save with no ETag proves no stored pair");
203
+ if (typeof article !== "string") return refused("missing-article", "The accepted bytes hold no article");
204
+ let projected, stateBytes;
205
+ try {
206
+ projected = articleOfPageState(state);
207
+ stateBytes = canonicalPageBytes(state);
208
+ } catch (error) {
209
+ return refused(error?.code ?? "invalid-state", error?.message ?? "The pinned page state does not decode");
210
+ }
211
+ if (projected !== article) return refused("article-mismatch", "The pinned page state does not project to the accepted article");
212
+ let proof;
213
+ try {
214
+ proof = await pageProof({
215
+ capture,
216
+ records,
217
+ accepted,
218
+ article,
219
+ state,
220
+ receipt: { file: etag, pageState: await digestHex(stateBytes) },
221
+ });
222
+ } catch (error) {
223
+ return refused(error?.code ?? "invalid-proof", error?.message ?? "The proof could not be built");
224
+ }
225
+ return covered(proof, records);
226
+ }
227
+
228
+ /**
229
+ * The proof of an accepted save, rebuilt from the page state the host persisted.
230
+ *
231
+ * The answer for the server's checkpoint, another writer's, and a save whose reply was
232
+ * lost: identity, generation and digest are checked against that exact ETag first, and
233
+ * only then is coverage asked. A host that cannot answer leaves the journal alone.
234
+ *
235
+ * @param {Object} options
236
+ * @param {Object} options.read - an answer from readPageState()
237
+ * @returns {Promise<{ok: true, proof: Object, covered: Array}|{ok: false, code: string, message: string}>}
238
+ */
239
+ export async function refetchedProof({ read, capture, records = [], accepted, article, etag = null } = {}) {
240
+ const verified = await verifyPageState(read, { accepted, article, etag });
241
+ if (!verified.ok) return verified;
242
+ let proof;
243
+ try {
244
+ proof = await pageProof({ capture, records, accepted, article, state: verified.state, receipt: verified.receipt });
245
+ } catch (error) {
246
+ return refused(error?.code ?? "invalid-proof", error?.message ?? "The proof could not be built");
247
+ }
248
+ return covered(proof, records);
249
+ }
250
+
251
+ /**
252
+ * Retire exactly the records one accepted save covers.
253
+ *
254
+ * The pinned capture is tried first, because it needs no request and no host. Only a
255
+ * capture that cannot prove these bytes asks the host, conditionally on the accepted
256
+ * ETag. Whichever answers, the channel is handed the caller's observed list and the
257
+ * proof together: it narrows the list to what the proof covers and never widens it, and
258
+ * a refusal here retires nothing.
259
+ *
260
+ * @param {Object} options
261
+ * @param {Object} options.channel - the `page.yjs` channel api
262
+ * @param {Array<Object>} options.records - {id, revision, update, context} the caller observed
263
+ * @param {string} options.capture
264
+ * @param {{authority: Object, lineage: Object}} options.accepted
265
+ * @param {string} options.article
266
+ * @param {Object} [options.state] - the page state pinned to the capture
267
+ * @param {string} options.etag
268
+ * @param {?string} [options.href] - the page-state resource, or null
269
+ * @param {Function} [options.read]
270
+ * @param {Function} [options.fetch]
271
+ * @returns {Promise<{ok: boolean, retired: Array, proof: ?Object, code?: string}>}
272
+ */
273
+ export async function checkpointAccepted({
274
+ channel,
275
+ records = [],
276
+ capture,
277
+ accepted,
278
+ article,
279
+ state = null,
280
+ etag = null,
281
+ href = null,
282
+ read = readPageState,
283
+ fetch: doFetch,
284
+ } = {}) {
285
+ if (!channel || typeof channel.checkpoint !== "function") return { ok: false, code: "no-channel", retired: [], proof: null };
286
+ let proven = await captureProof({ capture, records, accepted, article, state, etag });
287
+ if (!proven.ok) {
288
+ const answer = await read({ href, etag, fetch: doFetch });
289
+ proven = await refetchedProof({ read: answer, capture, records, accepted, article, etag });
290
+ }
291
+ if (!proven.ok) return { ok: false, code: proven.code, retired: [], proof: null };
292
+ const answer = await channel.checkpoint({ records: proven.covered, proof: proven.proof });
293
+ if (!answer || answer.ok !== true) return { ok: false, code: answer?.code ?? "checkpoint-failed", retired: [], proof: proven.proof };
294
+ return { ok: true, retired: answer.retired ?? [], proof: proven.proof };
295
+ }