@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
@@ -1,417 +1,240 @@
1
1
  /**
2
- * source.js — wire the source map into the save pipeline.
2
+ * source.js — the facade over the source map.
3
3
  *
4
4
  * On by default in edit mode; `exclude=source` turns it off. The mechanism lives in
5
- * core/source-map.js; this file is the
6
- * lifecycle around it: fetch the bytes this document was loaded from, pair them to
7
- * the page, render the save through them, verify, and re-pair when the file moves
8
- * under us.
9
- *
10
- * FOUR MOMENTS
11
- *
12
- * boot fetch the source, model it, refuse anything that is not this
13
- * document, and pair it against a save clone.
14
- * save render; verify; send the render if it verifies and today's full
15
- * serialization if it does not, counting the second case.
16
- * accepted re-model against the bytes the host took, so the next save is
17
- * measured from what is on disk rather than from what was there at
18
- * boot.
19
- * sync-applied re-pair, because a morph replaces live nodes and the map is keyed
20
- * by node identity.
21
- *
22
- * The accepted-save re-model is deferred and coalesced, so it never sits between a save
23
- * landing and the page hearing about it. A sync frame's re-pair shares that idle refresh,
24
- * so a burst of frames costs one walk, and a save, `text()` or `locate()` that comes
25
- * before it runs it first, so none of them work from a map the morph made stale.
26
- *
27
- * NOTHING HERE BLOCKS BOOT. The install is async and the loader does not wait for
28
- * it, because a save before it finishes is a save exactly as it is today. Waiting
29
- * would trade a guaranteed delay on every page for a better outcome on the rare save
30
- * that lands in the first few hundred milliseconds.
31
- *
32
- * WHEN IT DOES NOTHING
33
- *
34
- * Any of these leaves the save pipeline untouched, and each says so once in the
35
- * console rather than repeatedly at save time:
36
- *
37
- * the source fetch fails, redirects, or is not text/html
38
- * the fetched bytes disagree with the live document outside <html>
39
- * the render throws
40
- * the render does not verify
5
+ * core/source-map.js and the session around it in source/session.js, and neither is
6
+ * imported here: this file is the synchronous API a page reads (`clay.source`), the mode
7
+ * lease, and the one demand that loads the rest.
8
+ *
9
+ * ON DEMAND
10
+ *
11
+ * The parser is 48 KB of parse5 plus the source map, and a page pays for it only when it
12
+ * can change something. `prepare` installs a light lease and fetches nothing, `enable`
13
+ * admits demand, and demand is exactly two things: a save that needs a byte-preserving
14
+ * render, which asks `ensureForSave` from the save pipeline after its cheap gate, and
15
+ * code in edit mode reading `clay.source.ready`. A clean autosave, a reader and a page
16
+ * that never saves never load it, and an idle tail that fires after edit ready loads
17
+ * nothing either.
18
+ *
19
+ * Before the session installs, `text()`, `locate()` and `stats()` answer the unavailable
20
+ * values they always did, with `installed: false`. What the facade keeps in the meantime
21
+ * is the newest bytes this page knows the file holds — the reader's receipt for the
22
+ * session, an accepted save, a disk frame — so the install starts from the file rather
23
+ * than asking the host for a copy it has just written, and a newer receipt supersedes an
24
+ * older one.
25
+ *
26
+ * LIFECYCLE
27
+ *
28
+ * `prepare` installs the light lease, `enable` admits demand, `quiesce` keeps the
29
+ * renderer for the exit's barrier save, and `dispose` takes it back and invalidates any
30
+ * load still in flight: an answer that arrives after the session ended installs nothing.
31
+ * A load that fails is the same logged, non-fatal outcome as before — saves go out exactly
32
+ * as they would without this plugin — and `clay.source.ready` asks again.
41
33
  */
42
34
 
43
- import {
44
- model,
45
- checkSource,
46
- locate,
47
- nodeAt,
48
- pair,
49
- render,
50
- verify
51
- } from '../core/source-map.js';
52
- import {
53
- captureSaveClone,
54
- originalSnapshotNode,
55
- setSaveRenderer
56
- } from '../core/snapshot.js';
57
35
  import { onSaveAccepted } from '../core/save-core.js';
36
+ import { startsAtImport } from '../core/is-edit-mode.js';
58
37
 
59
- const state = {
38
+ // Today's answer before the session installs: the same fields `stats()` returns, with
39
+ // `installed: false` and nothing counted.
40
+ const UNAVAILABLE = {
60
41
  installed: false,
61
42
  refused: null,
62
- model: null,
63
- map: null,
64
- stats: null,
43
+ sourceBytes: null,
44
+ paired: 0,
45
+ unresolved: 0,
46
+ unmatchedLive: 0,
47
+ unmatchedSource: 0,
48
+ attrDiffs: 0,
65
49
  saves: 0,
66
50
  reprints: 0,
67
51
  lastReprint: null,
68
52
  partialReprints: 0,
69
53
  lastPartialReprint: null,
70
- // What the last render produced, so the save the host accepts can be counted and
71
- // announced as what it was. Cleared once reported.
72
- lastOutcome: null,
73
54
  lastRenderMs: null,
74
55
  lastBytes: null,
75
- timing: { fetch: null, model: null, pair: null, refresh: null },
76
56
  refreshes: 0,
77
- refreshQueued: false,
78
- pendingBytes: null,
57
+ timing: { fetch: null, model: null, pair: null, refresh: null },
79
58
  };
80
59
 
81
- const now = () => (typeof performance !== 'undefined' ? performance.now() : Date.now());
82
-
83
- /**
84
- * The bytes this document was loaded from.
85
- *
86
- * Every guard here is about one failure: writing somebody else's page into this
87
- * file. `redirect: 'manual'` and the `redirected` check catch a host that answers a
88
- * logged-out request with a login page at another URL; the content-type check
89
- * catches one that answers with JSON or an error page; `cache: 'no-store'` keeps a
90
- * stale cached copy of an older version of this document out of the model. The
91
- * shape check in checkSource is the backstop for whatever gets past all three.
92
- */
93
- async function fetchSource(url) {
94
- const res = await fetch(url, { cache: 'no-store', redirect: 'manual', credentials: 'same-origin' });
95
- if (!res.ok) throw new Error(`source fetch: status ${res.status}`);
96
- if (res.redirected || res.type === 'opaqueredirect') throw new Error('source fetch: redirected');
97
- const type = (res.headers.get('content-type') || '').toLowerCase();
98
- if (!type.startsWith('text/html')) throw new Error(`source fetch: content-type ${type || 'missing'}`);
99
- return res.text();
100
- }
101
-
102
- /**
103
- * Pair the model against the page.
104
- *
105
- * Over a SAVE CLONE, not the live DOM. The clone is in the file's own domain: edit
106
- * mode deactivated back to the inert attribute forms, [no-save] regions gone, every
107
- * document transform run. Pairing the live DOM instead would compare
108
- * `contenteditable="true"` against the file's `inert-contenteditable="true"` and lose
109
- * the two strongest signature tiers on every activated element and every ancestor of
110
- * one. The map is keyed through snapshot provenance to the LIVE node, because the
111
- * clone is rebuilt on every save and its nodes are new objects each time.
112
- *
113
- * This capture is an INSPECTION, not a save, and it happens at boot and again on every
114
- * incoming live-sync frame. So it takes neither of the two things a save capture does
115
- * on its way past: it does not close the undo batch, which at boot and mid-typing
116
- * would split the user's undo history at a point they did not make, and it does not
117
- * run the page's own [onbeforesave] and [onbeforesnapshot] handlers, which are author
118
- * JavaScript that can do anything and are meant to run once per save. The cost is that
119
- * a page using those handlers pairs against a tree slightly behind the one it will
120
- * save, which costs some formatting fidelity on the nodes a handler touches and
121
- * nothing else, because the render is verified either way.
122
- */
123
- function pairAgainstPage(m) {
124
- const clone = captureSaveClone({ flushUndo: false, authored: false });
125
- return pair(clone, m, originalSnapshotNode);
60
+ // The loaded session. Null until something demands it.
61
+ let session = null;
62
+ // The mode entry that prepared this session, as the install needs it: what the reader
63
+ // said this tab represents.
64
+ let context = null;
65
+ // The newest bytes this page knows the file holds, and the version that came with them.
66
+ let receipt = null;
67
+ // Whether demand is admitted: an edit boot's own session, or a lease that was enabled.
68
+ let admitted = startsAtImport();
69
+ // How many sessions have ended. A load that resolves for an older one installs nothing,
70
+ // so a late answer cannot put a renderer or a map back into a view.
71
+ let epoch = 0;
72
+ // The install this session asked for, so two saves in the same breath share it.
73
+ let installing = null;
74
+ // Whether this session's one install attempt has already answered. A save waits for an
75
+ // attempt that is still running and never for one that is done: a refusal and a failed
76
+ // load are both answers, and the next save goes out as it would without the plugin.
77
+ // `clay.source.ready` is the way to ask again, and it retries.
78
+ let answered = false;
79
+
80
+ let loading = null;
81
+ function loadSession() {
82
+ loading ??= import("../source/session.js").catch((error) => {
83
+ loading = null;
84
+ throw error;
85
+ });
86
+ return loading;
126
87
  }
127
88
 
128
- function install(sourceUrl) {
129
- const t0 = now();
130
- return fetchSource(sourceUrl).then((src) => {
131
- const t1 = now();
132
- const m = model(src);
133
- const refused = checkSource(m, document);
134
- if (refused) throw new Error(`source refused: ${refused}`);
135
- const t2 = now();
136
- const { map, stats } = pairAgainstPage(m);
137
- const t3 = now();
138
-
139
- state.model = m;
140
- state.map = map;
141
- state.stats = stats;
142
- state.timing = { fetch: t1 - t0, model: t2 - t1, pair: t3 - t2, refresh: null };
143
- state.installed = true;
144
- setSaveRenderer(renderSave);
145
- return summary();
146
- }).catch((err) => {
147
- // Not an error the page can do anything about, and not a failure of the save:
148
- // saves go out exactly as they would without this plugin. Said once.
149
- state.refused = String(err && err.message ? err.message : err);
150
- console.info(`clayjs: source map off (${state.refused}); saves use the full serialization`);
151
- return null;
152
- });
89
+ /** Keep these bytes as what this page believes the file holds. A newer one wins. */
90
+ function retain(bytes, etag, seq) {
91
+ if (typeof bytes !== 'string') return;
92
+ receipt = Object.freeze({ bytes, etag: etag ?? null, seq: seq ?? null });
153
93
  }
154
94
 
155
95
  /**
156
- * Render one save, or hand back today's bytes.
157
- *
158
- * Exported so a test can drive the whole path, including the fallback, without a
159
- * server: `renderSave(clone, today, { breakText: true })` makes every text node
160
- * reprint, which changes the bytes while leaving the tree identical, and the verifier
161
- * must still pass it — a tree comparison is the right answer there, and reading that
162
- * pass as "the verifier is blind" is a mistake this project has already made once.
163
- * `{ corrupt: 'drop-first-text' }` is the one that must be REJECTED: it changes the
164
- * tree, which is exactly what the verifier is for.
96
+ * Start the one install this session needs, or hand back the one already running.
165
97
  *
166
- * A render that does not verify is not thrown away whole. verify says where it first
167
- * differs; that element is printed in full and the render tried again, widening to the
168
- * parent while it still fails. Everything outside the printed elements is still the
169
- * author's bytes. Only when the widening reaches <html>, or the failure has no node to
170
- * point at (the prologue, a parse-error count), is today's full serialization sent.
171
- *
172
- * Nothing is counted or announced here. A render is not a save: an autosave that
173
- * found nothing changed, a refused save and a failed request all rendered. The
174
- * outcome is recorded, and `adopt` reports it when the host accepts these exact bytes.
98
+ * The session module is imported here and nowhere else, so a page that never demands the
99
+ * parser never evaluates source-map.js. A failed import clears the memo: the fallback is
100
+ * today's save, and the next demand asks again.
175
101
  */
176
- export function renderSave(clone, today, opts = {}) {
177
- if (!state.installed) return today;
178
- // A refresh still waiting for idle time runs now, so this save never renders from a
179
- // map a morph made stale. It pairs against the page, not this clone: onbeforesave may
180
- // have changed the clone, and the map must describe the live page.
181
- refreshNow();
182
- state.lastOutcome = null;
183
- const print = new Set();
184
- let firstDiff = null;
185
- for (let round = 0; round <= MAX_PRINT_ROUNDS; round++) {
186
- let out;
187
- try {
188
- out = render(clone, state.map, state.model, originalSnapshotNode, { ...opts, print });
189
- } catch (err) {
190
- return fallback('render threw: ' + (err && err.message ? err.message : err), today);
102
+ function installOnce() {
103
+ if (installing) return installing;
104
+ const mine = epoch;
105
+ const entry = context;
106
+ installing = loadSession().then((mod) => {
107
+ session = mod;
108
+ if (mine !== epoch) {
109
+ installing = null;
110
+ return null;
191
111
  }
192
- const v = verify(out.text, today, document, clone, state.model.parseErrors);
193
- if (v.ok) {
194
- state.lastRenderMs = out.ms;
195
- state.lastBytes = out.text.length;
196
- state.lastOutcome = print.size
197
- ? { text: out.text, scope: 'partial', reason: firstDiff, printed: print.size }
198
- : { text: out.text, scope: null };
199
- return out.text;
200
- }
201
- if (firstDiff === null) firstDiff = v.diff;
202
- if (!v.at || !widen(clone, nodeAt(clone, v.at), print)) return fallback(firstDiff, today);
203
- }
204
- return fallback(firstDiff, today);
205
- }
206
-
207
- const MAX_PRINT_ROUNDS = 8;
208
-
209
- /**
210
- * Add the next element to print: the one holding `node`, or, when that is already
211
- * inside a printed element, that element's parent. false when the only element left is
212
- * the root, which is the full serialization by another name.
213
- */
214
- function widen(clone, node, print) {
215
- let el = node && node.nodeType === 1 ? node : node && node.parentNode;
216
- for (let a = el; a && a !== clone; a = a.parentNode) {
217
- if (print.has(a)) { print.delete(a); el = a.parentNode; break; }
218
- }
219
- if (!el || el.nodeType !== 1 || el === clone) return false;
220
- print.add(el);
221
- return true;
222
- }
223
-
224
- /** Send today's full serialization, and remember that this render was a fallback. */
225
- function fallback(reason, today) {
226
- state.lastOutcome = { text: today, scope: 'full', reason };
227
- return today;
112
+ return mod.start({ context: entry, latest: () => receipt });
113
+ }).catch((error) => {
114
+ installing = null;
115
+ console.info('clayjs: source map off (' + (error && error.message ? error.message : error) + '); saves use the full serialization');
116
+ return null;
117
+ }).then((value) => {
118
+ if (mine === epoch) answered = true;
119
+ return value;
120
+ });
121
+ return installing;
228
122
  }
229
123
 
230
124
  /**
231
- * The fallback, and the alarm.
125
+ * The save pipeline's question: may this save be rendered through the source map?
232
126
  *
233
- * Reported for a save the host accepted. The bytes that went out were today's, so the
234
- * floor of this whole mechanism is the behaviour it replaces. The event and the
235
- * counter are how a reprint gets fixed
236
- * in the renderer or in the page, which is the only place it should ever be fixed: a
237
- * verifier loosened to make this counter look better would pass exactly the writes it
238
- * exists to stop.
239
- */
240
- function reprint(reason) {
241
- state.reprints++;
242
- state.lastReprint = reason;
243
- console.warn('clayjs: source map did not verify, saving the full serialization instead:', reason);
244
- document.dispatchEvent(new CustomEvent('clay:save-reprinted', { detail: { reason, scope: 'full' } }));
245
- }
246
-
247
- /**
248
- * Part of the document was printed rather than copied. Counted apart from a full
249
- * reprint because they are different news: this one kept the author's bytes everywhere
250
- * else, and its rate says how often the renderer and the page disagree about one
251
- * element, which is where the next renderer fix is.
127
+ * Called — through `clay.source.ensureForSave`, so a page that excluded this plugin asks
128
+ * nothing — after the cheap gate that says a save may happen and before the one capture.
129
+ * null means there is nothing to wait for: no session for this page, demand not admitted,
130
+ * or this session's attempt has already answered. The save then goes out exactly as it
131
+ * would without this plugin.
252
132
  */
253
- function partialReprint(reason, printed) {
254
- state.partialReprints++;
255
- state.lastPartialReprint = reason;
256
- console.info(`clayjs: source map printed ${printed} element(s) in full to match the page:`, reason);
257
- document.dispatchEvent(new CustomEvent('clay:save-reprinted', { detail: { reason, scope: 'partial', printed } }));
133
+ function ensureForSave() {
134
+ if (!admitted || answered) return null;
135
+ return installOnce();
258
136
  }
259
137
 
260
- /**
261
- * The host took these bytes, so this is what the file holds now.
262
- *
263
- * Deferred, like the re-pair below and for the same reason: re-modelling walks the
264
- * whole document, and doing it inline would put that walk between the save landing
265
- * and the page being told about it. Only the most recent accepted bytes matter, so a
266
- * burst of saves costs one refresh.
267
- *
268
- * It is also where a save is counted, because it is the one place that knows a save
269
- * reached the file.
270
- */
271
- function adopt(bytes) {
272
- if (!state.installed) return;
273
- report(bytes);
274
- state.pendingBytes = bytes;
275
- scheduleRefresh();
138
+ /** Take an install back: the renderer, a queued refresh, and any load still in flight. */
139
+ function end() {
140
+ epoch++;
141
+ admitted = false;
142
+ answered = false;
143
+ installing = null;
144
+ context = null;
145
+ if (session) session.release();
276
146
  }
277
147
 
278
- /**
279
- * Count and announce an accepted save, if these are the bytes the last render
280
- * produced. Bytes this module did not render (a caller of `saveHtml` with its own
281
- * string, or an older render) are not its save to report.
282
- */
283
- function report(bytes) {
284
- const outcome = state.lastOutcome;
285
- if (!outcome || outcome.text !== bytes) return;
286
- state.lastOutcome = null;
287
- state.saves++;
288
- if (outcome.scope === 'full') reprint(outcome.reason);
289
- else if (outcome.scope === 'partial') partialReprint(outcome.reason, outcome.printed);
290
- }
148
+ // An accepted save before the install is the file: the retained receipt becomes these
149
+ // bytes, and the session's superseded answer is what makes a newer one win.
150
+ onSaveAccepted((bytes, accepted) => {
151
+ retain(bytes, receipt ? receipt.etag : null, receipt ? receipt.seq : null);
152
+ if (session && !session.installed()) session.adoptAccepted(bytes, accepted);
153
+ });
291
154
 
292
- /**
293
- * A morph replaced live nodes, so the map is keyed by objects that are no longer in
294
- * the page, and it has to be re-paired either way. A DISK frame also carries the bytes
295
- * now on disk, written by somebody else: those become the model, the same as bytes this
296
- * tab saved, or the next save would copy the old formatting back over theirs. A peer
297
- * frame changed nothing on disk, so the model stays. The re-pair runs in idle time, or
298
- * earlier if a save, `text()` or `locate()` needs it first.
299
- */
300
- function queueRepair(event) {
301
- if (!state.installed) return;
155
+ // A disk frame is somebody else writing the file. Before the install it replaces the
156
+ // receipt the same way; after it, the session's own listener re-models from it.
157
+ document.addEventListener('clay:sync-applied', (event) => {
302
158
  const detail = event && event.detail;
303
- if (detail && detail.source === 'disk' && typeof detail.html === 'string') state.pendingBytes = detail.html;
304
- scheduleRefresh();
305
- }
159
+ if (!detail || detail.source !== 'disk' || typeof detail.html !== 'string') return;
160
+ retain(detail.html, detail.etag, detail.seq);
161
+ if (session && !session.installed()) session.adoptAccepted(detail.html, null);
162
+ });
306
163
 
307
- /**
308
- * Re-model if a save was accepted, re-pair either way, once, off the critical path.
309
- *
310
- * Coalescing is what makes this safe as well as cheap. Saves are serialized, so the
311
- * most recent accepted bytes are the file; a refresh that skipped an intermediate
312
- * save would still land on the right answer, and one that ran them out of order could
313
- * not.
314
- */
315
- function scheduleRefresh() {
316
- if (state.refreshQueued) return;
317
- state.refreshQueued = true;
318
- if (typeof requestIdleCallback === 'function') requestIdleCallback(refreshNow, { timeout: 2000 });
319
- else setTimeout(refreshNow, 0);
320
- }
321
-
322
- /**
323
- * Run a queued refresh now. A no-op when none is queued, which is also what makes the
324
- * idle callback of a refresh that `text()` or `locate()` already ran harmless.
325
- */
326
- function refreshNow() {
327
- if (!state.refreshQueued) return;
328
- state.refreshQueued = false;
329
- if (!state.installed) return;
330
- const bytes = state.pendingBytes;
331
- state.pendingBytes = null;
332
- const t = now();
333
- // The two halves fail independently, so they are tried independently. A refused
334
- // re-model used to skip the re-pair with it, and the re-pair is the half that
335
- // cannot be skipped: a morph replaced live nodes, so the old map is keyed by
336
- // objects no longer in the page, and every save after that reprints the whole
337
- // document until something else queues a refresh. The previous model still
338
- // describes real bytes, so re-pairing against it is the right answer.
339
- let m = state.model;
340
- if (bytes !== null) {
341
- try {
342
- const next = model(bytes);
343
- const refused = checkSource(next, document);
344
- if (refused) throw new Error(refused);
345
- m = next;
346
- } catch (err) {
347
- console.warn('clayjs: source map kept the previous model, the accepted bytes did not model:', err);
348
- }
349
- }
350
- try {
351
- const { map, stats } = pairAgainstPage(m);
352
- state.model = m;
353
- state.map = map;
354
- state.stats = stats;
355
- state.refreshes++;
356
- state.timing.refresh = now() - t;
357
- } catch (err) {
358
- // The map is now stale rather than wrong: it still describes the pairing as of
359
- // the last successful refresh. Renders off a stale map verify or fall back like
360
- // any other, so this costs formatting fidelity and nothing else.
361
- console.warn('clayjs: source map could not re-pair, continuing on the previous map:', err);
362
- }
363
- }
364
-
365
- function summary() {
366
- const s = state.stats;
367
- return {
368
- installed: state.installed,
369
- refused: state.refused,
370
- sourceBytes: state.model ? state.model.src.length : null,
371
- paired: s ? s.paired : 0,
372
- unresolved: s ? s.unresolved : 0,
373
- unmatchedLive: s ? s.unmatchedLive.length : 0,
374
- unmatchedSource: s ? s.unmatchedSource.length : 0,
375
- attrDiffs: s ? s.attrDiffs.length : 0,
376
- saves: state.saves,
377
- reprints: state.reprints,
378
- lastReprint: state.lastReprint,
379
- partialReprints: state.partialReprints,
380
- lastPartialReprint: state.lastPartialReprint,
381
- lastRenderMs: state.lastRenderMs,
382
- lastBytes: state.lastBytes,
383
- refreshes: state.refreshes,
384
- timing: state.timing,
385
- };
164
+ /** Render one save through the loaded session, or hand back today's bytes. */
165
+ export function renderSave(clone, today, opts = {}) {
166
+ return session ? session.renderSave(clone, today, opts) : today;
386
167
  }
387
168
 
388
169
  export const source = {
389
- ready: null,
390
- stats: () => { refreshNow(); return summary(); },
391
- /** The bytes this module believes are on disk right now. A refresh still queued from a save or a disk frame runs first, so this is never the file before that. */
392
- text: () => { refreshNow(); return state.model ? state.model.src : null; },
170
+ /**
171
+ * The install, in edit mode: reading this is the second thing that demands the parser.
172
+ * Null where there is nothing to install, which is what a view and a page without the
173
+ * plugin have always answered.
174
+ */
175
+ get ready() {
176
+ if (!admitted) return null;
177
+ return installOnce();
178
+ },
179
+ stats: () => (session ? session.stats() : { ...UNAVAILABLE }),
180
+ /** The bytes this module believes are on disk right now. */
181
+ text: () => (session ? session.text() : null),
393
182
  /**
394
183
  * Where a live element is in those bytes: `{ from, to, line, column }`, or null.
395
184
  *
396
- * Offsets and `column` are UTF-16 code units into `text()`, not bytes. Slice `text()` with them
397
- * and the answer is exact; hand them to something that counts bytes and it is wrong on any
398
- * document with non-ASCII content, which is the kind of mistake that shows up months later in
399
- * one customer's file.
400
- *
401
- * null whenever this module cannot answer, which includes the whole document when the plugin
402
- * never installed or refused it. `stats().installed` and `stats().refused` say which, and an
403
- * agent that cannot tell "this element is new" from "this document has no map" will eventually
404
- * write into a file it was never modelling.
185
+ * Offsets and `column` are UTF-16 code units into `text()`, not bytes. null whenever this
186
+ * module cannot answer, which includes the whole document before the session installed.
187
+ * `stats().installed` and `stats().refused` say which.
405
188
  */
406
- locate: (node) => { refreshNow(); return state.installed && node ? locate(node, state.map, state.model) : null; },
189
+ locate: (node) => (session ? session.locateLive(node) : null),
407
190
  /** What did not pair, for working out why a document reprints more than it should. */
408
- unpaired: () => { refreshNow(); return (state.stats
409
- ? { live: state.stats.unmatchedLive.slice(0, 50), source: state.stats.unmatchedSource.slice(0, 50) }
410
- : null); },
191
+ unpaired: () => (session ? session.unpaired() : null),
192
+ /** The save pipeline's own ask: a promise to wait for before its capture, or null. */
193
+ ensureForSave,
411
194
  };
412
195
 
413
- onSaveAccepted(adopt);
414
- document.addEventListener('clay:sync-applied', queueRepair);
415
- source.ready = install(window.location.href);
196
+ /**
197
+ * The source map as a mode lease.
198
+ *
199
+ * `prepare` records the file this session represents and installs the light lease; it
200
+ * fetches nothing and pairs nothing. `enable` admits demand, which is the only thing that
201
+ * belongs to the commit. `quiesce` keeps the renderer for the exit's barrier save,
202
+ * `resume` admits demand again after a refused exit, and `dispose` takes the session back
203
+ * and invalidates whatever was still loading.
204
+ *
205
+ * `initial: true` is this tab's own edit boot, whose session is this one: the lease adopts
206
+ * it, and the boot's demand installed — or did not — exactly as it would have.
207
+ */
208
+ export function sourceParticipant() {
209
+ return {
210
+ id: 'clay:source',
211
+ // After the save session (10), whose saves this renderer answers, and before the
212
+ // page (400), whose editor takes the article the pairing is taken over.
213
+ order: 30,
214
+ prepare(entry = {}) {
215
+ const represented = entry && entry.represented;
216
+ if (represented) {
217
+ context = { represented };
218
+ retain(represented.bytes, represented.etag, represented.seq);
219
+ }
220
+ if (entry && entry.initial === true) {
221
+ admitted = true;
222
+ return { enable() {}, quiesce() {}, resume() {}, dispose: end };
223
+ }
224
+ const mine = ++epoch;
225
+ admitted = false;
226
+ answered = false;
227
+ installing = null;
228
+ return {
229
+ enable() { if (mine === epoch) admitted = true; },
230
+ quiesce() {},
231
+ resume() { if (mine === epoch) admitted = true; },
232
+ dispose() { if (mine === epoch) end(); },
233
+ };
234
+ },
235
+ };
236
+ }
237
+
238
+ export { sourceParticipant as modeParticipant };
416
239
 
417
240
  export default source;