@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
@@ -21,7 +21,7 @@
21
21
  *
22
22
  * The bus version previewed the reply as it streamed. The wire carries status lines
23
23
  * only — replaceable progress, capped and droppable — so the page shows throttled
24
- * status lines ("Writing, 1.8 KB") and then the result once, with Keep/Revert as
24
+ * status lines ("Writing, 1.8 KB") and then the result once, with Keep/Discard as
25
25
  * before. `@page` reads the saved file from disk rather than a copy in the payload:
26
26
  * the plugin saves first and then sends `{ page: true }`, because a whole page never
27
27
  * fits the envelope.
@@ -29,12 +29,12 @@
29
29
  * The comment box is compose only: a click anywhere outside it closes it, keeping the
30
30
  * text as a draft for that target, and Send moves the whole live state to one compact
31
31
  * bar at the bottom of the viewport. The bar carries the host's progress and Stop, the
32
- * reply's warnings and Keep/Revert, and then the save, so no part of a running edit
32
+ * reply's warnings and Keep/Discard, and then the save, so no part of a running edit
33
33
  * depends on a popover the person may want out of the way.
34
34
  *
35
35
  * Undo integration: observers pause at request start. On Keep the element is rewound
36
36
  * to the snapshot while still paused, observers resume, then one final morph lands
37
- * the whole AI edit as a single undoable step. On Revert the rewind happens under
37
+ * the whole AI edit as a single undoable step. On Discard the rewind happens under
38
38
  * pause, so history never sees the edit at all.
39
39
  *
40
40
  * All injected chrome is runtime-only (`no-save no-watch no-snapshot`): never saved,
@@ -62,10 +62,7 @@ const UNIT_SELECTOR = "h1,h2,h3,h4,h5,h6,p,figure";
62
62
  const BLOCK_SELECTOR = "p,h1,h2,h3,h4,h5,h6,li,dt,dd,blockquote,pre,figcaption,td,th";
63
63
  const CONTAINER_SELECTOR = "section,article,aside,header,footer,nav,main,div,ul,ol,dl,table,figure,form";
64
64
  const NOT_TEXT = "script,style,template,textarea,input,select,button,iframe,svg,math";
65
- // The request ceiling, kept below the host's 1 MiB envelope so the refusal happens
66
- // here, where the message can say what to do about it.
67
- const MAX_PAYLOAD_BYTES = 900 * 1024;
68
- const TOO_LARGE = "This section is too large for AI editing; select a smaller part.";
65
+ const TOO_LARGE = "This section is too large for AI editing. Select a smaller part.";
69
66
  const SAVE_FAILED = "The save did not finish; the edit is still on the page.";
70
67
 
71
68
  let requestCounter = 0;
@@ -98,6 +95,7 @@ let helperState = null; // "ready" or "unavailable"
98
95
  let listeners = null; // the AbortController every handler one session added listens on
99
96
  let observers = []; // the page observers that session opened
100
97
  let styleEl = null; // the one stylesheet the chrome needed
98
+ let picker = null; // the area picker while it is over the page
101
99
  let chromeToken = 0; // the latest init(); a teardown retires the one before it
102
100
 
103
101
  function dropDraft(el) {
@@ -127,6 +125,15 @@ async function helperStateNow() {
127
125
  return helper && (helper.state === 'ready' || helper.state === 'unavailable') ? helper.state : null;
128
126
  }
129
127
 
128
+ // The page half of "rewrite a chosen area", read at use time: it is null until the
129
+ // page plugin's own dynamic import lands, and on any page that is no Block document.
130
+ const pageAi = () => window.clay?.blockeditor?.ai || null;
131
+
132
+ // A Block document whose own surface has not loaded yet (or failed): the DOM path
133
+ // must stand down, because morphing the editor would replace it.
134
+ const isBlockDoc = () => !!window.clay?.editPlugins?.includes('blockeditor') && !!document.querySelector('article[data-blockeditor]');
135
+ const blockDocWithoutSurface = () => isBlockDoc() && !pageAi()?.active();
136
+
130
137
  // ---------------------------------------------------------------- observers (undo/autosave)
131
138
 
132
139
  function pauseObservers() {
@@ -168,6 +175,9 @@ function liveRange() {
168
175
  // else it is the block around the whole selection, so a backward selection and a
169
176
  // forward one land on the same element.
170
177
  function targetFromRange(range) {
178
+ // A Block document has its own selection toolbar, and its blocks are ProseMirror
179
+ // DOM: the page surface names the area instead.
180
+ if (pageAi() || blockDocWithoutSurface()) return null;
171
181
  let node = range.commonAncestorContainer;
172
182
  if (node.nodeType !== 1) node = node.parentElement;
173
183
  if (!node || !document.body.contains(node)) return null;
@@ -270,16 +280,8 @@ function candidateFrom(html, tag) {
270
280
 
271
281
  // ---------------------------------------------------------------- session lifecycle
272
282
 
273
- function newSession(el, comment, quote) {
283
+ function newSession(el, comment, quote, { contextRefsOf, wantsPage }) {
274
284
  const docMode = el === document.body;
275
- // Context refs are @tokens containing a dot or slash (@notes.md, @src/x.js). Bare
276
- // @words are not refs: a leading one is an engine token (@fable, @codex — routed
277
- // helper-side), and mid-text ones are prose.
278
- // The @ has to start a word, so an email address or a URL is prose too.
279
- const contextRefs = [...comment.matchAll(/(?<![\w\u00C0-\u024F.@\/:-])@([\w.-]*[/.][\w./-]*)/g)]
280
- .map(m => m[1].replace(/\.+$/, '')) // a sentence's full stop is not part of the name
281
- .filter(ref => ref !== 'page')
282
- .filter(ref => /[/.]/.test(ref));
283
285
  const elementHTML = docMode ? strippedBodyHTML() : el.outerHTML;
284
286
  const payload = {
285
287
  id: 'req-' + Date.now().toString(36) + '-' + (++requestCounter),
@@ -287,7 +289,7 @@ function newSession(el, comment, quote) {
287
289
  tag: el.tagName.toLowerCase(),
288
290
  elementHTML,
289
291
  comment,
290
- contextRefs
292
+ contextRefs: contextRefsOf(comment)
291
293
  };
292
294
  if (quote) {
293
295
  payload.quote = quote.text;
@@ -295,7 +297,7 @@ function newSession(el, comment, quote) {
295
297
  }
296
298
  // Document mode reads the live page itself, and every other @page request reads
297
299
  // the file from disk: the plugin saves first, then asks for it by flag.
298
- if (!docMode && /(?<![\w\u00C0-\u024F.@\/:-])@page(?![\w\/-]|\.\w)/.test(comment)) payload.page = true;
300
+ if (!docMode && wantsPage(comment)) payload.page = true;
299
301
  return {
300
302
  id: payload.id,
301
303
  el,
@@ -312,7 +314,11 @@ function newSession(el, comment, quote) {
312
314
  }
313
315
 
314
316
  async function sendRequest(el, comment, quote) {
315
- session = newSession(el, comment, quote);
317
+ if (el.aiTarget) return sendTargetRequest(el, comment, quote);
318
+ // The comment conventions load here, where a request is sent, not at boot: a page
319
+ // whose composer is never typed into does not fetch them.
320
+ const { MAX_PAYLOAD_BYTES, contextRefsOf, wantsPage } = await import("../lib/ai-comment.js");
321
+ session = newSession(el, comment, quote, { contextRefsOf, wantsPage });
316
322
  // Refused here, before anything is sent or paused: the envelope would refuse it
317
323
  // anyway, and a refusal the page cannot explain is worse than one it can.
318
324
  if (new Blob([JSON.stringify(session.payload)]).size > MAX_PAYLOAD_BYTES) {
@@ -352,6 +358,71 @@ async function sendRequest(el, comment, quote) {
352
358
  else onError(outcome.error || 'the helper reported an error');
353
359
  }
354
360
 
361
+ // A page area's request. Nothing morphs and no save hold is taken, so the page keeps
362
+ // autosaving while a person types during a long request, and Keep is the target's own
363
+ // one undoable transaction. Only the bar and the panel belong to this plugin here.
364
+ async function sendTargetRequest(target, comment, quote) {
365
+ const live = {
366
+ id: null,
367
+ el: target,
368
+ target,
369
+ docMode: false,
370
+ state: 'requesting',
371
+ paused: false,
372
+ held: false,
373
+ handle: null,
374
+ finalCandidate: null
375
+ };
376
+ session = live;
377
+ // A refusal is the box's answer: the comment stays in it, unsent and still a draft.
378
+ let started;
379
+ try {
380
+ started = await target.start(comment, quote);
381
+ } catch (error) {
382
+ onError(error?.message || 'the helper reported an error');
383
+ return;
384
+ }
385
+ if (session !== live) return; // stopped while the request was being built
386
+ if (started.status !== 'ok') {
387
+ session = null;
388
+ setStatus(started.message, 'warn');
389
+ return;
390
+ }
391
+ live.id = started.id;
392
+ keepDraft(target, comment);
393
+ closePanelForSend();
394
+ barLifecycle = {};
395
+ showBar('working');
396
+ setBarStatus('Sending\u2026');
397
+ const handle = started.send({
398
+ onStatus: ({ text }) => { if (session === live) setBarStatus(text); }
399
+ });
400
+ live.handle = handle;
401
+ const outcome = await handle.done;
402
+ if (session !== live) return;
403
+ if (outcome.state !== 'done') {
404
+ if (outcome.state === 'cancelled') onError('HTML Clay stopped this edit.');
405
+ else onError(outcome.error || 'the helper reported an error');
406
+ return;
407
+ }
408
+ live.state = 'deciding';
409
+ let result;
410
+ try {
411
+ result = await target.propose(outcome.result || {});
412
+ } catch (error) {
413
+ onError(error?.message || 'the reply could not be applied');
414
+ return;
415
+ }
416
+ if (session !== live) return;
417
+ if (result.status !== 'ready') {
418
+ onError(result.message);
419
+ return;
420
+ }
421
+ dropDraft(target);
422
+ showBar('ready');
423
+ setBarStatus(readyText(result.warnings || [], (outcome.result || {}).model), result.warnings?.length ? 'warn' : '');
424
+ }
425
+
355
426
  // What a reply may not add: anything that runs code. Each part is counted, and the
356
427
  // reply is refused when it holds more of any part than the element the model was
357
428
  // given, so a page's own scripts and handlers are not a reason to refuse an edit
@@ -469,6 +540,11 @@ function revertSession() {
469
540
  const s = session;
470
541
  session = null;
471
542
  if (!s) return;
543
+ if (s.target) {
544
+ s.target.discard();
545
+ positionChrome();
546
+ return;
547
+ }
472
548
  try {
473
549
  if ((s.state === 'requesting' || s.state === 'deciding') && loadedEngine()) {
474
550
  sessionMorph(s, s.snapshot, { rewind: true });
@@ -484,6 +560,7 @@ async function keepSession() {
484
560
  const s = session;
485
561
  if (!s) return;
486
562
  session = null;
563
+ if (s.target) return keepTarget(s.target);
487
564
  // What is on screen is what Keep keeps: the preview may have been edited by hand.
488
565
  const liveResult = s.docMode ? strippedBodyHTML() : s.el.outerHTML;
489
566
  try {
@@ -509,6 +586,29 @@ async function keepSession() {
509
586
  scheduleBarClose();
510
587
  }
511
588
 
589
+ // The page area keeps itself: one transaction, its own preview cleared, and the save
590
+ // flushed by the surface. A stale result touches nothing and says so on the bar.
591
+ async function keepTarget(target) {
592
+ positionChrome();
593
+ showBar('saving');
594
+ setBarStatus('Saving\u2026');
595
+ const lifecycle = barLifecycle;
596
+ let result;
597
+ try {
598
+ result = await target.keep();
599
+ } catch (error) {
600
+ result = { status: 'failed', message: error?.message || SAVE_FAILED };
601
+ }
602
+ if (lifecycle !== barLifecycle) return;
603
+ if (!result || result.status !== 'kept') {
604
+ showBar('error');
605
+ setBarStatus((result && result.message) || SAVE_FAILED, 'warn');
606
+ return;
607
+ }
608
+ setBarStatus(result.message || 'Saved');
609
+ scheduleBarClose();
610
+ }
611
+
512
612
  // Stop is live from the moment the request starts, which includes the window where an
513
613
  // @page save is still in flight and there is no handle yet. Nothing is left on screen:
514
614
  // the edit is rewound and the bar goes with it.
@@ -613,7 +713,7 @@ function buildChrome() {
613
713
 
614
714
  barStop = part(bevelButton('Stop', { extra: ['flex:none'] }), 'bar-stop');
615
715
  barKeep = part(bevelButton('Keep', { variant: 'primary', extra: ['flex:none'] }), 'bar-keep');
616
- barRevert = part(bevelButton('Revert', { variant: 'quiet', extra: ['flex:none'] }), 'bar-revert');
716
+ barRevert = part(bevelButton('Discard', { variant: 'quiet', extra: ['flex:none'] }), 'bar-revert');
617
717
  barClose = part(bevelIconButton(GLYPHS.toastClose, { label: 'Dismiss error' }), 'bar-close');
618
718
  barClose.pin({ flex: 'none' });
619
719
  for (const control of [barStop, barKeep, barRevert, barClose]) setShown(control, false, 'inline-flex');
@@ -887,6 +987,10 @@ function closeBar(lifecycle) {
887
987
  // The ring follows the target while anything is on screen for it, which includes every
888
988
  // bar mode; the panel is placed only while it is the thing on screen.
889
989
  function positionChrome() {
990
+ // A Block document keeps a fixed bar of its own at the bottom on a phone, and the
991
+ // editor already reserves the room for it: the status bar sits above that.
992
+ const coarse = window.matchMedia?.('(pointer: coarse)')?.matches;
993
+ set(bar, 'bottom', pageAi()?.active() && coarse ? '112px' : '16px');
890
994
  if (!anchorEl) return;
891
995
  if (!anchorEl.isConnected) { abandonTarget(); return; }
892
996
  if (anchorEl === document.body) {
@@ -933,9 +1037,13 @@ function positionChrome() {
933
1037
  }
934
1038
 
935
1039
  function openPanel(el, quote) {
936
- // A composer already open on another target hands its text over as that target's draft
937
- // before anything here replaces the target or the box.
938
- if (!panel.hidden && anchorEl && anchorEl !== el) storeDraft();
1040
+ // A composer already open on the same target keeps its box as it is. On another target
1041
+ // its text travels with the person and the old target's draft is cleared.
1042
+ const wasOpen = !panel.hidden;
1043
+ const sameTarget = wasOpen && anchorEl === el;
1044
+ const changedTarget = wasOpen && !!anchorEl && anchorEl !== el;
1045
+ const carried = changedTarget ? textarea.value : null;
1046
+ if (changedTarget) dropDraft(anchorEl);
939
1047
  const range = quote ? liveRange() : null; // read before focus moves into the panel
940
1048
  const active = document.activeElement;
941
1049
  returnFocus = active && active !== document.body && !panel.contains(active) ? active : null;
@@ -944,10 +1052,12 @@ function openPanel(el, quote) {
944
1052
  hideChip();
945
1053
  setShown(panel, true);
946
1054
  pendingSelection = quote;
947
- const shown = quote ? (quote.text.trim().length > 400 ? quote.text.trim().slice(0, 400) + '\u2026' : quote.text.trim()) : '';
1055
+ const shown = quote
1056
+ ? (quote.text.trim().length > 400 ? quote.text.trim().slice(0, 400) + '\u2026' : quote.text.trim())
1057
+ : (el.aiTarget ? el.label : '');
948
1058
  panel.dataset.quote = shown;
949
1059
  setStatus('');
950
- textarea.value = drafts.get(el) || '';
1060
+ if (!sameTarget) textarea.value = carried ? carried : (drafts.get(el) || '');
951
1061
  highlight(anchorRange);
952
1062
  applyHelperState();
953
1063
  positionChrome();
@@ -975,6 +1085,7 @@ function closePanel() {
975
1085
  returnFocus = null;
976
1086
  pendingSelection = undefined;
977
1087
  clearHighlight();
1088
+ picker?.close();
978
1089
  setShown(panel, false);
979
1090
  setShown(ring, false);
980
1091
  textarea.value = '';
@@ -985,6 +1096,7 @@ function closePanel() {
985
1096
  // The send path closes the box without cancelling anything and without letting the
986
1097
  // target go: the request is now about that element, and the ring stays on it.
987
1098
  function closePanelForSend() {
1099
+ picker?.close();
988
1100
  anchorRange = null;
989
1101
  returnFocus = null;
990
1102
  pendingSelection = undefined;
@@ -1063,7 +1175,7 @@ function wireInteractions() {
1063
1175
  const startedInPanel = panelPointerDown;
1064
1176
  panelPointerDown = false;
1065
1177
  if (startedInPanel) return;
1066
- if (panel.contains(event.target) || bar.contains(event.target) || chip.contains(event.target) || docBubble.contains(event.target)) return;
1178
+ if (panel.contains(event.target) || bar.contains(event.target) || chip.contains(event.target) || docBubble.contains(event.target) || picker?.contains(event.target)) return;
1067
1179
  if (session || barMode) return; // one edit at a time, and the bar owns the screen
1068
1180
  const section = event.target.closest?.('[data-edit-id]');
1069
1181
  const unit = section ? unitFrom(event.target) : null;
@@ -1093,6 +1205,17 @@ function wireInteractions() {
1093
1205
  document.addEventListener('keydown', (event) => {
1094
1206
  if (isShortcut(event)) {
1095
1207
  if (barMode || session || pageOwnsFocus()) return;
1208
+ const ai = pageAi();
1209
+ if (ai?.active()) {
1210
+ const chosen = ai.targetFromSelection();
1211
+ // Nothing chosen: leave the key to the browser.
1212
+ if (!chosen) return;
1213
+ event.preventDefault();
1214
+ openPanel(chosen, chosen.quote);
1215
+ return;
1216
+ }
1217
+ // A Block document whose surface is not ready has no DOM path to fall back on.
1218
+ if (blockDocWithoutSurface()) return;
1096
1219
  if (anchorEl && !anchorEl.isConnected) closePanel();
1097
1220
  const range = liveRange();
1098
1221
  let target = range ? targetFromRange(range) : null;
@@ -1141,6 +1264,13 @@ function build() {
1141
1264
  wireInteractions();
1142
1265
  }
1143
1266
 
1267
+ // Whether this host lists an ai-edit helper at all, ready or turned off. The menu asks
1268
+ // before it offers Ask AI, so a page whose host cannot answer never shows the item.
1269
+ async function available() {
1270
+ await (boot || loadHelper());
1271
+ return helperState === 'ready' || helperState === 'unavailable';
1272
+ }
1273
+
1144
1274
  async function init() {
1145
1275
  if (!startsAtImport() || panel) return; // an owner/editor feature, built once
1146
1276
  if (!(await loadHelper())) return;
@@ -1164,18 +1294,72 @@ function canOpenTarget(el) {
1164
1294
  // are not valid targets. A listed but disabled helper opens the existing setup
1165
1295
  // explanation with Send unavailable, just like the keyboard shortcut.
1166
1296
  async function open(el, { prompt } = {}) {
1297
+ // A Block document is rewritten through its own surface: only the whole document
1298
+ // is a DOM entry point, and any element inside the editor is refused. This is the
1299
+ // guard that keeps the morph path off the editor.
1300
+ if (blockDocWithoutSurface()) return false;
1301
+ if (pageAi()?.active() && el !== document.body) return false;
1167
1302
  if (!canOpenTarget(el)) return false;
1168
1303
  await init();
1169
1304
  // A click listener that calls this runs its microtasks before the same click reaches
1170
1305
  // the click-away handler, so the opening has to wait for that event to finish.
1171
1306
  await new Promise(resolve => setTimeout(resolve, 0));
1172
- if (!panel || session || barMode || !canOpenTarget(el)) return false;
1173
- if (!panel.hidden && anchorEl === el) {
1307
+ if (!panel || session || barMode) return false;
1308
+ const ai = pageAi();
1309
+ const target = ai?.active() ? ai.wholePage() : el;
1310
+ if (target === el ? !canOpenTarget(el) : !target.isConnected) return false;
1311
+ if (!panel.hidden && anchorEl === target) {
1312
+ textarea.focus();
1313
+ return true;
1314
+ }
1315
+ if (typeof prompt === 'string' && !drafts.has(target)) keepDraft(target, prompt);
1316
+ openPanel(target, null);
1317
+ return true;
1318
+ }
1319
+
1320
+ // `clay.aiEdit.openTarget(target)` opens the composer on a target the page built
1321
+ // itself, such as a tool block's Revise or the new tool `/ai` asked for. Returns true
1322
+ // when the composer is showing for it.
1323
+ async function openTarget(target) {
1324
+ if (!target || target.aiTarget !== true) return false;
1325
+ await init();
1326
+ // The click or the key that asked is still travelling to the click-away handler, as
1327
+ // in open().
1328
+ await new Promise(resolve => setTimeout(resolve, 0));
1329
+ if (!panel || session || barMode) return false;
1330
+ if (!target.isConnected) return false;
1331
+ if (!panel.hidden && anchorEl === target) {
1174
1332
  textarea.focus();
1175
1333
  return true;
1176
1334
  }
1177
- if (typeof prompt === 'string' && !drafts.has(el)) keepDraft(el, prompt);
1178
- openPanel(el, null);
1335
+ openPanel(target, target.quote ?? null);
1336
+ return true;
1337
+ }
1338
+
1339
+ // `await clay.aiEdit.pick()` starts the area picker: the page under the pointer, one
1340
+ // click per block, and the composer on whatever is chosen. A Block document names its
1341
+ // own targets; anywhere else the picker hit-tests the page itself. Returns true when
1342
+ // the picker is up, false when there is nothing to pick with.
1343
+ async function pick() {
1344
+ await init();
1345
+ if (blockDocWithoutSurface()) return false;
1346
+ // The click that called this is still travelling to the click-away handler, exactly
1347
+ // as in open().
1348
+ await new Promise(resolve => setTimeout(resolve, 0));
1349
+ if (!panel || session || barMode) return false;
1350
+ if (picker?.picking) return true;
1351
+ const ai = pageAi();
1352
+ // The overlay loads here, where it is opened, not at boot. A failed fetch is the
1353
+ // same answer as nothing to pick with: the menu says so and nothing else changes.
1354
+ const mod = await import("./ai-edit/picker.js").catch(() => null);
1355
+ if (!mod) return false;
1356
+ picker = mod.createPicker({
1357
+ surface: ai?.active() ? ai : null,
1358
+ onPicked: (target) => openPanel(target, target.quote),
1359
+ onWholePage: () => openPanel(ai?.active() ? ai.wholePage() : document.body),
1360
+ onCancel: () => { if (!panel.hidden) closePanel(); },
1361
+ });
1362
+ picker.open();
1179
1363
  return true;
1180
1364
  }
1181
1365
 
@@ -1206,6 +1390,8 @@ function teardown() {
1206
1390
  listeners = null;
1207
1391
  for (const observer of observers) observer.disconnect();
1208
1392
  observers = [];
1393
+ picker?.close();
1394
+ picker = null;
1209
1395
  for (const node of [styleEl, ring, panel, bar, chip, docBubble]) node?.remove();
1210
1396
  styleEl = ring = panel = textarea = statusEl = pointer = bar = barStatus = null;
1211
1397
  barStop = barKeep = barRevert = barClose = chip = docBubble = null;
@@ -1231,7 +1417,7 @@ function teardown() {
1231
1417
  * the committed mode is still view; the commit that enters edit builds the chrome, inert
1232
1418
  * until somebody opens it, exactly as `clay.aiEdit.init()` does. What a session adds is
1233
1419
  * everything still unresolved — a request on the wire, a preview waiting for Keep or
1234
- * Revert, a comment typed and not sent — so `quiesce` refuses the exit and `resume`
1420
+ * Discard, a comment typed and not sent — so `quiesce` refuses the exit and `resume`
1235
1421
  * leaves that same preview, comment and selection timer exactly as they were.
1236
1422
  * `dispose` (after the barrier) stops every editor-only handler and takes the chrome
1237
1423
  * down, so a save or selection event in view draws nothing. `initial: true` adopts the
@@ -1249,10 +1435,10 @@ export function aiEditParticipant() {
1249
1435
  if (!initial) build();
1250
1436
  },
1251
1437
  quiesce() {
1252
- const unsent = (panel && !panel.hidden && textarea.value.trim() !== '') ||
1253
- [...drafted].some((el) => el.isConnected);
1438
+ const unsent = panel && !panel.hidden && textarea.value.trim() !== '';
1254
1439
  const pending = [];
1255
1440
  if (session) pending.push(session.id);
1441
+ if (picker?.picking) pending.push('picker');
1256
1442
  if (barMode === 'saving') pending.push('save');
1257
1443
  if (unsent) pending.push('prompt');
1258
1444
  if (!pending.length) return;
@@ -1276,4 +1462,4 @@ onDomReady(() => { boot = init().then(() => {}, () => {}); });
1276
1462
 
1277
1463
  export { aiEditParticipant as modeParticipant };
1278
1464
 
1279
- export const aiEdit = { init, open };
1465
+ export const aiEdit = { init, open, openTarget, pick, available };
@@ -3,7 +3,7 @@
3
3
  import { set, style } from "../lib/hostile-css.js";
4
4
  import { bevelSurface, pageScheme, paintLook } from "../ui/bevel-controls.js";
5
5
  import { TOKENS, FONT_SANS } from "../ui/bevel.js";
6
- import { saveFeedback } from "../core/save-feedback.js";
6
+ import { saveFeedback, FAILURE_CLAIMED } from "../core/save-feedback.js";
7
7
 
8
8
  // No 'conflict' here on purpose. core/conflict-notice.js owns that state now,
9
9
  // and it ships in every document rather than only the ones that turned this chip on.
@@ -12,25 +12,20 @@ import { saveFeedback } from "../core/save-feedback.js";
12
12
  // leaves core unaware that this plugin exists, which is the direction that
13
13
  // dependency has to point.
14
14
  const LABELS = {
15
- saving: "Saving…",
16
- saved: "Saved",
17
15
  error: "Couldn't save",
18
- offline: "Offline, not saved",
19
- retrying: "Saving…",
16
+ offline: "Offline, will retry",
20
17
  blocked: "Can't save here",
21
- pending: "Saving…",
22
- acknowledged: "Saving…",
23
- checkpointed: "Saved",
24
18
  unavailable: "Can't store changes: download a copy",
25
19
  };
26
20
 
27
- // States that stay on screen instead of fading: a save still in flight, one that keeps
28
- // trying, one the host will not take without help, and where the journal says the work is.
29
- const STICKY = new Set(["saving", "retrying", "blocked", "pending", "acknowledged", "unavailable"]);
21
+ // The chip has words only for a save that did not go through. A save on its way says
22
+ // nothing and leaves a standing failure up, and a save that landed takes the chip down.
23
+ // The block editor's retrying takes down a plain "Couldn't save" (it keeps trying on its
24
+ // own and raises its own notice if that runs long) but leaves "Offline, will retry" up,
25
+ // which is still true.
26
+ const QUIET = new Set(["saving", "pending", "acknowledged"]);
27
+ const CLEARS = new Set(["saved", "checkpointed"]);
30
28
 
31
- const ALARMING = new Set(["error", "offline", "blocked", "unavailable"]);
32
-
33
- const REST = [`background:${TOKENS.surface}`, `color:${TOKENS.ink}`, `border-color:${TOKENS["line-2"]}`];
34
29
  const ALARM = [
35
30
  `background:${TOKENS["ox-soft"]}`,
36
31
  `color:${TOKENS.ox}`,
@@ -38,7 +33,6 @@ const ALARM = [
38
33
  ];
39
34
 
40
35
  let el = null;
41
- let hideTimer = null;
42
36
 
43
37
  function ensure() {
44
38
  // Looked up on every call, never trusted once detached: a live-sync morph or a
@@ -57,35 +51,46 @@ function ensure() {
57
51
  return el;
58
52
  }
59
53
 
60
- export function show(state) {
54
+ function hide() {
55
+ if (!el) return;
56
+ set(el, "opacity", "0");
57
+ el.textContent = "";
58
+ delete el.dataset.state;
59
+ }
60
+
61
+ // Another notice is already saying this, so the chip says nothing: two save notices at once
62
+ // read as one problem reported twice.
63
+ export function standDown() {
64
+ hide();
65
+ }
66
+
67
+ export function show(state, detail) {
68
+ if (QUIET.has(state)) return;
69
+ if (CLEARS.has(state)) return hide();
70
+ if (state === "retrying") {
71
+ if (el?.dataset.state === "error") hide();
72
+ return;
73
+ }
61
74
  if (!LABELS[state]) return;
62
- // A failed or offline save is the persistent toast's to report when clay-ui is loaded,
63
- // and so is saved when the page turned on clay.saveToast. Blocked is the exception:
64
- // the page cannot save at all, so the chip stays even beside the generic toast.
65
- const ownedByToast = state !== "blocked" &&
66
- (ALARMING.has(state) || (state === "saved" && saveFeedback.savedToast));
67
- if (saveFeedback.toasts && ownedByToast) {
68
- clearTimeout(hideTimer);
69
- if (el) {
70
- set(el, "opacity", "0");
71
- el.textContent = "";
72
- delete el.dataset.state;
73
- }
75
+ // The block editor put its own notice up for this failure. Read here, after every
76
+ // listener has seen the event, so the order the two subscribed in does not matter.
77
+ if (state === "error" && detail?.[FAILURE_CLAIMED]) return;
78
+ // A failed or offline save is the persistent toast's to report when clay-ui is loaded.
79
+ // Blocked is the exception: the page cannot save at all, so the chip stays even beside
80
+ // the generic toast.
81
+ if (saveFeedback.toasts && state !== "blocked") {
82
+ hide();
74
83
  return;
75
84
  }
76
85
  const node = ensure();
77
86
  node.textContent = LABELS[state];
78
87
  node.dataset.state = state;
79
- style(node, ALARMING.has(state) ? ALARM : REST);
80
- paintLook(node, { background: ALARMING.has(state) ? null : TOKENS.surface });
88
+ style(node, ALARM);
89
+ paintLook(node, { background: null });
81
90
  set(node, "opacity", "1");
82
- clearTimeout(hideTimer);
83
- if (!STICKY.has(state)) hideTimer = setTimeout(() => { set(node, "opacity", "0"); }, 2200);
84
91
  }
85
92
 
86
93
  export function remove() {
87
- clearTimeout(hideTimer);
88
- hideTimer = null;
89
94
  el?.remove();
90
95
  el = null;
91
96
  }
@@ -7,8 +7,16 @@ const SAVE_EVENTS = ["saving", "saved", "error", "offline", "retrying", "blocked
7
7
  // The drawing arrives on demand; states that come before it are shown in order once it has.
8
8
  let chip = null;
9
9
  const loadChip = () => (chip ??= import("./indicator-chip.js"));
10
- const show = (state) => {
11
- loadChip().then((m) => { if (listeners) m.show(state); }, () => {});
10
+ const show = (state, detail) => {
11
+ loadChip().then((m) => { if (listeners) m.show(state, detail); }, () => {});
12
+ };
13
+
14
+ // The page editor puts up its own notice, with Download and Copy text, whenever it cannot
15
+ // save at all (plugins/page.js showBlocked and showFull), so the chip stands down for those.
16
+ const PAGE_NOTICED = new Set(["blocked", "unavailable"]);
17
+ const showPageState = (state) => {
18
+ if (!PAGE_NOTICED.has(state)) return show(state);
19
+ loadChip().then((m) => { if (listeners) m.standDown(); }, () => {});
12
20
  };
13
21
 
14
22
  let listeners = null;
@@ -25,9 +33,10 @@ function arm() {
25
33
  document.addEventListener(type, loadChip, { signal, once: true, capture: true });
26
34
  }
27
35
  for (const state of SAVE_EVENTS) {
28
- document.addEventListener("clay:save-" + state, () => show(state), { signal });
36
+ document.addEventListener("clay:save-" + state, (event) => show(state, event.detail), { signal });
29
37
  }
30
- document.addEventListener("clay:page-save-state", (event) => show(event.detail?.state), { signal });
38
+ document.addEventListener("clay:blockeditor-save-state", (event) => showPageState(event.detail?.state), { signal });
39
+ document.addEventListener("clay:save-status", (event) => show(event.detail?.state), { signal });
31
40
  }
32
41
 
33
42
  function disarm() {