@panphora/clayjs 1.17.0 → 1.18.1

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 (467) hide show
  1. package/dist/all.js +1 -1
  2. package/dist/clay-dom.js +1 -1
  3. package/dist/clay-events.js +1 -1
  4. package/dist/clay-internals.js +1 -1
  5. package/dist/clay-options.js +1 -1
  6. package/dist/clay-router.js +1 -1
  7. package/dist/clay-ui.js +1 -1
  8. package/dist/clay-utils.js +1 -1
  9. package/dist/clay.js +1 -1
  10. package/dist/clay.standalone.js +72474 -62090
  11. package/dist/m/attrs/{save-freeze-MYNVXREN.js → save-freeze-LWGF6XUN.js} +1 -1
  12. package/dist/m/c-2GRUM77Q.js +30 -0
  13. package/dist/m/c-2KIEGNC4.js +1 -0
  14. package/dist/m/{c-AMSNPOCX.js → c-2KL3BD52.js} +1 -1
  15. package/dist/m/c-2ZZBC45O.js +1 -0
  16. package/dist/m/c-372N6H3B.js +1 -0
  17. package/dist/m/{c-4WKFKEKF.js → c-3G2DGZUP.js} +1 -1
  18. package/dist/m/c-3LDGIBMN.js +1 -0
  19. package/dist/m/c-3O7OBDCM.js +1 -0
  20. package/dist/m/c-4ASQPRQL.js +1 -0
  21. package/dist/m/c-4TXNLX4K.js +6 -0
  22. package/dist/m/c-4YU3BR4D.js +6 -0
  23. package/dist/m/c-5734MF2P.js +1 -0
  24. package/dist/m/c-57XDY6HC.js +1 -0
  25. package/dist/m/{c-KAU6E43D.js → c-5B4YBZXC.js} +1 -1
  26. package/dist/m/c-5ONTYIUI.js +1 -0
  27. package/dist/m/c-5OOVFCJM.js +1 -0
  28. package/dist/m/{c-3XIRD4NU.js → c-5T6NZHNM.js} +1 -1
  29. package/dist/m/{c-74MGYWZF.js → c-5ZQV7KC4.js} +1 -1
  30. package/dist/m/{c-7OLLJPWP.js → c-6DB2OLRJ.js} +1 -1
  31. package/dist/m/c-6DNRCPNM.js +1 -0
  32. package/dist/m/c-6UGWFMQC.js +2 -0
  33. package/dist/m/c-7K4SCQB7.js +1 -0
  34. package/dist/m/c-7MG5RU63.js +1 -0
  35. package/dist/m/c-7PRJCA3E.js +1 -0
  36. package/dist/m/c-A6L3FMUS.js +1 -0
  37. package/dist/m/c-AMU3BPPR.js +4 -0
  38. package/dist/m/{c-Y5HBBIQ4.js → c-AMYEFA2E.js} +1 -1
  39. package/dist/m/c-AQYCG7QW.js +1 -0
  40. package/dist/m/c-ATKAOH23.js +1 -0
  41. package/dist/m/c-B5CS55WR.js +1 -0
  42. package/dist/m/c-B5UJQT7W.js +1 -0
  43. package/dist/m/c-B7J4K2AE.js +3 -0
  44. package/dist/m/c-BHZYSHW4.js +1 -0
  45. package/dist/m/{c-VHM5XQHK.js → c-BIJALSRD.js} +1 -1
  46. package/dist/m/c-BJ7YQ5QZ.js +1 -0
  47. package/dist/m/{c-KIPQJX4B.js → c-BRLCTNMC.js} +1 -1
  48. package/dist/m/{c-VE6ZMYMC.js → c-BUAYFEV5.js} +1 -1
  49. package/dist/m/c-C6IPRG34.js +1 -0
  50. package/dist/m/{c-KU3ZFADW.js → c-CAS3NND4.js} +1 -1
  51. package/dist/m/{c-PS5ARVWR.js → c-CCQ25GFY.js} +1 -1
  52. package/dist/m/c-CJFDLD43.js +1 -0
  53. package/dist/m/c-COPTL5WB.js +1 -0
  54. package/dist/m/c-D3E4WJ4M.js +166 -0
  55. package/dist/m/c-D5IKEKXW.js +7 -0
  56. package/dist/m/c-DHBRA6DL.js +1 -0
  57. package/dist/m/c-DKLS74XB.js +72 -0
  58. package/dist/m/c-DVYGFTSK.js +3 -0
  59. package/dist/m/c-E3ZXARWA.js +1 -0
  60. package/dist/m/c-EK63G3CG.js +1 -0
  61. package/dist/m/c-ERQSNR6K.js +13 -0
  62. package/dist/m/c-EVYAISP3.js +1 -0
  63. package/dist/m/c-EXKHPOVC.js +3 -0
  64. package/dist/m/{c-QJ4OM3G6.js → c-FWKHM4E7.js} +1 -1
  65. package/dist/m/{c-4Q2XTQ2N.js → c-GBKB5WDL.js} +1 -1
  66. package/dist/m/c-GNDL3TKB.js +1 -0
  67. package/dist/m/c-GRWMD6O5.js +1 -0
  68. package/dist/m/c-GRXHHFPO.js +1 -0
  69. package/dist/m/c-HG2C77BM.js +4 -0
  70. package/dist/m/c-HHGJBU4D.js +1 -0
  71. package/dist/m/c-HMV6P4RX.js +1 -0
  72. package/dist/m/c-HMYWIHFG.js +1 -0
  73. package/dist/m/c-HT2DKBV2.js +1 -0
  74. package/dist/m/c-I2SCCUEW.js +1 -0
  75. package/dist/m/{c-TJ6WOJL3.js → c-ICJYHABF.js} +2 -2
  76. package/dist/m/c-IMZCWYHR.js +7 -0
  77. package/dist/m/c-IVMD3FEM.js +1 -0
  78. package/dist/m/{c-WDC5GVKU.js → c-IVTRVENZ.js} +1 -1
  79. package/dist/m/c-JKZJIUIP.js +1 -0
  80. package/dist/m/{c-UDUUJPFZ.js → c-JNLX3RJB.js} +1 -1
  81. package/dist/m/c-K3ADYNLU.js +1 -0
  82. package/dist/m/c-KGHVFRR3.js +1 -0
  83. package/dist/m/{c-5QWQ7A5L.js → c-KJJPV77R.js} +1 -1
  84. package/dist/m/{c-CZUSOB7V.js → c-KNPDOMDY.js} +1 -1
  85. package/dist/m/{c-EZU56NQV.js → c-KP64LT2A.js} +2 -2
  86. package/dist/m/c-KQ7E3PYY.js +1 -0
  87. package/dist/m/c-L3F5ZLIV.js +1 -0
  88. package/dist/m/{c-3RUU4TPX.js → c-L42OLXWM.js} +3 -2
  89. package/dist/m/c-L5VFATT6.js +1 -0
  90. package/dist/m/c-L7JL5TL3.js +3 -0
  91. package/dist/m/c-LKSL4AUJ.js +1 -0
  92. package/dist/m/c-M6FNQCTQ.js +1 -0
  93. package/dist/m/c-MCOOHCSG.js +1 -0
  94. package/dist/m/c-MHI3GYKF.js +1 -0
  95. package/dist/m/c-MWXFLW4J.js +1 -0
  96. package/dist/m/{c-5RJFTLSF.js → c-NEL7HQW3.js} +1 -1
  97. package/dist/m/{c-DCXOXBF3.js → c-NQJRVJ56.js} +1 -1
  98. package/dist/m/c-NWX3MFT4.js +1 -0
  99. package/dist/m/{c-HPURYVZS.js → c-OJYM642Q.js} +1 -1
  100. package/dist/m/c-OKXI52N7.js +1 -0
  101. package/dist/m/{c-R3L7ZJGB.js → c-OPLUZR3A.js} +1 -1
  102. package/dist/m/{c-GSFIGKOP.js → c-OTMKN2OO.js} +1 -1
  103. package/dist/m/{c-6NC5I2TV.js → c-OYRZ7XG2.js} +1 -1
  104. package/dist/m/{c-EVCQ3BKA.js → c-P3OFYUMA.js} +1 -1
  105. package/dist/m/c-PLN76GF3.js +6 -0
  106. package/dist/m/c-PLP4JKFC.js +1 -0
  107. package/dist/m/c-Q26MME6N.js +1 -0
  108. package/dist/m/{c-S7H2DRCB.js → c-Q3XOWZEV.js} +1 -1
  109. package/dist/m/{c-FRLHSY7E.js → c-QIKT6SKM.js} +1 -1
  110. package/dist/m/c-QIXT3Q7C.js +28 -0
  111. package/dist/m/c-QO4HNGCQ.js +1 -0
  112. package/dist/m/{c-JU3NK25H.js → c-R27DWQKX.js} +1 -1
  113. package/dist/m/{c-RSMOODDU.js → c-R2XUODVY.js} +1 -1
  114. package/dist/m/{c-QNKREOL6.js → c-R7LBFMF5.js} +1 -1
  115. package/dist/m/c-RND3G2S2.js +1 -0
  116. package/dist/m/{c-Q622NS2Y.js → c-S2U6NJU4.js} +1 -1
  117. package/dist/m/c-SPZPGD3R.js +1 -0
  118. package/dist/m/c-SW4YEG2D.js +8 -0
  119. package/dist/m/c-T637T7SN.js +1 -0
  120. package/dist/m/c-TJAXGIRV.js +1 -0
  121. package/dist/m/{c-RYUXUUBX.js → c-TNEM3RNM.js} +1 -1
  122. package/dist/m/c-TTB5EK4S.js +32 -0
  123. package/dist/m/c-U7SHNWDH.js +1 -0
  124. package/dist/m/c-UDHIVLYH.js +20 -0
  125. package/dist/m/{c-BBVGTQG7.js → c-UGNV5MFB.js} +1 -1
  126. package/dist/m/{c-QICP2YR5.js → c-UORXLSD3.js} +1 -1
  127. package/dist/m/c-UPD33ZPE.js +1 -0
  128. package/dist/m/c-URSMSFCB.js +1 -0
  129. package/dist/m/c-UY4QX42B.js +1 -0
  130. package/dist/m/c-V44KWI2L.js +4 -0
  131. package/dist/m/c-VILENZTD.js +1 -0
  132. package/dist/m/{c-HDDEEL77.js → c-VJYEVRYK.js} +1 -1
  133. package/dist/m/{c-SMBZG5X4.js → c-VR2BJ4AN.js} +1 -1
  134. package/dist/m/c-W4JCVLAB.js +1 -0
  135. package/dist/m/c-W6DQSHXI.js +135 -0
  136. package/dist/m/c-WBGWGOM7.js +1 -0
  137. package/dist/m/{c-4L3BTFRK.js → c-WCXLG3MV.js} +1 -1
  138. package/dist/m/c-WGW764WX.js +1 -0
  139. package/dist/m/c-WMRJKQ2V.js +5 -0
  140. package/dist/m/c-WNTYKCDG.js +1 -0
  141. package/dist/m/c-WQWIRWO3.js +72 -0
  142. package/dist/m/c-X5LNHET5.js +1 -0
  143. package/dist/m/{c-I2ZATQCD.js → c-X5TSXZYJ.js} +2 -2
  144. package/dist/m/c-XGH4GS2T.js +1 -0
  145. package/dist/m/c-Y6F2FORL.js +1 -0
  146. package/dist/m/c-YBKHUAK7.js +10 -0
  147. package/dist/m/c-YBSS55KU.js +20 -0
  148. package/dist/m/c-YCLU4UNT.js +2 -0
  149. package/dist/m/c-YVZ2L7CE.js +1 -0
  150. package/dist/m/c-ZK4GIZH2.js +1 -0
  151. package/dist/m/c-ZRXJD4G6.js +1 -0
  152. package/dist/m/c-ZWBI4HYK.js +1 -0
  153. package/dist/m/core/{admin-attrs-QHOKPJ7W.js → admin-attrs-YY7T5FGU.js} +1 -1
  154. package/dist/m/core/autosave-S6OMNDCK.js +1 -0
  155. package/dist/m/core/conflict-notice-IWHMHH43.js +1 -0
  156. package/dist/m/core/{host-permission-FNXVEFNE.js → host-permission-6EVJGNPL.js} +1 -1
  157. package/dist/m/core/is-edit-mode-GCX6767E.js +1 -0
  158. package/dist/m/core/{persist-JFYRHMRJ.js → persist-UKXV5KLA.js} +1 -1
  159. package/dist/m/core/recovery-DRFZRJED.js +1 -0
  160. package/dist/m/core/save-LYY7K6U7.js +1 -0
  161. package/dist/m/core/save-core-MKRPSEEO.js +1 -0
  162. package/dist/m/core/snapshot-NU3YM6GR.js +1 -0
  163. package/dist/m/core/unsaved-warning-7QTKEN7Z.js +1 -0
  164. package/dist/m/internals/{index-PVXGPUDA.js → index-TGLQINN3.js} +1 -1
  165. package/dist/m/loader-3BBNZZGM.js +1 -0
  166. package/dist/m/manifest-26C55578.js +1 -0
  167. package/dist/m/manifest-edit-4396A009.js +1 -0
  168. package/dist/m/manifest.json +2111 -1573
  169. package/dist/m/page/{editor-NNJA4OKD.js → editor-6LRB3GE4.js} +123 -21
  170. package/dist/m/plugins/ai-edit-UTV7YO2I.js +6 -0
  171. package/dist/m/plugins/{cms-HP3LSTPN.js → cms-LRAOZHT7.js} +9 -9
  172. package/dist/m/plugins/{demo-RM5IR6AO.js → demo-OYO5GCAO.js} +1 -1
  173. package/dist/m/plugins/{derive-UAAA4V4S.js → derive-FRE5JJQE.js} +1 -1
  174. package/dist/m/plugins/indicator-4VDXWYTD.js +1 -0
  175. package/dist/m/plugins/live-AD4H6XSL.js +1 -0
  176. package/dist/m/plugins/menu-M5WW7DBF.js +1 -0
  177. package/dist/m/plugins/page-MD3T2ZX3.js +2 -0
  178. package/dist/m/plugins/page-view-PAWVZNFY.js +1 -0
  179. package/dist/m/plugins/{people-SGPJKANK.js → people-JCHVPF7P.js} +1 -1
  180. package/dist/m/plugins/quickcrop-BQ5YYFCH.js +26 -0
  181. package/dist/m/plugins/{richclay-NRQDSWX6.js → richclay-KLGETK77.js} +2 -2
  182. package/dist/m/plugins/sortable-4S6SQC3D.js +1 -0
  183. package/dist/m/plugins/source-TJXOIOYY.js +1 -0
  184. package/dist/m/plugins/undo-NLPGCJQY.js +1 -0
  185. package/dist/m/plugins/{unsent-HJFLQJ2O.js → unsent-OA4RGBUT.js} +1 -1
  186. package/dist/m/plugins/upgrade-HSUNDNJD.js +1 -0
  187. package/dist/m/plugins/{upload-WLYKK6TL.js → upload-NHGJXZK2.js} +1 -1
  188. package/dist/m/plugins/visitor-drafts-BW43DGY3.js +20 -0
  189. package/dist/m/plugins/visitor-drafts-owner-7ODQG7JX.js +1 -0
  190. package/dist/m/plugins/{wire-4N6GYAF6.js → wire-VBWXEXU2.js} +1 -1
  191. package/dist/m/reader-core-4W2KYEWQ.js +77 -0
  192. package/dist/m/route/route-62CHXL5B.js +1 -0
  193. package/dist/m/sync/live-sync-MO5DH33W.js +1 -0
  194. package/dist/m/ui/index-IVWN2QC4.js +1 -0
  195. package/dist/m/vendor/sap-core.vendor-RN4ZVY4X.js +6 -0
  196. package/dist/offline-manifest.json +1367 -930
  197. package/dist/page/themes/light-1.css +8 -7
  198. package/dist/server/core.cjs +1440 -288
  199. package/dist/server/offline-runtime.cjs +63 -63
  200. package/dist/server/offline-runtime.mjs +63 -63
  201. package/dist/server/page-compaction-worker.cjs +3710 -0
  202. package/dist/server/rooms.mjs +5 -0
  203. package/docs/guide.md +653 -101
  204. package/entries/sap.js +5 -5
  205. package/package.json +1 -1
  206. package/src/core/admin-contenteditable.js +5 -2
  207. package/src/core/admin-inputs.js +1 -0
  208. package/src/core/admin-onclick.js +2 -1
  209. package/src/core/conflict-notice-ui.js +4 -7
  210. package/src/core/download.js +2 -2
  211. package/src/core/host-meta.js +38 -0
  212. package/src/core/is-edit-mode.js +17 -1
  213. package/src/core/mode-key.js +125 -19
  214. package/src/core/mode-support.js +5 -0
  215. package/src/core/mode-transition.js +20 -6
  216. package/src/core/preserve-authored.js +4 -4
  217. package/src/core/recovery.js +121 -33
  218. package/src/core/save-accepted.js +33 -0
  219. package/src/core/save-core.js +106 -31
  220. package/src/core/save-job.js +108 -0
  221. package/src/core/save-prepare.js +53 -0
  222. package/src/core/save-size-notice.js +3 -1
  223. package/src/core/save-slices.js +15 -0
  224. package/src/core/save.js +557 -127
  225. package/src/core/serialize-steps.js +109 -0
  226. package/src/core/snapshot.js +170 -39
  227. package/src/core/source-map.js +83 -38
  228. package/src/core/stale-host-notice.js +1 -0
  229. package/src/core/upgrade-run.js +59 -8
  230. package/src/dom/active-element.js +5 -0
  231. package/src/lib/assets.js +1 -0
  232. package/src/lib/slice.js +65 -0
  233. package/src/lib/uimotion.js +774 -0
  234. package/src/lib/unsaved-state.js +2 -2
  235. package/src/live/fast-lane.js +10 -3
  236. package/src/live/local-lane.js +202 -11
  237. package/src/live/ops.js +16 -0
  238. package/src/live/page-compaction.js +162 -0
  239. package/src/live/server-lane.js +1 -0
  240. package/src/live/store.js +11 -3
  241. package/src/loader-logic.js +3 -1
  242. package/src/loader.js +14 -4
  243. package/src/page/adopt-boot.js +2 -1
  244. package/src/page/awareness.js +33 -2
  245. package/src/page/blocks.js +89 -27
  246. package/src/page/boot-pack.js +1 -1
  247. package/src/page/chart-tables.js +21 -3
  248. package/src/page/chart.js +406 -63
  249. package/src/page/checklist.js +10 -135
  250. package/src/page/checkpoint-proof.js +7 -1
  251. package/src/page/chrome.js +250 -51
  252. package/src/page/clipboard-shape.js +126 -0
  253. package/src/page/drag.js +33 -3
  254. package/src/page/editor.js +136 -25
  255. package/src/page/export-assets.js +357 -0
  256. package/src/page/export-html.js +209 -0
  257. package/src/page/export-markdown.js +361 -0
  258. package/src/page/features/checklist.js +428 -0
  259. package/src/page/features/export-html.js +189 -0
  260. package/src/page/features/export-markdown.js +169 -0
  261. package/src/page/features/import-markdown.js +349 -0
  262. package/src/page/features/media.js +61 -5
  263. package/src/page/features/registry.js +18 -0
  264. package/src/page/features/table.js +373 -83
  265. package/src/page/features/title-cover.js +331 -0
  266. package/src/page/features/title-icon.js +213 -0
  267. package/src/page/features/toc.js +87 -0
  268. package/src/page/format-command.js +25 -1
  269. package/src/page/format-gate.js +68 -18
  270. package/src/page/format-host.js +17 -0
  271. package/src/page/format-native-commands.js +21 -5
  272. package/src/page/image-actions.js +64 -0
  273. package/src/page/import-markdown.js +262 -0
  274. package/src/page/items.js +135 -17
  275. package/src/page/markdown.js +6 -2
  276. package/src/page/media.js +35 -119
  277. package/src/page/placeholder.js +14 -1
  278. package/src/page/projection.js +98 -10
  279. package/src/page/recover.js +10 -12
  280. package/src/page/recovery-ui.js +464 -0
  281. package/src/page/rich/chart.js +63 -7
  282. package/src/page/save-projection.js +28 -0
  283. package/src/page/save-states.js +13 -3
  284. package/src/page/schema.js +19 -3
  285. package/src/page/table-paste.js +3 -1
  286. package/src/page/table-style.js +4 -4
  287. package/src/page/table.js +44 -2
  288. package/src/page/themes/light-1.css +8 -7
  289. package/src/page/themes/light-1.source.css +8 -7
  290. package/src/page/toc-link.js +31 -0
  291. package/src/page/toc.js +5 -0
  292. package/src/page/view-hooks.js +38 -12
  293. package/src/page/view.js +13 -0
  294. package/src/page/ybind/binding.js +88 -5
  295. package/src/page/ybind/bridge.js +187 -20
  296. package/src/page/ybind/containment.js +162 -0
  297. package/src/page/ybind/doc.js +17 -9
  298. package/src/page/ybind/file-debt.js +63 -0
  299. package/src/page/ybind/layout.js +7 -2
  300. package/src/page/ybind/marks.js +1 -1
  301. package/src/page/ybind/moves.js +5 -4
  302. package/src/page/ybind/origins.js +106 -4
  303. package/src/page/ybind/parts.js +8 -1
  304. package/src/page/ybind/provider.js +113 -17
  305. package/src/page/ybind/recovery-proof.js +32 -9
  306. package/src/page/ybind/recovery-replay.js +558 -15
  307. package/src/page/ybind/recovery.js +174 -45
  308. package/src/page/ybind/table.js +67 -11
  309. package/src/page/ybind/walk-delta.js +258 -0
  310. package/src/page/ybind/walk.js +54 -29
  311. package/src/page/ybind/writer.js +1 -1
  312. package/src/plugins/ai-edit.js +13 -9
  313. package/src/plugins/indicator-chip.js +91 -0
  314. package/src/plugins/indicator.js +13 -80
  315. package/src/plugins/live.js +89 -14
  316. package/src/plugins/menu/actions.js +196 -0
  317. package/src/plugins/menu/people.js +279 -0
  318. package/src/plugins/menu/placement.js +121 -0
  319. package/src/plugins/menu/preferences.js +9 -0
  320. package/src/plugins/menu/seo.js +316 -397
  321. package/src/plugins/menu/style.js +171 -0
  322. package/src/plugins/menu/variants.js +296 -0
  323. package/src/plugins/menu.js +186 -183
  324. package/src/plugins/page-view.js +3 -2
  325. package/src/plugins/page.js +249 -44
  326. package/src/plugins/sortable.js +8 -1
  327. package/src/plugins/source.js +6 -0
  328. package/src/plugins/upgrade.js +51 -17
  329. package/src/plugins/upload-ui.js +12 -17
  330. package/src/plugins/visitor-drafts/bottom-ui.js +12 -4
  331. package/src/plugins/visitor-drafts/intro-style.js +7 -2
  332. package/src/plugins/visitor-drafts/intro.js +5 -3
  333. package/src/plugins/visitor-drafts/owner-chip.js +8 -12
  334. package/src/plugins/visitor-drafts/prompt.js +11 -9
  335. package/src/plugins/visitor-drafts/source.js +4 -3
  336. package/src/plugins/visitor-drafts/store.js +138 -9
  337. package/src/plugins/visitor-drafts/ui-memory.js +2 -2
  338. package/src/plugins/visitor-drafts-owner.js +4 -6
  339. package/src/plugins/visitor-drafts.js +5 -3
  340. package/src/recovery/store.js +1 -1
  341. package/src/route/route.js +4 -0
  342. package/src/server/rooms/index.js +5 -1
  343. package/src/server/rooms/live-data-rooms.js +302 -50
  344. package/src/server/rooms/page-compaction-worker.js +12 -0
  345. package/src/server/rooms/page-compactor.js +92 -0
  346. package/src/server/rooms/page-room-conformance.js +461 -0
  347. package/src/source/parse-steps.js +94 -0
  348. package/src/source/session.js +233 -57
  349. package/src/sync/editing-sender.js +12 -0
  350. package/src/sync/live-sync-edit.js +22 -2
  351. package/src/sync/live-sync.js +150 -23
  352. package/src/sync/page-relay.js +52 -0
  353. package/src/sync/presence-store.js +44 -4
  354. package/src/sync/presence.js +6 -3
  355. package/src/sync/saved-stream.js +11 -6
  356. package/src/ui/bevel-controls.js +1 -0
  357. package/src/ui/bevel-dialog.js +5 -2
  358. package/src/ui/editor-ui.js +77 -0
  359. package/src/ui/page-colour.js +281 -0
  360. package/src/ui/toast.js +16 -7
  361. package/src/vendor/hyper-html-api-upgrade.vendor.js +2 -2
  362. package/src/vendor/hyper-undo.vendor.js +1 -1
  363. package/src/vendor/hypercms.vendor.js +9 -9
  364. package/src/vendor/parse5.vendor.js +2 -2
  365. package/src/vendor/sap-core.vendor.js +5 -5
  366. package/dist/m/c-2SPVQSHC.js +0 -1
  367. package/dist/m/c-336SGU5W.js +0 -1
  368. package/dist/m/c-3H6BZDNJ.js +0 -6
  369. package/dist/m/c-4DFEJ2PO.js +0 -3
  370. package/dist/m/c-56QRZTZH.js +0 -1
  371. package/dist/m/c-5J4E7O4Y.js +0 -4
  372. package/dist/m/c-5LKDWQTL.js +0 -19
  373. package/dist/m/c-5V3MOVJT.js +0 -1
  374. package/dist/m/c-6AZOGAPE.js +0 -1
  375. package/dist/m/c-6CRUUXOX.js +0 -5
  376. package/dist/m/c-6HMB24EV.js +0 -1
  377. package/dist/m/c-6KEVEQ7L.js +0 -1
  378. package/dist/m/c-A3E3NIMC.js +0 -1
  379. package/dist/m/c-BQ6LOBEE.js +0 -1
  380. package/dist/m/c-BTDI4Y23.js +0 -1
  381. package/dist/m/c-CJ3A7ZKE.js +0 -1
  382. package/dist/m/c-CQHPGKX2.js +0 -1
  383. package/dist/m/c-DFS4XZJV.js +0 -2
  384. package/dist/m/c-DNENWY4O.js +0 -1
  385. package/dist/m/c-E4LIR5X5.js +0 -1
  386. package/dist/m/c-FDNHHZY5.js +0 -1
  387. package/dist/m/c-FROJUZMV.js +0 -1
  388. package/dist/m/c-G2JPKCTP.js +0 -1
  389. package/dist/m/c-GB6TU7WV.js +0 -1
  390. package/dist/m/c-GFJJEBTU.js +0 -1
  391. package/dist/m/c-GJAEGGXY.js +0 -1
  392. package/dist/m/c-GOWBDM6J.js +0 -1
  393. package/dist/m/c-HFKUVACH.js +0 -1
  394. package/dist/m/c-HHDFCNHV.js +0 -1
  395. package/dist/m/c-IEM4NTCS.js +0 -1
  396. package/dist/m/c-JDCX26UP.js +0 -25
  397. package/dist/m/c-KGVGROZT.js +0 -1
  398. package/dist/m/c-KQ6TYJAP.js +0 -135
  399. package/dist/m/c-LFPHCXY3.js +0 -1
  400. package/dist/m/c-LRPUO2MX.js +0 -1
  401. package/dist/m/c-LWLBHLVC.js +0 -3
  402. package/dist/m/c-M2L47S2X.js +0 -7
  403. package/dist/m/c-MFP2ZWD4.js +0 -1
  404. package/dist/m/c-MKSQ4Y57.js +0 -1
  405. package/dist/m/c-NUFFJVVJ.js +0 -1
  406. package/dist/m/c-NUHSPT5P.js +0 -1
  407. package/dist/m/c-O72HVYSA.js +0 -1
  408. package/dist/m/c-OIBUTDXI.js +0 -1
  409. package/dist/m/c-OW5QIGG3.js +0 -1
  410. package/dist/m/c-PGRCK73F.js +0 -1
  411. package/dist/m/c-Q7NQD65V.js +0 -1
  412. package/dist/m/c-QH63LQOS.js +0 -1
  413. package/dist/m/c-R7IWVINQ.js +0 -1
  414. package/dist/m/c-RM7J7BV2.js +0 -1
  415. package/dist/m/c-S4ZEH46B.js +0 -9
  416. package/dist/m/c-SDAXDM2D.js +0 -1
  417. package/dist/m/c-SMGU7RT3.js +0 -235
  418. package/dist/m/c-SPIORRYS.js +0 -1
  419. package/dist/m/c-SWLRT66R.js +0 -1
  420. package/dist/m/c-TCJTVAFN.js +0 -1
  421. package/dist/m/c-TQEMVJCS.js +0 -1
  422. package/dist/m/c-TYLXX4NF.js +0 -21
  423. package/dist/m/c-U62OB4BT.js +0 -1
  424. package/dist/m/c-U6YWUAR3.js +0 -8
  425. package/dist/m/c-UDCSU5G5.js +0 -2
  426. package/dist/m/c-VI5RMSVY.js +0 -1
  427. package/dist/m/c-VRZN56XY.js +0 -1
  428. package/dist/m/c-W5MCHEGA.js +0 -1
  429. package/dist/m/c-WNYBLIOZ.js +0 -1
  430. package/dist/m/c-WWXIDH4O.js +0 -28
  431. package/dist/m/c-XCNTK76U.js +0 -1
  432. package/dist/m/c-XMS6DPBR.js +0 -1
  433. package/dist/m/c-ZUCOKOHT.js +0 -1
  434. package/dist/m/c-ZVPVXTJF.js +0 -4
  435. package/dist/m/c-ZVRKXKM7.js +0 -1
  436. package/dist/m/core/autosave-HRYGUWV6.js +0 -1
  437. package/dist/m/core/conflict-notice-6MROJN2L.js +0 -1
  438. package/dist/m/core/is-edit-mode-KTWA3O5N.js +0 -1
  439. package/dist/m/core/recovery-FLTCC2OC.js +0 -1
  440. package/dist/m/core/save-HQZ4S667.js +0 -1
  441. package/dist/m/core/save-core-KGQWFUNH.js +0 -1
  442. package/dist/m/core/snapshot-EGQZNT45.js +0 -1
  443. package/dist/m/core/unsaved-warning-IDJLD66H.js +0 -1
  444. package/dist/m/loader-S4I7GKXR.js +0 -1
  445. package/dist/m/manifest-4C419D67.js +0 -1
  446. package/dist/m/manifest-edit-B077F288.js +0 -1
  447. package/dist/m/plugins/ai-edit-QN4PMMNL.js +0 -6
  448. package/dist/m/plugins/indicator-URDBBOQO.js +0 -1
  449. package/dist/m/plugins/live-HFWD4OIH.js +0 -1
  450. package/dist/m/plugins/menu-3HIEQSI6.js +0 -138
  451. package/dist/m/plugins/page-JOKB6XPR.js +0 -2
  452. package/dist/m/plugins/page-view-NXOJHNW6.js +0 -1
  453. package/dist/m/plugins/quickcrop-QNY3PKLD.js +0 -26
  454. package/dist/m/plugins/sortable-GWT4PWDV.js +0 -1
  455. package/dist/m/plugins/source-4ZRSAIYM.js +0 -1
  456. package/dist/m/plugins/undo-VDA5P7FH.js +0 -1
  457. package/dist/m/plugins/upgrade-W7FG5VFG.js +0 -1
  458. package/dist/m/plugins/visitor-drafts-LIDY7NH6.js +0 -21
  459. package/dist/m/plugins/visitor-drafts-owner-ZXCKBZE7.js +0 -1
  460. package/dist/m/reader-core-DXYCURFF.js +0 -68
  461. package/dist/m/route/route-KOCZSEGN.js +0 -1
  462. package/dist/m/sync/live-sync-J7VMYTKS.js +0 -1
  463. package/dist/m/ui/index-3JLVI27O.js +0 -1
  464. package/dist/m/vendor/sap-core.vendor-A7BMUYZT.js +0 -6
  465. /package/dist/m/{c-FILLTJA7.js → c-QTTJYJMA.js} +0 -0
  466. /package/dist/m/plugins/{quickcrop-QNY3PKLD.js.LEGAL.txt → quickcrop-BQ5YYFCH.js.LEGAL.txt} +0 -0
  467. /package/dist/m/plugins/{richclay-NRQDSWX6.js.LEGAL.txt → richclay-KLGETK77.js.LEGAL.txt} +0 -0
@@ -34,6 +34,15 @@ const boundaryRecord = (value) => {
34
34
 
35
35
  const sameBoundary = (a, b) => a.blockId === b.blockId && a.kind === b.kind && a.recoveryOf === b.recoveryOf;
36
36
 
37
+ // The keys walkFlow reads boundary and alias positions from: any key that parses to
38
+ // ["boundary" | "alias", client, clock], whatever its value.
39
+ const markerKey = (key) => {
40
+ let parts;
41
+ try { parts = JSON.parse(key); } catch { return null; }
42
+ if (!Array.isArray(parts) || parts.length !== 3 || (parts[0] !== "boundary" && parts[0] !== "alias")) return null;
43
+ return isClock(parts[1]) && isClock(parts[2]) ? parts : null;
44
+ };
45
+
37
46
  const recordKey = (key) => {
38
47
  if (typeof key !== "string" || key.charCodeAt(0) !== 91) return null;
39
48
  let parts;
@@ -46,6 +55,7 @@ const recordKey = (key) => {
46
55
  export function createOriginIndex(origins) {
47
56
  const spans = new Map();
48
57
  const aliases = new Map();
58
+ const aliasClocks = new Map();
49
59
  const boundaries = new Map();
50
60
 
51
61
  const lower = (list, clock) => {
@@ -53,13 +63,56 @@ export function createOriginIndex(origins) {
53
63
  while (lo < hi) { const mid = (lo + hi) >> 1; if (list[mid].clock < clock) lo = mid + 1; else hi = mid; }
54
64
  return lo;
55
65
  };
66
+ const lowerClock = (list, clock) => {
67
+ let lo = 0, hi = list.length;
68
+ while (lo < hi) { const mid = (lo + hi) >> 1; if (list[mid] < clock) lo = mid + 1; else hi = mid; }
69
+ return lo;
70
+ };
71
+ const markAlias = (client, clock, present) => {
72
+ const list = aliasClocks.get(client) ?? [];
73
+ const at = lowerClock(list, clock);
74
+ const there = list[at] === clock;
75
+ if (present && !there) list.splice(at, 0, clock);
76
+ else if (!present && there) list.splice(at, 1);
77
+ if (list.length) aliasClocks.set(client, list);
78
+ else aliasClocks.delete(client);
79
+ };
80
+ // Boundary and alias keys in the map's key order (Yjs keeps a key where it was first
81
+ // written), and each client's clocks that have one, sorted, for walkFlow.
82
+ const markerRecords = [];
83
+ const markerEntries = new Map();
84
+ const markerCounts = new Map();
85
+ const markerClocks = new Map();
86
+ const trackMarker = (key, live) => {
87
+ let entry = markerEntries.get(key);
88
+ if (entry === undefined) {
89
+ const parts = markerKey(key);
90
+ entry = parts ? { kind: parts[0], client: parts[1], clock: parts[2], live: false } : null;
91
+ markerEntries.set(key, entry);
92
+ if (entry) markerRecords.push(entry);
93
+ }
94
+ if (!entry || entry.live === live) return;
95
+ entry.live = live;
96
+ const counts = markerCounts.get(entry.client) ?? markerCounts.set(entry.client, new Map()).get(entry.client);
97
+ const list = markerClocks.get(entry.client) ?? markerClocks.set(entry.client, []).get(entry.client);
98
+ const n = (counts.get(entry.clock) ?? 0) + (live ? 1 : -1);
99
+ if (n) counts.set(entry.clock, n);
100
+ else counts.delete(entry.clock);
101
+ const at = lowerClock(list, entry.clock);
102
+ if (live && n === 1) list.splice(at, 0, entry.clock);
103
+ else if (!live && n === 0) list.splice(at, 1);
104
+ if (!list.length) { markerClocks.delete(entry.client); markerCounts.delete(entry.client); }
105
+ };
56
106
  const apply = (key, value) => {
107
+ trackMarker(key, value !== undefined);
57
108
  const parts = recordKey(key);
58
109
  if (!parts) return;
59
110
  const [kind, client, clock] = parts;
60
111
  if (kind === "alias") {
61
- if (value && isClock(value.sourceClient) && isClock(value.sourceClock)) aliases.set(idKey(client, clock), { client: value.sourceClient, clock: value.sourceClock });
112
+ const valid = Boolean(value && isClock(value.sourceClient) && isClock(value.sourceClock));
113
+ if (valid) aliases.set(idKey(client, clock), { client: value.sourceClient, clock: value.sourceClock });
62
114
  else aliases.delete(idKey(client, clock));
115
+ markAlias(client, clock, valid);
63
116
  return;
64
117
  }
65
118
  if (kind === "boundary") {
@@ -77,12 +130,17 @@ export function createOriginIndex(origins) {
77
130
  if (list.length) spans.set(client, list);
78
131
  else spans.delete(client);
79
132
  };
80
- for (const [key, value] of origins) apply(key, value);
133
+ for (const key of origins._map.keys()) apply(key, origins.get(key));
81
134
  const observer = (event) => { for (const key of event.keysChanged) apply(key, origins.get(key)); };
82
135
  origins.observe(observer);
83
136
 
137
+ const hasAlias = (client, clock) => {
138
+ const list = aliasClocks.get(client);
139
+ return list !== undefined && list[lowerClock(list, clock)] === clock;
140
+ };
141
+
84
142
  const resolve = (id) => {
85
- const alias = aliases.get(idKey(id.client, id.clock));
143
+ const alias = hasAlias(id.client, id.clock) ? aliases.get(idKey(id.client, id.clock)) : undefined;
86
144
  if (alias) return { client: alias.client, clock: alias.clock };
87
145
  const list = spans.get(id.client);
88
146
  if (!list) return { client: id.client, clock: id.clock };
@@ -95,8 +153,45 @@ export function createOriginIndex(origins) {
95
153
  return { client: record.sourceClient, clock: record.sourceClock + offset };
96
154
  };
97
155
 
156
+ const resolveRange = (client, clock, length) => {
157
+ const out = [];
158
+ const push = (sourceClient, sourceClock, size) => {
159
+ const last = out[out.length - 1];
160
+ if (last && last.client === sourceClient && last.clock + last.length === sourceClock) last.length += size;
161
+ else out.push({ client: sourceClient, clock: sourceClock, length: size });
162
+ };
163
+ const list = spans.get(client) ?? [];
164
+ const marks = aliasClocks.get(client) ?? [];
165
+ const end = clock + length;
166
+ let s = lower(list, clock + 1) - 1, a = lowerClock(marks, clock), at = clock;
167
+ while (at < end) {
168
+ if (a < marks.length && marks[a] === at) {
169
+ const target = aliases.get(idKey(client, at));
170
+ push(target.client, target.clock, 1);
171
+ at += 1;
172
+ a += 1;
173
+ } else {
174
+ let stop = end;
175
+ if (a < marks.length && marks[a] < stop) stop = marks[a];
176
+ if (s + 1 < list.length && list[s + 1].clock < stop) stop = list[s + 1].clock;
177
+ const span = s >= 0 ? list[s] : null;
178
+ if (span && at < span.clock + span.length) {
179
+ stop = Math.min(stop, span.clock + span.length);
180
+ push(span.sourceClient, span.sourceClock + at - span.clock, stop - at);
181
+ } else push(client, at, stop - at);
182
+ at = stop;
183
+ }
184
+ while (s + 1 < list.length && list[s + 1].clock <= at) s += 1;
185
+ }
186
+ return out;
187
+ };
188
+
98
189
  const boundaryOf = (id) => {
99
190
  if (!id || !isClock(id.client) || !isClock(id.clock)) return null;
191
+ if (!hasAlias(id.client, id.clock)) {
192
+ const record = boundaries.get(idKey(id.client, id.clock));
193
+ return record ? { ...record } : null;
194
+ }
100
195
  let key = idKey(id.client, id.clock);
101
196
  const seen = new Set();
102
197
  while (!seen.has(key)) {
@@ -164,8 +259,15 @@ export function createOriginIndex(origins) {
164
259
  if (!previous || !sameBoundary(previous, record)) throw conflicting(key);
165
260
  };
166
261
 
262
+ // null while a transaction that wrote origin records has not settled: the observer has
263
+ // not seen those keys yet, and walkFlow then reads the map's keys itself.
264
+ const markers = () => {
265
+ for (const t of origins.doc?._transactionCleanups ?? []) if (t.changed.has(origins)) return null;
266
+ return { records: markerRecords, clocks: markerClocks };
267
+ };
268
+
167
269
  return {
168
- origins, resolve, recordSpans, recordAlias, recordBoundary, boundaryOf,
270
+ origins, resolve, resolveRange, recordSpans, recordAlias, recordBoundary, boundaryOf, markers,
169
271
  get size() { return origins.size; },
170
272
  dispose() { origins.unobserve(observer); },
171
273
  };
@@ -1,5 +1,12 @@
1
1
  export const PART_KEY = /^(.+)\.(\d+)\.(\d+)$/;
2
- export const BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}(?:==)?|[A-Za-z0-9+/]{3}=?)?$/;
2
+ // A regexp group per quad keeps backtrack state per quad and overflows on a page of a few MB.
3
+ const NOT_BASE64 = /[^A-Za-z0-9+/]/;
4
+ export function isBase64(text) {
5
+ const pad = text.endsWith("==") ? 2 : text.endsWith("=") ? 1 : 0;
6
+ const body = text.length - pad;
7
+ if (NOT_BASE64.test(pad ? text.slice(0, body) : text)) return false;
8
+ return pad ? body % 4 + pad === 4 : body % 4 !== 1;
9
+ }
3
10
  const PART_CHARS = /^[A-Za-z0-9+/=\t\n\f\r ]*$/;
4
11
 
5
12
  export function entriesOf(y) {
@@ -2,8 +2,9 @@ import * as Y from "../../vendor/yjs.vendor.js";
2
2
  import { toBase64, fromBase64 } from "../../vendor/yjs.vendor.js";
3
3
  import { NET } from "./layout.js";
4
4
  import { ORIGINS_ORIGIN } from "./origins.js";
5
- import { PART_KEY, BASE64, entriesOf, captureYValue } from "./parts.js";
5
+ import { PART_KEY, isBase64, entriesOf, captureYValue } from "./parts.js";
6
6
  import { containsUpdate } from "./recovery-proof.js";
7
+ import { rangesOf, unite, within, docRanges } from "./containment.js";
7
8
 
8
9
  export { entriesOf } from "./parts.js";
9
10
 
@@ -63,7 +64,7 @@ export function docFromValue(value, { strict = false } = {}) {
63
64
  try {
64
65
  for (const [key, value] of entries) {
65
66
  const text = strict ? value.replace(/[\t\n\f\r ]/g, "") : value;
66
- if (strict && !BASE64.test(text)) throw new YDecodeError("invalid-update", `Invalid base64 in Yjs update: ${key}`);
67
+ if (strict && !isBase64(text)) throw new YDecodeError("invalid-update", `Invalid base64 in Yjs update: ${key}`);
67
68
  Y.applyUpdateV2(d, dec(text), NET);
68
69
  }
69
70
  if (strict && (d.store.pendingStructs || d.store.pendingDs)) {
@@ -84,7 +85,7 @@ const utf8Bytes = (text) => (encoder ? encoder.encode(text).length : text.length
84
85
  const sameDeletes = (a, b) => Y.equalDeleteSets(Y.createDeleteSetFromStructStore(a.store), Y.createDeleteSetFromStructStore(b.store));
85
86
  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; };
86
87
 
87
- export function createYProvider({ store, ydoc, compact: limits = {}, setTimer = setTimeout, clearTimer = clearTimeout, onStopped = () => {}, stats = { applied: 0, offered: 0, compactions: 0 }, journal = null }) {
88
+ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer = setTimeout, clearTimer = clearTimeout, onStopped = () => {}, stats = { applied: 0, offered: 0, compactions: 0 }, journal = null, onLocalUpdate = () => {} }) {
88
89
  const initial = entriesOf(store.value.y);
89
90
  const known = new Set();
90
91
  // Set when this tab's page can no longer reach the room: "lineage" when the room holds
@@ -99,8 +100,14 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
99
100
  let canCompact = !initial.some(([k]) => k[0] === "m");
100
101
  let counter = 0;
101
102
  const me = ydoc.clientID.toString(36);
102
- const nextKey = (p) => `${p}${me}-${(counter++).toString(36)}`;
103
+ const inst = Math.floor(Math.random() * 36 ** 6).toString(36);
104
+ let mode = store.room?.compaction?.v === 1 ? "host" : "legacy";
105
+ const nextKey = (p) => (mode === "host" ? `${p}${me}-${inst}-${(counter++).toString(36)}` : `${p}${me}-${(counter++).toString(36)}`);
103
106
  let timer = null;
107
+ let outbox = [];
108
+ // A refusal took back writes of this tab's that later updates build on: until a reconcile
109
+ // puts them back, nothing newer goes out.
110
+ let refusedGap = false;
104
111
  const generation = store.value.g;
105
112
  // Keys this tab wrote itself: the updates and offers the room has not confirmed yet.
106
113
  const own = new Set();
@@ -117,15 +124,50 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
117
124
  const parts = partsOf(key, enc(bytes));
118
125
  store.handle.update((data) => { for (const [k, v] of parts) data.y[k] = v; });
119
126
  };
127
+ const putWhole = (key, bytes) => {
128
+ known.add(key);
129
+ own.add(key);
130
+ const text = enc(bytes);
131
+ store.handle.update((data) => { data.y[key] = text; });
132
+ };
133
+ const isOwnYOp = (op) => {
134
+ const path = op.path;
135
+ if (!Array.isArray(path) || path.length !== 2 || path[0] !== "y") return true;
136
+ return own.has(path[1]);
137
+ };
138
+ const mineInFlight = () => store.pending.some((op) => isOwnYOp(op));
139
+ // Host mode orders this tab's writes only while the store is in the room: at save
140
+ // speed nothing confirms a write until the save lands, so updates go out at once.
141
+ const hosted = () => mode === "host" && store.handle.status?.mode !== "file";
142
+ const flushOutbox = () => {
143
+ if (!outbox.length) return;
144
+ const update = outbox.length === 1 ? outbox[0] : Y.mergeUpdatesV2(outbox);
145
+ outbox = [];
146
+ putWhole(nextKey("u"), update);
147
+ };
148
+ const offer = () => {
149
+ if (!hosted() || stopped || mineInFlight()) return;
150
+ if (refusedGap) {
151
+ refusedGap = false;
152
+ if (reconcile()) return;
153
+ }
154
+ if (!outbox.length) return;
155
+ const update = outbox.length === 1 ? outbox[0] : Y.mergeUpdatesV2(outbox);
156
+ outbox = [];
157
+ putWhole(nextKey("u"), update);
158
+ };
120
159
 
121
160
  for (const [k] of initial) known.add(k);
122
161
 
123
162
  // Ahead of the stop guards, and of the room's own delivery: work this tab can no longer
124
163
  // offer, and work a refused offer took back, is still work a crash must not lose.
125
164
  const onUpdate = (u, origin) => {
165
+ if (origin !== NET && origin !== ORIGINS_ORIGIN) onLocalUpdate(u);
126
166
  if (origin !== NET && origin !== ORIGINS_ORIGIN && origin !== RECOVERY_ORIGIN) journal?.local(u);
127
167
  if (stopped || origin === NET || origin === ORIGINS_ORIGIN) return;
128
- put(nextKey("u"), u);
168
+ if (hosted()) { outbox.push(u); offer(); }
169
+ else if (mode === "host") { outbox.push(u); flushOutbox(); }
170
+ else put(nextKey("u"), u);
129
171
  };
130
172
  ydoc.on("updateV2", onUpdate);
131
173
 
@@ -151,15 +193,24 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
151
193
  timer = null;
152
194
  const full = wantReconcile;
153
195
  if (full) reconcile();
154
- maybeCompact();
196
+ if (mode === "legacy") maybeCompact();
155
197
  }, 0);
156
198
  };
157
199
 
158
- const settle = () => { reconcile(); maybeCompact(); };
200
+ const settle = () => { reconcile(); if (mode === "legacy") maybeCompact(); };
201
+
202
+ // The room's entries already hold every item and deletion of this tab's document, read
203
+ // from each entry's cached ranges: nothing to offer, and no copy of the room is built.
204
+ const roomHolds = (y) => {
205
+ const mine = docRanges(ydoc);
206
+ return mine.complete && within(unite(entriesOf(y ?? {}).map(([k, v]) => rangesFor(k, v))), mine);
207
+ };
159
208
 
160
209
  function reconcile() {
161
210
  if (stopped) return false;
211
+ if (hosted() && mineInFlight()) return false;
162
212
  const y = store.value.y;
213
+ if (roomHolds(y)) { wantReconcile = false; reconcilePending = false; return false; }
163
214
  const scratch = new Y.Doc();
164
215
  for (const [, v] of entriesOf(y)) Y.applyUpdateV2(scratch, dec(v), NET);
165
216
  const sv = Y.encodeStateVector(scratch);
@@ -169,7 +220,8 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
169
220
  const diff = Y.encodeStateAsUpdateV2(ydoc, sv);
170
221
  scratch.destroy();
171
222
  stats.offered++;
172
- put(nextKey("r"), diff);
223
+ if (mode === "host") { outbox = []; putWhole(nextKey("r"), diff); }
224
+ else put(nextKey("r"), diff);
173
225
  wantReconcile = false;
174
226
  reconcilePending = false;
175
227
  return true;
@@ -182,24 +234,38 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
182
234
  let quietUntil = 0;
183
235
  const COVER_MAX = 256;
184
236
  const coverCache = new Map();
237
+ const META_MAX = 256;
238
+ const metas = new Map();
185
239
  const drop = (keys) => store.handle.update((data) => { for (const k of keys) delete data.y[k]; });
186
240
  const keysOf = (auth, bases) => Object.keys(auth).filter((k) => bases.has(baseOf(k)));
187
241
  // A key is retired only while what this tab shows under it is still what the proof was
188
242
  // made over: a late write under the same key keeps its own entry.
189
243
  const retire = (auth, keys) => { const visible = store.value.y ?? {}; drop(keys.filter((k) => k in visible && visible[k] === auth[k])); };
190
244
 
191
- // `known` and the coverage memo keep live references only: the authoritative entries,
192
- // this tab's own unconfirmed writes and the fold in progress. Pruned after a rebase, a
193
- // fold and a refusal. A key that is forgotten can be decoded again; Yjs is idempotent.
245
+ // The items and deletions one entry holds, read once per value.
246
+ const rangesFor = (key, value) => {
247
+ const meta = metas.get(key);
248
+ if (meta && meta.value === value) return meta.ranges;
249
+ if (metas.size >= META_MAX) metas.delete(metas.keys().next().value);
250
+ const ranges = rangesOf(dec(value));
251
+ metas.set(key, { value, ranges });
252
+ return ranges;
253
+ };
254
+
255
+ // `known` and the entry memo keep live references only: the authoritative entries, by
256
+ // their whole keys, this tab's own unconfirmed writes and the fold in progress. Pruned
257
+ // after a rebase, a fold and a refusal. A key that is forgotten can be decoded again;
258
+ // Yjs is idempotent.
194
259
  function prune() {
195
260
  const auth = store.authoritative.y ?? {};
196
261
  const visible = store.value.y ?? {};
197
262
  for (const k of own) if (!(k in visible)) own.delete(k);
198
- const held = fold ? [...fold.parts.map(([k]) => k), ...fold.inputs.keys()] : [];
199
- const retained = new Set([...Object.keys(auth), ...own, ...held]);
200
- for (const key of known) if (!retained.has(key)) known.delete(key);
201
263
  const values = new Map(entriesOf(auth));
264
+ const held = fold ? [fold.key, ...fold.parts.map(([k]) => k), ...fold.inputs.keys()] : [];
265
+ const retained = new Set([...Object.keys(auth), ...values.keys(), ...own, ...held]);
266
+ for (const key of known) if (!retained.has(key)) known.delete(key);
202
267
  for (const [key, proof] of coverCache) if (!retained.has(key) || values.get(key) !== proof.value) coverCache.delete(key);
268
+ for (const [key, meta] of metas) if (!retained.has(key) || (values.has(key) && values.get(key) !== meta.value)) metas.delete(key);
203
269
  }
204
270
 
205
271
  // Whether a fold's merged bytes already carry this input: merging it in again changes
@@ -240,7 +306,15 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
240
306
  prune();
241
307
  return true;
242
308
  }
243
- if (store.pending.length || !stillHeld(auth, active) || !coversInputs(active)) return false;
309
+ if (store.pending.length) return false;
310
+ if (!stillHeld(auth, active) || !coversInputs(active)) {
311
+ // A fold that can never speak for its inputs is re-planned, never held.
312
+ fold = null;
313
+ if (arrived.length) retire(auth, arrived.map(([k]) => k));
314
+ quietUntil = entriesOf(auth).length + compact.keys;
315
+ prune();
316
+ return true;
317
+ }
244
318
  fold = null;
245
319
  retire(auth, keysOf(auth, active.inputs));
246
320
  prune();
@@ -306,7 +380,7 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
306
380
  // with this tab's document is a page rebuilt from another article while the tab was away.
307
381
  const clientsOf = (y) => {
308
382
  const clients = new Set();
309
- for (const [, v] of entriesOf(y ?? {})) for (const c of Y.decodeStateVector(Y.encodeStateVectorFromUpdateV2(dec(v))).keys()) clients.add(c);
383
+ for (const [k, v] of entriesOf(y ?? {})) for (const [c, spans] of rangesFor(k, v).items) if (spans[0][0] === 0) clients.add(c);
310
384
  return clients;
311
385
  };
312
386
  // A page whose article has no blocks seeds with no client at all; it still shares its
@@ -321,6 +395,18 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
321
395
 
322
396
  const off = store.onRebase((ev) => {
323
397
  if (stopped) return;
398
+ if (ev.room) {
399
+ const was = mode;
400
+ mode = ev.room.compaction?.v === 1 ? "host" : "legacy";
401
+ // A room that stopped compacting confirms nothing in host order: what host mode
402
+ // was holding back goes out as ordinary updates.
403
+ if (was === "host" && mode === "legacy" && outbox.length) setTimer(() => {
404
+ if (stopped || mode !== "legacy" || !outbox.length) return;
405
+ const update = outbox.length === 1 ? outbox[0] : Y.mergeUpdatesV2(outbox);
406
+ outbox = [];
407
+ put(nextKey("u"), update);
408
+ }, 0);
409
+ }
324
410
  const ops = ev.source === "remote" && ev.frame ? ev.frame.ops : null;
325
411
  const plain = ops && ops.every((op) => op.path[0] === "y" && op.path.length === 2 && !PART_KEY.test(op.path[1]));
326
412
  if (!plain && ev.cause !== "refused" && !sharesLineage(ev.next.y)) {
@@ -334,6 +420,7 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
334
420
  }
335
421
  prune();
336
422
  schedule(ev.source !== "remote" || !ops, ev.source !== "remote");
423
+ offer();
337
424
  });
338
425
 
339
426
  // A page update the room refused as too large is never offered again: the difference would
@@ -342,6 +429,12 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
342
429
  // hold whole, and the room's page stays one a checkpoint can render.
343
430
  const offDelivery = store.onDelivery((event) => {
344
431
  if (event.phase !== "refused") return;
432
+ if (mode === "host") {
433
+ if (event.code === "page-full" || event.code === "compaction-failed") { stop(event.code); return; }
434
+ refusedGap = true;
435
+ schedule(true, false);
436
+ return;
437
+ }
345
438
  if (event.code !== "too-large" || stopped) { prune(); return; }
346
439
  stop("too-large");
347
440
  const auth = store.authoritative.y ?? {};
@@ -352,7 +445,10 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
352
445
  prune();
353
446
  });
354
447
 
448
+ const offConfirm = store.onConfirm(() => { offer(); });
449
+
355
450
  return { known, stats, settle, reconcile, get reconcilePending() { return reconcilePending; }, get stopped() { return stopped; },
451
+ get pending() { return outbox.length > 0; },
356
452
  // Test-only snapshot of the counters and cache sizes; no mutable set leaves the provider.
357
- _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); } };
453
+ _diagnostics: () => ({ known: known.size, cover: coverCache.size, fold: fold ? fold.parts.length : 0, generation }), compact: maybeCompact, dispose() { off(); offDelivery(); offConfirm(); ydoc.off("updateV2", onUpdate); if (timer) clearTimer(timer); } };
358
454
  }
@@ -14,8 +14,9 @@
14
14
  * state and the receipt arrive in the proof itself.
15
15
  */
16
16
 
17
- import { encodeStateAsUpdateV2, mergeUpdatesV2 } from "../../vendor/yjs.vendor.js";
17
+ import { encodeStateAsUpdateV2 } from "../../vendor/yjs.vendor.js";
18
18
  import { docFromValue } from "./provider.js";
19
+ import { indexOf, indexContains } from "./containment.js";
19
20
 
20
21
  export class PageProofError extends Error {
21
22
  constructor(code, message) {
@@ -40,17 +41,19 @@ const copyState = (state) => ({ v: state.v, g: state.g, y: { ...(state.y ?? {})
40
41
  export const equalBytes = (a, b) => a.length === b.length && a.every((value, index) => value === b[index]);
41
42
 
42
43
  /**
43
- * Whether merging `update` into `checkpoint` adds nothing: no insertion, no deletion.
44
+ * Whether applying `update` on top of `checkpoint` adds nothing: no insertion, no
45
+ * deletion. Compared by structure (the items and deletions the checkpoint holds), since
46
+ * merged bytes are not stable across merge orders. An update whose dependencies the
47
+ * checkpoint lacks is not contained: Yjs would hold it pending rather than refuse it. The
48
+ * checkpoint is integrated once for as long as its bytes are held, and each update after
49
+ * it is read, not applied.
44
50
  *
45
51
  * @param {Uint8Array} checkpoint - canonical or merged Yjs V2 update bytes
46
52
  * @param {Uint8Array} update - the bytes to look for
47
- * @param {Object} [Yjs] - the vendored Yjs module
48
53
  * @returns {boolean}
49
54
  */
50
- export function containsUpdate(checkpoint, update, Yjs = null) {
51
- const merge = Yjs?.mergeUpdatesV2 ?? mergeUpdatesV2;
52
- const canonical = merge([checkpoint]);
53
- return equalBytes(canonical, merge([canonical, update]));
55
+ export function containsUpdate(checkpoint, update) {
56
+ return indexContains(indexOf(checkpoint), update);
54
57
  }
55
58
 
56
59
  export async function digestHex(bytes) {
@@ -58,27 +61,47 @@ export async function digestHex(bytes) {
58
61
  return [...new Uint8Array(digest)].map((value) => value.toString(16).padStart(2, "0")).join("");
59
62
  }
60
63
 
64
+ // The last few states decoded, by their entries: a proof, its validation, the file debt
65
+ // and the journal's rebase all ask for the same state's bytes after one accepted save.
66
+ const CANONICAL_MAX = 4;
67
+ const canonical = [];
68
+ const sameState = (memo, state) => {
69
+ if (memo.v !== state.v || memo.g !== state.g) return false;
70
+ const keys = Object.keys(state.y ?? {});
71
+ if (keys.length !== memo.y.size) return false;
72
+ for (const key of keys) if (memo.y.get(key) !== state.y[key]) return false;
73
+ return true;
74
+ };
75
+
61
76
  /**
62
77
  * The accepted page state as one canonical update, decoded strictly.
63
78
  *
64
79
  * Pending structs, missing part and a delete set naming an unknown item all refuse:
65
- * a checkpoint nobody can decode proves nothing about a journaled record.
80
+ * a checkpoint nobody can decode proves nothing about a journaled record. The same state
81
+ * answers with the same bytes, which no caller may change.
66
82
  *
67
83
  * @param {Object} state - a version 1 page value
68
84
  * @returns {Uint8Array}
69
85
  */
70
86
  export function canonicalPageBytes(state) {
87
+ const hit = state && typeof state === "object" && state.y && typeof state.y === "object" ? canonical.find((memo) => sameState(memo, state)) : null;
88
+ if (hit) return hit.bytes;
71
89
  let doc;
72
90
  try {
73
91
  doc = docFromValue(state, { strict: true });
74
92
  } catch (error) {
75
93
  fail("invalid-state", `The accepted page state does not decode: ${error.message}`);
76
94
  }
95
+ let bytes;
77
96
  try {
78
- return encodeStateAsUpdateV2(doc);
97
+ bytes = encodeStateAsUpdateV2(doc);
98
+ indexOf(bytes, doc);
79
99
  } finally {
80
100
  doc.destroy();
81
101
  }
102
+ if (canonical.length >= CANONICAL_MAX) canonical.shift();
103
+ canonical.push({ v: state.v, g: state.g, y: new Map(Object.entries(state.y)), bytes });
104
+ return bytes;
82
105
  }
83
106
 
84
107
  // The same answer `sameAuthority` gives in recovery/identity.js, written here because the