@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
@@ -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
@@ -237,10 +238,16 @@ export function onSnapshot(callback) {
237
238
  * strings are only ever compared against each other, so they gain nothing from
238
239
  * source fidelity and would pay a full render on every keystroke's dirty check.
239
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
+ *
240
244
  * @param {?Function} renderer - (clone, today) => string, or null to restore outerHTML
245
+ * @param {Object} [options]
246
+ * @param {?Function} [options.steps] - (clone, today) => generator
241
247
  */
242
- export function setSaveRenderer(renderer) {
248
+ export function setSaveRenderer(renderer, { steps = null } = {}) {
243
249
  saveRenderer = renderer;
250
+ saveRendererSteps = steps;
244
251
  }
245
252
 
246
253
  const XHTML_NS = 'http://www.w3.org/1999/xhtml';
@@ -255,7 +262,22 @@ const XHTML_NS = 'http://www.w3.org/1999/xhtml';
255
262
  * reads as literal text, and every later save escaped it again. The text is swapped for
256
263
  * a marker only while serializing, so the clone holds the same nodes when this returns.
257
264
  */
258
- 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) {
259
281
  const blocks = [];
260
282
  const collect = (node) => {
261
283
  for (const child of node.childNodes) {
@@ -269,21 +291,34 @@ function serializeClone(clone) {
269
291
  }
270
292
  };
271
293
  collect(clone);
272
- 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) {
273
299
  const nonce = Math.random().toString(36).slice(2);
274
- const raw = blocks.map(([el, kids], i) => {
300
+ return blocks.map(([el, kids], i) => {
275
301
  const marker = `\uE000clay-noscript-${nonce}-${i}\uE000`;
276
302
  el.replaceChildren(el.ownerDocument.createTextNode(marker));
277
303
  return [marker, kids.map((n) => n.data).join('')];
278
304
  });
279
- 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;
280
317
  try {
281
- html = "<!DOCTYPE html>" + clone.outerHTML;
318
+ return resume();
282
319
  } finally {
283
- for (const [el, kids] of blocks) el.replaceChildren(...kids);
320
+ activeCapture = previous;
284
321
  }
285
- for (const [marker, text] of raw) html = html.split(marker).join(text);
286
- return html;
287
322
  }
288
323
 
289
324
  /**
@@ -303,15 +338,11 @@ function serializeSaveClone(clone, context = null) {
303
338
  }
304
339
  const today = serializeClone(clone);
305
340
  if (!saveRenderer) return today;
306
- const previous = activeCapture;
307
- activeCapture = context;
308
341
  try {
309
- return saveRenderer(clone, today);
342
+ return runSaveRenderer(context, () => saveRenderer(clone, today));
310
343
  } catch (err) {
311
344
  console.error('clayjs: save renderer failed, sending the full serialization', err);
312
345
  return today;
313
- } finally {
314
- activeCapture = previous;
315
346
  }
316
347
  }
317
348
 
@@ -343,13 +374,26 @@ export function addDocumentTransform(callback) {
343
374
  *
344
375
  * @returns {HTMLElement} Cloned document element with snapshot hooks applied
345
376
  */
346
- function clonePreventingOnclone(node, deep = true) {
377
+ export function clonePreventingOnclone(node, deep = true) {
347
378
  const prev = window.__preventOnclone;
348
379
  window.__preventOnclone = true;
349
380
  try { return node.cloneNode(deep); }
350
381
  finally { window.__preventOnclone = prev; }
351
382
  }
352
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
+
353
397
  export function captureSnapshot({ flushUndo = true, authored = true, forSave = false, requestRefresh = false, context = null } = {}) {
354
398
  // Force-close any pending undo idle batch BEFORE cloning the DOM, so the
355
399
  // snapshot reflects a clean undo boundary. Without this, a save that fires
@@ -473,12 +517,16 @@ export function captureSaveClone({ flushUndo = true, authored = true } = {}) {
473
517
  // check must not read that as an edit the person would lose on close.
474
518
  const EMPTY_INERT_ATTRS = ['style', 'class'];
475
519
 
476
- function serializeForCompare(clone) {
520
+ export function prepareForCompare(clone) {
477
521
  stripDerived(clone);
478
522
  for (const name of EMPTY_INERT_ATTRS) {
479
523
  if (clone.getAttribute(name) === '') clone.removeAttribute(name);
480
524
  for (const el of clone.querySelectorAll(`[${name}=""]`)) el.removeAttribute(name);
481
525
  }
526
+ }
527
+
528
+ export function serializeForCompare(clone) {
529
+ prepareForCompare(clone);
482
530
  return "<!DOCTYPE html>" + clone.outerHTML;
483
531
  }
484
532
 
@@ -565,6 +613,7 @@ export function captureForComparisonAndDirty({ flushUndo = true } = {}) {
565
613
  const dirtyClone = clone.querySelector(NO_TRIGGER_AUTOSAVE_SELECTOR)
566
614
  ? clonePreventingOnclone(clone)
567
615
  : null;
616
+ if (dirtyClone) carryProvenance(clone, dirtyClone);
568
617
 
569
618
  for (const el of clone.querySelectorAll(STRIP_FROM_COMPARISON)) {
570
619
  el.remove();
@@ -593,23 +642,32 @@ export function captureForComparisonAndDirty({ flushUndo = true } = {}) {
593
642
  }
594
643
 
595
644
  /**
596
- * 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).
597
648
  *
598
- * Clones the DOM once, then clones that clone for comparison.
599
- * 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.
600
653
  *
601
654
  * @param {Object} options
602
655
  * @param {boolean} options.emitForSync - Whether to emit snapshot-ready event (default: true)
603
656
  * @param {boolean} options.forRequest - These bytes go to a host, so the renderer may
604
657
  * attach the verified model they came from, for the accepted save to adopt
605
- * @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
606
664
  */
607
- 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 } = {}) {
608
666
  if (!Number.isSafeInteger(captureCounter + 1)) throw new Error('Save capture identity exhausted');
609
667
  const captureId = ++captureCounter;
610
668
  // The context this capture's renderer may attach a private artifact to, and the
611
669
  // record of whether the page has its own save handlers. It never reaches the object
612
- // this function returns: that keeps its four public fields.
670
+ // the capture returns: that keeps its four public fields.
613
671
  //
614
672
  // Only for a capture whose bytes go to a host. A capture taken for anything else (a
615
673
  // live-sync disk base, a download, an inspection) has no request to adopt it, so it
@@ -619,14 +677,12 @@ export function captureForSaveAndComparison({ emitForSync = true, flushUndo = tr
619
677
 
620
678
  // Emit for live-sync before any stripping.
621
679
  //
622
- // A listener gets the very clone this function goes on to serialize into
623
- // forSave, forComparison and forDirty — that sharing is the point, it saves a
624
- // second full DOM clone per save. So a listener may READ it and must not write
625
- // to it: anything it changes lands in the saved bytes and in both baselines,
626
- // 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.
627
683
  if (emitForSync) {
628
684
  document.dispatchEvent(new CustomEvent('clay:snapshot-ready', {
629
- detail: { documentElement: clone, forSave: true, captureId }
685
+ detail: { documentElement: clone, forSave: true, captureId, sliced }
630
686
  }));
631
687
  }
632
688
 
@@ -635,15 +691,14 @@ export function captureForSaveAndComparison({ emitForSync = true, flushUndo = tr
635
691
 
636
692
  // Clone for comparison before stripping (cheaper than cloning live DOM)
637
693
  const compareClone = clonePreventingOnclone(clone);
694
+ carryProvenance(clone, compareClone);
638
695
 
639
- // The dirty domain differs from the autosave domain only inside
640
- // no-trigger-autosave regions, so a document without one pays nothing: the two
641
- // strip sets remove exactly the same nodes and one string serves both. The
642
- // predicate reads the captured clone after authored handlers have run, and
643
- // 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.
644
698
  const dirtyClone = clone.querySelector(NO_TRIGGER_AUTOSAVE_SELECTOR)
645
699
  ? clonePreventingOnclone(clone)
646
700
  : null;
701
+ if (dirtyClone) carryProvenance(clone, dirtyClone);
647
702
 
648
703
  // Save clone: run hooks (freeze restore lives here), THEN strip [no-save]/[save-remove]
649
704
  // LAST (snapshot-algorithm step 7) so freeze-restored [no-save] content can't leak to disk.
@@ -656,7 +711,6 @@ export function captureForSaveAndComparison({ emitForSync = true, flushUndo = tr
656
711
  el.remove();
657
712
  }
658
713
  stripDiagnosticBeacons(clone);
659
- const forSave = serializeSaveClone(clone, context);
660
714
 
661
715
  // Compare clone: strip every autosave-off region, then run hooks
662
716
  for (const el of compareClone.querySelectorAll(STRIP_FROM_COMPARISON)) {
@@ -667,10 +721,8 @@ export function captureForSaveAndComparison({ emitForSync = true, flushUndo = tr
667
721
  hook(compareClone);
668
722
  }
669
723
  stripSnapshotRegions(compareClone);
670
- const forComparison = serializeForCompare(compareClone);
671
724
 
672
725
  // Dirty clone: same shape as the compare clone, one selector weaker.
673
- let forDirty = forComparison;
674
726
  if (dirtyClone) {
675
727
  for (const el of dirtyClone.querySelectorAll(STRIP_FROM_DIRTY_CHECK)) {
676
728
  el.remove();
@@ -680,14 +732,88 @@ export function captureForSaveAndComparison({ emitForSync = true, flushUndo = tr
680
732
  hook(dirtyClone);
681
733
  }
682
734
  stripSnapshotRegions(dirtyClone);
683
- forDirty = serializeForCompare(dirtyClone);
684
735
  }
685
736
 
686
- const result = { forSave, forComparison, forDirty, captureId };
687
- 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;
688
774
  return result;
689
775
  }
690
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
+
691
817
  /**
692
818
  * Capture for a protected live-sync merge: the save-domain and loss-domain
693
819
  * clones of ONE snapshot, plus a WeakMap pairing every loss-domain element to
@@ -822,6 +948,11 @@ export function serializeForSync(clone) {
822
948
  return "<!DOCTYPE html>" + shell.slice(0, shell.length - endTag.length) + clone.innerHTML + endTag;
823
949
  }
824
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
+
825
956
  /**
826
957
  * PHASE 1-2 (body only): For live-sync between admin users.
827
958
  *