@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
@@ -188,6 +188,13 @@ function makeSortable(sortableElem, Sortable, record = loose) {
188
188
  let options = {};
189
189
  options.draggable = draggableSelector(sortableElem);
190
190
 
191
+ // A reorder is not a text drag. SortableJS writes the item's text onto the drag by
192
+ // default, and a rich text field under the drop pastes it in, duplicating the text.
193
+ options.setData = (dataTransfer) => {
194
+ dataTransfer.clearData();
195
+ dataTransfer.setData('application/x-clay-sortable', '');
196
+ };
197
+
191
198
  // Check if Sortable instance already exists
192
199
  if (Sortable.get(sortableElem)) return;
193
200
 
@@ -154,7 +154,8 @@ async function showVersion(status, version) {
154
154
  const clay = window.clay;
155
155
  await whenMenuReady(clay);
156
156
  if (!menuReady(clay) || !version) return;
157
- const label = status === "current" ? `This doc is at latest version (${version})` : `Version ${version}`;
157
+ // Short enough for one line in a phone menu (David, Nov 11); the number stays in the plain line.
158
+ const label = status === "current" ? "File at latest version" : `Version ${version}`;
158
159
  clay.menu.addItem({ id: "upgrade", label, text: true });
159
160
  }
160
161
 
@@ -0,0 +1,303 @@
1
+ /**
2
+ * The one change a visitor draft's stored state ever takes.
3
+ *
4
+ * IndexedDB holds one record per draft key K, and only `transact()` changes it: one
5
+ * readwrite transaction reads what is stored, runs `commit()` over it, and writes the
6
+ * result. IndexedDB runs readwrite transactions on a store one after another across every
7
+ * tab of the origin, so this is the single writer, with no lock and no leader tab.
8
+ *
9
+ * What it applies:
10
+ *
11
+ * - operations: for each top-level key a tab changed, the value before (`b`) and after
12
+ * (`m`), a missing one meaning the key was absent. Each carries its own merge base, so
13
+ * the store's `merge3` lays it over whatever is stored. A tab's operations are a
14
+ * chain `{tab, reset, acked, ops: [{seq, savedAt, keys, unit?}]}`, where `reset` is
15
+ * the Start over marker of the generation the tab shows (a tab knows that before its
16
+ * first transaction; it does not know the number). An operation applies only when
17
+ * its chain's `reset` is the stored one and its `seq` is the next for its tab, so
18
+ * each applies exactly once and in order, whichever tab delivers it;
19
+ * - Start over: "end generation g", given explicitly or as a marker `<g+1>~<random>`
20
+ * in `localStorage`; an unverified marker starts empty and keeps recovery copies;
21
+ * - the move: the full version 1 record in `localStorage`, taken in as the first record
22
+ * when nothing is stored yet;
23
+ * - another writer's record: a version 1 record in `localStorage` without the cache's
24
+ * `w` field, written by a tab on a released ClayJS or by a page load of this one that
25
+ * had no database. It is taken in once: merged when the copy it was built on is held
26
+ * here, kept whole as a recovery copy when it is not. Nothing is merged on a guess.
27
+ *
28
+ * Stored rows (JSON text): K (the record, today's format), `K:meta`
29
+ * `{gen, rev, reset, ended?, tabs: {tab: {seq, at}}, foreign, unit, recovery?}`, `K:v1base` (the
30
+ * last record of another writer that was merged), `K:op:<tab>` (a chain a tab left at a
31
+ * hide), `K:conflict:<id>` with `K:conflict` naming the latest, and `K:recovery:<mark>`.
32
+ * `meta.unit` is the mark of the Block editor page the record holds (`unitMark`). A record
33
+ * id or ancestry identifies another writer's held base or the reset its copy follows.
34
+ * Operations of this ClayJS never read one. Recovery rows can also hold exact ended chains.
35
+ *
36
+ * This module imports nothing: the page fetches it with the database, after the boot, and
37
+ * the store that is already on the page hands it `merge3` and `unitMark`.
38
+ */
39
+
40
+ export const LINEAGE = 32;
41
+ // Recovery copies kept per draft, newest first.
42
+ export const RECOVERY_KEEP = 8;
43
+ // A tab not heard from in this long is forgotten; a chain it still sends carries `acked`.
44
+ export const TAB_TTL_MS = 30 * 24 * 60 * 60 * 1000;
45
+
46
+ const clone = (value) => (value === undefined ? undefined : JSON.parse(JSON.stringify(value)));
47
+ const isPlain = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
48
+
49
+ export const metaKey = (key) => key + ":meta";
50
+ export const rowKey = (key, tab) => `${key}:op:${tab}`;
51
+ export const conflictKey = (key, id) => (id === undefined ? key + ":conflict" : `${key}:conflict:${id}`);
52
+ export const recoveryKey = (key, mark) => `${key}:recovery:${mark}`;
53
+
54
+ // A version 1 record without the cache's `w` field is another writer's.
55
+ export const isForeign = (value) => isPlain(value) && value.v === 1 && value.w === undefined && isPlain(value.data);
56
+ // What names one: its id, or its time for a record from before ids (ClayJS 1.22.0 and earlier).
57
+ export const foreignMark = (record) => (typeof record.id === "string" ? record.id : "at:" + record.savedAt);
58
+
59
+ export const markerFor = (gen, nonce) => `${gen}~${nonce}`;
60
+ export function markerGen(marker) {
61
+ const match = /^(\d+)~/.exec(marker ?? "");
62
+ return match ? Number(match[1]) : null;
63
+ }
64
+
65
+ // One operation's keys laid over `data` with the store's three-way merge.
66
+ export function applyKeys(data, keys, merge3) {
67
+ const base = { ...data };
68
+ const mine = { ...data };
69
+ for (const [name, pair] of Object.entries(keys)) {
70
+ if ("b" in pair) base[name] = pair.b; else delete base[name];
71
+ if ("m" in pair) mine[name] = pair.m; else delete mine[name];
72
+ }
73
+ return merge3(base, mine, data);
74
+ }
75
+
76
+ export const isChain = (value) => isPlain(value) && typeof value.tab === "string" && typeof value.reset === "string" && Array.isArray(value.ops);
77
+ export const recoveryMark = (copy) => isChain(copy) ? `op:${copy.tab}:${copy.ops.at(-1)?.seq ?? copy.acked ?? 0}` : foreignMark(copy);
78
+
79
+ // Cleanup needs proof for this exact chain, even when its generation has ended.
80
+ export function settled(chain, meta) {
81
+ if (isPlain(chain) && meta.recovery?.includes(recoveryMark(chain))) return true;
82
+ if (!isChain(chain)) return false;
83
+ const at = Math.max(chain.acked ?? 0, chain.reset === meta.reset ? meta.tabs[chain.tab]?.seq ?? 0 : 0);
84
+ return chain.ops.every((op) => op.seq <= at);
85
+ }
86
+
87
+ export function emptyMeta(marker = null) {
88
+ return { gen: markerGen(marker) ?? 1, rev: 0, reset: marker ?? "", tabs: {}, foreign: null, unit: null };
89
+ }
90
+
91
+ const unitOf = (record) => (record && record.blockeditor ? { live: record.live, blockeditor: record.blockeditor } : null);
92
+
93
+ // The ids a record built on `record` descends from, nearest first.
94
+ function lineage(first, record, meta) {
95
+ const line = [...first, record?.id, ...(record?.ancestry ?? [])].filter((id) => typeof id === "string");
96
+ return line.length ? line.slice(0, LINEAGE) : ["root:" + meta.reset];
97
+ }
98
+
99
+ // The copy another writer's record was built on, when this side holds it and what it held
100
+ // is in the stored record: the stored record itself, a cache copy of this generation, or
101
+ // the last record of another writer that was merged. A record that descends from one of
102
+ // them (its ancestry names it, or a storage event says it replaced that cache copy)
103
+ // carries every change made since as its difference; the nearest one is the base.
104
+ function foreignBase(line, { record, seen, direct, v1base, meta }) {
105
+ const cache = isPlain(seen) && seen.v === 1 && isPlain(seen.data) && seen.w?.reset === meta.reset ? seen : null;
106
+ const at = (held) => (held && typeof held.id === "string" ? line.indexOf(held.id) : -1);
107
+ const held = [[record, at(record)], [cache, cache && direct ? 0 : at(cache)], [v1base, at(v1base)]].filter(([, index]) => index >= 0);
108
+ return held.length ? held.reduce((nearest, next) => (next[1] < nearest[1] ? next : nearest))[0] : null;
109
+ }
110
+
111
+ /**
112
+ * @param {{meta: ?Object, record: ?Object, v1base: ?Object}} stored
113
+ * @param {Object} input
114
+ * @param {Object} input.seed - the data a generation starts from (the served page's)
115
+ * @param {() => string} input.newId
116
+ * @param {number} input.now - milliseconds
117
+ * @param {Function} input.merge3 - the store's three-way merge
118
+ * @param {Function} input.unitMark - the store's mark of a Block editor page
119
+ * @param {?string} [input.marker] - the Start over marker `localStorage` holds now
120
+ * @param {Array<{ends: number, marker: string}>} [input.resets]
121
+ * @param {Array<Object>} [input.chains] - oldest carriers first, the caller's own last
122
+ * @param {?Object} [input.local] - what `localStorage` holds at the draft's key now, parsed
123
+ * @param {?Object} [input.seen] - the latest cache copy the caller saw at the draft's key
124
+ * @param {boolean} [input.direct] - a storage event says `local` replaced exactly that copy
125
+ */
126
+ export function commit(stored, { seed, newId, now, merge3, unitMark, marker = null, resets = [], chains = [], local = null, seen = null, direct = false }) {
127
+ const meta = stored.meta ? clone(stored.meta) : emptyMeta(marker);
128
+ // A database the browser cleared counts on from this ClayJS's cache, so the cache never
129
+ // reads as newer than the record.
130
+ if (!stored.meta && Number.isInteger(local?.w?.rev)) meta.rev = local.w.rev;
131
+ let record = stored.record ? clone(stored.record) : null;
132
+ let v1base = stored.v1base ? clone(stored.v1base) : null;
133
+ const out = { conflicts: [], recovered: [], released: [], dropped: [], stale: [], applied: {}, changed: !stored.meta };
134
+
135
+ function recover(copy, supersedes = []) {
136
+ const mark = recoveryMark(copy);
137
+ if (meta.recovery?.includes(mark)) return;
138
+ out.recovered.push(clone(copy));
139
+ const kept = [mark, ...(meta.recovery ?? []).filter((held) => held !== mark && !supersedes.includes(held))].slice(0, RECOVERY_KEEP);
140
+ out.dropped = [...new Set([...out.dropped, ...(meta.recovery ?? [])])].filter((held) => !kept.includes(held));
141
+ meta.recovery = kept;
142
+ out.changed = true;
143
+ return true;
144
+ }
145
+
146
+ const ends = [...resets];
147
+ if (marker && marker !== meta.reset) ends.push({ ends: markerGen(marker) === null ? null : markerGen(marker) - 1, marker });
148
+ for (const reset of ends) {
149
+ if (reset.marker === meta.reset || meta.ended?.includes(reset.marker) || (reset.ends !== null && reset.ends < meta.gen)) continue;
150
+ if (reset.ends !== meta.gen) {
151
+ if (record) recover(record);
152
+ }
153
+ const follows = typeof local?.reset === "string" ? local.reset === reset.marker
154
+ : reset.ends === null || (Array.isArray(local?.ancestry) && local.ancestry.includes("root:" + reset.marker));
155
+ if (isForeign(local) && foreignMark(local) !== meta.foreign && !follows) {
156
+ recover(local);
157
+ meta.foreign = foreignMark(local);
158
+ }
159
+ meta.ended = [...(meta.ended ?? []), meta.reset].slice(-LINEAGE);
160
+ Object.assign(meta, { gen: meta.gen + 1, reset: reset.marker, tabs: {}, unit: null });
161
+ meta.rev++;
162
+ record = null;
163
+ v1base = null;
164
+ out.changed = true;
165
+ }
166
+
167
+ // Nothing stored yet: the full copy in `localStorage` is the first record, whether a
168
+ // 1.23.0 tab wrote it (the move) or it is this ClayJS's cache, of this generation, of a
169
+ // database the browser has since cleared.
170
+ if (!stored.meta && !record && isPlain(local) && local.v === 1 && isPlain(local.data) && (local.w === undefined || local.w.reset === meta.reset)) {
171
+ const { w, ...copy } = local;
172
+ record = clone(copy);
173
+ if (w === undefined) {
174
+ v1base = clone(copy);
175
+ meta.foreign = foreignMark(local);
176
+ meta.rev++;
177
+ }
178
+ }
179
+ if (!stored.meta) meta.unit = unitMark(unitOf(record));
180
+
181
+ // Another writer's record met after the move, taken in once and before any chain.
182
+ if (stored.meta && isForeign(local) && foreignMark(local) !== meta.foreign) {
183
+ const mark = foreignMark(local);
184
+ const line = Array.isArray(local.ancestry) ? local.ancestry : [];
185
+ const base = record ? foreignBase(line, { record, seen, direct, v1base, meta }) : null;
186
+ if (typeof local.reset === "string" && meta.ended?.includes(local.reset)) {
187
+ if (recover(local, line)) out.released.push(clone(local));
188
+ } else if (!record) {
189
+ // Nothing is stored in this generation: its page is the draft, as it is between two
190
+ // tabs of that ClayJS.
191
+ record = clone(local);
192
+ v1base = clone(local);
193
+ meta.unit = unitMark(unitOf(record));
194
+ meta.rev++;
195
+ } else if (base) {
196
+ const merged = merge3(base.data, local.data, record.data);
197
+ // A page it carries counts as changed only when it differs from its base's.
198
+ const page = unitOf(local);
199
+ const paged = Boolean(page) && unitMark(page) !== unitMark(unitOf(base));
200
+ const pageMoved = paged && unitOf(record) && unitMark(unitOf(base)) !== meta.unit;
201
+ if (merged.conflict || pageMoved) out.conflicts.push(clone(record));
202
+ const unit = paged ? page : unitOf(record);
203
+ record = {
204
+ v: 1,
205
+ savedAt: local.savedAt ?? new Date(now).toISOString(),
206
+ id: newId(),
207
+ ancestry: lineage(typeof local.id === "string" ? [local.id] : [], record, meta),
208
+ data: merged.value,
209
+ ...(unit && { live: unit.live, blockeditor: unit.blockeditor }),
210
+ };
211
+ if (paged) meta.unit = unitMark(unit);
212
+ v1base = clone(local);
213
+ meta.rev++;
214
+ } else {
215
+ // The copy it was built on is not held here: it is kept whole, beside the draft. A
216
+ // later copy of the same line replaces the one it descends from.
217
+ if (recover(local, line)) out.released.push(clone(local));
218
+ }
219
+ meta.foreign = mark;
220
+ out.changed = true;
221
+ }
222
+
223
+ for (const chain of chains) {
224
+ if (!isChain(chain)) continue;
225
+ if (chain.reset !== meta.reset) {
226
+ const acked = chain.reset === stored.meta?.reset ? stored.meta.tabs[chain.tab]?.seq ?? 0 : 0;
227
+ if (chain.ops.some((op) => op.seq > Math.max(acked, chain.acked ?? 0))) {
228
+ recover(chain);
229
+ }
230
+ if (!out.stale.includes(chain.tab)) out.stale.push(chain.tab);
231
+ continue;
232
+ }
233
+ const before = meta.tabs[chain.tab]?.seq ?? 0;
234
+ let at = Math.max(before, Number.isInteger(chain.acked) ? chain.acked : 0);
235
+ for (const op of chain.ops) {
236
+ if (op.seq <= at) continue;
237
+ if (op.seq !== at + 1) break;
238
+ const merged = applyKeys(record ? record.data : seed, op.keys ?? {}, merge3);
239
+ const unitMoved = op.unit !== undefined && record && unitOf(record) && op.unit.seen !== meta.unit;
240
+ if (record && (merged.conflict || unitMoved)) out.conflicts.push(clone(record));
241
+ const unit = op.unit !== undefined ? op.unit.to : unitOf(record);
242
+ const id = newId();
243
+ record = {
244
+ v: 1,
245
+ savedAt: op.savedAt ?? new Date(now).toISOString(),
246
+ id,
247
+ ancestry: lineage([], record, meta),
248
+ data: merged.value,
249
+ ...(unit && { live: unit.live, blockeditor: unit.blockeditor }),
250
+ };
251
+ if (op.unit !== undefined) meta.unit = unitMark(unit);
252
+ meta.rev++;
253
+ at = op.seq;
254
+ out.changed = true;
255
+ }
256
+ if (at !== before || meta.tabs[chain.tab]) {
257
+ meta.tabs[chain.tab] = { seq: at, at: now };
258
+ out.changed = true;
259
+ }
260
+ out.applied[chain.tab] = at;
261
+ }
262
+
263
+ for (const [tab, entry] of Object.entries(meta.tabs)) {
264
+ if (now - (entry.at ?? 0) > TAB_TTL_MS) {
265
+ delete meta.tabs[tab];
266
+ out.changed = true;
267
+ }
268
+ }
269
+ return { meta, record, v1base, ...out };
270
+ }
271
+
272
+ const parse = (text) => {
273
+ if (text == null) return null;
274
+ try { return JSON.parse(text); } catch { return null; }
275
+ };
276
+
277
+ /**
278
+ * Run `commit()` in one readwrite transaction and write what it returns. `input` is a
279
+ * function, called inside the transaction once its reads are in, so what it reads from
280
+ * `localStorage` (journal slots, the marker, the cache) is read at that moment.
281
+ * Resolves on the transaction's `complete` event with `commit()`'s result.
282
+ */
283
+ export function transact(drafts, key, input, options) {
284
+ return drafts.change(key, (raw) => {
285
+ const stored = { meta: parse(raw.meta), record: parse(raw.record), v1base: parse(raw.v1base) };
286
+ const rows = raw.rows.map(([name, text]) => [name, parse(text)]);
287
+ const given = input(stored);
288
+ const result = commit(stored, { ...given, chains: [...rows.map(([, chain]) => chain), ...(given.chains ?? [])] });
289
+ const puts = [];
290
+ const deletes = [];
291
+ if (result.changed) {
292
+ puts.push([metaKey(key), JSON.stringify(result.meta)]);
293
+ if (result.record) puts.push([key, JSON.stringify(result.record)]); else if (stored.record) deletes.push(key);
294
+ if (result.v1base) puts.push([key + ":v1base", JSON.stringify(result.v1base)]); else if (stored.v1base) deletes.push(key + ":v1base");
295
+ }
296
+ for (const copy of result.conflicts) puts.push([conflictKey(key, foreignMark(copy)), JSON.stringify(copy)]);
297
+ if (result.conflicts.length) puts.push([conflictKey(key), JSON.stringify(result.conflicts[result.conflicts.length - 1])]);
298
+ for (const copy of result.recovered) puts.push([recoveryKey(key, recoveryMark(copy)), JSON.stringify(copy)]);
299
+ for (const mark of result.dropped) deletes.push(recoveryKey(key, mark));
300
+ for (const [name, chain] of rows) if (settled(chain, result.meta)) deletes.push(name);
301
+ return { puts, deletes, result };
302
+ }, options);
303
+ }
@@ -0,0 +1,221 @@
1
+ /**
2
+ * The IndexedDB half of the visitor draft store.
3
+ *
4
+ * One database per origin (`clay-visitor-drafts`), two stores:
5
+ *
6
+ * records key: the draft's text key (`clay:visitor-drafts:<slot>@<path>`) and its
7
+ * siblings `<key>:<name>`; value: JSON text
8
+ * parts key: `[recordKey, regionKey, partId]`; value: `{ rev, text }`
9
+ *
10
+ * Every change to a draft goes through `change()`, one readwrite transaction that reads,
11
+ * merges (the caller's `build`) and writes; see `commit.js`. The only other write is
12
+ * `putRow()`, a tab's own chain left blind at a hide. A transaction resolves only once it
13
+ * completes. Every failure rejects with an Error whose `code` says what happened
14
+ * (`unavailable`, `blocked`, `quota`, `aborted`, `unknown-schema`, `closed`, `bad-op`,
15
+ * `error`).
16
+ */
17
+
18
+ import { codeFor } from "../../lib/idb-error-code.js";
19
+
20
+ export const DB_NAME = "clay-visitor-drafts";
21
+ export const DB_VERSION = 1;
22
+ export const RECORDS = "records";
23
+ export const PARTS = "parts";
24
+ const OPEN_TIMEOUT_MS = 2000;
25
+
26
+ function failure(code, cause) {
27
+ const error = new Error(`visitor-drafts: the draft database failed (${code})`);
28
+ error.code = code;
29
+ if (cause !== undefined) error.cause = cause;
30
+ return error;
31
+ }
32
+
33
+ function cursorEntries(source, range) {
34
+ return new Promise((resolve, reject) => {
35
+ const out = [];
36
+ const req = source.openCursor(range);
37
+ req.onerror = () => reject(req.error);
38
+ req.onsuccess = () => {
39
+ const cursor = req.result;
40
+ if (!cursor) return resolve(out);
41
+ out.push([cursor.key, cursor.value]);
42
+ cursor.continue();
43
+ };
44
+ });
45
+ }
46
+
47
+ /**
48
+ * Open (and, the first time, create) the draft database.
49
+ *
50
+ * A database that does not open within `timeout` milliseconds (another tab holds an
51
+ * upgrade, or the browser never answers) rejects with `blocked`, so the caller can
52
+ * refuse to write over a draft it could not read rather than wait forever.
53
+ *
54
+ * @param {Object} [options]
55
+ * @param {?IDBFactory} [options.factory]
56
+ * @param {?typeof IDBKeyRange} [options.keyRange]
57
+ * @returns {Promise<{readAll: Function, readParts: Function, change: Function, putRow: Function, close: Function}>}
58
+ */
59
+ export function openDrafts({ factory = globalThis.indexedDB, keyRange = globalThis.IDBKeyRange, name = DB_NAME, timeout = OPEN_TIMEOUT_MS } = {}) {
60
+ return new Promise((resolve, reject) => {
61
+ if (!factory || typeof factory.open !== "function" || !keyRange) return reject(failure("unavailable"));
62
+ let req;
63
+ try {
64
+ req = factory.open(name, DB_VERSION);
65
+ } catch (error) {
66
+ return reject(failure(codeFor(error), error));
67
+ }
68
+ let settled = false;
69
+ const finish = (fn, value) => {
70
+ if (settled) return false;
71
+ settled = true;
72
+ clearTimeout(timer);
73
+ fn(value);
74
+ return true;
75
+ };
76
+ const timer = setTimeout(() => finish(reject, failure("blocked")), timeout);
77
+ req.onupgradeneeded = () => {
78
+ const db = req.result;
79
+ if (!db.objectStoreNames.contains(RECORDS)) db.createObjectStore(RECORDS);
80
+ if (!db.objectStoreNames.contains(PARTS)) db.createObjectStore(PARTS);
81
+ };
82
+ req.onsuccess = () => {
83
+ const db = req.result;
84
+ // An open that answered after the timeout is closed again, or it would hold the
85
+ // database against another tab's upgrade for as long as this page stays open.
86
+ if (!finish(resolve, wrap(db, keyRange))) db.close();
87
+ };
88
+ req.onerror = () => finish(reject, failure(codeFor(req.error), req.error));
89
+ req.onblocked = () => finish(reject, failure("blocked"));
90
+ });
91
+ }
92
+
93
+ function wrap(db, keyRange) {
94
+ let closed = false;
95
+ // Another tab opening a newer version needs this connection to stand aside.
96
+ db.onversionchange = () => {
97
+ closed = true;
98
+ try { db.close(); } catch {}
99
+ };
100
+
101
+ function transaction(stores, mode) {
102
+ if (closed) throw failure("closed");
103
+ try {
104
+ return db.transaction(stores, mode);
105
+ } catch (error) {
106
+ throw failure(codeFor(error), error);
107
+ }
108
+ }
109
+
110
+ // The record named `recordKey` and its siblings (`<recordKey>:<anything>`), as a Map
111
+ // from key to stored text. A key that only starts with `recordKey`, such as another
112
+ // document whose path extends this one's, is not a sibling.
113
+ async function readAll(recordKey) {
114
+ const tx = transaction(RECORDS, "readonly");
115
+ const range = keyRange.bound(recordKey, recordKey + ":￿");
116
+ try {
117
+ const entries = await cursorEntries(tx.objectStore(RECORDS), range);
118
+ return new Map(entries.filter(([key]) => key === recordKey || key.startsWith(recordKey + ":")));
119
+ } catch (error) {
120
+ throw failure(codeFor(error), error);
121
+ }
122
+ }
123
+
124
+ // The parts of one region of one record, as a Map from part id to `{ rev, text }`.
125
+ // An array sorts after every string, so `[recordKey, regionKey, []]` bounds them all.
126
+ async function readParts(recordKey, regionKey) {
127
+ const tx = transaction(PARTS, "readonly");
128
+ const range = keyRange.bound([recordKey, regionKey], [recordKey, regionKey, []]);
129
+ try {
130
+ const entries = await cursorEntries(tx.objectStore(PARTS), range);
131
+ return new Map(entries.map(([key, value]) => [key[2], value]));
132
+ } catch (error) {
133
+ throw failure(codeFor(error), error);
134
+ }
135
+ }
136
+
137
+ // The one read-merge-write. Reads the record, `:meta`, `:v1base` and every `:op:` row of
138
+ // `key`, hands them to `build` inside the transaction, and writes the `puts` and
139
+ // `deletes` it returns before the transaction can complete. A `build` or a write that
140
+ // throws aborts it, so nothing lands. Resolves with `build`'s `result` on `complete`.
141
+ function change(key, build, { timeout = 0 } = {}) {
142
+ return new Promise((resolve, reject) => {
143
+ let tx;
144
+ try {
145
+ tx = transaction([RECORDS, PARTS], "readwrite");
146
+ } catch (error) {
147
+ return reject(error);
148
+ }
149
+ let thrown = null;
150
+ let result;
151
+ let settled = false;
152
+ let timer;
153
+ const finish = (fn, value) => {
154
+ if (settled) return;
155
+ settled = true;
156
+ clearTimeout(timer);
157
+ fn(value);
158
+ };
159
+ if (timeout > 0) timer = setTimeout(() => {
160
+ thrown = failure("blocked");
161
+ try { tx.abort(); } catch {}
162
+ finish(reject, thrown);
163
+ }, timeout);
164
+ tx.oncomplete = () => finish(resolve, result);
165
+ tx.onabort = () => finish(reject, thrown || failure(tx.error ? codeFor(tx.error) : "aborted", tx.error));
166
+ try {
167
+ const store = tx.objectStore(RECORDS);
168
+ const rows = keyRange.bound(key + ":op:", key + ":op:\uffff");
169
+ const reads = { record: store.get(key), meta: store.get(key + ":meta"), v1base: store.get(key + ":v1base"), names: store.getAllKeys(rows), values: store.getAll(rows) };
170
+ // Requests in one transaction answer in the order they were made: the last one in
171
+ // means every read is.
172
+ reads.values.onsuccess = () => {
173
+ if (settled) return;
174
+ try {
175
+ const raw = {
176
+ record: reads.record.result ?? null,
177
+ meta: reads.meta.result ?? null,
178
+ v1base: reads.v1base.result ?? null,
179
+ rows: reads.names.result.map((name, i) => [name, reads.values.result[i]]),
180
+ };
181
+ const planned = build(raw);
182
+ for (const [name, value] of planned.puts) store.put(value, name);
183
+ for (const name of planned.deletes) store.delete(name);
184
+ result = planned.result;
185
+ } catch (error) {
186
+ thrown = failure(codeFor(error), error);
187
+ try { tx.abort(); } catch {}
188
+ }
189
+ };
190
+ } catch (error) {
191
+ thrown = failure(codeFor(error), error);
192
+ try { tx.abort(); } catch {}
193
+ }
194
+ });
195
+ }
196
+
197
+ // A tab's own unconfirmed chain, written blind (no read) at a hide, with an explicit
198
+ // commit so it needs no callback in a page about to go away. `change()` applies it later.
199
+ function putRow(name, value) {
200
+ return new Promise((resolve, reject) => {
201
+ if (!/:op:[^:]+$/.test(name)) return reject(failure("bad-op"));
202
+ let tx;
203
+ try {
204
+ tx = transaction(RECORDS, "readwrite");
205
+ tx.objectStore(RECORDS).put(value, name);
206
+ tx.commit?.();
207
+ } catch (error) {
208
+ return reject(error instanceof Error && error.code ? error : failure(codeFor(error), error));
209
+ }
210
+ tx.oncomplete = () => resolve();
211
+ tx.onabort = () => reject(failure(tx.error ? codeFor(tx.error) : "aborted", tx.error));
212
+ });
213
+ }
214
+
215
+ function close() {
216
+ closed = true;
217
+ try { db.close(); } catch {}
218
+ }
219
+
220
+ return { readAll, readParts, change, putRow, close };
221
+ }
@@ -22,7 +22,7 @@ export const CLOSE_CSS = `:host { color: inherit !important; }
22
22
  .clay-intro__close svg { display: block; width: 12px; height: 12px; fill: currentColor; stroke: none; }
23
23
  @media (pointer: coarse) { .clay-intro__close { width: 44px; height: 44px; top: 4px; right: 4px; } }
24
24
  .clay-intro__close[data-manual] {
25
- width: auto; height: 36px; padding: 0 14px;
25
+ width: auto; height: 36px; padding: 0 14px; opacity: 1;
26
26
  border-radius: 999px; font-size: 12px; font-weight: 500;
27
27
  text-transform: uppercase; letter-spacing: .04em; white-space: nowrap;
28
28
  }
@@ -6,7 +6,6 @@ import { bootPhase } from "../../core/boot-phase.js";
6
6
  export const COPY = {
7
7
  close: "Hide this introduction",
8
8
  hide: "Hide intro",
9
- show: "Show intro",
10
9
  label: (appName) => `About ${appName}`,
11
10
  };
12
11
 
@@ -86,7 +85,8 @@ export function mountIntro({ memory, template, appName, hadDraft }) {
86
85
  if (!template || !template.content.textContent.trim() && !template.content.querySelector("img, svg, video, canvas")) return null;
87
86
  if (template.getAttribute("visitor-intro") === "manual") {
88
87
  const printing = window.matchMedia && window.matchMedia("print").matches;
89
- const open = forced() || !(memory.get().intro.done || hadDraft || bootPhase.servedHash || printing);
88
+ // A returning visitor keeps the intro until they hide it themselves; hiding it is remembered (David, Nov 11).
89
+ const open = forced() || !(memory.get().intro.done || bootPhase.servedHash || printing);
90
90
  return mountManual({ memory, template, appName, open });
91
91
  }
92
92
  if (!forced()) {
@@ -138,36 +138,19 @@ function mount({ memory, template, appName }) {
138
138
  return section;
139
139
  }
140
140
 
141
+ // Hidden only by its own button, never by scrolling, and once hidden it stays hidden for this
142
+ // visitor: there is no way back from a menu (David, Nov 11).
141
143
  function mountManual({ memory, template, appName, open }) {
142
- let section = null;
143
- const showRow = (wanted) => {
144
- const menu = window.clay?.menu;
145
- if (!menu) return;
146
- if (wanted) menu.addItem({ id: "clay.intro", label: COPY.show, run: show });
147
- else menu.removeItem("clay.intro");
148
- };
149
- function attach() {
150
- section ??= buildSection({ template, appName, labelText: COPY.hide, onClose: hide });
151
- document.body.prepend(section);
152
- announceBottomUi();
153
- showRow(false);
154
- }
144
+ if (!open) return null;
145
+ const section = buildSection({ template, appName, labelText: COPY.hide, onClose: hide });
155
146
  function hide() {
156
147
  memory.get().intro.done = "closed";
157
148
  memory.save();
158
149
  section.remove();
159
150
  announceBottomUi();
160
- showRow(true);
161
151
  focusApp();
162
152
  }
163
- function show() {
164
- memory.get().intro.done = null;
165
- memory.save();
166
- attach();
167
- window.scrollTo(0, 0);
168
- }
169
- document.addEventListener("clay:menu-ready", () => showRow(!section?.isConnected));
170
- if (open) attach();
171
- else showRow(true);
153
+ document.body.prepend(section);
154
+ announceBottomUi();
172
155
  return section;
173
156
  }