@panphora/clayjs 1.17.0 → 1.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (467) hide show
  1. package/dist/all.js +1 -1
  2. package/dist/clay-dom.js +1 -1
  3. package/dist/clay-events.js +1 -1
  4. package/dist/clay-internals.js +1 -1
  5. package/dist/clay-options.js +1 -1
  6. package/dist/clay-router.js +1 -1
  7. package/dist/clay-ui.js +1 -1
  8. package/dist/clay-utils.js +1 -1
  9. package/dist/clay.js +1 -1
  10. package/dist/clay.standalone.js +72474 -62090
  11. package/dist/m/attrs/{save-freeze-MYNVXREN.js → save-freeze-LWGF6XUN.js} +1 -1
  12. package/dist/m/c-2GRUM77Q.js +30 -0
  13. package/dist/m/c-2KIEGNC4.js +1 -0
  14. package/dist/m/{c-AMSNPOCX.js → c-2KL3BD52.js} +1 -1
  15. package/dist/m/c-2ZZBC45O.js +1 -0
  16. package/dist/m/c-372N6H3B.js +1 -0
  17. package/dist/m/{c-4WKFKEKF.js → c-3G2DGZUP.js} +1 -1
  18. package/dist/m/c-3LDGIBMN.js +1 -0
  19. package/dist/m/c-3O7OBDCM.js +1 -0
  20. package/dist/m/c-4ASQPRQL.js +1 -0
  21. package/dist/m/c-4TXNLX4K.js +6 -0
  22. package/dist/m/c-4YU3BR4D.js +6 -0
  23. package/dist/m/c-5734MF2P.js +1 -0
  24. package/dist/m/c-57XDY6HC.js +1 -0
  25. package/dist/m/{c-KAU6E43D.js → c-5B4YBZXC.js} +1 -1
  26. package/dist/m/c-5ONTYIUI.js +1 -0
  27. package/dist/m/c-5OOVFCJM.js +1 -0
  28. package/dist/m/{c-3XIRD4NU.js → c-5T6NZHNM.js} +1 -1
  29. package/dist/m/{c-74MGYWZF.js → c-5ZQV7KC4.js} +1 -1
  30. package/dist/m/{c-7OLLJPWP.js → c-6DB2OLRJ.js} +1 -1
  31. package/dist/m/c-6DNRCPNM.js +1 -0
  32. package/dist/m/c-6UGWFMQC.js +2 -0
  33. package/dist/m/c-7K4SCQB7.js +1 -0
  34. package/dist/m/c-7MG5RU63.js +1 -0
  35. package/dist/m/c-7PRJCA3E.js +1 -0
  36. package/dist/m/c-A6L3FMUS.js +1 -0
  37. package/dist/m/c-AMU3BPPR.js +4 -0
  38. package/dist/m/{c-Y5HBBIQ4.js → c-AMYEFA2E.js} +1 -1
  39. package/dist/m/c-AQYCG7QW.js +1 -0
  40. package/dist/m/c-ATKAOH23.js +1 -0
  41. package/dist/m/c-B5CS55WR.js +1 -0
  42. package/dist/m/c-B5UJQT7W.js +1 -0
  43. package/dist/m/c-B7J4K2AE.js +3 -0
  44. package/dist/m/c-BHZYSHW4.js +1 -0
  45. package/dist/m/{c-VHM5XQHK.js → c-BIJALSRD.js} +1 -1
  46. package/dist/m/c-BJ7YQ5QZ.js +1 -0
  47. package/dist/m/{c-KIPQJX4B.js → c-BRLCTNMC.js} +1 -1
  48. package/dist/m/{c-VE6ZMYMC.js → c-BUAYFEV5.js} +1 -1
  49. package/dist/m/c-C6IPRG34.js +1 -0
  50. package/dist/m/{c-KU3ZFADW.js → c-CAS3NND4.js} +1 -1
  51. package/dist/m/{c-PS5ARVWR.js → c-CCQ25GFY.js} +1 -1
  52. package/dist/m/c-CJFDLD43.js +1 -0
  53. package/dist/m/c-COPTL5WB.js +1 -0
  54. package/dist/m/c-D3E4WJ4M.js +166 -0
  55. package/dist/m/c-D5IKEKXW.js +7 -0
  56. package/dist/m/c-DHBRA6DL.js +1 -0
  57. package/dist/m/c-DKLS74XB.js +72 -0
  58. package/dist/m/c-DVYGFTSK.js +3 -0
  59. package/dist/m/c-E3ZXARWA.js +1 -0
  60. package/dist/m/c-EK63G3CG.js +1 -0
  61. package/dist/m/c-ERQSNR6K.js +13 -0
  62. package/dist/m/c-EVYAISP3.js +1 -0
  63. package/dist/m/c-EXKHPOVC.js +3 -0
  64. package/dist/m/{c-QJ4OM3G6.js → c-FWKHM4E7.js} +1 -1
  65. package/dist/m/{c-4Q2XTQ2N.js → c-GBKB5WDL.js} +1 -1
  66. package/dist/m/c-GNDL3TKB.js +1 -0
  67. package/dist/m/c-GRWMD6O5.js +1 -0
  68. package/dist/m/c-GRXHHFPO.js +1 -0
  69. package/dist/m/c-HG2C77BM.js +4 -0
  70. package/dist/m/c-HHGJBU4D.js +1 -0
  71. package/dist/m/c-HMV6P4RX.js +1 -0
  72. package/dist/m/c-HMYWIHFG.js +1 -0
  73. package/dist/m/c-HT2DKBV2.js +1 -0
  74. package/dist/m/c-I2SCCUEW.js +1 -0
  75. package/dist/m/{c-TJ6WOJL3.js → c-ICJYHABF.js} +2 -2
  76. package/dist/m/c-IMZCWYHR.js +7 -0
  77. package/dist/m/c-IVMD3FEM.js +1 -0
  78. package/dist/m/{c-WDC5GVKU.js → c-IVTRVENZ.js} +1 -1
  79. package/dist/m/c-JKZJIUIP.js +1 -0
  80. package/dist/m/{c-UDUUJPFZ.js → c-JNLX3RJB.js} +1 -1
  81. package/dist/m/c-K3ADYNLU.js +1 -0
  82. package/dist/m/c-KGHVFRR3.js +1 -0
  83. package/dist/m/{c-5QWQ7A5L.js → c-KJJPV77R.js} +1 -1
  84. package/dist/m/{c-CZUSOB7V.js → c-KNPDOMDY.js} +1 -1
  85. package/dist/m/{c-EZU56NQV.js → c-KP64LT2A.js} +2 -2
  86. package/dist/m/c-KQ7E3PYY.js +1 -0
  87. package/dist/m/c-L3F5ZLIV.js +1 -0
  88. package/dist/m/{c-3RUU4TPX.js → c-L42OLXWM.js} +3 -2
  89. package/dist/m/c-L5VFATT6.js +1 -0
  90. package/dist/m/c-L7JL5TL3.js +3 -0
  91. package/dist/m/c-LKSL4AUJ.js +1 -0
  92. package/dist/m/c-M6FNQCTQ.js +1 -0
  93. package/dist/m/c-MCOOHCSG.js +1 -0
  94. package/dist/m/c-MHI3GYKF.js +1 -0
  95. package/dist/m/c-MWXFLW4J.js +1 -0
  96. package/dist/m/{c-5RJFTLSF.js → c-NEL7HQW3.js} +1 -1
  97. package/dist/m/{c-DCXOXBF3.js → c-NQJRVJ56.js} +1 -1
  98. package/dist/m/c-NWX3MFT4.js +1 -0
  99. package/dist/m/{c-HPURYVZS.js → c-OJYM642Q.js} +1 -1
  100. package/dist/m/c-OKXI52N7.js +1 -0
  101. package/dist/m/{c-R3L7ZJGB.js → c-OPLUZR3A.js} +1 -1
  102. package/dist/m/{c-GSFIGKOP.js → c-OTMKN2OO.js} +1 -1
  103. package/dist/m/{c-6NC5I2TV.js → c-OYRZ7XG2.js} +1 -1
  104. package/dist/m/{c-EVCQ3BKA.js → c-P3OFYUMA.js} +1 -1
  105. package/dist/m/c-PLN76GF3.js +6 -0
  106. package/dist/m/c-PLP4JKFC.js +1 -0
  107. package/dist/m/c-Q26MME6N.js +1 -0
  108. package/dist/m/{c-S7H2DRCB.js → c-Q3XOWZEV.js} +1 -1
  109. package/dist/m/{c-FRLHSY7E.js → c-QIKT6SKM.js} +1 -1
  110. package/dist/m/c-QIXT3Q7C.js +28 -0
  111. package/dist/m/c-QO4HNGCQ.js +1 -0
  112. package/dist/m/{c-JU3NK25H.js → c-R27DWQKX.js} +1 -1
  113. package/dist/m/{c-RSMOODDU.js → c-R2XUODVY.js} +1 -1
  114. package/dist/m/{c-QNKREOL6.js → c-R7LBFMF5.js} +1 -1
  115. package/dist/m/c-RND3G2S2.js +1 -0
  116. package/dist/m/{c-Q622NS2Y.js → c-S2U6NJU4.js} +1 -1
  117. package/dist/m/c-SPZPGD3R.js +1 -0
  118. package/dist/m/c-SW4YEG2D.js +8 -0
  119. package/dist/m/c-T637T7SN.js +1 -0
  120. package/dist/m/c-TJAXGIRV.js +1 -0
  121. package/dist/m/{c-RYUXUUBX.js → c-TNEM3RNM.js} +1 -1
  122. package/dist/m/c-TTB5EK4S.js +32 -0
  123. package/dist/m/c-U7SHNWDH.js +1 -0
  124. package/dist/m/c-UDHIVLYH.js +20 -0
  125. package/dist/m/{c-BBVGTQG7.js → c-UGNV5MFB.js} +1 -1
  126. package/dist/m/{c-QICP2YR5.js → c-UORXLSD3.js} +1 -1
  127. package/dist/m/c-UPD33ZPE.js +1 -0
  128. package/dist/m/c-URSMSFCB.js +1 -0
  129. package/dist/m/c-UY4QX42B.js +1 -0
  130. package/dist/m/c-V44KWI2L.js +4 -0
  131. package/dist/m/c-VILENZTD.js +1 -0
  132. package/dist/m/{c-HDDEEL77.js → c-VJYEVRYK.js} +1 -1
  133. package/dist/m/{c-SMBZG5X4.js → c-VR2BJ4AN.js} +1 -1
  134. package/dist/m/c-W4JCVLAB.js +1 -0
  135. package/dist/m/c-W6DQSHXI.js +135 -0
  136. package/dist/m/c-WBGWGOM7.js +1 -0
  137. package/dist/m/{c-4L3BTFRK.js → c-WCXLG3MV.js} +1 -1
  138. package/dist/m/c-WGW764WX.js +1 -0
  139. package/dist/m/c-WMRJKQ2V.js +5 -0
  140. package/dist/m/c-WNTYKCDG.js +1 -0
  141. package/dist/m/c-WQWIRWO3.js +72 -0
  142. package/dist/m/c-X5LNHET5.js +1 -0
  143. package/dist/m/{c-I2ZATQCD.js → c-X5TSXZYJ.js} +2 -2
  144. package/dist/m/c-XGH4GS2T.js +1 -0
  145. package/dist/m/c-Y6F2FORL.js +1 -0
  146. package/dist/m/c-YBKHUAK7.js +10 -0
  147. package/dist/m/c-YBSS55KU.js +20 -0
  148. package/dist/m/c-YCLU4UNT.js +2 -0
  149. package/dist/m/c-YVZ2L7CE.js +1 -0
  150. package/dist/m/c-ZK4GIZH2.js +1 -0
  151. package/dist/m/c-ZRXJD4G6.js +1 -0
  152. package/dist/m/c-ZWBI4HYK.js +1 -0
  153. package/dist/m/core/{admin-attrs-QHOKPJ7W.js → admin-attrs-YY7T5FGU.js} +1 -1
  154. package/dist/m/core/autosave-S6OMNDCK.js +1 -0
  155. package/dist/m/core/conflict-notice-IWHMHH43.js +1 -0
  156. package/dist/m/core/{host-permission-FNXVEFNE.js → host-permission-6EVJGNPL.js} +1 -1
  157. package/dist/m/core/is-edit-mode-GCX6767E.js +1 -0
  158. package/dist/m/core/{persist-JFYRHMRJ.js → persist-UKXV5KLA.js} +1 -1
  159. package/dist/m/core/recovery-DRFZRJED.js +1 -0
  160. package/dist/m/core/save-LYY7K6U7.js +1 -0
  161. package/dist/m/core/save-core-MKRPSEEO.js +1 -0
  162. package/dist/m/core/snapshot-NU3YM6GR.js +1 -0
  163. package/dist/m/core/unsaved-warning-7QTKEN7Z.js +1 -0
  164. package/dist/m/internals/{index-PVXGPUDA.js → index-TGLQINN3.js} +1 -1
  165. package/dist/m/loader-3BBNZZGM.js +1 -0
  166. package/dist/m/manifest-26C55578.js +1 -0
  167. package/dist/m/manifest-edit-4396A009.js +1 -0
  168. package/dist/m/manifest.json +2111 -1573
  169. package/dist/m/page/{editor-NNJA4OKD.js → editor-6LRB3GE4.js} +123 -21
  170. package/dist/m/plugins/ai-edit-UTV7YO2I.js +6 -0
  171. package/dist/m/plugins/{cms-HP3LSTPN.js → cms-LRAOZHT7.js} +9 -9
  172. package/dist/m/plugins/{demo-RM5IR6AO.js → demo-OYO5GCAO.js} +1 -1
  173. package/dist/m/plugins/{derive-UAAA4V4S.js → derive-FRE5JJQE.js} +1 -1
  174. package/dist/m/plugins/indicator-4VDXWYTD.js +1 -0
  175. package/dist/m/plugins/live-AD4H6XSL.js +1 -0
  176. package/dist/m/plugins/menu-M5WW7DBF.js +1 -0
  177. package/dist/m/plugins/page-MD3T2ZX3.js +2 -0
  178. package/dist/m/plugins/page-view-PAWVZNFY.js +1 -0
  179. package/dist/m/plugins/{people-SGPJKANK.js → people-JCHVPF7P.js} +1 -1
  180. package/dist/m/plugins/quickcrop-BQ5YYFCH.js +26 -0
  181. package/dist/m/plugins/{richclay-NRQDSWX6.js → richclay-KLGETK77.js} +2 -2
  182. package/dist/m/plugins/sortable-4S6SQC3D.js +1 -0
  183. package/dist/m/plugins/source-TJXOIOYY.js +1 -0
  184. package/dist/m/plugins/undo-NLPGCJQY.js +1 -0
  185. package/dist/m/plugins/{unsent-HJFLQJ2O.js → unsent-OA4RGBUT.js} +1 -1
  186. package/dist/m/plugins/upgrade-HSUNDNJD.js +1 -0
  187. package/dist/m/plugins/{upload-WLYKK6TL.js → upload-NHGJXZK2.js} +1 -1
  188. package/dist/m/plugins/visitor-drafts-BW43DGY3.js +20 -0
  189. package/dist/m/plugins/visitor-drafts-owner-7ODQG7JX.js +1 -0
  190. package/dist/m/plugins/{wire-4N6GYAF6.js → wire-VBWXEXU2.js} +1 -1
  191. package/dist/m/reader-core-4W2KYEWQ.js +77 -0
  192. package/dist/m/route/route-62CHXL5B.js +1 -0
  193. package/dist/m/sync/live-sync-MO5DH33W.js +1 -0
  194. package/dist/m/ui/index-IVWN2QC4.js +1 -0
  195. package/dist/m/vendor/sap-core.vendor-RN4ZVY4X.js +6 -0
  196. package/dist/offline-manifest.json +1367 -930
  197. package/dist/page/themes/light-1.css +8 -7
  198. package/dist/server/core.cjs +1440 -288
  199. package/dist/server/offline-runtime.cjs +63 -63
  200. package/dist/server/offline-runtime.mjs +63 -63
  201. package/dist/server/page-compaction-worker.cjs +3710 -0
  202. package/dist/server/rooms.mjs +5 -0
  203. package/docs/guide.md +653 -101
  204. package/entries/sap.js +5 -5
  205. package/package.json +1 -1
  206. package/src/core/admin-contenteditable.js +5 -2
  207. package/src/core/admin-inputs.js +1 -0
  208. package/src/core/admin-onclick.js +2 -1
  209. package/src/core/conflict-notice-ui.js +4 -7
  210. package/src/core/download.js +2 -2
  211. package/src/core/host-meta.js +38 -0
  212. package/src/core/is-edit-mode.js +17 -1
  213. package/src/core/mode-key.js +125 -19
  214. package/src/core/mode-support.js +5 -0
  215. package/src/core/mode-transition.js +20 -6
  216. package/src/core/preserve-authored.js +4 -4
  217. package/src/core/recovery.js +121 -33
  218. package/src/core/save-accepted.js +33 -0
  219. package/src/core/save-core.js +106 -31
  220. package/src/core/save-job.js +108 -0
  221. package/src/core/save-prepare.js +53 -0
  222. package/src/core/save-size-notice.js +3 -1
  223. package/src/core/save-slices.js +15 -0
  224. package/src/core/save.js +557 -127
  225. package/src/core/serialize-steps.js +109 -0
  226. package/src/core/snapshot.js +170 -39
  227. package/src/core/source-map.js +83 -38
  228. package/src/core/stale-host-notice.js +1 -0
  229. package/src/core/upgrade-run.js +59 -8
  230. package/src/dom/active-element.js +5 -0
  231. package/src/lib/assets.js +1 -0
  232. package/src/lib/slice.js +65 -0
  233. package/src/lib/uimotion.js +774 -0
  234. package/src/lib/unsaved-state.js +2 -2
  235. package/src/live/fast-lane.js +10 -3
  236. package/src/live/local-lane.js +202 -11
  237. package/src/live/ops.js +16 -0
  238. package/src/live/page-compaction.js +162 -0
  239. package/src/live/server-lane.js +1 -0
  240. package/src/live/store.js +11 -3
  241. package/src/loader-logic.js +3 -1
  242. package/src/loader.js +14 -4
  243. package/src/page/adopt-boot.js +2 -1
  244. package/src/page/awareness.js +33 -2
  245. package/src/page/blocks.js +89 -27
  246. package/src/page/boot-pack.js +1 -1
  247. package/src/page/chart-tables.js +21 -3
  248. package/src/page/chart.js +406 -63
  249. package/src/page/checklist.js +10 -135
  250. package/src/page/checkpoint-proof.js +7 -1
  251. package/src/page/chrome.js +250 -51
  252. package/src/page/clipboard-shape.js +126 -0
  253. package/src/page/drag.js +33 -3
  254. package/src/page/editor.js +136 -25
  255. package/src/page/export-assets.js +357 -0
  256. package/src/page/export-html.js +209 -0
  257. package/src/page/export-markdown.js +361 -0
  258. package/src/page/features/checklist.js +428 -0
  259. package/src/page/features/export-html.js +189 -0
  260. package/src/page/features/export-markdown.js +169 -0
  261. package/src/page/features/import-markdown.js +349 -0
  262. package/src/page/features/media.js +61 -5
  263. package/src/page/features/registry.js +18 -0
  264. package/src/page/features/table.js +373 -83
  265. package/src/page/features/title-cover.js +331 -0
  266. package/src/page/features/title-icon.js +213 -0
  267. package/src/page/features/toc.js +87 -0
  268. package/src/page/format-command.js +25 -1
  269. package/src/page/format-gate.js +68 -18
  270. package/src/page/format-host.js +17 -0
  271. package/src/page/format-native-commands.js +21 -5
  272. package/src/page/image-actions.js +64 -0
  273. package/src/page/import-markdown.js +262 -0
  274. package/src/page/items.js +135 -17
  275. package/src/page/markdown.js +6 -2
  276. package/src/page/media.js +35 -119
  277. package/src/page/placeholder.js +14 -1
  278. package/src/page/projection.js +98 -10
  279. package/src/page/recover.js +10 -12
  280. package/src/page/recovery-ui.js +464 -0
  281. package/src/page/rich/chart.js +63 -7
  282. package/src/page/save-projection.js +28 -0
  283. package/src/page/save-states.js +13 -3
  284. package/src/page/schema.js +19 -3
  285. package/src/page/table-paste.js +3 -1
  286. package/src/page/table-style.js +4 -4
  287. package/src/page/table.js +44 -2
  288. package/src/page/themes/light-1.css +8 -7
  289. package/src/page/themes/light-1.source.css +8 -7
  290. package/src/page/toc-link.js +31 -0
  291. package/src/page/toc.js +5 -0
  292. package/src/page/view-hooks.js +38 -12
  293. package/src/page/view.js +13 -0
  294. package/src/page/ybind/binding.js +88 -5
  295. package/src/page/ybind/bridge.js +187 -20
  296. package/src/page/ybind/containment.js +162 -0
  297. package/src/page/ybind/doc.js +17 -9
  298. package/src/page/ybind/file-debt.js +63 -0
  299. package/src/page/ybind/layout.js +7 -2
  300. package/src/page/ybind/marks.js +1 -1
  301. package/src/page/ybind/moves.js +5 -4
  302. package/src/page/ybind/origins.js +106 -4
  303. package/src/page/ybind/parts.js +8 -1
  304. package/src/page/ybind/provider.js +113 -17
  305. package/src/page/ybind/recovery-proof.js +32 -9
  306. package/src/page/ybind/recovery-replay.js +558 -15
  307. package/src/page/ybind/recovery.js +174 -45
  308. package/src/page/ybind/table.js +67 -11
  309. package/src/page/ybind/walk-delta.js +258 -0
  310. package/src/page/ybind/walk.js +54 -29
  311. package/src/page/ybind/writer.js +1 -1
  312. package/src/plugins/ai-edit.js +13 -9
  313. package/src/plugins/indicator-chip.js +91 -0
  314. package/src/plugins/indicator.js +13 -80
  315. package/src/plugins/live.js +89 -14
  316. package/src/plugins/menu/actions.js +196 -0
  317. package/src/plugins/menu/people.js +279 -0
  318. package/src/plugins/menu/placement.js +121 -0
  319. package/src/plugins/menu/preferences.js +9 -0
  320. package/src/plugins/menu/seo.js +316 -397
  321. package/src/plugins/menu/style.js +171 -0
  322. package/src/plugins/menu/variants.js +296 -0
  323. package/src/plugins/menu.js +186 -183
  324. package/src/plugins/page-view.js +3 -2
  325. package/src/plugins/page.js +249 -44
  326. package/src/plugins/sortable.js +8 -1
  327. package/src/plugins/source.js +6 -0
  328. package/src/plugins/upgrade.js +51 -17
  329. package/src/plugins/upload-ui.js +12 -17
  330. package/src/plugins/visitor-drafts/bottom-ui.js +12 -4
  331. package/src/plugins/visitor-drafts/intro-style.js +7 -2
  332. package/src/plugins/visitor-drafts/intro.js +5 -3
  333. package/src/plugins/visitor-drafts/owner-chip.js +8 -12
  334. package/src/plugins/visitor-drafts/prompt.js +11 -9
  335. package/src/plugins/visitor-drafts/source.js +4 -3
  336. package/src/plugins/visitor-drafts/store.js +138 -9
  337. package/src/plugins/visitor-drafts/ui-memory.js +2 -2
  338. package/src/plugins/visitor-drafts-owner.js +4 -6
  339. package/src/plugins/visitor-drafts.js +5 -3
  340. package/src/recovery/store.js +1 -1
  341. package/src/route/route.js +4 -0
  342. package/src/server/rooms/index.js +5 -1
  343. package/src/server/rooms/live-data-rooms.js +302 -50
  344. package/src/server/rooms/page-compaction-worker.js +12 -0
  345. package/src/server/rooms/page-compactor.js +92 -0
  346. package/src/server/rooms/page-room-conformance.js +461 -0
  347. package/src/source/parse-steps.js +94 -0
  348. package/src/source/session.js +233 -57
  349. package/src/sync/editing-sender.js +12 -0
  350. package/src/sync/live-sync-edit.js +22 -2
  351. package/src/sync/live-sync.js +150 -23
  352. package/src/sync/page-relay.js +52 -0
  353. package/src/sync/presence-store.js +44 -4
  354. package/src/sync/presence.js +6 -3
  355. package/src/sync/saved-stream.js +11 -6
  356. package/src/ui/bevel-controls.js +1 -0
  357. package/src/ui/bevel-dialog.js +5 -2
  358. package/src/ui/editor-ui.js +77 -0
  359. package/src/ui/page-colour.js +281 -0
  360. package/src/ui/toast.js +16 -7
  361. package/src/vendor/hyper-html-api-upgrade.vendor.js +2 -2
  362. package/src/vendor/hyper-undo.vendor.js +1 -1
  363. package/src/vendor/hypercms.vendor.js +9 -9
  364. package/src/vendor/parse5.vendor.js +2 -2
  365. package/src/vendor/sap-core.vendor.js +5 -5
  366. package/dist/m/c-2SPVQSHC.js +0 -1
  367. package/dist/m/c-336SGU5W.js +0 -1
  368. package/dist/m/c-3H6BZDNJ.js +0 -6
  369. package/dist/m/c-4DFEJ2PO.js +0 -3
  370. package/dist/m/c-56QRZTZH.js +0 -1
  371. package/dist/m/c-5J4E7O4Y.js +0 -4
  372. package/dist/m/c-5LKDWQTL.js +0 -19
  373. package/dist/m/c-5V3MOVJT.js +0 -1
  374. package/dist/m/c-6AZOGAPE.js +0 -1
  375. package/dist/m/c-6CRUUXOX.js +0 -5
  376. package/dist/m/c-6HMB24EV.js +0 -1
  377. package/dist/m/c-6KEVEQ7L.js +0 -1
  378. package/dist/m/c-A3E3NIMC.js +0 -1
  379. package/dist/m/c-BQ6LOBEE.js +0 -1
  380. package/dist/m/c-BTDI4Y23.js +0 -1
  381. package/dist/m/c-CJ3A7ZKE.js +0 -1
  382. package/dist/m/c-CQHPGKX2.js +0 -1
  383. package/dist/m/c-DFS4XZJV.js +0 -2
  384. package/dist/m/c-DNENWY4O.js +0 -1
  385. package/dist/m/c-E4LIR5X5.js +0 -1
  386. package/dist/m/c-FDNHHZY5.js +0 -1
  387. package/dist/m/c-FROJUZMV.js +0 -1
  388. package/dist/m/c-G2JPKCTP.js +0 -1
  389. package/dist/m/c-GB6TU7WV.js +0 -1
  390. package/dist/m/c-GFJJEBTU.js +0 -1
  391. package/dist/m/c-GJAEGGXY.js +0 -1
  392. package/dist/m/c-GOWBDM6J.js +0 -1
  393. package/dist/m/c-HFKUVACH.js +0 -1
  394. package/dist/m/c-HHDFCNHV.js +0 -1
  395. package/dist/m/c-IEM4NTCS.js +0 -1
  396. package/dist/m/c-JDCX26UP.js +0 -25
  397. package/dist/m/c-KGVGROZT.js +0 -1
  398. package/dist/m/c-KQ6TYJAP.js +0 -135
  399. package/dist/m/c-LFPHCXY3.js +0 -1
  400. package/dist/m/c-LRPUO2MX.js +0 -1
  401. package/dist/m/c-LWLBHLVC.js +0 -3
  402. package/dist/m/c-M2L47S2X.js +0 -7
  403. package/dist/m/c-MFP2ZWD4.js +0 -1
  404. package/dist/m/c-MKSQ4Y57.js +0 -1
  405. package/dist/m/c-NUFFJVVJ.js +0 -1
  406. package/dist/m/c-NUHSPT5P.js +0 -1
  407. package/dist/m/c-O72HVYSA.js +0 -1
  408. package/dist/m/c-OIBUTDXI.js +0 -1
  409. package/dist/m/c-OW5QIGG3.js +0 -1
  410. package/dist/m/c-PGRCK73F.js +0 -1
  411. package/dist/m/c-Q7NQD65V.js +0 -1
  412. package/dist/m/c-QH63LQOS.js +0 -1
  413. package/dist/m/c-R7IWVINQ.js +0 -1
  414. package/dist/m/c-RM7J7BV2.js +0 -1
  415. package/dist/m/c-S4ZEH46B.js +0 -9
  416. package/dist/m/c-SDAXDM2D.js +0 -1
  417. package/dist/m/c-SMGU7RT3.js +0 -235
  418. package/dist/m/c-SPIORRYS.js +0 -1
  419. package/dist/m/c-SWLRT66R.js +0 -1
  420. package/dist/m/c-TCJTVAFN.js +0 -1
  421. package/dist/m/c-TQEMVJCS.js +0 -1
  422. package/dist/m/c-TYLXX4NF.js +0 -21
  423. package/dist/m/c-U62OB4BT.js +0 -1
  424. package/dist/m/c-U6YWUAR3.js +0 -8
  425. package/dist/m/c-UDCSU5G5.js +0 -2
  426. package/dist/m/c-VI5RMSVY.js +0 -1
  427. package/dist/m/c-VRZN56XY.js +0 -1
  428. package/dist/m/c-W5MCHEGA.js +0 -1
  429. package/dist/m/c-WNYBLIOZ.js +0 -1
  430. package/dist/m/c-WWXIDH4O.js +0 -28
  431. package/dist/m/c-XCNTK76U.js +0 -1
  432. package/dist/m/c-XMS6DPBR.js +0 -1
  433. package/dist/m/c-ZUCOKOHT.js +0 -1
  434. package/dist/m/c-ZVPVXTJF.js +0 -4
  435. package/dist/m/c-ZVRKXKM7.js +0 -1
  436. package/dist/m/core/autosave-HRYGUWV6.js +0 -1
  437. package/dist/m/core/conflict-notice-6MROJN2L.js +0 -1
  438. package/dist/m/core/is-edit-mode-KTWA3O5N.js +0 -1
  439. package/dist/m/core/recovery-FLTCC2OC.js +0 -1
  440. package/dist/m/core/save-HQZ4S667.js +0 -1
  441. package/dist/m/core/save-core-KGQWFUNH.js +0 -1
  442. package/dist/m/core/snapshot-EGQZNT45.js +0 -1
  443. package/dist/m/core/unsaved-warning-IDJLD66H.js +0 -1
  444. package/dist/m/loader-S4I7GKXR.js +0 -1
  445. package/dist/m/manifest-4C419D67.js +0 -1
  446. package/dist/m/manifest-edit-B077F288.js +0 -1
  447. package/dist/m/plugins/ai-edit-QN4PMMNL.js +0 -6
  448. package/dist/m/plugins/indicator-URDBBOQO.js +0 -1
  449. package/dist/m/plugins/live-HFWD4OIH.js +0 -1
  450. package/dist/m/plugins/menu-3HIEQSI6.js +0 -138
  451. package/dist/m/plugins/page-JOKB6XPR.js +0 -2
  452. package/dist/m/plugins/page-view-NXOJHNW6.js +0 -1
  453. package/dist/m/plugins/quickcrop-QNY3PKLD.js +0 -26
  454. package/dist/m/plugins/sortable-GWT4PWDV.js +0 -1
  455. package/dist/m/plugins/source-4ZRSAIYM.js +0 -1
  456. package/dist/m/plugins/undo-VDA5P7FH.js +0 -1
  457. package/dist/m/plugins/upgrade-W7FG5VFG.js +0 -1
  458. package/dist/m/plugins/visitor-drafts-LIDY7NH6.js +0 -21
  459. package/dist/m/plugins/visitor-drafts-owner-ZXCKBZE7.js +0 -1
  460. package/dist/m/reader-core-DXYCURFF.js +0 -68
  461. package/dist/m/route/route-KOCZSEGN.js +0 -1
  462. package/dist/m/sync/live-sync-J7VMYTKS.js +0 -1
  463. package/dist/m/ui/index-3JLVI27O.js +0 -1
  464. package/dist/m/vendor/sap-core.vendor-A7BMUYZT.js +0 -6
  465. /package/dist/m/{c-FILLTJA7.js → c-QTTJYJMA.js} +0 -0
  466. /package/dist/m/plugins/{quickcrop-QNY3PKLD.js.LEGAL.txt → quickcrop-BQ5YYFCH.js.LEGAL.txt} +0 -0
  467. /package/dist/m/plugins/{richclay-NRQDSWX6.js.LEGAL.txt → richclay-KLGETK77.js.LEGAL.txt} +0 -0
@@ -1,15 +1,15 @@
1
- import { FEATURES, featureForType } from "../page/features/registry.js";
1
+ import { FEATURES, featureForNode, featureForType } from "../page/features/registry.js";
2
2
  import { mergeEngine } from "../sync/merge-engine.js";
3
3
  import { registerUnsavedState } from "../lib/unsaved-state.js";
4
4
  import { hostMeta } from "../core/host-meta.js";
5
- import { savePageForce, holdAllSaves, releaseAllSaves, suspendAutosave, resumeAutosave, getLastSavedBytes } from "../core/save.js";
6
- import { setSaveHeader } from "../core/save-core.js";
5
+ import { savePageForce, holdAllSaves, releaseAllSaves, suspendAutosave, resumeAutosave, getLastSavedBytes, savesAdmitted } from "../core/save.js";
6
+ import { setSaveHeader, onSaveAccepted } from "../core/save-core.js";
7
7
  import { lastSeenEtag, representedEtag, recordEtag } from "../core/etag.js";
8
8
  import { captureForSaveAndComparison } from "../core/snapshot.js";
9
- import { storeOf, stageStore, closeStore, checkpointWithin, requestCheckpoint, fastLane, pageLaneReady, pageWriterReady, registerPageCheckpoints } from "./live.js";
9
+ import { storeOf, stageStore, closeStore, checkpointWithin, requestCheckpoint, requestExitCheckpoint, fastLane, localLane, lanePending, pageLaneReady, pageWriterReady, registerPageCheckpoints } from "./live.js";
10
10
  import { serializeLive } from "../live/ops.js";
11
11
  import { parseJsonRelaxed } from "../vendor/hyper-morph-json.vendor.js";
12
- import { saveStateFor, BLOCKED_TEXT, RETRY_TEXT, downloadPageCopy, downloadSnapshot, copyPageText } from "../page/save-states.js";
12
+ import { saveStateFor, pageStateFor, BLOCKED_TEXT, RETRY_TEXT, downloadPageCopy, downloadSnapshot, copyPageText } from "../page/save-states.js";
13
13
  import toast, { toastPersistent, dismissPersistent } from "../ui/toast-lazy.js";
14
14
  import { claim } from "../page/runtime-owner.js";
15
15
  import { authorIdentity } from "../page/ybind/author.js";
@@ -22,11 +22,14 @@ import { style } from "../lib/hostile-css.js";
22
22
  const ARTICLE_SEL = "article[data-page]";
23
23
  const TAG_SEL = 'script[type="application/json"][merge="page"][clay~="live"]';
24
24
  const BLOCKED = { state: "blocked", code: "unsupported-host", message: "This host does not support page editing" };
25
+ const STORE_TEXT = "Changes cannot be stored on this device. Download a copy to keep them.";
25
26
  const RETRY_BOUND_MS = 45000;
26
27
  const GENERIC_ERROR = "Couldn't save";
27
28
  const GENERIC_OFFLINE = "Offline, not saved";
28
29
  const REPLACED_TEXT = "This page was changed outside the editor. Reload to see it.";
29
30
  const TOO_LARGE_TEXT = "That change is too large to share, so it was not saved. Reload the page to keep editing.";
31
+ const CLIENT_KEY = /^[ur][0-9a-z]+(-[0-9a-z]+){2}$/;
32
+ export const PAGE_FULL_MESSAGE = "This document is full, though your changes are still here to download. To edit again, reload this tab after another editor, or the owner in another browser, deletes content.";
30
33
  const OVERWRITTEN_TEXT = "Some changes you made were replaced by a change made outside the editor.";
31
34
  const REPLACED_HOLD = "The page was changed outside the editor; reload to see it.";
32
35
  const LOADING_MS = 150;
@@ -72,7 +75,7 @@ function pageCodeReady() {
72
75
  function docFeatureIds(doc) {
73
76
  const ids = new Set();
74
77
  doc.forEach((node) => {
75
- const id = featureForType(node.type.name);
78
+ const id = featureForNode(node);
76
79
  if (id) ids.add(id);
77
80
  });
78
81
  return [...ids];
@@ -129,7 +132,7 @@ function scrollToHashBlock(view, loc = globalThis.location) {
129
132
  globalThis.requestAnimationFrame?.(() => view.nodeDOM(found)?.scrollIntoView?.({ block: "start" }));
130
133
  }
131
134
 
132
- function dispatchState(state, code, message) {
135
+ function dispatchState(state, code = null, message = "") {
133
136
  document.dispatchEvent(new CustomEvent("clay:page-save-state", { detail: { state, code, message } }));
134
137
  }
135
138
 
@@ -160,11 +163,24 @@ function armRetry(inst, code, message) {
160
163
  }, RETRY_BOUND_MS);
161
164
  }
162
165
 
163
- function showBlocked(inst, code, message) {
166
+ // The persistent save-state toasts a session raises, taken down together: at the page's
167
+ // own teardown the fade plays, and at a session's end, where nobody is left to read it,
168
+ // the nodes go now.
169
+ function dismissSaveStateToasts(inst, now = false) {
170
+ if (inst.retryTimer) {
171
+ clearTimeout(inst.retryTimer);
172
+ inst.retryTimer = null;
173
+ }
174
+ dismissPersistent(RETRY_TEXT, { now });
175
+ dismissPersistent(BLOCKED_TEXT, { now });
176
+ dismissPersistent(STORE_TEXT, { now });
177
+ }
178
+
179
+ function showBlocked(inst, code, message, state = "blocked") {
164
180
  clearRetry(inst);
165
181
  dismissGeneric();
166
- dispatchState("blocked", code, message);
167
- toastPersistent(BLOCKED_TEXT, "error", {
182
+ dispatchState(state, code, message);
183
+ toastPersistent(state === "unavailable" ? STORE_TEXT : BLOCKED_TEXT, "error", {
168
184
  detail: message,
169
185
  actions: [
170
186
  { label: "Download a copy", onClick: downloadPageCopy },
@@ -176,6 +192,7 @@ function showBlocked(inst, code, message) {
176
192
  function showSaved(inst) {
177
193
  clearRetry(inst);
178
194
  dismissPersistent(BLOCKED_TEXT);
195
+ dismissPersistent(STORE_TEXT);
179
196
  dispatchState("saved", null, "");
180
197
  }
181
198
 
@@ -218,7 +235,7 @@ function stopEditing(inst) {
218
235
  // room refused it): the saved document with the article rendered from the tab's document.
219
236
  function downloadTabCopy(inst) {
220
237
  if (!inst.tabValue) return downloadPageCopy();
221
- const { forSave } = captureForSaveAndComparison();
238
+ const { forSave } = captureForSaveAndComparison({ emitForSync: false });
222
239
  const doc = new DOMParser().parseFromString(forSave, "text/html");
223
240
  const articles = doc.querySelectorAll(ARTICLE_SEL);
224
241
  if (articles.length !== 1) return downloadPageCopy();
@@ -229,20 +246,23 @@ function downloadTabCopy(inst) {
229
246
  return downloadSnapshot(`<!DOCTYPE html>\n${doc.documentElement.outerHTML}`);
230
247
  }
231
248
 
232
- function stopWithNotice(inst, text) {
249
+ function stopWithNotice(inst, text, { reload = true } = {}) {
233
250
  if (inst.replaced) return;
234
251
  inst.replaced = true;
235
252
  stopEditing(inst);
253
+ const download = { label: "Download your copy", onClick: () => downloadTabCopy(inst) };
236
254
  toastPersistent(text, "warning", {
237
- actions: [
238
- { label: "Reload", onClick: () => window.location.reload() },
239
- { label: "Download your copy", onClick: () => downloadTabCopy(inst) },
240
- ],
255
+ actions: reload ? [{ label: "Reload", onClick: () => window.location.reload() }, download] : [download],
241
256
  });
242
257
  }
243
258
 
244
259
  const showReplaced = (inst) => stopWithNotice(inst, REPLACED_TEXT);
245
260
  const showTooLarge = (inst) => stopWithNotice(inst, TOO_LARGE_TEXT);
261
+ const showFull = (inst, code) => {
262
+ dispatchState("blocked", code, PAGE_FULL_MESSAGE);
263
+ // A reload replays the refused edit and is refused again, so the notice offers only the download.
264
+ stopWithNotice(inst, PAGE_FULL_MESSAGE, { reload: false });
265
+ };
246
266
 
247
267
  function applySaveState(inst, state, code, message) {
248
268
  if (state === "saved") showSaved(inst);
@@ -290,8 +310,7 @@ function listenForSaves(inst) {
290
310
  });
291
311
  }
292
312
 
293
- // The page the host stores now, read once: the article a first write is offered to
294
- // again, and the version it is offered against.
313
+ // The page the host stores now, including any merge state it keeps beside the file.
295
314
  async function readStoredArticle() {
296
315
  const response = await fetch(new URL(globalThis.location.href).href, { cache: "no-store" });
297
316
  if (!response?.ok) return null;
@@ -299,12 +318,17 @@ async function readStoredArticle() {
299
318
  const doc = new DOMParser().parseFromString(html, "text/html");
300
319
  const article = doc.querySelector(ARTICLE_SEL);
301
320
  if (!article) return null;
302
- return { article, etag: response.headers?.get?.("etag") || null };
321
+ const tag = doc.querySelector(TAG_SEL);
322
+ // The served root attribute names the stored document (spec §4). A host may add a weak
323
+ // HTTP ETag of its own, which names the response, never the document.
324
+ const header = response.headers?.get?.("etag") || null;
325
+ const etag = doc.documentElement.getAttribute("documentetag") || (header && !/^W\//.test(header) ? header : null);
326
+ return { article, value: tag ? parseJsonRelaxed(tag.textContent) : null, etag };
303
327
  }
304
328
 
305
329
  // A refused first write: the stored document has moved on, or it does not descend
306
- // from the seed this tab staged. The stored document is read once, the seed is rebuilt
307
- // from its article, this tab's own pending edit replays over it, and the write goes
330
+ // from the seed this tab staged. The stored document is read once, its merge state is
331
+ // kept (or seeded from the article), this tab's pending edit replays over it, and the write goes
308
332
  // out once more against the version just read. A second refusal keeps the text in the
309
333
  // tab and shows the conflict, exactly as a lone refusal does.
310
334
  async function retryFirstWrite(inst, event) {
@@ -319,11 +343,11 @@ async function retryFirstWrite(inst, event) {
319
343
  if (!found || !isCurrent(inst)) return;
320
344
  const page = await pageCodeReady();
321
345
  if (!isCurrent(inst)) return;
322
- const seed = page.seedPageFromArticle(found.article.cloneNode(true));
346
+ const seed = found.value ?? page.seedPageFromArticle(found.article.cloneNode(true));
323
347
  // This tab's next write is conditional on the version it has just read.
324
348
  const at = found.etag ?? event?.detail?.etag ?? null;
325
349
  if (at) recordEtag(at);
326
- // The tab's own edit replays over the seed the stored article states; an op the
350
+ // The tab's own edit replays over the stored page state; an op the
327
351
  // stored article cannot hold is dropped by the store, never written over it.
328
352
  inst.store.rebase(seed, { source: "state" });
329
353
  await savePageForce();
@@ -413,12 +437,7 @@ function dispose() {
413
437
  registerPageCheckpoints(null);
414
438
  if (!inst) return;
415
439
  disposeInstall(inst);
416
- if (inst.retryTimer) {
417
- clearTimeout(inst.retryTimer);
418
- inst.retryTimer = null;
419
- }
420
- dismissPersistent(RETRY_TEXT);
421
- dismissPersistent(BLOCKED_TEXT);
440
+ dismissSaveStateToasts(inst);
422
441
  dismissPersistent(REPLACED_TEXT);
423
442
  for (const message of inst.notices?.values() ?? []) dismissPersistent(message);
424
443
  if (inst.savesHeld) {
@@ -446,8 +465,12 @@ let recoveryModules = null;
446
465
 
447
466
  function pageRecovery() {
448
467
  if (!recoveryModules) {
449
- recoveryModules = Promise.all([import("../page/ybind/recovery-replay.js"), import("../page/checkpoint-proof.js")])
450
- .then(([replay, proof]) => ({ replay, proof }))
468
+ recoveryModules = Promise.all([
469
+ import("../page/ybind/recovery-replay.js"),
470
+ import("../page/checkpoint-proof.js"),
471
+ import("../page/recovery-ui.js"),
472
+ ])
473
+ .then(([replay, proof, ui]) => ({ replay, proof, ui }))
451
474
  .catch((error) => {
452
475
  console.warn("clayjs: page journal could not load", error);
453
476
  return null;
@@ -485,11 +508,36 @@ async function replayJournal(inst) {
485
508
  const outcome = await modules.replay.replayInto({ journal, state: inst.store.value, into: inst.binding.ydoc });
486
509
  // The recovered page is this tab's page now: it is offered to the room exactly as an
487
510
  // edit would be, and it is not one more journal and not one giant undo step.
511
+ installJournalStatus(inst, modules);
488
512
  if (outcome.replayed.length) inst.binding.provider.settle();
489
513
  if (outcome.review.length) inst.recoveryReview = outcome.review;
514
+ installRecoveryReview(inst, modules.ui)?.update({ rows: modules.replay.describeReview(outcome.review) });
490
515
  return outcome;
491
516
  }
492
517
 
518
+ // The review of what this page may not replay, drawn in the editor's own chrome once per
519
+ // session, with the composition shadow that keeps typing which never became a
520
+ // transaction: one object, alive exactly as long as the session that mounted it.
521
+ function installRecoveryReview(inst, ui) {
522
+ if (inst.recoveryPanel) return inst.recoveryPanel;
523
+ const view = inst.view;
524
+ const mount = view?.dom.closest(".page-shell") ?? inst.article;
525
+ if (!mount) return null;
526
+ const review = ui.createRecoveryReview({
527
+ mount,
528
+ getView: () => (inst.view && !inst.view.isDestroyed ? inst.view : null),
529
+ getJournal: () => inst.binding?.journal ?? null,
530
+ isCurrent: () => isCurrent(inst) && inst.inputOpen && !!inst.view && !inst.view.isDestroyed,
531
+ });
532
+ const detach = review.watch(view?.dom ?? null);
533
+ addDisposer(inst, () => {
534
+ detach();
535
+ review.destroy();
536
+ });
537
+ inst.recoveryPanel = review;
538
+ return review;
539
+ }
540
+
493
541
  // One accepted save retires the records a proof covers, and no others.
494
542
  function installCheckpointProof(inst) {
495
543
  registerPageCheckpoints(async ({ capture, state, article, etag }) => {
@@ -511,10 +559,28 @@ function installCheckpointProof(inst) {
511
559
  etag,
512
560
  href: await hostPageStateHref(),
513
561
  });
514
- if (answer.ok) journal.retire(answer.retired);
562
+ if (!answer.ok) return;
563
+ journal.retire(answer.retired);
564
+ inst.pageStatus?.checkpoint();
565
+ await journal.rebase?.(modules.replay.journalBase(answer.proof.state));
515
566
  });
516
567
  }
517
568
 
569
+ // Both live in the journal's module, which a page without a journal never loads.
570
+ function installJournalStatus(inst, modules) {
571
+ const journal = inst.binding?.journal;
572
+ if (!journal || inst.pageStatus) return inst.pageStatus ?? null;
573
+ inst.pageStatus = modules.replay.watchJournal({
574
+ journal,
575
+ store: inst.store,
576
+ reduce: pageStateFor,
577
+ onState: (state) => dispatchState(state),
578
+ onUnavailable: (code) => showBlocked(inst, code, "", "unavailable"),
579
+ });
580
+ addDisposer(inst, () => inst.pageStatus?.dispose());
581
+ return inst.pageStatus;
582
+ }
583
+
518
584
  export function createSteps(inst, options) {
519
585
  let meta = null;
520
586
  let binding = null;
@@ -552,6 +618,17 @@ export function createSteps(inst, options) {
552
618
  supported: host.supported,
553
619
  installed: () => identity?.token ?? null,
554
620
  revision: () => null,
621
+ // A floor rise moves this tab to the lane scope of the format it now
622
+ // writes, so a tab that cannot read that format never shares it. The
623
+ // transport is this tab's to create, on its own schedule, so the gate
624
+ // asks for it by the move rather than by the object.
625
+ lane: {
626
+ switchScope: (next, options) => {
627
+ const transport = localLane();
628
+ if (!transport) return { status: "refused", code: "lane-unavailable" };
629
+ return transport.switchScope(next, { ...options, covered: Boolean(options?.covered) || !lanePending() });
630
+ },
631
+ },
555
632
  });
556
633
  return formatGate;
557
634
  })
@@ -613,6 +690,81 @@ export function createSteps(inst, options) {
613
690
  });
614
691
  return toolsOpening;
615
692
  };
693
+
694
+ // The document's own switches travel with it: the gear panel writes them into the
695
+ // page's live store, and a document that never touched one opens no store at all.
696
+ // Turning a switch off hides a tag; the owner and due values stay in the file.
697
+ const SETTINGS_SEL = 'script[type="application/json"][merge="page.settings"][clay~="live"]';
698
+ const SETTING_KEYS = ["assignees", "dates"];
699
+ let settingsOpening = null;
700
+ let settingsValue = { assignees: true, dates: true };
701
+
702
+ // Both switches default to on, and anything but an explicit false leaves them on.
703
+ const settingsFrom = (value) => ({
704
+ assignees: value?.assignees !== false,
705
+ dates: value?.dates !== false,
706
+ });
707
+
708
+ function readSettings() {
709
+ const tag = document.querySelector(SETTINGS_SEL);
710
+ if (!tag) return settingsFrom(null);
711
+ let value = null;
712
+ try { value = JSON.parse(tag.textContent); } catch (error) { value = null; }
713
+ return settingsFrom(value);
714
+ }
715
+
716
+ function applySettings(next) {
717
+ settingsValue = settingsFrom(next);
718
+ const dom = inst.view && !inst.view.isDestroyed ? inst.view.dom : null;
719
+ if (!dom) return;
720
+ for (const key of SETTING_KEYS) {
721
+ if (settingsValue[key]) dom.removeAttribute(`data-${key}`);
722
+ else dom.setAttribute(`data-${key}`, "false");
723
+ }
724
+ }
725
+
726
+ const openSettings = () => {
727
+ if (settingsOpening) return settingsOpening;
728
+ settingsOpening = (async () => {
729
+ if (!document.querySelector(SETTINGS_SEL)) {
730
+ const tag = document.createElement("script");
731
+ tag.setAttribute("type", "application/json");
732
+ tag.setAttribute("merge", "page.settings");
733
+ tag.setAttribute("clay", "live");
734
+ tag.textContent = "{}";
735
+ document.head.appendChild(tag);
736
+ }
737
+ let handle = null;
738
+ try {
739
+ handle = window.clay.live("page.settings");
740
+ await handle.ready;
741
+ } catch (error) {
742
+ console.warn("clayjs: page plugin could not open the settings store", error);
743
+ settingsOpening = null;
744
+ return null;
745
+ }
746
+ if (!isCurrent(inst)) return null;
747
+ applySettings(handle.data);
748
+ // A peer's change hides or shows the tags here too, without the view remounting.
749
+ handle.on("change", () => applySettings(handle.data));
750
+ return handle;
751
+ })();
752
+ return settingsOpening;
753
+ };
754
+
755
+ // The gear panel's API: what the document shows now, and a switch that writes the
756
+ // document's store and takes effect here at once, without waiting for the change.
757
+ inst.settings = {
758
+ read: () => ({ ...settingsValue }),
759
+ set: async (key, on) => {
760
+ if (!SETTING_KEYS.includes(key)) throw new TypeError(`clayjs: "${key}" is not a page setting`);
761
+ const handle = await openSettings();
762
+ if (!handle) throw new Error("clayjs: the page settings store could not be opened");
763
+ const next = { ...settingsFrom(handle.data), [key]: on !== false };
764
+ handle.set([key], next[key]);
765
+ applySettings(next);
766
+ },
767
+ };
616
768
  // One notice per feature a person needed and that failed, until Retry succeeds.
617
769
  const notices = new Map();
618
770
  const notifyFailed = (id) => {
@@ -690,9 +842,29 @@ export function createSteps(inst, options) {
690
842
  const code = await pageCodeReady();
691
843
  if (!isCurrent(inst)) return { ok: false, cancelled: true };
692
844
  if (seeded) {
693
- // The seed stays a value in memory: no tag and no write until someone edits.
694
845
  try {
695
- value = source.seedValue = code.seedPageFromArticle(source.article.cloneNode(true));
846
+ const meta = await hostMeta();
847
+ const href = meta?.extensions?.includes("page-recovery-v1") && meta.document?.pageState?.href;
848
+ const etag = inst.context?.represented ? inst.context.represented.etag : representedEtag() ?? lastSeenEtag();
849
+ if (href && etag) {
850
+ const { readPageState } = await import("../page/checkpoint-proof.js");
851
+ const stored = await readPageState({ href, etag });
852
+ if (!isCurrent(inst)) return { ok: false, cancelled: true };
853
+ if (stored.ok) {
854
+ if (stored.etag !== etag) return { ok: false, reason: "etag-mismatch" };
855
+ value = parseJsonRelaxed(stored.tag);
856
+ source.persisted = true;
857
+ } else if (stored.code !== "no-page-state") {
858
+ return { ok: false, reason: stored.code };
859
+ }
860
+ } else if (href) {
861
+ const stored = await readStoredArticle();
862
+ if (!isCurrent(inst)) return { ok: false, cancelled: true };
863
+ if (!stored || !stored.article.isEqualNode(source.article)) return { ok: false, reason: "stale-document" };
864
+ value = stored.value;
865
+ source.persisted = value !== null;
866
+ }
867
+ value = source.seedValue = value ?? code.seedPageFromArticle(source.article.cloneNode(true));
696
868
  } catch (error) {
697
869
  console.warn("clayjs: page plugin could not rebuild the page it found, leaving the page as saved HTML", error);
698
870
  return { ok: false, reason: "invalid-page-source" };
@@ -816,6 +988,7 @@ function mergeRepresentedPage(store, value) {
816
988
  const staged = stageStore("page", {
817
989
  value: source.seedValue,
818
990
  represented: options.represented ?? { bytes: source.article.outerHTML, etag: representedEtag() ?? lastSeenEtag() },
991
+ persisted: source.persisted,
819
992
  });
820
993
  inst.staged = staged;
821
994
  handle = staged.handle;
@@ -876,6 +1049,8 @@ function mergeRepresentedPage(store, value) {
876
1049
  if (reason === "lineage") {
877
1050
  fastLane()?.abandon?.("page");
878
1051
  showReplaced(inst);
1052
+ } else if (reason === "page-full" || reason === "compaction-failed") {
1053
+ showFull(inst, reason);
879
1054
  } else {
880
1055
  showTooLarge(inst);
881
1056
  }
@@ -884,12 +1059,17 @@ function mergeRepresentedPage(store, value) {
884
1059
  ownedTokens: identity.owned,
885
1060
  checkpointWithin,
886
1061
  requestCheckpoint,
1062
+ requestExitCheckpoint: (options) => requestExitCheckpoint({ name: store.name, ...options }),
887
1063
  journal,
1064
+ // An exit holds admission closed, which is the only time this tab's own input
1065
+ // still owing the file holds the leave: outside one, the binding reports what it
1066
+ // always reported.
1067
+ exitClosed: () => !savesAdmitted(),
888
1068
  registerUnsavedState: (def) => {
889
1069
  registered = registerUnsavedState({
890
1070
  id: def.id,
891
1071
  isPending: () => flushFailed || def.isPending(),
892
- flush: () => trackFlush(() => def.flush()),
1072
+ flush: (options) => trackFlush(() => def.flush(options)),
893
1073
  });
894
1074
  return registered;
895
1075
  },
@@ -972,6 +1152,13 @@ function mountAndAttach(inst, source, loaded) {
972
1152
  }
973
1153
  editorView = view;
974
1154
  inst.view = view;
1155
+ applySettings(readSettings());
1156
+ // A document that states settings opens their store so a peer's change reaches this
1157
+ // tab; one that states none opens it when a switch is first written.
1158
+ if (document.querySelector(SETTINGS_SEL)) openSettings().catch(() => null);
1159
+ else listen(inst, "clay:sync-applied", () => {
1160
+ if (!settingsOpening && document.querySelector(SETTINGS_SEL)) openSettings().catch(() => null);
1161
+ });
975
1162
  const shell = view.dom.closest(".page-shell");
976
1163
  // A shell still waiting for its boot takes no input of any kind: a control inside
977
1164
  // a non-editable editor would otherwise write before the boot says ready. It is
@@ -1062,7 +1249,8 @@ function mountAndAttach(inst, source, loaded) {
1062
1249
  // lane but no writer yet, this tab claims the room (a redundant set of an
1063
1250
  // existing Yjs update: a valid nonempty frame that changes nothing) and then
1064
1251
  // waits for the election, so exactly one tab adopts.
1065
- const found = code.classifyFile(source.article, source.tags);
1252
+ // A seeded page was built from this article: nothing to adopt.
1253
+ const found = source.seeded ? { kind: "untouched" } : code.classifyFile(source.article, source.tags);
1066
1254
  const needsAdoption = found.kind === "adopt";
1067
1255
  const awaitWriter = async () => {
1068
1256
  const lane = await pageLaneReady();
@@ -1071,8 +1259,13 @@ function mountAndAttach(inst, source, loaded) {
1071
1259
  if (lane.status.writer === null) {
1072
1260
  const y = inst.store.value.y;
1073
1261
  const keys = Object.keys(y);
1074
- const key = keys.length ? keys[0] : `claim-${binding.ydoc.clientID.toString(36)}`;
1075
- const value = keys.length ? y[keys[0]] : code.yValueOf(binding.ydoc).y.seed;
1262
+ // A compacting room admits only whole client keys: an existing one again, or this
1263
+ // tab's document under a fresh one.
1264
+ const me = binding.ydoc.clientID.toString(36);
1265
+ const key = inst.store.room?.compaction?.v === 1
1266
+ ? keys.find((k) => CLIENT_KEY.test(k)) ?? `u${me}-claim-0`
1267
+ : keys.length ? keys[0] : `claim-${me}`;
1268
+ const value = Object.hasOwn(y, key) ? y[key] : code.yValueOf(binding.ydoc).y.seed;
1076
1269
  inst.store.handle.set(["y", key], value);
1077
1270
  // The session is not admitted until the commit, so its ops stay in the tab: the
1078
1271
  // claim alone is framed now, before adoption writes anything, or no election runs.
@@ -1095,8 +1288,9 @@ function mountAndAttach(inst, source, loaded) {
1095
1288
  return !lane || lane.status.writer === null ? true : lane.isWriter();
1096
1289
  },
1097
1290
  requestCheckpoint: () => checkpointWithin(0),
1098
- snapshot: () => captureForSaveAndComparison().forSave,
1291
+ snapshot: () => captureForSaveAndComparison({ emitForSync: false }).forSave,
1099
1292
  mergeTagless: inst.mergeTagless,
1293
+ seeded: source.seeded,
1100
1294
  writerReady: awaitWriter,
1101
1295
  adopted: (rev) => fastLane()?.adopted?.("page", rev, inst.store.mark),
1102
1296
  notice: (counts) => {
@@ -1184,7 +1378,7 @@ function mountAndAttach(inst, source, loaded) {
1184
1378
  // a `resume` puts the very same session back.
1185
1379
  function closeInput(inst) {
1186
1380
  if (!inst.inputOpen) return false;
1187
- setEditorActive?.(inst.view, false);
1381
+ setEditorActive?.(inst.view, false, { quiesce: true });
1188
1382
  return true;
1189
1383
  }
1190
1384
 
@@ -1263,10 +1457,7 @@ function endSession(inst) {
1263
1457
  }
1264
1458
  setSaveHeader("Page-Write", null);
1265
1459
  disposeInstall(inst);
1266
- if (inst.retryTimer) {
1267
- clearTimeout(inst.retryTimer);
1268
- inst.retryTimer = null;
1269
- }
1460
+ dismissSaveStateToasts(inst, true);
1270
1461
  inst.titleObserver?.dispose();
1271
1462
  inst.titleObserver = null;
1272
1463
  clearLoadingStatus(inst);
@@ -1399,8 +1590,22 @@ export const page = {
1399
1590
  get view() {
1400
1591
  return active?.view ?? null;
1401
1592
  },
1593
+ // The document's settings, for the panel that switches them.
1594
+ get settings() {
1595
+ return active?.settings ?? null;
1596
+ },
1402
1597
  };
1403
1598
 
1599
+ // The page's accepted bytes are the only thing that clears what this tab owes the file,
1600
+ // and one module-level observer is enough: the hook API has no unsubscribe, so a binding
1601
+ // registers none of its own and the current session's binding answers each save.
1602
+ onSaveAccepted((html) => {
1603
+ const binding = active?.binding;
1604
+ if (!binding?.filePending()) return;
1605
+ const state = pageCode?.acceptedPageState(html);
1606
+ if (state) binding.acceptFile(state);
1607
+ });
1608
+
1404
1609
  // A live entry imports this module while the committed mode is still view: the install
1405
1610
  // belongs to the explicit edit boot alone, and a managed session starts its own.
1406
1611
  page.ready = startsAtImport() ? install() : null;
@@ -250,6 +250,7 @@ function makeSortable(sortableElem, Sortable, record = loose) {
250
250
  let press = null;
251
251
  let restoring = null;
252
252
  let started = false;
253
+ let native = false;
253
254
  let ends = [];
254
255
  // A press that ends without a drop (a click, a cancel) still has to hand the
255
256
  // gesture's exclusion back: the drop itself releases it once its entry is recorded.
@@ -271,7 +272,11 @@ function makeSortable(sortableElem, Sortable, record = loose) {
271
272
  ends = [];
272
273
  if (pending) later(record, () => restoreRuntimeAttrs(pending));
273
274
  };
274
- const settleSoon = () => {
275
+ const settleSoon = (event) => {
276
+ // Chrome cancels the pointer as a native drag begins, just before Sortable's onStart:
277
+ // that press ends on the drop or dragend, not on the cancel.
278
+ if (native && event?.type === 'pointercancel') return;
279
+ native = false;
275
280
  // Only the press that became a drag holds: a click has nothing to keep out of
276
281
  // history, and its own handler's writes are the next entry.
277
282
  if (started) {
@@ -291,9 +296,11 @@ function makeSortable(sortableElem, Sortable, record = loose) {
291
296
  recorded = recordRuntimeAttrs(item);
292
297
  press = beginMove(item);
293
298
  started = false;
299
+ native = false;
294
300
  record.presses.add(recorded);
295
301
  pressed.add(recorded);
296
302
  ends = ENDS.map(type => track(record, document, type, settleSoon, true));
303
+ ends.push(track(record, document, 'dragstart', () => { native = true; }, true));
297
304
  }, true);
298
305
  options.onUnchoose = restoreSoon;
299
306
  // The one signal a click never makes: Sortable says the press became a drag, so this
@@ -191,6 +191,12 @@ export const source = {
191
191
  unpaired: () => (session ? session.unpaired() : null),
192
192
  /** The save pipeline's own ask: a promise to wait for before its capture, or null. */
193
193
  ensureForSave,
194
+ /**
195
+ * How many times this session's map has been rebuilt, or 0 before it installs.
196
+ */
197
+ get repairGeneration() {
198
+ return session ? session.repairGeneration() : 0;
199
+ },
194
200
  };
195
201
 
196
202
  /**