@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
@@ -1,14 +1,15 @@
1
1
  import * as Y from "../../vendor/yjs.vendor.js";
2
2
  import { REMOTE_META, LOCAL, HISTORY_META, attrKeys, sameJson, parseKey, mintOp } from "./layout.js";
3
3
  import { walk, hashText } from "./walk.js";
4
+ import { captureDelta, rewalk, walkSignature } from "./walk-delta.js";
4
5
  import { docOf } from "./doc.js";
5
6
  import { localGroup } from "./group.js";
6
7
  import { yBefore, yAfter } from "./tokens.js";
7
8
  import { yIndexAt, posAtY, cellPoint, cellAt } from "./positions.js";
8
9
  import { state as pmState } from "../../vendor/prosemirror.vendor.js";
9
- import { repairSiblings } from "../model.js";
10
+ import { repairSiblings, isKey } from "../model.js";
10
11
  import { createOriginIndex, installUndoOrigins } from "./origins.js";
11
- import { reduceRecovery, canonicalRevision, restoreKey, physicalKeyOf, authorKey, RECOVERED_PREFIX, parsePhysicalKey, parseGoneIntents } from "./recovery.js";
12
+ import { reduceRecovery, reduceRecoverable, canonicalRevision, restoreKey, physicalKeyOf, authorKey, RECOVERED_PREFIX, parsePhysicalKey, parseGoneIntents } from "./recovery.js";
12
13
  import { moveBlocks } from "./moves.js";
13
14
  import { createBoardCommands } from "./board-commands.js";
14
15
  import { continuesGroup, intentOf } from "../history-intent.js";
@@ -20,6 +21,7 @@ export function sameBareShape(a, b) {
20
21
  if (a.childCount !== b.childCount) return false;
21
22
  for (let i = 0; i < a.childCount; i++) {
22
23
  const left = a.child(i), right = b.child(i);
24
+ if (left === right) continue;
23
25
  if (left.type !== right.type) return false;
24
26
  if (attrKeys(left.type).some((key) => !sameJson(left.attrs[key], right.attrs[key]))) return false;
25
27
  if ((left.isTextblock || left.type.name === "table") && !left.content.eq(right.content)) return false;
@@ -88,11 +90,46 @@ export function keepViewPos(derived, current = null) {
88
90
  }
89
91
  if (pos === block.attrs.pos && content === block.content) return block;
90
92
  changed = true;
91
- return block.type.create({ ...block.attrs, pos }, content, block.marks);
93
+ const attrs = { ...block.attrs, pos };
94
+ // The view's own node, when it is the node this would build: a block a full walk
95
+ // derived again unchanged keeps the object the view holds.
96
+ const prev = previous.get(block.attrs.id);
97
+ if (prev && content === block.content && prev.hasMarkup(block.type, attrs, block.marks) && prev.content.eq(content)) return prev;
98
+ return block.type.create(attrs, content, block.marks);
92
99
  });
93
100
  return changed ? derived.type.create(derived.attrs, placed) : derived;
94
101
  }
95
102
 
103
+ const orderedSiblings = (node) => {
104
+ const ids = new Set();
105
+ let previous = null;
106
+ for (let i = 0; i < node.childCount; i++) {
107
+ const { id, pos } = node.child(i).attrs;
108
+ if (!id || ids.has(id) || !isKey(pos) || (previous !== null && previous >= pos)) return false;
109
+ ids.add(id);
110
+ previous = pos;
111
+ }
112
+ return true;
113
+ };
114
+
115
+ const sameAttrsBesidePos = (left, right) => {
116
+ for (const key in left.attrs) if (key !== "pos" && left.attrs[key] !== right.attrs[key]) return false;
117
+ return true;
118
+ };
119
+
120
+ export function keepsView(current, derived) {
121
+ if (current.childCount !== derived.childCount || !orderedSiblings(current)) return false;
122
+ for (let i = 0; i < current.childCount; i++) {
123
+ const left = current.child(i), right = derived.child(i);
124
+ if (left === right) continue;
125
+ const markup = left.type === right.type && left.marks === right.marks && sameAttrsBesidePos(left, right);
126
+ if (!markup && !left.hasMarkup(right.type, { ...right.attrs, pos: left.attrs.pos }, right.marks)) return false;
127
+ if (!left.content.eq(right.content)) return false;
128
+ if (left.type.name === "table" && !orderedSiblings(left)) return false;
129
+ }
130
+ return true;
131
+ }
132
+
96
133
  const freezeDeep = (value) => {
97
134
  if (value && typeof value === "object") {
98
135
  for (const key of Object.keys(value)) freezeDeep(value[key]);
@@ -123,8 +160,16 @@ const newestLocalMarker = (pd, blockId) => {
123
160
 
124
161
  export function createBridge({ pd, onNotice = () => {}, ownedTokens = [], authorToken = null, stats = { groups: 0, mismatch: 0, renders: 0, first: null }, captureTimeout = 500 }) {
125
162
  let view = null;
126
- let w = walk(pd);
163
+ let w = null;
164
+ let docVersion = 0, walkedAt = -1;
165
+ // The one transaction since the last walk, when there was exactly one and it changed
166
+ // text inside one live block (walk-delta.js); any other history forces a full walk.
167
+ let delta = null, deltas = 0;
168
+ // A view node per walk segment it was proven equal to, so an unchanged block is not
169
+ // derived again and comes back as the same object the view holds.
170
+ const nodes = new WeakMap();
127
171
  const origins = createOriginIndex(pd.origins);
172
+ w = walk(pd, origins);
128
173
  const um = new Y.UndoManager([pd.flow, pd.attrs, pd.gone, pd.tableFields, pd.boardFields], {
129
174
  trackedOrigins: new Set([LOCAL]),
130
175
  captureTimeout,
@@ -159,12 +204,22 @@ export function createBridge({ pd, onNotice = () => {}, ownedTokens = [], author
159
204
  const authors = [...owners].filter(Boolean).sort();
160
205
  return { kind: "recovered-change", source: record.source, revision: record.revision, blockId: record.blockId, author: authors[0] ?? null, authors };
161
206
  };
162
- const recoveryState = () => {
163
- if (recoveryMemo && recoveryMemo.version === recoveryVersion) return recoveryMemo.snapshot;
164
- const reduced = reduceRecovery(pd, { index: origins });
207
+ const snapshotOf = (reduced) => {
165
208
  const recoverable = reduced.list.filter((record) => record.recoverable).map((record) => detached(record));
166
209
  const notices = detached(recoverable.map((record) => personalNotice(record)).filter((notice) => notice && !dismissed.has(`${notice.source}\u0000${notice.revision}`)));
167
- const snapshot = Object.freeze({ recoverable: Object.freeze(recoverable), notices: Object.freeze(notices) });
210
+ return Object.freeze({ recoverable: Object.freeze(recoverable), notices: Object.freeze(notices) });
211
+ };
212
+ const proveSnapshot = (snapshot, current) => {
213
+ const fresh = JSON.stringify(snapshotOf(reduceRecovery(pd, { index: origins, walked: current })));
214
+ if (fresh === JSON.stringify(snapshot)) { stats.provedRecoveries = (stats.provedRecoveries ?? 0) + 1; return; }
215
+ stats.walkMismatch = (stats.walkMismatch ?? 0) + 1;
216
+ throw new Error("Recovery snapshot built from the displacing sources diverged from a full reduction");
217
+ };
218
+ const recoveryState = () => {
219
+ if (recoveryMemo && recoveryMemo.version === recoveryVersion) return recoveryMemo.snapshot;
220
+ const current = walkedAt === docVersion ? w : undefined;
221
+ const snapshot = snapshotOf(reduceRecoverable(pd, { index: origins, walked: current }));
222
+ if (stats.proveWalks) proveSnapshot(snapshot, current);
168
223
  recoveryMemo = { version: recoveryVersion, snapshot };
169
224
  return snapshot;
170
225
  };
@@ -182,10 +237,75 @@ export function createBridge({ pd, onNotice = () => {}, ownedTokens = [], author
182
237
  recoverySignature = signature;
183
238
  for (const listener of [...recoveryListeners]) listener(snapshot);
184
239
  };
240
+ const walked = (result) => { w = result; walkedAt = docVersion; delta = null; deltas = 0; };
241
+ const currentWalk = () => (walkedAt === docVersion ? w : null);
242
+ const proveWalk = (next) => {
243
+ const a = walkSignature(pd, next), b = walkSignature(pd, walk(pd));
244
+ if (a === b) { stats.provedWalks = (stats.provedWalks ?? 0) + 1; return; }
245
+ stats.walkMismatch = (stats.walkMismatch ?? 0) + 1;
246
+ throw new Error(`Incremental walk diverged from the full walk:\n${a}\n---\n${b}`);
247
+ };
248
+ // Every walk the bridge takes runs after Yjs has settled (onSettled), when the index's
249
+ // observer has seen every origin record; the proof walks again with a fresh index that
250
+ // reads the boundary and alias keys from the map itself.
251
+ const intentsSignature = (intents) => JSON.stringify([intents.removals, intents.restores, intents.textDeletes, [...intents.copyActive], [...intents.attrs].map(([key, fields]) => [key, [...fields]])]);
252
+ const proveIntents = () => {
253
+ if (intentsSignature(parseGoneIntents(pd.gone)) === intentsSignature(parseGoneIntents(pd.gone, { kept: false }))) { stats.provedIntents = (stats.provedIntents ?? 0) + 1; return; }
254
+ stats.walkMismatch = (stats.walkMismatch ?? 0) + 1;
255
+ throw new Error("Removal records kept across writes diverged from a fresh parse of the gone map");
256
+ };
257
+ const proveIndexWalk = (full) => {
258
+ proveIntents();
259
+ const fresh = createOriginIndex(pd.origins);
260
+ let b;
261
+ try { b = walkSignature(pd, walk(pd, { resolve: fresh.resolve, boundaryOf: fresh.boundaryOf })); } finally { fresh.dispose(); }
262
+ const a = walkSignature(pd, full);
263
+ if (a === b) { stats.provedIndexWalks = (stats.provedIndexWalks ?? 0) + 1; return; }
264
+ stats.walkMismatch = (stats.walkMismatch ?? 0) + 1;
265
+ throw new Error(`A walk with the kept origin index diverged from one with a fresh index:\n${a}\n---\n${b}`);
266
+ };
267
+ const proveRecovery = () => {
268
+ const fresh = JSON.stringify(snapshotOf(reduceRecovery(pd, { index: origins, walked: w })));
269
+ if (fresh === JSON.stringify(recoveryState())) return;
270
+ stats.walkMismatch = (stats.walkMismatch ?? 0) + 1;
271
+ throw new Error("Recovery snapshot kept across an incremental walk diverged from a fresh one");
272
+ };
273
+ // Walks from the start, or re-walks the one block the only transaction since the last
274
+ // walk changed. Returns that transaction's delta when the walk was incremental.
275
+ const walkNow = () => {
276
+ const change = deltas === 1 ? delta : null;
277
+ const next = !change ? null : change.marker ? rewalk(pd, w, change.marker, change) : w;
278
+ if (!next) {
279
+ const full = walk(pd, origins);
280
+ if (stats.proveWalks) proveIndexWalk(full);
281
+ walked(full);
282
+ return null;
283
+ }
284
+ if (change.fields.size) for (const seg of next.visible) if (change.fields.has(seg.baseId)) nodes.delete(seg);
285
+ stats.incrementalWalks = (stats.incrementalWalks ?? 0) + 1;
286
+ if (stats.proveWalks) proveWalk(next);
287
+ walked(next);
288
+ return change;
289
+ };
290
+ const adopt = (doc) => {
291
+ if (doc.childCount !== w.visible.length) return;
292
+ for (let i = 0; i < doc.childCount; i++) if (!nodes.has(w.visible[i])) nodes.set(w.visible[i], doc.child(i));
293
+ };
294
+ const forget = (before, after) => {
295
+ if (before.childCount !== w.visible.length) return;
296
+ const d = docDiff(before, after);
297
+ if (!d) return;
298
+ const from = before.resolve(d.start).index(0), to = Math.min(before.childCount - 1, before.resolve(d.endA).index(0));
299
+ for (let i = from; i <= to; i++) nodes.delete(w.visible[i]);
300
+ };
185
301
  const refresh = () => {
186
- w = walk(pd);
187
- bumpRecovery();
188
- return docOf(pd, w);
302
+ const change = walkNow();
303
+ // Text this tab inserted into one live block has fresh ids, so it is no copy's source
304
+ // and the recovery snapshot stands. A deletion can displace a moved copy's source, and
305
+ // a peer's late insert can be the source of a hidden copy's displaced run.
306
+ if (!change || change.deleted || change.fields.size || (change.remote && w.hidden.length)) bumpRecovery();
307
+ else if (stats.proveWalks) proveRecovery();
308
+ return docOf(pd, w, nodes);
189
309
  };
190
310
  let saved = null;
191
311
  let lastIntent = null;
@@ -197,7 +317,7 @@ export function createBridge({ pd, onNotice = () => {}, ownedTokens = [], author
197
317
  saved = null;
198
318
  const derived = keepViewPos(fresh ?? refresh(), view.state.doc);
199
319
  const d = docDiff(view.state.doc, derived);
200
- if (!d) return;
320
+ if (!d) { adopt(view.state.doc); return; }
201
321
  const tr = view.state.tr.replace(d.start, d.endA, derived.slice(d.start, d.endB)).setMeta(REMOTE_META, true).setMeta("addToHistory", false);
202
322
  if (own) tr.setMeta("yundo", true);
203
323
  if (keep) {
@@ -226,6 +346,27 @@ export function createBridge({ pd, onNotice = () => {}, ownedTokens = [], author
226
346
  if (a !== null && a !== undefined && h !== null && h !== undefined) tr.setSelection(pmState.TextSelection.create(tr.doc, a, h));
227
347
  }
228
348
  view.dispatch(tr);
349
+ adopt(view.state.doc);
350
+ };
351
+ // A transaction that wrote nothing (an update the tab already had, Yjs's formatting
352
+ // cleanup after a remote insert) leaves the walk as it was: it is not a delta, it is
353
+ // never rendered, and a walk current before it stays current after it.
354
+ const began = new WeakMap();
355
+ const idle = new WeakSet();
356
+ const unchanged = (t) => {
357
+ if (t.changed.size || t.deleteSet.clients.size) return false;
358
+ for (const [client, clock] of t.afterState) if ((t.beforeState.get(client) ?? 0) !== clock) return false;
359
+ return true;
360
+ };
361
+ const stale = (t) => { began.set(t, docVersion); docVersion++; };
362
+ const onTransaction = (t) => {
363
+ if (unchanged(t)) {
364
+ idle.add(t);
365
+ if (walkedAt === began.get(t) && docVersion === walkedAt + 1) walkedAt = docVersion;
366
+ return;
367
+ }
368
+ deltas++;
369
+ delta = deltas === 1 ? captureDelta(pd, t, w) : null;
229
370
  };
230
371
  const onBefore = (t) => {
231
372
  if (groupOpen && !joining && t.origin === LOCAL) { um.stopCapturing(); groupOpen = false; }
@@ -245,12 +386,24 @@ export function createBridge({ pd, onNotice = () => {}, ownedTokens = [], author
245
386
  };
246
387
  const onAfter = (t) => {
247
388
  if (t.origin !== LOCAL) { lastIntent = null; groupOpen = false; }
389
+ };
390
+ // One render per batch Yjs settles (a transaction and those its observers queued), after
391
+ // its garbage collection and merges, so the walk it leaves is current for the next keystroke.
392
+ const onSettled = (doc, transactions) => {
393
+ let changed = false, local = false, own = false;
394
+ for (const t of transactions) {
395
+ if (idle.has(t)) continue;
396
+ changed = true;
397
+ if (t.origin === LOCAL) local = true;
398
+ else if (t.origin === um) own = true;
399
+ }
400
+ if (!changed) { saved = null; return; }
248
401
  if (!view) { bumpRecovery(); return; }
249
- if (t.origin === LOCAL) { saved = null; return; }
250
- render(t.origin === um);
402
+ if (local) { saved = null; return; }
403
+ render(own);
251
404
  };
252
405
  const portMove = (tr, move) => {
253
- const result = moveBlocks({ pd, view, um, origins, render }, move);
406
+ const result = moveBlocks({ pd, view, um, origins, render, walked: currentWalk() }, move);
254
407
  if (result.status !== "moved") {
255
408
  if (result.status === "refused") stats.moveRefusals = (stats.moveRefusals ?? 0) + 1;
256
409
  return result;
@@ -273,17 +426,23 @@ export function createBridge({ pd, onNotice = () => {}, ownedTokens = [], author
273
426
  return result;
274
427
  };
275
428
  const board = createBoardCommands({ pd, um, origins, render: () => (view ? render() : refresh()) });
429
+ pd.ydoc.on("beforeTransaction", stale);
276
430
  pd.ydoc.on("beforeTransaction", onBefore);
431
+ pd.ydoc.on("afterTransaction", onTransaction);
277
432
  pd.ydoc.on("afterTransaction", onAfter);
433
+ pd.ydoc.on("afterAllTransactions", onSettled);
278
434
  return {
279
435
  pd, stats, render, um, board,
280
436
  attach(v) {
281
437
  view = v;
282
- w = walk(pd);
438
+ walked(walk(pd, origins));
283
439
  },
284
440
  dispose() {
441
+ pd.ydoc.off("beforeTransaction", stale);
285
442
  pd.ydoc.off("beforeTransaction", onBefore);
443
+ pd.ydoc.off("afterTransaction", onTransaction);
286
444
  pd.ydoc.off("afterTransaction", onAfter);
445
+ pd.ydoc.off("afterAllTransactions", onSettled);
287
446
  offOrigins();
288
447
  origins.dispose();
289
448
  um.destroy();
@@ -320,7 +479,8 @@ export function createBridge({ pd, onNotice = () => {}, ownedTokens = [], author
320
479
  }
321
480
  return;
322
481
  }
323
- w = walk(pd);
482
+ if (walkedAt !== docVersion) walkNow();
483
+ else stats.reusedWalks = (stats.reusedWalks ?? 0) + 1;
324
484
  const history = transactions.every((t) => t.getMeta("addToHistory") !== false);
325
485
  const register = registerAuthor;
326
486
  registerAuthor = null;
@@ -334,10 +494,17 @@ export function createBridge({ pd, onNotice = () => {}, ownedTokens = [], author
334
494
  joining = false;
335
495
  }
336
496
  groupOpen = intent !== null;
497
+ const current = view.state.doc;
498
+ forget(local[0].before, current);
337
499
  const derived = refresh();
338
- if (!sameBareShape(derived, view.state.doc)) {
500
+ if (!sameBareShape(derived, current)) {
339
501
  stats.mismatch++;
340
- stats.first ??= { before: bareShape(local[0].before), pm: bareShape(view.state.doc), y: bareShape(derived) };
502
+ stats.first ??= { before: bareShape(local[0].before), pm: bareShape(current), y: bareShape(derived) };
503
+ } else if (keepsView(current, derived)) {
504
+ stats.renders++;
505
+ saved = null;
506
+ adopt(current);
507
+ return;
341
508
  }
342
509
  render(false, derived);
343
510
  },
@@ -377,7 +544,7 @@ export function createBridge({ pd, onNotice = () => {}, ownedTokens = [], author
377
544
  return { status: "restored", id: blockId };
378
545
  },
379
546
  moveBlocks(args) {
380
- return moveBlocks({ pd, view, um, origins, render }, args);
547
+ return moveBlocks({ pd, view, um, origins, render, walked: currentWalk() }, args);
381
548
  },
382
549
  undo() { return um.undo() !== null; },
383
550
  redo() { return um.redo() !== null; },
@@ -0,0 +1,162 @@
1
+ /**
2
+ * containment.js — whether an update adds anything to a state, asked without applying it.
3
+ *
4
+ * Applying an update changes nothing exactly when every item it carries is already there,
5
+ * by id, and every id it deletes is already deleted. So a state is read once into ranges,
6
+ * the clock ranges its items occupy and the ranges it deletes, and each update is then
7
+ * read, never applied: a hundred records cost one reading of the state, not a hundred.
8
+ *
9
+ * Two kinds of state answer. A state integrated into a document answers exactly as
10
+ * applying would, and one that leaves anything pending contains nothing. The union of some
11
+ * entries' own ranges answers for content: an update inside it adds no item and no explicit
12
+ * deletion any of those entries lacks, so retiring it loses nothing they do not hold. It can
13
+ * miss a deletion Yjs only makes while integrating, never claim one. A state vector alone
14
+ * proves nothing about deletions.
15
+ */
16
+
17
+ import { Doc, Skip, applyUpdateV2, decodeUpdateV2, createDeleteSetFromStructStore } from "../../vendor/yjs.vendor.js";
18
+
19
+ function merged(map) {
20
+ for (const [client, list] of map) {
21
+ list.sort((a, b) => a[0] - b[0]);
22
+ const out = [];
23
+ for (const range of list) {
24
+ const last = out[out.length - 1];
25
+ if (last && range[0] <= last[1]) { if (range[1] > last[1]) last[1] = range[1]; }
26
+ else out.push([range[0], range[1]]);
27
+ }
28
+ map.set(client, out);
29
+ }
30
+ return map;
31
+ }
32
+
33
+ const add = (map, client, from, to) => {
34
+ if (to <= from) return;
35
+ const list = map.get(client);
36
+ if (list) list.push([from, to]);
37
+ else map.set(client, [[from, to]]);
38
+ };
39
+
40
+ /**
41
+ * The clock ranges one update's items occupy and the ranges it deletes.
42
+ *
43
+ * @param {Uint8Array} update - a V2 update
44
+ * @returns {{items: Map<number, Array<[number, number]>>, deletes: Map<number, Array<[number, number]>>}}
45
+ */
46
+ export function rangesOf(update) {
47
+ const { structs, ds } = decodeUpdateV2(update);
48
+ const items = new Map();
49
+ for (const struct of structs) if (!(struct instanceof Skip)) add(items, struct.id.client, struct.id.clock, struct.id.clock + struct.length);
50
+ const deletes = new Map();
51
+ for (const [client, list] of ds.clients) for (const { clock, len } of list) add(deletes, client, clock, clock + len);
52
+ return { items: merged(items), deletes: merged(deletes) };
53
+ }
54
+
55
+ /**
56
+ * The ranges several entries hold together.
57
+ *
58
+ * @param {Array<Object>} list - from rangesOf() or unite()
59
+ * @returns {Object}
60
+ */
61
+ export function unite(list) {
62
+ if (list.length === 1) return list[0];
63
+ const items = new Map();
64
+ const deletes = new Map();
65
+ for (const ranges of list) {
66
+ for (const [client, spans] of ranges.items) for (const [from, to] of spans) add(items, client, from, to);
67
+ for (const [client, spans] of ranges.deletes) for (const [from, to] of spans) add(deletes, client, from, to);
68
+ }
69
+ return { items: merged(items), deletes: merged(deletes) };
70
+ }
71
+
72
+ function inside(spans, from, to) {
73
+ if (!spans) return false;
74
+ let lo = 0, hi = spans.length - 1;
75
+ while (lo <= hi) {
76
+ const mid = (lo + hi) >> 1;
77
+ const span = spans[mid];
78
+ if (from < span[0]) hi = mid - 1;
79
+ else if (from >= span[1]) lo = mid + 1;
80
+ else return to <= span[1];
81
+ }
82
+ return false;
83
+ }
84
+
85
+ /**
86
+ * Whether every item and deletion of `ranges` is already inside `state`.
87
+ *
88
+ * @param {Object} state - from unite(), rangesOf() or indexOf()
89
+ * @param {Object} ranges - from rangesOf()
90
+ * @returns {boolean}
91
+ */
92
+ export function within(state, ranges) {
93
+ if (state.complete === false) return false;
94
+ for (const [client, spans] of ranges.items) for (const [from, to] of spans) if (!inside(state.items.get(client), from, to)) return false;
95
+ for (const [client, spans] of ranges.deletes) for (const [from, to] of spans) if (!inside(state.deletes.get(client), from, to)) return false;
96
+ return true;
97
+ }
98
+
99
+ /**
100
+ * A document's state as ranges: every item from clock 0 to its state, and every deletion
101
+ * it holds, its own and the ones integration made.
102
+ *
103
+ * @param {Doc} doc
104
+ * @returns {Object}
105
+ */
106
+ export function docRanges(doc) {
107
+ if (doc.store.pendingStructs || doc.store.pendingDs) return { complete: false };
108
+ const items = new Map();
109
+ for (const [client, structs] of doc.store.clients) {
110
+ const last = structs[structs.length - 1];
111
+ add(items, client, 0, last.id.clock + last.length);
112
+ }
113
+ const deletes = new Map();
114
+ for (const [client, list] of createDeleteSetFromStructStore(doc.store).clients) for (const { clock, len } of list) add(deletes, client, clock, clock + len);
115
+ return { complete: true, items, deletes: merged(deletes) };
116
+ }
117
+
118
+ /**
119
+ * The state some updates make together, integrated once.
120
+ *
121
+ * @param {Array<Uint8Array>} updates
122
+ * @returns {Object}
123
+ */
124
+ export function stateIndex(updates) {
125
+ const doc = new Doc({ gc: false });
126
+ try {
127
+ for (const update of updates) applyUpdateV2(doc, update);
128
+ return docRanges(doc);
129
+ } finally {
130
+ doc.destroy();
131
+ }
132
+ }
133
+
134
+ const indexes = new WeakMap();
135
+
136
+ /**
137
+ * The state one checkpoint's bytes make, read once for as long as those bytes are held.
138
+ * The bytes are never written to after they are indexed.
139
+ *
140
+ * @param {Uint8Array} bytes
141
+ * @param {Doc} [doc] - the document these bytes were just encoded from, when there is one
142
+ * @returns {Object}
143
+ */
144
+ export function indexOf(bytes, doc = null) {
145
+ let index = indexes.get(bytes);
146
+ if (!index) {
147
+ index = doc ? docRanges(doc) : stateIndex([bytes]);
148
+ indexes.set(bytes, index);
149
+ }
150
+ return index;
151
+ }
152
+
153
+ /**
154
+ * Whether applying `update` to the indexed state would add no item and no deletion.
155
+ *
156
+ * @param {Object} index - from indexOf() or stateIndex()
157
+ * @param {Uint8Array} update
158
+ * @returns {boolean}
159
+ */
160
+ export function indexContains(index, update) {
161
+ return index.complete !== false && within(index, rangesOf(update));
162
+ }
@@ -26,16 +26,21 @@ const rawOf = (pd, b) => {
26
26
  return out;
27
27
  };
28
28
 
29
- export function docOf(pd, w) {
30
- const blocks = w.visible.map((b) => {
29
+ // `nodes`, when given, maps a walk segment to a node already proven equal to what this
30
+ // function would build for it (the bridge fills it from the view): that block costs
31
+ // nothing to derive again, and the same node object comes back.
32
+ export function docOf(pd, w, nodes = null) {
33
+ const build = (b) => {
31
34
  if (b.opaque) {
32
35
  const fields = b.recovered ? { ...copy(b.fields ?? {}) } : rawOf(pd, b);
33
36
  return { b, type: schema.nodes.opaque_block, fields, opaque: true };
34
37
  }
35
38
  const type = schema.nodes[b.t] ?? schema.nodes.paragraph;
36
39
  return { b, type, fields: { ...(b.fields ?? fieldsOf(pd, b, type)) } };
37
- });
38
- const byId = new Map(blocks.map((block) => [block.b.id, block]));
40
+ };
41
+ const blocks = w.visible.map((b) => { const node = nodes?.get(b); return node ? { b, node } : build(b); });
42
+ const byId = new Map(blocks.map((block, i) => [block.b.id, i]));
43
+ const parentOf = (i) => (blocks[i].node ? blocks[i].node.attrs.parent : blocks[i].fields.parent);
39
44
  const rankOf = (block) => {
40
45
  const item = block.b.recovered
41
46
  ? pd.gone._map.get(attrKey(block.b.sourceKey, "parent"))
@@ -48,17 +53,20 @@ export function docOf(pd, w) {
48
53
  while (byId.has(id) && !positions.has(id)) {
49
54
  positions.set(id, chain.length);
50
55
  chain.push(id);
51
- id = byId.get(id).fields.parent;
56
+ id = parentOf(byId.get(id));
52
57
  }
53
58
  if (!positions.has(id)) continue;
54
59
  const cycle = chain.slice(positions.get(id));
55
60
  const lowest = cycle.reduce((left, right) => {
56
- const a = rankOf(byId.get(left)), b = rankOf(byId.get(right));
61
+ const a = rankOf(blocks[byId.get(left)]), b = rankOf(blocks[byId.get(right)]);
57
62
  return a.clock < b.clock || (a.clock === b.clock && a.client < b.client) ? left : right;
58
63
  });
59
- byId.get(lowest).fields.parent = null;
64
+ const at = byId.get(lowest);
65
+ if (blocks[at].node) blocks[at] = build(blocks[at].b);
66
+ blocks[at].fields.parent = null;
60
67
  }
61
- const nodes = blocks.map(({ b, type, fields, opaque }) => {
68
+ const built = blocks.map(({ b, node, type, fields, opaque }) => {
69
+ if (node) return node;
62
70
  if (opaque) {
63
71
  const content = [];
64
72
  for (const r of b.runs) {
@@ -79,5 +87,5 @@ export function docOf(pd, w) {
79
87
  }
80
88
  return type.create(attrs, content);
81
89
  });
82
- return nodes.length ? schema.nodes.doc.create(null, nodes) : schema.nodes.doc.createAndFill();
90
+ return built.length ? schema.nodes.doc.create(null, built) : schema.nodes.doc.createAndFill();
83
91
  }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * file-debt.js — what this tab authored that no accepted file save has covered yet.
3
+ *
4
+ * One merged Yjs update per page binding, not a list of per-keystroke records and not an
5
+ * IndexedDB journal: a leave needs to know whether this tab's own input still owes the
6
+ * file, and it needs that answer synchronously. Adopting a peer's update, delivering one
7
+ * and compacting the store are not this tab's input, so none of them records here.
8
+ *
9
+ * Only accepted page bytes clear it, and only by the coverage primitive: a state that does
10
+ * not decode, or does not carry every insertion and deletion this tab owns, leaves the
11
+ * obligation exactly where it was.
12
+ */
13
+
14
+ import { mergeUpdatesV2 } from "../../vendor/yjs.vendor.js";
15
+ import { canonicalPageBytes, containsUpdate } from "./recovery-proof.js";
16
+
17
+ /**
18
+ * The file obligation of one page binding.
19
+ *
20
+ * @param {{changed?: Function}} [options] - notified whenever the answer may have changed
21
+ * @returns {{local: Function, pending: Function, accepted: Function}}
22
+ */
23
+ export function createPageFileDebt({ changed = () => {} } = {}) {
24
+ // Merged in runs of doubling length, as a binary counter carries: slot k holds 2^k of
25
+ // this tab's updates. A keystroke merges with the runs it completes, not with everything
26
+ // the session typed, so a long session without an accepted save stays cheap to type in.
27
+ let runs = [];
28
+ const owned = () => {
29
+ const parts = runs.filter(Boolean);
30
+ if (!parts.length) return null;
31
+ const merged = parts.length === 1 ? parts[0] : mergeUpdatesV2(parts);
32
+ runs = runs.map(() => null);
33
+ runs[runs.length - 1] = merged;
34
+ return merged;
35
+ };
36
+ return {
37
+ local(update) {
38
+ let carry = update.slice();
39
+ let k = 0;
40
+ for (; runs[k]; k++) {
41
+ carry = mergeUpdatesV2([runs[k], carry]);
42
+ runs[k] = null;
43
+ }
44
+ runs[k] = carry;
45
+ changed();
46
+ },
47
+ pending() {
48
+ return runs.some(Boolean);
49
+ },
50
+ accepted(state) {
51
+ const merged = owned();
52
+ if (!merged) return true;
53
+ let covered = false;
54
+ try {
55
+ covered = containsUpdate(canonicalPageBytes(state), merged);
56
+ } catch {}
57
+ if (!covered) return false;
58
+ runs = [];
59
+ changed();
60
+ return true;
61
+ },
62
+ };
63
+ }
@@ -9,9 +9,14 @@ export const HISTORY_META = Symbol("page-history");
9
9
  export const PAGE_ROOTS = Object.freeze(["flow", "attrs", "gone", "boardFields", "tableFields", "origins"]);
10
10
 
11
11
  const SKIP = new Set(["id", "pos"]);
12
- export const attrKeys = (type) => [...Object.keys(type.spec.attrs || {}).filter((k) => !SKIP.has(k)), ...(type.name === "table" ? ["rows"] : [])];
12
+ const KEYS = new WeakMap();
13
+ export const attrKeys = (type) => {
14
+ let keys = KEYS.get(type);
15
+ if (!keys) KEYS.set(type, keys = Object.freeze([...Object.keys(type.spec.attrs || {}).filter((k) => !SKIP.has(k)), ...(type.name === "table" ? ["rows"] : [])]));
16
+ return keys;
17
+ };
13
18
  export const defaultOf = (type, k) => type.name === "table" && k === "rows" ? {} : type.spec.attrs[k].default;
14
- export const sameJson = (a, b) => JSON.stringify(a) === JSON.stringify(b);
19
+ export const sameJson = (a, b) => a === b || JSON.stringify(a) === JSON.stringify(b);
15
20
  export const idKey = (id) => `${id.client}.${id.clock}`;
16
21
  export const parseKey = (k) => { const i = k.indexOf("."); return { client: +k.slice(0, i), clock: +k.slice(i + 1) }; };
17
22
  export const isHigh = (u) => u >= 0xd800 && u <= 0xdbff;
@@ -30,7 +30,7 @@ export function marksFromAttributes(a, blockType) {
30
30
  const marks = [];
31
31
  let unknown = null;
32
32
  for (const [n, v] of best) {
33
- if (schema.marks[n] && (!NATIVE_NAMES.has(n) || nativeAttrs(n, v))) marks.push(schema.marks[n].create(v === true ? null : v));
33
+ if (schema.marks[n] && (!NATIVE_NAMES.has(n) || nativeAttrs(n, v === true ? {} : v))) marks.push(schema.marks[n].create(v === true ? null : v));
34
34
  else (unknown ??= {})[n] = v;
35
35
  }
36
36
  // The walk asks this of every text run on every keystroke, and almost none carry a
@@ -4,6 +4,7 @@ import { state as pmState } from "../../vendor/prosemirror.vendor.js";
4
4
  import { walk } from "./walk.js";
5
5
  import { docOf } from "./doc.js";
6
6
  import { createProjectionMap } from "./projection-map.js";
7
+ import { beforeFence } from "./writer.js";
7
8
  import { unitIds } from "./origins.js";
8
9
  import { attrKey, copyActiveKey, parseGoneIntents, physicalKeyOf, recoveredRoot, removeKey, restoreKey } from "./recovery.js";
9
10
 
@@ -113,12 +114,12 @@ const liveRemovals = (pd, current, selected) => {
113
114
  return out;
114
115
  };
115
116
 
116
- export function moveBlocks({ pd, view, um, origins, render }, args = {}) {
117
+ export function moveBlocks({ pd, view, um, origins, render, walked = null }, args = {}) {
117
118
  const ids = Array.isArray(args?.ids) ? [...new Set(args.ids)] : [];
118
119
  const afterId = args?.afterId === undefined ? null : args.afterId;
119
120
  const hasParent = args?.parent !== undefined;
120
121
 
121
- const current = walk(pd, origins);
122
+ const current = walked ?? walk(pd, origins);
122
123
  // An opaque block is an operation this runtime must refuse before writing: it
123
124
  // has no turn-into, it cannot be carried, and its record is never rewritten.
124
125
  const protectedIds = new Set(current.visible.filter((seg) => seg.opaque).map((seg) => seg.id));
@@ -264,7 +265,7 @@ export function moveBlocks({ pd, view, um, origins, render }, args = {}) {
264
265
  let text = null, textAttrs = null;
265
266
  const flush = () => {
266
267
  if (text === null) return;
267
- pd.flow.insert(cursor, text, textAttrs ?? undefined);
268
+ beforeFence(pd, cursor, after.fences, () => pd.flow.insert(cursor, text, textAttrs ?? undefined));
268
269
  cursor += text.length;
269
270
  text = null;
270
271
  };
@@ -275,7 +276,7 @@ export function moveBlocks({ pd, view, um, origins, render }, args = {}) {
275
276
  text += unit.ch;
276
277
  } else {
277
278
  flush();
278
- pd.flow.insertEmbed(cursor, unit.embed, unit.attrs);
279
+ beforeFence(pd, cursor, after.fences, () => pd.flow.insertEmbed(cursor, unit.embed, unit.attrs));
279
280
  cursor += 1;
280
281
  }
281
282
  }