@panphora/clayjs 1.15.2 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (551) hide show
  1. package/README.md +4 -5
  2. package/THIRD-PARTY-NOTICES.md +16 -0
  3. package/compat/highlight-11.9.0.min.js +1213 -0
  4. package/compat/highlight-LICENSE +29 -0
  5. package/compat/journal-assets.lock.json +22 -0
  6. package/compat/overtype-2.6.1.min.js +116 -0
  7. package/compat/overtype-LICENSE +21 -0
  8. package/dist/all.js +90 -0
  9. package/dist/clay-dom.js +84 -0
  10. package/dist/clay-events.js +103 -0
  11. package/dist/clay-internals.js +85 -0
  12. package/dist/clay-options.js +93 -0
  13. package/dist/clay-router.js +98 -0
  14. package/dist/clay-ui.js +95 -0
  15. package/dist/clay-utils.js +84 -0
  16. package/dist/clay.js +146 -0
  17. package/dist/clay.standalone.js +84291 -57611
  18. package/dist/m/attrs/add-from-T5LJXFFI.js +1 -0
  19. package/dist/m/attrs/onaftersave-OKSWEJQF.js +1 -0
  20. package/dist/m/attrs/refetch-on-save-ZUE7PSCI.js +1 -0
  21. package/dist/m/attrs/save-freeze-MYNVXREN.js +1 -0
  22. package/dist/m/c-2AZDZWCK.js +1 -0
  23. package/dist/m/c-2SPVQSHC.js +1 -0
  24. package/dist/m/c-2YYDMLNZ.js +1 -0
  25. package/dist/m/c-336SGU5W.js +1 -0
  26. package/dist/m/c-33JJ6CBX.js +2 -0
  27. package/dist/m/c-3GJ4SDE2.js +1 -0
  28. package/dist/m/c-3H6BZDNJ.js +6 -0
  29. package/dist/m/c-3OWDMNJR.js +2 -0
  30. package/dist/m/c-3RUU4TPX.js +91 -0
  31. package/dist/m/c-3XIRD4NU.js +1 -0
  32. package/dist/m/c-46XYM55L.js +1 -0
  33. package/dist/m/c-4DFEJ2PO.js +3 -0
  34. package/dist/m/c-4JZDZFF6.js +1 -0
  35. package/dist/m/c-4L3BTFRK.js +1 -0
  36. package/dist/m/c-4Q2XTQ2N.js +2 -0
  37. package/dist/m/c-4WKFKEKF.js +1 -0
  38. package/dist/m/c-56QRZTZH.js +1 -0
  39. package/dist/m/c-5J4E7O4Y.js +4 -0
  40. package/dist/m/c-5LKDWQTL.js +19 -0
  41. package/dist/m/c-5QWQ7A5L.js +1 -0
  42. package/dist/m/c-5RJFTLSF.js +1 -0
  43. package/dist/m/c-5V3MOVJT.js +1 -0
  44. package/dist/m/c-5VYOWDTI.js +1 -0
  45. package/dist/m/c-5YIHJ3TJ.js +1 -0
  46. package/dist/m/c-6AZOGAPE.js +1 -0
  47. package/dist/m/c-6CRUUXOX.js +5 -0
  48. package/dist/m/c-6HMB24EV.js +1 -0
  49. package/dist/m/c-6KEVEQ7L.js +1 -0
  50. package/dist/m/c-6NC5I2TV.js +1 -0
  51. package/dist/m/c-74MGYWZF.js +1 -0
  52. package/dist/m/c-7DJDKPGL.js +1 -0
  53. package/dist/m/c-7OLLJPWP.js +1 -0
  54. package/dist/m/c-7SWMYKAI.js +1 -0
  55. package/dist/m/c-A3E3NIMC.js +1 -0
  56. package/dist/m/c-A7LS5UZW.js +1 -0
  57. package/dist/m/c-AMSNPOCX.js +1 -0
  58. package/dist/m/c-AQLFEATB.js +1 -0
  59. package/dist/m/c-B3JM5ZXC.js +1 -0
  60. package/dist/m/c-BBVGTQG7.js +1 -0
  61. package/dist/m/c-BPF2NSPV.js +1 -0
  62. package/dist/m/c-BQ6LOBEE.js +1 -0
  63. package/dist/m/c-BTDI4Y23.js +1 -0
  64. package/dist/m/c-BWFVSBDF.js +1 -0
  65. package/dist/m/c-BYTKL6UX.js +1 -0
  66. package/dist/m/c-CFHW5AMY.js +6 -0
  67. package/dist/m/c-CJ3A7ZKE.js +1 -0
  68. package/dist/m/c-CQHPGKX2.js +1 -0
  69. package/dist/m/c-CYWLPPUS.js +1 -0
  70. package/dist/m/c-CZUSOB7V.js +1 -0
  71. package/dist/m/c-DCFSEJZG.js +1 -0
  72. package/dist/m/c-DCXOXBF3.js +1 -0
  73. package/dist/m/c-DFS4XZJV.js +2 -0
  74. package/dist/m/c-DNENWY4O.js +1 -0
  75. package/dist/m/c-DNZ5IFM6.js +1 -0
  76. package/dist/m/c-E4LIR5X5.js +1 -0
  77. package/dist/m/c-EJTGYSLN.js +1 -0
  78. package/dist/m/c-EVCQ3BKA.js +1 -0
  79. package/dist/m/c-EZU56NQV.js +34 -0
  80. package/dist/m/c-FDNHHZY5.js +1 -0
  81. package/dist/m/c-FILLTJA7.js +1 -0
  82. package/dist/m/c-FRLHSY7E.js +1 -0
  83. package/dist/m/c-FROJUZMV.js +1 -0
  84. package/dist/m/c-G2JPKCTP.js +1 -0
  85. package/dist/m/c-GB6TU7WV.js +1 -0
  86. package/dist/m/c-GFJJEBTU.js +1 -0
  87. package/dist/m/c-GJAEGGXY.js +1 -0
  88. package/dist/m/c-GOWBDM6J.js +1 -0
  89. package/dist/m/c-GQ6F6IDK.js +1 -0
  90. package/dist/m/c-GSFIGKOP.js +1 -0
  91. package/dist/m/c-GY422N3Q.js +1 -0
  92. package/dist/m/c-H4LG442Q.js +1 -0
  93. package/dist/m/c-HA6INT6J.js +1 -0
  94. package/dist/m/c-HAWFNBYG.js +1 -0
  95. package/dist/m/c-HDDEEL77.js +1 -0
  96. package/dist/m/c-HFKUVACH.js +1 -0
  97. package/dist/m/c-HHDFCNHV.js +1 -0
  98. package/dist/m/c-HPURYVZS.js +2 -0
  99. package/dist/m/c-I2ZATQCD.js +2 -0
  100. package/dist/m/c-IEM4NTCS.js +1 -0
  101. package/dist/m/c-IJUK2GPW.js +1 -0
  102. package/dist/m/c-IW6GGOU2.js +1 -0
  103. package/dist/m/c-JAX2667S.js +6 -0
  104. package/dist/m/c-JDCX26UP.js +25 -0
  105. package/dist/m/c-JRKY44DK.js +1 -0
  106. package/dist/m/c-JU3NK25H.js +1 -0
  107. package/dist/m/c-KAU6E43D.js +1 -0
  108. package/dist/m/c-KBS3UV6B.js +1 -0
  109. package/dist/m/c-KGVGROZT.js +1 -0
  110. package/dist/m/c-KI7M4AY3.js +1 -0
  111. package/dist/m/c-KIPQJX4B.js +1 -0
  112. package/dist/m/c-KQ6TYJAP.js +135 -0
  113. package/dist/m/c-KU3ZFADW.js +1 -0
  114. package/dist/m/c-L4WQSC27.js +1 -0
  115. package/dist/m/c-LFPHCXY3.js +1 -0
  116. package/dist/m/c-LRPUO2MX.js +1 -0
  117. package/dist/m/c-LWLBHLVC.js +3 -0
  118. package/dist/m/c-LZJPNR2W.js +1 -0
  119. package/dist/m/c-M2L47S2X.js +7 -0
  120. package/dist/m/c-MFP2ZWD4.js +1 -0
  121. package/dist/m/c-MKSQ4Y57.js +1 -0
  122. package/dist/m/c-MTQHWQC3.js +1 -0
  123. package/dist/m/c-N326R2JD.js +1 -0
  124. package/dist/m/c-N7JPUPGC.js +1 -0
  125. package/dist/m/c-NRPJEVJS.js +1 -0
  126. package/dist/m/c-NUFFJVVJ.js +1 -0
  127. package/dist/m/c-NUHP2XDB.js +1 -0
  128. package/dist/m/c-NUHSPT5P.js +1 -0
  129. package/dist/m/c-O72HVYSA.js +1 -0
  130. package/dist/m/c-OCSZ4QGN.js +1 -0
  131. package/dist/m/c-OIBUTDXI.js +1 -0
  132. package/dist/m/c-OIDRZKAF.js +1 -0
  133. package/dist/m/c-OW5QIGG3.js +1 -0
  134. package/dist/m/c-P7KNDG34.js +1 -0
  135. package/dist/m/c-PGRCK73F.js +1 -0
  136. package/dist/m/c-PQ5LK6QE.js +993 -0
  137. package/dist/m/c-PS5ARVWR.js +9 -0
  138. package/dist/m/c-PZ4XEPUP.js +2 -0
  139. package/dist/m/c-Q622NS2Y.js +1 -0
  140. package/dist/m/c-Q7NQD65V.js +1 -0
  141. package/dist/m/c-QH63LQOS.js +1 -0
  142. package/dist/m/c-QHIWXHRO.js +1 -0
  143. package/dist/m/c-QHWJ5ZIJ.js +1 -0
  144. package/dist/m/c-QICP2YR5.js +1 -0
  145. package/dist/m/c-QJ4OM3G6.js +1 -0
  146. package/dist/m/c-QNKREOL6.js +1 -0
  147. package/dist/m/c-R3L7ZJGB.js +2 -0
  148. package/dist/m/c-R7IWVINQ.js +1 -0
  149. package/dist/m/c-RM7J7BV2.js +1 -0
  150. package/dist/m/c-RSMOODDU.js +1 -0
  151. package/dist/m/c-RVMWGXEJ.js +1 -0
  152. package/dist/m/c-RYUXUUBX.js +1 -0
  153. package/dist/m/c-S4ZEH46B.js +9 -0
  154. package/dist/m/c-S7H2DRCB.js +1 -0
  155. package/dist/m/c-S7LK4KUE.js +1 -0
  156. package/dist/m/c-SDAXDM2D.js +1 -0
  157. package/dist/m/c-SGIA4IEQ.js +188 -0
  158. package/dist/m/c-SMBZG5X4.js +1 -0
  159. package/dist/m/c-SMGU7RT3.js +235 -0
  160. package/dist/m/c-SPIORRYS.js +1 -0
  161. package/dist/m/c-SWLRT66R.js +1 -0
  162. package/dist/m/c-SZGYW5HT.js +1 -0
  163. package/dist/m/c-TCCA5BZN.js +2 -0
  164. package/dist/m/c-TCJTVAFN.js +1 -0
  165. package/dist/m/c-TJ6WOJL3.js +67 -0
  166. package/dist/m/c-TQEMVJCS.js +1 -0
  167. package/dist/m/c-TRS6TM6O.js +1 -0
  168. package/dist/m/c-TYLXX4NF.js +21 -0
  169. package/dist/m/c-U62OB4BT.js +1 -0
  170. package/dist/m/c-U6YWUAR3.js +8 -0
  171. package/dist/m/c-UDCSU5G5.js +2 -0
  172. package/dist/m/c-UDUUJPFZ.js +1 -0
  173. package/dist/m/c-UGHH4MB3.js +1 -0
  174. package/dist/m/c-UL2YUY52.js +1 -0
  175. package/dist/m/c-VAK7SG37.js +1 -0
  176. package/dist/m/c-VDSS4K3Z.js +1 -0
  177. package/dist/m/c-VE6ZMYMC.js +1 -0
  178. package/dist/m/c-VG5XIN4T.js +1 -0
  179. package/dist/m/c-VHM5XQHK.js +1 -0
  180. package/dist/m/c-VHNSBD2M.js +1 -0
  181. package/dist/m/c-VI5RMSVY.js +1 -0
  182. package/dist/m/c-VKEDWYDF.js +1 -0
  183. package/dist/m/c-VRZN56XY.js +1 -0
  184. package/dist/m/c-W5MCHEGA.js +1 -0
  185. package/dist/m/c-WDC5GVKU.js +1 -0
  186. package/dist/m/c-WN5AY3OZ.js +1 -0
  187. package/dist/m/c-WNYBLIOZ.js +1 -0
  188. package/dist/m/c-WRBKEJD4.js +1 -0
  189. package/dist/m/c-WWXIDH4O.js +28 -0
  190. package/dist/m/c-WYFDLITL.js +1 -0
  191. package/dist/m/c-XCNTK76U.js +1 -0
  192. package/dist/m/c-XMS6DPBR.js +1 -0
  193. package/dist/m/c-XN6MBKI6.js +67 -0
  194. package/dist/m/c-XPELB6E5.js +1 -0
  195. package/dist/m/c-XQXZAO7N.js +1 -0
  196. package/dist/m/c-Y5HBBIQ4.js +1 -0
  197. package/dist/m/c-ZE46QDIH.js +1 -0
  198. package/dist/m/c-ZUCOKOHT.js +1 -0
  199. package/dist/m/c-ZVPVXTJF.js +4 -0
  200. package/dist/m/c-ZVRKXKM7.js +1 -0
  201. package/dist/m/c-ZWIOJGMT.js +1 -0
  202. package/dist/m/core/admin-attrs-QHOKPJ7W.js +1 -0
  203. package/dist/m/core/autosave-HRYGUWV6.js +1 -0
  204. package/dist/m/core/conflict-notice-6MROJN2L.js +1 -0
  205. package/dist/m/core/draft-mode-WSQAT67S.js +1 -0
  206. package/dist/m/core/host-permission-FNXVEFNE.js +1 -0
  207. package/dist/m/core/is-edit-mode-KTWA3O5N.js +1 -0
  208. package/dist/m/core/persist-JFYRHMRJ.js +3 -0
  209. package/dist/m/core/recovery-FLTCC2OC.js +1 -0
  210. package/dist/m/core/save-HQZ4S667.js +1 -0
  211. package/dist/m/core/save-core-KGQWFUNH.js +1 -0
  212. package/dist/m/core/snapshot-EGQZNT45.js +1 -0
  213. package/dist/m/core/unsaved-warning-IDJLD66H.js +1 -0
  214. package/dist/m/dom/all-APS5ZSKP.js +1 -0
  215. package/dist/m/dom/dom-helpers-L7OSR6S4.js +1 -0
  216. package/dist/m/events/index-RN2JNGBV.js +1 -0
  217. package/dist/m/internals/index-PVXGPUDA.js +1 -0
  218. package/dist/m/lib/cache-bust-MUMFXZK6.js +1 -0
  219. package/dist/m/lib/region-policy-SB2AWVG5.js +1 -0
  220. package/dist/m/loader-S4I7GKXR.js +1 -0
  221. package/dist/m/manifest-4C419D67.js +1 -0
  222. package/dist/m/manifest-edit-B077F288.js +1 -0
  223. package/dist/m/manifest.json +3988 -0
  224. package/dist/m/options/options-EJMS3K24.js +1 -0
  225. package/dist/m/page/editor-NNJA4OKD.js +202 -0
  226. package/dist/m/plugins/ai-edit-QN4PMMNL.js +6 -0
  227. package/dist/m/plugins/bind-text-NX46N5UV.js +1 -0
  228. package/dist/m/plugins/cms-HP3LSTPN.js +2293 -0
  229. package/dist/m/plugins/demo-RM5IR6AO.js +1 -0
  230. package/dist/m/plugins/derive-UAAA4V4S.js +1 -0
  231. package/dist/m/plugins/indicator-URDBBOQO.js +1 -0
  232. package/dist/m/plugins/live-HFWD4OIH.js +1 -0
  233. package/dist/m/plugins/menu-3HIEQSI6.js +138 -0
  234. package/dist/m/plugins/message-LQ6EDH4V.js +1 -0
  235. package/dist/m/plugins/page-JOKB6XPR.js +2 -0
  236. package/dist/m/plugins/page-view-NXOJHNW6.js +1 -0
  237. package/dist/m/plugins/people-SGPJKANK.js +1 -0
  238. package/dist/m/plugins/quickcrop-QNY3PKLD.js +26 -0
  239. package/dist/m/plugins/quickcrop-QNY3PKLD.js.LEGAL.txt +7 -0
  240. package/dist/m/plugins/richclay-NRQDSWX6.js +517 -0
  241. package/dist/m/plugins/richclay-NRQDSWX6.js.LEGAL.txt +1 -0
  242. package/dist/m/plugins/sortable-GWT4PWDV.js +1 -0
  243. package/dist/m/plugins/source-4ZRSAIYM.js +1 -0
  244. package/dist/m/plugins/undo-VDA5P7FH.js +1 -0
  245. package/dist/m/plugins/unsent-HJFLQJ2O.js +1 -0
  246. package/dist/m/plugins/upgrade-W7FG5VFG.js +1 -0
  247. package/dist/m/plugins/upload-WLYKK6TL.js +1 -0
  248. package/dist/m/plugins/visitor-drafts-LIDY7NH6.js +21 -0
  249. package/dist/m/plugins/visitor-drafts-owner-ZXCKBZE7.js +1 -0
  250. package/dist/m/plugins/wire-4N6GYAF6.js +1 -0
  251. package/dist/m/reader-core-DXYCURFF.js +68 -0
  252. package/dist/m/route/route-KOCZSEGN.js +1 -0
  253. package/dist/m/sync/live-sync-J7VMYTKS.js +1 -0
  254. package/dist/m/ui/index-3JLVI27O.js +1 -0
  255. package/dist/m/utils/index-4TJCVLT7.js +1 -0
  256. package/dist/m/vendor/Sortable.vendor-A755ITLN.js +3 -0
  257. package/dist/m/vendor/Sortable.vendor-A755ITLN.js.LEGAL.txt +1 -0
  258. package/dist/m/vendor/sap-core.vendor-A7BMUYZT.js +6 -0
  259. package/dist/offline-manifest.json +4034 -0
  260. package/dist/page/themes/light-1.css +430 -0
  261. package/dist/server/core.cjs +23748 -22855
  262. package/dist/server/offline-runtime.cjs +4954 -0
  263. package/dist/server/offline-runtime.mjs +4923 -0
  264. package/dist/server/rooms.mjs +9 -0
  265. package/docs/guide.md +1078 -669
  266. package/entries/all.js +2 -0
  267. package/entries/clay-data.js +1 -1
  268. package/entries/clay-dom.js +2 -0
  269. package/entries/clay-events.js +2 -0
  270. package/entries/clay-internals.js +2 -0
  271. package/entries/clay-options.js +2 -0
  272. package/entries/clay-router.js +2 -0
  273. package/entries/clay-ui.js +2 -0
  274. package/entries/clay-utils.js +2 -0
  275. package/entries/clay.js +2 -0
  276. package/entries/sap.js +6 -6
  277. package/fonts/ibm-plex-mono-LICENSE +93 -0
  278. package/fonts/ibm-plex-mono-latin-300-normal.woff2 +0 -0
  279. package/fonts/ibm-plex-mono-latin-400-italic.woff2 +0 -0
  280. package/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
  281. package/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
  282. package/fonts/ibm-plex-mono-latin-600-normal.woff2 +0 -0
  283. package/fonts/ibm-plex-sans-LICENSE +93 -0
  284. package/fonts/ibm-plex-sans-latin-400-normal.woff2 +0 -0
  285. package/fonts/ibm-plex-sans-latin-500-normal.woff2 +0 -0
  286. package/fonts/ibm-plex-sans-latin-600-normal.woff2 +0 -0
  287. package/fonts/ibm-plex.css +71 -0
  288. package/fonts/inter-LICENSE +93 -0
  289. package/fonts/inter-cyrillic-ext-wght-normal.woff2 +0 -0
  290. package/fonts/inter-cyrillic-wght-normal.woff2 +0 -0
  291. package/fonts/inter-greek-ext-wght-normal.woff2 +0 -0
  292. package/fonts/inter-greek-wght-normal.woff2 +0 -0
  293. package/fonts/inter-latin-ext-wght-normal.woff2 +0 -0
  294. package/fonts/inter-latin-wght-normal.woff2 +0 -0
  295. package/fonts/inter-vietnamese-wght-normal.woff2 +0 -0
  296. package/fonts/inter.css +62 -0
  297. package/fonts/newsreader-LICENSE +93 -0
  298. package/fonts/newsreader-latin-ext-opsz-normal.woff2 +0 -0
  299. package/fonts/newsreader-latin-opsz-normal.woff2 +0 -0
  300. package/fonts/newsreader-vietnamese-opsz-normal.woff2 +0 -0
  301. package/fonts/newsreader.css +26 -0
  302. package/fonts/work-sans-LICENSE +93 -0
  303. package/fonts/work-sans-latin-wght-normal.woff2 +0 -0
  304. package/fonts/work-sans.css +8 -0
  305. package/package.json +20 -3
  306. package/packed-contract.json +1 -0
  307. package/src/attrs/add-from.js +27 -4
  308. package/src/attrs/save-freeze.js +7 -52
  309. package/src/core/admin-attrs.js +119 -12
  310. package/src/core/admin-contenteditable.js +12 -10
  311. package/src/core/admin-inputs.js +14 -15
  312. package/src/core/admin-onclick.js +2 -0
  313. package/src/core/autosave.js +74 -21
  314. package/src/core/boot-phase.js +2 -1
  315. package/src/core/conflict-download.js +19 -23
  316. package/src/core/conflict-notice-ui.js +1173 -0
  317. package/src/core/conflict-notice.js +140 -697
  318. package/src/core/download.js +32 -0
  319. package/src/core/edit-mode.js +147 -14
  320. package/src/core/etag.js +30 -12
  321. package/src/core/host-meta.js +90 -8
  322. package/src/core/host-permission.js +29 -0
  323. package/src/core/is-edit-mode.js +145 -12
  324. package/src/core/mode-key.js +125 -0
  325. package/src/core/mode-lifecycle.js +544 -0
  326. package/src/core/mode-receipt.js +30 -0
  327. package/src/core/mode-refusal.js +13 -0
  328. package/src/core/mode-resources.js +52 -0
  329. package/src/core/mode-support.js +82 -0
  330. package/src/core/mode-switch.js +259 -0
  331. package/src/core/mode-transition.js +112 -0
  332. package/src/core/page-data-write.js +305 -0
  333. package/src/core/page-data.js +26 -227
  334. package/src/core/page-format-wire.js +4 -0
  335. package/src/core/page-format.js +58 -0
  336. package/src/core/persist.js +37 -9
  337. package/src/core/preserve-authored.js +114 -0
  338. package/src/core/recovery.js +772 -0
  339. package/src/core/runtime-url.js +105 -0
  340. package/src/core/save-core.js +113 -12
  341. package/src/core/save-replace.js +72 -0
  342. package/src/core/save-size-notice.js +134 -0
  343. package/src/core/save-size.js +140 -0
  344. package/src/core/save.js +806 -119
  345. package/src/core/snapshot.js +159 -11
  346. package/src/core/source-map.js +157 -9
  347. package/src/core/unsaved-warning.js +14 -4
  348. package/src/core/upgrade-run.js +408 -0
  349. package/src/core/version.js +18 -0
  350. package/src/lib/assets.js +30 -0
  351. package/src/lib/autosave-debug-gate.js +50 -0
  352. package/src/lib/autosave-scheduler.js +140 -0
  353. package/src/lib/boot-imports.js +8 -0
  354. package/src/lib/derived.js +3 -1
  355. package/src/lib/dirty-gate.js +15 -0
  356. package/src/lib/doc-storage.js +33 -0
  357. package/src/lib/effect.js +9 -4
  358. package/src/lib/mutation.js +4 -2
  359. package/src/lib/offline-runtime.js +226 -0
  360. package/src/lib/preload.js +48 -0
  361. package/src/lib/root-attrs.js +1 -1
  362. package/src/lib/trash.js +18 -0
  363. package/src/lib/undo/capture-move.js +152 -0
  364. package/src/live/local-channel.js +61 -0
  365. package/src/live/local-lane.js +60 -9
  366. package/src/live/server-lane.js +6 -3
  367. package/src/loader-logic.js +76 -15
  368. package/src/loader.js +218 -30
  369. package/src/page/adopt-boot.js +76 -22
  370. package/src/page/adopt.js +25 -4
  371. package/src/page/awareness.js +230 -0
  372. package/src/page/blocks.js +113 -6
  373. package/src/page/blocksel.js +20 -4
  374. package/src/page/board-face.js +13 -0
  375. package/src/page/board-panel.js +5 -17
  376. package/src/page/board-style.js +2 -13
  377. package/src/page/board-view.js +1 -11
  378. package/src/page/board-views.js +3 -0
  379. package/src/page/board.js +2 -2
  380. package/src/page/boot-pack.js +26 -0
  381. package/src/page/boot.js +237 -0
  382. package/src/page/chart-tables.js +11 -0
  383. package/src/page/chart.js +6 -10
  384. package/src/page/checklist.js +1 -1
  385. package/src/page/checkpoint-proof.js +289 -0
  386. package/src/page/chrome.js +431 -63
  387. package/src/page/csv-import.js +17 -2
  388. package/src/page/document-title-dom.js +52 -0
  389. package/src/page/document-title-runtime.js +90 -0
  390. package/src/page/document-title.js +12 -0
  391. package/src/page/drag.js +48 -13
  392. package/src/page/drop.js +2 -1
  393. package/src/page/editor.js +399 -90
  394. package/src/page/emoji-data.js +162 -0
  395. package/src/page/feature-loader.js +72 -0
  396. package/src/page/features/board.js +21 -0
  397. package/src/page/features/chart.js +11 -0
  398. package/src/page/features/code.js +13 -0
  399. package/src/page/features/dates.js +180 -0
  400. package/src/page/features/emoji.js +187 -0
  401. package/src/page/features/media.js +290 -0
  402. package/src/page/features/mentions.js +188 -0
  403. package/src/page/features/registry.js +28 -0
  404. package/src/page/features/table.js +383 -0
  405. package/src/page/{tool-view.js → features/tool.js} +33 -2
  406. package/src/page/format-codec.js +215 -0
  407. package/src/page/format-command.js +9 -0
  408. package/src/page/format-gate.js +179 -0
  409. package/src/page/format-host.js +54 -0
  410. package/src/page/format-level.js +157 -0
  411. package/src/page/format-native-commands.js +142 -0
  412. package/src/page/format-native.js +42 -0
  413. package/src/page/highlight-slot.js +23 -0
  414. package/src/page/highlight.js +30 -16
  415. package/src/page/history-intent.js +78 -0
  416. package/src/page/inline.js +45 -28
  417. package/src/page/items.js +67 -14
  418. package/src/page/link.js +1 -0
  419. package/src/page/markdown.js +112 -3
  420. package/src/page/media-views.js +3 -0
  421. package/src/page/media.js +33 -245
  422. package/src/page/mention-picker.js +99 -0
  423. package/src/page/merge.js +1 -1
  424. package/src/page/outline-cache.js +11 -0
  425. package/src/page/placeholder.js +27 -7
  426. package/src/page/projection.js +240 -51
  427. package/src/page/reader-patch.js +306 -0
  428. package/src/page/recover.js +1 -1
  429. package/src/page/rich/page-tool.js +1 -4
  430. package/src/page/runtime-owner.js +27 -0
  431. package/src/page/save-projection.js +46 -4
  432. package/src/page/save-states.js +1 -1
  433. package/src/page/schema.js +47 -0
  434. package/src/page/table-rect-key.js +3 -0
  435. package/src/page/table-select.js +4 -3
  436. package/src/page/table-style.js +1 -1
  437. package/src/page/table-views.js +3 -0
  438. package/src/page/table.js +8 -293
  439. package/src/page/themes/content.css +25 -1
  440. package/src/page/themes/light-1.css +163 -2
  441. package/src/page/themes/light-1.source.css +269 -0
  442. package/src/page/tool-capability.js +3 -0
  443. package/src/page/tool-views.js +3 -0
  444. package/src/page/view-hooks.js +32 -0
  445. package/src/page/view.js +390 -107
  446. package/src/page/ybind/awareness.js +172 -0
  447. package/src/page/ybind/binding.js +40 -5
  448. package/src/page/ybind/bridge.js +50 -47
  449. package/src/page/ybind/decode.js +52 -19
  450. package/src/page/ybind/doc.js +27 -2
  451. package/src/page/ybind/group.js +12 -3
  452. package/src/page/ybind/index.js +2 -0
  453. package/src/page/ybind/lineage.js +71 -0
  454. package/src/page/ybind/marks.js +26 -1
  455. package/src/page/ybind/migrate.js +48 -13
  456. package/src/page/ybind/moves.js +32 -21
  457. package/src/page/ybind/positions.js +50 -0
  458. package/src/page/ybind/provider.js +97 -24
  459. package/src/page/ybind/recovery-proof.js +191 -0
  460. package/src/page/ybind/recovery-replay.js +488 -0
  461. package/src/page/ybind/tokens.js +15 -6
  462. package/src/page/ybind/walk.js +10 -3
  463. package/src/page/ybind/writer.js +48 -4
  464. package/src/plugins/ai-edit.js +167 -32
  465. package/src/plugins/bind-text.js +218 -0
  466. package/src/plugins/cms.js +93 -0
  467. package/src/plugins/indicator.js +57 -5
  468. package/src/plugins/live.js +319 -35
  469. package/src/plugins/menu/claygoo.svg.js +1 -0
  470. package/src/plugins/menu/seo-head.js +323 -0
  471. package/src/plugins/menu/seo.js +849 -0
  472. package/src/plugins/menu.js +425 -0
  473. package/src/plugins/page-view.js +131 -0
  474. package/src/plugins/page.js +1131 -270
  475. package/src/plugins/people.js +158 -31
  476. package/src/plugins/richclay.js +119 -9
  477. package/src/plugins/sortable.js +370 -60
  478. package/src/plugins/source.js +189 -366
  479. package/src/plugins/undo.js +289 -19
  480. package/src/plugins/unsent.js +132 -0
  481. package/src/plugins/upgrade-run.js +1 -0
  482. package/src/plugins/upgrade.js +210 -0
  483. package/src/plugins/upload-task.js +571 -0
  484. package/src/plugins/upload-ui.js +265 -0
  485. package/src/plugins/upload.js +34 -153
  486. package/src/plugins/visitor-drafts/inert.js +32 -0
  487. package/src/plugins/visitor-drafts/intro.js +2 -1
  488. package/src/plugins/visitor-drafts/owner-chip.js +3 -0
  489. package/src/plugins/visitor-drafts/prompt.js +2 -2
  490. package/src/plugins/visitor-drafts-owner.js +85 -0
  491. package/src/plugins/visitor-drafts.js +31 -42
  492. package/src/plugins/wire.js +82 -1
  493. package/src/reader-core.js +26 -0
  494. package/src/recovery/conflicts.js +287 -0
  495. package/src/recovery/identity.js +154 -0
  496. package/src/recovery/store.js +792 -0
  497. package/src/recovery/undo.js +257 -0
  498. package/src/server/checkpoint/index.js +68 -4
  499. package/src/server/offline-runtime/index.js +214 -0
  500. package/src/server/rooms/index.js +2 -1
  501. package/src/server/rooms/live-data-rooms.js +2 -2
  502. package/src/server/rooms/page-admission.js +33 -0
  503. package/src/server/rooms/page-lineage.js +56 -0
  504. package/src/server/rooms/page-rebase.js +11 -5
  505. package/src/server/rooms/page-state.js +2 -0
  506. package/src/server/rooms/page-write-admission.js +9 -3
  507. package/src/source/session.js +705 -0
  508. package/src/sync/conflict-revert.js +5 -4
  509. package/src/sync/conflicts-edit.js +119 -0
  510. package/src/sync/conflicts-facade.js +49 -0
  511. package/src/sync/conflicts.js +99 -1
  512. package/src/sync/editing-marker.js +37 -6
  513. package/src/sync/editing-sender.js +8 -1
  514. package/src/sync/live-sync-edit.js +63 -0
  515. package/src/sync/live-sync.js +364 -119
  516. package/src/sync/local-presence.js +187 -0
  517. package/src/sync/merge-declarations.js +10 -0
  518. package/src/sync/merge-engine.js +12 -0
  519. package/src/sync/presence-store.js +412 -0
  520. package/src/sync/presence-target.js +243 -0
  521. package/src/sync/presence.js +72 -41
  522. package/src/sync/saved-handoff.js +200 -0
  523. package/src/sync/saved-stream.js +544 -0
  524. package/src/sync/version-guard.js +92 -0
  525. package/src/sync/wire-profile.js +81 -0
  526. package/src/ui/bevel-controls.js +2 -8
  527. package/src/ui/page-scheme.js +7 -0
  528. package/src/ui/skins/cms.js +1 -1
  529. package/src/ui/theme-config.js +150 -0
  530. package/src/ui/theme.js +6 -138
  531. package/src/ui/toast-lazy.js +11 -0
  532. package/src/vendor/LICENSES-optional.md +253 -0
  533. package/src/vendor/highlight.vendor.js +3 -0
  534. package/src/vendor/hljs-bash.vendor.js +2 -0
  535. package/src/vendor/hljs-css.vendor.js +2 -0
  536. package/src/vendor/hljs-javascript.vendor.js +2 -0
  537. package/src/vendor/hljs-json.vendor.js +2 -0
  538. package/src/vendor/hljs-python.vendor.js +2 -0
  539. package/src/vendor/hljs-xml.vendor.js +2 -0
  540. package/src/vendor/hyper-html-api-data.vendor.js +1 -1
  541. package/src/vendor/hyper-html-api-upgrade.vendor.js +5 -0
  542. package/src/vendor/hyper-morph-identity.vendor.js +2 -0
  543. package/src/vendor/hyper-morph-json.vendor.js +3 -0
  544. package/src/vendor/hyper-morph.vendor.js +6 -6
  545. package/src/vendor/hyper-undo.vendor.js +1 -1
  546. package/src/vendor/hypercms.vendor.js +169 -84
  547. package/src/vendor/marked.vendor.js +68 -0
  548. package/src/vendor/overtype.vendor.js +994 -0
  549. package/src/vendor/richclay.vendor.js +17 -17
  550. package/src/vendor/sap-core.vendor.js +5 -5
  551. package/src/vendor/sap-ownership.vendor.js +11 -7
@@ -0,0 +1,705 @@
1
+ /**
2
+ * session.js — the source map at work: fetch, model, pair, render, adopt.
3
+ *
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
+ * session 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. plugins/source.js is the thin facade over it and imports it on demand, so
9
+ * nothing here — and with it core/source-map.js and parse5 — is evaluated until a save
10
+ * needs it or code reads `clay.source.ready`.
11
+ *
12
+ * FOUR MOMENTS
13
+ *
14
+ * boot fetch the source, model it, refuse anything that is not this
15
+ * document, and pair it against a save clone.
16
+ * save render; verify; send the render if it verifies and today's full
17
+ * serialization if it does not, counting the second case.
18
+ * accepted re-model against the bytes the host took, so the next save is
19
+ * measured from what is on disk rather than from what was there at
20
+ * boot.
21
+ * sync-applied re-pair, because a morph replaces live nodes and the map is keyed
22
+ * by node identity.
23
+ *
24
+ * The accepted-save re-model is deferred and coalesced, so it never sits between a save
25
+ * landing and the page hearing about it. A sync frame's re-pair shares that idle refresh,
26
+ * so a burst of frames costs one walk, and a save, `text()` or `locate()` that comes
27
+ * before it runs it first, so none of them work from a map the morph made stale.
28
+ *
29
+ * NOTHING HERE BLOCKS BOOT. The install is async and the loader does not wait for
30
+ * it. Only the save that asked for it waits, and a save after a failed install is the
31
+ * ordinary full serialization rather than an error.
32
+ *
33
+ * ON DEMAND
34
+ *
35
+ * Nothing here runs at import: `start` is the one entry, and the facade calls it when a
36
+ * save needs the parser or a reader asks for `clay.source.ready`. `release` takes an
37
+ * install back — the renderer, a scheduled refresh and the installed flag — and bumps
38
+ * `state.session`, so an answer that arrives after the session ended installs nothing.
39
+ *
40
+ * WHEN IT DOES NOTHING
41
+ *
42
+ * Any of these leaves the save pipeline untouched, and each says so once in the
43
+ * console rather than repeatedly at save time:
44
+ *
45
+ * the source fetch fails, redirects, or is not text/html
46
+ * the fetched bytes disagree with the live document outside <html>
47
+ * the render throws
48
+ * the render does not verify
49
+ */
50
+
51
+ import {
52
+ model,
53
+ checkSource,
54
+ locate,
55
+ nodeAt,
56
+ pair,
57
+ pairCaptured,
58
+ render,
59
+ verify,
60
+ verifyForSave
61
+ } from '../core/source-map.js';
62
+ import {
63
+ attachCaptureArtifact,
64
+ captureSaveClone,
65
+ currentCaptureContext,
66
+ originalSnapshotNode,
67
+ setSaveRenderer
68
+ } from '../core/snapshot.js';
69
+ import { onSaveAccepted } from '../core/save-core.js';
70
+ import { lastSeenEtag } from '../core/etag.js';
71
+
72
+ const state = {
73
+ installed: false,
74
+ refused: null,
75
+ // How many sessions have been uninstalled. An install applies nothing when the session
76
+ // that asked for it has ended in the meantime, so a late answer cannot put a renderer
77
+ // or a map back into view.
78
+ session: 0,
79
+ model: null,
80
+ map: null,
81
+ stats: null,
82
+ saves: 0,
83
+ reprints: 0,
84
+ lastReprint: null,
85
+ partialReprints: 0,
86
+ lastPartialReprint: null,
87
+ // What the last render produced, so the save the host accepts can be counted and
88
+ // announced as what it was. Cleared once reported.
89
+ lastOutcome: null,
90
+ lastRenderMs: null,
91
+ lastBytes: null,
92
+ timing: { fetch: null, model: null, pair: null, refresh: null },
93
+ refreshes: 0,
94
+ // How many times the map has been rebuilt from the page since this module
95
+ // installed. A candidate's map is only valid while this is unchanged: a morph
96
+ // replaces live nodes, and a disk frame replaces the model.
97
+ repairs: 0,
98
+ refreshQueued: false,
99
+ pendingBytes: null,
100
+ refetch: false,
101
+ // How many times newer file bytes have replaced the model: an accepted save, a disk
102
+ // frame, or a newer fetch. A fetch answers about the file at the moment the host read
103
+ // it, so this is what tells a late answer that something newer already landed.
104
+ adoptions: 0,
105
+ };
106
+
107
+ // How many times a boot fetch asks again after a save or a disk frame overtook it before
108
+ // it gives up and leaves saves on the full serialization.
109
+ const SUPERSEDED_RETRIES = 2;
110
+
111
+ const now = () => (typeof performance !== 'undefined' ? performance.now() : Date.now());
112
+
113
+ const PROJECTED_MAP_PREFIX = '<script type="application/json" id="clay-runtime-map"';
114
+
115
+ /**
116
+ * Bytes a host projected for its own delivery are not the file: the runtime URLs in them
117
+ * point at the local mirror and may carry a descriptor the file never held. Modelling them
118
+ * would make this tab's next save copy the projection back into the file.
119
+ */
120
+ const isProjected = (text) => text.includes(PROJECTED_MAP_PREFIX) || text.includes('/_/clayjs/');
121
+
122
+ /**
123
+ * The bytes this document was loaded from.
124
+ *
125
+ * Every guard here is about one failure: writing somebody else's page into this
126
+ * file. `redirect: 'manual'` and the `redirected` check catch a host that answers a
127
+ * logged-out request with a login page at another URL; the content-type check
128
+ * catches one that answers with JSON or an error page; `cache: 'no-store'` keeps a
129
+ * stale cached copy of an older version of this document out of the model. The
130
+ * shape check in checkSource is the backstop for whatever gets past all three.
131
+ */
132
+ async function fetchSource(url) {
133
+ const res = await fetch(url, {
134
+ cache: 'no-store',
135
+ redirect: 'manual',
136
+ credentials: 'same-origin',
137
+ headers: { 'Clay-Source': '1' }
138
+ });
139
+ if (!res.ok) throw new Error(`source fetch: status ${res.status}`);
140
+ if (res.redirected || res.type === 'opaqueredirect') throw new Error('source fetch: redirected');
141
+ const type = (res.headers.get('content-type') || '').toLowerCase();
142
+ if (!type.startsWith('text/html')) throw new Error(`source fetch: content-type ${type || 'missing'}`);
143
+ const text = await res.text();
144
+ if (text.includes(PROJECTED_MAP_PREFIX)) throw new Error('source fetch: projected response');
145
+ return text;
146
+ }
147
+
148
+ /**
149
+ * Pair the model against the page.
150
+ *
151
+ * Over a SAVE CLONE, not the live DOM. The clone is in the file's own domain: edit
152
+ * mode deactivated back to the inert attribute forms, [no-save] regions gone, every
153
+ * document transform run. Pairing the live DOM instead would compare
154
+ * `contenteditable="true"` against the file's `inert-contenteditable="true"` and lose
155
+ * the two strongest signature tiers on every activated element and every ancestor of
156
+ * one. The map is keyed through snapshot provenance to the LIVE node, because the
157
+ * clone is rebuilt on every save and its nodes are new objects each time.
158
+ *
159
+ * This capture is an INSPECTION, not a save, and it happens at boot and again on every
160
+ * incoming live-sync frame. So it takes neither of the two things a save capture does
161
+ * on its way past: it does not close the undo batch, which at boot and mid-typing
162
+ * would split the user's undo history at a point they did not make, and it does not
163
+ * run the page's own [onbeforesave] and [onbeforesnapshot] handlers, which are author
164
+ * JavaScript that can do anything and are meant to run once per save. The cost is that
165
+ * a page using those handlers pairs against a tree slightly behind the one it will
166
+ * save, which costs some formatting fidelity on the nodes a handler touches and
167
+ * nothing else, because the render is verified either way.
168
+ */
169
+ function pairAgainstPage(m) {
170
+ const clone = captureSaveClone({ flushUndo: false, authored: false });
171
+ return pair(clone, m, originalSnapshotNode);
172
+ }
173
+
174
+ /**
175
+ * The version a set of bytes names for itself, or null when nothing stamped it.
176
+ *
177
+ * A host writes `documentetag` on the root of the bytes it stored, so a fetch answers
178
+ * with the version it read, which is not necessarily the one this page was built from.
179
+ */
180
+ function stampedVersion(m) {
181
+ const html = m.doc && m.doc.childNodes.find((node) => node.nodeName === 'html');
182
+ const attr = html && Array.isArray(html.attrs)
183
+ ? html.attrs.find((attribute) => attribute.name === 'documentetag')
184
+ : null;
185
+ return attr && attr.value ? attr.value : null;
186
+ }
187
+
188
+ /**
189
+ * The version this session represents: the file the reader handed it, else the version this
190
+ * tab last saw, else — for an explicit edit boot, which has no reader — the version the
191
+ * document was served with.
192
+ */
193
+ function heldVersion(context) {
194
+ const represented = context && context.represented && context.represented.etag;
195
+ if (represented) return represented;
196
+ const seen = lastSeenEtag();
197
+ if (seen) return seen;
198
+ return context === null ? document.documentElement.getAttribute('documentetag') || null : null;
199
+ }
200
+
201
+ /**
202
+ * A fetch is an answer about the file as it was when the host read it, and a boot fetch
203
+ * can come back after the file has moved on. Bytes that name a version this page does
204
+ * not hold are that newer file: adopting them would pair this page against content it
205
+ * never received, and the next save would print its older bytes back over them. The
206
+ * stamp and the model go together, so neither is taken from a late answer.
207
+ */
208
+ function staleVersion(m, context) {
209
+ const stamp = stampedVersion(m);
210
+ const held = heldVersion(context);
211
+ if (!stamp || !held || stamp === held) return null;
212
+ return `source refused: the fetch answered version ${stamp} while this page holds ${held}`;
213
+ }
214
+
215
+ function install(sourceUrl, context = null, attempt = 0, render = true, latest = null) {
216
+ const session = state.session;
217
+ const t0 = now();
218
+ const generation = state.adoptions;
219
+ state.refused = null;
220
+ // Bytes this page already holds for the file — the reader's receipt, an accepted save,
221
+ // a disk frame — are the file, so the install starts from them instead of asking the
222
+ // host for a copy it has just written. Every check below runs on whichever answer it
223
+ // gets, so this is a request saved and not a guard skipped.
224
+ const receipt = latest ? latest() : null;
225
+ const receiptBytes = receipt && typeof receipt.bytes === 'string' && !isProjected(receipt.bytes)
226
+ ? receipt.bytes
227
+ : null;
228
+ return (receiptBytes !== null ? Promise.resolve(receiptBytes) : fetchSource(sourceUrl)).then((src) => {
229
+ if (session !== state.session) return null;
230
+ const t1 = now();
231
+ const m = model(src);
232
+ const refused = checkSource(m, document);
233
+ if (refused) throw new Error(`source refused: ${refused}`);
234
+ const stale = staleVersion(m, context);
235
+ if (stale) throw new Error(stale);
236
+ // A save was accepted, or a disk frame carried newer bytes, while this answer was on
237
+ // its way. Those bytes are the file now, so ask again rather than adopt an older
238
+ // answer; a page that will not hold still is refused instead of retried forever.
239
+ if (state.adoptions !== generation) {
240
+ if (attempt < SUPERSEDED_RETRIES) return install(sourceUrl, context, attempt + 1, render, latest);
241
+ throw new Error('source refused: a save replaced the file while the fetch was in flight');
242
+ }
243
+ const t2 = now();
244
+ const { map, stats } = pairAgainstPage(m);
245
+ const t3 = now();
246
+
247
+ state.model = m;
248
+ state.map = map;
249
+ state.stats = stats;
250
+ state.timing = { fetch: t1 - t0, model: t2 - t1, pair: t3 - t2, refresh: null };
251
+ state.installed = true;
252
+ if (render) setSaveRenderer(renderSave);
253
+ return summary();
254
+ }).catch((err) => {
255
+ // Not an error the page can do anything about, and not a failure of the save:
256
+ // saves go out exactly as they would without this plugin. Said once.
257
+ if (session !== state.session) return null;
258
+ state.refused = String(err && err.message ? err.message : err);
259
+ console.info(`clayjs: source map off (${state.refused}); saves use the full serialization`);
260
+ return null;
261
+ });
262
+ }
263
+
264
+ /**
265
+ * Render one save, or hand back today's bytes.
266
+ *
267
+ * Exported so a test can drive the whole path, including the fallback, without a
268
+ * server: `renderSave(clone, today, { breakText: true })` makes every text node
269
+ * reprint, which changes the bytes while leaving the tree identical, and the verifier
270
+ * must still pass it — a tree comparison is the right answer there, and reading that
271
+ * pass as "the verifier is blind" is a mistake this project has already made once.
272
+ * `{ corrupt: 'drop-first-text' }` is the one that must be REJECTED: it changes the
273
+ * tree, which is exactly what the verifier is for.
274
+ *
275
+ * A render that does not verify is not thrown away whole. verify says where it first
276
+ * differs; that element is printed in full and the render tried again, widening to the
277
+ * parent while it still fails. Everything outside the printed elements is still the
278
+ * author's bytes. Only when the widening reaches <html>, or the failure has no node to
279
+ * point at (the prologue, a parse-error count), is today's full serialization sent.
280
+ *
281
+ * Nothing is counted or announced here. A render is not a save: an autosave that
282
+ * found nothing changed, a refused save and a failed request all rendered. The
283
+ * outcome is recorded, and `adopt` reports it when the host accepts these exact bytes.
284
+ *
285
+ * A render that belongs to a save capture also hands that capture a private artifact:
286
+ * the model of the bytes it just verified, paired with the clone it verified them
287
+ * against, so the save the host accepts does not parse and pair those same bytes a
288
+ * second time. It is a candidate, not an answer: `adopt` installs it only if the host
289
+ * took these exact bytes and nothing has replaced the page's pairing since. A page
290
+ * with its own [onbeforesave] or [onbeforesnapshot] handlers gets none, because those
291
+ * handlers can edit the clone after the capture and the inspection path is what
292
+ * describes the live page there.
293
+ */
294
+ export function renderSave(clone, today, opts = {}) {
295
+ if (!state.installed) return today;
296
+ // A refresh still waiting for idle time runs now, so this save never renders from a
297
+ // map a morph made stale. It pairs against the page, not this clone: onbeforesave may
298
+ // have changed the clone, and the map must describe the live page.
299
+ refreshNow();
300
+ state.lastOutcome = null;
301
+ const capture = currentCaptureContext();
302
+ const adoptable = !!capture && !capture.authoredHandlers;
303
+ const print = new Set();
304
+ let firstDiff = null;
305
+ for (let round = 0; round <= MAX_PRINT_ROUNDS; round++) {
306
+ let out;
307
+ try {
308
+ out = render(clone, state.map, state.model, originalSnapshotNode, { ...opts, print });
309
+ } catch (err) {
310
+ return fallback('render threw: ' + (err && err.message ? err.message : err), today);
311
+ }
312
+ // The verifier's retained parse is a cost, so it is only asked for when there is a
313
+ // capture that could adopt it.
314
+ const v = adoptable
315
+ ? verifyForSave(out.text, today, document, clone, state.model.parseErrors)
316
+ : verify(out.text, today, document, clone, state.model.parseErrors);
317
+ if (v.ok) {
318
+ state.lastRenderMs = out.ms;
319
+ state.lastBytes = out.text.length;
320
+ state.lastOutcome = print.size
321
+ ? { text: out.text, scope: 'partial', reason: firstDiff, printed: print.size }
322
+ : { text: out.text, scope: null };
323
+ if (v.model) offerCandidate(v.model, out.text, clone, capture);
324
+ return out.text;
325
+ }
326
+ if (firstDiff === null) firstDiff = v.diff;
327
+ if (!v.at || !widen(clone, nodeAt(clone, v.at), print)) return fallback(firstDiff, today);
328
+ }
329
+ return fallback(firstDiff, today);
330
+ }
331
+
332
+ /**
333
+ * Hand this capture the model of the bytes it just verified, or nothing.
334
+ *
335
+ * The pairing is the whole point of the artifact: it must describe the live page, so
336
+ * it is built by walking the verified clone and that model together. Anything that
337
+ * does not correspond, node for node, leaves the capture without an artifact, and the
338
+ * accepted save takes the inspection path it takes today.
339
+ */
340
+ function offerCandidate(m, html, clone, capture) {
341
+ const paired = pairCaptured(clone, m, originalSnapshotNode);
342
+ if (!paired) return;
343
+ attachCaptureArtifact({
344
+ captureId: capture.captureId,
345
+ html,
346
+ source: {
347
+ model: m,
348
+ map: paired.map,
349
+ stats: paired.stats,
350
+ repairGeneration: state.repairs,
351
+ outcome: state.lastOutcome,
352
+ },
353
+ });
354
+ }
355
+
356
+ const MAX_PRINT_ROUNDS = 8;
357
+
358
+ /**
359
+ * Add the next element to print: the one holding `node`, or, when that is already
360
+ * inside a printed element, that element's parent. false when the only element left is
361
+ * the root, which is the full serialization by another name.
362
+ */
363
+ function widen(clone, node, print) {
364
+ let el = node && node.nodeType === 1 ? node : node && node.parentNode;
365
+ for (let a = el; a && a !== clone; a = a.parentNode) {
366
+ if (print.has(a)) { print.delete(a); el = a.parentNode; break; }
367
+ }
368
+ if (!el || el.nodeType !== 1 || el === clone) return false;
369
+ print.add(el);
370
+ return true;
371
+ }
372
+
373
+ /** Send today's full serialization, and remember that this render was a fallback. */
374
+ function fallback(reason, today) {
375
+ state.lastOutcome = { text: today, scope: 'full', reason };
376
+ return today;
377
+ }
378
+
379
+ /**
380
+ * The fallback, and the alarm.
381
+ *
382
+ * Reported for a save the host accepted. The bytes that went out were today's, so the
383
+ * floor of this whole mechanism is the behaviour it replaces. The event and the
384
+ * counter are how a reprint gets fixed
385
+ * in the renderer or in the page, which is the only place it should ever be fixed: a
386
+ * verifier loosened to make this counter look better would pass exactly the writes it
387
+ * exists to stop.
388
+ */
389
+ function reprint(reason) {
390
+ state.reprints++;
391
+ state.lastReprint = reason;
392
+ console.warn('clayjs: source map did not verify, saving the full serialization instead:', reason);
393
+ document.dispatchEvent(new CustomEvent('clay:save-reprinted', { detail: { reason, scope: 'full' } }));
394
+ }
395
+
396
+ /**
397
+ * Part of the document was printed rather than copied. Counted apart from a full
398
+ * reprint because they are different news: this one kept the author's bytes everywhere
399
+ * else, and its rate says how often the renderer and the page disagree about one
400
+ * element, which is where the next renderer fix is.
401
+ */
402
+ function partialReprint(reason, printed) {
403
+ state.partialReprints++;
404
+ state.lastPartialReprint = reason;
405
+ console.info(`clayjs: source map printed ${printed} element(s) in full to match the page:`, reason);
406
+ document.dispatchEvent(new CustomEvent('clay:save-reprinted', { detail: { reason, scope: 'partial', printed } }));
407
+ }
408
+
409
+ /**
410
+ * The host took these bytes, so this is what the file holds now.
411
+ *
412
+ * Deferred, like the re-pair below and for the same reason: re-modelling walks the
413
+ * whole document, and doing it inline would put that walk between the save landing
414
+ * and the page being told about it. Only the most recent accepted bytes matter, so a
415
+ * burst of saves costs one refresh.
416
+ *
417
+ * It is also where a save is counted, because it is the one place that knows a save
418
+ * reached the file.
419
+ *
420
+ * A capture that rendered its own verified bytes brought a candidate with them, and
421
+ * when the host took exactly those bytes that candidate IS the file: installing it is
422
+ * a few assignments instead of a parse of the accepted bytes and a whole-document
423
+ * re-pair. Everything else — a raw save, a fallback, a candidate whose pairing a morph
424
+ * has since invalidated, a disk version that arrived while the save was in flight —
425
+ * takes the guarded repair path, which is the behaviour this replaces.
426
+ */
427
+ function adopt(bytes, accepted) {
428
+ state.adoptions++;
429
+ if (!state.installed) return;
430
+ report(bytes, accepted && accepted.artifact ? accepted.artifact.source.outcome : null);
431
+ if (adoptCandidate(bytes, accepted)) return;
432
+ state.pendingBytes = bytes;
433
+ scheduleRefresh();
434
+ }
435
+
436
+ /**
437
+ * Install a capture's candidate model, if it is this save's and still describes the page.
438
+ *
439
+ * Three things have to agree, and each is a different mistake. The bytes: the artifact
440
+ * is the model of the string that exact render returned, not of a later one. The
441
+ * capture: `save.js` keeps the artifact with its request, so the ID rules out a
442
+ * candidate that reached an accepted listener by any other route. The generation: a
443
+ * morph replaces live nodes and a disk frame replaces the model, so a map built before
444
+ * one is keyed to a page that is no longer here.
445
+ */
446
+ function adoptCandidate(bytes, accepted) {
447
+ const artifact = accepted && accepted.artifact;
448
+ if (!artifact || !artifact.source) return false;
449
+ if (artifact.html !== bytes) return false;
450
+ if (accepted.captureId === null || accepted.captureId === undefined) return false;
451
+ if (artifact.captureId !== accepted.captureId) return false;
452
+ if (artifact.source.repairGeneration !== state.repairs) return false;
453
+ if (state.refreshQueued || state.pendingBytes !== null) return false;
454
+ state.model = artifact.source.model;
455
+ state.map = artifact.source.map;
456
+ state.stats = artifact.source.stats;
457
+ return true;
458
+ }
459
+
460
+ /**
461
+ * Count and announce an accepted save, if these are the bytes the last render
462
+ * produced. Bytes this module did not render (a caller of `saveHtml` with its own
463
+ * string, or an older render) are not its save to report.
464
+ */
465
+ function report(bytes, fromCapture = null) {
466
+ const outcome = state.lastOutcome && state.lastOutcome.text === bytes ? state.lastOutcome : fromCapture;
467
+ if (!outcome || outcome.text !== bytes) return;
468
+ if (state.lastOutcome === outcome) state.lastOutcome = null;
469
+ state.saves++;
470
+ if (outcome.scope === 'full') reprint(outcome.reason);
471
+ else if (outcome.scope === 'partial') partialReprint(outcome.reason, outcome.printed);
472
+ }
473
+
474
+ /**
475
+ * A morph replaced live nodes, so the map is keyed by objects that are no longer in
476
+ * the page, and it has to be re-paired either way. A DISK frame also carries the bytes
477
+ * now on disk, written by somebody else: those become the model, the same as bytes this
478
+ * tab saved, or the next save would copy the old formatting back over theirs. A peer
479
+ * frame changed nothing on disk, so the model stays. The re-pair runs in idle time, or
480
+ * earlier if a save, `text()` or `locate()` needs it first.
481
+ */
482
+ function queueRepair(event) {
483
+ const detail = event && event.detail;
484
+ if (detail && detail.source === 'disk' && typeof detail.html === 'string') {
485
+ // Newer bytes than anything already in flight, adopted or not: a fetch that started
486
+ // before this frame cannot answer for the file the frame describes, so the boot
487
+ // fetch asks again rather than installing the version it asked about.
488
+ state.adoptions++;
489
+ }
490
+ if (!state.installed) return;
491
+ if (detail && detail.source === 'disk' && typeof detail.html === 'string') {
492
+ if (isProjected(detail.html)) state.refetch = true;
493
+ else state.pendingBytes = detail.html;
494
+ }
495
+ scheduleRefresh();
496
+ }
497
+
498
+ /**
499
+ * Re-model if a save was accepted, re-pair either way, once, off the critical path.
500
+ *
501
+ * Coalescing is what makes this safe as well as cheap. Saves are serialized, so the
502
+ * most recent accepted bytes are the file; a refresh that skipped an intermediate
503
+ * save would still land on the right answer, and one that ran them out of order could
504
+ * not.
505
+ */
506
+ function scheduleRefresh() {
507
+ if (state.refreshQueued) return;
508
+ state.refreshQueued = true;
509
+ if (typeof requestIdleCallback === 'function') requestIdleCallback(refreshNow, { timeout: 2000 });
510
+ else setTimeout(refreshNow, 0);
511
+ }
512
+
513
+ /**
514
+ * Run a queued refresh now. A no-op when none is queued, which is also what makes the
515
+ * idle callback of a refresh that `text()` or `locate()` already ran harmless.
516
+ */
517
+ function refreshNow() {
518
+ if (!state.refreshQueued) return;
519
+ state.refreshQueued = false;
520
+ if (!state.installed) return;
521
+ const bytes = state.pendingBytes;
522
+ state.pendingBytes = null;
523
+ const refetch = state.refetch && bytes === null;
524
+ if (refetch) state.refetch = false;
525
+ // Counted even when the re-pair below fails: either way this page is not the page a
526
+ // candidate captured before it was built against.
527
+ state.repairs++;
528
+ const t = now();
529
+ // The two halves fail independently, so they are tried independently. A refused
530
+ // re-model used to skip the re-pair with it, and the re-pair is the half that
531
+ // cannot be skipped: a morph replaced live nodes, so the old map is keyed by
532
+ // objects no longer in the page, and every save after that reprints the whole
533
+ // document until something else queues a refresh. The previous model still
534
+ // describes real bytes, so re-pairing against it is the right answer.
535
+ let m = state.model;
536
+ if (bytes !== null) {
537
+ try {
538
+ const next = model(bytes);
539
+ const refused = checkSource(next, document);
540
+ if (refused) throw new Error(refused);
541
+ m = next;
542
+ } catch (err) {
543
+ console.warn('clayjs: source map kept the previous model, the accepted bytes did not model:', err);
544
+ }
545
+ }
546
+ try {
547
+ const { map, stats } = pairAgainstPage(m);
548
+ state.model = m;
549
+ state.map = map;
550
+ state.stats = stats;
551
+ state.refreshes++;
552
+ state.timing.refresh = now() - t;
553
+ } catch (err) {
554
+ // The map is now stale rather than wrong: it still describes the pairing as of
555
+ // the last successful refresh. Renders off a stale map verify or fall back like
556
+ // any other, so this costs formatting fidelity and nothing else.
557
+ console.warn('clayjs: source map could not re-pair, continuing on the previous map:', err);
558
+ }
559
+ if (refetch) refetchModel();
560
+ }
561
+
562
+ /**
563
+ * Take the model from the file itself, after a disk frame arrived with bytes this host
564
+ * projected rather than bytes on disk.
565
+ *
566
+ * The re-pair has already run against the previous model, which still describes real
567
+ * bytes, so a fetch that fails or is refused costs nothing: the model stays as it was
568
+ * and the same warning the refused branch gives says so. Anything that replaced the
569
+ * model while the fetch was in flight is newer than these bytes and keeps the answer.
570
+ */
571
+ function refetchModel() {
572
+ const generation = state.repairs;
573
+ const adoption = state.adoptions;
574
+ fetchSource(window.location.href)
575
+ .then((src) => {
576
+ const next = model(src);
577
+ const refused = checkSource(next, document);
578
+ if (refused) throw new Error(refused);
579
+ return next;
580
+ })
581
+ .then((m) => {
582
+ // A save landed, or a newer fetch answered, while this one was in flight: its bytes
583
+ // describe a file this page has already moved past.
584
+ if (state.repairs !== generation || state.adoptions !== adoption) return;
585
+ try {
586
+ state.repairs++;
587
+ state.adoptions++;
588
+ const { map, stats } = pairAgainstPage(m);
589
+ state.model = m;
590
+ state.map = map;
591
+ state.stats = stats;
592
+ state.refreshes++;
593
+ } catch (err) {
594
+ console.warn('clayjs: source map could not re-pair, continuing on the previous map:', err);
595
+ }
596
+ })
597
+ .catch((err) => {
598
+ console.warn('clayjs: source map kept the previous model, the accepted bytes did not model:', err);
599
+ });
600
+ }
601
+
602
+ function summary() {
603
+ const s = state.stats;
604
+ return {
605
+ installed: state.installed,
606
+ refused: state.refused,
607
+ sourceBytes: state.model ? state.model.src.length : null,
608
+ paired: s ? s.paired : 0,
609
+ unresolved: s ? s.unresolved : 0,
610
+ unmatchedLive: s ? s.unmatchedLive.length : 0,
611
+ unmatchedSource: s ? s.unmatchedSource.length : 0,
612
+ attrDiffs: s ? s.attrDiffs.length : 0,
613
+ saves: state.saves,
614
+ reprints: state.reprints,
615
+ lastReprint: state.lastReprint,
616
+ partialReprints: state.partialReprints,
617
+ lastPartialReprint: state.lastPartialReprint,
618
+ lastRenderMs: state.lastRenderMs,
619
+ lastBytes: state.lastBytes,
620
+ refreshes: state.refreshes,
621
+ timing: state.timing,
622
+ };
623
+ }
624
+
625
+ /**
626
+ * The bytes this module believes are on disk right now. A refresh still queued from a save
627
+ * or a disk frame runs first, so this is never the file before that.
628
+ */
629
+ export function text() {
630
+ refreshNow();
631
+ return state.model ? state.model.src : null;
632
+ }
633
+
634
+ /**
635
+ * Where a live element is in `text()`: `{ from, to, line, column }`, or null.
636
+ *
637
+ * Offsets and `column` are UTF-16 code units into `text()`, not bytes. Slice `text()` with them
638
+ * and the answer is exact; hand them to something that counts bytes and it is wrong on any
639
+ * document with non-ASCII content, which is the kind of mistake that shows up months later in
640
+ * one customer's file.
641
+ *
642
+ * null whenever this module cannot answer, which includes the whole document when the session
643
+ * never installed or refused it. `stats().installed` and `stats().refused` say which, and an
644
+ * agent that cannot tell "this element is new" from "this document has no map" will eventually
645
+ * write into a file it was never modelling.
646
+ */
647
+ export function locateLive(node) {
648
+ refreshNow();
649
+ return state.installed && node ? locate(node, state.map, state.model) : null;
650
+ }
651
+
652
+ /** What did not pair, for working out why a document reprints more than it should. */
653
+ export function unpaired() {
654
+ refreshNow();
655
+ return state.stats
656
+ ? { live: state.stats.unmatchedLive.slice(0, 50), source: state.stats.unmatchedSource.slice(0, 50) }
657
+ : null;
658
+ }
659
+
660
+ export function stats() {
661
+ refreshNow();
662
+ return summary();
663
+ }
664
+
665
+ export const installed = () => state.installed;
666
+
667
+ /** The facade's dispose: take the install back, and invalidate whatever was still loading. */
668
+ export function release() {
669
+ uninstall();
670
+ }
671
+
672
+ /**
673
+ * Take an install back: the renderer, a scheduled refresh, and the installed state that
674
+ * lets the hook and the listener do anything. The map and the model stay, so `text()` and
675
+ * `locateLive()` keep answering about the last file this module modelled while a view is
676
+ * reading it.
677
+ */
678
+ function uninstall() {
679
+ state.session++;
680
+ state.installed = false;
681
+ state.refreshQueued = false;
682
+ state.pendingBytes = null;
683
+ state.refetch = false;
684
+ setSaveRenderer(null);
685
+ }
686
+
687
+ /**
688
+ * The install a session's demand asks for.
689
+ *
690
+ * `context` is the mode entry's own context, when there was one: `represented` is the file
691
+ * the reader handed that session. `latest` is the facade's newest receipt, read when the
692
+ * install starts so a save or disk frame that landed while the parser loaded is the file
693
+ * this session models rather than an answer older than it.
694
+ */
695
+ export function start({ context = null, latest = null } = {}) {
696
+ return install(window.location.href, context, 0, true, latest);
697
+ }
698
+
699
+ /** Bytes the host took. Re-models against them, or records them for the install to use. */
700
+ export { adopt as adoptAccepted };
701
+
702
+ // Registered once, when the session first loads: both bodies return while no session is
703
+ // installed, so a page that never demands the parser never runs either.
704
+ onSaveAccepted(adopt);
705
+ document.addEventListener('clay:sync-applied', queueRepair);