@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
@@ -1,8 +1,9 @@
1
1
  import { FEATURES, featureForNode, featureForType } from "../page/features/registry.js";
2
2
  import { mergeEngine } from "../sync/merge-engine.js";
3
3
  import { registerUnsavedState } from "../lib/unsaved-state.js";
4
+ import Mutation from "../lib/mutation.js";
4
5
  import { hostMeta } from "../core/host-meta.js";
5
- import { savePageForce, holdAllSaves, releaseAllSaves, suspendAutosave, resumeAutosave, getLastSavedBytes, savesAdmitted } from "../core/save.js";
6
+ import { savePageForce, holdAllSaves, releaseAllSaves, suspendAutosave, resumeAutosave, getLastSavedBytes, savesAdmitted, claimFailure } from "../core/save.js";
6
7
  import { setSaveHeader, onSaveAccepted } from "../core/save-core.js";
7
8
  import { lastSeenEtag, representedEtag, recordEtag } from "../core/etag.js";
8
9
  import { captureForSaveAndComparison } from "../core/snapshot.js";
@@ -15,19 +16,23 @@ import { claim } from "../page/runtime-owner.js";
15
16
  import { authorIdentity } from "../page/ybind/author.js";
16
17
  import { createInstall, disposeInstall, addDisposer, finishError, isCurrent, ownsView, runBoot, activateInput } from "../page/boot.js";
17
18
  import { capabilityAdvertised } from "../page/tool-capability.js";
18
- import { startsAtImport } from "../core/is-edit-mode.js";
19
+ import { startsAtImport, isDraftMode } from "../core/is-edit-mode.js";
19
20
  import { toolViews } from "../page/tool-views.js";
20
21
  import { style } from "../lib/hostile-css.js";
22
+ import { addViewHook } from "../page/view-hooks.js";
23
+ import { previewShown } from "../page/ai-preview.js";
21
24
 
22
- const ARTICLE_SEL = "article[data-page]";
23
- const TAG_SEL = 'script[type="application/json"][merge="page"][clay~="live"]';
25
+ const ARTICLE_SEL = "article[data-blockeditor]";
26
+ const TAG_SEL = 'script[type="application/json"][merge="blockeditor"][clay~="live"]';
24
27
  const BLOCKED = { state: "blocked", code: "unsupported-host", message: "This host does not support page editing" };
25
28
  const STORE_TEXT = "Changes cannot be stored on this device. Download a copy to keep them.";
26
29
  const RETRY_BOUND_MS = 45000;
27
30
  const GENERIC_ERROR = "Couldn't save";
28
- const GENERIC_OFFLINE = "Offline, not saved";
31
+ const GENERIC_OFFLINE = "Offline, will retry";
29
32
  const REPLACED_TEXT = "This page was changed outside the editor. Reload to see it.";
30
33
  const TOO_LARGE_TEXT = "That change is too large to share, so it was not saved. Reload the page to keep editing.";
34
+ const VISITOR_MEDIA = "Download your copy to add pictures and files.";
35
+ const draftUpload = (file, opts) => Promise.resolve({ ok: false, code: "draft", msg: VISITOR_MEDIA });
31
36
  const CLIENT_KEY = /^[ur][0-9a-z]+(-[0-9a-z]+){2}$/;
32
37
  export const PAGE_FULL_MESSAGE = "This document is full, though your changes are still here to download. To edit again, reload this tab after another editor, or the owner in another browser, deletes content.";
33
38
  const OVERWRITTEN_TEXT = "Some changes you made were replaced by a change made outside the editor.";
@@ -46,11 +51,11 @@ const isPlainObject = (v) => v !== null && typeof v === "object" && !Array.isArr
46
51
  function unreadablePage(value) {
47
52
  if (!isPlainObject(value)) {
48
53
  console.warn("clayjs: page plugin: the page tag is not JSON, leaving the page as saved HTML");
49
- return "invalid-page-json";
54
+ return "invalid-blockeditor-json";
50
55
  }
51
56
  if (value.v !== 1 || !isPlainObject(value.y) || Object.hasOwn(value, "blocks") || Object.hasOwn(value, "deleted")) {
52
57
  console.warn("clayjs: page plugin: the page tag is not a strict Yjs page, leaving the page as saved HTML");
53
- return "unsupported-page-format";
58
+ return "unsupported-blockeditor-format";
54
59
  }
55
60
  return null;
56
61
  }
@@ -98,7 +103,7 @@ function armLoadingStatus(inst, article) {
98
103
  inst.statusTimer = null;
99
104
  if (!isCurrent(inst)) return;
100
105
  const note = article.ownerDocument.createElement("div");
101
- note.className = "page-boot-status";
106
+ note.className = "blockeditor-boot-status";
102
107
  note.setAttribute("clay", "editor-ui");
103
108
  note.setAttribute("sap-ignore", "");
104
109
  note.setAttribute("role", "status");
@@ -120,6 +125,22 @@ let ended = null;
120
125
 
121
126
  // Shorter than installAdoption's 10 s hold, so an unelected room still adopts.
122
127
  export const WRITER_WAIT_MS = 8_000;
128
+ // ai-edit loads late, after the editor takes input, so the first /ai or Revise can
129
+ // arrive before `clay.aiEdit` exists. The wait is bounded like the menu's own.
130
+ const AI_EDIT_WAIT_MS = 8_000;
131
+ const AI_UNAVAILABLE = "AI tool making isn't available on this page right now.";
132
+
133
+ function whenPresent(get, ms) {
134
+ return new Promise((resolve) => {
135
+ const start = Date.now();
136
+ const tick = () => {
137
+ const value = get();
138
+ if (value || Date.now() - start >= ms) resolve(value || null);
139
+ else setTimeout(tick, 50);
140
+ };
141
+ tick();
142
+ });
143
+ }
123
144
 
124
145
  function scrollToHashBlock(view, loc = globalThis.location) {
125
146
  const raw = loc?.hash?.slice(1);
@@ -133,7 +154,7 @@ function scrollToHashBlock(view, loc = globalThis.location) {
133
154
  }
134
155
 
135
156
  function dispatchState(state, code = null, message = "") {
136
- document.dispatchEvent(new CustomEvent("clay:page-save-state", { detail: { state, code, message } }));
157
+ document.dispatchEvent(new CustomEvent("clay:blockeditor-save-state", { detail: { state, code, message } }));
137
158
  }
138
159
 
139
160
  function dismissGeneric() {
@@ -183,7 +204,7 @@ function showBlocked(inst, code, message, state = "blocked") {
183
204
  toastPersistent(state === "unavailable" ? STORE_TEXT : BLOCKED_TEXT, "error", {
184
205
  detail: message,
185
206
  actions: [
186
- { label: "Download a copy", onClick: downloadPageCopy },
207
+ { label: "Download copy", onClick: downloadPageCopy },
187
208
  { label: "Copy text", onClick: copyPageText },
188
209
  ],
189
210
  });
@@ -275,16 +296,58 @@ function listen(inst, type, handler) {
275
296
  inst.listeners.push([type, handler]);
276
297
  }
277
298
 
299
+ // The page's own history, the one the editor's Cmd+Z already uses: nothing is undoable
300
+ // while a proposal is only a preview, because Discard is the whole of that change and
301
+ // Keep is the one step worth undoing.
302
+ function pageHistory(inst) {
303
+ const binding = inst?.binding;
304
+ const um = binding?.bridge?.um;
305
+ if (!binding || !um) return null;
306
+ const showing = () => {
307
+ const view = inst.view;
308
+ return Boolean(view && !view.isDestroyed && previewShown(view.state));
309
+ };
310
+ const focus = () => inst.view?.focus?.();
311
+ return {
312
+ undo: () => {
313
+ if (binding.undo()) focus();
314
+ },
315
+ redo: () => {
316
+ if (binding.redo()) focus();
317
+ },
318
+ get canUndo() {
319
+ return um.undoStack.length > 0 && !showing();
320
+ },
321
+ get canRedo() {
322
+ return um.redoStack.length > 0 && !showing();
323
+ },
324
+ };
325
+ }
326
+
327
+ // One event for whatever presents the history, the same one plugins/undo.js emits: after
328
+ // every entry the page's stack takes or replays, and whenever a preview comes up or goes.
329
+ function notifyHistory(inst) {
330
+ const history = pageHistory(inst);
331
+ document.dispatchEvent(new CustomEvent("clay:undo-change", {
332
+ detail: { canUndo: !!history?.canUndo, canRedo: !!history?.canRedo },
333
+ }));
334
+ }
335
+
278
336
  function listenForSaves(inst) {
337
+ // Retrying and blocked carry the editor's own notices, so core adds no generic one on
338
+ // top; a plain error is left to the generic "Couldn't save".
339
+ const reportFailure = (event, state) => {
340
+ if (state !== "error") claimFailure(event);
341
+ applySaveState(inst, state, event.detail?.code ?? null, event.detail?.msg ?? "");
342
+ };
279
343
  listen(inst, "clay:save-error", (event) => {
280
344
  const code = event.detail?.code ?? null;
281
345
  // The host holds exactly what this tab would have written: nothing to report.
282
- if (code === "page-nothing-to-write") return;
283
- applySaveState(inst, saveStateFor({ code }), code, event.detail?.msg ?? "");
346
+ if (code === "blockeditor-nothing-to-write") return claimFailure(event);
347
+ reportFailure(event, saveStateFor({ code }));
284
348
  });
285
349
  listen(inst, "clay:save-offline", (event) => {
286
- const code = event.detail?.code ?? null;
287
- applySaveState(inst, saveStateFor({ code, offline: true }), code, event.detail?.msg ?? "");
350
+ reportFailure(event, saveStateFor({ code: event.detail?.code ?? null, offline: true }));
288
351
  });
289
352
  listen(inst, "clay:save-conflict", (event) => {
290
353
  const code = event.detail?.code ?? null;
@@ -299,7 +362,7 @@ function listenForSaves(inst) {
299
362
  inst.adoption?.saved(getLastSavedBytes());
300
363
  });
301
364
  listen(inst, "clay:live-notice", (event) => {
302
- if (event.detail?.name !== "page") return;
365
+ if (event.detail?.name !== "blockeditor") return;
303
366
  if (event.detail?.code === "op-too-large") return;
304
367
  if (event.detail?.code === "unknown-store") {
305
368
  showReplaced(inst);
@@ -312,6 +375,7 @@ function listenForSaves(inst) {
312
375
 
313
376
  // The page the host stores now, including any merge state it keeps beside the file.
314
377
  async function readStoredArticle() {
378
+ if (isDraftMode) return null;
315
379
  const response = await fetch(new URL(globalThis.location.href).href, { cache: "no-store" });
316
380
  if (!response?.ok) return null;
317
381
  const html = await response.text();
@@ -335,7 +399,7 @@ async function retryFirstWrite(inst, event) {
335
399
  const staged = inst.staged;
336
400
  if (!staged || inst.staleRetry || inst.replaced) return;
337
401
  const refusal = event?.detail?.code ?? null;
338
- if (refusal !== "page-lineage-stale" && refusal !== "conflict") return;
402
+ if (refusal !== "blockeditor-lineage-stale" && refusal !== "conflict") return;
339
403
  if (!inst.store || !staged.isPending()) return;
340
404
  inst.staleRetry = true;
341
405
  try {
@@ -387,15 +451,13 @@ function releaseMounted(inst) {
387
451
  inst.adoption = null;
388
452
  try { inst.recover?.destroy(); } catch (error) { console.error("clayjs: page plugin recover list failed to dispose", error); }
389
453
  inst.recover = null;
390
- inst.aiTool?.dispose();
391
- inst.aiTool = null;
392
454
  inst.awareness?.dispose();
393
455
  inst.awareness = null;
394
456
  inst.toolHost?.dispose();
395
457
  inst.toolHost = null;
396
458
  if (inst.view) toolViews.delete(inst.view);
397
459
  if (inst.article) {
398
- const shell = inst.article.querySelector(".page-shell");
460
+ const shell = inst.article.querySelector(".blockeditor-shell");
399
461
  if (shell) shell.remove();
400
462
  }
401
463
  try { inst.binding?.dispose(); } catch (error) { console.error("clayjs: page plugin binding failed to dispose", error); }
@@ -403,7 +465,7 @@ function releaseMounted(inst) {
403
465
  try { inst.staged?.close(); } catch (error) { console.error("clayjs: page plugin staged store failed to close", error); }
404
466
  inst.staged = null;
405
467
  if (inst.pageStore) {
406
- try { closeStore("page", inst.pageStore); } catch (error) { console.error("clayjs: page plugin store failed to close", error); }
468
+ try { closeStore("blockeditor", inst.pageStore); } catch (error) { console.error("clayjs: page plugin store failed to close", error); }
407
469
  inst.pageStore = null;
408
470
  }
409
471
  inst.binding = null;
@@ -433,7 +495,7 @@ function dispose() {
433
495
  generation += 1;
434
496
  const inst = active;
435
497
  active = null;
436
- setSaveHeader("Page-Write", null);
498
+ setSaveHeader("Blockeditor-Write", null);
437
499
  registerPageCheckpoints(null);
438
500
  if (!inst) return;
439
501
  disposeInstall(inst);
@@ -479,11 +541,12 @@ function pageRecovery() {
479
541
  return recoveryModules;
480
542
  }
481
543
 
544
+ // A visitor's draft has no recovery identity on this host: its record in this browser is the copy.
482
545
  // What this session journals under, or null when this page cannot pin an identity or this
483
546
  // host has no recovery service: the document is then edited with no local copy, as before.
484
547
  async function openJournal(meta, store) {
485
548
  const service = globalThis.window?.clay?.recovery ?? null;
486
- if (!service || !store) return null;
549
+ if (isDraftMode || !service || !store) return null;
487
550
  const modules = await pageRecovery();
488
551
  if (!modules) return null;
489
552
  try {
@@ -521,7 +584,7 @@ async function replayJournal(inst) {
521
584
  function installRecoveryReview(inst, ui) {
522
585
  if (inst.recoveryPanel) return inst.recoveryPanel;
523
586
  const view = inst.view;
524
- const mount = view?.dom.closest(".page-shell") ?? inst.article;
587
+ const mount = view?.dom.closest(".blockeditor-shell") ?? inst.article;
525
588
  if (!mount) return null;
526
589
  const review = ui.createRecoveryReview({
527
590
  mount,
@@ -581,6 +644,23 @@ function installJournalStatus(inst, modules) {
581
644
  return inst.pageStatus;
582
645
  }
583
646
 
647
+ // The page store the session mounts, brought to the file the reader holds. A page store's
648
+ // value is a set of Yjs updates and this tab's own unsaved ops are updates of their own, so
649
+ // installing the file's value as the store's own leaves the unconfirmed work replayed over
650
+ // the page the file states; the binding that mounts next applies both sets in one Y
651
+ // document, which is the merge. False when the store cannot take the value, and the entry
652
+ // refuses rather than mounting a page the file does not state.
653
+ function mergeRepresentedPage(store, value) {
654
+ if (unreadablePage(value)) return false;
655
+ if (serializeLive(store.authoritative) === serializeLive(value)) return true;
656
+ try {
657
+ return store.rebase(value, { source: "state" });
658
+ } catch (error) {
659
+ console.warn("clayjs: page plugin could not reconcile the open page store with the saved file", error);
660
+ return false;
661
+ }
662
+ }
663
+
584
664
  export function createSteps(inst, options) {
585
665
  let meta = null;
586
666
  let binding = null;
@@ -591,7 +671,7 @@ export function createSteps(inst, options) {
591
671
  let featuresReady = null;
592
672
  let savedContent = [];
593
673
  let toolsStore = null;
594
- let aiTool = null;
674
+ let pageAiReady = null;
595
675
  let toolsOpening = null;
596
676
  let chrome = null;
597
677
  let setEditorActive = null;
@@ -642,7 +722,7 @@ export function createSteps(inst, options) {
642
722
  // A document with a tool block, at boot or when one arrives, opens the store its
643
723
  // tools keep state in and the host that mounts them. A document with no tool
644
724
  // never waits for either.
645
- const TOOLS_SEL = 'script[type="application/json"][merge="page.tools"][clay~="live"]';
725
+ const TOOLS_SEL = 'script[type="application/json"][merge="blockeditor.tools"][clay~="live"]';
646
726
  const openTools = () => {
647
727
  if (toolsOpening) return toolsOpening;
648
728
  toolsOpening = (async () => {
@@ -652,14 +732,14 @@ export function createSteps(inst, options) {
652
732
  if (!document.querySelector(TOOLS_SEL)) {
653
733
  const tools = document.createElement("script");
654
734
  tools.setAttribute("type", "application/json");
655
- tools.setAttribute("merge", "page.tools");
735
+ tools.setAttribute("merge", "blockeditor.tools");
656
736
  tools.setAttribute("clay", "live");
657
737
  tools.textContent = "{}";
658
738
  document.head.appendChild(tools);
659
739
  }
660
740
  if (document.querySelector(TOOLS_SEL)) {
661
741
  try {
662
- toolsStore = window.clay.live("page.tools");
742
+ toolsStore = window.clay.live("blockeditor.tools");
663
743
  await toolsStore.ready;
664
744
  } catch (error) {
665
745
  console.warn("clayjs: page plugin could not open the tool store", error);
@@ -676,13 +756,6 @@ export function createSteps(inst, options) {
676
756
  tables: () => tablesFromDoc(editorView?.state.doc ?? binding.doc()).map(({ id, cols, rows }) => ({ id, cols, rows })),
677
757
  });
678
758
  inst.toolHost = toolHost;
679
- if (capabilityAdvertised(meta)) {
680
- const { createAiTool } = await import("../page/ai-tool.js");
681
- if (!isCurrent(inst)) return null;
682
- const transport = window.clay?.wire?.pageTool ?? options.transport ?? null;
683
- aiTool = createAiTool({ getView: () => inst.view, store: toolsStore, transport, acceptState: options.acceptState });
684
- inst.aiTool = aiTool;
685
- }
686
759
  return toolHost;
687
760
  })().catch((error) => {
688
761
  toolsOpening = null;
@@ -694,7 +767,7 @@ export function createSteps(inst, options) {
694
767
  // The document's own switches travel with it: the gear panel writes them into the
695
768
  // page's live store, and a document that never touched one opens no store at all.
696
769
  // Turning a switch off hides a tag; the owner and due values stay in the file.
697
- const SETTINGS_SEL = 'script[type="application/json"][merge="page.settings"][clay~="live"]';
770
+ const SETTINGS_SEL = 'script[type="application/json"][merge="blockeditor.settings"][clay~="live"]';
698
771
  const SETTING_KEYS = ["assignees", "dates"];
699
772
  let settingsOpening = null;
700
773
  let settingsValue = { assignees: true, dates: true };
@@ -729,14 +802,14 @@ export function createSteps(inst, options) {
729
802
  if (!document.querySelector(SETTINGS_SEL)) {
730
803
  const tag = document.createElement("script");
731
804
  tag.setAttribute("type", "application/json");
732
- tag.setAttribute("merge", "page.settings");
805
+ tag.setAttribute("merge", "blockeditor.settings");
733
806
  tag.setAttribute("clay", "live");
734
807
  tag.textContent = "{}";
735
808
  document.head.appendChild(tag);
736
809
  }
737
810
  let handle = null;
738
811
  try {
739
- handle = window.clay.live("page.settings");
812
+ handle = window.clay.live("blockeditor.settings");
740
813
  await handle.ready;
741
814
  } catch (error) {
742
815
  console.warn("clayjs: page plugin could not open the settings store", error);
@@ -786,13 +859,23 @@ export function createSteps(inst, options) {
786
859
  });
787
860
  };
788
861
  inst.notices = notices;
789
- // Making or revising a tool opens the store and the host first, so the panel
790
- // keeps what it makes.
862
+ // Making or revising a tool opens the store and the host first, so the panel keeps
863
+ // what it makes; the panel itself is ai-edit's, on a target this page builds.
791
864
  const openAi = async ({ target = null, remove = null } = {}) => {
792
865
  if (chrome && editorView && remove) chrome.holdSlashRange(editorView, remove);
793
866
  await features.ensure("tool").catch(() => {});
794
- if (!isCurrent(inst) || !aiTool || !editorView) return;
795
- aiTool.open({ target, remove: remove ? chrome.heldSlashRange(editorView.state) : null });
867
+ if (!isCurrent(inst) || !editorView) return;
868
+ const held = remove ? chrome.heldSlashRange(editorView.state) : null;
869
+ const surface = await (pageAiReady ?? Promise.resolve(null));
870
+ if (!surface || !isCurrent(inst)) return;
871
+ const aiTarget = target ? surface.reviseTool(target.toolId) : surface.newTool(held);
872
+ const ai = window.clay?.aiEdit || await whenPresent(() => window.clay?.aiEdit, AI_EDIT_WAIT_MS);
873
+ if (!isCurrent(inst)) return;
874
+ if (!ai?.openTarget) {
875
+ toast(AI_UNAVAILABLE, "warning");
876
+ return;
877
+ }
878
+ ai.openTarget(aiTarget);
796
879
  };
797
880
  const aiRequested = () => capabilityAdvertised(meta);
798
881
 
@@ -809,7 +892,7 @@ export function createSteps(inst, options) {
809
892
  : null;
810
893
  const articles = represented ? represented.querySelectorAll(ARTICLE_SEL) : liveArticles;
811
894
  const tags = represented ? represented.querySelectorAll(TAG_SEL) : document.querySelectorAll(TAG_SEL);
812
- if (!articles.length && !tags.length) return { ok: false, reason: "no-page" };
895
+ if (!articles.length && !tags.length) return { ok: false, reason: "no-blockeditor" };
813
896
  // A page saved without its merge state (HTML Clay, an outside edit, a download)
814
897
  // is booted from its article alone: the seed is deterministic, so every tab
815
898
  // rebuilding the same article holds the same state and their edits merge. The tag
@@ -818,11 +901,11 @@ export function createSteps(inst, options) {
818
901
  // the next install to read as a page the file already has.
819
902
  const seeded = articles.length === 1 && tags.length === 0;
820
903
  if (articles.length !== 1 || (!seeded && tags.length !== 1)) {
821
- console.warn(`clayjs: page plugin needs exactly one article[data-page] and one merge="page" live tag; found ${articles.length} article(s) and ${tags.length} tag(s), leaving the page as saved HTML`);
822
- return { ok: false, reason: "ambiguous-page" };
904
+ console.warn(`clayjs: page plugin needs exactly one article[data-blockeditor] and one merge="blockeditor" live tag; found ${articles.length} article(s) and ${tags.length} tag(s), leaving the page as saved HTML`);
905
+ return { ok: false, reason: "ambiguous-blockeditor" };
823
906
  }
824
907
  const element = liveArticles[0];
825
- if (!element) return { ok: false, reason: "no-page" };
908
+ if (!element) return { ok: false, reason: "no-blockeditor" };
826
909
  // The file the reader represented is the page this session edits: its tag and the
827
910
  // article the seed is built from come off those bytes once, so a tag another tab
828
911
  // changed and a reader's own local state cannot reach what is staged. The live
@@ -844,7 +927,7 @@ export function createSteps(inst, options) {
844
927
  if (seeded) {
845
928
  try {
846
929
  const meta = await hostMeta();
847
- const href = meta?.extensions?.includes("page-recovery-v1") && meta.document?.pageState?.href;
930
+ const href = meta?.extensions?.includes("blockeditor-recovery-v1") && meta.document?.blockeditorState?.href;
848
931
  const etag = inst.context?.represented ? inst.context.represented.etag : representedEtag() ?? lastSeenEtag();
849
932
  if (href && etag) {
850
933
  const { readPageState } = await import("../page/checkpoint-proof.js");
@@ -854,7 +937,7 @@ export function createSteps(inst, options) {
854
937
  if (stored.etag !== etag) return { ok: false, reason: "etag-mismatch" };
855
938
  value = parseJsonRelaxed(stored.tag);
856
939
  source.persisted = true;
857
- } else if (stored.code !== "no-page-state") {
940
+ } else if (stored.code !== "no-blockeditor-state") {
858
941
  return { ok: false, reason: stored.code };
859
942
  }
860
943
  } else if (href) {
@@ -867,7 +950,7 @@ export function createSteps(inst, options) {
867
950
  value = source.seedValue = value ?? code.seedPageFromArticle(source.article.cloneNode(true));
868
951
  } catch (error) {
869
952
  console.warn("clayjs: page plugin could not rebuild the page it found, leaving the page as saved HTML", error);
870
- return { ok: false, reason: "invalid-page-source" };
953
+ return { ok: false, reason: "invalid-blockeditor-source" };
871
954
  }
872
955
  const unreadable = unreadablePage(value);
873
956
  if (unreadable) return { ok: false, reason: unreadable };
@@ -877,7 +960,7 @@ export function createSteps(inst, options) {
877
960
  source.blocks = code.blocksFromYValue(value);
878
961
  } catch (error) {
879
962
  console.warn("clayjs: page plugin: the page tag is not a valid Yjs page, leaving the page as saved HTML", error);
880
- return { ok: false, reason: "invalid-page-json" };
963
+ return { ok: false, reason: "invalid-blockeditor-json" };
881
964
  }
882
965
  return { ok: true };
883
966
  })();
@@ -885,23 +968,6 @@ export function createSteps(inst, options) {
885
968
  return { ok: true, source };
886
969
  }
887
970
 
888
- // The page store the session mounts, brought to the file the reader holds. A page store's
889
- // value is a set of Yjs updates and this tab's own unsaved ops are updates of their own, so
890
- // installing the file's value as the store's own leaves the unconfirmed work replayed over
891
- // the page the file states; the binding that mounts next applies both sets in one Y
892
- // document, which is the merge. False when the store cannot take the value, and the entry
893
- // refuses rather than mounting a page the file does not state.
894
- function mergeRepresentedPage(store, value) {
895
- if (unreadablePage(value)) return false;
896
- if (serializeLive(store.authoritative) === serializeLive(value)) return true;
897
- try {
898
- return store.rebase(value, { source: "state" });
899
- } catch (error) {
900
- console.warn("clayjs: page plugin could not reconcile the open page store with the saved file", error);
901
- return false;
902
- }
903
- }
904
-
905
971
  // The page's own code, the editor it starts and the feature views the saved article
906
972
  // demands are separate downloads, and the page's own starts here beside the room join.
907
973
  // Nothing evaluates the editor until the coordinator calls it; the parts are
@@ -919,7 +985,7 @@ function mergeRepresentedPage(store, value) {
919
985
  install: async (id, mod) => {
920
986
  if (!editorView) await editorReady;
921
987
  if (!isCurrent(inst) || !editorView) return null;
922
- const context = { view: editorView, upload: options.upload, board: binding.board, undo: binding.undo, redo: binding.redo, revise: aiRequested() ? (toolId, rev) => { openAi({ target: { toolId, rev } }); } : null };
988
+ const context = { view: editorView, upload: isDraftMode ? draftUpload : options.upload, board: binding.board, undo: binding.undo, redo: binding.redo, revise: aiRequested() ? (toolId) => { openAi({ target: { toolId } }); } : null };
923
989
  if (id === "tool") {
924
990
  context.toolHost = await openTools();
925
991
  if (!isCurrent(inst)) return null;
@@ -954,9 +1020,10 @@ function mergeRepresentedPage(store, value) {
954
1020
  // download: it starts here, beside authority, and only a page that must rebuild
955
1021
  // its tag waits for it before the join. Nothing else on this path touches a vendor.
956
1022
  const codePromise = pageCodeReady();
957
- try { meta = await hostMeta(); } catch (error) { meta = null; }
1023
+ // A visitor's draft never asks the host: the editor's only store is this browser's.
1024
+ try { meta = isDraftMode ? { spec: null, extensions: ["blockeditor"], document: null } : await hostMeta(); } catch (error) { meta = null; }
958
1025
  if (!isCurrent(inst)) return { ok: false, cancelled: true };
959
- if (!meta || !Array.isArray(meta.extensions) || !meta.extensions.includes("page")) {
1026
+ if (!meta || !Array.isArray(meta.extensions) || !meta.extensions.includes("blockeditor")) {
960
1027
  showBlocked(inst, BLOCKED.code, BLOCKED.message);
961
1028
  return { ok: false, reason: "missing-capability" };
962
1029
  }
@@ -965,7 +1032,7 @@ function mergeRepresentedPage(store, value) {
965
1032
  // is merged by the boot instead of being read as a replacement.
966
1033
  inst.mergeTagless = !meta.extensions.includes("live-data");
967
1034
  // The one declaration that lets a host answer a seed-only body with nothing-to-write.
968
- setSaveHeader("Page-Write", "v1");
1035
+ setSaveHeader("Blockeditor-Write", "v1");
969
1036
  // A seeded page is the one boot whose join needs the page code, which rebuilds
970
1037
  // the seed the article states.
971
1038
  let read = null;
@@ -985,7 +1052,7 @@ function mergeRepresentedPage(store, value) {
985
1052
  let ownsStore = true;
986
1053
  try {
987
1054
  if (source.seeded) {
988
- const staged = stageStore("page", {
1055
+ const staged = stageStore("blockeditor", {
989
1056
  value: source.seedValue,
990
1057
  represented: options.represented ?? { bytes: source.article.outerHTML, etag: representedEtag() ?? lastSeenEtag() },
991
1058
  persisted: source.persisted,
@@ -993,14 +1060,14 @@ function mergeRepresentedPage(store, value) {
993
1060
  inst.staged = staged;
994
1061
  handle = staged.handle;
995
1062
  } else {
996
- const opened = storeOf("page") === null;
1063
+ const opened = storeOf("blockeditor") === null;
997
1064
  ownsStore = opened;
998
- handle = window.clay.live("page");
999
- if (opened) inst.pageStore = storeOf("page");
1065
+ handle = window.clay.live("blockeditor");
1066
+ if (opened) inst.pageStore = storeOf("blockeditor");
1000
1067
  }
1001
1068
  } catch (error) {
1002
1069
  console.warn("clayjs: page plugin could not open the page store", error);
1003
- return { ok: false, reason: "missing-page-store" };
1070
+ return { ok: false, reason: "missing-blockeditor-store" };
1004
1071
  }
1005
1072
  try {
1006
1073
  await handle.ready;
@@ -1018,14 +1085,14 @@ function mergeRepresentedPage(store, value) {
1018
1085
  // file before anything mounts on it, and a store that cannot take it refuses the
1019
1086
  // entry rather than mounting a page the file does not state. A store that has joined
1020
1087
  // a room is the exception: the room is newer than any file, and its value stands.
1021
- const inRoom = storeOf("page")?.handle?.status?.mode === "fast";
1022
- if (!ownsStore && !inRoom && !mergeRepresentedPage(storeOf("page"), source.tags?.[0])) {
1088
+ const inRoom = storeOf("blockeditor")?.handle?.status?.mode === "fast";
1089
+ if (!ownsStore && !inRoom && !mergeRepresentedPage(storeOf("blockeditor"), source.tags?.[0])) {
1023
1090
  return { ok: false, reason: "stale-store" };
1024
1091
  }
1025
1092
  code ??= await codePromise;
1026
1093
  if (!isCurrent(inst)) return { ok: false, cancelled: true };
1027
- const store = storeOf("page");
1028
- if (!store) return { ok: false, reason: "missing-page-store" };
1094
+ const store = storeOf("blockeditor");
1095
+ if (!store) return { ok: false, reason: "missing-blockeditor-store" };
1029
1096
  inst.store = store;
1030
1097
 
1031
1098
  let flushFailed = false;
@@ -1047,9 +1114,9 @@ function mergeRepresentedPage(store, value) {
1047
1114
  onStopped: (reason) => {
1048
1115
  inst.tabValue = code.yValueOf(binding.ydoc);
1049
1116
  if (reason === "lineage") {
1050
- fastLane()?.abandon?.("page");
1117
+ fastLane()?.abandon?.("blockeditor");
1051
1118
  showReplaced(inst);
1052
- } else if (reason === "page-full" || reason === "compaction-failed") {
1119
+ } else if (reason === "blockeditor-full" || reason === "compaction-failed") {
1053
1120
  showFull(inst, reason);
1054
1121
  } else {
1055
1122
  showTooLarge(inst);
@@ -1138,8 +1205,10 @@ function mountAndAttach(inst, source, loaded) {
1138
1205
  board: binding.board,
1139
1206
  editable: false,
1140
1207
  features,
1208
+ upload: isDraftMode ? draftUpload : options.upload,
1141
1209
  onAiTool: aiRequested() ? (_view, remove) => { openAi({ remove }); } : null,
1142
- reviseTool: aiRequested() ? (toolId, rev) => { openAi({ target: { toolId, rev } }); } : null,
1210
+ mediaNote: isDraftMode ? VISITOR_MEDIA : null,
1211
+ reviseTool: aiRequested() ? (toolId) => { openAi({ target: { toolId } }); } : null,
1143
1212
  formatGate: inst.loadFormatGate,
1144
1213
  });
1145
1214
  } catch (error) {
@@ -1152,6 +1221,22 @@ function mountAndAttach(inst, source, loaded) {
1152
1221
  }
1153
1222
  editorView = view;
1154
1223
  inst.view = view;
1224
+ // The AI surface is the page's own, and it arrives beside the editor rather than in
1225
+ // the plugin chunk: nothing rewrites a page until a person asks it to.
1226
+ pageAiReady = import("../page/ai-surface.js")
1227
+ .then(({ createPageAi }) => {
1228
+ if (!isCurrent(inst) || inst.view !== view) return null;
1229
+ inst.pageAi = createPageAi({
1230
+ getView: () => inst.view,
1231
+ store: () => toolsStore,
1232
+ acceptState: options.acceptState,
1233
+ });
1234
+ return inst.pageAi;
1235
+ })
1236
+ .catch((error) => {
1237
+ console.warn("clayjs: page plugin could not load the AI surface", error);
1238
+ return null;
1239
+ });
1155
1240
  applySettings(readSettings());
1156
1241
  // A document that states settings opens their store so a peer's change reaches this
1157
1242
  // tab; one that states none opens it when a switch is first written.
@@ -1159,18 +1244,52 @@ function mountAndAttach(inst, source, loaded) {
1159
1244
  else listen(inst, "clay:sync-applied", () => {
1160
1245
  if (!settingsOpening && document.querySelector(SETTINGS_SEL)) openSettings().catch(() => null);
1161
1246
  });
1162
- const shell = view.dom.closest(".page-shell");
1247
+ const shell = view.dom.closest(".blockeditor-shell");
1163
1248
  // A shell still waiting for its boot takes no input of any kind: a control inside
1164
1249
  // a non-editable editor would otherwise write before the boot says ready. It is
1165
1250
  // cleared at the one place input is enabled.
1166
1251
  if (shell) shell.inert = true;
1167
1252
  if (loaded.featureIds.length && shell) shell.style.visibility = "hidden";
1168
1253
  // One synchronous move: the finished shell replaces the saved surface as a
1169
- // single change, and the article element itself stays where it is.
1170
- if (!loaded.featureIds.length) for (const node of savedContent) node.remove();
1171
- if (shell) article.appendChild(shell);
1254
+ // single change, and the article element itself stays where it is. The editor
1255
+ // chunk can land after the boot settle, so the move is paused: it is the editor
1256
+ // presenting the saved surface, never an edit to save.
1257
+ Mutation.pause();
1258
+ try {
1259
+ if (!loaded.featureIds.length) for (const node of savedContent) node.remove();
1260
+ if (shell) article.appendChild(shell);
1261
+ } finally {
1262
+ Mutation.resume();
1263
+ }
1172
1264
  loaded.refreshView?.(view);
1173
1265
  binding.attach(view);
1266
+ // The page's history tells whoever presents it once it exists (a menu opened before the
1267
+ // editor came up gets its Undo row then), and once per entry it takes or replays. A
1268
+ // peer's typing takes none of them: their work is not this tab's to undo.
1269
+ const um = binding.bridge?.um;
1270
+ if (um) {
1271
+ const notify = () => notifyHistory(inst);
1272
+ um.on("stack-item-added", notify);
1273
+ um.on("stack-item-popped", notify);
1274
+ um.on("stack-cleared", notify);
1275
+ addDisposer(inst, () => {
1276
+ um.off("stack-item-added", notify);
1277
+ um.off("stack-item-popped", notify);
1278
+ um.off("stack-cleared", notify);
1279
+ });
1280
+ notify();
1281
+ }
1282
+ // A proposal shown or cleared changes what the pair may do without touching the stack,
1283
+ // so the change of that flag is what is told, not every transaction.
1284
+ let previewWas = previewShown(view.state);
1285
+ addDisposer(inst, addViewHook(view, {
1286
+ update: (next) => {
1287
+ const previewIs = previewShown(next.state);
1288
+ if (previewIs === previewWas) return;
1289
+ previewWas = previewIs;
1290
+ notifyHistory(inst);
1291
+ },
1292
+ }));
1174
1293
  installAwareness(inst, view);
1175
1294
  editorReadyResolve();
1176
1295
  // The parts the saved article demands install onto the mounted view now, whether
@@ -1180,7 +1299,7 @@ function mountAndAttach(inst, source, loaded) {
1180
1299
  scrollToHashBlock(view);
1181
1300
  inst.recover = loaded.code.createRecoverList({
1182
1301
  binding,
1183
- mount: view.dom.closest(".page-shell") ?? article,
1302
+ mount: view.dom.closest(".blockeditor-shell") ?? article,
1184
1303
  ownTokens: [identity.token, ...identity.owned],
1185
1304
  onRestored: (id) => loaded.code.focusBlock(view, id),
1186
1305
  onStale: () => toast("That change was updated. The list now shows the latest version.", "warning"),
@@ -1269,7 +1388,7 @@ function mountAndAttach(inst, source, loaded) {
1269
1388
  inst.store.handle.set(["y", key], value);
1270
1389
  // The session is not admitted until the commit, so its ops stay in the tab: the
1271
1390
  // claim alone is framed now, before adoption writes anything, or no election runs.
1272
- lane.release("page");
1391
+ lane.release("blockeditor");
1273
1392
  }
1274
1393
  // A lane that never reports a writer must not cost the hand edit: give up
1275
1394
  // before adoption's own bound, and adopt as the file-mode tab would.
@@ -1292,7 +1411,7 @@ function mountAndAttach(inst, source, loaded) {
1292
1411
  mergeTagless: inst.mergeTagless,
1293
1412
  seeded: source.seeded,
1294
1413
  writerReady: awaitWriter,
1295
- adopted: (rev) => fastLane()?.adopted?.("page", rev, inst.store.mark),
1414
+ adopted: (rev) => fastLane()?.adopted?.("blockeditor", rev, inst.store.mark),
1296
1415
  notice: (counts) => {
1297
1416
  if (!counts.copy) return;
1298
1417
  toastPersistent(OVERWRITTEN_TEXT, "warning", {
@@ -1325,12 +1444,12 @@ function mountAndAttach(inst, source, loaded) {
1325
1444
  async function prepareInput(inst, mounted) {
1326
1445
  // A replacement that landed while the page was still booting keeps the editor
1327
1446
  // read-only: the notice is already up, and this tab must not edit the old article.
1328
- if (inst.replaced) return { ok: false, reason: "page-replaced" };
1447
+ if (inst.replaced) return { ok: false, reason: "blockeditor-replaced" };
1329
1448
  await featuresReady;
1330
1449
  if (!isCurrent(inst)) return { ok: false, cancelled: true };
1331
1450
  // The gate: this installation's own view, still in the article it claimed, over
1332
1451
  // the binding the room accepted. Anything else is not something to type into.
1333
- const shell = mounted.view.dom.closest(".page-shell");
1452
+ const shell = mounted.view.dom.closest(".blockeditor-shell");
1334
1453
  if (!ownsView(inst, mounted) || !shell || !shell.isConnected || mounted.view.isDestroyed) {
1335
1454
  return { ok: false, reason: "view-detached" };
1336
1455
  }
@@ -1350,17 +1469,23 @@ function mountAndAttach(inst, source, loaded) {
1350
1469
  // opens. It changes activation only, and only after preparation already passed, so
1351
1470
  // the commit that enters edit cannot fail here.
1352
1471
  function openInput(inst, mounted) {
1353
- const shell = mounted.view.dom.closest(".page-shell");
1472
+ const shell = mounted.view.dom.closest(".blockeditor-shell");
1473
+ // Like the boot move, revealing the editor over the saved surface is presentation, not an edit.
1354
1474
  const showEditor = () => {
1355
- for (const node of savedContent) node.remove();
1356
- if (shell) shell.style.visibility = "";
1475
+ Mutation.pause();
1476
+ try {
1477
+ for (const node of savedContent) node.remove();
1478
+ if (shell) shell.style.visibility = "";
1479
+ } finally {
1480
+ Mutation.resume();
1481
+ }
1357
1482
  };
1358
1483
  // A replacement that landed during the feature wait keeps the editor read-only,
1359
1484
  // exactly as one that landed before it does.
1360
1485
  if (inst.replaced) {
1361
1486
  showEditor();
1362
1487
  stopEditing(inst);
1363
- return { ok: false, reason: "page-replaced" };
1488
+ return { ok: false, reason: "blockeditor-replaced" };
1364
1489
  }
1365
1490
  if (inst.inputOpen) return { ok: true };
1366
1491
  if (!ownsView(inst, mounted) || !shell || !shell.isConnected || mounted.view.isDestroyed) {
@@ -1431,7 +1556,7 @@ function begin(options = {}, { activate = true, context = null } = {}) {
1431
1556
  options.onPhase?.(result);
1432
1557
  },
1433
1558
  }),
1434
- article: null, view: null, binding: null, adoption: null, source: null, store: null, toolHost: null, aiTool: null, bootHtml: "", bootRev: null, savedRev: null, staged: null, pageStore: null, releaseArticle: null, replaced: false, savesHeld: false, flush: null, loadFormatGate: null, listeners: [], retryTimer: null, retryShown: false, statusTimer: null, statusNote: null, notices: null, editorReadyResolve: null, context, inputOpen: false, sessionHeld: false, autosaveSuspended: false, staleRetry: false,
1559
+ article: null, view: null, binding: null, adoption: null, source: null, store: null, toolHost: null, pageAi: null, bootHtml: "", bootRev: null, savedRev: null, staged: null, pageStore: null, releaseArticle: null, replaced: false, savesHeld: false, flush: null, loadFormatGate: null, listeners: [], retryTimer: null, retryShown: false, statusTimer: null, statusNote: null, notices: null, editorReadyResolve: null, context, inputOpen: false, sessionHeld: false, autosaveSuspended: false, staleRetry: false,
1435
1560
  };
1436
1561
  active = inst;
1437
1562
  const boot = run(inst, options, { activate }).catch((error) => {
@@ -1455,7 +1580,7 @@ function endSession(inst) {
1455
1580
  active = null;
1456
1581
  generation += 1;
1457
1582
  }
1458
- setSaveHeader("Page-Write", null);
1583
+ setSaveHeader("Blockeditor-Write", null);
1459
1584
  disposeInstall(inst);
1460
1585
  dismissSaveStateToasts(inst, true);
1461
1586
  inst.titleObserver?.dispose();
@@ -1557,7 +1682,7 @@ export function preparePageSession(context = null, options = {}) {
1557
1682
  */
1558
1683
  export function pageParticipant(options = {}) {
1559
1684
  return {
1560
- id: "clay:page",
1685
+ id: "clay:blockeditor",
1561
1686
  // After live (200) and upload (340): the page builds its stores over the admission
1562
1687
  // those two already opened.
1563
1688
  order: 400,
@@ -1590,6 +1715,14 @@ export const page = {
1590
1715
  get view() {
1591
1716
  return active?.view ?? null;
1592
1717
  },
1718
+ // The page's chosen-area AI, read by ai-edit at use time: null until the editor is up.
1719
+ get ai() {
1720
+ return active?.pageAi ?? null;
1721
+ },
1722
+ // The document's history, for the Claygoo menu: null until its editor is up.
1723
+ get history() {
1724
+ return active ? pageHistory(active) : null;
1725
+ },
1593
1726
  // The document's settings, for the panel that switches them.
1594
1727
  get settings() {
1595
1728
  return active?.settings ?? null;
@@ -1606,6 +1739,32 @@ onSaveAccepted((html) => {
1606
1739
  if (state) binding.acceptFile(state);
1607
1740
  });
1608
1741
 
1742
+ if (isDraftMode) {
1743
+ // Another tab of this visitor's draft recorded its page: it joins this tab's in one Y
1744
+ // document, the same merge a represented page takes, and the next save records both.
1745
+ document.addEventListener("clay:drafts-blockeditor-external", (event) => {
1746
+ const { live, adopted } = event.detail || {};
1747
+ if (!live || typeof live !== "object") return;
1748
+ let merged = true;
1749
+ for (const [name, value] of Object.entries(live)) {
1750
+ const store = storeOf(name);
1751
+ if (!store) continue;
1752
+ try {
1753
+ const ok = name === "blockeditor" ? mergeRepresentedPage(store, value) : store.rebase(value, { source: "state" });
1754
+ if (ok === false) merged = false;
1755
+ } catch (error) {
1756
+ console.warn("clayjs: another tab's page could not join this one", error);
1757
+ merged = false;
1758
+ }
1759
+ }
1760
+ // The provider re-offers this tab's own edits on a timer; a hide before it fires would
1761
+ // capture the other tab's page alone. Reconcile now, so a capture holds both.
1762
+ if (merged) active?.binding?.provider?.settle?.();
1763
+ if (merged) adopted?.();
1764
+ });
1765
+ document.dispatchEvent(new CustomEvent("clay:drafts-blockeditor-listening"));
1766
+ }
1767
+
1609
1768
  // A live entry imports this module while the committed mode is still view: the install
1610
1769
  // belongs to the explicit edit boot alone, and a managed session starts its own.
1611
1770
  page.ready = startsAtImport() ? install() : null;