@panphora/clayjs 1.16.0 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (511) 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 +72519 -59839
  11. package/dist/m/attrs/refetch-on-save-ZUE7PSCI.js +1 -0
  12. package/dist/m/attrs/save-freeze-LWGF6XUN.js +1 -0
  13. package/dist/m/c-2GRUM77Q.js +30 -0
  14. package/dist/m/c-2KIEGNC4.js +1 -0
  15. package/dist/m/{c-5R4PTRG2.js → c-2KL3BD52.js} +1 -1
  16. package/dist/m/c-2YYDMLNZ.js +1 -0
  17. package/dist/m/c-2ZZBC45O.js +1 -0
  18. package/dist/m/c-372N6H3B.js +1 -0
  19. package/dist/m/{c-4WKFKEKF.js → c-3G2DGZUP.js} +1 -1
  20. package/dist/m/c-3LDGIBMN.js +1 -0
  21. package/dist/m/c-3O7OBDCM.js +1 -0
  22. package/dist/m/c-4ASQPRQL.js +1 -0
  23. package/dist/m/c-4TXNLX4K.js +6 -0
  24. package/dist/m/c-4YU3BR4D.js +6 -0
  25. package/dist/m/c-5734MF2P.js +1 -0
  26. package/dist/m/c-57XDY6HC.js +1 -0
  27. package/dist/m/{c-VFX45IZQ.js → c-5B4YBZXC.js} +1 -1
  28. package/dist/m/c-5ONTYIUI.js +1 -0
  29. package/dist/m/c-5OOVFCJM.js +1 -0
  30. package/dist/m/c-5T6NZHNM.js +1 -0
  31. package/dist/m/{c-YDH7Q3NY.js → c-5ZQV7KC4.js} +1 -1
  32. package/dist/m/{c-Z36J63PP.js → c-6DB2OLRJ.js} +1 -1
  33. package/dist/m/c-6DNRCPNM.js +1 -0
  34. package/dist/m/c-6UGWFMQC.js +2 -0
  35. package/dist/m/c-7K4SCQB7.js +1 -0
  36. package/dist/m/c-7MG5RU63.js +1 -0
  37. package/dist/m/c-7PRJCA3E.js +1 -0
  38. package/dist/m/c-A6L3FMUS.js +1 -0
  39. package/dist/m/c-AMU3BPPR.js +4 -0
  40. package/dist/m/c-AMYEFA2E.js +1 -0
  41. package/dist/m/c-AQYCG7QW.js +1 -0
  42. package/dist/m/c-ATKAOH23.js +1 -0
  43. package/dist/m/c-B3JM5ZXC.js +1 -0
  44. package/dist/m/c-B5CS55WR.js +1 -0
  45. package/dist/m/c-B5UJQT7W.js +1 -0
  46. package/dist/m/c-B7J4K2AE.js +3 -0
  47. package/dist/m/c-BHZYSHW4.js +1 -0
  48. package/dist/m/c-BIJALSRD.js +1 -0
  49. package/dist/m/c-BJ7YQ5QZ.js +1 -0
  50. package/dist/m/c-BPF2NSPV.js +1 -0
  51. package/dist/m/{c-KIPQJX4B.js → c-BRLCTNMC.js} +1 -1
  52. package/dist/m/c-BUAYFEV5.js +1 -0
  53. package/dist/m/c-C6IPRG34.js +1 -0
  54. package/dist/m/{c-2JL6E75N.js → c-CAS3NND4.js} +1 -1
  55. package/dist/m/{c-4DRMS4QG.js → c-CCQ25GFY.js} +1 -1
  56. package/dist/m/c-CJFDLD43.js +1 -0
  57. package/dist/m/c-COPTL5WB.js +1 -0
  58. package/dist/m/c-D3E4WJ4M.js +166 -0
  59. package/dist/m/c-D5IKEKXW.js +7 -0
  60. package/dist/m/c-DHBRA6DL.js +1 -0
  61. package/dist/m/c-DKLS74XB.js +72 -0
  62. package/dist/m/c-DVYGFTSK.js +3 -0
  63. package/dist/m/c-E3ZXARWA.js +1 -0
  64. package/dist/m/c-EK63G3CG.js +1 -0
  65. package/dist/m/c-ERQSNR6K.js +13 -0
  66. package/dist/m/c-EVYAISP3.js +1 -0
  67. package/dist/m/c-EXKHPOVC.js +3 -0
  68. package/dist/m/{c-7VGZ7RU5.js → c-FWKHM4E7.js} +1 -1
  69. package/dist/m/{c-4Q2XTQ2N.js → c-GBKB5WDL.js} +1 -1
  70. package/dist/m/c-GNDL3TKB.js +1 -0
  71. package/dist/m/c-GRWMD6O5.js +1 -0
  72. package/dist/m/c-GRXHHFPO.js +1 -0
  73. package/dist/m/{c-OYTFP5QN.js → c-GY422N3Q.js} +1 -1
  74. package/dist/m/c-HAWFNBYG.js +1 -0
  75. package/dist/m/c-HG2C77BM.js +4 -0
  76. package/dist/m/c-HHGJBU4D.js +1 -0
  77. package/dist/m/c-HMV6P4RX.js +1 -0
  78. package/dist/m/c-HMYWIHFG.js +1 -0
  79. package/dist/m/c-HT2DKBV2.js +1 -0
  80. package/dist/m/c-I2SCCUEW.js +1 -0
  81. package/dist/m/{c-HRDCD3WN.js → c-ICJYHABF.js} +2 -2
  82. package/dist/m/c-IMZCWYHR.js +7 -0
  83. package/dist/m/c-IVMD3FEM.js +1 -0
  84. package/dist/m/c-IVTRVENZ.js +1 -0
  85. package/dist/m/c-IW6GGOU2.js +1 -0
  86. package/dist/m/c-JKZJIUIP.js +1 -0
  87. package/dist/m/{c-NZMNXVF6.js → c-JNLX3RJB.js} +1 -1
  88. package/dist/m/c-JRKY44DK.js +1 -0
  89. package/dist/m/c-K3ADYNLU.js +1 -0
  90. package/dist/m/c-KBS3UV6B.js +1 -0
  91. package/dist/m/c-KGHVFRR3.js +1 -0
  92. package/dist/m/c-KJJPV77R.js +1 -0
  93. package/dist/m/{c-CZUSOB7V.js → c-KNPDOMDY.js} +1 -1
  94. package/dist/m/{c-5Y6TNX5W.js → c-KP64LT2A.js} +2 -2
  95. package/dist/m/c-KQ7E3PYY.js +1 -0
  96. package/dist/m/c-L3F5ZLIV.js +1 -0
  97. package/dist/m/{c-LR4CGEPT.js → c-L42OLXWM.js} +3 -2
  98. package/dist/m/c-L5VFATT6.js +1 -0
  99. package/dist/m/c-L7JL5TL3.js +3 -0
  100. package/dist/m/c-LKSL4AUJ.js +1 -0
  101. package/dist/m/c-M6FNQCTQ.js +1 -0
  102. package/dist/m/c-MCOOHCSG.js +1 -0
  103. package/dist/m/c-MHI3GYKF.js +1 -0
  104. package/dist/m/c-MWXFLW4J.js +1 -0
  105. package/dist/m/c-NEL7HQW3.js +1 -0
  106. package/dist/m/c-NQJRVJ56.js +1 -0
  107. package/dist/m/c-NWX3MFT4.js +1 -0
  108. package/dist/m/{c-HPURYVZS.js → c-OJYM642Q.js} +1 -1
  109. package/dist/m/c-OKXI52N7.js +1 -0
  110. package/dist/m/{c-MMUIUUZG.js → c-OPLUZR3A.js} +1 -1
  111. package/dist/m/{c-J7CA54IP.js → c-OTMKN2OO.js} +1 -1
  112. package/dist/m/{c-LTGMXXKO.js → c-OYRZ7XG2.js} +1 -1
  113. package/dist/m/c-P3OFYUMA.js +1 -0
  114. package/dist/m/c-PLN76GF3.js +6 -0
  115. package/dist/m/c-PLP4JKFC.js +1 -0
  116. package/dist/m/c-Q26MME6N.js +1 -0
  117. package/dist/m/{c-S7H2DRCB.js → c-Q3XOWZEV.js} +1 -1
  118. package/dist/m/{c-SWRJFCNN.js → c-QIKT6SKM.js} +1 -1
  119. package/dist/m/c-QIXT3Q7C.js +28 -0
  120. package/dist/m/c-QO4HNGCQ.js +1 -0
  121. package/dist/m/c-R27DWQKX.js +1 -0
  122. package/dist/m/{c-HVYXIS72.js → c-R2XUODVY.js} +1 -1
  123. package/dist/m/{c-QNKREOL6.js → c-R7LBFMF5.js} +1 -1
  124. package/dist/m/c-RND3G2S2.js +1 -0
  125. package/dist/m/c-S2U6NJU4.js +1 -0
  126. package/dist/m/c-SPZPGD3R.js +1 -0
  127. package/dist/m/c-SW4YEG2D.js +8 -0
  128. package/dist/m/c-SZGYW5HT.js +1 -0
  129. package/dist/m/c-T637T7SN.js +1 -0
  130. package/dist/m/c-TJAXGIRV.js +1 -0
  131. package/dist/m/{c-KKWLDOPM.js → c-TNEM3RNM.js} +1 -1
  132. package/dist/m/c-TTB5EK4S.js +32 -0
  133. package/dist/m/c-U7SHNWDH.js +1 -0
  134. package/dist/m/c-UDHIVLYH.js +20 -0
  135. package/dist/m/{c-BBVGTQG7.js → c-UGNV5MFB.js} +1 -1
  136. package/dist/m/{c-TQZ2JYHD.js → c-UORXLSD3.js} +1 -1
  137. package/dist/m/c-UPD33ZPE.js +1 -0
  138. package/dist/m/c-URSMSFCB.js +1 -0
  139. package/dist/m/c-UY4QX42B.js +1 -0
  140. package/dist/m/c-V44KWI2L.js +4 -0
  141. package/dist/m/c-VG5XIN4T.js +1 -0
  142. package/dist/m/c-VILENZTD.js +1 -0
  143. package/dist/m/{c-33HUYEDI.js → c-VJYEVRYK.js} +1 -1
  144. package/dist/m/c-VR2BJ4AN.js +1 -0
  145. package/dist/m/c-W4JCVLAB.js +1 -0
  146. package/dist/m/c-W6DQSHXI.js +135 -0
  147. package/dist/m/c-WBGWGOM7.js +1 -0
  148. package/dist/m/{c-4L3BTFRK.js → c-WCXLG3MV.js} +1 -1
  149. package/dist/m/c-WGW764WX.js +1 -0
  150. package/dist/m/c-WMRJKQ2V.js +5 -0
  151. package/dist/m/c-WNTYKCDG.js +1 -0
  152. package/dist/m/c-WQWIRWO3.js +72 -0
  153. package/dist/m/c-X5LNHET5.js +1 -0
  154. package/dist/m/c-X5TSXZYJ.js +2 -0
  155. package/dist/m/c-XGH4GS2T.js +1 -0
  156. package/dist/m/c-Y6F2FORL.js +1 -0
  157. package/dist/m/c-YBKHUAK7.js +10 -0
  158. package/dist/m/c-YBSS55KU.js +20 -0
  159. package/dist/m/c-YCLU4UNT.js +2 -0
  160. package/dist/m/c-YVZ2L7CE.js +1 -0
  161. package/dist/m/c-ZK4GIZH2.js +1 -0
  162. package/dist/m/c-ZRXJD4G6.js +1 -0
  163. package/dist/m/c-ZWBI4HYK.js +1 -0
  164. package/dist/m/{c-HT33EQRN.js → c-ZWIOJGMT.js} +1 -1
  165. package/dist/m/core/admin-attrs-YY7T5FGU.js +1 -0
  166. package/dist/m/core/autosave-S6OMNDCK.js +1 -0
  167. package/dist/m/core/conflict-notice-IWHMHH43.js +1 -0
  168. package/dist/m/core/host-permission-6EVJGNPL.js +1 -0
  169. package/dist/m/core/is-edit-mode-GCX6767E.js +1 -0
  170. package/dist/m/core/{persist-HKHA66IY.js → persist-UKXV5KLA.js} +1 -1
  171. package/dist/m/core/recovery-DRFZRJED.js +1 -0
  172. package/dist/m/core/save-LYY7K6U7.js +1 -0
  173. package/dist/m/core/save-core-MKRPSEEO.js +1 -0
  174. package/dist/m/core/snapshot-NU3YM6GR.js +1 -0
  175. package/dist/m/core/unsaved-warning-7QTKEN7Z.js +1 -0
  176. package/dist/m/events/index-RN2JNGBV.js +1 -0
  177. package/dist/m/internals/index-TGLQINN3.js +1 -0
  178. package/dist/m/lib/cache-bust-MUMFXZK6.js +1 -0
  179. package/dist/m/loader-3BBNZZGM.js +1 -0
  180. package/dist/m/manifest-F449FF98.js +1 -0
  181. package/dist/m/manifest-edit-4396A009.js +1 -0
  182. package/dist/m/manifest.json +2509 -1474
  183. package/dist/m/options/options-EJMS3K24.js +1 -0
  184. package/dist/m/page/editor-6LRB3GE4.js +304 -0
  185. package/dist/m/plugins/ai-edit-UTV7YO2I.js +6 -0
  186. package/dist/m/plugins/{cms-HP3LSTPN.js → cms-LRAOZHT7.js} +9 -9
  187. package/dist/m/plugins/demo-OYO5GCAO.js +1 -0
  188. package/dist/m/plugins/{derive-UAAA4V4S.js → derive-FRE5JJQE.js} +1 -1
  189. package/dist/m/plugins/indicator-4VDXWYTD.js +1 -0
  190. package/dist/m/plugins/live-AD4H6XSL.js +1 -0
  191. package/dist/m/plugins/menu-M5WW7DBF.js +1 -0
  192. package/dist/m/plugins/page-MD3T2ZX3.js +2 -0
  193. package/dist/m/plugins/page-view-PAWVZNFY.js +1 -0
  194. package/dist/m/plugins/people-JCHVPF7P.js +1 -0
  195. package/dist/m/plugins/quickcrop-BQ5YYFCH.js +26 -0
  196. package/dist/m/plugins/{richclay-RC3L2F6X.js → richclay-KLGETK77.js} +2 -2
  197. package/dist/m/plugins/sortable-4S6SQC3D.js +1 -0
  198. package/dist/m/plugins/source-TJXOIOYY.js +1 -0
  199. package/dist/m/plugins/undo-NLPGCJQY.js +1 -0
  200. package/dist/m/plugins/{unsent-HJFLQJ2O.js → unsent-OA4RGBUT.js} +1 -1
  201. package/dist/m/plugins/upgrade-HSUNDNJD.js +1 -0
  202. package/dist/m/plugins/upload-NHGJXZK2.js +1 -0
  203. package/dist/m/plugins/visitor-drafts-BW43DGY3.js +20 -0
  204. package/dist/m/plugins/visitor-drafts-owner-7ODQG7JX.js +1 -0
  205. package/dist/m/plugins/wire-VBWXEXU2.js +1 -0
  206. package/dist/m/reader-core-4W2KYEWQ.js +77 -0
  207. package/dist/m/route/route-62CHXL5B.js +1 -0
  208. package/dist/m/sync/live-sync-MO5DH33W.js +1 -0
  209. package/dist/m/ui/index-IVWN2QC4.js +1 -0
  210. package/dist/m/vendor/sap-core.vendor-RN4ZVY4X.js +6 -0
  211. package/dist/offline-manifest.json +1560 -889
  212. package/dist/page/themes/light-1.css +33 -8
  213. package/dist/server/core.cjs +1554 -311
  214. package/dist/server/offline-runtime.cjs +63 -63
  215. package/dist/server/offline-runtime.mjs +63 -63
  216. package/dist/server/page-compaction-worker.cjs +3710 -0
  217. package/dist/server/rooms.mjs +5 -0
  218. package/docs/guide.md +653 -101
  219. package/entries/sap.js +5 -5
  220. package/package.json +1 -1
  221. package/src/core/admin-contenteditable.js +5 -2
  222. package/src/core/admin-inputs.js +1 -0
  223. package/src/core/admin-onclick.js +2 -1
  224. package/src/core/boot-phase.js +2 -1
  225. package/src/core/conflict-download.js +3 -29
  226. package/src/core/conflict-notice-ui.js +4 -7
  227. package/src/core/download.js +32 -0
  228. package/src/core/host-meta.js +56 -0
  229. package/src/core/is-edit-mode.js +17 -1
  230. package/src/core/mode-key.js +125 -19
  231. package/src/core/mode-support.js +5 -0
  232. package/src/core/mode-transition.js +20 -6
  233. package/src/core/preserve-authored.js +4 -4
  234. package/src/core/recovery.js +121 -33
  235. package/src/core/save-accepted.js +33 -0
  236. package/src/core/save-core.js +111 -32
  237. package/src/core/save-job.js +108 -0
  238. package/src/core/save-prepare.js +53 -0
  239. package/src/core/save-size-notice.js +3 -1
  240. package/src/core/save-slices.js +15 -0
  241. package/src/core/save.js +557 -127
  242. package/src/core/serialize-steps.js +109 -0
  243. package/src/core/snapshot.js +209 -40
  244. package/src/core/source-map.js +83 -38
  245. package/src/core/stale-host-notice.js +1 -0
  246. package/src/core/upgrade-run.js +59 -8
  247. package/src/dom/active-element.js +5 -0
  248. package/src/lib/assets.js +1 -0
  249. package/src/lib/autosave-scheduler.js +3 -4
  250. package/src/lib/slice.js +65 -0
  251. package/src/lib/uimotion.js +774 -0
  252. package/src/lib/unsaved-state.js +2 -2
  253. package/src/live/fast-lane.js +10 -3
  254. package/src/live/local-lane.js +202 -11
  255. package/src/live/ops.js +16 -0
  256. package/src/live/page-compaction.js +162 -0
  257. package/src/live/server-lane.js +1 -0
  258. package/src/live/store.js +11 -3
  259. package/src/loader-logic.js +3 -1
  260. package/src/loader.js +14 -4
  261. package/src/page/adopt-boot.js +2 -1
  262. package/src/page/awareness.js +33 -2
  263. package/src/page/blocks.js +122 -27
  264. package/src/page/board-views.js +2 -16
  265. package/src/page/boot-pack.js +1 -1
  266. package/src/page/chart-tables.js +29 -0
  267. package/src/page/chart.js +411 -72
  268. package/src/page/checklist.js +10 -135
  269. package/src/page/checkpoint-proof.js +295 -0
  270. package/src/page/chrome.js +257 -40
  271. package/src/page/clipboard-shape.js +126 -0
  272. package/src/page/document-title-dom.js +12 -0
  273. package/src/page/document-title-runtime.js +15 -1
  274. package/src/page/document-title.js +4 -1
  275. package/src/page/drag.js +33 -3
  276. package/src/page/editor.js +207 -31
  277. package/src/page/emoji-data.js +162 -0
  278. package/src/page/export-assets.js +357 -0
  279. package/src/page/export-html.js +209 -0
  280. package/src/page/export-markdown.js +361 -0
  281. package/src/page/features/board.js +12 -3
  282. package/src/page/features/chart.js +11 -0
  283. package/src/page/features/checklist.js +428 -0
  284. package/src/page/features/dates.js +180 -0
  285. package/src/page/features/emoji.js +187 -0
  286. package/src/page/features/export-html.js +189 -0
  287. package/src/page/features/export-markdown.js +169 -0
  288. package/src/page/features/import-markdown.js +349 -0
  289. package/src/page/features/media.js +61 -5
  290. package/src/page/features/mentions.js +188 -0
  291. package/src/page/features/registry.js +24 -1
  292. package/src/page/features/table.js +375 -85
  293. package/src/page/features/title-cover.js +331 -0
  294. package/src/page/features/title-icon.js +213 -0
  295. package/src/page/features/toc.js +87 -0
  296. package/src/page/format-command.js +33 -0
  297. package/src/page/format-gate.js +68 -18
  298. package/src/page/format-host.js +71 -0
  299. package/src/page/format-native-commands.js +158 -0
  300. package/src/page/format-native.js +42 -0
  301. package/src/page/image-actions.js +64 -0
  302. package/src/page/import-markdown.js +262 -0
  303. package/src/page/inline.js +32 -8
  304. package/src/page/items.js +141 -11
  305. package/src/page/markdown.js +30 -2
  306. package/src/page/media.js +35 -119
  307. package/src/page/mention-picker.js +99 -0
  308. package/src/page/merge.js +1 -1
  309. package/src/page/placeholder.js +14 -1
  310. package/src/page/projection.js +156 -25
  311. package/src/page/recover.js +10 -12
  312. package/src/page/recovery-ui.js +464 -0
  313. package/src/page/rich/chart.js +63 -7
  314. package/src/page/save-projection.js +67 -0
  315. package/src/page/save-states.js +14 -4
  316. package/src/page/schema.js +52 -3
  317. package/src/page/table-paste.js +3 -1
  318. package/src/page/table-style.js +4 -4
  319. package/src/page/table.js +44 -2
  320. package/src/page/themes/content.css +25 -1
  321. package/src/page/themes/light-1.css +33 -8
  322. package/src/page/themes/light-1.source.css +8 -7
  323. package/src/page/toc-link.js +31 -0
  324. package/src/page/toc.js +5 -0
  325. package/src/page/view-hooks.js +58 -0
  326. package/src/page/view.js +13 -0
  327. package/src/page/ybind/binding.js +116 -6
  328. package/src/page/ybind/bridge.js +187 -20
  329. package/src/page/ybind/containment.js +162 -0
  330. package/src/page/ybind/doc.js +17 -9
  331. package/src/page/ybind/file-debt.js +63 -0
  332. package/src/page/ybind/layout.js +7 -2
  333. package/src/page/ybind/marks.js +2 -1
  334. package/src/page/ybind/moves.js +5 -4
  335. package/src/page/ybind/origins.js +106 -4
  336. package/src/page/ybind/parts.js +8 -1
  337. package/src/page/ybind/provider.js +125 -18
  338. package/src/page/ybind/recovery-proof.js +32 -9
  339. package/src/page/ybind/recovery-replay.js +905 -4
  340. package/src/page/ybind/recovery.js +174 -45
  341. package/src/page/ybind/table.js +67 -11
  342. package/src/page/ybind/walk-delta.js +258 -0
  343. package/src/page/ybind/walk.js +54 -29
  344. package/src/page/ybind/writer.js +1 -1
  345. package/src/plugins/ai-edit.js +13 -9
  346. package/src/plugins/indicator-chip.js +91 -0
  347. package/src/plugins/indicator.js +13 -80
  348. package/src/plugins/live.js +126 -15
  349. package/src/plugins/menu/actions.js +196 -0
  350. package/src/plugins/menu/people.js +279 -0
  351. package/src/plugins/menu/placement.js +121 -0
  352. package/src/plugins/menu/preferences.js +9 -0
  353. package/src/plugins/menu/seo.js +316 -397
  354. package/src/plugins/menu/style.js +171 -0
  355. package/src/plugins/menu/variants.js +296 -0
  356. package/src/plugins/menu.js +186 -183
  357. package/src/plugins/page-view.js +3 -2
  358. package/src/plugins/page.js +362 -43
  359. package/src/plugins/sortable.js +8 -1
  360. package/src/plugins/source.js +6 -0
  361. package/src/plugins/upgrade.js +51 -17
  362. package/src/plugins/upload-ui.js +12 -17
  363. package/src/plugins/visitor-drafts/bottom-ui.js +12 -4
  364. package/src/plugins/visitor-drafts/intro-style.js +7 -2
  365. package/src/plugins/visitor-drafts/intro.js +7 -4
  366. package/src/plugins/visitor-drafts/owner-chip.js +8 -12
  367. package/src/plugins/visitor-drafts/prompt.js +11 -9
  368. package/src/plugins/visitor-drafts/source.js +4 -3
  369. package/src/plugins/visitor-drafts/store.js +138 -9
  370. package/src/plugins/visitor-drafts/ui-memory.js +2 -2
  371. package/src/plugins/visitor-drafts-owner.js +4 -6
  372. package/src/plugins/visitor-drafts.js +5 -3
  373. package/src/recovery/store.js +1 -1
  374. package/src/route/route.js +4 -0
  375. package/src/server/checkpoint/index.js +2 -2
  376. package/src/server/rooms/index.js +5 -1
  377. package/src/server/rooms/live-data-rooms.js +302 -50
  378. package/src/server/rooms/page-compaction-worker.js +12 -0
  379. package/src/server/rooms/page-compactor.js +92 -0
  380. package/src/server/rooms/page-room-conformance.js +461 -0
  381. package/src/source/parse-steps.js +94 -0
  382. package/src/source/session.js +233 -57
  383. package/src/sync/editing-sender.js +12 -0
  384. package/src/sync/live-sync-edit.js +22 -2
  385. package/src/sync/live-sync.js +150 -23
  386. package/src/sync/page-relay.js +52 -0
  387. package/src/sync/presence-store.js +44 -4
  388. package/src/sync/presence.js +6 -3
  389. package/src/sync/saved-stream.js +28 -14
  390. package/src/ui/bevel-controls.js +1 -0
  391. package/src/ui/bevel-dialog.js +5 -2
  392. package/src/ui/editor-ui.js +77 -0
  393. package/src/ui/page-colour.js +281 -0
  394. package/src/ui/toast.js +16 -7
  395. package/src/vendor/hyper-html-api-upgrade.vendor.js +2 -2
  396. package/src/vendor/hyper-undo.vendor.js +1 -1
  397. package/src/vendor/hypercms.vendor.js +9 -9
  398. package/src/vendor/parse5.vendor.js +2 -2
  399. package/src/vendor/sap-core.vendor.js +5 -5
  400. package/dist/m/attrs/refetch-on-save-NWPPXOCZ.js +0 -1
  401. package/dist/m/attrs/save-freeze-FUP4Q4X3.js +0 -1
  402. package/dist/m/c-2M73252O.js +0 -1
  403. package/dist/m/c-2SPVQSHC.js +0 -1
  404. package/dist/m/c-35NBGMZB.js +0 -1
  405. package/dist/m/c-3IQRECYS.js +0 -1
  406. package/dist/m/c-4B6H4G5V.js +0 -1
  407. package/dist/m/c-4FFHZM32.js +0 -1
  408. package/dist/m/c-4YSCRICO.js +0 -1
  409. package/dist/m/c-54RCDCBU.js +0 -1
  410. package/dist/m/c-5M4YEKCX.js +0 -4
  411. package/dist/m/c-5V3MOVJT.js +0 -1
  412. package/dist/m/c-66O32MV3.js +0 -28
  413. package/dist/m/c-6HMB24EV.js +0 -1
  414. package/dist/m/c-7O6AFR65.js +0 -1
  415. package/dist/m/c-ANQTJ3B5.js +0 -2
  416. package/dist/m/c-AYVOREBE.js +0 -1
  417. package/dist/m/c-B2ZXPOS3.js +0 -3
  418. package/dist/m/c-BMIZKMPV.js +0 -1
  419. package/dist/m/c-BTDI4Y23.js +0 -1
  420. package/dist/m/c-CJZSLMVQ.js +0 -1
  421. package/dist/m/c-DBE6ORSI.js +0 -1
  422. package/dist/m/c-EDHUR4MX.js +0 -1
  423. package/dist/m/c-EY6UOM54.js +0 -1
  424. package/dist/m/c-F3CKJTJS.js +0 -1
  425. package/dist/m/c-F7Y6FLPY.js +0 -1
  426. package/dist/m/c-FA4DZVTC.js +0 -1
  427. package/dist/m/c-G2BUZITR.js +0 -1
  428. package/dist/m/c-GB3JTV6E.js +0 -1
  429. package/dist/m/c-GBGWXTX4.js +0 -1
  430. package/dist/m/c-GHBCCVG4.js +0 -1
  431. package/dist/m/c-GJAEGGXY.js +0 -1
  432. package/dist/m/c-GOWBDM6J.js +0 -1
  433. package/dist/m/c-IB52R237.js +0 -1
  434. package/dist/m/c-IKWNE4R4.js +0 -1
  435. package/dist/m/c-IU3IYEU6.js +0 -1
  436. package/dist/m/c-IZ2EBRAJ.js +0 -1
  437. package/dist/m/c-KR4UW4KW.js +0 -1
  438. package/dist/m/c-KY3VJGUA.js +0 -8
  439. package/dist/m/c-LFPHCXY3.js +0 -1
  440. package/dist/m/c-LRPUO2MX.js +0 -1
  441. package/dist/m/c-LXENEBV7.js +0 -1
  442. package/dist/m/c-NMJRXKPC.js +0 -8
  443. package/dist/m/c-OIBUTDXI.js +0 -1
  444. package/dist/m/c-PPOSCAQD.js +0 -1
  445. package/dist/m/c-QIX5BJYN.js +0 -1
  446. package/dist/m/c-QQHEX6HA.js +0 -1
  447. package/dist/m/c-R7RMANJI.js +0 -9
  448. package/dist/m/c-RI4NT75Y.js +0 -1
  449. package/dist/m/c-RM7J7BV2.js +0 -1
  450. package/dist/m/c-RUJKSUQW.js +0 -1
  451. package/dist/m/c-SGCCC4FW.js +0 -25
  452. package/dist/m/c-T4VO4OCB.js +0 -1
  453. package/dist/m/c-TQEMVJCS.js +0 -1
  454. package/dist/m/c-TR6RMDQ7.js +0 -3
  455. package/dist/m/c-UKHTLWUW.js +0 -1
  456. package/dist/m/c-UNN7LWCT.js +0 -1
  457. package/dist/m/c-UXBMRDZB.js +0 -235
  458. package/dist/m/c-V7PZZRG7.js +0 -1
  459. package/dist/m/c-VL52ADDR.js +0 -1
  460. package/dist/m/c-WNYBLIOZ.js +0 -1
  461. package/dist/m/c-WWDBYXLK.js +0 -1
  462. package/dist/m/c-XNNW2CWK.js +0 -1
  463. package/dist/m/c-Y6USGTS5.js +0 -5
  464. package/dist/m/c-YG2NJYS6.js +0 -135
  465. package/dist/m/c-Z4VRC7GU.js +0 -7
  466. package/dist/m/c-Z6K52OLP.js +0 -19
  467. package/dist/m/c-ZDY2MDAA.js +0 -21
  468. package/dist/m/c-ZUCOKOHT.js +0 -1
  469. package/dist/m/core/admin-attrs-VGNLAUDK.js +0 -1
  470. package/dist/m/core/autosave-E4PEFPZ6.js +0 -1
  471. package/dist/m/core/conflict-notice-VQ6Y64PH.js +0 -1
  472. package/dist/m/core/host-permission-XN3K2IW3.js +0 -1
  473. package/dist/m/core/is-edit-mode-JXHDVGUL.js +0 -1
  474. package/dist/m/core/recovery-TBBSICE2.js +0 -1
  475. package/dist/m/core/save-WUCHTQBE.js +0 -1
  476. package/dist/m/core/save-core-FJVHT5M3.js +0 -1
  477. package/dist/m/core/snapshot-F3SWDZMM.js +0 -1
  478. package/dist/m/core/unsaved-warning-STKBHE6G.js +0 -1
  479. package/dist/m/events/index-23DPTUT4.js +0 -1
  480. package/dist/m/internals/index-WOVAP3RU.js +0 -1
  481. package/dist/m/lib/cache-bust-CGWT6ZJI.js +0 -1
  482. package/dist/m/loader-HXX6YNH5.js +0 -1
  483. package/dist/m/manifest-F22DEDAC.js +0 -1
  484. package/dist/m/manifest-edit-C3C7376B.js +0 -1
  485. package/dist/m/options/options-2LH76JQ3.js +0 -1
  486. package/dist/m/page/editor-XUTB2HB2.js +0 -189
  487. package/dist/m/plugins/ai-edit-ZQ4CGWIP.js +0 -6
  488. package/dist/m/plugins/demo-ZIBFAA3R.js +0 -1
  489. package/dist/m/plugins/indicator-PV4YW2ID.js +0 -1
  490. package/dist/m/plugins/live-NUTTXSBS.js +0 -1
  491. package/dist/m/plugins/menu-HCKO3DSP.js +0 -138
  492. package/dist/m/plugins/page-KSUH7G6J.js +0 -2
  493. package/dist/m/plugins/page-view-W73OW7WI.js +0 -1
  494. package/dist/m/plugins/people-AJJ32YCG.js +0 -1
  495. package/dist/m/plugins/quickcrop-QNY3PKLD.js +0 -26
  496. package/dist/m/plugins/sortable-YRMQX3ZU.js +0 -1
  497. package/dist/m/plugins/source-KHK5UUSK.js +0 -1
  498. package/dist/m/plugins/undo-OF2L3CXU.js +0 -1
  499. package/dist/m/plugins/upgrade-ZNYODP5Y.js +0 -1
  500. package/dist/m/plugins/upload-XSGNILUY.js +0 -1
  501. package/dist/m/plugins/visitor-drafts-XK5GW55Q.js +0 -21
  502. package/dist/m/plugins/visitor-drafts-owner-KSSH75EV.js +0 -1
  503. package/dist/m/plugins/wire-ZWVWKF76.js +0 -1
  504. package/dist/m/reader-core-ILWLXVMP.js +0 -68
  505. package/dist/m/route/route-KOCZSEGN.js +0 -1
  506. package/dist/m/sync/live-sync-4AUVXVJU.js +0 -1
  507. package/dist/m/ui/index-EVHKMKBL.js +0 -1
  508. package/dist/m/vendor/sap-core.vendor-A7BMUYZT.js +0 -6
  509. /package/dist/m/{c-FILLTJA7.js → c-QTTJYJMA.js} +0 -0
  510. /package/dist/m/plugins/{quickcrop-QNY3PKLD.js.LEGAL.txt → quickcrop-BQ5YYFCH.js.LEGAL.txt} +0 -0
  511. /package/dist/m/plugins/{richclay-RC3L2F6X.js.LEGAL.txt → richclay-KLGETK77.js.LEGAL.txt} +0 -0
@@ -0,0 +1,109 @@
1
+ /**
2
+ * serialize-steps.js — the save's serializations as generators, for a document big enough
3
+ * to prepare its save across tasks.
4
+ *
5
+ * Each one returns exactly the string its one-call twin in snapshot.js returns, built child
6
+ * by child so it can yield between pieces. They live apart from snapshot.js because every
7
+ * page loads that at boot, and only the sliced save (core/save-job.js, a lazy chunk) and a
8
+ * sliced save's live-sync relay ever run these.
9
+ */
10
+
11
+ import {
12
+ clonePreventingOnclone,
13
+ noscriptBlocks,
14
+ prepareForCompare,
15
+ swapNoscriptMarkers
16
+ } from './snapshot.js';
17
+ import { TAB_LOCAL_ROOT_ATTRS } from '../lib/root-attrs.js';
18
+
19
+ const XHTML_NS = 'http://www.w3.org/1999/xhtml';
20
+
21
+ // How much serialized output a sliced save writes before it hands the page back, and how
22
+ // big a subtree has to be before it is serialized child by child instead of in one call.
23
+ const SLICE_BYTES = 64 * 1024;
24
+ const SLICE_MIN_ELEMENTS = 256;
25
+
26
+ function sliceable(el) {
27
+ return el.namespaceURI === XHTML_NS
28
+ && el.localName !== 'template'
29
+ && el.getElementsByTagName('*').length >= SLICE_MIN_ELEMENTS;
30
+ }
31
+
32
+ /**
33
+ * The children of `el`, each one step, `yield`ing every `SLICE_BYTES` of output. A text
34
+ * or comment child is serialized inside `bare`, a childless copy of its parent, so its
35
+ * escaping follows the same parent element the one-call serializer would use.
36
+ */
37
+ function* serializeChildrenSteps(el, bare, out, budget) {
38
+ for (const child of el.childNodes) {
39
+ if (child.nodeType === 1 && sliceable(child)) {
40
+ yield* serializeElementSteps(child, out, budget);
41
+ continue;
42
+ }
43
+ let piece;
44
+ if (child.nodeType === 1) piece = child.outerHTML;
45
+ else {
46
+ bare.replaceChildren(child.cloneNode(false));
47
+ piece = bare.innerHTML;
48
+ }
49
+ out.push(piece);
50
+ budget.bytes += piece.length;
51
+ if (budget.bytes >= SLICE_BYTES) {
52
+ budget.bytes = 0;
53
+ yield;
54
+ }
55
+ }
56
+ }
57
+
58
+ /**
59
+ * `el.outerHTML`, built from its open tag, each child's own serialization and its end
60
+ * tag, `yield`ing every `SLICE_BYTES` of output.
61
+ */
62
+ function* serializeElementSteps(el, out, budget) {
63
+ const bare = clonePreventingOnclone(el, false);
64
+ const shell = bare.outerHTML;
65
+ const endTag = `</${bare.localName}>`;
66
+ out.push(shell.slice(0, shell.length - endTag.length));
67
+ yield* serializeChildrenSteps(el, bare, out, budget);
68
+ out.push(endTag);
69
+ }
70
+
71
+ /** `"<!DOCTYPE html>" + root.outerHTML`, in steps. */
72
+ export function* serializeTreeSteps(root) {
73
+ const out = ["<!DOCTYPE html>"];
74
+ yield* serializeElementSteps(root, out, { bytes: 0 });
75
+ return out.join('');
76
+ }
77
+
78
+ /** `serializeClone`, in steps: the same bytes, `yield`ing every `SLICE_BYTES` of output. */
79
+ export function* serializeCloneSteps(clone) {
80
+ const blocks = noscriptBlocks(clone);
81
+ if (!blocks.length) return yield* serializeTreeSteps(clone);
82
+ const raw = swapNoscriptMarkers(blocks);
83
+ let html;
84
+ try {
85
+ html = yield* serializeTreeSteps(clone);
86
+ } finally {
87
+ for (const [el, kids] of blocks) el.replaceChildren(...kids);
88
+ }
89
+ for (const [marker, text] of raw) html = html.split(marker).join(text);
90
+ return html;
91
+ }
92
+
93
+ /** `serializeForCompare`, in steps. */
94
+ export function* serializeForCompareSteps(clone) {
95
+ prepareForCompare(clone);
96
+ return yield* serializeTreeSteps(clone);
97
+ }
98
+
99
+ /** `serializeForSync`, in steps: the same bytes, `yield`ing every `SLICE_BYTES` of output. */
100
+ export function* serializeForSyncSteps(clone) {
101
+ const bareRoot = clonePreventingOnclone(clone, false);
102
+ for (const name of TAB_LOCAL_ROOT_ATTRS) bareRoot.removeAttribute(name);
103
+ const shell = bareRoot.outerHTML;
104
+ const endTag = `</${bareRoot.localName}>`;
105
+ const out = ["<!DOCTYPE html>", shell.slice(0, shell.length - endTag.length)];
106
+ yield* serializeChildrenSteps(clone, clonePreventingOnclone(clone, false), out, { bytes: 0 });
107
+ out.push(endTag);
108
+ return out.join('');
109
+ }
@@ -58,6 +58,7 @@ const snapshotProvenance = new WeakMap();
58
58
  // because many things want a turn; serializing the document is one decision, so a
59
59
  // second renderer would be two answers to "what bytes does this save send".
60
60
  let saveRenderer = null;
61
+ let saveRendererSteps = null;
61
62
 
62
63
  // The capture identity for save captures. A refused save that may retry reads it
63
64
  // before sending and again before each retry: a capture newer than the one those
@@ -105,6 +106,37 @@ export function attachCaptureArtifact(artifact) {
105
106
  if (activeCapture && artifact) activeCapture.artifact = artifact;
106
107
  }
107
108
 
109
+ // The page state of one save capture: the exact value its page tag carried when its
110
+ // bytes were serialized, copied so the edits that follow the capture can never reach it.
111
+ // Keyed by the capture id every accepted event carries, and dropped when the capture
112
+ // settles, so an accepted save reads the state it was captured from rather than the
113
+ // store's value at the moment the host answered.
114
+ let capturePageStateReader = null;
115
+ const capturePageStates = new Map();
116
+
117
+ /**
118
+ * Register the reader that finds a page state in a final save clone.
119
+ *
120
+ * A page's own code registers it (page/save-projection.js), so a document without a
121
+ * page pays one null check per capture and nothing else. Read from the clone rather
122
+ * than from a store because that tag is what the file receives, after every transform
123
+ * and every authored save handler has run.
124
+ */
125
+ export function setCapturePageState(reader) {
126
+ capturePageStateReader = reader;
127
+ }
128
+
129
+ /**
130
+ * The page state pinned to one capture, or null.
131
+ *
132
+ * @param {?number} captureId
133
+ * @returns {?Object}
134
+ */
135
+ export function capturePageState(captureId) {
136
+ if (captureId === null || captureId === undefined) return null;
137
+ return capturePageStates.get(captureId) ?? null;
138
+ }
139
+
108
140
  /** The artifact this capture's renderer attached, or null. */
109
141
  export function captureArtifact(result) {
110
142
  return result ? captureArtifacts.get(result) || null : null;
@@ -112,7 +144,9 @@ export function captureArtifact(result) {
112
144
 
113
145
  /** Forget a capture's artifact: it was skipped, refused, or has now settled. */
114
146
  export function releaseCaptureArtifact(result) {
115
- if (result) captureArtifacts.delete(result);
147
+ if (!result) return;
148
+ captureArtifacts.delete(result);
149
+ capturePageStates.delete(result.captureId);
116
150
  }
117
151
 
118
152
  /**
@@ -204,10 +238,16 @@ export function onSnapshot(callback) {
204
238
  * strings are only ever compared against each other, so they gain nothing from
205
239
  * source fidelity and would pay a full render on every keystroke's dirty check.
206
240
  *
241
+ * `steps` is the same render as a generator, for a save that verifies across tasks; a
242
+ * renderer without one is called synchronously at that point.
243
+ *
207
244
  * @param {?Function} renderer - (clone, today) => string, or null to restore outerHTML
245
+ * @param {Object} [options]
246
+ * @param {?Function} [options.steps] - (clone, today) => generator
208
247
  */
209
- export function setSaveRenderer(renderer) {
248
+ export function setSaveRenderer(renderer, { steps = null } = {}) {
210
249
  saveRenderer = renderer;
250
+ saveRendererSteps = steps;
211
251
  }
212
252
 
213
253
  const XHTML_NS = 'http://www.w3.org/1999/xhtml';
@@ -222,7 +262,22 @@ const XHTML_NS = 'http://www.w3.org/1999/xhtml';
222
262
  * reads as literal text, and every later save escaped it again. The text is swapped for
223
263
  * a marker only while serializing, so the clone holds the same nodes when this returns.
224
264
  */
225
- function serializeClone(clone) {
265
+ export function serializeClone(clone) {
266
+ const blocks = noscriptBlocks(clone);
267
+ if (!blocks.length) return "<!DOCTYPE html>" + clone.outerHTML;
268
+ const raw = swapNoscriptMarkers(blocks);
269
+ let html;
270
+ try {
271
+ html = "<!DOCTYPE html>" + clone.outerHTML;
272
+ } finally {
273
+ for (const [el, kids] of blocks) el.replaceChildren(...kids);
274
+ }
275
+ for (const [marker, text] of raw) html = html.split(marker).join(text);
276
+ return html;
277
+ }
278
+
279
+ /** Every <noscript> in the tree whose content is text only, with the children it held. */
280
+ export function noscriptBlocks(clone) {
226
281
  const blocks = [];
227
282
  const collect = (node) => {
228
283
  for (const child of node.childNodes) {
@@ -236,21 +291,34 @@ function serializeClone(clone) {
236
291
  }
237
292
  };
238
293
  collect(clone);
239
- if (!blocks.length) return "<!DOCTYPE html>" + clone.outerHTML;
294
+ return blocks;
295
+ }
296
+
297
+ /** Swap each block's text for a marker, and answer the text each marker stands for. */
298
+ export function swapNoscriptMarkers(blocks) {
240
299
  const nonce = Math.random().toString(36).slice(2);
241
- const raw = blocks.map(([el, kids], i) => {
300
+ return blocks.map(([el, kids], i) => {
242
301
  const marker = `\uE000clay-noscript-${nonce}-${i}\uE000`;
243
302
  el.replaceChildren(el.ownerDocument.createTextNode(marker));
244
303
  return [marker, kids.map((n) => n.data).join('')];
245
304
  });
246
- let html;
305
+ }
306
+
307
+ /**
308
+ * Run one resume of a save renderer with `context` as the capture being serialized.
309
+ *
310
+ * The capture is never the active one while the task that is not running it looks: a
311
+ * renderer that verifies across tasks is resumed once per task, and each resume is its
312
+ * own call in here.
313
+ */
314
+ export function runSaveRenderer(context, resume) {
315
+ const previous = activeCapture;
316
+ activeCapture = context;
247
317
  try {
248
- html = "<!DOCTYPE html>" + clone.outerHTML;
318
+ return resume();
249
319
  } finally {
250
- for (const [el, kids] of blocks) el.replaceChildren(...kids);
320
+ activeCapture = previous;
251
321
  }
252
- for (const [marker, text] of raw) html = html.split(marker).join(text);
253
- return html;
254
322
  }
255
323
 
256
324
  /**
@@ -263,17 +331,18 @@ function serializeClone(clone) {
263
331
  * cost somebody a save.
264
332
  */
265
333
  function serializeSaveClone(clone, context = null) {
334
+ if (context && capturePageStateReader) {
335
+ let state = null;
336
+ try { state = capturePageStateReader(clone); } catch (_) {}
337
+ if (state) capturePageStates.set(context.captureId, state);
338
+ }
266
339
  const today = serializeClone(clone);
267
340
  if (!saveRenderer) return today;
268
- const previous = activeCapture;
269
- activeCapture = context;
270
341
  try {
271
- return saveRenderer(clone, today);
342
+ return runSaveRenderer(context, () => saveRenderer(clone, today));
272
343
  } catch (err) {
273
344
  console.error('clayjs: save renderer failed, sending the full serialization', err);
274
345
  return today;
275
- } finally {
276
- activeCapture = previous;
277
346
  }
278
347
  }
279
348
 
@@ -305,13 +374,26 @@ export function addDocumentTransform(callback) {
305
374
  *
306
375
  * @returns {HTMLElement} Cloned document element with snapshot hooks applied
307
376
  */
308
- function clonePreventingOnclone(node, deep = true) {
377
+ export function clonePreventingOnclone(node, deep = true) {
309
378
  const prev = window.__preventOnclone;
310
379
  window.__preventOnclone = true;
311
380
  try { return node.cloneNode(deep); }
312
381
  finally { window.__preventOnclone = prev; }
313
382
  }
314
383
 
384
+ // A clone OF the save clone (the comparison and dirty trees) is not the page, so the
385
+ // view recorded no provenance for it: a transform that asks originalSnapshotNode would
386
+ // get null there and answer differently than on the save clone it came from. The two
387
+ // trees are isomorphic when this runs, so a lockstep walk registers each clone node
388
+ // against the original its save-clone twin maps to.
389
+ function carryProvenance(saveNode, cloneNode) {
390
+ const live = snapshotProvenance.get(saveNode);
391
+ if (live) snapshotProvenance.set(cloneNode, live);
392
+ const saveKids = saveNode.children;
393
+ const cloneKids = cloneNode.children;
394
+ for (let i = 0; i < saveKids.length; i++) carryProvenance(saveKids[i], cloneKids[i]);
395
+ }
396
+
315
397
  export function captureSnapshot({ flushUndo = true, authored = true, forSave = false, requestRefresh = false, context = null } = {}) {
316
398
  // Force-close any pending undo idle batch BEFORE cloning the DOM, so the
317
399
  // snapshot reflects a clean undo boundary. Without this, a save that fires
@@ -435,12 +517,16 @@ export function captureSaveClone({ flushUndo = true, authored = true } = {}) {
435
517
  // check must not read that as an edit the person would lose on close.
436
518
  const EMPTY_INERT_ATTRS = ['style', 'class'];
437
519
 
438
- function serializeForCompare(clone) {
520
+ export function prepareForCompare(clone) {
439
521
  stripDerived(clone);
440
522
  for (const name of EMPTY_INERT_ATTRS) {
441
523
  if (clone.getAttribute(name) === '') clone.removeAttribute(name);
442
524
  for (const el of clone.querySelectorAll(`[${name}=""]`)) el.removeAttribute(name);
443
525
  }
526
+ }
527
+
528
+ export function serializeForCompare(clone) {
529
+ prepareForCompare(clone);
444
530
  return "<!DOCTYPE html>" + clone.outerHTML;
445
531
  }
446
532
 
@@ -527,6 +613,7 @@ export function captureForComparisonAndDirty({ flushUndo = true } = {}) {
527
613
  const dirtyClone = clone.querySelector(NO_TRIGGER_AUTOSAVE_SELECTOR)
528
614
  ? clonePreventingOnclone(clone)
529
615
  : null;
616
+ if (dirtyClone) carryProvenance(clone, dirtyClone);
530
617
 
531
618
  for (const el of clone.querySelectorAll(STRIP_FROM_COMPARISON)) {
532
619
  el.remove();
@@ -555,23 +642,32 @@ export function captureForComparisonAndDirty({ flushUndo = true } = {}) {
555
642
  }
556
643
 
557
644
  /**
558
- * Single-capture function for both saving and comparison.
645
+ * Take one save capture's looking at the live page: in one synchronous call, everything
646
+ * that must see it (the snapshot, its hooks, snapshot-ready, the authored handlers, the
647
+ * comparison clones and every transform over all three).
559
648
  *
560
- * Clones the DOM once, then clones that clone for comparison.
561
- * More efficient than calling captureForSave() and captureForComparison() separately.
649
+ * What comes back is the pin the rest of the capture reads: every value that work needs,
650
+ * so a page edited afterwards cannot change the answer. It is the one place the strip and
651
+ * transform rules live, for both the one-task capture and a job that finishes across
652
+ * tasks — core/save-job.js, which only a save that prepares across tasks loads.
562
653
  *
563
654
  * @param {Object} options
564
655
  * @param {boolean} options.emitForSync - Whether to emit snapshot-ready event (default: true)
565
656
  * @param {boolean} options.forRequest - These bytes go to a host, so the renderer may
566
657
  * attach the verified model they came from, for the accepted save to adopt
567
- * @returns {{ forSave: string, forComparison: string, forDirty: string, captureId: number }}
658
+ * @param {?{name: string, content: string}} options.pageFormatMarker - The document format
659
+ * these bytes must declare, as the attributes of the marker element. It goes into the
660
+ * save clone after the comparison domains are read and before the renderer verifies, so
661
+ * the bytes verified are the bytes sent; the comparison baselines stay marker-free,
662
+ * exactly as they do on the one-task path
663
+ * @returns {Object} the pin `job.steps()` and the one-task capture both read
568
664
  */
569
- export function captureForSaveAndComparison({ emitForSync = true, flushUndo = true, requestRefresh = false, forRequest = false } = {}) {
665
+ export function prepareSaveCapture({ emitForSync = true, flushUndo = true, requestRefresh = false, forRequest = false, pageFormatMarker = null, sliced = false } = {}) {
570
666
  if (!Number.isSafeInteger(captureCounter + 1)) throw new Error('Save capture identity exhausted');
571
667
  const captureId = ++captureCounter;
572
668
  // The context this capture's renderer may attach a private artifact to, and the
573
669
  // record of whether the page has its own save handlers. It never reaches the object
574
- // this function returns: that keeps its four public fields.
670
+ // the capture returns: that keeps its four public fields.
575
671
  //
576
672
  // Only for a capture whose bytes go to a host. A capture taken for anything else (a
577
673
  // live-sync disk base, a download, an inspection) has no request to adopt it, so it
@@ -581,14 +677,12 @@ export function captureForSaveAndComparison({ emitForSync = true, flushUndo = tr
581
677
 
582
678
  // Emit for live-sync before any stripping.
583
679
  //
584
- // A listener gets the very clone this function goes on to serialize into
585
- // forSave, forComparison and forDirty — that sharing is the point, it saves a
586
- // second full DOM clone per save. So a listener may READ it and must not write
587
- // to it: anything it changes lands in the saved bytes and in both baselines,
588
- // and a baseline the dirty check cannot reproduce warns on close forever.
680
+ // A listener gets the very clone this capture goes on to serialize into forSave,
681
+ // forComparison and forDirty — that sharing is the point, it saves a second full DOM
682
+ // clone per save. So a listener may READ it and must not write to it.
589
683
  if (emitForSync) {
590
684
  document.dispatchEvent(new CustomEvent('clay:snapshot-ready', {
591
- detail: { documentElement: clone, forSave: true, captureId }
685
+ detail: { documentElement: clone, forSave: true, captureId, sliced }
592
686
  }));
593
687
  }
594
688
 
@@ -597,15 +691,14 @@ export function captureForSaveAndComparison({ emitForSync = true, flushUndo = tr
597
691
 
598
692
  // Clone for comparison before stripping (cheaper than cloning live DOM)
599
693
  const compareClone = clonePreventingOnclone(clone);
694
+ carryProvenance(clone, compareClone);
600
695
 
601
- // The dirty domain differs from the autosave domain only inside
602
- // no-trigger-autosave regions, so a document without one pays nothing: the two
603
- // strip sets remove exactly the same nodes and one string serves both. The
604
- // predicate reads the captured clone after authored handlers have run, and
605
- // covers all three spellings via the policy's own selector.
696
+ // The dirty domain differs from the autosave domain only inside no-trigger-autosave
697
+ // regions, so a document without one pays nothing: one string serves both.
606
698
  const dirtyClone = clone.querySelector(NO_TRIGGER_AUTOSAVE_SELECTOR)
607
699
  ? clonePreventingOnclone(clone)
608
700
  : null;
701
+ if (dirtyClone) carryProvenance(clone, dirtyClone);
609
702
 
610
703
  // Save clone: run hooks (freeze restore lives here), THEN strip [no-save]/[save-remove]
611
704
  // LAST (snapshot-algorithm step 7) so freeze-restored [no-save] content can't leak to disk.
@@ -618,7 +711,6 @@ export function captureForSaveAndComparison({ emitForSync = true, flushUndo = tr
618
711
  el.remove();
619
712
  }
620
713
  stripDiagnosticBeacons(clone);
621
- const forSave = serializeSaveClone(clone, context);
622
714
 
623
715
  // Compare clone: strip every autosave-off region, then run hooks
624
716
  for (const el of compareClone.querySelectorAll(STRIP_FROM_COMPARISON)) {
@@ -629,10 +721,8 @@ export function captureForSaveAndComparison({ emitForSync = true, flushUndo = tr
629
721
  hook(compareClone);
630
722
  }
631
723
  stripSnapshotRegions(compareClone);
632
- const forComparison = serializeForCompare(compareClone);
633
724
 
634
725
  // Dirty clone: same shape as the compare clone, one selector weaker.
635
- let forDirty = forComparison;
636
726
  if (dirtyClone) {
637
727
  for (const el of dirtyClone.querySelectorAll(STRIP_FROM_DIRTY_CHECK)) {
638
728
  el.remove();
@@ -642,14 +732,88 @@ export function captureForSaveAndComparison({ emitForSync = true, flushUndo = tr
642
732
  hook(dirtyClone);
643
733
  }
644
734
  stripSnapshotRegions(dirtyClone);
645
- forDirty = serializeForCompare(dirtyClone);
646
735
  }
647
736
 
648
- const result = { forSave, forComparison, forDirty, captureId };
649
- if (context && context.artifact) captureArtifacts.set(result, context.artifact);
737
+ return {
738
+ captureId,
739
+ context,
740
+ pageFormatMarker,
741
+ clone,
742
+ compareClone,
743
+ dirtyClone,
744
+ renderer: saveRenderer,
745
+ rendererSteps: saveRendererSteps,
746
+ pageStateReader: capturePageStateReader,
747
+ result: null,
748
+ };
749
+ }
750
+
751
+ /**
752
+ * Copy the page state this capture's bytes carry, before anything edits the clone.
753
+ *
754
+ * A capture taken for a request records the exact value its page tag carried, so the save
755
+ * the host accepts reads the state these bytes describe rather than the store's value at
756
+ * the moment the host answered.
757
+ */
758
+ export function readCapturePageState(pin) {
759
+ if (!pin.context || !pin.pageStateReader) return;
760
+ let state = null;
761
+ try { state = pin.pageStateReader(pin.clone); } catch (_) {}
762
+ if (state) capturePageStates.set(pin.captureId, state);
763
+ }
764
+
765
+ /**
766
+ * Finish a save capture: the result object, and the artifact its renderer attached.
767
+ *
768
+ * @returns {{ forSave: string, forComparison: string, forDirty: string, captureId: number }}
769
+ */
770
+ export function completeSaveCapture(pin, { forSave, forComparison, forDirty }) {
771
+ const result = { forSave, forComparison, forDirty, captureId: pin.captureId };
772
+ if (pin.context && pin.context.artifact) captureArtifacts.set(result, pin.context.artifact);
773
+ pin.result = result;
650
774
  return result;
651
775
  }
652
776
 
777
+ /** Give back everything a capture holds. Idempotent. */
778
+ export function releaseSaveCapture(pin) {
779
+ releaseCaptureArtifact(pin.result);
780
+ capturePageStates.delete(pin.captureId);
781
+ if (pin.context) pin.context.artifact = null;
782
+ pin.clone = pin.compareClone = pin.dirtyClone = null;
783
+ pin.result = null;
784
+ }
785
+
786
+ /**
787
+ * Single-capture function for both saving and comparison.
788
+ *
789
+ * Clones the DOM once, then clones that clone for comparison.
790
+ * More efficient than calling captureForSave() and captureForComparison() separately.
791
+ *
792
+ * The one-task form of a save capture: the same preparation, the same bytes and the same
793
+ * release as a job drained in the caller's own task, written out rather than resumed so
794
+ * nothing a page with small documents never uses has to be loaded to save it.
795
+ *
796
+ * @param {Object} options
797
+ * @param {boolean} options.emitForSync - Whether to emit snapshot-ready event (default: true)
798
+ * @param {boolean} options.forRequest - These bytes go to a host, so the renderer may
799
+ * attach the verified model they came from, for the accepted save to adopt
800
+ * @returns {{ forSave: string, forComparison: string, forDirty: string, captureId: number }}
801
+ */
802
+ export function captureForSaveAndComparison(opts = {}) {
803
+ const pin = prepareSaveCapture(opts);
804
+ try {
805
+ readCapturePageState(pin);
806
+ const today = serializeClone(pin.clone);
807
+ const forComparison = pin.compareClone ? serializeForCompare(pin.compareClone) : null;
808
+ const forDirty = pin.dirtyClone ? serializeForCompare(pin.dirtyClone) : forComparison;
809
+ const forSave = pin.renderer ? runSaveRenderer(pin.context, () => pin.renderer(pin.clone, today)) : today;
810
+ return completeSaveCapture(pin, { forSave, forComparison, forDirty });
811
+ } catch (err) {
812
+ releaseSaveCapture(pin);
813
+ throw err;
814
+ }
815
+ }
816
+
653
817
  /**
654
818
  * Capture for a protected live-sync merge: the save-domain and loss-domain
655
819
  * clones of ONE snapshot, plus a WeakMap pairing every loss-domain element to
@@ -784,6 +948,11 @@ export function serializeForSync(clone) {
784
948
  return "<!DOCTYPE html>" + shell.slice(0, shell.length - endTag.length) + clone.innerHTML + endTag;
785
949
  }
786
950
 
951
+ /** A private deep copy of a capture's clone, for work that outlives the capture event. */
952
+ export function copyCaptureClone(clone) {
953
+ return clonePreventingOnclone(clone);
954
+ }
955
+
787
956
  /**
788
957
  * PHASE 1-2 (body only): For live-sync between admin users.
789
958
  *