@panphora/clayjs 1.16.0 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (511) hide show
  1. package/dist/all.js +1 -1
  2. package/dist/clay-dom.js +1 -1
  3. package/dist/clay-events.js +1 -1
  4. package/dist/clay-internals.js +1 -1
  5. package/dist/clay-options.js +1 -1
  6. package/dist/clay-router.js +1 -1
  7. package/dist/clay-ui.js +1 -1
  8. package/dist/clay-utils.js +1 -1
  9. package/dist/clay.js +1 -1
  10. package/dist/clay.standalone.js +72519 -59839
  11. package/dist/m/attrs/refetch-on-save-ZUE7PSCI.js +1 -0
  12. package/dist/m/attrs/save-freeze-LWGF6XUN.js +1 -0
  13. package/dist/m/c-2GRUM77Q.js +30 -0
  14. package/dist/m/c-2KIEGNC4.js +1 -0
  15. package/dist/m/{c-5R4PTRG2.js → c-2KL3BD52.js} +1 -1
  16. package/dist/m/c-2YYDMLNZ.js +1 -0
  17. package/dist/m/c-2ZZBC45O.js +1 -0
  18. package/dist/m/c-372N6H3B.js +1 -0
  19. package/dist/m/{c-4WKFKEKF.js → c-3G2DGZUP.js} +1 -1
  20. package/dist/m/c-3LDGIBMN.js +1 -0
  21. package/dist/m/c-3O7OBDCM.js +1 -0
  22. package/dist/m/c-4ASQPRQL.js +1 -0
  23. package/dist/m/c-4TXNLX4K.js +6 -0
  24. package/dist/m/c-4YU3BR4D.js +6 -0
  25. package/dist/m/c-5734MF2P.js +1 -0
  26. package/dist/m/c-57XDY6HC.js +1 -0
  27. package/dist/m/{c-VFX45IZQ.js → c-5B4YBZXC.js} +1 -1
  28. package/dist/m/c-5ONTYIUI.js +1 -0
  29. package/dist/m/c-5OOVFCJM.js +1 -0
  30. package/dist/m/c-5T6NZHNM.js +1 -0
  31. package/dist/m/{c-YDH7Q3NY.js → c-5ZQV7KC4.js} +1 -1
  32. package/dist/m/{c-Z36J63PP.js → c-6DB2OLRJ.js} +1 -1
  33. package/dist/m/c-6DNRCPNM.js +1 -0
  34. package/dist/m/c-6UGWFMQC.js +2 -0
  35. package/dist/m/c-7K4SCQB7.js +1 -0
  36. package/dist/m/c-7MG5RU63.js +1 -0
  37. package/dist/m/c-7PRJCA3E.js +1 -0
  38. package/dist/m/c-A6L3FMUS.js +1 -0
  39. package/dist/m/c-AMU3BPPR.js +4 -0
  40. package/dist/m/c-AMYEFA2E.js +1 -0
  41. package/dist/m/c-AQYCG7QW.js +1 -0
  42. package/dist/m/c-ATKAOH23.js +1 -0
  43. package/dist/m/c-B3JM5ZXC.js +1 -0
  44. package/dist/m/c-B5CS55WR.js +1 -0
  45. package/dist/m/c-B5UJQT7W.js +1 -0
  46. package/dist/m/c-B7J4K2AE.js +3 -0
  47. package/dist/m/c-BHZYSHW4.js +1 -0
  48. package/dist/m/c-BIJALSRD.js +1 -0
  49. package/dist/m/c-BJ7YQ5QZ.js +1 -0
  50. package/dist/m/c-BPF2NSPV.js +1 -0
  51. package/dist/m/{c-KIPQJX4B.js → c-BRLCTNMC.js} +1 -1
  52. package/dist/m/c-BUAYFEV5.js +1 -0
  53. package/dist/m/c-C6IPRG34.js +1 -0
  54. package/dist/m/{c-2JL6E75N.js → c-CAS3NND4.js} +1 -1
  55. package/dist/m/{c-4DRMS4QG.js → c-CCQ25GFY.js} +1 -1
  56. package/dist/m/c-CJFDLD43.js +1 -0
  57. package/dist/m/c-COPTL5WB.js +1 -0
  58. package/dist/m/c-D3E4WJ4M.js +166 -0
  59. package/dist/m/c-D5IKEKXW.js +7 -0
  60. package/dist/m/c-DHBRA6DL.js +1 -0
  61. package/dist/m/c-DKLS74XB.js +72 -0
  62. package/dist/m/c-DVYGFTSK.js +3 -0
  63. package/dist/m/c-E3ZXARWA.js +1 -0
  64. package/dist/m/c-EK63G3CG.js +1 -0
  65. package/dist/m/c-ERQSNR6K.js +13 -0
  66. package/dist/m/c-EVYAISP3.js +1 -0
  67. package/dist/m/c-EXKHPOVC.js +3 -0
  68. package/dist/m/{c-7VGZ7RU5.js → c-FWKHM4E7.js} +1 -1
  69. package/dist/m/{c-4Q2XTQ2N.js → c-GBKB5WDL.js} +1 -1
  70. package/dist/m/c-GNDL3TKB.js +1 -0
  71. package/dist/m/c-GRWMD6O5.js +1 -0
  72. package/dist/m/c-GRXHHFPO.js +1 -0
  73. package/dist/m/{c-OYTFP5QN.js → c-GY422N3Q.js} +1 -1
  74. package/dist/m/c-HAWFNBYG.js +1 -0
  75. package/dist/m/c-HG2C77BM.js +4 -0
  76. package/dist/m/c-HHGJBU4D.js +1 -0
  77. package/dist/m/c-HMV6P4RX.js +1 -0
  78. package/dist/m/c-HMYWIHFG.js +1 -0
  79. package/dist/m/c-HT2DKBV2.js +1 -0
  80. package/dist/m/c-I2SCCUEW.js +1 -0
  81. package/dist/m/{c-HRDCD3WN.js → c-ICJYHABF.js} +2 -2
  82. package/dist/m/c-IMZCWYHR.js +7 -0
  83. package/dist/m/c-IVMD3FEM.js +1 -0
  84. package/dist/m/c-IVTRVENZ.js +1 -0
  85. package/dist/m/c-IW6GGOU2.js +1 -0
  86. package/dist/m/c-JKZJIUIP.js +1 -0
  87. package/dist/m/{c-NZMNXVF6.js → c-JNLX3RJB.js} +1 -1
  88. package/dist/m/c-JRKY44DK.js +1 -0
  89. package/dist/m/c-K3ADYNLU.js +1 -0
  90. package/dist/m/c-KBS3UV6B.js +1 -0
  91. package/dist/m/c-KGHVFRR3.js +1 -0
  92. package/dist/m/c-KJJPV77R.js +1 -0
  93. package/dist/m/{c-CZUSOB7V.js → c-KNPDOMDY.js} +1 -1
  94. package/dist/m/{c-5Y6TNX5W.js → c-KP64LT2A.js} +2 -2
  95. package/dist/m/c-KQ7E3PYY.js +1 -0
  96. package/dist/m/c-L3F5ZLIV.js +1 -0
  97. package/dist/m/{c-LR4CGEPT.js → c-L42OLXWM.js} +3 -2
  98. package/dist/m/c-L5VFATT6.js +1 -0
  99. package/dist/m/c-L7JL5TL3.js +3 -0
  100. package/dist/m/c-LKSL4AUJ.js +1 -0
  101. package/dist/m/c-M6FNQCTQ.js +1 -0
  102. package/dist/m/c-MCOOHCSG.js +1 -0
  103. package/dist/m/c-MHI3GYKF.js +1 -0
  104. package/dist/m/c-MWXFLW4J.js +1 -0
  105. package/dist/m/c-NEL7HQW3.js +1 -0
  106. package/dist/m/c-NQJRVJ56.js +1 -0
  107. package/dist/m/c-NWX3MFT4.js +1 -0
  108. package/dist/m/{c-HPURYVZS.js → c-OJYM642Q.js} +1 -1
  109. package/dist/m/c-OKXI52N7.js +1 -0
  110. package/dist/m/{c-MMUIUUZG.js → c-OPLUZR3A.js} +1 -1
  111. package/dist/m/{c-J7CA54IP.js → c-OTMKN2OO.js} +1 -1
  112. package/dist/m/{c-LTGMXXKO.js → c-OYRZ7XG2.js} +1 -1
  113. package/dist/m/c-P3OFYUMA.js +1 -0
  114. package/dist/m/c-PLN76GF3.js +6 -0
  115. package/dist/m/c-PLP4JKFC.js +1 -0
  116. package/dist/m/c-Q26MME6N.js +1 -0
  117. package/dist/m/{c-S7H2DRCB.js → c-Q3XOWZEV.js} +1 -1
  118. package/dist/m/{c-SWRJFCNN.js → c-QIKT6SKM.js} +1 -1
  119. package/dist/m/c-QIXT3Q7C.js +28 -0
  120. package/dist/m/c-QO4HNGCQ.js +1 -0
  121. package/dist/m/c-R27DWQKX.js +1 -0
  122. package/dist/m/{c-HVYXIS72.js → c-R2XUODVY.js} +1 -1
  123. package/dist/m/{c-QNKREOL6.js → c-R7LBFMF5.js} +1 -1
  124. package/dist/m/c-RND3G2S2.js +1 -0
  125. package/dist/m/c-S2U6NJU4.js +1 -0
  126. package/dist/m/c-SPZPGD3R.js +1 -0
  127. package/dist/m/c-SW4YEG2D.js +8 -0
  128. package/dist/m/c-SZGYW5HT.js +1 -0
  129. package/dist/m/c-T637T7SN.js +1 -0
  130. package/dist/m/c-TJAXGIRV.js +1 -0
  131. package/dist/m/{c-KKWLDOPM.js → c-TNEM3RNM.js} +1 -1
  132. package/dist/m/c-TTB5EK4S.js +32 -0
  133. package/dist/m/c-U7SHNWDH.js +1 -0
  134. package/dist/m/c-UDHIVLYH.js +20 -0
  135. package/dist/m/{c-BBVGTQG7.js → c-UGNV5MFB.js} +1 -1
  136. package/dist/m/{c-TQZ2JYHD.js → c-UORXLSD3.js} +1 -1
  137. package/dist/m/c-UPD33ZPE.js +1 -0
  138. package/dist/m/c-URSMSFCB.js +1 -0
  139. package/dist/m/c-UY4QX42B.js +1 -0
  140. package/dist/m/c-V44KWI2L.js +4 -0
  141. package/dist/m/c-VG5XIN4T.js +1 -0
  142. package/dist/m/c-VILENZTD.js +1 -0
  143. package/dist/m/{c-33HUYEDI.js → c-VJYEVRYK.js} +1 -1
  144. package/dist/m/c-VR2BJ4AN.js +1 -0
  145. package/dist/m/c-W4JCVLAB.js +1 -0
  146. package/dist/m/c-W6DQSHXI.js +135 -0
  147. package/dist/m/c-WBGWGOM7.js +1 -0
  148. package/dist/m/{c-4L3BTFRK.js → c-WCXLG3MV.js} +1 -1
  149. package/dist/m/c-WGW764WX.js +1 -0
  150. package/dist/m/c-WMRJKQ2V.js +5 -0
  151. package/dist/m/c-WNTYKCDG.js +1 -0
  152. package/dist/m/c-WQWIRWO3.js +72 -0
  153. package/dist/m/c-X5LNHET5.js +1 -0
  154. package/dist/m/c-X5TSXZYJ.js +2 -0
  155. package/dist/m/c-XGH4GS2T.js +1 -0
  156. package/dist/m/c-Y6F2FORL.js +1 -0
  157. package/dist/m/c-YBKHUAK7.js +10 -0
  158. package/dist/m/c-YBSS55KU.js +20 -0
  159. package/dist/m/c-YCLU4UNT.js +2 -0
  160. package/dist/m/c-YVZ2L7CE.js +1 -0
  161. package/dist/m/c-ZK4GIZH2.js +1 -0
  162. package/dist/m/c-ZRXJD4G6.js +1 -0
  163. package/dist/m/c-ZWBI4HYK.js +1 -0
  164. package/dist/m/{c-HT33EQRN.js → c-ZWIOJGMT.js} +1 -1
  165. package/dist/m/core/admin-attrs-YY7T5FGU.js +1 -0
  166. package/dist/m/core/autosave-S6OMNDCK.js +1 -0
  167. package/dist/m/core/conflict-notice-IWHMHH43.js +1 -0
  168. package/dist/m/core/host-permission-6EVJGNPL.js +1 -0
  169. package/dist/m/core/is-edit-mode-GCX6767E.js +1 -0
  170. package/dist/m/core/{persist-HKHA66IY.js → persist-UKXV5KLA.js} +1 -1
  171. package/dist/m/core/recovery-DRFZRJED.js +1 -0
  172. package/dist/m/core/save-LYY7K6U7.js +1 -0
  173. package/dist/m/core/save-core-MKRPSEEO.js +1 -0
  174. package/dist/m/core/snapshot-NU3YM6GR.js +1 -0
  175. package/dist/m/core/unsaved-warning-7QTKEN7Z.js +1 -0
  176. package/dist/m/events/index-RN2JNGBV.js +1 -0
  177. package/dist/m/internals/index-TGLQINN3.js +1 -0
  178. package/dist/m/lib/cache-bust-MUMFXZK6.js +1 -0
  179. package/dist/m/loader-3BBNZZGM.js +1 -0
  180. package/dist/m/manifest-F449FF98.js +1 -0
  181. package/dist/m/manifest-edit-4396A009.js +1 -0
  182. package/dist/m/manifest.json +2509 -1474
  183. package/dist/m/options/options-EJMS3K24.js +1 -0
  184. package/dist/m/page/editor-6LRB3GE4.js +304 -0
  185. package/dist/m/plugins/ai-edit-UTV7YO2I.js +6 -0
  186. package/dist/m/plugins/{cms-HP3LSTPN.js → cms-LRAOZHT7.js} +9 -9
  187. package/dist/m/plugins/demo-OYO5GCAO.js +1 -0
  188. package/dist/m/plugins/{derive-UAAA4V4S.js → derive-FRE5JJQE.js} +1 -1
  189. package/dist/m/plugins/indicator-4VDXWYTD.js +1 -0
  190. package/dist/m/plugins/live-AD4H6XSL.js +1 -0
  191. package/dist/m/plugins/menu-M5WW7DBF.js +1 -0
  192. package/dist/m/plugins/page-MD3T2ZX3.js +2 -0
  193. package/dist/m/plugins/page-view-PAWVZNFY.js +1 -0
  194. package/dist/m/plugins/people-JCHVPF7P.js +1 -0
  195. package/dist/m/plugins/quickcrop-BQ5YYFCH.js +26 -0
  196. package/dist/m/plugins/{richclay-RC3L2F6X.js → richclay-KLGETK77.js} +2 -2
  197. package/dist/m/plugins/sortable-4S6SQC3D.js +1 -0
  198. package/dist/m/plugins/source-TJXOIOYY.js +1 -0
  199. package/dist/m/plugins/undo-NLPGCJQY.js +1 -0
  200. package/dist/m/plugins/{unsent-HJFLQJ2O.js → unsent-OA4RGBUT.js} +1 -1
  201. package/dist/m/plugins/upgrade-HSUNDNJD.js +1 -0
  202. package/dist/m/plugins/upload-NHGJXZK2.js +1 -0
  203. package/dist/m/plugins/visitor-drafts-BW43DGY3.js +20 -0
  204. package/dist/m/plugins/visitor-drafts-owner-7ODQG7JX.js +1 -0
  205. package/dist/m/plugins/wire-VBWXEXU2.js +1 -0
  206. package/dist/m/reader-core-4W2KYEWQ.js +77 -0
  207. package/dist/m/route/route-62CHXL5B.js +1 -0
  208. package/dist/m/sync/live-sync-MO5DH33W.js +1 -0
  209. package/dist/m/ui/index-IVWN2QC4.js +1 -0
  210. package/dist/m/vendor/sap-core.vendor-RN4ZVY4X.js +6 -0
  211. package/dist/offline-manifest.json +1560 -889
  212. package/dist/page/themes/light-1.css +33 -8
  213. package/dist/server/core.cjs +1554 -311
  214. package/dist/server/offline-runtime.cjs +63 -63
  215. package/dist/server/offline-runtime.mjs +63 -63
  216. package/dist/server/page-compaction-worker.cjs +3710 -0
  217. package/dist/server/rooms.mjs +5 -0
  218. package/docs/guide.md +653 -101
  219. package/entries/sap.js +5 -5
  220. package/package.json +1 -1
  221. package/src/core/admin-contenteditable.js +5 -2
  222. package/src/core/admin-inputs.js +1 -0
  223. package/src/core/admin-onclick.js +2 -1
  224. package/src/core/boot-phase.js +2 -1
  225. package/src/core/conflict-download.js +3 -29
  226. package/src/core/conflict-notice-ui.js +4 -7
  227. package/src/core/download.js +32 -0
  228. package/src/core/host-meta.js +56 -0
  229. package/src/core/is-edit-mode.js +17 -1
  230. package/src/core/mode-key.js +125 -19
  231. package/src/core/mode-support.js +5 -0
  232. package/src/core/mode-transition.js +20 -6
  233. package/src/core/preserve-authored.js +4 -4
  234. package/src/core/recovery.js +121 -33
  235. package/src/core/save-accepted.js +33 -0
  236. package/src/core/save-core.js +111 -32
  237. package/src/core/save-job.js +108 -0
  238. package/src/core/save-prepare.js +53 -0
  239. package/src/core/save-size-notice.js +3 -1
  240. package/src/core/save-slices.js +15 -0
  241. package/src/core/save.js +557 -127
  242. package/src/core/serialize-steps.js +109 -0
  243. package/src/core/snapshot.js +209 -40
  244. package/src/core/source-map.js +83 -38
  245. package/src/core/stale-host-notice.js +1 -0
  246. package/src/core/upgrade-run.js +59 -8
  247. package/src/dom/active-element.js +5 -0
  248. package/src/lib/assets.js +1 -0
  249. package/src/lib/autosave-scheduler.js +3 -4
  250. package/src/lib/slice.js +65 -0
  251. package/src/lib/uimotion.js +774 -0
  252. package/src/lib/unsaved-state.js +2 -2
  253. package/src/live/fast-lane.js +10 -3
  254. package/src/live/local-lane.js +202 -11
  255. package/src/live/ops.js +16 -0
  256. package/src/live/page-compaction.js +162 -0
  257. package/src/live/server-lane.js +1 -0
  258. package/src/live/store.js +11 -3
  259. package/src/loader-logic.js +3 -1
  260. package/src/loader.js +14 -4
  261. package/src/page/adopt-boot.js +2 -1
  262. package/src/page/awareness.js +33 -2
  263. package/src/page/blocks.js +122 -27
  264. package/src/page/board-views.js +2 -16
  265. package/src/page/boot-pack.js +1 -1
  266. package/src/page/chart-tables.js +29 -0
  267. package/src/page/chart.js +411 -72
  268. package/src/page/checklist.js +10 -135
  269. package/src/page/checkpoint-proof.js +295 -0
  270. package/src/page/chrome.js +257 -40
  271. package/src/page/clipboard-shape.js +126 -0
  272. package/src/page/document-title-dom.js +12 -0
  273. package/src/page/document-title-runtime.js +15 -1
  274. package/src/page/document-title.js +4 -1
  275. package/src/page/drag.js +33 -3
  276. package/src/page/editor.js +207 -31
  277. package/src/page/emoji-data.js +162 -0
  278. package/src/page/export-assets.js +357 -0
  279. package/src/page/export-html.js +209 -0
  280. package/src/page/export-markdown.js +361 -0
  281. package/src/page/features/board.js +12 -3
  282. package/src/page/features/chart.js +11 -0
  283. package/src/page/features/checklist.js +428 -0
  284. package/src/page/features/dates.js +180 -0
  285. package/src/page/features/emoji.js +187 -0
  286. package/src/page/features/export-html.js +189 -0
  287. package/src/page/features/export-markdown.js +169 -0
  288. package/src/page/features/import-markdown.js +349 -0
  289. package/src/page/features/media.js +61 -5
  290. package/src/page/features/mentions.js +188 -0
  291. package/src/page/features/registry.js +24 -1
  292. package/src/page/features/table.js +375 -85
  293. package/src/page/features/title-cover.js +331 -0
  294. package/src/page/features/title-icon.js +213 -0
  295. package/src/page/features/toc.js +87 -0
  296. package/src/page/format-command.js +33 -0
  297. package/src/page/format-gate.js +68 -18
  298. package/src/page/format-host.js +71 -0
  299. package/src/page/format-native-commands.js +158 -0
  300. package/src/page/format-native.js +42 -0
  301. package/src/page/image-actions.js +64 -0
  302. package/src/page/import-markdown.js +262 -0
  303. package/src/page/inline.js +32 -8
  304. package/src/page/items.js +141 -11
  305. package/src/page/markdown.js +30 -2
  306. package/src/page/media.js +35 -119
  307. package/src/page/mention-picker.js +99 -0
  308. package/src/page/merge.js +1 -1
  309. package/src/page/placeholder.js +14 -1
  310. package/src/page/projection.js +156 -25
  311. package/src/page/recover.js +10 -12
  312. package/src/page/recovery-ui.js +464 -0
  313. package/src/page/rich/chart.js +63 -7
  314. package/src/page/save-projection.js +67 -0
  315. package/src/page/save-states.js +14 -4
  316. package/src/page/schema.js +52 -3
  317. package/src/page/table-paste.js +3 -1
  318. package/src/page/table-style.js +4 -4
  319. package/src/page/table.js +44 -2
  320. package/src/page/themes/content.css +25 -1
  321. package/src/page/themes/light-1.css +33 -8
  322. package/src/page/themes/light-1.source.css +8 -7
  323. package/src/page/toc-link.js +31 -0
  324. package/src/page/toc.js +5 -0
  325. package/src/page/view-hooks.js +58 -0
  326. package/src/page/view.js +13 -0
  327. package/src/page/ybind/binding.js +116 -6
  328. package/src/page/ybind/bridge.js +187 -20
  329. package/src/page/ybind/containment.js +162 -0
  330. package/src/page/ybind/doc.js +17 -9
  331. package/src/page/ybind/file-debt.js +63 -0
  332. package/src/page/ybind/layout.js +7 -2
  333. package/src/page/ybind/marks.js +2 -1
  334. package/src/page/ybind/moves.js +5 -4
  335. package/src/page/ybind/origins.js +106 -4
  336. package/src/page/ybind/parts.js +8 -1
  337. package/src/page/ybind/provider.js +125 -18
  338. package/src/page/ybind/recovery-proof.js +32 -9
  339. package/src/page/ybind/recovery-replay.js +905 -4
  340. package/src/page/ybind/recovery.js +174 -45
  341. package/src/page/ybind/table.js +67 -11
  342. package/src/page/ybind/walk-delta.js +258 -0
  343. package/src/page/ybind/walk.js +54 -29
  344. package/src/page/ybind/writer.js +1 -1
  345. package/src/plugins/ai-edit.js +13 -9
  346. package/src/plugins/indicator-chip.js +91 -0
  347. package/src/plugins/indicator.js +13 -80
  348. package/src/plugins/live.js +126 -15
  349. package/src/plugins/menu/actions.js +196 -0
  350. package/src/plugins/menu/people.js +279 -0
  351. package/src/plugins/menu/placement.js +121 -0
  352. package/src/plugins/menu/preferences.js +9 -0
  353. package/src/plugins/menu/seo.js +316 -397
  354. package/src/plugins/menu/style.js +171 -0
  355. package/src/plugins/menu/variants.js +296 -0
  356. package/src/plugins/menu.js +186 -183
  357. package/src/plugins/page-view.js +3 -2
  358. package/src/plugins/page.js +362 -43
  359. package/src/plugins/sortable.js +8 -1
  360. package/src/plugins/source.js +6 -0
  361. package/src/plugins/upgrade.js +51 -17
  362. package/src/plugins/upload-ui.js +12 -17
  363. package/src/plugins/visitor-drafts/bottom-ui.js +12 -4
  364. package/src/plugins/visitor-drafts/intro-style.js +7 -2
  365. package/src/plugins/visitor-drafts/intro.js +7 -4
  366. package/src/plugins/visitor-drafts/owner-chip.js +8 -12
  367. package/src/plugins/visitor-drafts/prompt.js +11 -9
  368. package/src/plugins/visitor-drafts/source.js +4 -3
  369. package/src/plugins/visitor-drafts/store.js +138 -9
  370. package/src/plugins/visitor-drafts/ui-memory.js +2 -2
  371. package/src/plugins/visitor-drafts-owner.js +4 -6
  372. package/src/plugins/visitor-drafts.js +5 -3
  373. package/src/recovery/store.js +1 -1
  374. package/src/route/route.js +4 -0
  375. package/src/server/checkpoint/index.js +2 -2
  376. package/src/server/rooms/index.js +5 -1
  377. package/src/server/rooms/live-data-rooms.js +302 -50
  378. package/src/server/rooms/page-compaction-worker.js +12 -0
  379. package/src/server/rooms/page-compactor.js +92 -0
  380. package/src/server/rooms/page-room-conformance.js +461 -0
  381. package/src/source/parse-steps.js +94 -0
  382. package/src/source/session.js +233 -57
  383. package/src/sync/editing-sender.js +12 -0
  384. package/src/sync/live-sync-edit.js +22 -2
  385. package/src/sync/live-sync.js +150 -23
  386. package/src/sync/page-relay.js +52 -0
  387. package/src/sync/presence-store.js +44 -4
  388. package/src/sync/presence.js +6 -3
  389. package/src/sync/saved-stream.js +28 -14
  390. package/src/ui/bevel-controls.js +1 -0
  391. package/src/ui/bevel-dialog.js +5 -2
  392. package/src/ui/editor-ui.js +77 -0
  393. package/src/ui/page-colour.js +281 -0
  394. package/src/ui/toast.js +16 -7
  395. package/src/vendor/hyper-html-api-upgrade.vendor.js +2 -2
  396. package/src/vendor/hyper-undo.vendor.js +1 -1
  397. package/src/vendor/hypercms.vendor.js +9 -9
  398. package/src/vendor/parse5.vendor.js +2 -2
  399. package/src/vendor/sap-core.vendor.js +5 -5
  400. package/dist/m/attrs/refetch-on-save-NWPPXOCZ.js +0 -1
  401. package/dist/m/attrs/save-freeze-FUP4Q4X3.js +0 -1
  402. package/dist/m/c-2M73252O.js +0 -1
  403. package/dist/m/c-2SPVQSHC.js +0 -1
  404. package/dist/m/c-35NBGMZB.js +0 -1
  405. package/dist/m/c-3IQRECYS.js +0 -1
  406. package/dist/m/c-4B6H4G5V.js +0 -1
  407. package/dist/m/c-4FFHZM32.js +0 -1
  408. package/dist/m/c-4YSCRICO.js +0 -1
  409. package/dist/m/c-54RCDCBU.js +0 -1
  410. package/dist/m/c-5M4YEKCX.js +0 -4
  411. package/dist/m/c-5V3MOVJT.js +0 -1
  412. package/dist/m/c-66O32MV3.js +0 -28
  413. package/dist/m/c-6HMB24EV.js +0 -1
  414. package/dist/m/c-7O6AFR65.js +0 -1
  415. package/dist/m/c-ANQTJ3B5.js +0 -2
  416. package/dist/m/c-AYVOREBE.js +0 -1
  417. package/dist/m/c-B2ZXPOS3.js +0 -3
  418. package/dist/m/c-BMIZKMPV.js +0 -1
  419. package/dist/m/c-BTDI4Y23.js +0 -1
  420. package/dist/m/c-CJZSLMVQ.js +0 -1
  421. package/dist/m/c-DBE6ORSI.js +0 -1
  422. package/dist/m/c-EDHUR4MX.js +0 -1
  423. package/dist/m/c-EY6UOM54.js +0 -1
  424. package/dist/m/c-F3CKJTJS.js +0 -1
  425. package/dist/m/c-F7Y6FLPY.js +0 -1
  426. package/dist/m/c-FA4DZVTC.js +0 -1
  427. package/dist/m/c-G2BUZITR.js +0 -1
  428. package/dist/m/c-GB3JTV6E.js +0 -1
  429. package/dist/m/c-GBGWXTX4.js +0 -1
  430. package/dist/m/c-GHBCCVG4.js +0 -1
  431. package/dist/m/c-GJAEGGXY.js +0 -1
  432. package/dist/m/c-GOWBDM6J.js +0 -1
  433. package/dist/m/c-IB52R237.js +0 -1
  434. package/dist/m/c-IKWNE4R4.js +0 -1
  435. package/dist/m/c-IU3IYEU6.js +0 -1
  436. package/dist/m/c-IZ2EBRAJ.js +0 -1
  437. package/dist/m/c-KR4UW4KW.js +0 -1
  438. package/dist/m/c-KY3VJGUA.js +0 -8
  439. package/dist/m/c-LFPHCXY3.js +0 -1
  440. package/dist/m/c-LRPUO2MX.js +0 -1
  441. package/dist/m/c-LXENEBV7.js +0 -1
  442. package/dist/m/c-NMJRXKPC.js +0 -8
  443. package/dist/m/c-OIBUTDXI.js +0 -1
  444. package/dist/m/c-PPOSCAQD.js +0 -1
  445. package/dist/m/c-QIX5BJYN.js +0 -1
  446. package/dist/m/c-QQHEX6HA.js +0 -1
  447. package/dist/m/c-R7RMANJI.js +0 -9
  448. package/dist/m/c-RI4NT75Y.js +0 -1
  449. package/dist/m/c-RM7J7BV2.js +0 -1
  450. package/dist/m/c-RUJKSUQW.js +0 -1
  451. package/dist/m/c-SGCCC4FW.js +0 -25
  452. package/dist/m/c-T4VO4OCB.js +0 -1
  453. package/dist/m/c-TQEMVJCS.js +0 -1
  454. package/dist/m/c-TR6RMDQ7.js +0 -3
  455. package/dist/m/c-UKHTLWUW.js +0 -1
  456. package/dist/m/c-UNN7LWCT.js +0 -1
  457. package/dist/m/c-UXBMRDZB.js +0 -235
  458. package/dist/m/c-V7PZZRG7.js +0 -1
  459. package/dist/m/c-VL52ADDR.js +0 -1
  460. package/dist/m/c-WNYBLIOZ.js +0 -1
  461. package/dist/m/c-WWDBYXLK.js +0 -1
  462. package/dist/m/c-XNNW2CWK.js +0 -1
  463. package/dist/m/c-Y6USGTS5.js +0 -5
  464. package/dist/m/c-YG2NJYS6.js +0 -135
  465. package/dist/m/c-Z4VRC7GU.js +0 -7
  466. package/dist/m/c-Z6K52OLP.js +0 -19
  467. package/dist/m/c-ZDY2MDAA.js +0 -21
  468. package/dist/m/c-ZUCOKOHT.js +0 -1
  469. package/dist/m/core/admin-attrs-VGNLAUDK.js +0 -1
  470. package/dist/m/core/autosave-E4PEFPZ6.js +0 -1
  471. package/dist/m/core/conflict-notice-VQ6Y64PH.js +0 -1
  472. package/dist/m/core/host-permission-XN3K2IW3.js +0 -1
  473. package/dist/m/core/is-edit-mode-JXHDVGUL.js +0 -1
  474. package/dist/m/core/recovery-TBBSICE2.js +0 -1
  475. package/dist/m/core/save-WUCHTQBE.js +0 -1
  476. package/dist/m/core/save-core-FJVHT5M3.js +0 -1
  477. package/dist/m/core/snapshot-F3SWDZMM.js +0 -1
  478. package/dist/m/core/unsaved-warning-STKBHE6G.js +0 -1
  479. package/dist/m/events/index-23DPTUT4.js +0 -1
  480. package/dist/m/internals/index-WOVAP3RU.js +0 -1
  481. package/dist/m/lib/cache-bust-CGWT6ZJI.js +0 -1
  482. package/dist/m/loader-HXX6YNH5.js +0 -1
  483. package/dist/m/manifest-F22DEDAC.js +0 -1
  484. package/dist/m/manifest-edit-C3C7376B.js +0 -1
  485. package/dist/m/options/options-2LH76JQ3.js +0 -1
  486. package/dist/m/page/editor-XUTB2HB2.js +0 -189
  487. package/dist/m/plugins/ai-edit-ZQ4CGWIP.js +0 -6
  488. package/dist/m/plugins/demo-ZIBFAA3R.js +0 -1
  489. package/dist/m/plugins/indicator-PV4YW2ID.js +0 -1
  490. package/dist/m/plugins/live-NUTTXSBS.js +0 -1
  491. package/dist/m/plugins/menu-HCKO3DSP.js +0 -138
  492. package/dist/m/plugins/page-KSUH7G6J.js +0 -2
  493. package/dist/m/plugins/page-view-W73OW7WI.js +0 -1
  494. package/dist/m/plugins/people-AJJ32YCG.js +0 -1
  495. package/dist/m/plugins/quickcrop-QNY3PKLD.js +0 -26
  496. package/dist/m/plugins/sortable-YRMQX3ZU.js +0 -1
  497. package/dist/m/plugins/source-KHK5UUSK.js +0 -1
  498. package/dist/m/plugins/undo-OF2L3CXU.js +0 -1
  499. package/dist/m/plugins/upgrade-ZNYODP5Y.js +0 -1
  500. package/dist/m/plugins/upload-XSGNILUY.js +0 -1
  501. package/dist/m/plugins/visitor-drafts-XK5GW55Q.js +0 -21
  502. package/dist/m/plugins/visitor-drafts-owner-KSSH75EV.js +0 -1
  503. package/dist/m/plugins/wire-ZWVWKF76.js +0 -1
  504. package/dist/m/reader-core-ILWLXVMP.js +0 -68
  505. package/dist/m/route/route-KOCZSEGN.js +0 -1
  506. package/dist/m/sync/live-sync-4AUVXVJU.js +0 -1
  507. package/dist/m/ui/index-EVHKMKBL.js +0 -1
  508. package/dist/m/vendor/sap-core.vendor-A7BMUYZT.js +0 -6
  509. /package/dist/m/{c-FILLTJA7.js → c-QTTJYJMA.js} +0 -0
  510. /package/dist/m/plugins/{quickcrop-QNY3PKLD.js.LEGAL.txt → quickcrop-BQ5YYFCH.js.LEGAL.txt} +0 -0
  511. /package/dist/m/plugins/{richclay-RC3L2F6X.js.LEGAL.txt → richclay-KLGETK77.js.LEGAL.txt} +0 -0
@@ -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
  /**
@@ -9,7 +9,7 @@
9
9
  // a visitor, a draft, no network, a failed fetch, a bad digest or an equal version.
10
10
  // The 24-hour cache is hyper-html-api's own; the dotted-version comparison is
11
11
  // core/version.js, so the rules engine that upgrades a document stays the click's.
12
- import { isNewerVersion } from "../core/version.js";
12
+ import { isNewerVersion, parseVersion } from "../core/version.js";
13
13
 
14
14
  const CACHE_PREFIX = "hha:upgrade-check:";
15
15
  const TTL_MS = 24 * 60 * 60 * 1000;
@@ -77,12 +77,9 @@ async function latestManifest(sourceHref, currentVersion, { fetchFn, now }) {
77
77
  return { latest, latestUrl };
78
78
  }
79
79
 
80
- export async function findUpdate({
81
- doc = document,
82
- loc = location,
83
- fetchFn = (url, init) => fetch(url, init),
84
- now = Date.now(),
85
- } = {}) {
80
+ // The copy this page says it is: its version and the source it was copied from, or
81
+ // null for a page that is not a copy (or a person who cannot act on an offer).
82
+ function copyOf({ doc, loc }) {
86
83
  const clay = (typeof window !== "undefined" && window.clay) || {};
87
84
  // A visitor cannot act on the offer, and a draft is not the document the source
88
85
  // would replace. Neither fetches.
@@ -101,32 +98,64 @@ export async function findUpdate({
101
98
  if (!source.pathname.endsWith(".html")) return null;
102
99
  // A pristine published copy points at itself: it is the source, not a copy of one.
103
100
  if (loc && source.origin === loc.origin && source.pathname === loc.pathname) return null;
101
+ return { source, currentVersion };
102
+ }
103
+
104
+ // What the check learned. "current" is said only after the source answered and named
105
+ // nothing newer; anything that stopped the check short is "unknown", and so is an
106
+ // update the person already declined.
107
+ export async function checkUpdate({
108
+ doc = document,
109
+ loc = location,
110
+ fetchFn = (url, init) => fetch(url, init),
111
+ now = Date.now(),
112
+ } = {}) {
113
+ const copy = copyOf({ doc, loc });
114
+ if (!copy) return { status: "none", version: null, update: null };
115
+ const { source, currentVersion } = copy;
116
+ const result = (status, update = null) => ({ status, version: currentVersion, update });
104
117
  const latestUrl = new URL(source.href);
105
118
  latestUrl.pathname = latestUrl.pathname.replace(/\.html$/, ".json");
106
119
 
107
120
  const { latest } = await latestManifest(source.href, currentVersion, { fetchFn, now });
108
- if (!latest || typeof latest !== "object") return null;
109
- if (typeof latest.version !== "string" || !isNewerVersion(latest.version, currentVersion)) return null;
121
+ if (!latest || typeof latest !== "object" || typeof latest.version !== "string") return result("unknown");
122
+ if (!parseVersion(latest.version) || !parseVersion(currentVersion)) return result("unknown");
123
+ if (!isNewerVersion(latest.version, currentVersion)) return result("current");
110
124
 
125
+ const clay = (typeof window !== "undefined" && window.clay) || {};
111
126
  const storage = clay.storage && clay.storage.local;
112
- if (storage && storage.get(DISMISSED_KEY) === latest.version) return null;
127
+ if (storage && storage.get(DISMISSED_KEY) === latest.version) return result("unknown");
113
128
 
114
- if (typeof latest.html !== "string" || !latest.html) return null;
129
+ if (typeof latest.html !== "string" || !latest.html) return result("unknown");
115
130
  const htmlUrl = new URL(latest.html, latestUrl).href;
116
131
  const res = await fetchOrNull(fetchFn, htmlUrl);
117
- if (!res) return null;
132
+ if (!res) return result("unknown");
118
133
  let bytes;
119
134
  try {
120
135
  bytes = await res.arrayBuffer();
121
136
  } catch {
122
- return null;
137
+ return result("unknown");
123
138
  }
124
139
  const digest = await digestOf(bytes);
125
140
  // A manifest whose digest does not match its HTML is a host that served the wrong
126
141
  // bytes. Nothing is offered, because nothing can be trusted to be that version.
127
- if (!digest || digest !== String(latest.sha256 || "").toLowerCase()) return null;
142
+ if (!digest || digest !== String(latest.sha256 || "").toLowerCase()) return result("unknown");
128
143
 
129
- return { version: latest.version, html: htmlUrl, sha256: digest, latestUrl: latestUrl.href };
144
+ return result("update", { version: latest.version, html: htmlUrl, sha256: digest, latestUrl: latestUrl.href });
145
+ }
146
+
147
+ export async function findUpdate(options) {
148
+ return (await checkUpdate(options)).update;
149
+ }
150
+
151
+ // The menu's footer while and after the check: the bare version until the check proves
152
+ // more, then the latest-version line or, when there is one, the upgrade itself.
153
+ async function showVersion(status, version) {
154
+ const clay = window.clay;
155
+ await whenMenuReady(clay);
156
+ if (!menuReady(clay) || !version) return;
157
+ const label = status === "current" ? `This doc is at latest version (${version})` : `Version ${version}`;
158
+ clay.menu.addItem({ id: "upgrade", label, text: true });
130
159
  }
131
160
 
132
161
  function dismiss(version) {
@@ -184,8 +213,13 @@ export async function start(options = {}) {
184
213
  const clay = window.clay;
185
214
  // Toasts and the menu are drawn by satellites that may land after boot settles.
186
215
  await Promise.resolve(clay && clay.loaded && clay.loaded.ui).catch(() => {});
187
- const update = await findUpdate(options).catch(() => null);
188
- if (!update) return null;
216
+ const copy = copyOf({ doc: options.doc || document, loc: options.loc || location });
217
+ if (copy) showVersion("checking", copy.currentVersion);
218
+ const { status, version, update } = await checkUpdate(options).catch(() => ({ status: "unknown", version: copy && copy.currentVersion, update: null }));
219
+ if (!update) {
220
+ if (status !== "none") await showVersion(status, version);
221
+ return null;
222
+ }
189
223
  await announce(update);
190
224
  return update;
191
225
  }
@@ -13,12 +13,15 @@
13
13
  * stylesheet load the first time somebody mounts one, so a document that never
14
14
  * uploads never downloads them.
15
15
  *
16
- * The look is a set of `--clay-upload-*` custom properties, each with a default in
17
- * its own `var()`. A container that sets one — `.journal-drop { --clay-upload-ink:
18
- * #333 }` — restyles the row without replacing any of its behaviour.
16
+ * The row draws inside its own shadow root, so the page's CSS cannot restyle it. Its
17
+ * look is a set of `--clay-upload-*` custom properties, each with a default in its own
18
+ * `var()`, and those do reach in: a container that sets one — `.journal-drop {
19
+ * --clay-upload-ink: #333 }` — restyles the row without replacing any of its
20
+ * behaviour. The font family is the one thing it still inherits from the page.
19
21
  */
20
22
 
21
23
  import { canRetry } from "./upload-task.js";
24
+ import { mountEditorUI } from "../ui/editor-ui.js";
22
25
 
23
26
  const CSS = `.clay-upload {
24
27
  display: flex;
@@ -110,16 +113,8 @@ const CSS = `.clay-upload {
110
113
  .clay-upload__button { color: var(--clay-upload-button-ink, #8AB4FF); }
111
114
  .clay-upload[data-clay-upload-state="failed"] .clay-upload__label { color: var(--clay-upload-failure-ink, #FFB4AB); }
112
115
  }
113
- @media print { .clay-upload { display: none; } }`;
114
-
115
- function stylesOnce() {
116
- if (document.getElementById("clay-upload-style")) return;
117
- const style = document.createElement("style");
118
- style.id = "clay-upload-style";
119
- style.setAttribute("clay", "editor-ui");
120
- style.textContent = CSS;
121
- document.head.append(style);
122
- }
116
+ @media print { .clay-upload { display: none; } }
117
+ :host { font-family: inherit !important; }`;
123
118
 
124
119
  function percentOf({ loaded, total, percent }) {
125
120
  if (typeof percent === "number") return percent;
@@ -143,8 +138,6 @@ export function mount(task, container) {
143
138
  throw new TypeError("clay.upload.mount needs a container");
144
139
  }
145
140
 
146
- stylesOnce();
147
-
148
141
  const name = task.file?.name || "File";
149
142
 
150
143
  const root = document.createElement("div");
@@ -237,9 +230,11 @@ export function mount(task, container) {
237
230
  if (!retryEl.hidden) retryEl.setAttribute("aria-label", lastError ? `Retry ${name}: ${lastError}` : `Retry ${name}`);
238
231
  }
239
232
 
233
+ let ui = null;
234
+
240
235
  function unmount() {
241
236
  unsubscribe();
242
- root.remove();
237
+ ui?.remove();
243
238
  }
244
239
 
245
240
  cancelEl.addEventListener("click", () => {
@@ -257,7 +252,7 @@ export function mount(task, container) {
257
252
  });
258
253
 
259
254
  const unsubscribe = task.subscribe(draw);
260
- host.append(root);
255
+ ui = mountEditorUI({ name: "upload", content: root, css: [CSS], parent: host });
261
256
 
262
257
  return { unmount };
263
258
  }
@@ -4,12 +4,20 @@ export function announceBottomUi() {
4
4
  document.dispatchEvent(new CustomEvent(BOTTOM_UI_CHANGED));
5
5
  }
6
6
 
7
+ // ClayJS's own interfaces sit in shadow roots, where a hit test stops at the host, and the
8
+ // host takes no box of its own. So a hit on one looks inside it too.
9
+ function hitsAt(scope, x, y) {
10
+ const hits = scope.elementsFromPoint ? scope.elementsFromPoint(x, y) : [];
11
+ return hits
12
+ .filter((hit) => hit.getRootNode() === scope)
13
+ .flatMap((hit) => (hit.localName === "clay-editor-ui" && hit.shadowRoot ? hitsAt(hit.shadowRoot, x, y) : [hit]));
14
+ }
15
+
7
16
  export function bottomBarHeight(x, skip) {
8
17
  const h = window.innerHeight;
9
18
  let raise = 0;
10
- const hits = document.elementsFromPoint ? document.elementsFromPoint(x, h - 3) : [];
11
- for (const hit of hits) {
12
- for (let el = hit; el && el !== document.body && el !== document.documentElement; el = el.parentElement) {
19
+ for (const hit of hitsAt(document, x, h - 3)) {
20
+ for (let el = hit; el && el !== document.body && el !== document.documentElement; el = el.parentElement ?? el.getRootNode().host) {
13
21
  if (skip(el)) break;
14
22
  const position = getComputedStyle(el).position;
15
23
  if (position === "fixed" || position === "sticky") {
@@ -23,7 +31,7 @@ export function bottomBarHeight(x, skip) {
23
31
  }
24
32
 
25
33
  export function promptPanel(box) {
26
- const panel = document.querySelector("#hcp:not([hidden])");
34
+ const panel = document.querySelector('clay-editor-ui[data-clay-ui="download-prompt"]')?.shadowRoot.querySelector("#hcp:not([hidden])");
27
35
  if (!panel) return 0;
28
36
  const r = panel.getBoundingClientRect();
29
37
  if (!r.width || !r.height || r.right <= box.left || r.left >= box.right) return 0;
@@ -4,6 +4,12 @@ body:has(> .clay-intro) { height: auto !important; overflow: visible !important;
4
4
  position: relative;
5
5
  display: block;
6
6
  }
7
+ @media print { .clay-intro { display: none; } }
8
+ `;
9
+
10
+ // The close button sits in its own shadow root inside the author's strip, so only it is
11
+ // shielded from the page's CSS. It still takes the strip's text colour.
12
+ export const CLOSE_CSS = `:host { color: inherit !important; }
7
13
  .clay-intro__close {
8
14
  position: absolute; top: 10px; right: 10px; z-index: 1;
9
15
  display: inline-flex; align-items: center; justify-content: center;
@@ -11,9 +17,8 @@ body:has(> .clay-intro) { height: auto !important; overflow: visible !important;
11
17
  border: 0; border-radius: 4px; background: none; box-shadow: none;
12
18
  color: inherit; opacity: .55; cursor: pointer; line-height: 1;
13
19
  }
14
- .clay-intro .clay-intro__close:hover { opacity: 1; background: none; color: inherit; }
20
+ .clay-intro__close:hover { opacity: 1; background: none; color: inherit; }
15
21
  .clay-intro__close:focus-visible { opacity: 1; outline: 2px solid currentColor; outline-offset: 2px; }
16
22
  .clay-intro__close svg { display: block; width: 12px; height: 12px; fill: currentColor; stroke: none; }
17
23
  @media (pointer: coarse) { .clay-intro__close { width: 44px; height: 44px; top: 4px; right: 4px; } }
18
- @media print { .clay-intro { display: none; } }
19
24
  `;
@@ -1,5 +1,7 @@
1
- import { INTRO_CSS } from "./intro-style.js";
1
+ import { CLOSE_CSS, INTRO_CSS } from "./intro-style.js";
2
+ import { mountEditorUI } from "../../ui/editor-ui.js";
2
3
  import { announceBottomUi } from "./bottom-ui.js";
4
+ import { bootPhase } from "../../core/boot-phase.js";
3
5
 
4
6
  export const COPY = {
5
7
  close: "Hide this introduction",
@@ -45,7 +47,7 @@ export function mountIntro({ memory, template, appName, hadDraft }) {
45
47
  if (!template || !template.content.textContent.trim() && !template.content.querySelector("img, svg, video, canvas")) return null;
46
48
  if (!forced()) {
47
49
  if (memory.get().intro.done || hadDraft) return null;
48
- if (window.location.hash) return null;
50
+ if (bootPhase.servedHash) return null;
49
51
  if (window.matchMedia && window.matchMedia("print").matches) return null;
50
52
  }
51
53
  return mount({ memory, template, appName });
@@ -66,7 +68,8 @@ function mount({ memory, template, appName }) {
66
68
  close.className = "clay-intro__close";
67
69
  close.setAttribute("aria-label", COPY.close);
68
70
  close.innerHTML = '<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor" aria-hidden="true"><path d="M1.4 0 6 4.6 10.6 0 12 1.4 7.4 6 12 10.6 10.6 12 6 7.4 1.4 12 0 10.6 4.6 6 0 1.4z"/></svg>';
69
- section.appendChild(close);
71
+ close.addEventListener("click", () => dismiss("closed"));
72
+ mountEditorUI({ name: "intro-close", content: close, css: [CLOSE_CSS], parent: section });
70
73
 
71
74
  let gone = false;
72
75
  let frame = 0;
@@ -100,7 +103,7 @@ function mount({ memory, template, appName }) {
100
103
  }
101
104
 
102
105
  section.addEventListener("click", (event) => {
103
- if (event.target.closest(".clay-intro__close, [visitor-intro-dismiss]")) {
106
+ if (event.target.closest("[visitor-intro-dismiss]")) {
104
107
  dismiss("closed");
105
108
  return;
106
109
  }
@@ -1,5 +1,6 @@
1
1
  import { isDraftMode } from "../../core/is-edit-mode.js";
2
2
  import { BOTTOM_UI_CHANGED, announceBottomUi, bottomBarHeight, promptPanel } from "./bottom-ui.js";
3
+ import { mountEditorUI } from "../../ui/editor-ui.js";
3
4
 
4
5
  export const COPY = {
5
6
  draft: "You're seeing a private draft, like a visitor.",
@@ -77,18 +78,10 @@ const CHIP_CSS = `.clay-owner {
77
78
  @media print { .clay-owner { display: none; } }
78
79
  `;
79
80
 
80
- function stylesOnce() {
81
- if (document.getElementById("clay-owner-style")) return;
82
- const style = document.createElement("style");
83
- style.id = "clay-owner-style";
84
- style.setAttribute("clay", "editor-ui");
85
- style.textContent = CHIP_CSS;
86
- document.head.appendChild(style);
87
- }
81
+ export const ownerChip = () => document.querySelector('clay-editor-ui[data-clay-ui="owner"]')?.shadowRoot.getElementById("clay-owner") ?? null;
88
82
 
89
83
  export function mountOwnerChip() {
90
- if (document.getElementById("clay-owner")) return null;
91
- stylesOnce();
84
+ if (ownerChip()) return null;
92
85
 
93
86
  const draft = isDraftMode;
94
87
  const chip = document.createElement("div");
@@ -118,10 +111,10 @@ export function mountOwnerChip() {
118
111
  close.className = "clay-owner__x";
119
112
  close.setAttribute("aria-label", COPY.close);
120
113
  close.textContent = "\u00d7";
121
- close.addEventListener("click", () => { chip.remove(); liftOff(); release(); });
114
+ close.addEventListener("click", () => { ui.remove(); liftOff(); release(); });
122
115
 
123
116
  chip.append(lead, action, close);
124
- document.body.appendChild(chip);
117
+ const ui = mountEditorUI({ name: "owner", content: chip, css: [CHIP_CSS], attributes: { "sap-ignore": "" } });
125
118
 
126
119
  const vars = document.createElement("style");
127
120
  vars.id = "clay-owner-vars";
@@ -172,10 +165,13 @@ export function mountOwnerChip() {
172
165
  document.addEventListener("transitionend", lift, true);
173
166
  document.addEventListener("pointerup", lift, true);
174
167
  window.addEventListener("hashchange", lift);
168
+ // Transition events stop at a shadow root, so the chip's own changes are heard here.
169
+ chip.addEventListener("transitionend", lift);
175
170
  lift();
176
171
  // Whoever removes the chip takes its listeners back with it: a chip mounted and unmounted
177
172
  // once per mode session would otherwise leave six behind each time.
178
173
  chip.liftOff = liftOff;
174
+ chip.unmount = () => { liftOff(); ui.remove(); };
179
175
 
180
176
  return chip;
181
177
  }
@@ -1,7 +1,5 @@
1
1
  import { announceBottomUi, bottomBarHeight } from "./bottom-ui.js";
2
-
3
- const FONT_HREF = "https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@500&family=IBM+Plex+Sans:wght@400;500;600&family=Newsreader:opsz,wght@6..72,500&display=swap";
4
-
2
+ import { mountEditorUI } from "../../ui/editor-ui.js";
5
3
  const ASIDE = `<aside class="hcp" id="hcp" hidden aria-label="Download this app" data-hcp-state="tab" data-hcp-reason="open" sap-ignore clay="editor-ui">
6
4
  <section class="hcp-card" id="hcp-card" role="dialog" aria-modal="false" aria-labelledby="hcp-title">
7
5
  <button type="button" class="hcp-x" data-hcp="minimize" aria-label="Minimize"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" aria-hidden="true"><path d="M5 12h14"/></svg></button>
@@ -24,13 +22,15 @@ const ASIDE = `<aside class="hcp" id="hcp" hidden aria-label="Download this app"
24
22
  </aside>`;
25
23
 
26
24
  export async function mountPrompt({ memory, appName, link }) {
27
- if (!document.getElementById("hcp-style")) {
28
- const { PROMPT_CSS } = await import("./prompt-style.js");
29
- document.head.insertAdjacentHTML("beforeend", `<link rel="stylesheet" clay="editor-ui" href="${FONT_HREF}">\n<style clay="editor-ui" id="hcp-style">${PROMPT_CSS}</style>`);
25
+ const { PROMPT_CSS } = await import("./prompt-style.js");
26
+ const existing = document.querySelector('clay-editor-ui[data-clay-ui="download-prompt"]');
27
+ let root = existing?.shadowRoot.getElementById("hcp");
28
+ if (!root) {
29
+ const parsed = document.createElement("template");
30
+ parsed.innerHTML = ASIDE;
31
+ root = parsed.content.firstElementChild;
32
+ mountEditorUI({ name: "download-prompt", content: root, css: [PROMPT_CSS], attributes: { "sap-ignore": "" } });
30
33
  }
31
- if (!document.getElementById("hcp")) document.body.insertAdjacentHTML("beforeend", ASIDE);
32
-
33
- const root = document.getElementById("hcp");
34
34
  const ui = memory.get();
35
35
  if (!ui.prompt || typeof ui.prompt !== "object") ui.prompt = {};
36
36
  const mem = ui.prompt;
@@ -158,6 +158,8 @@ export async function mountPrompt({ memory, appName, link }) {
158
158
  addEventListener("scroll", lift, { passive: true });
159
159
  addEventListener("resize", lift);
160
160
  document.addEventListener("transitionend", lift, true);
161
+ // Transition events stop at a shadow root, so the prompt's own slides are heard here.
162
+ root.addEventListener("transitionend", lift);
161
163
 
162
164
  set(mem.shown || mem.dismissed ? "mini" : "tab");
163
165
  if (!mem.shown && (mem.uses || 0) >= USES_TO_SHOW) maybeShow("return");
@@ -1,3 +1,5 @@
1
+ import { draftPath } from "./store.js";
2
+
1
3
  const SRC = /^[a-z0-9][a-z0-9-]{0,39}$/;
2
4
 
3
5
  export function rememberSource(memory, search = window.location.search) {
@@ -9,8 +11,7 @@ export function rememberSource(memory, search = window.location.search) {
9
11
  }
10
12
 
11
13
  export function downloadUrl(src, loc = window.location) {
12
- let path = loc.pathname.replace(/\/+$/, "");
13
- if (path && !/\.(html|htmlclay)$/.test(path)) path += ".html";
14
- return `/_/download${path}?as=htmlclay${src ? `&src=${encodeURIComponent(src)}` : ""}`;
14
+ const path = draftPath(loc.pathname);
15
+ return `/_/download${path === "/" ? "" : path}?as=htmlclay${src ? `&src=${encodeURIComponent(src)}` : ""}`;
15
16
  }
16
17
 
@@ -4,6 +4,20 @@ import { draftSlot, setDraftStoreFailed } from "../../core/draft-mode.js";
4
4
  export const KEY_PREFIX = "clay:visitor-drafts:";
5
5
  export const RECORD_VERSION = 1;
6
6
 
7
+ // The document a pathname names, spelled the way the hosts resolve it: the path up to
8
+ // and including the first segment that ends in .html or .htmlclay (anything after it is
9
+ // a route inside that document), repeated slashes as one, the root index.html as "/",
10
+ // and "/" for a host that serves one document at every path.
11
+ export function draftPath(pathname = window.location.pathname) {
12
+ const clean = String(pathname || "/").replace(/\/{2,}/g, "/");
13
+ const file = clean.match(/^(.*?\/[^/]+\.(?:html|htmlclay))(?:\/|$)/i);
14
+ const path = file ? file[1] : "/";
15
+ return path.toLowerCase() === "/index.html" ? "/" : path;
16
+ }
17
+
18
+ export const legacyKey = (slot) => KEY_PREFIX + slot;
19
+ export const draftKey = (slot, path = draftPath()) => `${KEY_PREFIX}${slot}@${path}`;
20
+
7
21
  export function safeStorage() {
8
22
  try { return window.localStorage; } catch { return null; }
9
23
  }
@@ -66,26 +80,88 @@ function complete(draft, page, top = false) {
66
80
  return draft;
67
81
  }
68
82
 
69
- // Three-way merge of this tab's value with another tab's, from the value both
70
- // started at. Top-level keys and nested objects merge per field. A list both tabs
71
- // changed is a conflict, since a card moved between lists or a reorder cannot be
72
- // told apart from a delete and an add: this tab's value wins.
73
- export function merge3(base, mine, theirs) {
83
+ const sameIds = (a, b) => a.length === b.length && a.every((id, i) => id === b[i]);
84
+
85
+ // Rows matched by id, when neither tab reordered the rows all three still have: a row
86
+ // one tab added is kept where that tab put it, a row one tab removed goes unless the
87
+ // other tab changed it, and a row both kept merges field by field. Anything else is
88
+ // null, and the caller treats the list as one value.
89
+ function mergeRows(base, mine, theirs) {
90
+ const index = (rows) => new Map(rows.map((row) => [row.id, row]));
91
+ const was = index(base);
92
+ const mineById = index(mine);
93
+ const theirsById = index(theirs);
94
+ const shared = (rows) => rows.filter((row) => was.has(row.id) && mineById.has(row.id) && theirsById.has(row.id)).map((row) => row.id);
95
+ const order = shared(base);
96
+ if (!sameIds(order, shared(mine)) || !sameIds(order, shared(theirs))) return null;
97
+ for (const [id, row] of was) {
98
+ if (mineById.has(id) && !theirsById.has(id) && !same(mineById.get(id), row)) return null;
99
+ if (!mineById.has(id) && theirsById.has(id) && !same(theirsById.get(id), row)) return null;
100
+ }
101
+ let conflict = false;
102
+ const out = [];
103
+ for (const row of mine) {
104
+ if (was.has(row.id) && !theirsById.has(row.id)) continue;
105
+ if (!theirsById.has(row.id)) { out.push(row); continue; }
106
+ const part = mergeValue(was.get(row.id), row, theirsById.get(row.id), true);
107
+ conflict ||= part.conflict;
108
+ out.push(part.value);
109
+ }
110
+ theirs.forEach((row, i) => {
111
+ if (was.has(row.id) || mineById.has(row.id)) return;
112
+ let at = 0;
113
+ for (let j = i - 1; j >= 0; j--) {
114
+ const k = out.findIndex((kept) => kept.id === theirs[j].id);
115
+ if (k !== -1) { at = k + 1; break; }
116
+ }
117
+ out.splice(at, 0, row);
118
+ });
119
+ return { value: out, conflict };
120
+ }
121
+
122
+ function mergeValue(base, mine, theirs, rows) {
74
123
  if (same(mine, theirs) || same(theirs, base)) return { value: mine, conflict: false };
75
124
  if (same(mine, base)) return { value: theirs, conflict: false };
76
125
  if (isPlain(mine) && isPlain(theirs)) {
77
126
  const value = {};
78
127
  let conflict = false;
79
128
  for (const name of new Set([...Object.keys(mine), ...Object.keys(theirs)])) {
80
- const part = merge3(isPlain(base) ? base[name] : undefined, mine[name], theirs[name]);
129
+ const part = mergeValue(isPlain(base) ? base[name] : undefined, mine[name], theirs[name], rows);
81
130
  conflict ||= part.conflict;
82
131
  if (part.value !== undefined) value[name] = part.value;
83
132
  }
84
133
  return { value, conflict };
85
134
  }
135
+ if (rows && hasIds(base) && hasIds(mine) && hasIds(theirs)) {
136
+ const merged = mergeRows(base, mine, theirs);
137
+ if (merged) return merged;
138
+ }
86
139
  return { value: mine, conflict: true };
87
140
  }
88
141
 
142
+ function countIds(value, counts = new Map()) {
143
+ if (Array.isArray(value)) {
144
+ if (hasIds(value)) for (const row of value) counts.set(row.id, (counts.get(row.id) || 0) + 1);
145
+ for (const item of value) countIds(item, counts);
146
+ } else if (isPlain(value)) {
147
+ for (const item of Object.values(value)) countIds(item, counts);
148
+ }
149
+ return counts;
150
+ }
151
+
152
+ // Three-way merge of this tab's value with another tab's, from the value both started
153
+ // at. Objects merge per field and lists of rows with ids merge per row. A list merge that
154
+ // would hold one id in two places (a card moved to different lists in each tab) is
155
+ // undone, and that list is a conflict this tab wins, as is any reorder.
156
+ export function merge3(base, mine, theirs) {
157
+ const merged = mergeValue(base, mine, theirs, true);
158
+ const before = countIds(mine);
159
+ for (const [id, count] of countIds(merged.value)) {
160
+ if (count > 1 && count > (before.get(id) || 0)) return mergeValue(base, mine, theirs, false);
161
+ }
162
+ return merged;
163
+ }
164
+
89
165
  function storageFailure(doc, error) {
90
166
  setDraftStoreFailed(true);
91
167
  doc.dispatchEvent(new CustomEvent("clay:drafts-store-failed", { detail: { error } }));
@@ -94,9 +170,58 @@ function storageFailure(doc, error) {
94
170
  return failure;
95
171
  }
96
172
 
97
- export function createStore({ doc = document, storage = safeStorage(), now = () => new Date() } = {}) {
173
+ const savedAtOf = (raw) => {
174
+ try { return String(JSON.parse(raw)?.savedAt ?? ""); } catch { return ""; }
175
+ };
176
+
177
+ // A draft saved before drafts were scoped to the document's path lives under the slot
178
+ // alone. It is copied into this path's key with its UI memory, and never written or
179
+ // removed: a tab still on an older ClayJS may write it again, and another document on
180
+ // this origin with the same slot may still need it. The marker keeps the old record's
181
+ // savedAt, so a later write by such a tab is caught up on the next load, and a reset
182
+ // never brings the old draft back.
183
+ export function adoptLegacy(slot, { path = draftPath(), storage = safeStorage() } = {}) {
184
+ const from = legacyKey(slot);
185
+ const to = draftKey(slot, path);
186
+ const marker = to + ":legacy";
187
+ try {
188
+ const generation = storage?.getItem(to);
189
+ const old = storage?.getItem(from);
190
+ if (old == null) {
191
+ // A visitor who only dismissed the intro has UI memory and no draft; it is copied once too.
192
+ const ui = storage?.getItem(from + ":ui");
193
+ if (ui != null && storage.getItem(to + ":ui") == null && storage.getItem(to + ":ui-legacy") == null) {
194
+ storage.setItem(to + ":ui", ui);
195
+ storage.setItem(to + ":ui-legacy", "1");
196
+ }
197
+ return "none";
198
+ }
199
+ const stamp = savedAtOf(old);
200
+ const seen = storage.getItem(marker);
201
+ if (seen === stamp) return "none";
202
+ // A visitor who wrote to this document while the copy ran keeps what they typed.
203
+ const current = storage.getItem(to);
204
+ if (current !== generation) return "none";
205
+ if (current == null || (seen != null && savedAtOf(current) === seen)) {
206
+ storage.setItem(to, old);
207
+ } else if (current !== old) {
208
+ storage.setItem(to + ":conflict", old);
209
+ console.warn("visitor-drafts: an older tab changed this draft after it moved; its copy was kept under", to + ":conflict");
210
+ }
211
+ if (seen == null) {
212
+ const ui = storage.getItem(from + ":ui");
213
+ if (ui != null && storage.getItem(to + ":ui") == null) storage.setItem(to + ":ui", ui);
214
+ }
215
+ storage.setItem(marker, stamp);
216
+ return "copied";
217
+ } catch {
218
+ return "failed";
219
+ }
220
+ }
221
+
222
+ export function createStore({ doc = document, storage = safeStorage(), now = () => new Date(), path = draftPath(), fallback = null } = {}) {
98
223
  const slot = draftSlot(doc);
99
- const key = KEY_PREFIX + slot;
224
+ const key = draftKey(slot, path);
100
225
  const rules = engine.findRulesIn(doc, "api").rules;
101
226
 
102
227
  let lastRaw = null;
@@ -105,7 +230,11 @@ export function createStore({ doc = document, storage = safeStorage(), now = ()
105
230
  let stopped = false;
106
231
 
107
232
  function getRaw() {
108
- try { return storage ? storage.getItem(key) : null; } catch { return null; }
233
+ try {
234
+ if (!storage) return null;
235
+ const raw = storage.getItem(key);
236
+ return raw == null && fallback ? storage.getItem(fallback) : raw;
237
+ } catch { return null; }
109
238
  }
110
239
 
111
240
  function parse(raw) {
@@ -1,5 +1,5 @@
1
- export function createUiMemory(slot, storage) {
2
- const key = `clay:visitor-drafts:${slot}:ui`;
1
+ export function createUiMemory(draftKey, storage) {
2
+ const key = `${draftKey}:ui`;
3
3
  const blank = () => ({ v: 1, prompt: {}, intro: { done: null }, src: null });
4
4
  let mem = blank();
5
5
  try {