@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
@@ -8,20 +8,21 @@
8
8
  import { contains, parseBy, frameSet, descendants, unionRanges, formatBy } from "../live/provenance.js";
9
9
  import { createStore } from "../live/store.js";
10
10
  import { serializeLive, stampOf, parseStamp, textHash } from "../live/ops.js";
11
- import { onSnapshot } from "../core/snapshot.js";
11
+ import { capturePageState, onSnapshot } from "../core/snapshot.js";
12
12
  import { onSaveAccepted, onSaveAttempt, onSaveOutcome, isSaveInProgress } from "../core/save-core.js";
13
13
  import { savePageForce, baselineSettled, isSaveConflicted, autosaveIsSuspended } from "../core/save.js";
14
14
  import { withKeepalive } from "../core/save-core.js";
15
15
  import { isEditMode } from "../core/is-edit-mode.js";
16
16
  import { registerUnsavedState } from "../lib/unsaved-state.js";
17
17
  import { createCheckpoints } from "../live/checkpoint.js";
18
- import { HyperMorph } from "../vendor/hyper-morph.vendor.js";
18
+ import { parseJsonRelaxed } from "../vendor/hyper-morph-json.vendor.js";
19
19
  import * as hostMeta from "../core/host-meta.js";
20
20
  import { createFastLane } from "../live/fast-lane.js";
21
21
  import { createSaveHistoryRecovery, RECOVERY_WINDOW_MS } from "../live/save-history.js";
22
22
  import { serverTransport } from "../live/server-lane.js";
23
23
  import { localTransport, localLaneSupported } from "../live/local-lane.js";
24
24
  import { captureYValue } from "../page/ybind/parts.js";
25
+ import { lastSeenEtag, recordEtag } from "../core/etag.js";
25
26
 
26
27
  const NAME = /^[A-Za-z0-9_.:-]{1,64}$/;
27
28
  const entries = new Map();
@@ -406,17 +407,25 @@ function sortedKeys(value) {
406
407
 
407
408
  function canonical(text) {
408
409
  try {
409
- return JSON.stringify(sortedKeys(HyperMorph.parseJsonRelaxed(text)));
410
+ return JSON.stringify(sortedKeys(parseJsonRelaxed(text)));
410
411
  } catch {
411
412
  return null;
412
413
  }
413
414
  }
414
415
 
416
+ // The writer session a mode lease owns (see liveWriterParticipant). Null until one is
417
+ // prepared, and null again once it is disposed: a page whose loader has not registered
418
+ // a writer keeps the old rule, one lane per edit-mode tab. While a session has closed
419
+ // admission (entry preparing, or an exit draining what this tab still holds) no local
420
+ // op is framed and no checkpoint saves.
421
+ let writerSession = null;
422
+ const writerAdmitted = () => !writerSession || writerSession.admitted;
423
+
415
424
  // Saved stores with changes no accepted save holds yet. Only an edit-mode tab
416
425
  // saves. In file mode that is this tab's unconfirmed ops; in fast mode the room
417
426
  // says the file does not reach its latest change, and only the writer saves.
418
427
  function unsavedLiveChanges() {
419
- if (!isEditMode) return false;
428
+ if (!isEditMode || !writerAdmitted()) return false;
420
429
  for (const entry of entries.values()) {
421
430
  if (entry.store.ephemeral) continue;
422
431
  if (entry.store.handle.status.mode === "fast") {
@@ -437,7 +446,7 @@ function fileMoved() {
437
446
 
438
447
  const checkpoints = createCheckpoints({
439
448
  needsSave: unsavedLiveChanges,
440
- blocked: () => !baselineSettled() || isSaveConflicted() || autosaveIsSuspended() || historyPending(),
449
+ blocked: () => !writerAdmitted() || !baselineSettled() || isSaveConflicted() || autosaveIsSuspended() || historyPending(),
441
450
  save: () => savePageForce().finally(fileMoved),
442
451
  saveNow: () => withKeepalive(true, () => savePageForce()),
443
452
  });
@@ -446,6 +455,9 @@ const checkpoints = createCheckpoints({
446
455
  // clay.live() call in an edit-mode tab.
447
456
  let fast = null;
448
457
  let laneRequested = false;
458
+ // Bumped by every request and every release, so an answer that arrives after its
459
+ // session was released starts nothing.
460
+ let laneRequest = 0;
449
461
  let serverLane = null;
450
462
  // The host merges exact save provenance (live-by v2); without it a save that
451
463
  // needs provenance is refused rather than risk applying an edit twice.
@@ -455,6 +467,144 @@ let local = null;
455
467
  // forward): only a closing page's save is reported to the local room at capture.
456
468
  let closing = false;
457
469
 
470
+ // A listener for the store behind a session: a reader's tag, and the values other
471
+ // tabs saved to it, keep following the file.
472
+ function sessionStores() {
473
+ return [...entries.values()].filter((entry) => !entry.store.ephemeral);
474
+ }
475
+
476
+ // What this tab still holds that the page's own bytes cannot carry: ops a store in a
477
+ // room has not had acknowledged. A reader's own change is not one of them.
478
+ function liveWriterPending() {
479
+ if (!writerSession || writerSession.admitted || !fast || fast.status.stopped) return false;
480
+ for (const entry of sessionStores()) {
481
+ if (entry.store.handle.status.mode === "fast" && entry.store.pending.length > 0) return true;
482
+ }
483
+ return false;
484
+ }
485
+
486
+ // Frame what the lane has not sent and wait for the room to take it. This is the one
487
+ // transfer that lets the writer be released: a checkpoint writes the room's value, so
488
+ // an unacknowledged op of this tab has to travel through the room, not the file.
489
+ export async function drainLiveWriter({ timeoutMs = 5000 } = {}) {
490
+ const deadline = Date.now() + timeoutMs;
491
+ const frame = () => {
492
+ for (const entry of sessionStores()) {
493
+ if (entry.store.handle.status.mode !== "fast") continue;
494
+ if (entry.store.pending.length > 0) fast?.release(entry.store.name);
495
+ }
496
+ };
497
+ frame();
498
+ while (liveWriterPending()) {
499
+ if (Date.now() >= deadline) return { ok: false, code: "timeout" };
500
+ await new Promise((resolve) => setTimeout(resolve, 5));
501
+ frame();
502
+ }
503
+ return { ok: true };
504
+ }
505
+
506
+ registerUnsavedState({ id: "live:writer", isPending: liveWriterPending, flush: drainLiveWriter });
507
+
508
+ // The lane a session starts from: the reader's own ops are not this session's edits,
509
+ // and no position a previous session's lane reported is carried into this one.
510
+ function resetWriterSession() {
511
+ for (const entry of entries.values()) {
512
+ entry.framesSince = false;
513
+ const ids = entry.store.pendingEntries().map((item) => item.id);
514
+ if (ids.length) entry.store.rebase(entry.store.authoritative, { source: "state", dropIds: ids });
515
+ }
516
+ }
517
+
518
+ // Give back everything the lane holds: the transport's subscription (its stream
519
+ // handlers and its local channel), the room position, and the request for a lane, so
520
+ // the next session starts a fresh one.
521
+ function releaseWriter() {
522
+ const lane = fast;
523
+ fast = null;
524
+ laneRequested = false;
525
+ laneRequest++;
526
+ serverLane = null;
527
+ local = null;
528
+ provenanceHost = false;
529
+ // The gates went with the lane: a second entry must not read this released one as ready.
530
+ laneReady = null;
531
+ writerReady = null;
532
+ settleLane = null;
533
+ settleWriter = null;
534
+ lane?.stop();
535
+ }
536
+
537
+ /**
538
+ * clay.live as a mode participant: the store's writer, started by the commit that
539
+ * enters edit and released by the one that leaves it.
540
+ *
541
+ * `prepare` closes admission and puts the stores back on the reader's value, so
542
+ * nothing a reader changed can be uploaded as this person's edit. `enable` starts
543
+ * the tab's lane. `quiesce` closes admission again and drains the ops the room has
544
+ * not acknowledged, so a refused exit resumes the very same lane. `dispose` (after the
545
+ * barrier) stops the lane and forgets its subscription, its room and its send queue.
546
+ */
547
+ export function liveWriterParticipant() {
548
+ return {
549
+ id: "clay:live",
550
+ order: 200,
551
+ prepare(context = {}) {
552
+ // An adoption is the session this tab's edit boot started: the exit takes that one
553
+ // back, and the entry asks for no second lane.
554
+ if (context.initial === true) {
555
+ if (!writerSession) writerSession = { admitted: true };
556
+ return writerLease(writerSession, { adopted: true });
557
+ }
558
+ freshLaneGates();
559
+ // The lane's transport opens with the preparation, not with the commit: the page
560
+ // (order 400) waits on the room while it prepares, and it prepares before any
561
+ // enable runs. Admission is still the commit's, so what a person does is what
562
+ // reaches the room.
563
+ const session = { admitted: false, wantsLane: true };
564
+ writerSession = session;
565
+ resetWriterSession();
566
+ ensureLane();
567
+ return writerLease(session);
568
+ },
569
+ };
570
+ }
571
+
572
+ function releaseHeld() {
573
+ for (const entry of sessionStores()) {
574
+ if (entry.store.pending.length > 0) fast?.release(entry.store.name);
575
+ }
576
+ }
577
+
578
+ function writerLease(session, { adopted = false } = {}) {
579
+ return {
580
+ enable() {
581
+ if (adopted || writerSession !== session) return;
582
+ session.admitted = true;
583
+ ensureLane();
584
+ // A lane opened at preparation may already hold ops the page made before the
585
+ // commit (an adoption's), which onLocal kept back while admission was closed.
586
+ releaseHeld();
587
+ },
588
+ quiesce() {
589
+ if (writerSession !== session) return;
590
+ session.admitted = false;
591
+ return drainLiveWriter();
592
+ },
593
+ resume() {
594
+ if (writerSession !== session) return;
595
+ session.admitted = true;
596
+ releaseHeld();
597
+ },
598
+ dispose() {
599
+ if (writerSession !== session) return;
600
+ writerSession = null;
601
+ releaseWriter();
602
+ },
603
+ };
604
+ }
605
+
606
+ export { liveWriterParticipant as modeParticipant };
607
+
458
608
  // The page plugin's one-shot gates: the fast lane's setup, and the room's
459
609
  // writer election. Both resolve null when this tab has no lane at all.
460
610
  let laneReady = null;
@@ -462,11 +612,15 @@ let writerReady = null;
462
612
  let settleLane = null;
463
613
  let settleWriter = null;
464
614
 
615
+ // A fresh pair for a new session: the page plugin awaits these, and the ones a released
616
+ // session settled answer for a lane that no longer exists.
617
+ function freshLaneGates() {
618
+ laneReady = new Promise((resolve) => { settleLane = resolve; });
619
+ writerReady = new Promise((resolve) => { settleWriter = resolve; });
620
+ }
621
+
465
622
  function laneGates() {
466
- if (!laneReady) {
467
- laneReady = new Promise((resolve) => { settleLane = resolve; });
468
- writerReady = new Promise((resolve) => { settleWriter = resolve; });
469
- }
623
+ if (!laneReady) freshLaneGates();
470
624
  return { laneReady, writerReady };
471
625
  }
472
626
 
@@ -597,7 +751,7 @@ function seedLocalRoom() {
597
751
  continue;
598
752
  }
599
753
  try {
600
- const value = HyperMorph.parseJsonRelaxed(tag.element.textContent);
754
+ const value = parseJsonRelaxed(tag.element.textContent);
601
755
  if (value !== null && typeof value === "object") values.set(name, { value, ephemeral: tag.ephemeral });
602
756
  } catch {}
603
757
  }
@@ -606,17 +760,21 @@ function seedLocalRoom() {
606
760
 
607
761
  function ensureLane() {
608
762
  laneGates();
609
- if (laneRequested || !isEditMode) return;
763
+ if (laneRequested || !(writerSession ? writerSession.admitted || writerSession.wantsLane : isEditMode)) return;
610
764
  laneRequested = true;
765
+ const request = ++laneRequest;
611
766
  hostMeta.hostSupports("live-data").then(
612
767
  async (supported) => {
613
768
  const provenance = supported && (await hostMeta.hostSupports("live-provenance"));
769
+ if (request !== laneRequest) return;
614
770
  serverLane = supported;
615
771
  provenanceHost = provenance;
616
772
  if (supported) {
617
773
  fast = createFastLane(laneOptions(serverTransport()));
618
774
  } else if (localLaneSupported()) {
619
- local = localTransport({ seed: seedLocalRoom });
775
+ // The document's own floor picks the channel scope: a floor-1 document
776
+ // never shares the lane a tab that cannot read it is on.
777
+ local = localTransport({ seed: seedLocalRoom, floor: document.querySelector('meta[name="clay-page-format"]')?.getAttribute("content") });
620
778
  fast = createFastLane(laneOptions(local));
621
779
  } else {
622
780
  settleLane?.(null);
@@ -707,7 +865,7 @@ export function serializeLines(value) {
707
865
  // already holds (its frames that landed before the value's stamp).
708
866
  function hashOfText(text) {
709
867
  try {
710
- return textHash(serializeLive(HyperMorph.parseJsonRelaxed(text)));
868
+ return textHash(serializeLive(parseJsonRelaxed(text)));
711
869
  } catch {
712
870
  return null;
713
871
  }
@@ -716,28 +874,16 @@ function hashOfText(text) {
716
874
  function receive(entry, text, source, dropIds) {
717
875
  let value;
718
876
  try {
719
- value = HyperMorph.parseJsonRelaxed(text);
877
+ value = parseJsonRelaxed(text);
720
878
  } catch {
721
879
  return;
722
880
  }
723
881
  if (entry.store.rebase(value, { source, ...(dropIds?.length && { dropIds }) })) entry.framesSince = true;
724
882
  }
725
883
 
726
- export function live(name) {
727
- const existing = entries.get(name);
728
- if (existing) return existing.store.handle;
729
- const tag = liveTags().get(name);
730
- if (!tag) throw new Error(`clay.live: this page has no <script type="application/json" merge="${name}" clay="live"> tag`);
731
- const text = tag.element.textContent;
732
- let value;
733
- try {
734
- value = HyperMorph.parseJsonRelaxed(text);
735
- } catch (err) {
736
- throw new SyntaxError(`clay.live("${name}"): the tag's JSON does not parse: ${err.message}`);
737
- }
738
- const store = createStore({ name, value, ephemeral: tag.ephemeral });
739
- const layout = tag.element.getAttribute("live-format") === "lines" ? "lines" : text.includes("\n") ? "pretty" : "line";
740
- const entry = { store, layout, framesSince: false, last: 0, landed: 0, leftAt: lineageOf(tag.element), captureOps: new Map(), withheld: false, reading: false, timer: null, recovery: null, historicalRefreshPending: false, refreshTimer: null };
884
+ // One open store and what the plugin follows about it. A staged store has no lane.
885
+ function openEntry(name, store, { layout = "line", leftAt = null, staged = null } = {}) {
886
+ const entry = { store, layout, framesSince: false, last: 0, landed: 0, leftAt, captureOps: new Map(), withheld: false, reading: false, timer: null, recovery: null, historicalRefreshPending: false, refreshTimer: null, staged };
741
887
  entries.set(name, entry);
742
888
  store.onDelivery((event) => {
743
889
  if (event.code === "receipt-expired") document.dispatchEvent(new CustomEvent("clay:live-notice", {
@@ -756,14 +902,79 @@ export function live(name) {
756
902
  settle(entry);
757
903
  if (store.handle.status.mode === "file" && ["undone", "refused", "decided"].includes(event.cause)) entry.leftAt = null;
758
904
  });
759
- if (!tag.ephemeral) store.onLocal(() => checkpoints.changed());
760
- store.onLocal((ops) => fast?.local(name, ops, store.mark));
905
+ if (!store.ephemeral) store.onLocal(() => checkpoints.changed());
906
+ store.onLocal((ops) => { if (!entry.staged && writerAdmitted()) fast?.local(name, ops, store.mark); });
761
907
  store.handle.on("change", () => {
762
908
  if (!store.ephemeral && store.handle.status.mode === "fast") checkpoints.changed();
763
909
  });
764
- fast?.storeOpened(name);
910
+ if (!staged) {
911
+ fast?.storeOpened(name);
912
+ ensureLane();
913
+ }
914
+ return entry;
915
+ }
916
+
917
+ export function live(name) {
918
+ const existing = entries.get(name);
919
+ if (existing) return existing.store.handle;
920
+ const tag = liveTags().get(name);
921
+ if (!tag) throw new Error(`clay.live: this page has no <script type="application/json" merge="${name}" clay="live"> tag`);
922
+ const text = tag.element.textContent;
923
+ let value;
924
+ try {
925
+ value = parseJsonRelaxed(text);
926
+ } catch (err) {
927
+ throw new SyntaxError(`clay.live("${name}"): the tag's JSON does not parse: ${err.message}`);
928
+ }
929
+ const store = createStore({ name, value, ephemeral: tag.ephemeral });
930
+ const layout = tag.element.getAttribute("live-format") === "lines" ? "lines" : text.includes("\n") ? "pretty" : "line";
931
+ const entry = openEntry(name, store, { layout, leftAt: lineageOf(tag.element) });
932
+ return entry.store.handle;
933
+ }
934
+
935
+ // A page whose file carries no state of its own yet: the seed in memory, no tag in
936
+ // the document, no request, until its first genuine op is written by the ordinary
937
+ // save. Only an accepted write opens its room.
938
+ export function stageStore(name, { value, represented = null, signal = null } = {}) {
939
+ const existing = entries.get(name);
940
+ if (existing && !existing.staged) throw new Error(`clay.live("${name}"): the store is open in a room`);
941
+ const entry = existing ?? openEntry(name, createStore({ name, value, ephemeral: false }), { staged: { represented, signal } });
942
+ return {
943
+ handle: entry.store.handle,
944
+ isPending: () => !!entry.staged && entry.store.pending.length > 0,
945
+ flush: (options = {}) => flushStaged(entry, options),
946
+ dispose() {
947
+ if (entry.staged && entries.get(name) === entry) entries.delete(name);
948
+ },
949
+ // The session that staged this store is over, whether or not its first write was
950
+ // accepted: the room keeps the value, and the next session stages its own from the
951
+ // bytes it represents.
952
+ close() {
953
+ if (entries.get(name) === entry) entries.delete(name);
954
+ },
955
+ };
956
+ }
957
+
958
+ // The first write, through the save this tab would make anyway.
959
+ async function flushStaged(entry, { timeoutMs = 10_000 } = {}) {
960
+ const staged = entry.staged;
961
+ if (!staged || !entry.store.pending.length) return { ok: true };
962
+ if (staged.conflict) return { ok: false, code: staged.conflict };
963
+ const at = staged.represented?.etag || null;
964
+ const held = lastSeenEtag();
965
+ if (at && held && at !== held) return { ok: false, code: "stale-document" };
966
+ if (at && !held) recordEtag(at);
967
+ const result = await requestCheckpoint({ timeoutMs });
968
+ return result.ok ? { ok: true } : { ok: false, code: staged.conflict ?? result.code ?? null };
969
+ }
970
+
971
+ function activateStaged(entry) {
972
+ if (!entry.staged) return;
973
+ entry.staged = null;
974
+ // Its file had no tag and the live page never gets one: every later capture builds it.
975
+ entry.tagless = true;
976
+ fast?.storeOpened(entry.store.name);
765
977
  ensureLane();
766
- return store.handle;
767
978
  }
768
979
 
769
980
  // Every capture carries each saved store's current value. A never-saved store
@@ -773,7 +984,19 @@ onSnapshot((clone, { forSave, requestRefresh }) => {
773
984
  const tags = liveTags(clone, { warn: false });
774
985
  for (const entry of entries.values()) {
775
986
  if (entry.store.ephemeral) continue;
776
- const tag = tags.get(entry.store.name);
987
+ let tag = tags.get(entry.store.name);
988
+ // A staged store reaches the file here, where the boot would have put its tag, and
989
+ // so does one that started that way, on every save after its first.
990
+ if (!tag && (entry.staged || entry.tagless)) {
991
+ const article = clone.querySelector("article[data-page]");
992
+ if (!article) continue;
993
+ const element = article.ownerDocument.createElement("script");
994
+ element.setAttribute("type", "application/json");
995
+ element.setAttribute("merge", entry.store.name);
996
+ element.setAttribute("clay", "live");
997
+ article.parentNode.insertBefore(element, article);
998
+ tag = { name: entry.store.name, ephemeral: false, element };
999
+ }
777
1000
  if (!tag) continue;
778
1001
  // A recovering store's own operations are not written again while the answer
779
1002
  // about them is still open, and not before the value the file holds now has
@@ -936,6 +1159,7 @@ function confirmPin(html, pin) {
936
1159
  } else {
937
1160
  entry.store.confirm(saved.mark);
938
1161
  entry.leftAt = saved.lineage && tag.element.getAttribute("live-by") === saved.by ? saved.lineage : { disk: true };
1162
+ activateStaged(entry);
939
1163
  }
940
1164
  settle(entry);
941
1165
  }
@@ -992,6 +1216,17 @@ onSaveOutcome(({ captureId, result, attempts, sent }) => {
992
1216
  if (result?.ok) return;
993
1217
  const pin = capturePins.get(captureId);
994
1218
  if (!pin) return;
1219
+ const stagedEntry = [...pin.keys()].map((name) => entries.get(name)).find((entry) => entry?.staged);
1220
+ // Nothing was written: the staged store is clean and nothing follows.
1221
+ if (stagedEntry && result.code === "page-nothing-to-write") {
1222
+ stagedEntry.store.confirm(stagedEntry.store.mark);
1223
+ dropCapture(captureId);
1224
+ releaseBody();
1225
+ settle(stagedEntry);
1226
+ return;
1227
+ }
1228
+ // This tab's value does not descend from the stored document: never send it again.
1229
+ if (stagedEntry && result.code === "page-lineage-stale") stagedEntry.staged.conflict = "conflict";
995
1230
  dropCapture(captureId);
996
1231
  const landed = mayHaveLanded(result, sent);
997
1232
  const plan = landed ? historyPlan(captureId) : null;
@@ -1000,11 +1235,47 @@ onSaveOutcome(({ captureId, result, attempts, sent }) => {
1000
1235
  if (plan) startHistoryRecovery(plan, { result, attempts, startedAt: Date.now() });
1001
1236
  });
1002
1237
 
1238
+ // The recovery integration's turn at an accepted save, when one journals this page.
1239
+ // Registered by page code once it holds a `page.yjs` channel, so a page that journals
1240
+ // nothing loads neither the proof builder nor the conditional client, and pays one null
1241
+ // check per save.
1242
+ let pageCheckpoints = null;
1243
+
1244
+ /**
1245
+ * Hand every accepted save to a page checkpoint integration, or null to stop.
1246
+ *
1247
+ * The integration builds the proof from the capture's pinned page state and the bytes
1248
+ * the host took; nothing here decides what is covered. Its answer is not awaited: a save
1249
+ * that succeeded is saved whether or not a journal can be retired, and a failure there
1250
+ * must never reach the save's own outcome.
1251
+ *
1252
+ * @param {?Function} install - ({capture, state, article, etag}) => unknown
1253
+ */
1254
+ export function registerPageCheckpoints(install) {
1255
+ pageCheckpoints = install;
1256
+ }
1257
+
1258
+ function offerCheckpoint(captureId, html) {
1259
+ if (!pageCheckpoints) return;
1260
+ // Read synchronously, before this capture is released: the value is the one its page
1261
+ // tag carried, and the edits that followed it are not in these bytes.
1262
+ const state = capturePageState(captureId);
1263
+ if (!state) return;
1264
+ let article = null;
1265
+ try { article = parseHtml(html).querySelector("article[data-page]")?.innerHTML ?? null; }
1266
+ catch (_) { article = null; }
1267
+ const etag = lastSeenEtag();
1268
+ Promise.resolve()
1269
+ .then(() => pageCheckpoints({ capture: String(captureId), state, article, etag }))
1270
+ .catch(() => {});
1271
+ }
1272
+
1003
1273
  // An accepted body confirms the ops of the capture that sent it, and only those.
1004
1274
  // A null context (a raw save) cannot consume a captured pin.
1005
1275
  function handleAccepted(html, context) {
1006
1276
  const captureId = context?.captureId;
1007
1277
  if (captureId === null || captureId === undefined) return;
1278
+ offerCheckpoint(captureId, html);
1008
1279
  const pin = capturePins.get(captureId);
1009
1280
  if (!pin) return;
1010
1281
  dropCapture(captureId);
@@ -1088,6 +1359,8 @@ export function receiveFrame(html, seq = null) {
1088
1359
  else if (pageTag.getAttribute("live-by") !== by) pageTag.setAttribute("live-by", by);
1089
1360
  }
1090
1361
  if (entry && !entry.store.ephemeral) {
1362
+ // An old seed is never merged into a newer article before its first write.
1363
+ if (entry.staged) continue;
1091
1364
  if (entry.store.handle.status.mode === "file") receive(entry, text, "remote", dropIds);
1092
1365
  continue;
1093
1366
  }
@@ -1100,10 +1373,13 @@ document.addEventListener("clay:sync-applied", (event) => receiveFrame(event.det
1100
1373
  // The last chance to save live changes made since the previous checkpoint. A
1101
1374
  // keepalive body is capped near 60 KB; a larger page's save may not finish.
1102
1375
  document.addEventListener("visibilitychange", () => {
1103
- if (document.visibilityState === "hidden") checkpoints.flush();
1376
+ if (document.visibilityState === "hidden" && isEditMode && writerAdmitted()) checkpoints.flush();
1104
1377
  });
1105
1378
  window.addEventListener("pagehide", () => {
1106
1379
  closing = true;
1380
+ // A view, and an edit whose session has closed admission or been released, saves
1381
+ // nothing here: its bytes are the reader's, and the exit's own barrier writes them.
1382
+ if (!isEditMode || !writerAdmitted()) return;
1107
1383
  // A hide save still on the wire is answered after the page is gone too.
1108
1384
  const latest = unsentCaptureId ?? wireCaptureId;
1109
1385
  const pin = checkpoints.flushing && latest !== null ? capturePins.get(latest) : null;
@@ -1121,6 +1397,14 @@ export function storeOf(name) {
1121
1397
  return entries.get(name)?.store || null;
1122
1398
  }
1123
1399
 
1400
+ // A caller that opened a store itself is done with it: the next entry reads the tag the
1401
+ // reader now states. Only the store that caller opened is closed; one the app opened
1402
+ // first, or a staged store, stays.
1403
+ export function closeStore(name, store) {
1404
+ const entry = entries.get(name);
1405
+ if (entry && entry.store === store && !entry.staged) entries.delete(name);
1406
+ }
1407
+
1124
1408
  export function openStores() {
1125
1409
  return [...entries.values()].map((entry) => entry.store);
1126
1410
  }
@@ -0,0 +1 @@
1
+ export default '<svg xmlns="http://www.w3.org/2000/svg" viewBox="-45.87 -57.96 100.26 60.98" fill="currentColor" aria-hidden="true" focusable="false"><path fill-rule="evenodd" d="M44.35 -4.56Q40.03 -0.52 34.69 -0.39Q29.34 -0.25 24.81 -0.14Q20.27 -0.02 15.6 0Q10.93 0.02 6.41 0Q1.89 -0.02 -2.79 -0.02Q-7.48 -0.02 -12.11 -0.14Q-16.73 -0.25 -21.64 -0.27Q-26.55 -0.3 -31.72 -2.25Q-36.88 -4.2 -39.87 -9.12Q-42.87 -14.03 -42.8 -19.61Q-42.74 -25.18 -40.43 -29.94Q-38.12 -34.69 -34.82 -38.78Q-31.52 -42.87 -27.25 -45.6Q-22.98 -48.33 -18.35 -50.59Q-13.73 -52.85 -8.7 -53.88Q-3.68 -54.91 1.32 -54.93Q6.32 -54.96 11.44 -54.56Q16.55 -54.16 21.35 -52.2Q26.16 -50.24 30.69 -48.01Q35.22 -45.78 38.99 -42.18Q42.76 -38.59 45.67 -34.37Q48.59 -30.15 49.99 -24.87Q51.39 -19.59 50.04 -14.09Q48.68 -8.6 44.35 -4.56ZM0.66 -30.49C0.5 -28.73 -0.88 -27.42 -2.42 -27.57C-3.96 -27.71 -5.08 -29.25 -4.91 -31.01C-4.75 -32.77 -3.36 -34.08 -1.83 -33.94C-0.29 -33.79 0.83 -32.25 0.66 -30.49ZM13.61 -29.28C13.44 -27.52 12.06 -26.21 10.52 -26.35C8.98 -26.5 7.87 -28.04 8.03 -29.8C8.2 -31.56 9.58 -32.87 11.12 -32.73C12.66 -32.58 13.77 -31.04 13.61 -29.28ZM-1.23 -17.71Q-0.62 -17.35 -0.03 -17.08Q0.56 -16.81 1.15 -16.54Q1.73 -16.28 2.4 -16Q3.07 -15.72 3.87 -15.6Q4.66 -15.48 5.4 -15.55Q6.13 -15.62 6.77 -15.79Q7.41 -15.96 7.97 -16.23Q8.53 -16.5 9 -16.91Q9.46 -17.32 9.73 -17.9Q9.99 -18.47 10.08 -19.12Q10.17 -19.77 10.08 -20.42Q9.99 -21.06 9.45 -21.32Q8.91 -21.58 8.27 -21.38Q7.63 -21.17 6.99 -20.91Q6.36 -20.65 5.7 -20.61Q5.04 -20.56 4.33 -20.73Q3.62 -20.9 2.92 -21.14Q2.23 -21.39 1.54 -21.68Q0.85 -21.97 0.16 -22.27Q-0.52 -22.56 -1.22 -22.79Q-1.92 -23.03 -2.49 -22.61Q-3.05 -22.19 -3.23 -21.5Q-3.4 -20.81 -3.2 -20.09Q-3.01 -19.38 -3.02 -19.15Q-3.02 -18.93 -2.42 -18.5Q-1.83 -18.06 -1.23 -17.71Z"/></svg>';