@panphora/clayjs 1.23.0 → 1.24.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 (471) hide show
  1. package/dist/all.js +1 -1
  2. package/dist/blockeditor/themes/light-1.css +431 -0
  3. package/dist/clay-dom.js +1 -1
  4. package/dist/clay-events.js +1 -1
  5. package/dist/clay-internals.js +1 -1
  6. package/dist/clay-options.js +1 -1
  7. package/dist/clay-router.js +1 -1
  8. package/dist/clay-ui.js +1 -1
  9. package/dist/clay-utils.js +1 -1
  10. package/dist/clay.js +1 -1
  11. package/dist/clay.standalone.js +30285 -26613
  12. package/dist/m/c-277QP5ET.js +1 -0
  13. package/dist/m/c-2BLVA6IN.js +5 -0
  14. package/dist/m/c-2OJMOUPC.js +1 -0
  15. package/dist/m/c-2PVGJFI5.js +1 -0
  16. package/dist/m/{c-AYVGD6A6.js → c-3UW5EJ77.js} +1 -1
  17. package/dist/m/{c-T5NK2TVY.js → c-42ZNV7LI.js} +1 -1
  18. package/dist/m/{c-3TLG3OLG.js → c-46LJAZZJ.js} +1 -1
  19. package/dist/m/c-47IBJGPQ.js +1 -0
  20. package/dist/m/c-5FXEKU4Y.js +1 -0
  21. package/dist/m/c-5KXDKC5G.js +1 -0
  22. package/dist/m/{c-EUGJVHDD.js → c-5RZNFTWD.js} +1 -1
  23. package/dist/m/c-65LJU2H2.js +72 -0
  24. package/dist/m/{c-NQJRVJ56.js → c-6IJEVE6B.js} +1 -1
  25. package/dist/m/{c-NW54NHKE.js → c-6LEYVQJK.js} +1 -1
  26. package/dist/m/{c-GSRLXYWL.js → c-6Q6RGFBF.js} +1 -1
  27. package/dist/m/c-6RGEQNVV.js +1 -0
  28. package/dist/m/c-7CM4GMTS.js +6 -0
  29. package/dist/m/c-7NCWENHV.js +1 -0
  30. package/dist/m/c-7V6MGMX2.js +1 -0
  31. package/dist/m/{c-LKSL4AUJ.js → c-7WZSUTYP.js} +1 -1
  32. package/dist/m/c-A4L7APIU.js +1 -0
  33. package/dist/m/c-APGHGDZM.js +1 -0
  34. package/dist/m/c-AT7GLQGV.js +34 -0
  35. package/dist/m/{c-ZRXJD4G6.js → c-B7GOFGQR.js} +1 -1
  36. package/dist/m/{c-KDXIJEEF.js → c-BGN7RCTJ.js} +1 -1
  37. package/dist/m/c-BKM46LUJ.js +1 -0
  38. package/dist/m/c-BOMQIQ3V.js +1 -0
  39. package/dist/m/{c-62HZW5DH.js → c-C5AKJVRH.js} +1 -1
  40. package/dist/m/{c-BXYSDKAN.js → c-CJ7QYEWI.js} +1 -1
  41. package/dist/m/c-CTGRDT6E.js +1 -0
  42. package/dist/m/c-CV4T65JE.js +6 -0
  43. package/dist/m/c-CVSWLX3X.js +1 -0
  44. package/dist/m/c-CZHTXGJS.js +1 -0
  45. package/dist/m/c-D7NT5S4F.js +1 -0
  46. package/dist/m/c-DD26VXAP.js +1 -0
  47. package/dist/m/{c-ANNFZFLN.js → c-DDS23EY6.js} +1 -1
  48. package/dist/m/{c-ALDVFPWL.js → c-DFKBGSN2.js} +1 -1
  49. package/dist/m/c-DIKR7H57.js +147 -0
  50. package/dist/m/c-DXPOFLEJ.js +1 -0
  51. package/dist/m/c-EDE45NM7.js +1 -0
  52. package/dist/m/c-EILLN2H2.js +1 -0
  53. package/dist/m/c-EODTTNVA.js +1 -0
  54. package/dist/m/{c-G6COVIZB.js → c-ESK4S67I.js} +1 -1
  55. package/dist/m/c-FDWR3FL7.js +1 -0
  56. package/dist/m/{c-WQZTI5P2.js → c-FEC6LGFF.js} +1 -1
  57. package/dist/m/c-FLEIU6CG.js +1 -0
  58. package/dist/m/c-FQAQRXGK.js +1 -0
  59. package/dist/m/{c-W4JCVLAB.js → c-FSQXFG5T.js} +1 -1
  60. package/dist/m/c-FVPNX2GO.js +135 -0
  61. package/dist/m/c-FYDKYW6E.js +1 -0
  62. package/dist/m/c-GNI7WHKF.js +32 -0
  63. package/dist/m/c-GO635CRU.js +3 -0
  64. package/dist/m/c-H3FNLBHN.js +2 -0
  65. package/dist/m/c-HDVH3MVC.js +72 -0
  66. package/dist/m/c-HIX5H5L7.js +1 -0
  67. package/dist/m/c-HKSLOVU4.js +1 -0
  68. package/dist/m/{c-SGRIYOAJ.js → c-HL6BLJNA.js} +1 -1
  69. package/dist/m/c-HTLYXCUH.js +1 -0
  70. package/dist/m/{c-QIR5CAKM.js → c-IUL7PU4P.js} +1 -1
  71. package/dist/m/c-IXO7EI2Q.js +1 -0
  72. package/dist/m/{c-W5E5P2TZ.js → c-JQF2EUCG.js} +1 -1
  73. package/dist/m/{c-NEL7HQW3.js → c-JWDCQL6J.js} +1 -1
  74. package/dist/m/c-KQKKIVLW.js +20 -0
  75. package/dist/m/c-LATNQ6Q5.js +1 -0
  76. package/dist/m/c-LD6UVV5R.js +1 -0
  77. package/dist/m/c-LS7IVQVW.js +1 -0
  78. package/dist/m/{c-JCOQVZYS.js → c-LSVN3OLG.js} +1 -1
  79. package/dist/m/{c-JDZDF2QM.js → c-LTSPHVAK.js} +1 -1
  80. package/dist/m/c-MFOSMAVM.js +3 -0
  81. package/dist/m/c-MJ7GVE46.js +1 -0
  82. package/dist/m/c-MP6S4AZU.js +6 -0
  83. package/dist/m/c-MQKSKQHG.js +2 -0
  84. package/dist/m/{c-UHUNXWKN.js → c-MQYEDF3Q.js} +1 -1
  85. package/dist/m/{c-IHUWUKIU.js → c-MR75YPRT.js} +1 -1
  86. package/dist/m/c-ND3Q2G2I.js +1 -0
  87. package/dist/m/c-NEQRFC4O.js +1 -0
  88. package/dist/m/{c-5FKRZNJV.js → c-NFQFV7OP.js} +1 -1
  89. package/dist/m/c-NHXOBD6A.js +1 -0
  90. package/dist/m/{c-QIKT6SKM.js → c-NICICT4P.js} +1 -1
  91. package/dist/m/c-NQAPWM7O.js +1 -0
  92. package/dist/m/{c-QUGCKMWF.js → c-NVU3DORS.js} +1 -1
  93. package/dist/m/{c-LWIDDWAR.js → c-OCYG4NBQ.js} +22 -22
  94. package/dist/m/c-OH2FPYSY.js +19 -0
  95. package/dist/m/c-OYHI67B6.js +1 -0
  96. package/dist/m/{c-R2XUODVY.js → c-OYW5MFPZ.js} +1 -1
  97. package/dist/m/{c-5B4YBZXC.js → c-P2HKMJAW.js} +1 -1
  98. package/dist/m/{c-TSBVMWAH.js → c-PCNJETUA.js} +1 -1
  99. package/dist/m/c-PDBNCJCT.js +1 -0
  100. package/dist/m/c-PGSBXHUE.js +1 -0
  101. package/dist/m/{c-5TIZ2I64.js → c-PYG2NGWU.js} +1 -1
  102. package/dist/m/c-Q26XHBLQ.js +1 -0
  103. package/dist/m/c-Q6BG3ZLO.js +1 -0
  104. package/dist/m/c-QENKN7QI.js +1 -0
  105. package/dist/m/c-QQFHEODI.js +1 -0
  106. package/dist/m/c-QTEUO2K6.js +1 -0
  107. package/dist/m/c-R23UHMXG.js +1 -0
  108. package/dist/m/c-RETTCOWO.js +10 -0
  109. package/dist/m/c-SE3SIY7Y.js +7 -0
  110. package/dist/m/{c-XX75FFNL.js → c-SGGGCIVD.js} +1 -1
  111. package/dist/m/c-SGH3UTF3.js +1 -0
  112. package/dist/m/c-SIM7Z3U7.js +1 -0
  113. package/dist/m/{c-X5LNHET5.js → c-SKMIR5HQ.js} +1 -1
  114. package/dist/m/{c-R27DWQKX.js → c-STRI73GQ.js} +1 -1
  115. package/dist/m/c-SUXHUL7U.js +1 -0
  116. package/dist/m/c-SXGEWPFC.js +10 -0
  117. package/dist/m/c-SXNSSHPP.js +1 -0
  118. package/dist/m/c-T3P3AXEW.js +1 -0
  119. package/dist/m/{c-PXLTUYZC.js → c-TFLRT5JX.js} +1 -1
  120. package/dist/m/{c-YCLU4UNT.js → c-TGIMBAWE.js} +1 -1
  121. package/dist/m/c-TGSHHFO6.js +1 -0
  122. package/dist/m/c-U4NTUVDZ.js +1 -0
  123. package/dist/m/c-UOOIF2AL.js +1 -0
  124. package/dist/m/{c-6TYGD5AB.js → c-UOUUI265.js} +1 -1
  125. package/dist/m/c-V2U3F4QX.js +1 -0
  126. package/dist/m/c-V2V5FKXX.js +1 -0
  127. package/dist/m/c-VBXRXTVS.js +26 -0
  128. package/dist/m/c-VHSMPITY.js +1 -0
  129. package/dist/m/c-VPXCXBDT.js +1 -0
  130. package/dist/m/c-WCG6AKA7.js +1 -0
  131. package/dist/m/c-WNOLMHI2.js +1 -0
  132. package/dist/m/{c-CRPIDGXC.js → c-X6MOYATI.js} +1 -1
  133. package/dist/m/{c-FQGHR32O.js → c-X75G43SZ.js} +1 -1
  134. package/dist/m/c-XB6EAIAR.js +13 -0
  135. package/dist/m/c-XMIDHEKF.js +1 -0
  136. package/dist/m/c-XUZMRQ7O.js +4 -0
  137. package/dist/m/{c-2Z22FCFK.js → c-XW5TIYYB.js} +1 -1
  138. package/dist/m/c-YDW4VCHP.js +1 -0
  139. package/dist/m/{c-QGAPG7OM.js → c-YLHTD3KX.js} +1 -1
  140. package/dist/m/c-YNIEIPBS.js +1 -0
  141. package/dist/m/c-Z4G2UK6T.js +4 -0
  142. package/dist/m/c-Z6MROEQ4.js +1 -0
  143. package/dist/m/c-ZFO5RW6G.js +2 -0
  144. package/dist/m/c-ZGCO4J3H.js +8 -0
  145. package/dist/m/c-ZKKAF4UT.js +3 -0
  146. package/dist/m/c-ZRGEN6QQ.js +4 -0
  147. package/dist/m/core/{admin-attrs-U7IJNYBG.js → admin-attrs-ONRWVXPK.js} +1 -1
  148. package/dist/m/core/{autosave-F5YQCKVL.js → autosave-TBKAAAFB.js} +1 -1
  149. package/dist/m/core/conflict-notice-FOEE6I7E.js +1 -0
  150. package/dist/m/core/draft-mode-4MCL3LYI.js +1 -0
  151. package/dist/m/core/{host-permission-6EVJGNPL.js → host-permission-X4BENZEU.js} +1 -1
  152. package/dist/m/core/{is-edit-mode-GCX6767E.js → is-edit-mode-FFHCQ5DR.js} +1 -1
  153. package/dist/m/core/{persist-CJXIL7D4.js → persist-6ILUR34S.js} +1 -1
  154. package/dist/m/core/{recovery-DRFZRJED.js → recovery-APWADU4K.js} +1 -1
  155. package/dist/m/core/save-6HT2LABE.js +1 -0
  156. package/dist/m/core/save-core-EVMNZQ4V.js +1 -0
  157. package/dist/m/core/site-edits-G4BOZMV4.js +1 -0
  158. package/dist/m/core/{unsaved-warning-HNQVTA2D.js → unsaved-warning-ITZDIZ2I.js} +1 -1
  159. package/dist/m/internals/{index-NRPNDTSA.js → index-HS3CQDFW.js} +1 -1
  160. package/dist/m/loader-GLLVDUZA.js +1 -0
  161. package/dist/m/manifest-50FF1C45.js +1 -0
  162. package/dist/m/manifest-edit-BE1A621D.js +1 -0
  163. package/dist/m/manifest.json +2072 -1811
  164. package/dist/m/page/editor-KAZDJNND.js +307 -0
  165. package/dist/m/plugins/ai-edit-LABECCSN.js +6 -0
  166. package/dist/m/plugins/{cms-H7DTMUQX.js → cms-T2HLI7JG.js} +16 -16
  167. package/dist/m/plugins/{demo-42BSYQ7T.js → demo-T7B4RIKL.js} +1 -1
  168. package/dist/m/plugins/{derive-QVI3LI3A.js → derive-6LPJOCAV.js} +1 -1
  169. package/dist/m/plugins/indicator-J4ABFARX.js +1 -0
  170. package/dist/m/plugins/{live-DCUBKXY4.js → live-PGBIEMGB.js} +1 -1
  171. package/dist/m/plugins/menu-YZL3X2AJ.js +1 -0
  172. package/dist/m/plugins/page-WOCNX2IN.js +2 -0
  173. package/dist/m/plugins/page-view-SJODOOGE.js +1 -0
  174. package/dist/m/plugins/{people-5XRNRP5U.js → people-C4TCWFOQ.js} +1 -1
  175. package/dist/m/plugins/{quickcrop-WWF5I422.js → quickcrop-X5MRIBAP.js} +2 -2
  176. package/dist/m/plugins/richclay-Y2NV44NA.js +549 -0
  177. package/dist/m/plugins/sortable-XCHQG65Z.js +1 -0
  178. package/dist/m/plugins/{source-6BKMK4PO.js → source-YK7MO23W.js} +1 -1
  179. package/dist/m/plugins/{undo-3JEYK5QY.js → undo-VLRBQNPN.js} +1 -1
  180. package/dist/m/plugins/upgrade-NNTJBSK2.js +1 -0
  181. package/dist/m/plugins/{upload-TTCQRIM5.js → upload-ZKKJV76X.js} +1 -1
  182. package/dist/m/plugins/visitor-drafts-RTZNONHT.js +20 -0
  183. package/dist/m/plugins/{visitor-drafts-owner-Z5B2OQBR.js → visitor-drafts-owner-LHUHFU3L.js} +1 -1
  184. package/dist/m/plugins/{visitor-menu-4LQMKLPP.js → visitor-menu-FY4KM2SW.js} +1 -1
  185. package/dist/m/plugins/wire-M2NR354P.js +1 -0
  186. package/dist/m/{reader-core-POFQCKRV.js → reader-core-T2S3JC4E.js} +2 -2
  187. package/dist/m/sync/live-sync-Z64YREWQ.js +1 -0
  188. package/dist/m/ui/index-MGTWOW2C.js +1 -0
  189. package/dist/m/vendor/{sap-core.vendor-OA5SKL7Q.js → sap-core.vendor-QAEPMETX.js} +1 -1
  190. package/dist/offline-manifest.json +1250 -1082
  191. package/dist/server/core.cjs +132 -109
  192. package/dist/server/page-compaction-worker.cjs +5 -5
  193. package/dist/server/rooms.mjs +3 -1
  194. package/docs/guide.md +89 -44
  195. package/entries/clay-data.js +1 -1
  196. package/entries/sap.js +1 -1
  197. package/package.json +5 -3
  198. package/src/blockeditor/themes/content.css +70 -0
  199. package/src/blockeditor/themes/light-1.css +431 -0
  200. package/src/blockeditor/themes/light-1.source.css +270 -0
  201. package/src/blockeditor/themes/light-2.css +211 -0
  202. package/src/blockeditor/themes/light-3.css +130 -0
  203. package/src/blockeditor/themes/light-4.css +299 -0
  204. package/src/core/conflict-notice.js +28 -0
  205. package/src/core/draft-mode.js +6 -0
  206. package/src/core/host-meta.js +6 -6
  207. package/src/core/is-edit-mode.js +9 -3
  208. package/src/core/mode-support.js +3 -0
  209. package/src/core/mode-switch.js +4 -4
  210. package/src/core/mode-transition.js +3 -1
  211. package/src/core/page-format-wire.js +1 -1
  212. package/src/core/page-format.js +8 -8
  213. package/src/core/save-accepted.js +6 -7
  214. package/src/core/save-core.js +52 -23
  215. package/src/core/save-failure-notice.js +37 -0
  216. package/src/core/save-feedback.js +6 -0
  217. package/src/core/save-replace.js +1 -1
  218. package/src/core/save-size.js +2 -2
  219. package/src/core/save.js +78 -15
  220. package/src/core/site-edits.js +166 -0
  221. package/src/core/unsaved-warning.js +2 -2
  222. package/src/core/upgrade-run.js +4 -4
  223. package/src/lib/ai-comment.js +23 -0
  224. package/src/lib/effect.js +8 -0
  225. package/src/lib/idb-error-code.js +20 -0
  226. package/src/live/fast-lane.js +10 -10
  227. package/src/live/local-lane.js +10 -10
  228. package/src/live/page-compaction.js +8 -8
  229. package/src/live/server-lane.js +1 -1
  230. package/src/loader-logic.js +48 -18
  231. package/src/loader.js +11 -6
  232. package/src/page/adopt-boot.js +1 -1
  233. package/src/page/adopt.js +2 -2
  234. package/src/page/ai-area.js +182 -0
  235. package/src/page/ai-preview.js +80 -0
  236. package/src/page/ai-surface.js +257 -0
  237. package/src/page/ai-tool.js +308 -145
  238. package/src/page/announce.js +2 -2
  239. package/src/page/awareness.js +18 -18
  240. package/src/page/blocks.js +35 -34
  241. package/src/page/blocksel.js +2 -2
  242. package/src/page/board-drag.js +11 -11
  243. package/src/page/board-face.js +1 -1
  244. package/src/page/board-panel.js +26 -26
  245. package/src/page/board-style.js +127 -127
  246. package/src/page/board-view.js +48 -48
  247. package/src/page/chart.js +29 -29
  248. package/src/page/checkpoint-proof.js +19 -19
  249. package/src/page/chrome.js +74 -65
  250. package/src/page/clipboard-shape.js +1 -1
  251. package/src/page/csv-import.js +4 -4
  252. package/src/page/drag.js +5 -5
  253. package/src/page/editor.js +301 -296
  254. package/src/page/export-html.js +8 -8
  255. package/src/page/features/checklist.js +36 -36
  256. package/src/page/features/export-html.js +19 -19
  257. package/src/page/features/export-markdown.js +10 -10
  258. package/src/page/features/import-markdown.js +13 -13
  259. package/src/page/features/media.js +50 -50
  260. package/src/page/features/table.js +17 -17
  261. package/src/page/features/title-cover.js +17 -17
  262. package/src/page/features/title-icon.js +7 -7
  263. package/src/page/features/toc.js +1 -1
  264. package/src/page/features/tool.js +42 -18
  265. package/src/page/format-command.js +4 -4
  266. package/src/page/format-gate.js +6 -6
  267. package/src/page/format-host.js +6 -6
  268. package/src/page/format-level.js +3 -3
  269. package/src/page/format-native-commands.js +2 -2
  270. package/src/page/highlight-slot.js +1 -1
  271. package/src/page/highlight.js +4 -4
  272. package/src/page/history-intent.js +2 -2
  273. package/src/page/image-actions.js +4 -4
  274. package/src/page/link.js +10 -10
  275. package/src/page/media.js +3 -3
  276. package/src/page/mention-picker.js +7 -7
  277. package/src/page/merge.js +1 -1
  278. package/src/page/placeholder.js +3 -3
  279. package/src/page/projection.js +11 -11
  280. package/src/page/reader-patch.js +2 -2
  281. package/src/page/recover.js +26 -26
  282. package/src/page/recovery-ui.js +28 -28
  283. package/src/page/rich/chart.js +1 -1
  284. package/src/page/rich/page-tool.js +21 -6
  285. package/src/page/rich/tool-runtime.js +3 -2
  286. package/src/page/save-projection.js +2 -2
  287. package/src/page/save-states.js +9 -9
  288. package/src/page/schema.js +9 -9
  289. package/src/page/table-style.js +21 -21
  290. package/src/page/toc-link.js +1 -1
  291. package/src/page/tool-capability.js +8 -3
  292. package/src/page/tool-transport.js +52 -0
  293. package/src/page/tool-views.js +4 -0
  294. package/src/page/tools.js +6 -1
  295. package/src/page/view.js +6 -6
  296. package/src/page/ybind/binding.js +1 -1
  297. package/src/page/ybind/conversion.js +1 -1
  298. package/src/page/ybind/layout.js +4 -4
  299. package/src/page/ybind/migrate.js +1 -1
  300. package/src/page/ybind/origins.js +2 -2
  301. package/src/page/ybind/provider.js +2 -2
  302. package/src/page/ybind/recovery-proof.js +3 -3
  303. package/src/page/ybind/recovery-replay.js +2 -2
  304. package/src/plugins/ai-edit/picker.js +235 -0
  305. package/src/plugins/ai-edit.js +220 -34
  306. package/src/plugins/indicator-chip.js +39 -34
  307. package/src/plugins/indicator.js +13 -4
  308. package/src/plugins/live.js +21 -13
  309. package/src/plugins/menu/actions.js +89 -17
  310. package/src/plugins/menu/seo.js +32 -6
  311. package/src/plugins/menu/shell-style.js +26 -7
  312. package/src/plugins/menu/shell.js +12 -7
  313. package/src/plugins/menu/variants.js +6 -10
  314. package/src/plugins/menu/visitor.js +44 -34
  315. package/src/plugins/menu.js +3 -2
  316. package/src/plugins/page-view.js +2 -2
  317. package/src/plugins/page.js +257 -98
  318. package/src/plugins/sortable.js +7 -0
  319. package/src/plugins/upgrade.js +2 -1
  320. package/src/plugins/visitor-drafts/commit.js +303 -0
  321. package/src/plugins/visitor-drafts/idb.js +221 -0
  322. package/src/plugins/visitor-drafts/intro-style.js +1 -1
  323. package/src/plugins/visitor-drafts/intro.js +8 -25
  324. package/src/plugins/visitor-drafts/prompt.js +30 -4
  325. package/src/plugins/visitor-drafts/session.js +345 -0
  326. package/src/plugins/visitor-drafts/store.js +351 -32
  327. package/src/plugins/visitor-drafts.js +158 -27
  328. package/src/server/checkpoint/index.js +8 -8
  329. package/src/server/rooms/index.js +2 -2
  330. package/src/server/rooms/live-data-rooms.js +35 -35
  331. package/src/server/rooms/live-page-history.js +2 -2
  332. package/src/server/rooms/page-admission.js +34 -6
  333. package/src/server/rooms/page-conversion.js +1 -1
  334. package/src/server/rooms/page-lineage.js +4 -4
  335. package/src/server/rooms/page-rebase.js +4 -4
  336. package/src/server/rooms/page-room-conformance.js +14 -14
  337. package/src/server/rooms/page-state.js +2 -2
  338. package/src/server/rooms/page-write-admission.js +7 -7
  339. package/src/server/rooms/page-write-guard.js +3 -3
  340. package/src/sync/data-guard-notice.js +265 -0
  341. package/src/sync/live-sync.js +54 -11
  342. package/src/sync/page-relay.js +4 -4
  343. package/src/sync/presence-store.js +1 -1
  344. package/src/sync/presence-target.js +3 -3
  345. package/src/sync/presence.js +1 -1
  346. package/src/sync/stream.js +54 -13
  347. package/src/sync/wire-profile.js +8 -3
  348. package/src/ui/bevel.js +1 -1
  349. package/src/ui/index.js +9 -10
  350. package/src/ui/skins/cms.js +1 -1
  351. package/src/ui/skins/quickcrop.js +1 -1
  352. package/src/ui/skins/richclay.js +2 -2
  353. package/src/ui/toast-lazy.js +12 -3
  354. package/src/ui/toast.js +9 -3
  355. package/src/vendor/hyper-html-api-data.vendor.js +1 -1
  356. package/src/vendor/hyper-html-api-upgrade.vendor.js +2 -2
  357. package/src/vendor/hypercms.vendor.js +16 -16
  358. package/src/vendor/richclay.vendor.js +48 -20
  359. package/src/vendor/sap-core.vendor.js +1 -1
  360. package/tool-generation/context.md +44 -0
  361. package/tool-generation/system.md +23 -0
  362. package/dist/m/c-24PMUJIV.js +0 -2
  363. package/dist/m/c-2AYVYEI4.js +0 -1
  364. package/dist/m/c-2AZDZWCK.js +0 -1
  365. package/dist/m/c-2L6O5PDS.js +0 -6
  366. package/dist/m/c-2RYOGUQM.js +0 -2
  367. package/dist/m/c-2ZVOEU4M.js +0 -1
  368. package/dist/m/c-37NAONZA.js +0 -1
  369. package/dist/m/c-3GJ4SDE2.js +0 -1
  370. package/dist/m/c-3O7OBDCM.js +0 -1
  371. package/dist/m/c-5OJ7YF2N.js +0 -1
  372. package/dist/m/c-5WMXEKFH.js +0 -135
  373. package/dist/m/c-6RDOKLN7.js +0 -1
  374. package/dist/m/c-A4IP6MBC.js +0 -1
  375. package/dist/m/c-A6L3FMUS.js +0 -1
  376. package/dist/m/c-ATKAOH23.js +0 -1
  377. package/dist/m/c-B6O7AJYG.js +0 -32
  378. package/dist/m/c-BCGWJYXW.js +0 -1
  379. package/dist/m/c-BGKAT2FL.js +0 -7
  380. package/dist/m/c-BYTKL6UX.js +0 -1
  381. package/dist/m/c-DHBRA6DL.js +0 -1
  382. package/dist/m/c-DTMZ3ZHI.js +0 -1
  383. package/dist/m/c-E3ZXARWA.js +0 -1
  384. package/dist/m/c-EK63G3CG.js +0 -1
  385. package/dist/m/c-ELYIIYRS.js +0 -1
  386. package/dist/m/c-EXKHPOVC.js +0 -3
  387. package/dist/m/c-FCSZ3DC5.js +0 -20
  388. package/dist/m/c-FTWGYI5E.js +0 -1
  389. package/dist/m/c-G4PWUDD5.js +0 -1
  390. package/dist/m/c-GBKB5WDL.js +0 -2
  391. package/dist/m/c-GBTVCJXC.js +0 -1
  392. package/dist/m/c-GC2HICFJ.js +0 -1
  393. package/dist/m/c-GRWMD6O5.js +0 -1
  394. package/dist/m/c-HCNWJE4A.js +0 -4
  395. package/dist/m/c-HG2C77BM.js +0 -4
  396. package/dist/m/c-JGHEACGK.js +0 -1
  397. package/dist/m/c-JGLX6TBK.js +0 -1
  398. package/dist/m/c-JNRKHYWO.js +0 -1
  399. package/dist/m/c-KBS3UV6B.js +0 -1
  400. package/dist/m/c-KQ7E3PYY.js +0 -1
  401. package/dist/m/c-L3F5ZLIV.js +0 -1
  402. package/dist/m/c-L4KMT7FO.js +0 -6
  403. package/dist/m/c-L5VFATT6.js +0 -1
  404. package/dist/m/c-LCYPMULG.js +0 -1
  405. package/dist/m/c-LIGQYZ7M.js +0 -6
  406. package/dist/m/c-LKIWEPAV.js +0 -1
  407. package/dist/m/c-MPSKIU2I.js +0 -1
  408. package/dist/m/c-N7JPUPGC.js +0 -1
  409. package/dist/m/c-NK33MMYF.js +0 -1
  410. package/dist/m/c-NQLWOHLH.js +0 -10
  411. package/dist/m/c-NTHUTABN.js +0 -1
  412. package/dist/m/c-NWX3MFT4.js +0 -1
  413. package/dist/m/c-OKXI52N7.js +0 -1
  414. package/dist/m/c-OPONJ2UK.js +0 -128
  415. package/dist/m/c-OWQWOGYI.js +0 -1
  416. package/dist/m/c-P6Q2ZTFQ.js +0 -1
  417. package/dist/m/c-QEN6HZCA.js +0 -1
  418. package/dist/m/c-QIXT3Q7C.js +0 -28
  419. package/dist/m/c-QM5LXRDT.js +0 -9
  420. package/dist/m/c-QYYUVLVG.js +0 -1
  421. package/dist/m/c-RQRMPZXD.js +0 -1
  422. package/dist/m/c-SBCBXKOZ.js +0 -26
  423. package/dist/m/c-SKF5HL4I.js +0 -4
  424. package/dist/m/c-SRPC3VBE.js +0 -1
  425. package/dist/m/c-TANBH7FI.js +0 -1
  426. package/dist/m/c-THGQQDYY.js +0 -1
  427. package/dist/m/c-UD5JZTDL.js +0 -1
  428. package/dist/m/c-UDGEFWNU.js +0 -1
  429. package/dist/m/c-UGNV5MFB.js +0 -1
  430. package/dist/m/c-VIRFF5HG.js +0 -8
  431. package/dist/m/c-VJYEVRYK.js +0 -1
  432. package/dist/m/c-VQCEOGKH.js +0 -13
  433. package/dist/m/c-VYJ7O4RI.js +0 -34
  434. package/dist/m/c-W7IPVCAA.js +0 -1
  435. package/dist/m/c-WBGWGOM7.js +0 -1
  436. package/dist/m/c-WJ5DRBHT.js +0 -72
  437. package/dist/m/c-X5WC4PX5.js +0 -1
  438. package/dist/m/c-XCFQEWW6.js +0 -3
  439. package/dist/m/c-XGH4GS2T.js +0 -1
  440. package/dist/m/c-XLQ3BDMM.js +0 -1
  441. package/dist/m/c-ZQXNHZZT.js +0 -72
  442. package/dist/m/core/conflict-notice-42H4ASLA.js +0 -1
  443. package/dist/m/core/draft-mode-WSQAT67S.js +0 -1
  444. package/dist/m/core/save-ESPSQPLV.js +0 -1
  445. package/dist/m/core/save-core-6SQHOR6W.js +0 -1
  446. package/dist/m/loader-FBDE2UAB.js +0 -1
  447. package/dist/m/manifest-1D2F5CA5.js +0 -1
  448. package/dist/m/manifest-edit-F7CC5CFD.js +0 -1
  449. package/dist/m/page/editor-OSKOW6RS.js +0 -304
  450. package/dist/m/plugins/ai-edit-VV4QTINH.js +0 -6
  451. package/dist/m/plugins/indicator-WVPGMOZ7.js +0 -1
  452. package/dist/m/plugins/menu-Q4MFFQ6E.js +0 -1
  453. package/dist/m/plugins/page-OAWUJQEL.js +0 -2
  454. package/dist/m/plugins/page-view-DZ26ZDDQ.js +0 -1
  455. package/dist/m/plugins/richclay-PGOOJ4OK.js +0 -517
  456. package/dist/m/plugins/sortable-266HY6TR.js +0 -1
  457. package/dist/m/plugins/upgrade-BNZVFHMZ.js +0 -1
  458. package/dist/m/plugins/visitor-drafts-2AUYKAIJ.js +0 -20
  459. package/dist/m/plugins/wire-W2EV36KZ.js +0 -1
  460. package/dist/m/sync/live-sync-XGUPHI3I.js +0 -1
  461. package/dist/m/ui/index-VGYAKAKI.js +0 -1
  462. package/dist/page/themes/light-1.css +0 -431
  463. package/src/page/themes/content.css +0 -70
  464. package/src/page/themes/light-1.css +0 -431
  465. package/src/page/themes/light-1.source.css +0 -270
  466. package/src/page/themes/light-2.css +0 -211
  467. package/src/page/themes/light-3.css +0 -130
  468. package/src/page/themes/light-4.css +0 -299
  469. /package/dist/m/{c-FILLTJA7.js → c-QTTJYJMA.js} +0 -0
  470. /package/dist/m/plugins/{quickcrop-WWF5I422.js.LEGAL.txt → quickcrop-X5MRIBAP.js.LEGAL.txt} +0 -0
  471. /package/dist/m/plugins/{richclay-PGOOJ4OK.js.LEGAL.txt → richclay-Y2NV44NA.js.LEGAL.txt} +0 -0
@@ -4,6 +4,18 @@ 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
  const LINEAGE = 32;
7
+ // What one tab's journal slot in `localStorage` may hold, and what its unconfirmed
8
+ // operations may hold in memory, in characters of JSON.
9
+ export const SLOT_LIMIT = 512 * 1024;
10
+ export const PENDING_LIMIT = 16 * 1024 * 1024;
11
+
12
+ export const newRecordId = () => Math.random().toString(36).slice(2, 10) + Math.random().toString(36).slice(2, 6);
13
+
14
+ function textMark(text) {
15
+ let hash = 0x811c9dc5;
16
+ for (let i = 0; i < text.length; i++) hash = Math.imul(hash ^ text.charCodeAt(i), 0x01000193);
17
+ return (hash >>> 0).toString(36) + "." + text.length.toString(36);
18
+ }
7
19
 
8
20
  // The document a pathname names, spelled the way the hosts resolve it: the path up to
9
21
  // and including the first segment that ends in .html or .htmlclay (anything after it is
@@ -55,10 +67,39 @@ export function fitsInto(wanted, got) {
55
67
  return wanted === got;
56
68
  }
57
69
 
58
- function same(a, b) {
70
+ export function same(a, b) {
59
71
  return fitsInto(a, b) && fitsInto(b, a);
60
72
  }
61
73
 
74
+ const STORE_SEL = 'script[type="application/json"][merge][clay~="live"]';
75
+ const ARTICLE_SEL = "article[data-blockeditor]";
76
+ const isPagePart = (name) => name === "blockeditor" || /^blockeditor\.\d+$/.test(name);
77
+ const canonical = (live) => JSON.stringify(Object.keys(live).sort().map((name) => [name, live[name]]));
78
+
79
+ // A page document's page: every live store's value and the article the save projected
80
+ // from them. Null for a document that is not one page, or whose store does not parse.
81
+ function readUnit(source) {
82
+ const articles = source.querySelectorAll(ARTICLE_SEL);
83
+ if (articles.length !== 1) return null;
84
+ const live = {};
85
+ for (const tag of source.querySelectorAll(STORE_SEL)) {
86
+ try { live[tag.getAttribute("merge")] = JSON.parse(tag.textContent); } catch { return null; }
87
+ }
88
+ return { live, blockeditor: { rev: articles[0].getAttribute("data-rev") || "", html: articles[0].innerHTML } };
89
+ }
90
+
91
+ const sameUnit = (a, b) => (!a || !b ? a === b : a.blockeditor.rev === b.blockeditor.rev && canonical(a.live) === canonical(b.live));
92
+
93
+ // One page content, one mark: two pages `sameUnit` calls the same carry the same one.
94
+ export const unitMark = (unit) => (isPlain(unit?.live) && isPlain(unit?.blockeditor) ? `${unit.blockeditor.rev}:${textMark(canonical(unit.live))}` : null);
95
+
96
+ function unitOf(record) {
97
+ if (!isPlain(record?.live) || !isPlain(record?.blockeditor)) return null;
98
+ const { live, blockeditor } = record;
99
+ if (!Object.keys(live).length || typeof blockeditor.rev !== "string" || typeof blockeditor.html !== "string") return null;
100
+ return { live, blockeditor };
101
+ }
102
+
62
103
  const hasIds = (rows) => Array.isArray(rows)
63
104
  && rows.every((row) => isPlain(row) && (typeof row.id === "string" || typeof row.id === "number"))
64
105
  && new Set(rows.map((row) => row.id)).size === rows.length;
@@ -163,7 +204,7 @@ export function merge3(base, mine, theirs) {
163
204
  return merged;
164
205
  }
165
206
 
166
- function storageFailure(doc, error) {
207
+ export function storageFailure(doc, error) {
167
208
  setDraftStoreFailed(true);
168
209
  doc.dispatchEvent(new CustomEvent("clay:drafts-store-failed", { detail: { error } }));
169
210
  const failure = new Error("Couldn't save in this browser. Storage is full or turned off.");
@@ -220,21 +261,85 @@ export function adoptLegacy(slot, { path = draftPath(), storage = safeStorage()
220
261
  }
221
262
  }
222
263
 
223
- export function createStore({ doc = document, storage = safeStorage(), now = () => new Date(), path = draftPath(), fallback = null } = {}) {
264
+ // The `site` key holds values the server writes into every page of a multi-page site from its
265
+ // site.json. A draft must never capture or replay them, or a returning visitor would see stale ones.
266
+ function withoutSiteKey(value) {
267
+ if (!isPlain(value) || !("site" in value)) return value;
268
+ const { site, ...rest } = value;
269
+ return rest;
270
+ }
271
+
272
+ export function createStore({ doc = document, storage = safeStorage(), now = () => new Date(), path = draftPath(), fallback = null, slotLimit = SLOT_LIMIT, pendingLimit = PENDING_LIMIT } = {}) {
224
273
  const slot = draftSlot(doc);
225
274
  const key = draftKey(slot, path);
226
- const rules = engine.findRulesIn(doc, "api").rules;
275
+ const rules = withoutSiteKey(engine.findRulesIn(doc, "api").rules);
227
276
 
228
277
  let lastRaw = null;
229
278
  let base = null;
279
+ // The served page's data, read before any restore, and the stored data this tab's next
280
+ // operation is built on: the record it restored or last took in with its own operations
281
+ // laid over, or the served page's while nothing is stored.
282
+ let seed = null;
283
+ let tip = null;
284
+ // Nothing of a draft is on this page yet: no restore, no operation, nothing taken in.
285
+ let fresh = true;
230
286
  let unparked = null;
231
287
  let stopped = false;
232
288
  // This tab's own last write: its record id, its ancestry, and the data of the record it
233
289
  // was built on. A read in one tab can miss a write another tab made a few milliseconds
234
290
  // earlier, so a record whose ancestry lacks this write never saw it.
235
291
  let own = null;
292
+ // The page as the served file states it, read once before any restore: an untouched
293
+ // page is never recorded, so the owner's later page reaches a visitor who never wrote in it.
294
+ let served;
295
+ // The page this tab's editor holds as its starting point, and the page in the record
296
+ // this tab last read or wrote. Null when no page is recorded.
297
+ let editorUnit = null;
298
+ let seenUnit = null;
299
+
300
+ const readServed = () => { if (served === undefined) served = readUnit(doc); };
301
+ function recordable(unit) {
302
+ if (!unit) return null;
303
+ readServed();
304
+ if (!served) return unit;
305
+ const differs = unit.blockeditor.rev !== served.blockeditor.rev ||
306
+ Object.keys(unit.live).some((name) => !isPagePart(name) && JSON.stringify(unit.live[name]) !== JSON.stringify(served.live[name]));
307
+ // An untouched page is never recorded, so the owner's later page reaches a visitor who
308
+ // never wrote in it. Once this tab has held a recorded page, every page is recorded,
309
+ // even one returned to the served text, so a deletion reaches the other tabs too.
310
+ return differs || editorUnit || seenUnit ? unit : null;
311
+ }
312
+
313
+ // Runs once the article exists. The visitor-drafts module runs before the page and live
314
+ // plugins and registers first, so on a page still parsing this listener runs before theirs.
315
+ function whenArticle(fn) {
316
+ if (doc.querySelector(ARTICLE_SEL) || doc.readyState !== "loading") return fn();
317
+ doc.addEventListener("DOMContentLoaded", fn, { once: true });
318
+ }
319
+
320
+ // The stored values go into the store tags as JSON text with `<` escaped, so a value
321
+ // cannot close its tag. The article is left alone: the editor renders from the store.
322
+ function placeUnit(unit) {
323
+ readServed();
324
+ const article = doc.querySelector(ARTICLE_SEL);
325
+ if (!article) return false;
326
+ const tags = new Map([...doc.querySelectorAll(STORE_SEL)].map((tag) => [tag.getAttribute("merge"), tag]));
327
+ for (const [name, tag] of tags) if (isPagePart(name) && !(name in unit.live)) tag.remove();
328
+ for (const [name, value] of Object.entries(unit.live)) {
329
+ let tag = tags.get(name);
330
+ if (!tag) {
331
+ tag = doc.createElement("script");
332
+ tag.setAttribute("type", "application/json");
333
+ tag.setAttribute("merge", name);
334
+ tag.setAttribute("clay", "live");
335
+ article.parentNode.insertBefore(tag, article);
336
+ }
337
+ tag.textContent = JSON.stringify(value).replace(/</g, "\\u003c");
338
+ }
339
+ return true;
340
+ }
236
341
 
237
- const newId = () => Math.random().toString(36).slice(2, 10) + Math.random().toString(36).slice(2, 6);
342
+ const newId = newRecordId;
238
343
 
239
344
  // A record's ancestry is the ids it descends from, nearest first, ending at the empty
240
345
  // slot it grew from, or at a record from a runtime that kept no ids, named by its bytes.
@@ -245,11 +350,7 @@ export function createStore({ doc = document, storage = safeStorage(), now = ()
245
350
  try { return "root:" + (storage?.getItem(resetKey) ?? ""); } catch { return "root:"; }
246
351
  }
247
352
 
248
- function legacyId(raw) {
249
- let hash = 0x811c9dc5;
250
- for (let i = 0; i < raw.length; i++) hash = Math.imul(hash ^ raw.charCodeAt(i), 0x01000193);
251
- return "legacy:" + (hash >>> 0).toString(36) + "." + raw.length.toString(36);
252
- }
353
+ const legacyId = (raw) => "legacy:" + textMark(raw);
253
354
 
254
355
  function lineageOf(record, raw) {
255
356
  if (!record) return null;
@@ -373,30 +474,53 @@ export function createStore({ doc = document, storage = safeStorage(), now = ()
373
474
  return null;
374
475
  }
375
476
 
376
- function parkedResult(raw) {
477
+ function parkedResult({ raw, record }) {
377
478
  const parkedKey = park(raw);
378
479
  if (!parkedKey) lastRaw = raw;
379
480
  base = live();
481
+ tip = record?.data ?? base;
380
482
  return parkedKey
381
483
  ? { restored: false, hadDraft: true, parked: true }
382
484
  : { restored: false, hadDraft: true, parked: false, blocked: true };
383
485
  }
384
486
 
385
- function restore() {
386
- const found = read();
487
+ const seedData = () => (seed ??= live());
488
+
489
+ // `found` is the record to put on the page: the one `localStorage` holds, or the
490
+ // database's when the page waited for it (null: it holds none).
491
+ function restore(found = read()) {
492
+ seedData();
387
493
  if (!found) {
388
- base = live();
494
+ base = tip = seed;
495
+ whenArticle(readServed);
389
496
  return { restored: false, hadDraft: false };
390
497
  }
391
- if (!found.record) return parkedResult(found.raw);
392
- const fitted = fit(found.record.data);
393
- if (!fitted || !Object.keys(fitted.data).length) return parkedResult(found.raw);
394
- applyData(doc, fitted.data, rules);
498
+ fresh = false;
499
+ if (!found.record) return parkedResult(found);
500
+ const unit = unitOf(found.record);
501
+ const hasData = Object.keys(found.record.data).length > 0;
502
+ const fitted = hasData ? fit(found.record.data) : { data: {}, dropped: [] };
503
+ const dataFits = Boolean(fitted) && (!hasData || Object.keys(fitted.data).length > 0);
504
+ // The page does not depend on the rules: a page is placed even when no saved field fits,
505
+ // and the whole record is kept aside as well.
506
+ if (!unit && !dataFits) return parkedResult(found);
507
+ if (!unit && !hasData) return parkedResult(found);
508
+ if (hasData && dataFits) applyData(doc, fitted.data, rules);
509
+ if (unit) {
510
+ whenArticle(() => {
511
+ if (placeUnit(unit)) return;
512
+ console.warn("visitor-drafts: this browser's saved page has no page to go into; it was kept aside");
513
+ park(found.raw, { keepSlot: true });
514
+ });
515
+ editorUnit = unit;
516
+ seenUnit = unit;
517
+ }
395
518
  lastRaw = found.raw;
396
519
  base = live();
397
- if (fitted.dropped.length) {
520
+ tip = found.record.data;
521
+ if (!dataFits || fitted.dropped.length) {
398
522
  const parkedKey = park(found.raw, { keepSlot: true });
399
- return { restored: true, hadDraft: true, data: fitted.data, partial: true, parked: Boolean(parkedKey), blocked: !parkedKey };
523
+ return { restored: true, hadDraft: true, data: dataFits ? fitted.data : {}, partial: true, parked: Boolean(parkedKey), blocked: !parkedKey };
400
524
  }
401
525
  return { restored: true, hadDraft: true, data: fitted.data };
402
526
  }
@@ -405,7 +529,14 @@ export function createStore({ doc = document, storage = safeStorage(), now = ()
405
529
  // rules), so only what the page captures is compared.
406
530
  function changed() {
407
531
  if (stopped) return false;
408
- try { return !fitsInto(captured(), base); } catch { return true; }
532
+ try {
533
+ const getHTML = window.clay?.getHTML;
534
+ if (typeof getHTML !== "function") return !fitsInto(live(), base);
535
+ const parsed = new DOMParser().parseFromString(getHTML.call(window.clay), "text/html");
536
+ const unit = recordable(readUnit(parsed));
537
+ // A page equal to the one another tab recorded and this tab took in is not this tab's change.
538
+ return !fitsInto(extractData(parsed, rules), base) || (!sameUnit(unit, editorUnit) && !sameUnit(unit, seenUnit));
539
+ } catch { return true; }
409
540
  }
410
541
 
411
542
  // One copy, the latest: a save that would replace another tab's change keeps it
@@ -446,22 +577,161 @@ export function createStore({ doc = document, storage = safeStorage(), now = ()
446
577
  return fitted.data;
447
578
  }
448
579
 
580
+ // With the database as the record, a save is an operation: for each key the page changed,
581
+ // the stored value it was built on (`b`, absent when nothing was) and the value now (`m`).
582
+ // `tab.ops` are the ones the database has not confirmed, oldest first, under the Start
583
+ // over marker this page booted on; `sizes` runs beside them. A copy that fits goes into
584
+ // this tab's journal slot at once, so closing the tab loses nothing: `journaled` is the
585
+ // last sequence number the slot holds.
586
+ const tab = { id: newId(), reset: "", acked: 0, seq: 0, journaled: 0, ops: [], sizes: [] };
587
+ try { tab.reset = storage?.getItem(resetKey) ?? ""; } catch {}
588
+ try {
589
+ const known = JSON.parse(window.sessionStorage.getItem(key + ":generation"));
590
+ if (known?.marker === tab.reset && typeof known.reset === "string") tab.reset = known.reset;
591
+ } catch {}
592
+ const slotKey = `${key}:op:${tab.id}`;
593
+ const chain = () => ({ tab: tab.id, reset: tab.reset, acked: tab.acked, at: now().getTime(), ops: tab.ops });
594
+
595
+ function journal() {
596
+ try {
597
+ if (!tab.ops.length) {
598
+ tab.journaled = tab.seq;
599
+ storage.removeItem(slotKey);
600
+ return;
601
+ }
602
+ const text = JSON.stringify(chain());
603
+ if (text.length > slotLimit) return;
604
+ storage.setItem(slotKey, text);
605
+ tab.journaled = tab.seq;
606
+ } catch {}
607
+ }
608
+
609
+ // What the page held before its unconfirmed operations, for a page load that falls back
610
+ // to `localStorage` after it made some.
611
+ let before = null;
612
+
613
+ function capture(html) {
614
+ if (stopped) return null;
615
+ if (unparked != null && park(unparked, { keepSlot: true }) === null) {
616
+ throw storageFailure(doc, new Error("an earlier saved copy could not be kept aside"));
617
+ }
618
+ // Too much is waiting: nothing more is taken until it drains, and the page keeps it.
619
+ if (tab.sizes.reduce((sum, size) => sum + size, 0) >= pendingLimit) throw storageFailure(doc, new Error("too many changes are waiting to be saved"));
620
+ const parsed = new DOMParser().parseFromString(html, "text/html");
621
+ const mine = extractData(parsed, rules);
622
+ const mineUnit = recordable(readUnit(parsed));
623
+ const unitChanged = !sameUnit(mineUnit, editorUnit);
624
+ const keys = {};
625
+ for (const name of Object.keys(mine)) {
626
+ if (name in base && fitsInto(mine[name], base[name])) continue;
627
+ // Fields the stored value has and this page's rules lack are carried through.
628
+ keys[name] = name in tip ? { b: tip[name], m: complete(mine[name], tip[name]) } : { m: mine[name] };
629
+ }
630
+ if (!Object.keys(keys).length && !unitChanged) {
631
+ if (!tab.ops.length) {
632
+ setDraftStoreFailed(false);
633
+ doc.dispatchEvent(new CustomEvent("clay:drafts-saved", { detail: { data: mine, unchanged: true } }));
634
+ }
635
+ return null;
636
+ }
637
+ if (!tab.ops.length) before = { base, editorUnit, seenUnit };
638
+ const op = { seq: ++tab.seq, savedAt: now().toISOString(), keys, ...(unitChanged && { unit: { seen: unitMark(seenUnit), to: mineUnit } }) };
639
+ base = { ...base };
640
+ tip = { ...tip };
641
+ for (const name of Object.keys(keys)) {
642
+ base[name] = mine[name];
643
+ tip[name] = keys[name].m;
644
+ }
645
+ if (unitChanged) editorUnit = seenUnit = mineUnit;
646
+ fresh = false;
647
+ tab.ops.push(op);
648
+ tab.sizes.push(JSON.stringify(op).length);
649
+ journal();
650
+ return op;
651
+ }
652
+
653
+ // A hide's synchronous half of a save. False while an operation is neither in the
654
+ // journal slot nor confirmed: closing the tab now could lose it.
655
+ function keep() {
656
+ if (stopped) return true;
657
+ try {
658
+ if (changed()) capture(window.clay.getHTML());
659
+ } catch { return false; }
660
+ return tab.journaled === tab.seq;
661
+ }
662
+
663
+ function acknowledge(seq) {
664
+ if (seq <= tab.acked) return false;
665
+ tab.acked = seq;
666
+ const done = tab.ops.filter((op) => op.seq <= seq).length;
667
+ tab.ops.splice(0, done);
668
+ tab.sizes.splice(0, done);
669
+ journal();
670
+ return done > 0;
671
+ }
672
+
673
+ function drop() {
674
+ tab.ops.length = 0;
675
+ tab.sizes.length = 0;
676
+ journal();
677
+ }
678
+
679
+ // The database did not open: what the journal holds goes back to being this page's
680
+ // unsaved changes, for `write` to save.
681
+ function release() {
682
+ if (tab.ops.length) ({ base, editorUnit, seenUnit } = before);
683
+ drop();
684
+ }
685
+
686
+ // A page with nothing unsaved takes in what the database holds: the keys whose stored
687
+ // value is no longer the one this tab was built on.
688
+ function take(data) {
689
+ const moved = Object.fromEntries(Object.entries(data).filter(([name, value]) => !same(value, tip[name])));
690
+ if (!Object.keys(moved).length) return false;
691
+ const shown = adopt(moved);
692
+ tip = { ...tip, ...moved };
693
+ if (shown) base = { ...base, ...shown };
694
+ fresh = false;
695
+ return true;
696
+ }
697
+
698
+ // The generation this page shows has ended, by this tab's Start over or another tab's:
699
+ // nothing more is saved from it.
700
+ function stop({ keepPending = false } = {}) {
701
+ stopped = true;
702
+ if (!keepPending) drop();
703
+ }
704
+
705
+ const journalSlots = () => storedKeys().filter((name) => name.startsWith(key + ":op:"));
706
+
449
707
  function write(html) {
450
708
  if (stopped) return null;
451
709
  if (unparked != null && park(unparked, { keepSlot: true }) === null) {
452
710
  throw storageFailure(doc, new Error("an earlier saved copy could not be kept aside"));
453
711
  }
454
- const mine = extractData(new DOMParser().parseFromString(html, "text/html"), rules);
712
+ const parsed = new DOMParser().parseFromString(html, "text/html");
713
+ const mine = extractData(parsed, rules);
714
+ const mineUnit = recordable(readUnit(parsed));
715
+ const mineChanged = !sameUnit(mineUnit, editorUnit);
455
716
  const current = getRaw();
456
- if (fitsInto(mine, base) && (current === lastRaw || current == null)) {
717
+ if (fitsInto(mine, base) && !mineChanged && (current === lastRaw || current == null)) {
457
718
  setDraftStoreFailed(false);
458
719
  doc.dispatchEvent(new CustomEvent("clay:drafts-saved", { detail: { data: mine, unchanged: true } }));
459
720
  return mine;
460
721
  }
461
722
  const { data, stored, ancestry, parentData } = merge(mine);
723
+ const record = current == null ? null : parse(current);
724
+ const theirs = unitOf(record);
725
+ // A tab whose page did not change keeps the recorded page: its editor may hold an older one.
726
+ // Both tabs changed it: this tab's page is written and the other is kept aside, as
727
+ // for any field both tabs changed.
728
+ const unit = mineChanged ? mineUnit : (record ? theirs : mineUnit);
729
+ if (mineChanged && record && !sameUnit(theirs, seenUnit)) keepConflict(current);
462
730
  const id = newId();
463
- const raw = stored ?? JSON.stringify({ v: RECORD_VERSION, savedAt: now().toISOString(), id, ancestry, data });
464
- if (!stored) {
731
+ const raw = stored && sameUnit(unit, theirs)
732
+ ? stored
733
+ : JSON.stringify({ v: RECORD_VERSION, savedAt: now().toISOString(), id, ancestry, reset: tab.reset, data, ...(unit && { live: unit.live, blockeditor: unit.blockeditor }) });
734
+ if (raw !== stored) {
465
735
  try {
466
736
  if (!storage) throw new Error("no storage");
467
737
  storage.setItem(key, raw);
@@ -474,6 +744,10 @@ export function createStore({ doc = document, storage = safeStorage(), now = ()
474
744
  if (taken.length) adopt(Object.fromEntries(taken.map((name) => [name, data[name]])));
475
745
  lastRaw = raw;
476
746
  base = data;
747
+ if (mineChanged) editorUnit = mineUnit;
748
+ // Only a page this tab wrote, or the first record, is one this tab has seen: a page kept
749
+ // from another tab is not in this tab's editor yet.
750
+ if (mineChanged || !record) seenUnit = unit;
477
751
  setDraftStoreFailed(false);
478
752
  doc.dispatchEvent(new CustomEvent("clay:drafts-saved", { detail: { data } }));
479
753
  return data;
@@ -506,19 +780,64 @@ export function createStore({ doc = document, storage = safeStorage(), now = ()
506
780
  // adopting a record that dropped its own work.
507
781
  if (forked(record) && typeof window.clay?.getHTML === "function") return writeLive();
508
782
  const adopted = record && adopt(record.data);
509
- if (!adopted) return false;
783
+ if (!adopted && !unitOf(record)) return false;
510
784
  lastRaw = raw;
511
- base = { ...base, ...adopted };
785
+ base = { ...base, ...(adopted || {}) };
512
786
  return true;
513
787
  }
514
788
 
789
+ // The page another tab recorded since this tab last read or wrote one, or null. `record`
790
+ // is the database's; without one the copy in `localStorage` is read.
791
+ function externalUnit(record) {
792
+ if (stopped) return null;
793
+ if (record === undefined) {
794
+ const raw = getRaw();
795
+ record = raw == null ? null : parse(raw);
796
+ }
797
+ const theirs = unitOf(record);
798
+ return theirs && !sameUnit(theirs, seenUnit) ? theirs : null;
799
+ }
800
+
801
+ // This tab's editor took that page in beside its own: it is no longer another tab's.
802
+ function adoptedUnit(unit) {
803
+ seenUnit = unit;
804
+ }
805
+
515
806
  function clear() {
516
- stopped = true;
807
+ stop();
517
808
  unparked = null;
518
809
  setDraftStoreFailed(false);
519
810
  try { storage.removeItem(key); } catch {}
520
- try { storage.setItem(resetKey, newId()); } catch {}
521
- }
522
-
523
- return { slot, key, rules, read, restore, write, writeLive, flush, changed, adoptExternal, park, clear, snapshot };
811
+ const match = /^(\d+)~/.exec(tab.reset);
812
+ const gen = match ? Number(match[1]) : tab.reset === "" ? 1 : null;
813
+ try { storage.setItem(resetKey, gen === null ? newId() : `${gen + 1}~${newId()}`); } catch {}
814
+ }
815
+
816
+ return {
817
+ slot, key, rules, read, restore, write, writeLive, flush, changed, adoptExternal, externalUnit, adoptedUnit, park, clear, snapshot,
818
+ tab: tab.id, slotKey, chain, capture, keep, acknowledge, release, take, stop, journalSlots,
819
+ seed: seedData,
820
+ // For the session, which is fetched after the boot and imports nothing from here.
821
+ merge3,
822
+ unitMark,
823
+ newId,
824
+ failed: (error) => storageFailure(doc, error),
825
+ generation: () => tab.reset,
826
+ enter: (reset) => { tab.reset = reset; },
827
+ fresh: () => fresh,
828
+ shown: () => base !== null,
829
+ pending: () => tab.ops.length,
830
+ unsafe: () => tab.journaled < tab.seq,
831
+ stopped: () => stopped,
832
+ // What `localStorage` holds at the draft's key, whatever wrote it: a record, the head of
833
+ // a draft whose full record is only in the database (`idb`), or nothing readable.
834
+ held() {
835
+ try {
836
+ const held = JSON.parse(getRaw());
837
+ return isPlain(held) ? held : null;
838
+ } catch { return null; }
839
+ },
840
+ // A draft is kept when `localStorage` holds its copy or head, or any tab's journal.
841
+ kept: () => Boolean(read()) || tab.ops.length > 0 || journalSlots().length > 0,
842
+ };
524
843
  }