@panphora/clayjs 1.15.2 → 1.16.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 (506) 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 +83090 -58706
  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-NWPPXOCZ.js +1 -0
  21. package/dist/m/attrs/save-freeze-FUP4Q4X3.js +1 -0
  22. package/dist/m/c-2AZDZWCK.js +1 -0
  23. package/dist/m/c-2JL6E75N.js +1 -0
  24. package/dist/m/c-2M73252O.js +1 -0
  25. package/dist/m/c-2SPVQSHC.js +1 -0
  26. package/dist/m/c-33HUYEDI.js +1 -0
  27. package/dist/m/c-33JJ6CBX.js +2 -0
  28. package/dist/m/c-35NBGMZB.js +1 -0
  29. package/dist/m/c-3GJ4SDE2.js +1 -0
  30. package/dist/m/c-3IQRECYS.js +1 -0
  31. package/dist/m/c-3OWDMNJR.js +2 -0
  32. package/dist/m/c-46XYM55L.js +1 -0
  33. package/dist/m/c-4B6H4G5V.js +1 -0
  34. package/dist/m/c-4DRMS4QG.js +9 -0
  35. package/dist/m/c-4FFHZM32.js +1 -0
  36. package/dist/m/c-4JZDZFF6.js +1 -0
  37. package/dist/m/c-4L3BTFRK.js +1 -0
  38. package/dist/m/c-4Q2XTQ2N.js +2 -0
  39. package/dist/m/c-4WKFKEKF.js +1 -0
  40. package/dist/m/c-4YSCRICO.js +1 -0
  41. package/dist/m/c-54RCDCBU.js +1 -0
  42. package/dist/m/c-5M4YEKCX.js +4 -0
  43. package/dist/m/c-5R4PTRG2.js +1 -0
  44. package/dist/m/c-5V3MOVJT.js +1 -0
  45. package/dist/m/c-5VYOWDTI.js +1 -0
  46. package/dist/m/c-5Y6TNX5W.js +34 -0
  47. package/dist/m/c-5YIHJ3TJ.js +1 -0
  48. package/dist/m/c-66O32MV3.js +28 -0
  49. package/dist/m/c-6HMB24EV.js +1 -0
  50. package/dist/m/c-7DJDKPGL.js +1 -0
  51. package/dist/m/c-7O6AFR65.js +1 -0
  52. package/dist/m/c-7SWMYKAI.js +1 -0
  53. package/dist/m/c-7VGZ7RU5.js +1 -0
  54. package/dist/m/c-A7LS5UZW.js +1 -0
  55. package/dist/m/c-ANQTJ3B5.js +2 -0
  56. package/dist/m/c-AQLFEATB.js +1 -0
  57. package/dist/m/c-AYVOREBE.js +1 -0
  58. package/dist/m/c-B2ZXPOS3.js +3 -0
  59. package/dist/m/c-BBVGTQG7.js +1 -0
  60. package/dist/m/c-BMIZKMPV.js +1 -0
  61. package/dist/m/c-BTDI4Y23.js +1 -0
  62. package/dist/m/c-BWFVSBDF.js +1 -0
  63. package/dist/m/c-BYTKL6UX.js +1 -0
  64. package/dist/m/c-CFHW5AMY.js +6 -0
  65. package/dist/m/c-CJZSLMVQ.js +1 -0
  66. package/dist/m/c-CYWLPPUS.js +1 -0
  67. package/dist/m/c-CZUSOB7V.js +1 -0
  68. package/dist/m/c-DBE6ORSI.js +1 -0
  69. package/dist/m/c-DCFSEJZG.js +1 -0
  70. package/dist/m/c-DNZ5IFM6.js +1 -0
  71. package/dist/m/c-EDHUR4MX.js +1 -0
  72. package/dist/m/c-EJTGYSLN.js +1 -0
  73. package/dist/m/c-EY6UOM54.js +1 -0
  74. package/dist/m/c-F3CKJTJS.js +1 -0
  75. package/dist/m/c-F7Y6FLPY.js +1 -0
  76. package/dist/m/c-FA4DZVTC.js +1 -0
  77. package/dist/m/c-FILLTJA7.js +1 -0
  78. package/dist/m/c-G2BUZITR.js +1 -0
  79. package/dist/m/c-GB3JTV6E.js +1 -0
  80. package/dist/m/c-GBGWXTX4.js +1 -0
  81. package/dist/m/c-GHBCCVG4.js +1 -0
  82. package/dist/m/c-GJAEGGXY.js +1 -0
  83. package/dist/m/c-GOWBDM6J.js +1 -0
  84. package/dist/m/c-GQ6F6IDK.js +1 -0
  85. package/dist/m/c-H4LG442Q.js +1 -0
  86. package/dist/m/c-HA6INT6J.js +1 -0
  87. package/dist/m/c-HPURYVZS.js +2 -0
  88. package/dist/m/c-HRDCD3WN.js +67 -0
  89. package/dist/m/c-HT33EQRN.js +1 -0
  90. package/dist/m/c-HVYXIS72.js +1 -0
  91. package/dist/m/c-IB52R237.js +1 -0
  92. package/dist/m/c-IJUK2GPW.js +1 -0
  93. package/dist/m/c-IKWNE4R4.js +1 -0
  94. package/dist/m/c-IU3IYEU6.js +1 -0
  95. package/dist/m/c-IZ2EBRAJ.js +1 -0
  96. package/dist/m/c-J7CA54IP.js +1 -0
  97. package/dist/m/c-JAX2667S.js +6 -0
  98. package/dist/m/c-KI7M4AY3.js +1 -0
  99. package/dist/m/c-KIPQJX4B.js +1 -0
  100. package/dist/m/c-KKWLDOPM.js +1 -0
  101. package/dist/m/c-KR4UW4KW.js +1 -0
  102. package/dist/m/c-KY3VJGUA.js +8 -0
  103. package/dist/m/c-L4WQSC27.js +1 -0
  104. package/dist/m/c-LFPHCXY3.js +1 -0
  105. package/dist/m/c-LR4CGEPT.js +91 -0
  106. package/dist/m/c-LRPUO2MX.js +1 -0
  107. package/dist/m/c-LTGMXXKO.js +1 -0
  108. package/dist/m/c-LXENEBV7.js +1 -0
  109. package/dist/m/c-LZJPNR2W.js +1 -0
  110. package/dist/m/c-MMUIUUZG.js +2 -0
  111. package/dist/m/c-MTQHWQC3.js +1 -0
  112. package/dist/m/c-N326R2JD.js +1 -0
  113. package/dist/m/c-N7JPUPGC.js +1 -0
  114. package/dist/m/c-NMJRXKPC.js +8 -0
  115. package/dist/m/c-NRPJEVJS.js +1 -0
  116. package/dist/m/c-NUHP2XDB.js +1 -0
  117. package/dist/m/c-NZMNXVF6.js +1 -0
  118. package/dist/m/c-OCSZ4QGN.js +1 -0
  119. package/dist/m/c-OIBUTDXI.js +1 -0
  120. package/dist/m/c-OIDRZKAF.js +1 -0
  121. package/dist/m/c-OYTFP5QN.js +1 -0
  122. package/dist/m/c-P7KNDG34.js +1 -0
  123. package/dist/m/c-PPOSCAQD.js +1 -0
  124. package/dist/m/c-PQ5LK6QE.js +993 -0
  125. package/dist/m/c-PZ4XEPUP.js +2 -0
  126. package/dist/m/c-QHIWXHRO.js +1 -0
  127. package/dist/m/c-QHWJ5ZIJ.js +1 -0
  128. package/dist/m/c-QIX5BJYN.js +1 -0
  129. package/dist/m/c-QNKREOL6.js +1 -0
  130. package/dist/m/c-QQHEX6HA.js +1 -0
  131. package/dist/m/c-R7RMANJI.js +9 -0
  132. package/dist/m/c-RI4NT75Y.js +1 -0
  133. package/dist/m/c-RM7J7BV2.js +1 -0
  134. package/dist/m/c-RUJKSUQW.js +1 -0
  135. package/dist/m/c-RVMWGXEJ.js +1 -0
  136. package/dist/m/c-S7H2DRCB.js +1 -0
  137. package/dist/m/c-S7LK4KUE.js +1 -0
  138. package/dist/m/c-SGCCC4FW.js +25 -0
  139. package/dist/m/c-SGIA4IEQ.js +188 -0
  140. package/dist/m/c-SWRJFCNN.js +1 -0
  141. package/dist/m/c-T4VO4OCB.js +1 -0
  142. package/dist/m/c-TCCA5BZN.js +2 -0
  143. package/dist/m/c-TQEMVJCS.js +1 -0
  144. package/dist/m/c-TQZ2JYHD.js +1 -0
  145. package/dist/m/c-TR6RMDQ7.js +3 -0
  146. package/dist/m/c-TRS6TM6O.js +1 -0
  147. package/dist/m/c-UGHH4MB3.js +1 -0
  148. package/dist/m/c-UKHTLWUW.js +1 -0
  149. package/dist/m/c-UL2YUY52.js +1 -0
  150. package/dist/m/c-UNN7LWCT.js +1 -0
  151. package/dist/m/c-UXBMRDZB.js +235 -0
  152. package/dist/m/c-V7PZZRG7.js +1 -0
  153. package/dist/m/c-VAK7SG37.js +1 -0
  154. package/dist/m/c-VDSS4K3Z.js +1 -0
  155. package/dist/m/c-VFX45IZQ.js +1 -0
  156. package/dist/m/c-VHNSBD2M.js +1 -0
  157. package/dist/m/c-VKEDWYDF.js +1 -0
  158. package/dist/m/c-VL52ADDR.js +1 -0
  159. package/dist/m/c-WN5AY3OZ.js +1 -0
  160. package/dist/m/c-WNYBLIOZ.js +1 -0
  161. package/dist/m/c-WRBKEJD4.js +1 -0
  162. package/dist/m/c-WWDBYXLK.js +1 -0
  163. package/dist/m/c-WYFDLITL.js +1 -0
  164. package/dist/m/c-XN6MBKI6.js +67 -0
  165. package/dist/m/c-XNNW2CWK.js +1 -0
  166. package/dist/m/c-XPELB6E5.js +1 -0
  167. package/dist/m/c-XQXZAO7N.js +1 -0
  168. package/dist/m/c-Y6USGTS5.js +5 -0
  169. package/dist/m/c-YDH7Q3NY.js +1 -0
  170. package/dist/m/c-YG2NJYS6.js +135 -0
  171. package/dist/m/c-Z36J63PP.js +1 -0
  172. package/dist/m/c-Z4VRC7GU.js +7 -0
  173. package/dist/m/c-Z6K52OLP.js +19 -0
  174. package/dist/m/c-ZDY2MDAA.js +21 -0
  175. package/dist/m/c-ZE46QDIH.js +1 -0
  176. package/dist/m/c-ZUCOKOHT.js +1 -0
  177. package/dist/m/core/admin-attrs-VGNLAUDK.js +1 -0
  178. package/dist/m/core/autosave-E4PEFPZ6.js +1 -0
  179. package/dist/m/core/conflict-notice-VQ6Y64PH.js +1 -0
  180. package/dist/m/core/draft-mode-WSQAT67S.js +1 -0
  181. package/dist/m/core/host-permission-XN3K2IW3.js +1 -0
  182. package/dist/m/core/is-edit-mode-JXHDVGUL.js +1 -0
  183. package/dist/m/core/persist-HKHA66IY.js +3 -0
  184. package/dist/m/core/recovery-TBBSICE2.js +1 -0
  185. package/dist/m/core/save-WUCHTQBE.js +1 -0
  186. package/dist/m/core/save-core-FJVHT5M3.js +1 -0
  187. package/dist/m/core/snapshot-F3SWDZMM.js +1 -0
  188. package/dist/m/core/unsaved-warning-STKBHE6G.js +1 -0
  189. package/dist/m/dom/all-APS5ZSKP.js +1 -0
  190. package/dist/m/dom/dom-helpers-L7OSR6S4.js +1 -0
  191. package/dist/m/events/index-23DPTUT4.js +1 -0
  192. package/dist/m/internals/index-WOVAP3RU.js +1 -0
  193. package/dist/m/lib/cache-bust-CGWT6ZJI.js +1 -0
  194. package/dist/m/lib/region-policy-SB2AWVG5.js +1 -0
  195. package/dist/m/loader-HXX6YNH5.js +1 -0
  196. package/dist/m/manifest-F22DEDAC.js +1 -0
  197. package/dist/m/manifest-edit-C3C7376B.js +1 -0
  198. package/dist/m/manifest.json +3491 -0
  199. package/dist/m/options/options-2LH76JQ3.js +1 -0
  200. package/dist/m/page/editor-XUTB2HB2.js +189 -0
  201. package/dist/m/plugins/ai-edit-ZQ4CGWIP.js +6 -0
  202. package/dist/m/plugins/bind-text-NX46N5UV.js +1 -0
  203. package/dist/m/plugins/cms-HP3LSTPN.js +2293 -0
  204. package/dist/m/plugins/demo-ZIBFAA3R.js +1 -0
  205. package/dist/m/plugins/derive-UAAA4V4S.js +1 -0
  206. package/dist/m/plugins/indicator-PV4YW2ID.js +1 -0
  207. package/dist/m/plugins/live-NUTTXSBS.js +1 -0
  208. package/dist/m/plugins/menu-HCKO3DSP.js +138 -0
  209. package/dist/m/plugins/message-LQ6EDH4V.js +1 -0
  210. package/dist/m/plugins/page-KSUH7G6J.js +2 -0
  211. package/dist/m/plugins/page-view-W73OW7WI.js +1 -0
  212. package/dist/m/plugins/people-AJJ32YCG.js +1 -0
  213. package/dist/m/plugins/quickcrop-QNY3PKLD.js +26 -0
  214. package/dist/m/plugins/quickcrop-QNY3PKLD.js.LEGAL.txt +7 -0
  215. package/dist/m/plugins/richclay-RC3L2F6X.js +517 -0
  216. package/dist/m/plugins/richclay-RC3L2F6X.js.LEGAL.txt +1 -0
  217. package/dist/m/plugins/sortable-YRMQX3ZU.js +1 -0
  218. package/dist/m/plugins/source-KHK5UUSK.js +1 -0
  219. package/dist/m/plugins/undo-OF2L3CXU.js +1 -0
  220. package/dist/m/plugins/unsent-HJFLQJ2O.js +1 -0
  221. package/dist/m/plugins/upgrade-ZNYODP5Y.js +1 -0
  222. package/dist/m/plugins/upload-XSGNILUY.js +1 -0
  223. package/dist/m/plugins/visitor-drafts-XK5GW55Q.js +21 -0
  224. package/dist/m/plugins/visitor-drafts-owner-KSSH75EV.js +1 -0
  225. package/dist/m/plugins/wire-ZWVWKF76.js +1 -0
  226. package/dist/m/reader-core-ILWLXVMP.js +68 -0
  227. package/dist/m/route/route-KOCZSEGN.js +1 -0
  228. package/dist/m/sync/live-sync-4AUVXVJU.js +1 -0
  229. package/dist/m/ui/index-EVHKMKBL.js +1 -0
  230. package/dist/m/utils/index-4TJCVLT7.js +1 -0
  231. package/dist/m/vendor/Sortable.vendor-A755ITLN.js +3 -0
  232. package/dist/m/vendor/Sortable.vendor-A755ITLN.js.LEGAL.txt +1 -0
  233. package/dist/m/vendor/sap-core.vendor-A7BMUYZT.js +6 -0
  234. package/dist/offline-manifest.json +3800 -0
  235. package/dist/page/themes/light-1.css +406 -0
  236. package/dist/server/core.cjs +23662 -22860
  237. package/dist/server/offline-runtime.cjs +4954 -0
  238. package/dist/server/offline-runtime.mjs +4923 -0
  239. package/dist/server/rooms.mjs +9 -0
  240. package/docs/guide.md +1078 -669
  241. package/entries/all.js +2 -0
  242. package/entries/clay-data.js +1 -1
  243. package/entries/clay-dom.js +2 -0
  244. package/entries/clay-events.js +2 -0
  245. package/entries/clay-internals.js +2 -0
  246. package/entries/clay-options.js +2 -0
  247. package/entries/clay-router.js +2 -0
  248. package/entries/clay-ui.js +2 -0
  249. package/entries/clay-utils.js +2 -0
  250. package/entries/clay.js +2 -0
  251. package/entries/sap.js +6 -6
  252. package/fonts/ibm-plex-mono-LICENSE +93 -0
  253. package/fonts/ibm-plex-mono-latin-300-normal.woff2 +0 -0
  254. package/fonts/ibm-plex-mono-latin-400-italic.woff2 +0 -0
  255. package/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
  256. package/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
  257. package/fonts/ibm-plex-mono-latin-600-normal.woff2 +0 -0
  258. package/fonts/ibm-plex-sans-LICENSE +93 -0
  259. package/fonts/ibm-plex-sans-latin-400-normal.woff2 +0 -0
  260. package/fonts/ibm-plex-sans-latin-500-normal.woff2 +0 -0
  261. package/fonts/ibm-plex-sans-latin-600-normal.woff2 +0 -0
  262. package/fonts/ibm-plex.css +71 -0
  263. package/fonts/inter-LICENSE +93 -0
  264. package/fonts/inter-cyrillic-ext-wght-normal.woff2 +0 -0
  265. package/fonts/inter-cyrillic-wght-normal.woff2 +0 -0
  266. package/fonts/inter-greek-ext-wght-normal.woff2 +0 -0
  267. package/fonts/inter-greek-wght-normal.woff2 +0 -0
  268. package/fonts/inter-latin-ext-wght-normal.woff2 +0 -0
  269. package/fonts/inter-latin-wght-normal.woff2 +0 -0
  270. package/fonts/inter-vietnamese-wght-normal.woff2 +0 -0
  271. package/fonts/inter.css +62 -0
  272. package/fonts/newsreader-LICENSE +93 -0
  273. package/fonts/newsreader-latin-ext-opsz-normal.woff2 +0 -0
  274. package/fonts/newsreader-latin-opsz-normal.woff2 +0 -0
  275. package/fonts/newsreader-vietnamese-opsz-normal.woff2 +0 -0
  276. package/fonts/newsreader.css +26 -0
  277. package/fonts/work-sans-LICENSE +93 -0
  278. package/fonts/work-sans-latin-wght-normal.woff2 +0 -0
  279. package/fonts/work-sans.css +8 -0
  280. package/package.json +20 -3
  281. package/packed-contract.json +1 -0
  282. package/src/attrs/add-from.js +27 -4
  283. package/src/attrs/save-freeze.js +7 -52
  284. package/src/core/admin-attrs.js +119 -12
  285. package/src/core/admin-contenteditable.js +12 -10
  286. package/src/core/admin-inputs.js +14 -15
  287. package/src/core/admin-onclick.js +2 -0
  288. package/src/core/autosave.js +74 -21
  289. package/src/core/conflict-download.js +22 -0
  290. package/src/core/conflict-notice-ui.js +1173 -0
  291. package/src/core/conflict-notice.js +140 -697
  292. package/src/core/edit-mode.js +147 -14
  293. package/src/core/etag.js +30 -12
  294. package/src/core/host-meta.js +72 -8
  295. package/src/core/host-permission.js +29 -0
  296. package/src/core/is-edit-mode.js +145 -12
  297. package/src/core/mode-key.js +125 -0
  298. package/src/core/mode-lifecycle.js +544 -0
  299. package/src/core/mode-receipt.js +30 -0
  300. package/src/core/mode-refusal.js +13 -0
  301. package/src/core/mode-resources.js +52 -0
  302. package/src/core/mode-support.js +82 -0
  303. package/src/core/mode-switch.js +259 -0
  304. package/src/core/mode-transition.js +112 -0
  305. package/src/core/page-data-write.js +305 -0
  306. package/src/core/page-data.js +26 -227
  307. package/src/core/page-format-wire.js +4 -0
  308. package/src/core/page-format.js +58 -0
  309. package/src/core/persist.js +37 -9
  310. package/src/core/preserve-authored.js +114 -0
  311. package/src/core/recovery.js +772 -0
  312. package/src/core/runtime-url.js +105 -0
  313. package/src/core/save-core.js +109 -12
  314. package/src/core/save-replace.js +72 -0
  315. package/src/core/save-size-notice.js +134 -0
  316. package/src/core/save-size.js +140 -0
  317. package/src/core/save.js +805 -118
  318. package/src/core/snapshot.js +121 -11
  319. package/src/core/source-map.js +157 -9
  320. package/src/core/unsaved-warning.js +14 -4
  321. package/src/core/upgrade-run.js +408 -0
  322. package/src/core/version.js +18 -0
  323. package/src/lib/assets.js +30 -0
  324. package/src/lib/autosave-debug-gate.js +50 -0
  325. package/src/lib/autosave-scheduler.js +141 -0
  326. package/src/lib/boot-imports.js +8 -0
  327. package/src/lib/derived.js +3 -1
  328. package/src/lib/dirty-gate.js +15 -0
  329. package/src/lib/doc-storage.js +33 -0
  330. package/src/lib/effect.js +9 -4
  331. package/src/lib/mutation.js +4 -2
  332. package/src/lib/offline-runtime.js +226 -0
  333. package/src/lib/preload.js +48 -0
  334. package/src/lib/root-attrs.js +1 -1
  335. package/src/lib/trash.js +18 -0
  336. package/src/lib/undo/capture-move.js +152 -0
  337. package/src/live/local-channel.js +61 -0
  338. package/src/live/local-lane.js +60 -9
  339. package/src/live/server-lane.js +6 -3
  340. package/src/loader-logic.js +76 -15
  341. package/src/loader.js +218 -30
  342. package/src/page/adopt-boot.js +76 -22
  343. package/src/page/adopt.js +25 -4
  344. package/src/page/awareness.js +230 -0
  345. package/src/page/blocks.js +79 -5
  346. package/src/page/blocksel.js +20 -4
  347. package/src/page/board-face.js +13 -0
  348. package/src/page/board-panel.js +5 -17
  349. package/src/page/board-style.js +2 -13
  350. package/src/page/board-view.js +1 -11
  351. package/src/page/board-views.js +17 -0
  352. package/src/page/board.js +2 -2
  353. package/src/page/boot-pack.js +26 -0
  354. package/src/page/boot.js +237 -0
  355. package/src/page/checklist.js +1 -1
  356. package/src/page/chrome.js +411 -61
  357. package/src/page/csv-import.js +17 -2
  358. package/src/page/document-title-dom.js +40 -0
  359. package/src/page/document-title-runtime.js +76 -0
  360. package/src/page/document-title.js +9 -0
  361. package/src/page/drag.js +48 -13
  362. package/src/page/drop.js +2 -1
  363. package/src/page/editor.js +330 -86
  364. package/src/page/feature-loader.js +72 -0
  365. package/src/page/features/board.js +12 -0
  366. package/src/page/features/code.js +13 -0
  367. package/src/page/features/media.js +290 -0
  368. package/src/page/features/registry.js +23 -0
  369. package/src/page/features/table.js +383 -0
  370. package/src/page/{tool-view.js → features/tool.js} +33 -2
  371. package/src/page/format-codec.js +215 -0
  372. package/src/page/format-gate.js +179 -0
  373. package/src/page/format-level.js +157 -0
  374. package/src/page/highlight-slot.js +23 -0
  375. package/src/page/highlight.js +30 -16
  376. package/src/page/history-intent.js +78 -0
  377. package/src/page/inline.js +18 -25
  378. package/src/page/items.js +52 -11
  379. package/src/page/link.js +1 -0
  380. package/src/page/markdown.js +86 -1
  381. package/src/page/media-views.js +3 -0
  382. package/src/page/media.js +33 -245
  383. package/src/page/outline-cache.js +11 -0
  384. package/src/page/placeholder.js +27 -7
  385. package/src/page/projection.js +196 -50
  386. package/src/page/reader-patch.js +306 -0
  387. package/src/page/recover.js +1 -1
  388. package/src/page/rich/page-tool.js +1 -4
  389. package/src/page/runtime-owner.js +27 -0
  390. package/src/page/save-projection.js +7 -4
  391. package/src/page/schema.js +14 -0
  392. package/src/page/table-rect-key.js +3 -0
  393. package/src/page/table-select.js +4 -3
  394. package/src/page/table-style.js +1 -1
  395. package/src/page/table-views.js +3 -0
  396. package/src/page/table.js +8 -293
  397. package/src/page/themes/light-1.css +139 -2
  398. package/src/page/themes/light-1.source.css +269 -0
  399. package/src/page/tool-capability.js +3 -0
  400. package/src/page/tool-views.js +3 -0
  401. package/src/page/view.js +390 -107
  402. package/src/page/ybind/awareness.js +172 -0
  403. package/src/page/ybind/binding.js +9 -1
  404. package/src/page/ybind/bridge.js +50 -47
  405. package/src/page/ybind/decode.js +52 -19
  406. package/src/page/ybind/doc.js +27 -2
  407. package/src/page/ybind/group.js +12 -3
  408. package/src/page/ybind/index.js +2 -0
  409. package/src/page/ybind/lineage.js +71 -0
  410. package/src/page/ybind/marks.js +25 -1
  411. package/src/page/ybind/migrate.js +48 -13
  412. package/src/page/ybind/moves.js +32 -21
  413. package/src/page/ybind/positions.js +50 -0
  414. package/src/page/ybind/provider.js +84 -22
  415. package/src/page/ybind/recovery-proof.js +191 -0
  416. package/src/page/ybind/recovery-replay.js +130 -0
  417. package/src/page/ybind/tokens.js +15 -6
  418. package/src/page/ybind/walk.js +10 -3
  419. package/src/page/ybind/writer.js +48 -4
  420. package/src/plugins/ai-edit.js +167 -32
  421. package/src/plugins/bind-text.js +218 -0
  422. package/src/plugins/cms.js +93 -0
  423. package/src/plugins/indicator.js +57 -5
  424. package/src/plugins/live.js +282 -34
  425. package/src/plugins/menu/claygoo.svg.js +1 -0
  426. package/src/plugins/menu/seo-head.js +323 -0
  427. package/src/plugins/menu/seo.js +849 -0
  428. package/src/plugins/menu.js +425 -0
  429. package/src/plugins/page-view.js +131 -0
  430. package/src/plugins/page.js +1022 -275
  431. package/src/plugins/people.js +158 -31
  432. package/src/plugins/richclay.js +119 -9
  433. package/src/plugins/sortable.js +370 -60
  434. package/src/plugins/source.js +189 -366
  435. package/src/plugins/undo.js +289 -19
  436. package/src/plugins/unsent.js +132 -0
  437. package/src/plugins/upgrade-run.js +1 -0
  438. package/src/plugins/upgrade.js +210 -0
  439. package/src/plugins/upload-task.js +571 -0
  440. package/src/plugins/upload-ui.js +265 -0
  441. package/src/plugins/upload.js +34 -153
  442. package/src/plugins/visitor-drafts/inert.js +32 -0
  443. package/src/plugins/visitor-drafts/owner-chip.js +3 -0
  444. package/src/plugins/visitor-drafts/prompt.js +2 -2
  445. package/src/plugins/visitor-drafts-owner.js +85 -0
  446. package/src/plugins/visitor-drafts.js +31 -42
  447. package/src/plugins/wire.js +82 -1
  448. package/src/reader-core.js +26 -0
  449. package/src/recovery/conflicts.js +287 -0
  450. package/src/recovery/identity.js +154 -0
  451. package/src/recovery/store.js +792 -0
  452. package/src/recovery/undo.js +257 -0
  453. package/src/server/checkpoint/index.js +68 -4
  454. package/src/server/offline-runtime/index.js +214 -0
  455. package/src/server/rooms/index.js +2 -1
  456. package/src/server/rooms/live-data-rooms.js +2 -2
  457. package/src/server/rooms/page-admission.js +33 -0
  458. package/src/server/rooms/page-lineage.js +56 -0
  459. package/src/server/rooms/page-rebase.js +11 -5
  460. package/src/server/rooms/page-state.js +2 -0
  461. package/src/server/rooms/page-write-admission.js +9 -3
  462. package/src/source/session.js +705 -0
  463. package/src/sync/conflict-revert.js +5 -4
  464. package/src/sync/conflicts-edit.js +119 -0
  465. package/src/sync/conflicts-facade.js +49 -0
  466. package/src/sync/conflicts.js +99 -1
  467. package/src/sync/editing-marker.js +37 -6
  468. package/src/sync/editing-sender.js +8 -1
  469. package/src/sync/live-sync-edit.js +63 -0
  470. package/src/sync/live-sync.js +364 -119
  471. package/src/sync/local-presence.js +187 -0
  472. package/src/sync/merge-declarations.js +10 -0
  473. package/src/sync/merge-engine.js +12 -0
  474. package/src/sync/presence-store.js +412 -0
  475. package/src/sync/presence-target.js +243 -0
  476. package/src/sync/presence.js +72 -41
  477. package/src/sync/saved-handoff.js +200 -0
  478. package/src/sync/saved-stream.js +535 -0
  479. package/src/sync/version-guard.js +92 -0
  480. package/src/sync/wire-profile.js +81 -0
  481. package/src/ui/bevel-controls.js +2 -8
  482. package/src/ui/page-scheme.js +7 -0
  483. package/src/ui/skins/cms.js +1 -1
  484. package/src/ui/theme-config.js +150 -0
  485. package/src/ui/theme.js +6 -138
  486. package/src/ui/toast-lazy.js +11 -0
  487. package/src/vendor/LICENSES-optional.md +253 -0
  488. package/src/vendor/highlight.vendor.js +3 -0
  489. package/src/vendor/hljs-bash.vendor.js +2 -0
  490. package/src/vendor/hljs-css.vendor.js +2 -0
  491. package/src/vendor/hljs-javascript.vendor.js +2 -0
  492. package/src/vendor/hljs-json.vendor.js +2 -0
  493. package/src/vendor/hljs-python.vendor.js +2 -0
  494. package/src/vendor/hljs-xml.vendor.js +2 -0
  495. package/src/vendor/hyper-html-api-data.vendor.js +1 -1
  496. package/src/vendor/hyper-html-api-upgrade.vendor.js +5 -0
  497. package/src/vendor/hyper-morph-identity.vendor.js +2 -0
  498. package/src/vendor/hyper-morph-json.vendor.js +3 -0
  499. package/src/vendor/hyper-morph.vendor.js +6 -6
  500. package/src/vendor/hyper-undo.vendor.js +1 -1
  501. package/src/vendor/hypercms.vendor.js +169 -84
  502. package/src/vendor/marked.vendor.js +68 -0
  503. package/src/vendor/overtype.vendor.js +994 -0
  504. package/src/vendor/richclay.vendor.js +17 -17
  505. package/src/vendor/sap-core.vendor.js +5 -5
  506. package/src/vendor/sap-ownership.vendor.js +11 -7
@@ -0,0 +1,535 @@
1
+ /**
2
+ * saved-stream.js — the receive-only transport a reader follows saved files with.
3
+ *
4
+ * A reader has no write half. It needs the saved lane's startup GET, its frames and
5
+ * its reconnect repair, and nothing else: no POST, no PATCH, no live relay, no page
6
+ * store to admit anything into, and no write credentials. So this is a transport of
7
+ * its own rather than a mode of live-sync.js, and it imports neither the save lane,
8
+ * the live lane, the snapshot pipeline, the merge engine nor the editor.
9
+ *
10
+ * It owns a REPRESENTED RECEIPT — `{ bytes, etag, seq }` — and moves it only when
11
+ * `onSnapshot` acknowledges a complete apply. `bytes` is one unmodified HTML string
12
+ * from an accepted frame or GET, `etag` belongs to those bytes, and `seq` is the
13
+ * resume floor that apply is known to cover. Transport watermarks never become part
14
+ * of it: a cursor says what the stream will replay, not what this page holds.
15
+ *
16
+ * Branches, spelled out because each one is a way to show a reader a file it does
17
+ * not hold or to hide one it does:
18
+ *
19
+ * - a full frame with a newer sequence validates and applies; a duplicate or an
20
+ * older one is ignored without moving the receipt;
21
+ * - a frame with no sequence (an unstamped legacy host) is processed in arrival
22
+ * order and keeps `seq: null` rather than inventing one;
23
+ * - a cursor with `resync: true`, the startup version check and any content-less
24
+ * external-change or ETag notice refetch the served file with `cache: 'no-store'`;
25
+ * a stamp alone never repairs missing bytes, and a notice never moves the receipt;
26
+ * - a cursor jump without resync is transport bookkeeping only: other lanes consume
27
+ * numbers, so a gap does not prove a content frame is missing;
28
+ * - every host sends a sequence, so that orders the frames: a newer one applies and
29
+ * one the receipt already covers is ignored. A stamp confirms identity only when
30
+ * both sides carry one;
31
+ * - a frame repeating the bytes the article holds moves the receipt and repaints
32
+ * nothing;
33
+ * - after a GET the cursor is a covered replay floor, not an ordering of versions.
34
+ * The fetched body's version is unconfirmed until a frame names it, and a frame
35
+ * naming another stamp refetches rather than overwrite bytes that may be newer. A
36
+ * host that stamps neither side is ordered by sequence;
37
+ * - a body read while frames were still arriving is discarded in favour of one the
38
+ * stream can place, but only twice: a steady writer would otherwise starve the
39
+ * reader of every fetched body;
40
+ * - a bad frame, a refused apply or a failed GET keeps the article and the receipt
41
+ * and reports a recoverable status; it never pretends the file was acknowledged;
42
+ * - stop aborts and invalidates every outstanding continuation, and nothing calls
43
+ * back after it.
44
+ */
45
+
46
+ import { SyncStream } from './stream.js';
47
+ import { resolveWireProfile } from './wire-profile.js';
48
+
49
+ const LANE = 'saved';
50
+ const MAX_DISCARDS = 2;
51
+
52
+ function mintClientId() {
53
+ return Math.random().toString(36).slice(2, 11) + Date.now().toString(36);
54
+ }
55
+
56
+ function mintResumeId() {
57
+ try {
58
+ const bytes = new Uint8Array(16);
59
+ (globalThis.crypto || window.crypto).getRandomValues(bytes);
60
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
61
+ } catch {
62
+ return Math.random().toString(36).slice(2) + Date.now().toString(36);
63
+ }
64
+ }
65
+
66
+ /**
67
+ * The version a fetched served document names on its own root, or null. Read from
68
+ * the body, never from discovery: discovery answers about a later moment than any
69
+ * response. Parsed detached, so this touches the live page nowhere.
70
+ */
71
+ function readBodyStamp(html) {
72
+ if (typeof html !== 'string' || html === '') return null;
73
+ const parsed = new DOMParser().parseFromString(html, 'text/html');
74
+ return parsed.documentElement?.getAttribute('documentetag') || null;
75
+ }
76
+
77
+ const stampOf = (value) => (typeof value === 'string' && value ? value : null);
78
+
79
+ /**
80
+ * The page URL every request in this module resolves against, or null when the page
81
+ * has no host to follow. `file://` and a sandboxed frame have an opaque origin:
82
+ * `window.location.origin` is the string `"null"` there and `new URL` refuses it as
83
+ * a base, and a `file:` page URL names no server either way. Opening a stream on
84
+ * such a page is not a failure to report, it is a request that does not exist.
85
+ */
86
+ function resolvePageURL(documentURL) {
87
+ const origin = window.location.origin;
88
+ // The string `"null"` is what an opaque origin reports, and `new URL` refuses it
89
+ // as a base before any `file:` page URL is even read.
90
+ if (typeof origin !== 'string' || origin === '' || origin === 'null') return null;
91
+ const url = new URL(documentURL || window.location.href, origin);
92
+ return url.protocol === 'http:' || url.protocol === 'https:' ? url.href : null;
93
+ }
94
+
95
+ const seqOf = (value) => (Number.isSafeInteger(value) ? value : null);
96
+
97
+ function normalizeReceipt(represented) {
98
+ if (represented == null) return Object.freeze({ bytes: null, etag: null, seq: null });
99
+ if (typeof represented.bytes !== 'string' || represented.bytes === '') {
100
+ throw new TypeError('createSavedStream: represented.bytes must be a full HTML string');
101
+ }
102
+ return Object.freeze({
103
+ bytes: represented.bytes,
104
+ etag: stampOf(represented.etag),
105
+ seq: seqOf(represented.seq),
106
+ });
107
+ }
108
+
109
+ /**
110
+ * Follow saved frames for one reader.
111
+ *
112
+ * `onSnapshot(candidate)` runs synchronously and returns true only after the whole
113
+ * candidate applied, so the receipt moves on acknowledgement and nowhere else. The
114
+ * page URL is resolved against the real origin, never `document.baseURI`: a `<base>`
115
+ * in the authored document must not choose where the reader streams or fetches.
116
+ *
117
+ * @param {Object} options
118
+ * @param {string} [options.documentURL] - The page whose saved file to follow.
119
+ * @param {?string} [options.servedEtag] - The stamp this response was served with.
120
+ * @param {?{bytes: string, etag: ?string, seq: ?number}} [options.represented]
121
+ * @param {function({bytes: string, etag: ?string, seq: ?number}): boolean} options.onSnapshot
122
+ * @param {function(Object)} [options.onStatus]
123
+ * @returns {{start: function(): Object, adopt: function(Object): Object, stop: function(), ready: ?Promise, represented: Object}}
124
+ */
125
+ export function createSavedStream({ documentURL, servedEtag = null, represented = null, onSnapshot, onStatus } = {}) {
126
+ if (typeof onSnapshot !== 'function') {
127
+ throw new TypeError('createSavedStream: onSnapshot must be a function');
128
+ }
129
+
130
+ const pageURL = resolvePageURL(documentURL);
131
+ const startupCheck = Boolean(stampOf(servedEtag));
132
+
133
+ let receipt = normalizeReceipt(represented);
134
+ // An opaque origin — `file://`, a sandboxed frame — has no host to stream from, and no
135
+ // URL to resolve one against: there is no stream, and the reader holds the served
136
+ // article alone. Returning an inert handle keeps that a no-stream rather than an
137
+ // exception on the way to one.
138
+ if (pageURL === null) {
139
+ const idle = {
140
+ ready: null,
141
+ get represented() {
142
+ return receipt;
143
+ },
144
+ adopt() {
145
+ return idle;
146
+ },
147
+ start() {
148
+ idle.ready = Promise.resolve(idle);
149
+ return idle;
150
+ },
151
+ stop() {},
152
+ };
153
+ return idle;
154
+ }
155
+ // Whether the receipt's version is known to sit at the stream's position, so a
156
+ // newer frame applies straight from the stream. An accepted GET body leaves this
157
+ // false until a frame names its stamp.
158
+ let matched = true;
159
+ let watermark = receipt.seq ?? 0;
160
+ let arrivals = 0;
161
+ let discards = 0;
162
+ // Where an acknowledged apply goes: the caller's `onSnapshot`, or the reader that
163
+ // adopted this stream at acquisition (`adopt`).
164
+ let sink = onSnapshot;
165
+ let notify = onStatus;
166
+ let generation = 0;
167
+ let stopped = false;
168
+ let stream = null;
169
+ let repairJob = null;
170
+ let pendingRepair = null;
171
+ let repairPaused = false;
172
+ // The roster this reader draws arrives as a named `presence` frame on this same
173
+ // stream — there is no second connection — and the store behind it is a first-use
174
+ // download, so a host that names nobody costs a reader nothing. A reader has no
175
+ // write half, so the lease it takes cannot publish at all: its send refuses, and no
176
+ // request leaves for a target a page tried to set in view mode.
177
+ let presenceStore = null;
178
+ let presenceLease = null;
179
+ let presenceLoad = null;
180
+ const presenceQueue = [];
181
+
182
+ const active = (gen) => !stopped && gen === generation;
183
+
184
+ function report(state, detail = {}) {
185
+ if (stopped || typeof notify !== 'function') return;
186
+ notify({ state, ...detail });
187
+ }
188
+
189
+ function leavePresence() {
190
+ presenceQueue.length = 0;
191
+ if (presenceLease) {
192
+ presenceLease.detach();
193
+ presenceLease = null;
194
+ }
195
+ presenceStore = null;
196
+ presenceLoad = null;
197
+ }
198
+
199
+ // A frame that lands while the download is in flight waits in order, and a
200
+ // download that failed keeps the queue so the next frame asks again.
201
+ function takePresence(data) {
202
+ if (presenceStore) {
203
+ presenceStore.receive(data);
204
+ return;
205
+ }
206
+ presenceQueue.push(data);
207
+ if (presenceLoad) return;
208
+ presenceLoad = import('./presence-store.js')
209
+ .then(({ presence }) => {
210
+ presenceStore = presence;
211
+ presenceLease = presence.attachTransport({ send: () => Promise.resolve(false) });
212
+ for (const frame of presenceQueue.splice(0)) presence.receive(frame);
213
+ return presence;
214
+ })
215
+ .catch(() => {
216
+ presenceLoad = null;
217
+ return null;
218
+ });
219
+ }
220
+
221
+ function closeStream() {
222
+ leavePresence();
223
+ if (!stream) return;
224
+ const closing = stream;
225
+ stream = null;
226
+ closing.onopen = null;
227
+ closing.onerror = null;
228
+ closing.onmessage = null;
229
+ closing.close();
230
+ }
231
+
232
+ function abortRepair() {
233
+ if (!repairJob) return;
234
+ const job = repairJob;
235
+ repairJob = null;
236
+ job.controller.abort();
237
+ }
238
+
239
+ /**
240
+ * Record one accepted body. The only writer of `receipt`, and it writes only when
241
+ * the candidate applied completely.
242
+ */
243
+ function accept(candidate) {
244
+ if (stopped) return false;
245
+ const next = {
246
+ bytes: candidate.bytes,
247
+ etag: stampOf(candidate.etag),
248
+ seq: seqOf(candidate.seq),
249
+ };
250
+ if (typeof next.bytes !== 'string' || next.bytes === '') {
251
+ report('recoverable', { reason: 'invalid-bytes', seq: next.seq });
252
+ return false;
253
+ }
254
+ if (next.bytes === receipt.bytes && next.etag === receipt.etag && next.seq === receipt.seq) {
255
+ return true;
256
+ }
257
+ let applied = false;
258
+ try {
259
+ applied = sink(Object.freeze(next)) === true;
260
+ } catch {
261
+ applied = false;
262
+ }
263
+ if (!applied) {
264
+ report('recoverable', { reason: 'refused', seq: next.seq, etag: next.etag });
265
+ return false;
266
+ }
267
+ receipt = Object.freeze(next);
268
+ report('applied', { seq: receipt.seq, etag: receipt.etag });
269
+ return true;
270
+ }
271
+
272
+ function latestSeq(reason, kind) {
273
+ if (kind === 'startup') return null;
274
+ const candidate = Math.max(reason.seq ?? 0, watermark);
275
+ return candidate > 0 ? candidate : null;
276
+ }
277
+
278
+ function deferRepair(reason, why) {
279
+ pendingRepair = reason;
280
+ repairPaused = true;
281
+ report('recoverable', { reason: why, seq: reason.seq ?? null });
282
+ }
283
+
284
+ function requestRepair(reason) {
285
+ if (stopped) return;
286
+ pendingRepair = { kind: reason.kind, seq: seqOf(reason.seq) };
287
+ repairPaused = false;
288
+ runRepair();
289
+ }
290
+
291
+ function runRepair() {
292
+ if (stopped || repairJob || repairPaused || !pendingRepair) return;
293
+ const gen = generation;
294
+ const reason = pendingRepair;
295
+ pendingRepair = null;
296
+ const controller = new AbortController();
297
+ repairJob = { controller };
298
+ const arrivalsAtStart = arrivals;
299
+ const watermarkAtStart = watermark;
300
+
301
+ const settle = (body) => {
302
+ if (repairJob && repairJob.controller === controller) repairJob = null;
303
+ if (!active(gen) || controller.signal.aborted) return;
304
+ if (body == null) {
305
+ deferRepair(reason, 'repair-failed');
306
+ return;
307
+ }
308
+ if (arrivals !== arrivalsAtStart || watermark !== watermarkAtStart) {
309
+ // Something arrived while this body was read, so it may describe a page
310
+ // this reader has already left. A reason queued by the arrival is newer
311
+ // and owns the next attempt; otherwise ask again from the latest watermark.
312
+ discards += 1;
313
+ if (discards <= MAX_DISCARDS) {
314
+ pendingRepair = pendingRepair ?? { kind: reason.kind, seq: latestSeq(reason, reason.kind) };
315
+ runRepair();
316
+ return;
317
+ }
318
+ // Twice is as long as a reader waits: under a steady writer every body is
319
+ // read while a frame lands, so the third is applied, not dropped.
320
+ acceptBody(body.text, { kind: reason.kind, seq: latestSeq(reason, reason.kind) }, body.etag);
321
+ runRepair();
322
+ return;
323
+ }
324
+ acceptBody(body.text, reason, body.etag);
325
+ runRepair();
326
+ };
327
+
328
+ fetch(pageURL, { cache: 'no-store', signal: controller.signal })
329
+ .then((response) =>
330
+ response.ok
331
+ ? response.text().then((text) => ({ text, etag: stampOf(response.headers?.get?.('etag')) }))
332
+ : null
333
+ )
334
+ .then(settle)
335
+ .catch(() => settle(null));
336
+ }
337
+
338
+ function acceptBody(html, reason, responseEtag) {
339
+ // The response's own stamp names the body when the host sends one, otherwise the
340
+ // served root attribute does. A discovery stamp is never attached, and identical
341
+ // bytes already named by a stamp keep that name.
342
+ const bodyStamp = stampOf(responseEtag) ?? readBodyStamp(html);
343
+ const etag = bodyStamp ?? (html === receipt.bytes ? receipt.etag : null);
344
+ const seq = reason.kind === 'startup' ? receipt.seq : reason.seq ?? receipt.seq;
345
+ if (!accept({ bytes: html, etag, seq })) return;
346
+ // This body is the baseline now, but its version stands at the stream's position
347
+ // only once a frame names it: until then another stamp refetches.
348
+ matched = false;
349
+ discards = 0;
350
+ repairPaused = false;
351
+ report('current', { seq: receipt.seq, etag: receipt.etag });
352
+ }
353
+
354
+ // A frame repeating the bytes on screen: the receipt moves, nothing repaints.
355
+ function advance(etag, seq) {
356
+ const next = {
357
+ bytes: receipt.bytes,
358
+ etag: etag ?? receipt.etag,
359
+ seq: seq ?? receipt.seq,
360
+ };
361
+ if (next.etag === receipt.etag && next.seq === receipt.seq) return;
362
+ receipt = Object.freeze(next);
363
+ report('current', { seq: receipt.seq, etag: receipt.etag });
364
+ }
365
+
366
+ function onFrame(bytes, rawEtag, seq) {
367
+ const etag = stampOf(rawEtag);
368
+ // An older sequence is a version this reader has already left: it never rewinds.
369
+ if (seq !== null && receipt.seq !== null && seq <= receipt.seq) return;
370
+ // A frame naming the represented stamp places it at the stream's position, so
371
+ // the frames after it apply straight from the stream again.
372
+ if (!matched && etag !== null && receipt.etag !== null) {
373
+ if (etag !== receipt.etag) {
374
+ requestRepair({ kind: 'unmatched', seq });
375
+ return;
376
+ }
377
+ matched = true;
378
+ }
379
+ if (bytes === receipt.bytes) {
380
+ advance(etag, seq);
381
+ return;
382
+ }
383
+ if (accept({ bytes, etag, seq })) matched = true;
384
+ }
385
+
386
+ function onNotification(data, seq) {
387
+ const info = data.data;
388
+ if (info && info.kind === 'external-change') {
389
+ if (typeof info.html === 'string') onFrame(info.html, info.etag, seq);
390
+ else requestRepair({ kind: 'notice', seq });
391
+ return;
392
+ }
393
+ if (typeof data.msg === 'string' && data.msg.endsWith('changed on disk outside this tab')) {
394
+ requestRepair({ kind: 'notice', seq });
395
+ }
396
+ }
397
+
398
+ function onMessage(raw, gen) {
399
+ if (!active(gen)) return;
400
+ arrivals++;
401
+ let data;
402
+ try {
403
+ data = JSON.parse(raw);
404
+ } catch {
405
+ report('recoverable', { reason: 'bad-json' });
406
+ return;
407
+ }
408
+ if (!data || typeof data !== 'object' || Array.isArray(data)) {
409
+ report('recoverable', { reason: 'bad-frame' });
410
+ return;
411
+ }
412
+ const seq = seqOf(data.seq);
413
+ if (seq !== null && seq > watermark) watermark = seq;
414
+ if (data.type === 'notification') {
415
+ onNotification(data, seq);
416
+ return;
417
+ }
418
+ if (data.error) {
419
+ report('recoverable', { reason: 'server-error', seq });
420
+ return;
421
+ }
422
+ if (typeof data.html !== 'string') {
423
+ // A stamp with no document proves nothing about these bytes: refetch instead.
424
+ if (stampOf(data.etag)) requestRepair({ kind: 'notice', seq });
425
+ else report('recoverable', { reason: 'bad-frame', seq });
426
+ return;
427
+ }
428
+ onFrame(data.html, data.etag, seq);
429
+ }
430
+
431
+ function onCursor(raw, gen) {
432
+ if (!active(gen)) return;
433
+ arrivals++;
434
+ let data;
435
+ try {
436
+ data = JSON.parse(raw);
437
+ } catch {
438
+ return;
439
+ }
440
+ if (!data || typeof data !== 'object') return;
441
+ const seq = seqOf(data.seq);
442
+ if (seq !== null && seq > watermark) watermark = seq;
443
+ if (data.resync === true) {
444
+ requestRepair({ kind: 'resync', seq });
445
+ return;
446
+ }
447
+ if (data.startup === true) requestRepair({ kind: 'startup', seq: null });
448
+ }
449
+
450
+ function onOpen(gen) {
451
+ if (!active(gen)) return;
452
+ report('open', { seq: receipt.seq, etag: receipt.etag });
453
+ if (repairPaused) {
454
+ repairPaused = false;
455
+ runRepair();
456
+ return;
457
+ }
458
+ // No served stamp means no provenance to check, so nothing here asks the
459
+ // version question. A reader with no bytes at all has to ask for them, or a
460
+ // quiet file would leave it with nothing to show.
461
+ if (receipt.bytes === null && !startupCheck) requestRepair({ kind: 'startup', seq: null });
462
+ }
463
+
464
+ const handle = {
465
+ ready: null,
466
+ get represented() {
467
+ return receipt;
468
+ },
469
+ // Take this stream over: a reader starting from an acquisition body adopts the
470
+ // stream that fetched it, so one connection carries the file from then on.
471
+ adopt({ onSnapshot: nextSink, onStatus: nextNotify } = {}) {
472
+ if (typeof nextSink === 'function') sink = nextSink;
473
+ if (typeof nextNotify === 'function') notify = nextNotify;
474
+ return handle;
475
+ },
476
+ start() {
477
+ stopped = false;
478
+ const gen = ++generation;
479
+ closeStream();
480
+ abortRepair();
481
+ pendingRepair = null;
482
+ repairPaused = false;
483
+ arrivals = 0;
484
+ discards = 0;
485
+ watermark = receipt.seq ?? 0;
486
+ handle.ready = resolveWireProfile().then((resolved) => {
487
+ if (!active(gen)) return handle;
488
+ const resumeId = mintResumeId();
489
+ const clientId = mintClientId();
490
+ const path = resolved.profile.streamPath(pageURL, LANE, resumeId, clientId);
491
+ // Resolved against the real origin: a <base href> would otherwise point
492
+ // the reader's stream at an origin the authored document chose.
493
+ const opened = new SyncStream(new URL(path, window.location.origin).href, {
494
+ shared: resolved.sharedSync,
495
+ sharedData: resolved.sharedData,
496
+ documentURL: pageURL,
497
+ lane: LANE,
498
+ clientId,
499
+ startupCheck,
500
+ });
501
+ stream = opened;
502
+ opened.onopen = () => onOpen(gen);
503
+ opened.onerror = () => { if (active(gen)) report('reconnecting'); };
504
+ opened.onmessage = (event) => onMessage(event.data, gen);
505
+ opened.addEventListener('cursor', (event) => onCursor(event.data, gen));
506
+ // Named, so it never reaches `onmessage` and never looks like the saved file
507
+ // it is not: who is present and the file a reader holds are separate
508
+ // questions, and a roster frame moves no receipt and no resume cursor.
509
+ opened.addEventListener('presence', (event) => {
510
+ if (!active(gen)) return;
511
+ let data;
512
+ try {
513
+ data = JSON.parse(event.data);
514
+ } catch {
515
+ return;
516
+ }
517
+ if (data && typeof data === 'object' && !Array.isArray(data)) takePresence(data);
518
+ });
519
+ return handle;
520
+ });
521
+ return handle;
522
+ },
523
+ stop() {
524
+ if (stopped) return;
525
+ stopped = true;
526
+ generation++;
527
+ closeStream();
528
+ abortRepair();
529
+ pendingRepair = null;
530
+ repairPaused = false;
531
+ },
532
+ };
533
+
534
+ return handle;
535
+ }
@@ -0,0 +1,92 @@
1
+ // Two versions of one document never merge. A copy of a starter template carries
2
+ // `<meta name="hyper-version">`, and an upgrade replaces layout, code and data
3
+ // together; morphing that frame into a tab still showing the previous version would
4
+ // interleave two templates, so the frame is refused whole and the tab is told to
5
+ // reload instead.
6
+ //
7
+ // Only a page that states its own version can make this judgement, and only a frame
8
+ // that states one can disagree: a document without the meta behaves exactly as it did
9
+ // before, which is every page that is not a template copy.
10
+ //
11
+ // Holding the saves is not optional once the frame is refused. This tab's DOM is now
12
+ // a version the host no longer stores, and its next save would write that version
13
+ // back over the upgrade with no refusal to stop it.
14
+ import { isEditMode } from "../core/is-edit-mode.js";
15
+
16
+ export const UPGRADED_MESSAGE = "This document was upgraded.";
17
+
18
+ let stopped = false;
19
+
20
+ function metaOf(root, name) {
21
+ const el = root.querySelector(`meta[name="${name}"]`);
22
+ const value = el ? el.getAttribute("content") : null;
23
+ return value && value.trim() ? value.trim() : null;
24
+ }
25
+
26
+ /** This page's own version, or null when it does not state one. */
27
+ export function pageVersion(doc = document) {
28
+ return metaOf(doc, "hyper-version");
29
+ }
30
+
31
+ /** The version a saved frame carries, or null when it carries none. */
32
+ export function frameVersion(html) {
33
+ if (typeof html !== "string" || !html.includes("hyper-version")) return null;
34
+ return metaOf(new DOMParser().parseFromString(html, "text/html"), "hyper-version");
35
+ }
36
+
37
+ // One version is not another. A page that states none can make no such judgement.
38
+ function replacesMine(theirs) {
39
+ const mine = pageVersion();
40
+ return Boolean(mine) && Boolean(theirs) && theirs !== mine;
41
+ }
42
+
43
+ /**
44
+ * Whether this frame must not be applied: it names a version other than this page's,
45
+ * or an earlier frame already said so and nothing this tab holds is trustworthy since.
46
+ */
47
+ export function upgradeReplaces(html) {
48
+ if (stopped) return true;
49
+ return replacesMine(frameVersion(html));
50
+ }
51
+
52
+ /**
53
+ * The same judgement for an incoming document somebody else already parsed: the
54
+ * reader holds the file's own tree and reads its head from there.
55
+ */
56
+ export function upgradeReplacesDocument(other) {
57
+ if (stopped) return true;
58
+ if (!other || typeof other.querySelector !== "function") return false;
59
+ return replacesMine(metaOf(other, "hyper-version"));
60
+ }
61
+
62
+ /**
63
+ * Refuse this document from here on: every save is held, every later frame is
64
+ * dropped, and the notice system is asked to show the reload prompt.
65
+ *
66
+ * @returns {Promise<void>} settles once the hold is in place
67
+ */
68
+ export function stopForUpgrade() {
69
+ if (stopped) return Promise.resolve();
70
+ stopped = true;
71
+ // The save lane is edit mode's, and a reader must not download it for a hold it
72
+ // has no saves to apply. Already loaded wherever this can matter, so this is the
73
+ // module's own copy rather than a fetch.
74
+ const hold = isEditMode
75
+ ? import("../core/save.js").then((save) => save.holdAllSaves(UPGRADED_MESSAGE), () => {})
76
+ : Promise.resolve();
77
+ // Where the shell (core/conflict-notice.js) is loaded, it is already listening and
78
+ // fetches the notice for this event. A reader has no shell, so the notice is fetched
79
+ // here and handed the event it would otherwise never see.
80
+ const event = new CustomEvent("clay:document-upgraded", { detail: { message: UPGRADED_MESSAGE } });
81
+ const drawn = isEditMode
82
+ ? Promise.resolve()
83
+ : import("../core/conflict-notice-ui.js").then((ui) => ui.start(event), () => {});
84
+ document.dispatchEvent(event);
85
+ return Promise.all([hold, drawn]).then(() => {});
86
+ }
87
+
88
+ export const _test = {
89
+ reset() {
90
+ stopped = false;
91
+ },
92
+ };