@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
@@ -0,0 +1,182 @@
1
+ // The area engine: chosen blocks out as a request, a reply back as one undoable
2
+ // transaction. No UI, no transport: a panel sends capture.html and proposes what
3
+ // comes back, then keeps the proposal through keepAreaTransaction.
4
+ import { planAdoption, adoptionTransaction, readBack, recordsOf } from "./adopt.js";
5
+ import { canon, fragmentCleaner, parseArticle, renderModel, resolveIds } from "./projection.js";
6
+ import { blockMap } from "./model.js";
7
+ import { asStep } from "./blocksel.js";
8
+
9
+ export const AREA_GUIDE = "This element holds blocks from a document editor. Reply with the same outer div. Inside it use p, h1, h2, h3, ul, ol, li, blockquote, pre with code, details with summary, hr, table with thead, tbody, tr, th, td, and the inline tags strong, em, s, u, code, a. You may also keep any tag already in the element. Keep each data-id on the block it belongs to, and give a new block no data-id. An hr with data-clay-keep stands for something you cannot see: keep it where it belongs unless the request says to remove it. Keep each checkbox on a list item unless the request says to remove it. No classes, no style attributes, no scripts.";
10
+
11
+ const HELD_TYPES = new Set(["image", "file", "chart", "board", "toc", "tool", "preserved"]);
12
+ const REWRITE_TYPES = new Set(["paragraph", "heading", "list_item", "quote", "callout", "code_block", "toggle", "divider", "table"]);
13
+
14
+ const refused = (code, message) => ({ status: "refused", code, message });
15
+ const body = ({ id, parent, ...rest }) => ({ ...rest, parent: parent ?? null });
16
+ const same = (a, b) => canon(body(a)) === canon(body(b));
17
+ const freshSeed = () => globalThis.crypto?.randomUUID?.() ?? `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
18
+ const CARRY_FIELDS = ["owner", "due", "pageIcon", "pageCoverSrc", "pageCoverAlt", "pageCoverX", "pageCoverY"];
19
+ const carryFields = (was, item) => {
20
+ const out = { ...item };
21
+ for (const key of CARRY_FIELDS) {
22
+ if (out[key] === undefined || out[key] === null) {
23
+ if (was[key] !== undefined) out[key] = was[key];
24
+ }
25
+ }
26
+ return out;
27
+ };
28
+
29
+ const reconcileColumns = (item, was) => {
30
+ const used = new Set(item.cols.map((col) => col.id).filter(Boolean));
31
+ const cols = item.cols.map((col, i) => {
32
+ if (col.id) return col;
33
+ const source = was.cols[i];
34
+ if (!source?.id || used.has(source.id)) return col;
35
+ used.add(source.id);
36
+ return { ...col, id: source.id };
37
+ }).map((col) => {
38
+ const source = col.id ? was.cols.find((base) => base.id === col.id) : null;
39
+ if (!source) return col;
40
+ const out = { ...col };
41
+ if (out.kind === undefined && source.kind !== undefined) out.kind = source.kind;
42
+ if (out.width === undefined && source.width !== undefined) out.width = source.width;
43
+ return out;
44
+ });
45
+ return { ...item, cols };
46
+ };
47
+
48
+ export function captureArea(doc, ids, { dom = document } = {}) {
49
+ const blocks = blockMap(doc);
50
+ if (ids.some((id) => blocks[id]?.t === "opaque_block")) return refused("opaque", "This area holds a block this editor cannot rewrite.");
51
+ const baseItems = readBack(recordsOf(blocks), dom);
52
+ const wanted = new Set(ids);
53
+ const span = baseItems.filter((item) => wanted.has(item.id));
54
+ if (span.length !== ids.length) return refused("not-found", "Some of these blocks are no longer in the document.");
55
+ const held = new Map();
56
+ const typeOf = new Map();
57
+ const model = span.map((item) => {
58
+ const t = blocks[item.id]?.t ?? item.t;
59
+ if (!HELD_TYPES.has(t)) return item;
60
+ held.set(item.id, item);
61
+ typeOf.set(item.id, t);
62
+ return { id: item.id, t: "divider", parent: item.parent };
63
+ });
64
+ if (span.every((item) => held.has(item.id))) return refused("no-text", "This area holds nothing that can be rewritten.");
65
+ const template = dom.createElement("template");
66
+ template.innerHTML = renderModel(model, { clean: fragmentCleaner(dom) });
67
+ for (const el of template.content.querySelectorAll("hr[data-id]")) {
68
+ const id = el.getAttribute("data-id");
69
+ if (!held.has(id)) continue;
70
+ el.setAttribute("data-clay-keep", typeOf.get(id));
71
+ }
72
+ const lo = baseItems.indexOf(span[0]);
73
+ const hi = baseItems.indexOf(span[span.length - 1]);
74
+ if (hi - lo + 1 !== span.length) return refused("not-found", "These blocks are not next to each other.");
75
+ return {
76
+ status: "ok",
77
+ capture: {
78
+ ids: span.map((item) => item.id),
79
+ blocks,
80
+ baseItems,
81
+ before: baseItems.slice(0, lo),
82
+ span,
83
+ after: baseItems.slice(hi + 1),
84
+ parent: span[0].parent ?? null,
85
+ held,
86
+ html: `<div data-clay-area>${template.innerHTML}</div>`,
87
+ },
88
+ };
89
+ }
90
+
91
+ export function staleIds(doc, capture, dom = document) {
92
+ const items = readBack(recordsOf(blockMap(doc)), dom);
93
+ const now = new Map(items.map((item) => [item.id, item]));
94
+ const changed = capture.span
95
+ .filter((item) => !now.has(item.id) || !same(item, now.get(item.id)))
96
+ .map((item) => item.id);
97
+ if (changed.length) return changed;
98
+ const lo = items.findIndex((item) => item.id === capture.ids[0]);
99
+ const unbroken = capture.ids.every((id, n) => items[lo + n]?.id === id);
100
+ return unbroken ? [] : [...capture.ids];
101
+ }
102
+
103
+ export function proposeArea(state, capture, replyHtml, { seed = "", dom = document } = {}) {
104
+ const stale = staleIds(state.doc, capture, dom);
105
+ if (stale.length) return refused("stale", "These blocks changed while AI was working, so nothing was changed.");
106
+
107
+ const template = dom.createElement("template");
108
+ template.innerHTML = replyHtml ?? "";
109
+ const elements = [...template.content.children];
110
+ const only = elements.length === 1 ? elements[0] : null;
111
+ const wrapper = only && only.tagName === "DIV" && (only.hasAttribute("data-clay-area") || !only.hasAttribute("data-id")) ? only : null;
112
+ const article = dom.createElement("article");
113
+ article.append(...(wrapper ? wrapper.childNodes : template.content.childNodes));
114
+ let items = parseArticle(article);
115
+ if (!items.length) return refused("empty", "AI did not write any blocks. Try again.");
116
+
117
+ const inside = new Set(capture.ids);
118
+ items = items.map((item) => (item.id && !inside.has(item.id) ? { ...item, id: null } : item));
119
+ if (items.some((item) => !(item.id && capture.held.has(item.id)) && !REWRITE_TYPES.has(item.t))) {
120
+ return refused("active", "AI used formatting this editor cannot put in a document. Try again.");
121
+ }
122
+
123
+ const base = new Map(capture.span.map((item) => [item.id, item]));
124
+ const used = new Set(items.map((item) => item.id).filter(Boolean));
125
+ let cursor = 0;
126
+ const repaired = items.map((item) => {
127
+ if (item.id) {
128
+ const at = capture.ids.indexOf(item.id);
129
+ if (at >= 0) cursor = at + 1;
130
+ return item;
131
+ }
132
+ const id = capture.ids[cursor];
133
+ const was = id ? base.get(id) : null;
134
+ if (!id || used.has(id) || !was || was.t !== item.t) return item;
135
+ used.add(id);
136
+ cursor++;
137
+ return { ...item, id };
138
+ });
139
+
140
+ const settled = repaired.map((item) => {
141
+ const was = item.id ? base.get(item.id) : null;
142
+ return was?.t === "table" && item.t === "table" && Array.isArray(item.cols) && Array.isArray(was.cols)
143
+ ? reconcileColumns(item, was)
144
+ : item;
145
+ });
146
+
147
+ const taken = new Set(capture.baseItems.filter((item) => !base.has(item.id)).map((item) => item.id));
148
+ let reply = resolveIds(settled, { seed, known: base, taken });
149
+ for (let attempt = 0; attempt < 4 && reply.some((item) => taken.has(item.id)); attempt++) {
150
+ reply = resolveIds(settled, { seed: freshSeed(), known: base, taken });
151
+ }
152
+ if (reply.some((item) => taken.has(item.id))) return refused("active", "The new blocks from AI could not be added to the document. Try again.");
153
+ reply = reply.map((item) => (item.id && capture.held.has(item.id) ? { ...capture.held.get(item.id) } : item));
154
+ reply = reply.map((item) => {
155
+ const was = item.id ? base.get(item.id) : null;
156
+ return was && was.t === item.t ? carryFields(was, item) : item;
157
+ });
158
+ if (capture.parent !== null) reply = reply.map((item) => (item.parent ? item : { ...item, parent: capture.parent }));
159
+
160
+ const plan = planAdoption(capture.blocks, [...capture.before, ...reply, ...capture.after], capture.baseItems);
161
+ if (!plan) return refused("no-change", "AI did not change anything.");
162
+ const tr = adoptionTransaction(state, plan);
163
+ if (!tr) return refused("no-change", "AI did not change anything.");
164
+ const nodes = new Map();
165
+ tr.doc.forEach((node) => nodes.set(node.attrs.id, node));
166
+ return { status: "ready", plan, reply, nodes: reply.map((item) => nodes.get(item.id)).filter(Boolean) };
167
+ }
168
+
169
+ export function keepAreaTransaction(state, capture, proposal, dom = document) {
170
+ const stale = staleIds(state.doc, capture, dom);
171
+ if (stale.length) return { status: "stale", ids: stale };
172
+ const blocks = recordsOf(blockMap(state.doc));
173
+ const items = readBack(blocks, dom);
174
+ const lo = items.findIndex((item) => item.id === capture.ids[0]);
175
+ if (lo < 0) return { status: "stale", ids: [...capture.ids] };
176
+ const hi = lo + capture.ids.length - 1;
177
+ const plan = planAdoption(blocks, [...items.slice(0, lo), ...proposal.reply, ...items.slice(hi + 1)], items);
178
+ if (!plan) return { status: "stale", ids: [] };
179
+ const tr = adoptionTransaction(state, plan);
180
+ if (!tr) return { status: "stale", ids: [] };
181
+ return { status: "ok", tr: asStep(tr) };
182
+ }
@@ -0,0 +1,80 @@
1
+ // The preview of proposed blocks: the chosen blocks hidden, the proposal drawn in their
2
+ // place. Nothing is dispatched into the document, so Discard leaves no trace and Keep is
3
+ // still one transaction away. While a preview is up the editor answers read-only.
4
+ import { state, view as pmView } from "../vendor/prosemirror.vendor.js";
5
+
6
+ const { Plugin, PluginKey } = state;
7
+ const PREVIEW = "blockeditor-ai-preview";
8
+
9
+ export const aiPreviewKey = new PluginKey("blockeditor-ai-preview");
10
+
11
+ // One node decoration per hidden block, and the proposal as a widget at the first of
12
+ // them. Side -1 puts it above the block it stands for, and it takes no part in
13
+ // selection or key handling.
14
+ function decorationsFor(doc, ids, dom) {
15
+ const wanted = new Set(ids);
16
+ const decos = [];
17
+ let first = -1;
18
+ doc.forEach((node, at) => {
19
+ if (!wanted.has(node.attrs?.id)) return;
20
+ if (first < 0) first = at;
21
+ decos.push(pmView.Decoration.node(at, at + node.nodeSize, { class: "blockeditor-ai-hidden" }));
22
+ });
23
+ if (first < 0) return null;
24
+ decos.push(pmView.Decoration.widget(first, dom, { side: -1, ignoreSelection: true, stopEvent: () => true }));
25
+ return pmView.DecorationSet.create(doc, decos);
26
+ }
27
+
28
+ // A proposal that stands for no block of its own: a new tool appears at the bookmark
29
+ // where it will be inserted, and the page around it stays as it is.
30
+ function widgetFor(doc, at, dom) {
31
+ if (typeof at !== "number" || at < 0 || at > doc.content.size) return null;
32
+ return pmView.DecorationSet.create(doc, [
33
+ pmView.Decoration.widget(at, dom, { side: 1, ignoreSelection: true, stopEvent: () => true }),
34
+ ]);
35
+ }
36
+
37
+ export function aiPreviewPlugin() {
38
+ return new Plugin({
39
+ key: aiPreviewKey,
40
+ state: {
41
+ init: () => null,
42
+ // The set is built from the dispatch that shows it and mapped through every other
43
+ // transaction, so a peer's edit moves the hidden blocks and the proposal with it.
44
+ apply(tr, value, prev, next) {
45
+ const meta = tr.getMeta(PREVIEW);
46
+ if (meta === undefined) {
47
+ if (!value) return null;
48
+ const decorations = value.decorations.map(tr.mapping, tr.doc);
49
+ return decorations ? { decorations } : null;
50
+ }
51
+ if (!meta) return null;
52
+ const decorations = meta.ids ? decorationsFor(next.doc, meta.ids, meta.dom) : widgetFor(next.doc, meta.at, meta.dom);
53
+ return decorations ? { decorations } : null;
54
+ },
55
+ },
56
+ props: {
57
+ decorations: (st) => aiPreviewKey.getState(st)?.decorations ?? null,
58
+ // Any props.editable answering false makes ProseMirror read-only, which is how
59
+ // this composes with the boot's own setEditorActive rather than replacing it.
60
+ editable: (st) => (aiPreviewKey.getState(st) ? false : undefined),
61
+ },
62
+ });
63
+ }
64
+
65
+ export function showPreview(view, { ids, dom }) {
66
+ if (!view || view.isDestroyed || !ids?.length || !dom) return;
67
+ view.dispatch(view.state.tr.setMeta(PREVIEW, { ids: [...ids], dom }));
68
+ }
69
+
70
+ export function showWidget(view, { at, dom }) {
71
+ if (!view || view.isDestroyed || typeof at !== "number" || !dom) return;
72
+ view.dispatch(view.state.tr.setMeta(PREVIEW, { at, dom }));
73
+ }
74
+
75
+ export function clearPreview(view) {
76
+ if (!view || view.isDestroyed || !previewShown(view.state)) return;
77
+ view.dispatch(view.state.tr.setMeta(PREVIEW, null));
78
+ }
79
+
80
+ export const previewShown = (state) => !!aiPreviewKey.getState(state);
@@ -0,0 +1,257 @@
1
+ // clay.blockeditor.ai: the page half of "rewrite a chosen area". It builds a target for the
2
+ // chosen blocks, sends their captured HTML as one request, previews the reply over them
3
+ // and keeps it as one undoable transaction. The panel that drives it is ai-edit's.
4
+ import { state, model } from "../vendor/prosemirror.vendor.js";
5
+ import { schema } from "./schema.js";
6
+ import { outline, siblingSpan, touchedBlocks } from "./blocks.js";
7
+ import { currentSpan } from "./blocksel.js";
8
+ import { showPreview, clearPreview } from "./ai-preview.js";
9
+ import { MAX_PAYLOAD_BYTES, contextRefsOf, wantsPage } from "../lib/ai-comment.js";
10
+ import { createToolTargets } from "./ai-tool.js";
11
+
12
+ const { TextSelection } = state;
13
+ const HELPER = "ai-edit";
14
+ const EDIT_ID = "page area";
15
+ const TOO_LARGE = "This section is too large for AI editing. Select a smaller part.";
16
+ const STALE = "Someone changed this area while AI was working, so nothing was changed.";
17
+ const GONE = "This page is not open for editing any more.";
18
+ const NO_WIRE = "AI editing isn't available on this page right now.";
19
+
20
+ let requestCounter = 0;
21
+
22
+ // Every block is a top-level node, so one pass finds any of them.
23
+ function nodeOf(doc, id) {
24
+ let found = null;
25
+ doc.forEach((node, at) => { if (node.attrs?.id === id) found = { node, at }; });
26
+ return found;
27
+ }
28
+
29
+ const isTool = (doc, id) => {
30
+ const found = nodeOf(doc, id);
31
+ return found?.node?.type === schema.nodes.tool;
32
+ };
33
+
34
+ const idsOf = (ents, lo, hi) => ents.slice(lo, hi + 1).map((e) => e.node.attrs.id).filter(Boolean);
35
+
36
+ const freshSeed = () => globalThis.crypto?.randomUUID?.() ?? `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
37
+
38
+ const emptyRect = () => ({ x: 0, y: 0, left: 0, top: 0, right: 0, bottom: 0, width: 0, height: 0 });
39
+
40
+ function unionRect(rects) {
41
+ if (!rects.length) return emptyRect();
42
+ const left = Math.min(...rects.map((r) => r.left));
43
+ const top = Math.min(...rects.map((r) => r.top));
44
+ const right = Math.max(...rects.map((r) => r.right));
45
+ const bottom = Math.max(...rects.map((r) => r.bottom));
46
+ return { x: left, y: top, left, top, right, bottom, width: right - left, height: bottom - top };
47
+ }
48
+
49
+ // A non-empty text selection inside one block says which words the request is about.
50
+ // Its offsets are counted from the start of that block, as the DOM path counts them
51
+ // from the start of its element.
52
+ function quoteIn(st, ents, span) {
53
+ const sel = st.selection;
54
+ if (!(sel instanceof TextSelection) || sel.empty) return null;
55
+ const [a, b] = touchedBlocks(st.doc, sel, ents);
56
+ if (a !== b || span.lo !== a || span.hi !== a) return null;
57
+ const block = ents[a];
58
+ if (sel.from < block.at || sel.to > block.end) return null;
59
+ const text = st.doc.textBetween(sel.from, sel.to, " ");
60
+ if (!text.trim()) return null;
61
+ const start = st.doc.textBetween(block.at + 1, sel.from, " ").length;
62
+ return { text, start, end: start + text.length };
63
+ }
64
+
65
+ export function createPageAi({ getView, store = () => null, acceptState } = {}) {
66
+ const viewNow = () => {
67
+ const view = getView();
68
+ return view && !view.isDestroyed ? view : null;
69
+ };
70
+
71
+ const targets = new Map();
72
+ const tools = createToolTargets({
73
+ getView: viewNow,
74
+ store,
75
+ acceptState,
76
+ });
77
+
78
+ const reviseTarget = (id) => tools.reviseTool(id);
79
+
80
+ function areaTarget(ids, { quote = null, label = null } = {}) {
81
+ for (const [key, cached] of targets) if (!cached.isConnected) targets.delete(key);
82
+ const cached = targets.get(ids.join(","));
83
+ if (cached) {
84
+ cached.quote = quote ?? undefined;
85
+ return cached;
86
+ }
87
+ let capture = null;
88
+ let proposal = null;
89
+ const target = {
90
+ aiTarget: true,
91
+ label: label ?? (ids.length === 1 ? "This block" : `${ids.length} blocks`),
92
+ ref: [...ids],
93
+ get isConnected() {
94
+ const view = viewNow();
95
+ return !!view && ids.some((id) => nodeOf(view.state.doc, id));
96
+ },
97
+ getBoundingClientRect() {
98
+ const view = viewNow();
99
+ if (!view) return emptyRect();
100
+ const preview = view.dom.querySelector(".blockeditor-ai-preview");
101
+ if (preview) return preview.getBoundingClientRect();
102
+ const ends = [target.ref[0], target.ref[target.ref.length - 1]];
103
+ const rects = ends
104
+ .map((id) => nodeOf(view.state.doc, id))
105
+ .map((found) => (found ? view.nodeDOM(found.at) : null))
106
+ .filter((node) => node && typeof node.getBoundingClientRect === "function")
107
+ .map((node) => node.getBoundingClientRect());
108
+ return unionRect(rects);
109
+ },
110
+ async start(comment, argument) {
111
+ const view = viewNow();
112
+ if (!view) return { status: "refused", code: "gone", message: GONE };
113
+ // Read here, not imported: a page with exclude=wire must not pull the wire in.
114
+ const wire = window.clay?.wire;
115
+ if (!wire) return { status: "refused", code: "no-wire", message: NO_WIRE };
116
+ // Fetched where a person asks, as the /ai tool client is, so a boot that never
117
+ // rewrites anything never downloads the area engine.
118
+ const { captureArea, AREA_GUIDE } = await import("./ai-area.js");
119
+ const found = captureArea(view.state.doc, target.ref);
120
+ capture = null;
121
+ proposal = null;
122
+ if (found.status !== "ok") return { status: "refused", code: found.code, message: found.message };
123
+ const quoted = argument ?? target.quote;
124
+ const payload = {
125
+ id: "req-" + Date.now().toString(36) + "-" + (++requestCounter),
126
+ editId: EDIT_ID,
127
+ tag: "div",
128
+ elementHTML: found.capture.html,
129
+ comment: comment + "\n\n" + AREA_GUIDE,
130
+ contextRefs: contextRefsOf(comment),
131
+ };
132
+ if (quoted) {
133
+ payload.quote = quoted.text;
134
+ if (Number.isInteger(quoted.start)) payload.selection = { start: quoted.start, end: quoted.end };
135
+ }
136
+ if (wantsPage(comment)) payload.page = true;
137
+ // Refused here, before anything is sent: the envelope would refuse it anyway,
138
+ // and a refusal the page cannot explain is worse than one it can.
139
+ if (new Blob([JSON.stringify(payload)]).size > MAX_PAYLOAD_BYTES) {
140
+ return { status: "refused", code: "too-large", message: TOO_LARGE };
141
+ }
142
+ capture = found.capture;
143
+ return {
144
+ status: "ok",
145
+ id: payload.id,
146
+ send: (hooks = {}) => {
147
+ let live = true;
148
+ let handle = null;
149
+ const done = (async () => {
150
+ // @page reads the file the host has on disk, so the page is saved first.
151
+ if (payload.page) await window.clay?.save?.();
152
+ if (!live) return { state: "cancelled" };
153
+ handle = wire.send(payload, { helper: HELPER, onStatus: hooks.onStatus });
154
+ return handle.done;
155
+ })();
156
+ return { done, cancel() { live = false; handle?.cancel(); } };
157
+ },
158
+ };
159
+ },
160
+ async propose(result) {
161
+ const view = viewNow();
162
+ if (!view || !capture) return { status: "refused", message: "This area is no longer open for AI editing." };
163
+ const { proposeArea } = await import("./ai-area.js");
164
+ const ready = proposeArea(view.state, capture, result?.html ?? "", { seed: freshSeed() });
165
+ if (ready.status !== "ready") return { status: "refused", message: ready.message };
166
+ proposal = ready;
167
+ const dom = view.dom.ownerDocument.createElement("div");
168
+ dom.className = "blockeditor-ai-preview";
169
+ const serializer = model.DOMSerializer.fromSchema(schema);
170
+ dom.appendChild(serializer.serializeFragment(model.Fragment.from(ready.nodes), { document: view.dom.ownerDocument }));
171
+ showPreview(view, { ids: capture.ids, dom });
172
+ return { status: "ready" };
173
+ },
174
+ async keep() {
175
+ const view = viewNow();
176
+ if (!view || !capture || !proposal) return { status: "failed", message: STALE };
177
+ const { keepAreaTransaction } = await import("./ai-area.js");
178
+ const result = keepAreaTransaction(view.state, capture, proposal);
179
+ clearPreview(view);
180
+ if (result.status !== "ok") return { status: "failed", message: STALE };
181
+ view.dispatch(result.tr);
182
+ if (view.dom.isConnected) view.focus();
183
+ try {
184
+ await window.clay?.save?.flush?.();
185
+ } catch {
186
+ return { status: "failed", message: "Your change is in the document, but saving failed. It will be saved with your next edit." };
187
+ }
188
+ return { status: "kept" };
189
+ },
190
+ discard() {
191
+ const view = viewNow();
192
+ if (!view) return;
193
+ clearPreview(view);
194
+ if (view.dom.isConnected) view.focus();
195
+ },
196
+ };
197
+ target.quote = quote ?? undefined;
198
+ targets.set(ids.join(","), target);
199
+ return target;
200
+ }
201
+
202
+ return {
203
+ active: () => !!viewNow(),
204
+ targetFromSelection() {
205
+ const view = viewNow();
206
+ if (!view) return null;
207
+ const ents = outline(view.state.doc);
208
+ if (!ents.length) return null;
209
+ const span = currentSpan(view.state, ents);
210
+ const ids = idsOf(ents, span.lo, span.hi);
211
+ if (!ids.length) return null;
212
+ // One tool block is revised, not rewritten: its menu's Revise and Cmd+J on it
213
+ // open the same panel on the same target.
214
+ if (ids.length === 1 && isTool(view.state.doc, ids[0])) return reviseTarget(ids[0]);
215
+ return areaTarget(ids, { quote: quoteIn(view.state, ents, span) });
216
+ },
217
+ targetAt(x, y) {
218
+ const view = viewNow();
219
+ if (!view) return null;
220
+ const hit = view.posAtCoords({ left: x, top: y });
221
+ if (!hit) return null;
222
+ const ents = outline(view.state.doc);
223
+ if (!ents.length) return null;
224
+ const at = hit.inside >= 0 ? hit.inside : hit.pos;
225
+ // A tool is one atomic node, so a point on it resolves to the boundary just after
226
+ // it: the block the person pointed at is the one that ends there.
227
+ const ends = ents.findIndex((e) => e.node.type === schema.nodes.tool && e.end === at);
228
+ const index = ends >= 0 ? ends : Math.min(Math.max(view.state.doc.resolve(at).index(0), 0), ents.length - 1);
229
+ const { lo, hi } = siblingSpan(ents, index, index);
230
+ const ids = idsOf(ents, lo, hi);
231
+ if (!ids.length) return null;
232
+ if (ids.length === 1 && isTool(view.state.doc, ids[0])) return reviseTarget(ids[0]);
233
+ return areaTarget(ids);
234
+ },
235
+ between(a, b) {
236
+ const view = viewNow();
237
+ if (!view) return null;
238
+ const ents = outline(view.state.doc);
239
+ const indexes = [...(a?.ref ?? []), ...(b?.ref ?? [])]
240
+ .map((id) => ents.findIndex((e) => e.node.attrs.id === id))
241
+ .filter((at) => at >= 0);
242
+ if (!indexes.length) return null;
243
+ const { lo, hi } = siblingSpan(ents, Math.min(...indexes), Math.max(...indexes));
244
+ const ids = idsOf(ents, lo, hi);
245
+ return ids.length ? areaTarget(ids) : null;
246
+ },
247
+ wholePage() {
248
+ const view = viewNow();
249
+ const ids = view ? outline(view.state.doc).map((e) => e.node.attrs.id).filter(Boolean) : [];
250
+ return areaTarget(ids, { label: "This page" });
251
+ },
252
+ // `/ai` and Revise are targets the same panel drives, with the tool's own request
253
+ // shape and the tool's own Keep.
254
+ newTool(remove) { return tools.newTool(remove ?? null); },
255
+ reviseTool(toolId) { return tools.reviseTool(toolId); },
256
+ };
257
+ }