@panphora/clayjs 1.15.2 → 1.17.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 (551) hide show
  1. package/README.md +4 -5
  2. package/THIRD-PARTY-NOTICES.md +16 -0
  3. package/compat/highlight-11.9.0.min.js +1213 -0
  4. package/compat/highlight-LICENSE +29 -0
  5. package/compat/journal-assets.lock.json +22 -0
  6. package/compat/overtype-2.6.1.min.js +116 -0
  7. package/compat/overtype-LICENSE +21 -0
  8. package/dist/all.js +90 -0
  9. package/dist/clay-dom.js +84 -0
  10. package/dist/clay-events.js +103 -0
  11. package/dist/clay-internals.js +85 -0
  12. package/dist/clay-options.js +93 -0
  13. package/dist/clay-router.js +98 -0
  14. package/dist/clay-ui.js +95 -0
  15. package/dist/clay-utils.js +84 -0
  16. package/dist/clay.js +146 -0
  17. package/dist/clay.standalone.js +84291 -57611
  18. package/dist/m/attrs/add-from-T5LJXFFI.js +1 -0
  19. package/dist/m/attrs/onaftersave-OKSWEJQF.js +1 -0
  20. package/dist/m/attrs/refetch-on-save-ZUE7PSCI.js +1 -0
  21. package/dist/m/attrs/save-freeze-MYNVXREN.js +1 -0
  22. package/dist/m/c-2AZDZWCK.js +1 -0
  23. package/dist/m/c-2SPVQSHC.js +1 -0
  24. package/dist/m/c-2YYDMLNZ.js +1 -0
  25. package/dist/m/c-336SGU5W.js +1 -0
  26. package/dist/m/c-33JJ6CBX.js +2 -0
  27. package/dist/m/c-3GJ4SDE2.js +1 -0
  28. package/dist/m/c-3H6BZDNJ.js +6 -0
  29. package/dist/m/c-3OWDMNJR.js +2 -0
  30. package/dist/m/c-3RUU4TPX.js +91 -0
  31. package/dist/m/c-3XIRD4NU.js +1 -0
  32. package/dist/m/c-46XYM55L.js +1 -0
  33. package/dist/m/c-4DFEJ2PO.js +3 -0
  34. package/dist/m/c-4JZDZFF6.js +1 -0
  35. package/dist/m/c-4L3BTFRK.js +1 -0
  36. package/dist/m/c-4Q2XTQ2N.js +2 -0
  37. package/dist/m/c-4WKFKEKF.js +1 -0
  38. package/dist/m/c-56QRZTZH.js +1 -0
  39. package/dist/m/c-5J4E7O4Y.js +4 -0
  40. package/dist/m/c-5LKDWQTL.js +19 -0
  41. package/dist/m/c-5QWQ7A5L.js +1 -0
  42. package/dist/m/c-5RJFTLSF.js +1 -0
  43. package/dist/m/c-5V3MOVJT.js +1 -0
  44. package/dist/m/c-5VYOWDTI.js +1 -0
  45. package/dist/m/c-5YIHJ3TJ.js +1 -0
  46. package/dist/m/c-6AZOGAPE.js +1 -0
  47. package/dist/m/c-6CRUUXOX.js +5 -0
  48. package/dist/m/c-6HMB24EV.js +1 -0
  49. package/dist/m/c-6KEVEQ7L.js +1 -0
  50. package/dist/m/c-6NC5I2TV.js +1 -0
  51. package/dist/m/c-74MGYWZF.js +1 -0
  52. package/dist/m/c-7DJDKPGL.js +1 -0
  53. package/dist/m/c-7OLLJPWP.js +1 -0
  54. package/dist/m/c-7SWMYKAI.js +1 -0
  55. package/dist/m/c-A3E3NIMC.js +1 -0
  56. package/dist/m/c-A7LS5UZW.js +1 -0
  57. package/dist/m/c-AMSNPOCX.js +1 -0
  58. package/dist/m/c-AQLFEATB.js +1 -0
  59. package/dist/m/c-B3JM5ZXC.js +1 -0
  60. package/dist/m/c-BBVGTQG7.js +1 -0
  61. package/dist/m/c-BPF2NSPV.js +1 -0
  62. package/dist/m/c-BQ6LOBEE.js +1 -0
  63. package/dist/m/c-BTDI4Y23.js +1 -0
  64. package/dist/m/c-BWFVSBDF.js +1 -0
  65. package/dist/m/c-BYTKL6UX.js +1 -0
  66. package/dist/m/c-CFHW5AMY.js +6 -0
  67. package/dist/m/c-CJ3A7ZKE.js +1 -0
  68. package/dist/m/c-CQHPGKX2.js +1 -0
  69. package/dist/m/c-CYWLPPUS.js +1 -0
  70. package/dist/m/c-CZUSOB7V.js +1 -0
  71. package/dist/m/c-DCFSEJZG.js +1 -0
  72. package/dist/m/c-DCXOXBF3.js +1 -0
  73. package/dist/m/c-DFS4XZJV.js +2 -0
  74. package/dist/m/c-DNENWY4O.js +1 -0
  75. package/dist/m/c-DNZ5IFM6.js +1 -0
  76. package/dist/m/c-E4LIR5X5.js +1 -0
  77. package/dist/m/c-EJTGYSLN.js +1 -0
  78. package/dist/m/c-EVCQ3BKA.js +1 -0
  79. package/dist/m/c-EZU56NQV.js +34 -0
  80. package/dist/m/c-FDNHHZY5.js +1 -0
  81. package/dist/m/c-FILLTJA7.js +1 -0
  82. package/dist/m/c-FRLHSY7E.js +1 -0
  83. package/dist/m/c-FROJUZMV.js +1 -0
  84. package/dist/m/c-G2JPKCTP.js +1 -0
  85. package/dist/m/c-GB6TU7WV.js +1 -0
  86. package/dist/m/c-GFJJEBTU.js +1 -0
  87. package/dist/m/c-GJAEGGXY.js +1 -0
  88. package/dist/m/c-GOWBDM6J.js +1 -0
  89. package/dist/m/c-GQ6F6IDK.js +1 -0
  90. package/dist/m/c-GSFIGKOP.js +1 -0
  91. package/dist/m/c-GY422N3Q.js +1 -0
  92. package/dist/m/c-H4LG442Q.js +1 -0
  93. package/dist/m/c-HA6INT6J.js +1 -0
  94. package/dist/m/c-HAWFNBYG.js +1 -0
  95. package/dist/m/c-HDDEEL77.js +1 -0
  96. package/dist/m/c-HFKUVACH.js +1 -0
  97. package/dist/m/c-HHDFCNHV.js +1 -0
  98. package/dist/m/c-HPURYVZS.js +2 -0
  99. package/dist/m/c-I2ZATQCD.js +2 -0
  100. package/dist/m/c-IEM4NTCS.js +1 -0
  101. package/dist/m/c-IJUK2GPW.js +1 -0
  102. package/dist/m/c-IW6GGOU2.js +1 -0
  103. package/dist/m/c-JAX2667S.js +6 -0
  104. package/dist/m/c-JDCX26UP.js +25 -0
  105. package/dist/m/c-JRKY44DK.js +1 -0
  106. package/dist/m/c-JU3NK25H.js +1 -0
  107. package/dist/m/c-KAU6E43D.js +1 -0
  108. package/dist/m/c-KBS3UV6B.js +1 -0
  109. package/dist/m/c-KGVGROZT.js +1 -0
  110. package/dist/m/c-KI7M4AY3.js +1 -0
  111. package/dist/m/c-KIPQJX4B.js +1 -0
  112. package/dist/m/c-KQ6TYJAP.js +135 -0
  113. package/dist/m/c-KU3ZFADW.js +1 -0
  114. package/dist/m/c-L4WQSC27.js +1 -0
  115. package/dist/m/c-LFPHCXY3.js +1 -0
  116. package/dist/m/c-LRPUO2MX.js +1 -0
  117. package/dist/m/c-LWLBHLVC.js +3 -0
  118. package/dist/m/c-LZJPNR2W.js +1 -0
  119. package/dist/m/c-M2L47S2X.js +7 -0
  120. package/dist/m/c-MFP2ZWD4.js +1 -0
  121. package/dist/m/c-MKSQ4Y57.js +1 -0
  122. package/dist/m/c-MTQHWQC3.js +1 -0
  123. package/dist/m/c-N326R2JD.js +1 -0
  124. package/dist/m/c-N7JPUPGC.js +1 -0
  125. package/dist/m/c-NRPJEVJS.js +1 -0
  126. package/dist/m/c-NUFFJVVJ.js +1 -0
  127. package/dist/m/c-NUHP2XDB.js +1 -0
  128. package/dist/m/c-NUHSPT5P.js +1 -0
  129. package/dist/m/c-O72HVYSA.js +1 -0
  130. package/dist/m/c-OCSZ4QGN.js +1 -0
  131. package/dist/m/c-OIBUTDXI.js +1 -0
  132. package/dist/m/c-OIDRZKAF.js +1 -0
  133. package/dist/m/c-OW5QIGG3.js +1 -0
  134. package/dist/m/c-P7KNDG34.js +1 -0
  135. package/dist/m/c-PGRCK73F.js +1 -0
  136. package/dist/m/c-PQ5LK6QE.js +993 -0
  137. package/dist/m/c-PS5ARVWR.js +9 -0
  138. package/dist/m/c-PZ4XEPUP.js +2 -0
  139. package/dist/m/c-Q622NS2Y.js +1 -0
  140. package/dist/m/c-Q7NQD65V.js +1 -0
  141. package/dist/m/c-QH63LQOS.js +1 -0
  142. package/dist/m/c-QHIWXHRO.js +1 -0
  143. package/dist/m/c-QHWJ5ZIJ.js +1 -0
  144. package/dist/m/c-QICP2YR5.js +1 -0
  145. package/dist/m/c-QJ4OM3G6.js +1 -0
  146. package/dist/m/c-QNKREOL6.js +1 -0
  147. package/dist/m/c-R3L7ZJGB.js +2 -0
  148. package/dist/m/c-R7IWVINQ.js +1 -0
  149. package/dist/m/c-RM7J7BV2.js +1 -0
  150. package/dist/m/c-RSMOODDU.js +1 -0
  151. package/dist/m/c-RVMWGXEJ.js +1 -0
  152. package/dist/m/c-RYUXUUBX.js +1 -0
  153. package/dist/m/c-S4ZEH46B.js +9 -0
  154. package/dist/m/c-S7H2DRCB.js +1 -0
  155. package/dist/m/c-S7LK4KUE.js +1 -0
  156. package/dist/m/c-SDAXDM2D.js +1 -0
  157. package/dist/m/c-SGIA4IEQ.js +188 -0
  158. package/dist/m/c-SMBZG5X4.js +1 -0
  159. package/dist/m/c-SMGU7RT3.js +235 -0
  160. package/dist/m/c-SPIORRYS.js +1 -0
  161. package/dist/m/c-SWLRT66R.js +1 -0
  162. package/dist/m/c-SZGYW5HT.js +1 -0
  163. package/dist/m/c-TCCA5BZN.js +2 -0
  164. package/dist/m/c-TCJTVAFN.js +1 -0
  165. package/dist/m/c-TJ6WOJL3.js +67 -0
  166. package/dist/m/c-TQEMVJCS.js +1 -0
  167. package/dist/m/c-TRS6TM6O.js +1 -0
  168. package/dist/m/c-TYLXX4NF.js +21 -0
  169. package/dist/m/c-U62OB4BT.js +1 -0
  170. package/dist/m/c-U6YWUAR3.js +8 -0
  171. package/dist/m/c-UDCSU5G5.js +2 -0
  172. package/dist/m/c-UDUUJPFZ.js +1 -0
  173. package/dist/m/c-UGHH4MB3.js +1 -0
  174. package/dist/m/c-UL2YUY52.js +1 -0
  175. package/dist/m/c-VAK7SG37.js +1 -0
  176. package/dist/m/c-VDSS4K3Z.js +1 -0
  177. package/dist/m/c-VE6ZMYMC.js +1 -0
  178. package/dist/m/c-VG5XIN4T.js +1 -0
  179. package/dist/m/c-VHM5XQHK.js +1 -0
  180. package/dist/m/c-VHNSBD2M.js +1 -0
  181. package/dist/m/c-VI5RMSVY.js +1 -0
  182. package/dist/m/c-VKEDWYDF.js +1 -0
  183. package/dist/m/c-VRZN56XY.js +1 -0
  184. package/dist/m/c-W5MCHEGA.js +1 -0
  185. package/dist/m/c-WDC5GVKU.js +1 -0
  186. package/dist/m/c-WN5AY3OZ.js +1 -0
  187. package/dist/m/c-WNYBLIOZ.js +1 -0
  188. package/dist/m/c-WRBKEJD4.js +1 -0
  189. package/dist/m/c-WWXIDH4O.js +28 -0
  190. package/dist/m/c-WYFDLITL.js +1 -0
  191. package/dist/m/c-XCNTK76U.js +1 -0
  192. package/dist/m/c-XMS6DPBR.js +1 -0
  193. package/dist/m/c-XN6MBKI6.js +67 -0
  194. package/dist/m/c-XPELB6E5.js +1 -0
  195. package/dist/m/c-XQXZAO7N.js +1 -0
  196. package/dist/m/c-Y5HBBIQ4.js +1 -0
  197. package/dist/m/c-ZE46QDIH.js +1 -0
  198. package/dist/m/c-ZUCOKOHT.js +1 -0
  199. package/dist/m/c-ZVPVXTJF.js +4 -0
  200. package/dist/m/c-ZVRKXKM7.js +1 -0
  201. package/dist/m/c-ZWIOJGMT.js +1 -0
  202. package/dist/m/core/admin-attrs-QHOKPJ7W.js +1 -0
  203. package/dist/m/core/autosave-HRYGUWV6.js +1 -0
  204. package/dist/m/core/conflict-notice-6MROJN2L.js +1 -0
  205. package/dist/m/core/draft-mode-WSQAT67S.js +1 -0
  206. package/dist/m/core/host-permission-FNXVEFNE.js +1 -0
  207. package/dist/m/core/is-edit-mode-KTWA3O5N.js +1 -0
  208. package/dist/m/core/persist-JFYRHMRJ.js +3 -0
  209. package/dist/m/core/recovery-FLTCC2OC.js +1 -0
  210. package/dist/m/core/save-HQZ4S667.js +1 -0
  211. package/dist/m/core/save-core-KGQWFUNH.js +1 -0
  212. package/dist/m/core/snapshot-EGQZNT45.js +1 -0
  213. package/dist/m/core/unsaved-warning-IDJLD66H.js +1 -0
  214. package/dist/m/dom/all-APS5ZSKP.js +1 -0
  215. package/dist/m/dom/dom-helpers-L7OSR6S4.js +1 -0
  216. package/dist/m/events/index-RN2JNGBV.js +1 -0
  217. package/dist/m/internals/index-PVXGPUDA.js +1 -0
  218. package/dist/m/lib/cache-bust-MUMFXZK6.js +1 -0
  219. package/dist/m/lib/region-policy-SB2AWVG5.js +1 -0
  220. package/dist/m/loader-S4I7GKXR.js +1 -0
  221. package/dist/m/manifest-4C419D67.js +1 -0
  222. package/dist/m/manifest-edit-B077F288.js +1 -0
  223. package/dist/m/manifest.json +3988 -0
  224. package/dist/m/options/options-EJMS3K24.js +1 -0
  225. package/dist/m/page/editor-NNJA4OKD.js +202 -0
  226. package/dist/m/plugins/ai-edit-QN4PMMNL.js +6 -0
  227. package/dist/m/plugins/bind-text-NX46N5UV.js +1 -0
  228. package/dist/m/plugins/cms-HP3LSTPN.js +2293 -0
  229. package/dist/m/plugins/demo-RM5IR6AO.js +1 -0
  230. package/dist/m/plugins/derive-UAAA4V4S.js +1 -0
  231. package/dist/m/plugins/indicator-URDBBOQO.js +1 -0
  232. package/dist/m/plugins/live-HFWD4OIH.js +1 -0
  233. package/dist/m/plugins/menu-3HIEQSI6.js +138 -0
  234. package/dist/m/plugins/message-LQ6EDH4V.js +1 -0
  235. package/dist/m/plugins/page-JOKB6XPR.js +2 -0
  236. package/dist/m/plugins/page-view-NXOJHNW6.js +1 -0
  237. package/dist/m/plugins/people-SGPJKANK.js +1 -0
  238. package/dist/m/plugins/quickcrop-QNY3PKLD.js +26 -0
  239. package/dist/m/plugins/quickcrop-QNY3PKLD.js.LEGAL.txt +7 -0
  240. package/dist/m/plugins/richclay-NRQDSWX6.js +517 -0
  241. package/dist/m/plugins/richclay-NRQDSWX6.js.LEGAL.txt +1 -0
  242. package/dist/m/plugins/sortable-GWT4PWDV.js +1 -0
  243. package/dist/m/plugins/source-4ZRSAIYM.js +1 -0
  244. package/dist/m/plugins/undo-VDA5P7FH.js +1 -0
  245. package/dist/m/plugins/unsent-HJFLQJ2O.js +1 -0
  246. package/dist/m/plugins/upgrade-W7FG5VFG.js +1 -0
  247. package/dist/m/plugins/upload-WLYKK6TL.js +1 -0
  248. package/dist/m/plugins/visitor-drafts-LIDY7NH6.js +21 -0
  249. package/dist/m/plugins/visitor-drafts-owner-ZXCKBZE7.js +1 -0
  250. package/dist/m/plugins/wire-4N6GYAF6.js +1 -0
  251. package/dist/m/reader-core-DXYCURFF.js +68 -0
  252. package/dist/m/route/route-KOCZSEGN.js +1 -0
  253. package/dist/m/sync/live-sync-J7VMYTKS.js +1 -0
  254. package/dist/m/ui/index-3JLVI27O.js +1 -0
  255. package/dist/m/utils/index-4TJCVLT7.js +1 -0
  256. package/dist/m/vendor/Sortable.vendor-A755ITLN.js +3 -0
  257. package/dist/m/vendor/Sortable.vendor-A755ITLN.js.LEGAL.txt +1 -0
  258. package/dist/m/vendor/sap-core.vendor-A7BMUYZT.js +6 -0
  259. package/dist/offline-manifest.json +4034 -0
  260. package/dist/page/themes/light-1.css +430 -0
  261. package/dist/server/core.cjs +23748 -22855
  262. package/dist/server/offline-runtime.cjs +4954 -0
  263. package/dist/server/offline-runtime.mjs +4923 -0
  264. package/dist/server/rooms.mjs +9 -0
  265. package/docs/guide.md +1078 -669
  266. package/entries/all.js +2 -0
  267. package/entries/clay-data.js +1 -1
  268. package/entries/clay-dom.js +2 -0
  269. package/entries/clay-events.js +2 -0
  270. package/entries/clay-internals.js +2 -0
  271. package/entries/clay-options.js +2 -0
  272. package/entries/clay-router.js +2 -0
  273. package/entries/clay-ui.js +2 -0
  274. package/entries/clay-utils.js +2 -0
  275. package/entries/clay.js +2 -0
  276. package/entries/sap.js +6 -6
  277. package/fonts/ibm-plex-mono-LICENSE +93 -0
  278. package/fonts/ibm-plex-mono-latin-300-normal.woff2 +0 -0
  279. package/fonts/ibm-plex-mono-latin-400-italic.woff2 +0 -0
  280. package/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
  281. package/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
  282. package/fonts/ibm-plex-mono-latin-600-normal.woff2 +0 -0
  283. package/fonts/ibm-plex-sans-LICENSE +93 -0
  284. package/fonts/ibm-plex-sans-latin-400-normal.woff2 +0 -0
  285. package/fonts/ibm-plex-sans-latin-500-normal.woff2 +0 -0
  286. package/fonts/ibm-plex-sans-latin-600-normal.woff2 +0 -0
  287. package/fonts/ibm-plex.css +71 -0
  288. package/fonts/inter-LICENSE +93 -0
  289. package/fonts/inter-cyrillic-ext-wght-normal.woff2 +0 -0
  290. package/fonts/inter-cyrillic-wght-normal.woff2 +0 -0
  291. package/fonts/inter-greek-ext-wght-normal.woff2 +0 -0
  292. package/fonts/inter-greek-wght-normal.woff2 +0 -0
  293. package/fonts/inter-latin-ext-wght-normal.woff2 +0 -0
  294. package/fonts/inter-latin-wght-normal.woff2 +0 -0
  295. package/fonts/inter-vietnamese-wght-normal.woff2 +0 -0
  296. package/fonts/inter.css +62 -0
  297. package/fonts/newsreader-LICENSE +93 -0
  298. package/fonts/newsreader-latin-ext-opsz-normal.woff2 +0 -0
  299. package/fonts/newsreader-latin-opsz-normal.woff2 +0 -0
  300. package/fonts/newsreader-vietnamese-opsz-normal.woff2 +0 -0
  301. package/fonts/newsreader.css +26 -0
  302. package/fonts/work-sans-LICENSE +93 -0
  303. package/fonts/work-sans-latin-wght-normal.woff2 +0 -0
  304. package/fonts/work-sans.css +8 -0
  305. package/package.json +20 -3
  306. package/packed-contract.json +1 -0
  307. package/src/attrs/add-from.js +27 -4
  308. package/src/attrs/save-freeze.js +7 -52
  309. package/src/core/admin-attrs.js +119 -12
  310. package/src/core/admin-contenteditable.js +12 -10
  311. package/src/core/admin-inputs.js +14 -15
  312. package/src/core/admin-onclick.js +2 -0
  313. package/src/core/autosave.js +74 -21
  314. package/src/core/boot-phase.js +2 -1
  315. package/src/core/conflict-download.js +19 -23
  316. package/src/core/conflict-notice-ui.js +1173 -0
  317. package/src/core/conflict-notice.js +140 -697
  318. package/src/core/download.js +32 -0
  319. package/src/core/edit-mode.js +147 -14
  320. package/src/core/etag.js +30 -12
  321. package/src/core/host-meta.js +90 -8
  322. package/src/core/host-permission.js +29 -0
  323. package/src/core/is-edit-mode.js +145 -12
  324. package/src/core/mode-key.js +125 -0
  325. package/src/core/mode-lifecycle.js +544 -0
  326. package/src/core/mode-receipt.js +30 -0
  327. package/src/core/mode-refusal.js +13 -0
  328. package/src/core/mode-resources.js +52 -0
  329. package/src/core/mode-support.js +82 -0
  330. package/src/core/mode-switch.js +259 -0
  331. package/src/core/mode-transition.js +112 -0
  332. package/src/core/page-data-write.js +305 -0
  333. package/src/core/page-data.js +26 -227
  334. package/src/core/page-format-wire.js +4 -0
  335. package/src/core/page-format.js +58 -0
  336. package/src/core/persist.js +37 -9
  337. package/src/core/preserve-authored.js +114 -0
  338. package/src/core/recovery.js +772 -0
  339. package/src/core/runtime-url.js +105 -0
  340. package/src/core/save-core.js +113 -12
  341. package/src/core/save-replace.js +72 -0
  342. package/src/core/save-size-notice.js +134 -0
  343. package/src/core/save-size.js +140 -0
  344. package/src/core/save.js +806 -119
  345. package/src/core/snapshot.js +159 -11
  346. package/src/core/source-map.js +157 -9
  347. package/src/core/unsaved-warning.js +14 -4
  348. package/src/core/upgrade-run.js +408 -0
  349. package/src/core/version.js +18 -0
  350. package/src/lib/assets.js +30 -0
  351. package/src/lib/autosave-debug-gate.js +50 -0
  352. package/src/lib/autosave-scheduler.js +140 -0
  353. package/src/lib/boot-imports.js +8 -0
  354. package/src/lib/derived.js +3 -1
  355. package/src/lib/dirty-gate.js +15 -0
  356. package/src/lib/doc-storage.js +33 -0
  357. package/src/lib/effect.js +9 -4
  358. package/src/lib/mutation.js +4 -2
  359. package/src/lib/offline-runtime.js +226 -0
  360. package/src/lib/preload.js +48 -0
  361. package/src/lib/root-attrs.js +1 -1
  362. package/src/lib/trash.js +18 -0
  363. package/src/lib/undo/capture-move.js +152 -0
  364. package/src/live/local-channel.js +61 -0
  365. package/src/live/local-lane.js +60 -9
  366. package/src/live/server-lane.js +6 -3
  367. package/src/loader-logic.js +76 -15
  368. package/src/loader.js +218 -30
  369. package/src/page/adopt-boot.js +76 -22
  370. package/src/page/adopt.js +25 -4
  371. package/src/page/awareness.js +230 -0
  372. package/src/page/blocks.js +113 -6
  373. package/src/page/blocksel.js +20 -4
  374. package/src/page/board-face.js +13 -0
  375. package/src/page/board-panel.js +5 -17
  376. package/src/page/board-style.js +2 -13
  377. package/src/page/board-view.js +1 -11
  378. package/src/page/board-views.js +3 -0
  379. package/src/page/board.js +2 -2
  380. package/src/page/boot-pack.js +26 -0
  381. package/src/page/boot.js +237 -0
  382. package/src/page/chart-tables.js +11 -0
  383. package/src/page/chart.js +6 -10
  384. package/src/page/checklist.js +1 -1
  385. package/src/page/checkpoint-proof.js +289 -0
  386. package/src/page/chrome.js +431 -63
  387. package/src/page/csv-import.js +17 -2
  388. package/src/page/document-title-dom.js +52 -0
  389. package/src/page/document-title-runtime.js +90 -0
  390. package/src/page/document-title.js +12 -0
  391. package/src/page/drag.js +48 -13
  392. package/src/page/drop.js +2 -1
  393. package/src/page/editor.js +399 -90
  394. package/src/page/emoji-data.js +162 -0
  395. package/src/page/feature-loader.js +72 -0
  396. package/src/page/features/board.js +21 -0
  397. package/src/page/features/chart.js +11 -0
  398. package/src/page/features/code.js +13 -0
  399. package/src/page/features/dates.js +180 -0
  400. package/src/page/features/emoji.js +187 -0
  401. package/src/page/features/media.js +290 -0
  402. package/src/page/features/mentions.js +188 -0
  403. package/src/page/features/registry.js +28 -0
  404. package/src/page/features/table.js +383 -0
  405. package/src/page/{tool-view.js → features/tool.js} +33 -2
  406. package/src/page/format-codec.js +215 -0
  407. package/src/page/format-command.js +9 -0
  408. package/src/page/format-gate.js +179 -0
  409. package/src/page/format-host.js +54 -0
  410. package/src/page/format-level.js +157 -0
  411. package/src/page/format-native-commands.js +142 -0
  412. package/src/page/format-native.js +42 -0
  413. package/src/page/highlight-slot.js +23 -0
  414. package/src/page/highlight.js +30 -16
  415. package/src/page/history-intent.js +78 -0
  416. package/src/page/inline.js +45 -28
  417. package/src/page/items.js +67 -14
  418. package/src/page/link.js +1 -0
  419. package/src/page/markdown.js +112 -3
  420. package/src/page/media-views.js +3 -0
  421. package/src/page/media.js +33 -245
  422. package/src/page/mention-picker.js +99 -0
  423. package/src/page/merge.js +1 -1
  424. package/src/page/outline-cache.js +11 -0
  425. package/src/page/placeholder.js +27 -7
  426. package/src/page/projection.js +240 -51
  427. package/src/page/reader-patch.js +306 -0
  428. package/src/page/recover.js +1 -1
  429. package/src/page/rich/page-tool.js +1 -4
  430. package/src/page/runtime-owner.js +27 -0
  431. package/src/page/save-projection.js +46 -4
  432. package/src/page/save-states.js +1 -1
  433. package/src/page/schema.js +47 -0
  434. package/src/page/table-rect-key.js +3 -0
  435. package/src/page/table-select.js +4 -3
  436. package/src/page/table-style.js +1 -1
  437. package/src/page/table-views.js +3 -0
  438. package/src/page/table.js +8 -293
  439. package/src/page/themes/content.css +25 -1
  440. package/src/page/themes/light-1.css +163 -2
  441. package/src/page/themes/light-1.source.css +269 -0
  442. package/src/page/tool-capability.js +3 -0
  443. package/src/page/tool-views.js +3 -0
  444. package/src/page/view-hooks.js +32 -0
  445. package/src/page/view.js +390 -107
  446. package/src/page/ybind/awareness.js +172 -0
  447. package/src/page/ybind/binding.js +40 -5
  448. package/src/page/ybind/bridge.js +50 -47
  449. package/src/page/ybind/decode.js +52 -19
  450. package/src/page/ybind/doc.js +27 -2
  451. package/src/page/ybind/group.js +12 -3
  452. package/src/page/ybind/index.js +2 -0
  453. package/src/page/ybind/lineage.js +71 -0
  454. package/src/page/ybind/marks.js +26 -1
  455. package/src/page/ybind/migrate.js +48 -13
  456. package/src/page/ybind/moves.js +32 -21
  457. package/src/page/ybind/positions.js +50 -0
  458. package/src/page/ybind/provider.js +97 -24
  459. package/src/page/ybind/recovery-proof.js +191 -0
  460. package/src/page/ybind/recovery-replay.js +488 -0
  461. package/src/page/ybind/tokens.js +15 -6
  462. package/src/page/ybind/walk.js +10 -3
  463. package/src/page/ybind/writer.js +48 -4
  464. package/src/plugins/ai-edit.js +167 -32
  465. package/src/plugins/bind-text.js +218 -0
  466. package/src/plugins/cms.js +93 -0
  467. package/src/plugins/indicator.js +57 -5
  468. package/src/plugins/live.js +319 -35
  469. package/src/plugins/menu/claygoo.svg.js +1 -0
  470. package/src/plugins/menu/seo-head.js +323 -0
  471. package/src/plugins/menu/seo.js +849 -0
  472. package/src/plugins/menu.js +425 -0
  473. package/src/plugins/page-view.js +131 -0
  474. package/src/plugins/page.js +1131 -270
  475. package/src/plugins/people.js +158 -31
  476. package/src/plugins/richclay.js +119 -9
  477. package/src/plugins/sortable.js +370 -60
  478. package/src/plugins/source.js +189 -366
  479. package/src/plugins/undo.js +289 -19
  480. package/src/plugins/unsent.js +132 -0
  481. package/src/plugins/upgrade-run.js +1 -0
  482. package/src/plugins/upgrade.js +210 -0
  483. package/src/plugins/upload-task.js +571 -0
  484. package/src/plugins/upload-ui.js +265 -0
  485. package/src/plugins/upload.js +34 -153
  486. package/src/plugins/visitor-drafts/inert.js +32 -0
  487. package/src/plugins/visitor-drafts/intro.js +2 -1
  488. package/src/plugins/visitor-drafts/owner-chip.js +3 -0
  489. package/src/plugins/visitor-drafts/prompt.js +2 -2
  490. package/src/plugins/visitor-drafts-owner.js +85 -0
  491. package/src/plugins/visitor-drafts.js +31 -42
  492. package/src/plugins/wire.js +82 -1
  493. package/src/reader-core.js +26 -0
  494. package/src/recovery/conflicts.js +287 -0
  495. package/src/recovery/identity.js +154 -0
  496. package/src/recovery/store.js +792 -0
  497. package/src/recovery/undo.js +257 -0
  498. package/src/server/checkpoint/index.js +68 -4
  499. package/src/server/offline-runtime/index.js +214 -0
  500. package/src/server/rooms/index.js +2 -1
  501. package/src/server/rooms/live-data-rooms.js +2 -2
  502. package/src/server/rooms/page-admission.js +33 -0
  503. package/src/server/rooms/page-lineage.js +56 -0
  504. package/src/server/rooms/page-rebase.js +11 -5
  505. package/src/server/rooms/page-state.js +2 -0
  506. package/src/server/rooms/page-write-admission.js +9 -3
  507. package/src/source/session.js +705 -0
  508. package/src/sync/conflict-revert.js +5 -4
  509. package/src/sync/conflicts-edit.js +119 -0
  510. package/src/sync/conflicts-facade.js +49 -0
  511. package/src/sync/conflicts.js +99 -1
  512. package/src/sync/editing-marker.js +37 -6
  513. package/src/sync/editing-sender.js +8 -1
  514. package/src/sync/live-sync-edit.js +63 -0
  515. package/src/sync/live-sync.js +364 -119
  516. package/src/sync/local-presence.js +187 -0
  517. package/src/sync/merge-declarations.js +10 -0
  518. package/src/sync/merge-engine.js +12 -0
  519. package/src/sync/presence-store.js +412 -0
  520. package/src/sync/presence-target.js +243 -0
  521. package/src/sync/presence.js +72 -41
  522. package/src/sync/saved-handoff.js +200 -0
  523. package/src/sync/saved-stream.js +544 -0
  524. package/src/sync/version-guard.js +92 -0
  525. package/src/sync/wire-profile.js +81 -0
  526. package/src/ui/bevel-controls.js +2 -8
  527. package/src/ui/page-scheme.js +7 -0
  528. package/src/ui/skins/cms.js +1 -1
  529. package/src/ui/theme-config.js +150 -0
  530. package/src/ui/theme.js +6 -138
  531. package/src/ui/toast-lazy.js +11 -0
  532. package/src/vendor/LICENSES-optional.md +253 -0
  533. package/src/vendor/highlight.vendor.js +3 -0
  534. package/src/vendor/hljs-bash.vendor.js +2 -0
  535. package/src/vendor/hljs-css.vendor.js +2 -0
  536. package/src/vendor/hljs-javascript.vendor.js +2 -0
  537. package/src/vendor/hljs-json.vendor.js +2 -0
  538. package/src/vendor/hljs-python.vendor.js +2 -0
  539. package/src/vendor/hljs-xml.vendor.js +2 -0
  540. package/src/vendor/hyper-html-api-data.vendor.js +1 -1
  541. package/src/vendor/hyper-html-api-upgrade.vendor.js +5 -0
  542. package/src/vendor/hyper-morph-identity.vendor.js +2 -0
  543. package/src/vendor/hyper-morph-json.vendor.js +3 -0
  544. package/src/vendor/hyper-morph.vendor.js +6 -6
  545. package/src/vendor/hyper-undo.vendor.js +1 -1
  546. package/src/vendor/hypercms.vendor.js +169 -84
  547. package/src/vendor/marked.vendor.js +68 -0
  548. package/src/vendor/overtype.vendor.js +994 -0
  549. package/src/vendor/richclay.vendor.js +17 -17
  550. package/src/vendor/sap-core.vendor.js +5 -5
  551. package/src/vendor/sap-ownership.vendor.js +11 -7
@@ -0,0 +1,237 @@
1
+ export const PHASES = Object.freeze({
2
+ loading: "loading",
3
+ joining: "joining",
4
+ adopting: "adopting",
5
+ ready: "ready",
6
+ blocked: "blocked",
7
+ error: "error",
8
+ });
9
+
10
+ const TERMINAL = new Set([PHASES.ready, PHASES.blocked, PHASES.error]);
11
+
12
+ let installs = 0;
13
+
14
+ const monotonic = () => globalThis.performance?.now?.() ?? Date.now();
15
+
16
+ export function createInstall({ gen = 0, isCurrent = () => true, onPhase = null } = {}) {
17
+ installs += 1;
18
+ return {
19
+ installId: installs,
20
+ gen,
21
+ isCurrent,
22
+ onPhase: typeof onPhase === "function" ? onPhase : null,
23
+ phase: PHASES.loading,
24
+ result: null,
25
+ at: monotonic(),
26
+ controller: new AbortController(),
27
+ disposers: [],
28
+ disposed: false,
29
+ steps: null,
30
+ deferred: null,
31
+ };
32
+ }
33
+
34
+ export function isCurrent(inst) {
35
+ return !inst.disposed && !inst.controller.signal.aborted && inst.isCurrent() !== false;
36
+ }
37
+
38
+ // One owner: the view this installation mounted, and nothing a later or a cancelled
39
+ // installation staged. The final input gate asks this before it accepts a key.
40
+ export function ownsView(inst, mounted) {
41
+ return isCurrent(inst) && !!mounted?.view && inst.mounted === mounted.view;
42
+ }
43
+
44
+ export function setPhase(inst, phase, reason = null) {
45
+ if (TERMINAL.has(inst.phase) && inst.result) return inst.result;
46
+ inst.phase = phase;
47
+ inst.at = monotonic();
48
+ inst.result = Object.freeze({ phase, reason: reason ?? null, installId: inst.installId, at: inst.at });
49
+ try {
50
+ inst.onPhase?.(inst.result);
51
+ } catch (error) {
52
+ console.error("clayjs: page boot phase callback failed", error);
53
+ }
54
+ return inst.result;
55
+ }
56
+
57
+ export function finishBlocked(inst, reason) {
58
+ const result = setPhase(inst, PHASES.blocked, reason);
59
+ inst.editorReadyResolve?.();
60
+ return result;
61
+ }
62
+
63
+ export function finishError(inst, reason) {
64
+ return setPhase(inst, PHASES.error, reason);
65
+ }
66
+
67
+ export function addDisposer(inst, dispose) {
68
+ if (typeof dispose === "function") inst.disposers.push(dispose);
69
+ return dispose;
70
+ }
71
+
72
+ export function disposeInstall(inst) {
73
+ if (inst.disposed) return;
74
+ inst.disposed = true;
75
+ inst.mounted = null;
76
+ try {
77
+ inst.controller.abort();
78
+ } catch (error) {
79
+ console.error("clayjs: page boot abort failed", error);
80
+ }
81
+ const disposers = inst.disposers.splice(0).reverse();
82
+ for (const dispose of disposers) {
83
+ try {
84
+ dispose();
85
+ } catch (error) {
86
+ console.error("clayjs: a page boot disposer failed", error);
87
+ }
88
+ }
89
+ }
90
+
91
+ export function mark(name, inst) {
92
+ const perf = globalThis.performance;
93
+ if (typeof perf?.mark !== "function") return;
94
+ try {
95
+ perf.mark(name, { detail: { installId: inst.installId } });
96
+ } catch (error) {
97
+ try { perf.mark(name); } catch (ignored) { return; }
98
+ }
99
+ }
100
+
101
+ // A boot that ends without an editor gives up what its steps acquired on the way
102
+ // there, through the install's own teardown, before it reports where it stopped.
103
+ function release(inst, steps) {
104
+ try {
105
+ steps.release?.(inst);
106
+ } catch (error) {
107
+ console.error("clayjs: page boot release failed", error);
108
+ }
109
+ }
110
+
111
+ function stop(inst, result, steps) {
112
+ if (result.cancelled) return null;
113
+ release(inst, steps);
114
+ return finishBlocked(inst, result.reason ?? "boot-incomplete");
115
+ }
116
+
117
+ // A branch of the parallel boot reports its own failure here, so a rejection in one
118
+ // cannot leave an unhandled rejection or a late continuation mounting the other. The
119
+ // mark it owns is emitted when it settles, which is what proves the two overlapped.
120
+ function branch(promise, inst, reason, name = null) {
121
+ return Promise.resolve(promise)
122
+ .then((result) => result ?? { ok: false, reason }, (error) => {
123
+ console.error(`clayjs: page boot ${reason}`, error);
124
+ return { ok: false, reason };
125
+ })
126
+ .then((result) => {
127
+ if (result.ok && name && isCurrent(inst)) mark(name, inst);
128
+ return result;
129
+ });
130
+ }
131
+
132
+ // The wait on the parallel branches: the first one to fail, or the install's abort,
133
+ // answers it at once. A boot blocked by the download that failed must not sit in
134
+ // `joining` on the join that never answered, and a disposed install must not wait for
135
+ // either of them.
136
+ function settleBranches(inst, branches) {
137
+ return new Promise((resolve) => {
138
+ const signal = inst.controller.signal;
139
+ let done = false;
140
+ const finish = (outcome) => {
141
+ if (done) return;
142
+ done = true;
143
+ resolve(outcome);
144
+ };
145
+ if (signal.aborted) return finish({ cancelled: true });
146
+ signal.addEventListener("abort", () => finish({ cancelled: true }), { once: true });
147
+ const results = new Array(branches.length);
148
+ let left = branches.length;
149
+ branches.forEach((branch, index) => {
150
+ branch.then((result) => {
151
+ if (done) return;
152
+ if (!result.ok) return finish({ result, branch });
153
+ results[index] = result;
154
+ left -= 1;
155
+ if (left === 0) finish({ results });
156
+ });
157
+ });
158
+ });
159
+ }
160
+
161
+ // One coordinator for the edit boot: read the source, take the assets while
162
+ // authority is prepared, mount, adopt, then enable input. Every step reports an
163
+ // outcome and every await is followed by an ownership check, so a late or
164
+ // cancelled installation can neither mutate the page nor mark it ready.
165
+ export async function runBoot(inst, steps, { activate = true } = {}) {
166
+ mark("document:boot", inst);
167
+ setPhase(inst, PHASES.loading);
168
+
169
+ const read = steps.readSource(inst);
170
+ if (!isCurrent(inst)) return null;
171
+ if (!read.ok) return finishBlocked(inst, read.reason);
172
+ const source = read.source;
173
+
174
+ // The editor's code arrives while authority is prepared, so the wait is the longer
175
+ // of the two plus adoption rather than their sum. Neither branch subscribes a
176
+ // resource or touches the page: only the coordinator does, after both are in.
177
+ setPhase(inst, PHASES.joining);
178
+ const settled = await settleBranches(inst, [
179
+ branch(steps.loadEditorAssets(inst, source), inst, "editor-assets-failed", "document:assets-ready"),
180
+ branch(steps.prepareAuthority(inst, source), inst, "authority-failed", "document:authority-ready"),
181
+ ]);
182
+ if (settled.cancelled || !isCurrent(inst)) return null;
183
+ if (!settled.results) {
184
+ // The branch still on the wire is not worth waiting for, and it must not go on to
185
+ // subscribe anything: the abort ends it where its next ownership check is.
186
+ try {
187
+ inst.controller.abort();
188
+ } catch (error) {
189
+ console.error("clayjs: page boot abort failed", error);
190
+ }
191
+ return stop(inst, settled.result, steps);
192
+ }
193
+ const [loaded, admitted] = settled.results;
194
+
195
+ const mounted = steps.mountAndAttach(inst, source, loaded, admitted);
196
+ if (!isCurrent(inst)) return null;
197
+ if (!mounted.ok) return stop(inst, mounted, steps);
198
+ inst.mounted = mounted.view ?? null;
199
+
200
+ setPhase(inst, PHASES.adopting);
201
+ const adoption = await steps.adoptSource(inst, source, mounted);
202
+ if (!isCurrent(inst)) return null;
203
+ if (!adoption.ok) return stop(inst, adoption, steps);
204
+
205
+ // A session that has not committed yet stages the editor and leaves input and every
206
+ // write closed: the checks input waits for run now, so the commit that opens it has
207
+ // nothing left to wait on or fail.
208
+ if (!activate) {
209
+ if (steps.prepareInput) {
210
+ const prepared = await steps.prepareInput(inst, mounted);
211
+ if (!isCurrent(inst)) return null;
212
+ if (!prepared.ok) return stop(inst, prepared, steps);
213
+ }
214
+ inst.deferred = mounted;
215
+ return { phase: inst.phase, view: mounted.view, deferred: true };
216
+ }
217
+
218
+ const enabled = await steps.enableInput(inst, mounted);
219
+ if (!isCurrent(inst)) return null;
220
+ if (!enabled.ok) return stop(inst, enabled, steps);
221
+
222
+ mark("document:input-ready", inst);
223
+ return setPhase(inst, PHASES.ready);
224
+ }
225
+
226
+ // The commit that enters edit opens the input the preparation held back. Every check
227
+ // it needs passed for this very mount, so it is synchronous: it changes activation
228
+ // only, never durable data.
229
+ export function activateInput(inst) {
230
+ const mounted = inst.deferred;
231
+ inst.deferred = null;
232
+ if (!mounted || !isCurrent(inst)) return inst.result ?? null;
233
+ const opened = inst.steps?.openInput?.(inst, mounted);
234
+ if (!opened?.ok) return finishBlocked(inst, opened?.reason ?? "input-refused");
235
+ mark("document:input-ready", inst);
236
+ return setPhase(inst, PHASES.ready);
237
+ }
@@ -0,0 +1,11 @@
1
+ import { schema } from "./schema.js";
2
+ import { orderedIds } from "./model.js";
3
+
4
+ // The tables a chart can be drawn from: a top-level table with at least one
5
+ // column. The insert menu reads this without the chart module itself, which only
6
+ // a document that takes a chart loads.
7
+ export function chartTables(doc) {
8
+ const tables = [];
9
+ doc.forEach((node) => { if (node.type === schema.nodes.table && orderedIds(node.attrs.cols).length) tables.push(node); });
10
+ return tables;
11
+ }
package/src/page/chart.js CHANGED
@@ -1,6 +1,6 @@
1
- import { state } from "../vendor/prosemirror.vendor.js";
2
1
  import { schema } from "./schema.js";
3
2
  import { blockJson, keyBetween, newId, orderedIds } from "./model.js";
3
+ import { chartTables } from "./chart-tables.js";
4
4
  import { chartModel, renderChartSvg } from "./rich/chart.js";
5
5
  import { openPageMenu } from "./chrome.js";
6
6
  import { project } from "./projection.js";
@@ -50,11 +50,7 @@ export function insertChartAfterTable(view, tablePos) {
50
50
  return true;
51
51
  }
52
52
 
53
- export function chartTables(doc) {
54
- const tables = [];
55
- doc.forEach((node) => { if (node.type === schema.nodes.table && orderedIds(node.attrs.cols).length) tables.push(node); });
56
- return tables;
57
- }
53
+ export { chartTables };
58
54
 
59
55
  // /chart: an empty line becomes the chart, otherwise it goes below the line.
60
56
  // One table is charted at once; several open a picker named by their columns.
@@ -213,8 +209,8 @@ export class ChartView {
213
209
  }
214
210
  }
215
211
 
216
- export function chartPlugin() {
217
- return new state.Plugin({
218
- view: () => ({ update(view) { for (const chart of views.get(view) ?? []) chart.refresh(); } }),
219
- });
212
+ // The hook the chart view part registers for its editor view: every change can
213
+ // have moved a chart's source table.
214
+ export function refreshCharts(view) {
215
+ for (const chart of views.get(view) ?? []) chart.refresh();
220
216
  }
@@ -1,7 +1,7 @@
1
1
  import { state, view as pmView } from "../vendor/prosemirror.vendor.js";
2
2
  import { schema } from "./schema.js";
3
3
  import { asStep } from "./blocksel.js";
4
- import { el, cardFace } from "./board-view.js";
4
+ import { el, cardFace } from "./board-face.js";
5
5
 
6
6
  const { Plugin } = state;
7
7
  const { Decoration, DecorationSet } = pmView;
@@ -0,0 +1,289 @@
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
+ * @param {Object} read - an answer from readPageState()
144
+ * @param {Object} options
145
+ * @param {{authority: Object, lineage: Object}} options.accepted
146
+ * @param {string} options.article - the final outgoing canonical article
147
+ * @param {string} options.etag - the ETag the accepted save carried
148
+ * @returns {Promise<{ok: true, state: Object, receipt: {file: string, pageState: string}}|{ok: false, code: string, message: string}>}
149
+ */
150
+ export async function verifyPageState(read, { accepted, article, etag } = {}) {
151
+ if (!read || read.ok !== true) return refused(read?.code ?? "unavailable", read?.message ?? "The page state was not read");
152
+ if (idKey(read.documentId) !== idKey(accepted?.authority?.documentId)) return refused("foreign-document", "The page state belongs to another document");
153
+ if (typeof accepted?.lineage?.generation !== "string" || read.generation !== accepted.lineage.generation) return refused("foreign-generation", "The page state belongs to another generation");
154
+ if (read.etag !== etag) return refused("etag-mismatch", "The page state belongs to another version of this document");
155
+ let stateBytes, state;
156
+ try { ({ state, bytes: stateBytes } = stateOfTag(read.tag)); }
157
+ catch (error) { return refused(error?.code ?? "invalid-state", error?.message ?? "The page state does not decode"); }
158
+ if (await digestHex(stateBytes) !== read.stateHash) return refused("state-digest-mismatch", "The page state is not the one the host hashed");
159
+ if (typeof article !== "string") return refused("missing-article", "A proof names the final outgoing article");
160
+ let projected;
161
+ try { projected = articleOfPageState(state); }
162
+ catch (error) { return refused("invalid-state", error?.message ?? "The page state does not project"); }
163
+ if (projected !== article) return refused("article-mismatch", "The page state does not project to the accepted article");
164
+ return { ok: true, state, receipt: { file: read.etag, pageState: read.stateHash } };
165
+ }
166
+
167
+ // A proof is only worth the records the validator confirms, over the revisions the
168
+ // caller observed: a proof that covers nothing keeps everything.
169
+ async function covered(proof, records) {
170
+ const observed = (records || []).map(({ id, revision }) => ({ id, revision }));
171
+ try {
172
+ return { ok: true, proof, covered: await validatePageCheckpoint(proof, observed) };
173
+ } catch (error) {
174
+ return refused(error?.code ?? "invalid-proof", error?.message ?? "The proof does not validate");
175
+ }
176
+ }
177
+
178
+ /**
179
+ * The proof of one accepted save, from the page state pinned to its capture.
180
+ *
181
+ * The receipt is the accepted ETag and the digest of the state the proof is made over,
182
+ * which is this tab's own account that this capture was written and hashed. Nothing
183
+ * about a later edit, a store value or an acknowledgement reaches this function.
184
+ *
185
+ * @param {Object} options
186
+ * @param {string} options.capture - the capture the save belongs to
187
+ * @param {Array<Object>} options.records - {id, revision, update, context} the caller observed
188
+ * @param {{authority: Object, lineage: Object}} options.accepted
189
+ * @param {string} options.article - the final outgoing canonical article
190
+ * @param {Object} options.state - the page state pinned to that capture
191
+ * @param {string} options.etag - the ETag the accepted save carried
192
+ * @returns {Promise<{ok: true, proof: Object, covered: Array}|{ok: false, code: string, message: string}>}
193
+ */
194
+ export async function captureProof({ capture, records = [], accepted, article, state = null, etag = null } = {}) {
195
+ if (!state) return refused("no-capture-state", "No page state was pinned to this capture");
196
+ if (typeof etag !== "string" || !etag) return refused("missing-etag", "An accepted save with no ETag proves no stored pair");
197
+ if (typeof article !== "string") return refused("missing-article", "The accepted bytes hold no article");
198
+ let projected, stateBytes;
199
+ try {
200
+ projected = articleOfPageState(state);
201
+ stateBytes = canonicalPageBytes(state);
202
+ } catch (error) {
203
+ return refused(error?.code ?? "invalid-state", error?.message ?? "The pinned page state does not decode");
204
+ }
205
+ if (projected !== article) return refused("article-mismatch", "The pinned page state does not project to the accepted article");
206
+ let proof;
207
+ try {
208
+ proof = await pageProof({
209
+ capture,
210
+ records,
211
+ accepted,
212
+ article,
213
+ state,
214
+ receipt: { file: etag, pageState: await digestHex(stateBytes) },
215
+ });
216
+ } catch (error) {
217
+ return refused(error?.code ?? "invalid-proof", error?.message ?? "The proof could not be built");
218
+ }
219
+ return covered(proof, records);
220
+ }
221
+
222
+ /**
223
+ * The proof of an accepted save, rebuilt from the page state the host persisted.
224
+ *
225
+ * The answer for the server's checkpoint, another writer's, and a save whose reply was
226
+ * lost: identity, generation and digest are checked against that exact ETag first, and
227
+ * only then is coverage asked. A host that cannot answer leaves the journal alone.
228
+ *
229
+ * @param {Object} options
230
+ * @param {Object} options.read - an answer from readPageState()
231
+ * @returns {Promise<{ok: true, proof: Object, covered: Array}|{ok: false, code: string, message: string}>}
232
+ */
233
+ export async function refetchedProof({ read, capture, records = [], accepted, article, etag = null } = {}) {
234
+ const verified = await verifyPageState(read, { accepted, article, etag });
235
+ if (!verified.ok) return verified;
236
+ let proof;
237
+ try {
238
+ proof = await pageProof({ capture, records, accepted, article, state: verified.state, receipt: verified.receipt });
239
+ } catch (error) {
240
+ return refused(error?.code ?? "invalid-proof", error?.message ?? "The proof could not be built");
241
+ }
242
+ return covered(proof, records);
243
+ }
244
+
245
+ /**
246
+ * Retire exactly the records one accepted save covers.
247
+ *
248
+ * The pinned capture is tried first, because it needs no request and no host. Only a
249
+ * capture that cannot prove these bytes asks the host, conditionally on the accepted
250
+ * ETag. Whichever answers, the channel is handed the caller's observed list and the
251
+ * proof together: it narrows the list to what the proof covers and never widens it, and
252
+ * a refusal here retires nothing.
253
+ *
254
+ * @param {Object} options
255
+ * @param {Object} options.channel - the `page.yjs` channel api
256
+ * @param {Array<Object>} options.records - {id, revision, update, context} the caller observed
257
+ * @param {string} options.capture
258
+ * @param {{authority: Object, lineage: Object}} options.accepted
259
+ * @param {string} options.article
260
+ * @param {Object} [options.state] - the page state pinned to the capture
261
+ * @param {string} options.etag
262
+ * @param {?string} [options.href] - the page-state resource, or null
263
+ * @param {Function} [options.read]
264
+ * @param {Function} [options.fetch]
265
+ * @returns {Promise<{ok: boolean, retired: Array, proof: ?Object, code?: string}>}
266
+ */
267
+ export async function checkpointAccepted({
268
+ channel,
269
+ records = [],
270
+ capture,
271
+ accepted,
272
+ article,
273
+ state = null,
274
+ etag = null,
275
+ href = null,
276
+ read = readPageState,
277
+ fetch: doFetch,
278
+ } = {}) {
279
+ if (!channel || typeof channel.checkpoint !== "function") return { ok: false, code: "no-channel", retired: [], proof: null };
280
+ let proven = await captureProof({ capture, records, accepted, article, state, etag });
281
+ if (!proven.ok) {
282
+ const answer = await read({ href, etag, fetch: doFetch });
283
+ proven = await refetchedProof({ read: answer, capture, records, accepted, article, etag });
284
+ }
285
+ if (!proven.ok) return { ok: false, code: proven.code, retired: [], proof: null };
286
+ const answer = await channel.checkpoint({ records: proven.covered, proof: proven.proof });
287
+ if (!answer || answer.ok !== true) return { ok: false, code: answer?.code ?? "checkpoint-failed", retired: [], proof: proven.proof };
288
+ return { ok: true, retired: answer.retired ?? [], proof: proven.proof };
289
+ }