@panphora/clayjs 1.15.1 → 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
@@ -1,8 +1,20 @@
1
1
  import { schema } from "../schema.js";
2
+ import { entriesOfYjs, isOpaque, opaqueMark, yjsFields } from "../format-codec.js";
2
3
 
3
4
  export const markName = (k) => k.split("!")[0].split("~")[0];
4
5
  export const isCancel = (k) => k.includes("!");
5
6
  export const marksAttrs = (marks) => { const o = {}; for (const m of marks) o[m.type.name] = m.attrs && Object.keys(m.attrs).length ? { ...m.attrs } : true; return o; };
7
+ // The Yjs fields a set of editor marks stands for. An opaque mark is not a live
8
+ // key of its own: its named descriptors go under their own names and its compact
9
+ // tokens under the reserved one.
10
+ export const marksFields = (marks) => {
11
+ const o = {};
12
+ for (const m of marks) {
13
+ if (isOpaque(m)) { for (const [key, value] of Object.entries(yjsFields(m.attrs.entries))) o[key] = JSON.parse(JSON.stringify(value)); continue; }
14
+ o[m.type.name] = m.attrs && Object.keys(m.attrs).length ? { ...m.attrs } : true;
15
+ }
16
+ return o;
17
+ };
6
18
  export const marksKey = (marks) => marks.map((m) => m.type.name + (m.attrs && Object.keys(m.attrs).length ? JSON.stringify(m.attrs) : "")).sort().join("|");
7
19
  export function marksFromAttributes(a, blockType) {
8
20
  if (blockType === "code_block") return [];
@@ -14,5 +26,17 @@ export function marksFromAttributes(a, blockType) {
14
26
  if (v === null || v === undefined || isCancel(k) || cancelled.has(k)) continue;
15
27
  best.set(markName(k), v);
16
28
  }
17
- return [...best].filter(([n]) => schema.marks[n]).map(([n, v]) => schema.marks[n].create(v === true ? null : v));
29
+ const marks = [];
30
+ let unknown = null;
31
+ for (const [n, v] of best) {
32
+ if (schema.marks[n]) marks.push(schema.marks[n].create(v === true ? null : v));
33
+ else (unknown ??= {})[n] = v;
34
+ }
35
+ // The walk asks this of every text run on every keystroke, and almost none carry a
36
+ // mark from a newer version, so the codec runs only when one is there.
37
+ if (unknown) {
38
+ const opaque = entriesOfYjs(unknown);
39
+ if (opaque.length) marks.push(opaqueMark(opaque));
40
+ }
41
+ return marks;
18
42
  }
@@ -1,10 +1,11 @@
1
1
  import * as Y from "../../vendor/yjs.vendor.js";
2
2
  import { schema } from "../schema.js";
3
3
  import { nodeFromBlock } from "../model.js";
4
- import { flatten, marksOf } from "../inline.js";
4
+ import { flatten, markKey, marksOf } from "../inline.js";
5
5
  import { ATOM, BREAK, DEFAULTS, blocksOfValues, orderedIds, unitsOf, runsOf, canon, hash64, parseModel } from "../projection.js";
6
6
  import { blockFromItem } from "../adopt.js";
7
7
  import { createPageDoc, attrKeys, defaultOf, SEED } from "./layout.js";
8
+ import { marksFields } from "./marks.js";
8
9
  import { blocksFromYValue } from "./decode.js";
9
10
  import { PART, enc } from "./provider.js";
10
11
  import { boardKey } from "./board.js";
@@ -34,13 +35,54 @@ function sourceBlocks(values) {
34
35
  return blocksOfValues(values);
35
36
  }
36
37
 
38
+ // A mark key as the record keeps it: the codec validates every descriptor, and the
39
+ // known codes and the opaque ones come back in the order the binding writes them.
40
+ const canonicalKey = (key, id) => {
41
+ let marks;
42
+ try { marks = marksOf(schema, key); } catch { return fail(`Invalid mark key on block: ${id}`); }
43
+ return markKey([...marks].sort((a, b) => a.type.rank - b.type.rank));
44
+ };
45
+
46
+ const markUnits = (block, id) => {
47
+ const units = unitsOf(block.m, block.c.length);
48
+ if (!units) fail(`Invalid mark runs on block: ${id}`);
49
+ for (let i = 0; i < units.length; i++) {
50
+ const key = units[i];
51
+ if (typeof key !== "string") fail(`Invalid mark key on block: ${id}`);
52
+ if (key.startsWith("@") && (key !== BREAK || block.c[i] !== ATOM)) fail(`Unsupported inline atom on block: ${id}`);
53
+ if (block.t === "code_block" && key) fail(`Unsupported formatting in code block: ${id}`);
54
+ if (!key.startsWith("@")) units[i] = canonicalKey(key, id);
55
+ }
56
+ return units;
57
+ };
58
+
59
+ // A block type this runtime has no schema for keeps the record it stands for: its
60
+ // own type, its text and marks and every other field a newer peer wrote on it.
61
+ function opaqueRecord(id, block, pos) {
62
+ const out = { t: block.t, pos };
63
+ for (const key of Object.keys(block)) {
64
+ if (key === "t" || key === "pos" || key === "c" || key === "m") continue;
65
+ out[key] = copy(block[key]);
66
+ }
67
+ if (block.c != null && typeof block.c !== "string") fail(`Invalid text on block: ${id}`);
68
+ out.c = block.c ?? "";
69
+ if (typeof block.c === "string" && block.m != null) {
70
+ const units = markUnits(block, id);
71
+ if (units.some(Boolean)) out.m = runsOf(units);
72
+ } else if (block.m != null) fail(`Mark runs require flat text on block: ${id}`);
73
+ return out;
74
+ }
75
+
37
76
  function normalizeBlock(id, block, index) {
38
- if (!Object.hasOwn(DEFAULTS, block.t)) fail(`Unsupported hybrid block type: ${block.t}`);
77
+ const known = Object.hasOwn(DEFAULTS, block.t);
78
+ if (!known && (typeof block.t !== "string" || !block.t || block.t === "opaque_block")) fail(`Unsupported hybrid block type: ${block.t}`);
79
+ const pos = String(index + 1).padStart(8, "0");
80
+ if (!known) return opaqueRecord(id, block, pos);
39
81
  const type = schema.nodes[block.t];
40
82
  const fields = attrKeys(type);
41
83
  const allowed = new Set(["t", "pos", "c", "m", ...fields, ...(block.t === "table" ? ["rows"] : [])]);
42
- for (const key of Object.keys(block)) if (!allowed.has(key)) fail(`Unsupported field on block ${id}: ${key}`);
43
- const out = { t: block.t, pos: String(index + 1).padStart(8, "0") };
84
+ const out = { t: block.t, pos };
85
+ for (const key of Object.keys(block)) if (!allowed.has(key)) out[key] = copy(block[key]);
44
86
  for (const key of fields) {
45
87
  const value = block[key] === undefined ? defaultOf(type, key) : block[key];
46
88
  if (canon(value) !== canon(defaultOf(type, key))) out[key] = copy(value);
@@ -56,14 +98,7 @@ function normalizeBlock(id, block, index) {
56
98
  }
57
99
  if (block.c != null && typeof block.c !== "string" && !Array.isArray(block.c)) fail(`Invalid text on block: ${id}`);
58
100
  if (typeof block.c === "string" && block.m != null) {
59
- const units = unitsOf(block.m, block.c.length);
60
- if (!units) fail(`Invalid mark runs on block: ${id}`);
61
- for (let i = 0; i < units.length; i++) {
62
- const key = units[i];
63
- if (typeof key !== "string") fail(`Invalid mark key on block: ${id}`);
64
- if (key.startsWith("@") && (key !== BREAK || block.c[i] !== ATOM)) fail(`Unsupported inline atom on block: ${id}`);
65
- if (block.t === "code_block" && key) fail(`Unsupported formatting in code block: ${id}`);
66
- }
101
+ markUnits(block, id);
67
102
  } else if (block.m != null) fail(`Mark runs require flat text on block: ${id}`);
68
103
  const node = nodeFromBlock(schema, id, block);
69
104
  node.check();
@@ -107,7 +142,7 @@ export function buildPageValue(values, clientID) {
107
142
  if (key === BREAK) {
108
143
  for (let i = 0; i < length; i++) pd.flow.insertEmbed(pd.flow.length, { br: 1 }, {});
109
144
  } else {
110
- const attrs = Object.fromEntries(marksOf(schema, key).map((mark) => [`${mark.type.name}~${clientID}-m${(op++).toString(36)}`, Object.keys(mark.attrs).length ? copy(mark.attrs) : true]));
145
+ const attrs = Object.fromEntries(Object.entries(marksFields(marksOf(schema, key))).map(([name, value]) => [`${name}~${clientID}-m${(op++).toString(36)}`, value && typeof value === "object" ? copy(value) : value]));
111
146
  pd.flow.insert(pd.flow.length, c.slice(offset, offset + length), attrs);
112
147
  }
113
148
  offset += length;
@@ -59,6 +59,7 @@ const MAX_INDENT = 8;
59
59
  const ATTR_RULES = {
60
60
  indent: (value, block) => block.t === "list_item" && Number.isInteger(value) && value >= 0 && value <= MAX_INDENT,
61
61
  open: (value, block) => block.t === "toggle" && typeof value === "boolean",
62
+ parent: (value, block, byId) => value === null || (typeof value === "string" && byId.get(value)?.t === "toggle" && !inSubtree(byId, value, block.id)),
62
63
  };
63
64
 
64
65
  const attrChanges = (args, byId) => {
@@ -69,7 +70,7 @@ const attrChanges = (args, byId) => {
69
70
  const block = byId.get(id);
70
71
  if (!block || !fields || typeof fields !== "object" || Array.isArray(fields)) return null;
71
72
  for (const [field, value] of Object.entries(fields)) {
72
- if (!ATTR_RULES[field] || !ATTR_RULES[field](value, block)) return null;
73
+ if (!ATTR_RULES[field] || !ATTR_RULES[field](value, block, byId)) return null;
73
74
  out.push({ id, field, value });
74
75
  }
75
76
  }
@@ -118,6 +119,9 @@ export function moveBlocks({ pd, view, um, origins, render }, args = {}) {
118
119
  const hasParent = args?.parent !== undefined;
119
120
 
120
121
  const current = walk(pd, origins);
122
+ // An opaque block is an operation this runtime must refuse before writing: it
123
+ // has no turn-into, it cannot be carried, and its record is never rewritten.
124
+ const protectedIds = new Set(current.visible.filter((seg) => seg.opaque).map((seg) => seg.id));
121
125
  const list = visibleBlocks(pd, current, view);
122
126
  const byId = new Map(list.map((block) => [block.id, block]));
123
127
  const order = list.map((block) => block.id);
@@ -126,10 +130,12 @@ export function moveBlocks({ pd, view, um, origins, render }, args = {}) {
126
130
  for (const id of ids) if (!byId.has(id)) return refuse("missing-source");
127
131
  const changes = attrChanges(args, byId);
128
132
  if (!changes) return refuse("invalid-attrs");
133
+ const parentChanges = changes.filter((change) => change.field === "parent");
129
134
 
130
135
  const roots = new Set(ids.filter((id) => !ids.some((other) => other !== id && isAncestor(byId, other, id))));
131
136
  const selected = list.filter((block) => ids.some((root) => inSubtree(byId, block.id, root)));
132
137
  const selectedSet = new Set(selected.map((block) => block.id));
138
+ if (selected.some((block) => protectedIds.has(block.id)) || changes.some((change) => protectedIds.has(change.id))) return refuse("unsupported-format");
133
139
 
134
140
  const segments = new Map(current.visible.map((seg) => [seg.id, seg]));
135
141
  for (const block of selected) {
@@ -174,14 +180,33 @@ export function moveBlocks({ pd, view, um, origins, render }, args = {}) {
174
180
  const movedIds = selected.map((block) => block.id);
175
181
  if (sameOrder && !movedRoots.length && !changes.length) return { status: "unchanged" };
176
182
 
183
+ const caret = (() => {
184
+ const sel = view?.state.selection;
185
+ if (!(sel instanceof pmState.TextSelection)) return null;
186
+ const at = (pos) => { const $pos = view.state.doc.resolve(pos); return $pos.depth ? { id: $pos.node(1).attrs.id, offset: $pos.parentOffset } : null; };
187
+ const anchor = at(sel.anchor), head = at(sel.head);
188
+ return anchor && head && (selectedSet.has(anchor.id) || selectedSet.has(head.id)) ? { anchor, head } : null;
189
+ })();
190
+ const restoreCaret = () => {
191
+ if (!caret || !view) return;
192
+ const place = ({ id, offset }) => {
193
+ let found = null;
194
+ view.state.doc.forEach((node, pos) => { if (found === null && node.attrs.id === id) found = pos + 1 + Math.min(offset, node.content.size); });
195
+ return found;
196
+ };
197
+ const anchor = place(caret.anchor), head = place(caret.head);
198
+ if (anchor !== null && head !== null) view.dispatch(view.state.tr.setSelection(pmState.TextSelection.create(view.state.doc, anchor, head)).setMeta(REMOTE_META, true).setMeta("addToHistory", false));
199
+ };
177
200
  if (sameOrder) {
178
201
  um?.stopCapturing();
179
202
  pd.ydoc.transact(() => {
180
- for (const block of movedRoots) setParent(pd, segments.get(block.id), block.id, parent);
181
- for (const change of changes) setAttr(pd, segments.get(change.id), change.id, change.field, change.value);
203
+ if (parentChanges.length) for (const change of parentChanges) setParent(pd, segments.get(change.id), change.id, change.value);
204
+ else for (const block of movedRoots) setParent(pd, segments.get(block.id), block.id, parent);
205
+ for (const change of changes) if (change.field !== "parent") setAttr(pd, segments.get(change.id), change.id, change.field, change.value);
182
206
  }, LOCAL);
183
207
  um?.stopCapturing();
184
208
  render();
209
+ restoreCaret();
185
210
  return { status: "moved", ids: movedIds };
186
211
  }
187
212
 
@@ -216,13 +241,6 @@ export function moveBlocks({ pd, view, um, origins, render }, args = {}) {
216
241
  const baseline = intents.restores.find((restore) => restore.key === winner?.key)?.baseline ?? { type: seg.t, attrs: seg.fields ?? {} };
217
242
  promotions.set(seg.sourceKey, { covers: removalsOf.map((removal) => removal.key), baseline: JSON.parse(JSON.stringify(baseline)) });
218
243
  }
219
- const caret = (() => {
220
- const sel = view?.state.selection;
221
- if (!(sel instanceof pmState.TextSelection)) return null;
222
- const at = (pos) => { const $pos = view.state.doc.resolve(pos); return $pos.depth ? { id: $pos.node(1).attrs.id, offset: $pos.parentOffset } : null; };
223
- const anchor = at(sel.anchor), head = at(sel.head);
224
- return anchor && head && (selectedSet.has(anchor.id) || selectedSet.has(head.id)) ? { anchor, head } : null;
225
- })();
226
244
  if (!removals.length) throw new Error("The selected blocks have no live physical markers to retire");
227
245
 
228
246
  um?.stopCapturing();
@@ -270,19 +288,12 @@ export function moveBlocks({ pd, view, um, origins, render }, args = {}) {
270
288
  pd.gone.set(copyActiveKey(physicalKeyOf(dest[0])), true);
271
289
  }
272
290
  if (spans.length) origins.recordSpans(spans);
273
- for (const block of movedRoots) setParent(pd, segments.get(block.id), block.id, parent);
274
- for (const change of changes) setAttr(pd, segments.get(change.id), change.id, change.field, change.value);
291
+ if (parentChanges.length) for (const change of parentChanges) setParent(pd, segments.get(change.id), change.id, change.value);
292
+ else for (const block of movedRoots) setParent(pd, segments.get(block.id), block.id, parent);
293
+ for (const change of changes) if (change.field !== "parent") setAttr(pd, segments.get(change.id), change.id, change.field, change.value);
275
294
  }, LOCAL);
276
295
  um?.stopCapturing();
277
296
  render();
278
- if (caret && view) {
279
- const place = ({ id, offset }) => {
280
- let found = null;
281
- view.state.doc.forEach((node, pos) => { if (found === null && node.attrs.id === id) found = pos + 1 + Math.min(offset, node.content.size); });
282
- return found;
283
- };
284
- const anchor = place(caret.anchor), head = place(caret.head);
285
- if (anchor !== null && head !== null) view.dispatch(view.state.tr.setSelection(pmState.TextSelection.create(view.state.doc, anchor, head)).setMeta(REMOTE_META, true).setMeta("addToHistory", false));
286
- }
297
+ restoreCaret();
287
298
  return { status: "moved", ids: movedIds };
288
299
  }
@@ -0,0 +1,50 @@
1
+ // Where a position sits on the page: the map between a ProseMirror position in
2
+ // the projected document and an index in the page's Yjs `flow`. Extracted from
3
+ // bridge.js so the same functions keep this tab's own caret through a peer's edit
4
+ // and place a peer's caret here, instead of each approximating the other.
5
+ import { tokenIndex, yBefore } from "./tokens.js";
6
+
7
+ export const yIndexAt = (doc, holes, pos, visible) => yBefore(holes, tokenIndex(doc, new Map(), visible)[pos]);
8
+
9
+ export function posAtY(doc, holes, tokens, y, visible = null) {
10
+ let lo = 0, hi = tokens;
11
+ while (lo < hi) { const mid = (lo + hi + 1) >> 1; if (yBefore(holes, mid) <= y) lo = mid; else hi = mid - 1; }
12
+ const idx = tokenIndex(doc, new Map(), visible);
13
+ let first = -1;
14
+ for (let p = 0; p < idx.length; p++) {
15
+ if (idx[p] < lo) continue;
16
+ if (idx[p] > lo) break;
17
+ if (first < 0) first = p;
18
+ if (doc.resolve(p).parent.inlineContent) return p;
19
+ }
20
+ return first < 0 ? doc.content.size : first;
21
+ }
22
+
23
+ export const cellPoint = (doc, pos) => {
24
+ const $pos = doc.resolve(pos);
25
+ if ($pos.depth !== 3 || $pos.parent.type.name !== "table_cell") return null;
26
+ return { table: $pos.node(1).attrs.id, row: $pos.node(2).attrs.id, col: $pos.parent.attrs.col, offset: $pos.parentOffset };
27
+ };
28
+
29
+ // The cell a point names, as its own range: what an outline draws and what a
30
+ // character offset would pretend to know.
31
+ export const cellRange = (doc, point) => {
32
+ let found = null;
33
+ doc.forEach((table, at) => {
34
+ if (found !== null || table.type.name !== "table" || table.attrs.id !== point.table) return;
35
+ table.forEach((row, rowAt) => {
36
+ if (found !== null || row.attrs.id !== point.row) return;
37
+ row.forEach((cell, cellAt) => {
38
+ if (found === null && cell.attrs.col === point.col) found = { from: at + 2 + rowAt + cellAt, to: at + 2 + rowAt + cellAt + cell.nodeSize, block: point.table, row: point.row, col: point.col };
39
+ });
40
+ });
41
+ });
42
+ return found;
43
+ };
44
+
45
+ export const cellAt = (doc, point) => {
46
+ const range = cellRange(doc, point);
47
+ if (!range) return null;
48
+ const cell = doc.nodeAt(range.from);
49
+ return range.from + 1 + Math.min(point.offset ?? 0, cell ? cell.content.size : 0);
50
+ };
@@ -3,6 +3,7 @@ import { toBase64, fromBase64 } from "../../vendor/yjs.vendor.js";
3
3
  import { NET } from "./layout.js";
4
4
  import { ORIGINS_ORIGIN } from "./origins.js";
5
5
  import { PART_KEY, BASE64, entriesOf, captureYValue } from "./parts.js";
6
+ import { containsUpdate } from "./recovery-proof.js";
6
7
 
7
8
  export { entriesOf } from "./parts.js";
8
9
 
@@ -11,7 +12,6 @@ export const dec = (s) => fromBase64(s);
11
12
 
12
13
  export const PART = 12 * 1024;
13
14
  const baseOf = (k) => PART_KEY.exec(k)?.[1] ?? k;
14
- const sameBytes = (a, b) => a.length === b.length && a.every((v, i) => v === b[i]);
15
15
 
16
16
  export function yValueOf(ydoc) {
17
17
  return { v: 1, g: 0, y: { seed: enc(Y.encodeStateAsUpdateV2(ydoc)) } };
@@ -75,6 +75,8 @@ export function docFromValue(value, { strict = false } = {}) {
75
75
  }
76
76
  }
77
77
 
78
+ const encoder = typeof TextEncoder === "function" ? new TextEncoder() : null;
79
+ const utf8Bytes = (text) => (encoder ? encoder.encode(text).length : text.length * 2);
78
80
  const sameDeletes = (a, b) => Y.equalDeleteSets(Y.createDeleteSetFromStructStore(a.store), Y.createDeleteSetFromStructStore(b.store));
79
81
  const svEqual = (a, b) => { if (a.size !== b.size) return false; for (const [c, n] of a) if (b.get(c) !== n) return false; return true; };
80
82
 
@@ -95,6 +97,9 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
95
97
  const me = ydoc.clientID.toString(36);
96
98
  const nextKey = (p) => `${p}${me}-${(counter++).toString(36)}`;
97
99
  let timer = null;
100
+ const generation = store.value.g;
101
+ // Keys this tab wrote itself: the updates and offers the room has not confirmed yet.
102
+ const own = new Set();
98
103
 
99
104
  const partsOf = (key, s) => {
100
105
  if (s.length <= PART) return [[key, s]];
@@ -104,6 +109,7 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
104
109
  const put = (key, bytes) => {
105
110
  canCompact = true;
106
111
  known.add(key);
112
+ own.add(key);
107
113
  const parts = partsOf(key, enc(bytes));
108
114
  store.handle.update((data) => { for (const [k, v] of parts) data.y[k] = v; });
109
115
  };
@@ -163,20 +169,70 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
163
169
  const compact = { keys: 32, room: 3840 * 1024, ...limits };
164
170
  let fold = null;
165
171
  let quietUntil = 0;
172
+ const COVER_MAX = 256;
166
173
  const coverCache = new Map();
167
174
  const drop = (keys) => store.handle.update((data) => { for (const k of keys) delete data.y[k]; });
168
175
  const keysOf = (auth, bases) => Object.keys(auth).filter((k) => bases.has(baseOf(k)));
176
+ // A key is retired only while what this tab shows under it is still what the proof was
177
+ // made over: a late write under the same key keeps its own entry.
178
+ const retire = (auth, keys) => { const visible = store.value.y ?? {}; drop(keys.filter((k) => k in visible && visible[k] === auth[k])); };
169
179
 
180
+ // `known` and the coverage memo keep live references only: the authoritative entries,
181
+ // this tab's own unconfirmed writes and the fold in progress. Pruned after a rebase, a
182
+ // fold and a refusal. A key that is forgotten can be decoded again; Yjs is idempotent.
183
+ function prune() {
184
+ const auth = store.authoritative.y ?? {};
185
+ const visible = store.value.y ?? {};
186
+ for (const k of own) if (!(k in visible)) own.delete(k);
187
+ const held = fold ? [...fold.parts.map(([k]) => k), ...fold.inputs.keys()] : [];
188
+ const retained = new Set([...Object.keys(auth), ...own, ...held]);
189
+ for (const key of known) if (!retained.has(key)) known.delete(key);
190
+ const values = new Map(entriesOf(auth));
191
+ for (const [key, proof] of coverCache) if (!retained.has(key) || values.get(key) !== proof.value) coverCache.delete(key);
192
+ }
193
+
194
+ // Whether a fold's merged bytes already carry this input: merging it in again changes
195
+ // neither an insertion nor a deletion. Equal state vectors say nothing about deletes.
196
+ function coveredBy(key, value, foldKey, base) {
197
+ let proof = coverCache.get(key);
198
+ if (proof && proof.value !== value) { coverCache.delete(key); proof = null; }
199
+ if (!proof) {
200
+ if (coverCache.size >= COVER_MAX) coverCache.delete(coverCache.keys().next().value);
201
+ proof = { value, folds: new Map() };
202
+ coverCache.set(key, proof);
203
+ }
204
+ if (!proof.folds.has(foldKey)) proof.folds.set(foldKey, containsUpdate(base, dec(value)));
205
+ return proof.folds.get(foldKey);
206
+ }
207
+
208
+ // An input the authority no longer has was retired already; one it holds differently
209
+ // was rewritten after the fold was built, and the fold no longer speaks for it.
210
+ const stillHeld = (auth, active) => { for (const [k, v] of active.held) if (auth[k] !== undefined && auth[k] !== v) return false; return true; };
211
+ const coversInputs = (active) => {
212
+ const base = dec(active.merged);
213
+ for (const [k, v] of active.inputs) if (v !== null && !coveredBy(k, v, active.key, base)) return false;
214
+ return true;
215
+ };
216
+
217
+ // Inputs retire only once the authority holds every proposed part with the identical
218
+ // value, still holds each input as it was captured, and the fold covers every one of
219
+ // them. Anything less keeps the originals; pending confirmation keeps them too.
170
220
  function finishFold(auth) {
171
- if (fold.parts.every((k) => k in auth)) {
172
- drop(keysOf(auth, fold.inputs));
221
+ const active = fold;
222
+ const whole = captureYValue({ v: 1, y: auth }).y;
223
+ const arrived = active.parts.filter(([k, v]) => auth[k] === v && whole[k] === v);
224
+ if (arrived.length !== active.parts.length) {
225
+ if (store.pending.length) return false;
173
226
  fold = null;
227
+ if (arrived.length) retire(auth, arrived.map(([k]) => k));
228
+ quietUntil = entriesOf(auth).length + compact.keys;
229
+ prune();
174
230
  return true;
175
231
  }
176
- if (store.pending.length) return false;
177
- drop(fold.parts.filter((k) => k in auth));
178
- quietUntil = entriesOf(auth).length + compact.keys;
232
+ if (store.pending.length || !stillHeld(auth, active) || !coversInputs(active)) return false;
179
233
  fold = null;
234
+ retire(auth, keysOf(auth, active.inputs));
235
+ prune();
180
236
  return true;
181
237
  }
182
238
 
@@ -185,11 +241,7 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
185
241
  const val = new Map(entries);
186
242
  const norm = new Map(folds);
187
243
  const normal = (k) => { const v = norm.get(k); if (typeof v === "string") norm.set(k, Y.mergeUpdatesV2([dec(v)])); return norm.get(k); };
188
- const covered = (k, v, mk) => {
189
- const c = `${k}|${mk}`;
190
- if (!coverCache.has(c)) { const b = normal(mk); coverCache.set(c, sameBytes(Y.mergeUpdatesV2([b, dec(v)]), b)); }
191
- return coverCache.get(c);
192
- };
244
+ const covered = (k, v, mk) => coveredBy(k, v, mk, normal(mk));
193
245
  const gone = new Set();
194
246
  for (const [k, v] of entries) {
195
247
  if (!crowded && k[0] !== "m") continue;
@@ -201,31 +253,36 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
201
253
  }
202
254
  }
203
255
  if (!gone.size) return false;
204
- drop(keysOf(auth, gone));
256
+ retire(auth, keysOf(auth, gone));
205
257
  return true;
206
258
  }
207
259
 
208
260
  function maybeCompact() {
209
- if (!canCompact) return false;
261
+ if (stopped || !canCompact || store.value.g !== generation) return false;
210
262
  const auth = store.authoritative.y;
211
263
  if (fold) return finishFold(auth);
212
264
  const entries = entriesOf(auth);
213
265
  const crowded = entries.length > Math.max(compact.keys, quietUntil);
214
266
  if (!crowded && entries.filter(([k]) => k[0] === "m").length < 2) return false;
215
- if (dropCovered(auth, entries, crowded)) return true;
267
+ if (dropCovered(auth, entries, crowded)) { prune(); return true; }
216
268
  if (!crowded) return false;
217
269
  const merged = enc(Y.mergeUpdatesV2(entries.map(([, v]) => dec(v))));
218
- const used = Object.values(auth).reduce((n, v) => n + v.length, 0);
219
- if (used + merged.length > compact.room) {
270
+ const key = nextKey("m");
271
+ const parts = partsOf(key, merged);
272
+ const candidate = { ...store.value, y: { ...store.value.y } };
273
+ for (const [k, v] of parts) candidate.y[k] = v;
274
+ if (utf8Bytes(JSON.stringify(candidate)) > compact.room) {
220
275
  quietUntil = entries.length + compact.keys;
221
276
  return false;
222
277
  }
223
- const key = nextKey("m");
224
- known.add(key);
225
- const parts = partsOf(key, merged);
226
278
  const captured = captureYValue({ v: 1, y: auth }).y;
227
279
  const incompleteFolds = Object.keys(auth).filter((k) => !(k in captured)).map(baseOf).filter((k) => k[0] === "m");
228
- fold = { key, parts: parts.map(([k]) => k), inputs: new Set([...entries.map(([k]) => k), ...incompleteFolds]) };
280
+ const inputs = new Map(entries);
281
+ for (const k of incompleteFolds) inputs.set(k, null);
282
+ const held = new Map();
283
+ for (const k of keysOf(auth, inputs)) if (inputs.get(baseOf(k)) !== null) held.set(k, captured[k] ?? auth[k]);
284
+ fold = { key, parts, merged, inputs, held };
285
+ known.add(key);
229
286
  store.handle.update((data) => { for (const [k, v] of parts) data.y[k] = v; });
230
287
  stats.compactions++;
231
288
  return true;
@@ -264,6 +321,7 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
264
321
  } else {
265
322
  for (const [k, v] of entriesOf(ev.next.y ?? {})) applyKey(k, v);
266
323
  }
324
+ prune();
267
325
  schedule(ev.source !== "remote" || !ops, ev.source !== "remote");
268
326
  });
269
327
 
@@ -272,14 +330,18 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
272
330
  // the refused one, so they are all taken back, with the parts of updates the room does not
273
331
  // hold whole, and the room's page stays one a checkpoint can render.
274
332
  const offDelivery = store.onDelivery((event) => {
275
- if (event.phase !== "refused" || event.code !== "too-large" || stopped) return;
333
+ if (event.phase !== "refused") return;
334
+ if (event.code !== "too-large" || stopped) { prune(); return; }
276
335
  stop("too-large");
277
336
  const auth = store.authoritative.y ?? {};
278
337
  const whole = captureYValue({ v: 1, y: auth }).y;
279
338
  const unconfirmed = Object.keys(store.value.y ?? {}).filter((k) => !(k in auth));
280
339
  const partial = Object.keys(auth).filter((k) => PART_KEY.test(k) && !(k in whole) && baseOf(k).slice(1).startsWith(`${me}-`));
281
340
  if (unconfirmed.length || partial.length) drop([...unconfirmed, ...partial]);
341
+ prune();
282
342
  });
283
343
 
284
- return { known, stats, settle, reconcile, get reconcilePending() { return reconcilePending; }, get stopped() { return stopped; }, compact: maybeCompact, dispose() { off(); offDelivery(); ydoc.off("updateV2", onUpdate); if (timer) clearTimer(timer); } };
344
+ return { known, stats, settle, reconcile, get reconcilePending() { return reconcilePending; }, get stopped() { return stopped; },
345
+ // Test-only snapshot of the counters and cache sizes; no mutable set leaves the provider.
346
+ _diagnostics: () => ({ known: known.size, cover: coverCache.size, fold: fold ? fold.parts.length : 0, generation }), compact: maybeCompact, dispose() { off(); offDelivery(); ydoc.off("updateV2", onUpdate); if (timer) clearTimer(timer); } };
285
347
  }