@panphora/clayjs 1.17.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 (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-F449FF98.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
@@ -7,6 +7,8 @@
7
7
  * For full save system with state management, use save.js instead.
8
8
  */
9
9
 
10
+ import { notifySaveAccepted } from "./save-accepted.js";
11
+ export { onSaveAccepted } from "./save-accepted.js";
10
12
  import { isEditMode, isDraftMode } from "./is-edit-mode.js";
11
13
  import { draftTransport } from "./draft-mode.js";
12
14
  import { consumeUserDriven, consumeExplicitSave, markUserDriven } from "../lib/user-gesture.js";
@@ -15,6 +17,10 @@ import { lastSeenEtag, conditionalSaves, recordEtag } from "./etag.js";
15
17
  import { hostMeta } from "./host-meta.js";
16
18
  import { saveSize, storedFile, knownSaveLimit, saveLimit, announceSaveSize } from "./save-size.js";
17
19
  import { PAGE_FORMAT, PAGE_FORMAT_HEADER, knownPageFormatFloor, withPageFormatFloor } from "./page-format.js";
20
+
21
+ // Re-exported for the save lane: it reads the floor and writes the marker these bytes
22
+ // carry, without a second import of the format module.
23
+ export { PAGE_FORMAT_META, knownPageFormatFloor } from "./page-format.js";
18
24
  import "./runtime-url.js";
19
25
  import {
20
26
  getPageContents,
@@ -30,11 +36,27 @@ import {
30
36
  // =============================================================================
31
37
 
32
38
  let saveInProgress = false;
39
+ // Who holds the lane while a save prepares. A capture that will send later takes the lane
40
+ // before its first await, so no second save can start work on the page it is reading; the
41
+ // token is what lets that same save enter `runSave` while its own claim still stands.
42
+ let laneHolder = null;
33
43
  const SAVE_PATH = '/_/save';
34
44
  const SAVE_TIMEOUT_MS = 12000;
35
45
  // The browser caps a keepalive body (Chromium refuses one over 64 KB), and it counts
36
46
  // BYTES: the hint is only attached to a save whose UTF-8 encoding fits well inside that.
37
47
  const KEEPALIVE_MAX_BODY = 60000;
48
+ // Above this, a save body travels as a Blob instead of a string, so the document is
49
+ // encoded once here rather than again inside fetch, and a retry of the same attempt
50
+ // reuses that Blob. Counted in UTF-16 units, which are never more than the UTF-8 bytes
51
+ // they encode to, so a body under the threshold by this measure is under it in bytes.
52
+ const SAVE_BLOB_MIN_BYTES = 1024 * 1024;
53
+ // The media type the string body has always arrived with, written out explicitly
54
+ // because a Blob's own type never reaches the wire as a request header. text/plain is
55
+ // CORS-safelisted, so this is still the same un-preflighted request.
56
+ const SAVE_BODY_TYPE = 'text/plain;charset=UTF-8';
57
+ // The one body a Blob was last built for, kept so the retry of a large save sends the
58
+ // bytes it already encoded instead of encoding the document a second time.
59
+ let lastBodyBlob = null;
38
60
  // Set by `withKeepalive`, read once by the next `sendOnce`: the hint belongs to one
39
61
  // request, not to the save pipeline.
40
62
  let nextSaveKeepalive = false;
@@ -48,7 +70,6 @@ let nextSaveKeepalive = false;
48
70
  //
49
71
  // Not a DOM event, deliberately. The whole document rides in the argument, and a
50
72
  // CustomEvent would put it on a bus any script on the page can listen to.
51
- const saveAcceptedHooks = [];
52
73
 
53
74
  // Headers another module asks every save to carry, by name.
54
75
  const extraSaveHeaders = new Map();
@@ -93,32 +114,37 @@ export function isSaveInProgress() {
93
114
  }
94
115
 
95
116
  /**
96
- * Run a callback with the bytes the host just accepted.
117
+ * Take the save lane for a capture that will prepare before it can send.
97
118
  *
98
- * Called once per accepted save, after the response, with the exact body that was
99
- * sent. Never called for a refused, failed or skipped save, and never in test mode,
100
- * where nothing reached a host.
119
+ * Runs in the caller's own task, before its first await, so a second save asked for while
120
+ * the first is preparing records intent instead of capturing the page again. The token
121
+ * comes back opaque: only the holder can enter `runSave` with it, and only the holder
122
+ * (or `runSave` settling that request) gives the lane back.
101
123
  *
102
- * @param {Function} callback - Receives the accepted HTML string
124
+ * @returns {?Object} the owner token, or null when a save already holds the lane
103
125
  */
104
- export function onSaveAccepted(callback) {
105
- saveAcceptedHooks.push(callback);
126
+ export function reserveSaveLane() {
127
+ if (saveInProgress) return null;
128
+ saveInProgress = true;
129
+ laneHolder = { reserved: true };
130
+ return laneHolder;
106
131
  }
107
132
 
108
- function notifySaveAccepted(html, captureId, artifact = null) {
109
- for (const hook of saveAcceptedHooks) {
110
- try {
111
- // The artifact is the capture's own private model of these bytes, and it is
112
- // offered only when it exists: a raw save and a capture without one keep the
113
- // shape they had.
114
- const accepted = artifact
115
- ? { captureId: captureId ?? null, artifact }
116
- : { captureId: captureId ?? null };
117
- hook(html, accepted);
118
- } catch (err) {
119
- console.error('clayjs: onSaveAccepted hook failed:', err);
120
- }
121
- }
133
+ /**
134
+ * Give the lane back after a preparation that took it. Idempotent, and harmless once the
135
+ * save it was reserved for has settled the lane itself.
136
+ *
137
+ * @param {Object} owner - the token `reserveSaveLane` returned
138
+ */
139
+ export function releaseSaveLane(owner) {
140
+ if (!owner || laneHolder !== owner) return;
141
+ laneHolder = null;
142
+ saveInProgress = false;
143
+ }
144
+
145
+ /** Whether the next save rides a keepalive hint, which an unload flush cannot wait on. */
146
+ export function keepaliveHeld() {
147
+ return nextSaveKeepalive;
122
148
  }
123
149
 
124
150
  // Listeners for "this exact body is about to leave". One call per real fetch, with
@@ -482,6 +508,10 @@ function formatConversionHeader(conversion) {
482
508
  * carrying the document alongside an unstripped snapshot; the snapshot's home is
483
509
  * the §10 relay, which the live-sync plugin already posts it to.
484
510
  *
511
+ * Above `SAVE_BLOB_MIN_BYTES` the body is a Blob wrapping that same one string, with the
512
+ * same media type written out explicitly: a different container for identical bytes,
513
+ * which is what keeps the encoding off the main thread's critical path.
514
+ *
485
515
  * @param {string} content - HTML to save
486
516
  * @param {boolean} userDriven - Whether a human gesture is behind this save
487
517
  * @param {AbortSignal} signal
@@ -517,11 +547,15 @@ export function buildSaveRequest(content, userDriven, signal, saveId, etag, keep
517
547
  // authorization, and refuses a lower one before mutation.
518
548
  [PAGE_FORMAT_HEADER]: String(PAGE_FORMAT)
519
549
  },
520
- body: content
550
+ body: bodyForSave(content)
521
551
  };
522
552
 
523
553
  for (const [name, value] of extraSaveHeaders) options.headers[name] = value;
524
554
 
555
+ // Set after the process-global headers so the type describing THESE bytes is the one
556
+ // that goes out, the same rule the conversion header below follows.
557
+ if (typeof options.body !== 'string') options.headers['Content-Type'] = SAVE_BODY_TYPE;
558
+
525
559
  // The conversion request belongs to ONE logical save, and goes on every attempt
526
560
  // of it. Set after the process-global headers so the capture-bound value is the
527
561
  // one that describes these bytes.
@@ -529,11 +563,34 @@ export function buildSaveRequest(content, userDriven, signal, saveId, etag, keep
529
563
 
530
564
  if (etag) options.headers['If-Match'] = etag;
531
565
 
532
- if (keepalive && new Blob([content]).size < KEEPALIVE_MAX_BODY) options.keepalive = true;
566
+ // The length test comes first because it is a floor on the byte count: a body too
567
+ // long by this measure is too big in bytes too, and never has to be encoded to find
568
+ // out. A keepalive body is always under 64 KB, so it never reaches the Blob path.
569
+ if (keepalive && content.length < KEEPALIVE_MAX_BODY && new Blob([content]).size < KEEPALIVE_MAX_BODY) options.keepalive = true;
533
570
 
534
571
  return { url: resolveSaveUrl(path), options };
535
572
  }
536
573
 
574
+ /**
575
+ * The body of one save: the string itself, or, above `SAVE_BLOB_MIN_BYTES`, a Blob
576
+ * wrapping that one string with no further joining.
577
+ *
578
+ * The point is where the encoding happens. A string body is encoded to UTF-8 inside
579
+ * `fetch`, synchronously and on the main thread; at 11 MB that is the visible stall.
580
+ * A Blob carries the same bytes with the same media type, and building it once here
581
+ * means the retry of the same attempt sends the very Blob it already built.
582
+ *
583
+ * @param {string} content - HTML to save
584
+ * @returns {string|Blob}
585
+ */
586
+ function bodyForSave(content) {
587
+ if (content.length < SAVE_BLOB_MIN_BYTES) return content;
588
+ if (lastBodyBlob && lastBodyBlob.content === content) return lastBodyBlob.blob;
589
+ const blob = new Blob([content], { type: SAVE_BODY_TYPE });
590
+ lastBodyBlob = { content, blob };
591
+ return blob;
592
+ }
593
+
537
594
  /**
538
595
  * The stamp to send as If-Match, or null for a save that goes out unconditional.
539
596
  *
@@ -885,7 +942,7 @@ async function sendSave(content, { captureId = null, conversionHeader = null, at
885
942
  // `pageFormatFloor` is internal: one save may carry a floor this tab has not
886
943
  // noted yet, which is how the format gate's activation writes the marker. It
887
944
  // raises these bytes only, never what the tab knows the document at.
888
- function runSave(html, captureId, callback, conversion, capture = null, pageFormatFloor = 0) {
945
+ function runSave(html, captureId, callback, conversion, capture = null, pageFormatFloor = 0, lane = null) {
889
946
  // A capture taken before the floor rose cannot drop the marker by omitting it:
890
947
  // the bytes that go out carry the floor this tab last saw. At floor 0 this is
891
948
  // the string itself, unchanged.
@@ -897,6 +954,10 @@ function runSave(html, captureId, callback, conversion, capture = null, pageForm
897
954
  // recovered through the host's receipt reaches `accepted` like any other success,
898
955
  // so it adopts the artifact of the request that landed.
899
956
  const artifact = captureArtifact(capture);
957
+ // A prepared save already holds the lane: `saveInProgress` is this call's own claim,
958
+ // and its preparation gives it back once this save's bookkeeping is done.
959
+ const holdsLane = () => lane !== null && laneHolder === lane;
960
+ const releaseOwnLane = () => { if (!holdsLane()) saveInProgress = false; };
900
961
  const finished = (result, accepted = false) => {
901
962
  if (accepted) notifySaveAccepted(html, captureId ?? null, artifact);
902
963
  releaseCaptureArtifact(capture);
@@ -913,7 +974,7 @@ function runSave(html, captureId, callback, conversion, capture = null, pageForm
913
974
  if (!isEditMode && !window.clay?.testMode) {
914
975
  return finished(skippedResult('Not in edit mode'));
915
976
  }
916
- if (saveInProgress) {
977
+ if (saveInProgress && !holdsLane()) {
917
978
  return finished(skippedResult('Save already in progress'));
918
979
  }
919
980
 
@@ -929,7 +990,7 @@ function runSave(html, captureId, callback, conversion, capture = null, pageForm
929
990
  // Test mode: skip network request, return mock success
930
991
  if (window.clay?.testMode) {
931
992
  setTimeout(() => {
932
- saveInProgress = false;
993
+ releaseOwnLane();
933
994
  finished(successResult({ msg: 'Test mode: save skipped' }));
934
995
  }, 0);
935
996
  return;
@@ -945,7 +1006,7 @@ function runSave(html, captureId, callback, conversion, capture = null, pageForm
945
1006
  const size = saveSize(storedFile(html), knownSaveLimit());
946
1007
  announceSaveSize(size);
947
1008
  if (size.state === 'over') {
948
- saveInProgress = false;
1009
+ releaseOwnLane();
949
1010
  return finished(tooLargeResult(size));
950
1011
  }
951
1012
 
@@ -955,7 +1016,7 @@ function runSave(html, captureId, callback, conversion, capture = null, pageForm
955
1016
  // follow-up save can start it immediately rather than being told the lane
956
1017
  // is busy by the save that just finished.
957
1018
  .then(result => {
958
- saveInProgress = false;
1019
+ releaseOwnLane();
959
1020
  // A host's 413 is the same refusal the guard above makes, so it carries the
960
1021
  // same measurement. Its own answer about the size wins over nothing: the bytes
961
1022
  // are this attempt's, and the limit is still only what discovery advertised.
@@ -998,8 +1059,8 @@ export function saveHtml(html, callback = () => {}) {
998
1059
  * @param {Object} [options.conversion] - {source:'file', base} or {source:'room', base, epoch}
999
1060
  * @returns {Promise<Object>}
1000
1061
  */
1001
- export function saveCapturedHtml(html, captureId, callback = () => {}, { conversion, capture = null, pageFormatFloor } = {}) {
1002
- return runSave(html, captureId, callback, conversion, capture, pageFormatFloor);
1062
+ export function saveCapturedHtml(html, captureId, callback = () => {}, { conversion, capture = null, pageFormatFloor, lane = null } = {}) {
1063
+ return runSave(html, captureId, callback, conversion, capture, pageFormatFloor, lane);
1003
1064
  }
1004
1065
 
1005
1066
  /**
@@ -1063,3 +1124,17 @@ export function replacePageWith(url, callback = () => {}) {
1063
1124
  });
1064
1125
  });
1065
1126
  }
1127
+
1128
+ // The exit's own page checkpoint, published by save.js and read by the live plugin's
1129
+ // exit flush. It lives here because save.js is mocked by name in most page suites,
1130
+ // while this module is not: the slot lets an exit save a page without either module
1131
+ // importing the other's internals.
1132
+ let exitSaveFn = null;
1133
+
1134
+ export function setExitSave(fn) {
1135
+ exitSaveFn = fn;
1136
+ }
1137
+
1138
+ export function exitSave() {
1139
+ return exitSaveFn;
1140
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * save-job.js — a save capture's remaining work as a resumable job.
3
+ *
4
+ * `beginSaveCapture` takes the one atomic look at the live page (`prepareSaveCapture`,
5
+ * which stays on the eager side because every save needs it) and hands back a job whose
6
+ * rest — the three serializations, the format marker and the renderer's verification — is
7
+ * a generator. Nothing else here decides anything: what the job must not do while it is
8
+ * half verified, and whether its bytes may go out, are the caller's (`core/save.js`).
9
+ *
10
+ * This module is reached only through the dynamic import that a save first takes the
11
+ * sliced path with, so a page whose documents are small never loads it and the Document
12
+ * editor's ready path never pays for it.
13
+ */
14
+
15
+ import {
16
+ completeSaveCapture,
17
+ prepareSaveCapture,
18
+ readCapturePageState,
19
+ releaseSaveCapture,
20
+ runSaveRenderer
21
+ } from './snapshot.js';
22
+ import { serializeCloneSteps, serializeForCompareSteps } from './serialize-steps.js';
23
+
24
+ /**
25
+ * Begin one save capture: everything that must see the live page in this call, then a job
26
+ * whose remaining work is `job.steps()`. The job holds every value that work reads, so a
27
+ * page edited between two steps cannot change the answer; `job.drop()` releases what it
28
+ * captured.
29
+ *
30
+ * @param {Object} options
31
+ * @param {boolean} options.emitForSync - Whether to emit snapshot-ready event (default: true)
32
+ * @param {boolean} options.forRequest - These bytes go to a host, so the renderer may
33
+ * attach the verified model they came from, for the accepted save to adopt
34
+ * @param {?{name: string, content: string}} options.pageFormatMarker - The document format
35
+ * these bytes must declare, as the attributes of the marker element. It goes into the
36
+ * save clone after the comparison domains are read and before the renderer verifies, so
37
+ * the bytes verified are the bytes sent; the comparison baselines stay marker-free,
38
+ * exactly as they do on the one-task path
39
+ * @returns {{ captureId: number, steps: function(): Generator, drop: function(): void }}
40
+ */
41
+ export function beginSaveCapture(options = {}) {
42
+ const pin = prepareSaveCapture({ ...options, sliced: true });
43
+ return Object.freeze({
44
+ captureId: pin.captureId,
45
+ steps: () => saveSteps(pin),
46
+ drop: () => releaseSaveCapture(pin),
47
+ });
48
+ }
49
+
50
+ /**
51
+ * Declare the document format these bytes carry, in the save clone only.
52
+ *
53
+ * The same marker `withPageFormatFloor` puts in the final string, applied to the tree the
54
+ * renderer verifies so a prepared save cannot verify one document and send another. A
55
+ * clone already at that floor is left exactly as it is, because the string transform
56
+ * leaves such a document unchanged too.
57
+ */
58
+ function setFormatMarker(clone, marker) {
59
+ const head = clone.querySelector('head');
60
+ if (!head) return;
61
+ const existing = clone.querySelector(`meta[name="${marker.name}"]`);
62
+ if (existing) {
63
+ if (existing.getAttribute('content') !== marker.content) existing.setAttribute('content', marker.content);
64
+ return;
65
+ }
66
+ const meta = clone.ownerDocument.createElement('meta');
67
+ meta.setAttribute('name', marker.name);
68
+ meta.setAttribute('content', marker.content);
69
+ head.appendChild(meta);
70
+ }
71
+
72
+ /**
73
+ * The renderer as a generator, resumed with the capture it belongs to active and put back
74
+ * before every yield: this capture is never the active one while this task is not running
75
+ * it.
76
+ */
77
+ function* rendererSteps(clone, today, pin) {
78
+ if (!pin.renderer) return today;
79
+ let gen = null;
80
+ for (;;) {
81
+ let step;
82
+ try {
83
+ step = runSaveRenderer(pin.context, () => {
84
+ gen ||= pin.rendererSteps ? pin.rendererSteps(clone, today) : false;
85
+ return gen ? gen.next() : { done: true, value: pin.renderer(clone, today) };
86
+ });
87
+ } catch (err) {
88
+ console.error('clayjs: save renderer failed, sending the full serialization', err);
89
+ return today;
90
+ }
91
+ if (step.done) return step.value;
92
+ yield;
93
+ }
94
+ }
95
+
96
+ /** The resumable half of a save capture: three serializations that each yield too, then the renderer. */
97
+ function* saveSteps(pin) {
98
+ readCapturePageState(pin);
99
+ const today = yield* serializeCloneSteps(pin.clone);
100
+ yield;
101
+ const forComparison = pin.compareClone ? (yield* serializeForCompareSteps(pin.compareClone)) : null;
102
+ yield;
103
+ const forDirty = pin.dirtyClone ? (yield* serializeForCompareSteps(pin.dirtyClone)) : forComparison;
104
+ yield;
105
+ if (pin.pageFormatMarker) setFormatMarker(pin.clone, pin.pageFormatMarker);
106
+ const forSave = yield* rendererSteps(pin.clone, today, pin);
107
+ return completeSaveCapture(pin, { forSave, forComparison, forDirty });
108
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * save-prepare.js — a save capture's remaining work, run across tasks.
3
+ *
4
+ * A large document's verification is seconds of uninterrupted work, so a save that has
5
+ * to do it prepares across tasks instead: `beginSaveCapture` takes the one atomic
6
+ * snapshot in its caller's task, and this module resumes the job's steps, handing the
7
+ * event loop back whenever a slice has spent its budget. Typing during that wait saves
8
+ * the checkpoint that was captured and leaves later edits dirty.
9
+ *
10
+ * Nothing here decides whether a save may go out: the caller owns the lane, the epochs
11
+ * and the request. This is the driver alone, so it stays off the editor's ready path —
12
+ * a page whose documents are small never loads it.
13
+ */
14
+
15
+ import { defaultYieldTask, runSliced } from "../lib/slice.js";
16
+
17
+ // The job itself is in the same lazily loaded graph: a save that prepares across tasks
18
+ // takes its capture here, so the generator it resumes is never on the ready path.
19
+ export { beginSaveCapture } from "./save-job.js";
20
+
21
+ const monotonic = () => (globalThis.performance ? globalThis.performance.now() : Date.now());
22
+
23
+ /**
24
+ * Resume a capture's steps, measuring the main-thread time they spend.
25
+ *
26
+ * `activeMs` is the sum of the slices' own execution time, never the wait between them:
27
+ * it is what the autosave scheduler is told about, so a slow host or a busy tab does not
28
+ * lengthen the cooldown.
29
+ *
30
+ * @param {Generator} steps - `job.steps()` from `beginSaveCapture`
31
+ * @param {Object} [options]
32
+ * @param {number} [options.budgetMs] - main-thread time one slice may spend before yielding
33
+ * @param {{aborted: boolean}} [options.signal] - read before every step; a job whose page
34
+ * changed underneath it stops here and hands back nothing
35
+ * @returns {Promise<{result: ?Object, aborted: boolean, activeMs: number}>}
36
+ */
37
+ export async function runPreparedSteps(steps, { budgetMs = 4, signal = null } = {}) {
38
+ let activeMs = 0;
39
+ let mark = monotonic();
40
+ const yieldTask = async () => {
41
+ activeMs += monotonic() - mark;
42
+ await defaultYieldTask();
43
+ mark = monotonic();
44
+ };
45
+ let outcome;
46
+ try {
47
+ outcome = await runSliced(steps, { budgetMs, signal, yieldTask });
48
+ } finally {
49
+ activeMs += monotonic() - mark;
50
+ }
51
+ const aborted = outcome !== null && typeof outcome === "object" && outcome.aborted === true;
52
+ return { result: aborted ? null : outcome, aborted, activeMs };
53
+ }
@@ -1,6 +1,7 @@
1
1
  import { isEditMode } from "./is-edit-mode.js";
2
2
  import { downloadStored } from "./conflict-download.js";
3
- import { bevelSurface, bevelText, bevelButton, pageScheme, setShown } from "../ui/bevel-controls.js";
3
+ import { bevelSurface, bevelText, bevelButton, pageScheme, paintLook, setShown } from "../ui/bevel-controls.js";
4
+ import { TOKENS } from "../ui/bevel.js";
4
5
  import { set } from "../lib/hostile-css.js";
5
6
 
6
7
  // The one quiet word about how large this document is.
@@ -90,6 +91,7 @@ function render(size) {
90
91
  // already recommends downloading one.
91
92
  setShown(copy, size.state === "over", "inline-flex");
92
93
  place();
94
+ paintLook(root, { background: TOKENS.surface });
93
95
  }
94
96
 
95
97
  function clear() {
@@ -0,0 +1,15 @@
1
+ /**
2
+ * save-slices.js — how big a document has to be before its work hands the page back.
3
+ *
4
+ * Two walks ask the same question and must answer it the same way: a save preparing its
5
+ * bytes (core/save.js) and the source map installing or repairing its model
6
+ * (source/session.js). The number lives here, on its own, because core/save.js
7
+ * initializes the save system when it is imported: a source module that read the
8
+ * threshold from it would start the editor's discovery requests behind the source
9
+ * plugin's back, which is a fetch that plugin never asked for.
10
+ */
11
+
12
+ // A document whose last save was at least this big prepares its next one across tasks.
13
+ // Verifying a file that size is seconds of uninterrupted work, and every keystroke behind
14
+ // it waits; below the threshold the one-task capture is cheaper than the scheduling.
15
+ export const SLICED_SAVE_MIN_BYTES = 300 * 1024;