@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
@@ -12,7 +12,7 @@ import { capturePageState, onSnapshot } from "../core/snapshot.js";
12
12
  import { onSaveAccepted, onSaveAttempt, onSaveOutcome, isSaveInProgress, saveFateIsUnknown, exitSave } from "../core/save-core.js";
13
13
  import { savePageForce, baselineSettled, isSaveConflicted, autosaveIsSuspended } from "../core/save.js";
14
14
  import { withKeepalive } from "../core/save-core.js";
15
- import { isEditMode } from "../core/is-edit-mode.js";
15
+ import { isEditMode, isDraftMode } from "../core/is-edit-mode.js";
16
16
  import { registerUnsavedState } from "../lib/unsaved-state.js";
17
17
  import { createCheckpoints } from "../live/checkpoint.js";
18
18
  import { parseJsonRelaxed } from "../vendor/hyper-morph-json.vendor.js";
@@ -687,10 +687,12 @@ function endDomPin(maybeLanded, ownerId = null) {
687
687
 
688
688
  // A capture that sent nothing is definitely unsent. Otherwise only a 4xx proves
689
689
  // the host wrote nothing, and only when no attempt timed out. A lost answer, a
690
- // timeout, a 5xx or a historical duplicate may have landed.
690
+ // timeout, a 5xx or a historical duplicate may have landed. A refusal from this
691
+ // browser's draft store wrote nothing anywhere.
691
692
  function mayHaveLanded(result, sent) {
693
+ if (result?.code === "draft-storage" || result?.code === "draft-not-ready") return false;
692
694
  const code = result?.code;
693
- const precommit = code === "page-converting" || code === "page-capacity";
695
+ const precommit = code === "blockeditor-converting" || code === "blockeditor-capacity";
694
696
  return code === "save-already-applied" || result?.hadTimeout === true ||
695
697
  (sent !== false && !precommit && !(result?.status >= 400 && result?.status < 500));
696
698
  }
@@ -765,6 +767,12 @@ function ensureLane() {
765
767
  laneGates();
766
768
  if (laneRequested || !(writerSession ? writerSession.admitted || writerSession.wantsLane : isEditMode)) return;
767
769
  laneRequested = true;
770
+ // A visitor's draft opens no lane: its tabs share the draft record, not a room.
771
+ if (isDraftMode) {
772
+ settleLane?.(null);
773
+ settleWriter?.(null);
774
+ return;
775
+ }
768
776
  const request = ++laneRequest;
769
777
  hostMeta.hostSupports("live-data").then(
770
778
  async (supported) => {
@@ -786,7 +794,7 @@ function ensureLane() {
786
794
  // never shares the lane a tab that cannot read it is on. The room scope
787
795
  // puts this tab's sequencer in the host-mode room, which a tab of the lane
788
796
  // before it never joins.
789
- local = lane.localTransport({ seed: seedLocalRoom, rooms: true, floor: document.querySelector('meta[name="clay-page-format"]')?.getAttribute("content") });
797
+ local = lane.localTransport({ seed: seedLocalRoom, rooms: true, floor: document.querySelector('meta[name="clay-blockeditor-format"]')?.getAttribute("content") });
790
798
  fast = lane.createFastLane(laneOptions(local));
791
799
  } else {
792
800
  settleLane?.(null);
@@ -853,7 +861,7 @@ function parseHtml(html) {
853
861
  function textOf(entry) {
854
862
  const fastMode = entry.store.handle.status.mode === "fast";
855
863
  const current = fastMode ? entry.store.authoritative : entry.store.value;
856
- const value = fastMode && /^page(\.[0-9]+)?$/.test(entry.store.name) ? captureYValue(current) : current;
864
+ const value = fastMode && /^blockeditor(\.[0-9]+)?$/.test(entry.store.name) ? captureYValue(current) : current;
857
865
  if (entry.layout === "lines") return serializeLines(value);
858
866
  return entry.layout === "pretty" ? JSON.stringify(value, null, 2).replace(/</g, "\\u003c") : serializeLive(value);
859
867
  }
@@ -903,12 +911,12 @@ function openEntry(name, store, { layout = "line", leftAt = null, staged = null
903
911
  if (event.code === "receipt-expired") document.dispatchEvent(new CustomEvent("clay:live-notice", {
904
912
  detail: { name, code: event.code, ops: event.ops, message: "The room retired an update receipt. Check the saved value before restoring this update." },
905
913
  }));
906
- if (event.phase === "refused" && event.code === "too-large" && !/^page(\.[0-9]+)?$/.test(name)) document.dispatchEvent(new CustomEvent("clay:live-notice", {
914
+ if (event.phase === "refused" && event.code === "too-large" && !/^blockeditor(\.[0-9]+)?$/.test(name)) document.dispatchEvent(new CustomEvent("clay:live-notice", {
907
915
  detail: { name, code: "op-too-large", message: "That change was too large to share, so it was undone." },
908
916
  }));
909
917
  // The room was opened from a document with no store of this name, so the
910
918
  // page it holds is not the one this tab is editing.
911
- if (event.phase === "left" && event.code === "no-store" && /^page(\.[0-9]+)?$/.test(name)) document.dispatchEvent(new CustomEvent("clay:live-notice", {
919
+ if (event.phase === "left" && event.code === "no-store" && /^blockeditor(\.[0-9]+)?$/.test(name)) document.dispatchEvent(new CustomEvent("clay:live-notice", {
912
920
  detail: { name, code: "unknown-store", message: `The room has no "${name}" store, so the page it holds is not the one this tab is editing.` },
913
921
  }));
914
922
  });
@@ -1003,7 +1011,7 @@ onSnapshot((clone, { forSave, requestRefresh }) => {
1003
1011
  // A staged store reaches the file here, where the boot would have put its tag, and
1004
1012
  // so does one that started that way, on every save after its first.
1005
1013
  if (!tag && (entry.staged || entry.tagless)) {
1006
- const article = clone.querySelector("article[data-page]");
1014
+ const article = clone.querySelector("article[data-blockeditor]");
1007
1015
  if (!article) continue;
1008
1016
  const element = article.ownerDocument.createElement("script");
1009
1017
  element.setAttribute("type", "application/json");
@@ -1124,7 +1132,7 @@ for (const type of ["clay:save-error", "clay:save-offline"]) {
1124
1132
  document.addEventListener(type, (event) => {
1125
1133
  const status = event.detail?.status;
1126
1134
  const code = event.detail?.code;
1127
- const precommit = code === "page-converting" || code === "page-capacity";
1135
+ const precommit = code === "blockeditor-converting" || code === "blockeditor-capacity";
1128
1136
  endDomPin(code === "save-already-applied" || event.detail?.hadTimeout === true ||
1129
1137
  (event.detail?.sent !== false && !precommit && !(status >= 400 && status < 500)), event.detail?.captureId);
1130
1138
  });
@@ -1233,7 +1241,7 @@ onSaveOutcome(({ captureId, result, attempts, sent }) => {
1233
1241
  if (!pin) return;
1234
1242
  const stagedEntry = [...pin.keys()].map((name) => entries.get(name)).find((entry) => entry?.staged);
1235
1243
  // Nothing was written: the staged store is clean and nothing follows.
1236
- if (stagedEntry && result.code === "page-nothing-to-write") {
1244
+ if (stagedEntry && result.code === "blockeditor-nothing-to-write") {
1237
1245
  stagedEntry.store.confirm(stagedEntry.store.mark);
1238
1246
  dropCapture(captureId);
1239
1247
  releaseBody();
@@ -1241,7 +1249,7 @@ onSaveOutcome(({ captureId, result, attempts, sent }) => {
1241
1249
  return;
1242
1250
  }
1243
1251
  // This tab's value does not descend from the stored document: never send it again.
1244
- if (stagedEntry && result.code === "page-lineage-stale") stagedEntry.staged.conflict = "conflict";
1252
+ if (stagedEntry && result.code === "blockeditor-lineage-stale") stagedEntry.staged.conflict = "conflict";
1245
1253
  dropCapture(captureId);
1246
1254
  const landed = mayHaveLanded(result, sent);
1247
1255
  const plan = landed ? historyPlan(captureId) : null;
@@ -1277,7 +1285,7 @@ function offerCheckpoint(captureId, html) {
1277
1285
  const state = capturePageState(captureId);
1278
1286
  if (!state) return;
1279
1287
  let article = null;
1280
- try { article = parseHtml(html).querySelector("article[data-page]")?.innerHTML ?? null; }
1288
+ try { article = parseHtml(html).querySelector("article[data-blockeditor]")?.innerHTML ?? null; }
1281
1289
  catch (_) { article = null; }
1282
1290
  const etag = lastSeenEtag();
1283
1291
  Promise.resolve()
@@ -1491,7 +1499,7 @@ export async function requestExitCheckpoint({ name, needsSave, permit, deadline,
1491
1499
  const result = await withKeepalive(keepalive, () => save(permit));
1492
1500
  if (over()) return failed("timeout");
1493
1501
  if (!result?.ok) return failed(result?.code ?? "checkpoint-failed");
1494
- return needsSave() ? failed("page-not-checkpointed") : { ok: true };
1502
+ return needsSave() ? failed("blockeditor-not-checkpointed") : { ok: true };
1495
1503
  }
1496
1504
  }
1497
1505
 
@@ -1,11 +1,14 @@
1
- // The menu's editing items: Undo and Redo, Ask AI, and Download a copy. Each one calls
2
- // the service that already does the job and keeps no second copy of its state.
1
+ // The menu's editing items: Undo and Redo, Ask AI to improve this page, and Download a
2
+ // copy. Each one calls the service that already does the job and keeps no second copy
3
+ // of its state.
3
4
  import { modeRefusalText } from "../../core/mode-refusal.js";
4
5
  import { downloadHtml, documentName } from "../../core/download.js";
5
6
  import { keys, revealLabel } from "./preferences.js";
7
+ import { hostMeta } from "../../core/host-meta.js";
6
8
 
7
9
  const RESUME_KEY = "menu-resume";
8
10
  const READY_MS = 5000;
11
+ const PAGE_READY_MS = 8000;
9
12
 
10
13
  const clay = () => window.clay || {};
11
14
  const session = () => clay().storage && clay().storage.session;
@@ -28,8 +31,31 @@ function whenPresent(get, ms = READY_MS) {
28
31
  });
29
32
  }
30
33
 
34
+ // A reload entry runs before the page session it reloaded into has finished preparing,
35
+ // so the surface it needs is not there yet. Waiting here is bounded: a page that never
36
+ // gets one ends at the same unavailable message a missing helper does.
37
+ function waitForPageAi(ms = PAGE_READY_MS) {
38
+ const page = clay().blockeditor;
39
+ if (!page) return Promise.resolve(true);
40
+ const start = Date.now();
41
+ return new Promise((resolve) => {
42
+ let done = false;
43
+ const finish = (ok) => { if (!done) { done = true; clearTimeout(timer); resolve(ok); } };
44
+ const tick = () => {
45
+ if (done) return;
46
+ if (clay().blockeditor?.ai) return finish(true);
47
+ if (Date.now() - start >= ms) return finish(false);
48
+ setTimeout(tick, 50);
49
+ };
50
+ const timer = setTimeout(() => finish(false), ms);
51
+ Promise.resolve(page.ready).catch(() => null).then(tick, tick);
52
+ });
53
+ }
54
+
31
55
  export function attachActions({ addItem, note, currentMode }) {
32
56
  const resumers = new Map();
57
+ // A receipt for an item that registers later, once its host has answered.
58
+ let pendingReceipt = null;
33
59
 
34
60
  // Runs `run` in edit mode, entering it first when the page is in view. A reload entry
35
61
  // leaves a one-shot receipt in session storage so the action runs once the page is
@@ -56,15 +82,24 @@ export function attachActions({ addItem, note, currentMode }) {
56
82
 
57
83
  function resumable(action, run) {
58
84
  resumers.set(action, run);
85
+ if (pendingReceipt === action) {
86
+ pendingReceipt = null;
87
+ if (currentMode() === "edit") run();
88
+ }
59
89
  return () => inEditMode(action, run);
60
90
  }
61
91
 
62
92
  let history = { canUndo: false, canRedo: false };
63
93
  let heard = false;
64
94
 
95
+ // A Document keeps its history in its own editor, so the pair reads the page's service
96
+ // there and hyper-undo everywhere else. Reading it at use time means an editor that
97
+ // closed after the item was drawn takes the press with it.
98
+ const historySource = () => (pageWants("blockeditor") && clay().blockeditor?.history ? clay().blockeditor.history : pageWants("undo") ? clay().undo : null);
99
+
65
100
  function renderUndo() {
66
- if (!pageWants("undo")) return;
67
- const live = currentMode() === "edit" && clay().undo;
101
+ if (!pageWants("blockeditor") && !pageWants("undo")) return;
102
+ const live = currentMode() === "edit" && historySource();
68
103
  // The menu loads late, so edits made before it are read from the service itself.
69
104
  if (!heard && live && typeof live.canUndo === "boolean") history = { canUndo: live.canUndo, canRedo: !!live.canRedo };
70
105
  addItem({
@@ -75,14 +110,14 @@ export function attachActions({ addItem, note, currentMode }) {
75
110
  label: "Undo",
76
111
  keys: keys("⌘Z", "Ctrl+Z"),
77
112
  disabled: !(live && history.canUndo),
78
- run: () => clay().undo?.undo(),
113
+ run: () => historySource()?.undo(),
79
114
  },
80
115
  {
81
116
  id: "clay.undo.redo",
82
117
  label: "Redo",
83
118
  keys: keys("⌘⇧Z", "Ctrl+Shift+Z"),
84
119
  disabled: !(live && history.canRedo),
85
- run: () => clay().undo?.redo(),
120
+ run: () => historySource()?.redo(),
86
121
  },
87
122
  ],
88
123
  });
@@ -97,17 +132,39 @@ export function attachActions({ addItem, note, currentMode }) {
97
132
  document.addEventListener("clay:modechange", renderUndo);
98
133
  renderUndo();
99
134
 
135
+ // Only a host that lists an ai-edit helper, ready or turned off, gets the item: on
136
+ // hyperclay.com someone with an edit link who is not listed would otherwise see an
137
+ // item that can only say it is unavailable. In view mode the plugin is not loaded
138
+ // yet, so there is nobody to ask and the item waits for the click, as it always has.
100
139
  if (pageWants("ai-edit")) {
101
- addItem({
102
- id: "clay.ai",
103
- label: "Ask AI…",
104
- keys: keys("⌘J", "Ctrl+J"),
105
- run: resumable("ai", async () => {
106
- const ai = await whenPresent(() => clay().aiEdit);
107
- const opened = ai ? await ai.open(document.body) : false;
108
- if (!opened) note("Ask AI isn't available on this page right now.");
109
- }),
110
- });
140
+ (async () => {
141
+ // ai-edit loads late, after the editor is ready, so it can attach after this menu.
142
+ // Ask for it where it lands, as the item's own run does, rather than where the
143
+ // menu boots: the item is drawn once there is a plugin to say what the host lists.
144
+ const loaded = clay().aiEdit || (currentMode() === "edit" ? await whenPresent(() => clay().aiEdit) : null);
145
+ if (loaded?.available) {
146
+ if (!(await loaded.available())) return;
147
+ } else if (!loaded && pageWants("blockeditor")) {
148
+ // In view mode a Block document has no plugin to ask, so the host's own
149
+ // discovery says whether an ai-edit helper exists at all.
150
+ const meta = await hostMeta();
151
+ const listed = Array.isArray(meta.document?.helpers) && meta.document.helpers.some(
152
+ (helper) => helper && helper.name === "ai-edit" && (helper.state === "ready" || helper.state === "unavailable"),
153
+ );
154
+ if (!listed) return;
155
+ }
156
+ addItem({
157
+ id: "clay.ai",
158
+ label: "Ask AI to improve this page",
159
+ keys: keys("⌘J", "Ctrl+J"),
160
+ run: resumable("ai", async () => {
161
+ const ai = await whenPresent(() => clay().aiEdit);
162
+ const ready = ai && (pageWants("blockeditor") || !!clay().blockeditor) ? await waitForPageAi() : !!ai;
163
+ const opened = ready ? await ai.pick() : false;
164
+ if (!opened) note("Ask AI isn't available on this page right now.");
165
+ }),
166
+ });
167
+ })();
111
168
  }
112
169
 
113
170
  if (pageWants("cms") && clay().cms?.hasRules?.(document)) {
@@ -127,6 +184,18 @@ export function attachActions({ addItem, note, currentMode }) {
127
184
  });
128
185
  }
129
186
 
187
+ // The Yjs page store (`blockeditor`, and its split parts `blockeditor.1`, `blockeditor.2`, …) holds upload paths
188
+ // inside base64 updates, where no text rewrite reaches them. A copy that leaves this host
189
+ // leaves those tags out, as Hyperclay's own download does: the article names every upload
190
+ // in full, and the editor that opens the copy seeds the page from it.
191
+ const PAGE_STORE_TAG = /<script\b[^>]*>[\s\S]*?<\/script>\n?/gi;
192
+ const isPageStoreTag = (tag) => {
193
+ const open = tag.slice(0, tag.indexOf(">") + 1);
194
+ return /\bmerge="blockeditor(?:\.\d+)?"/.test(open) && /\bclay="[^"]*\blive\b/.test(open);
195
+ };
196
+ const withoutPageStore = (html) =>
197
+ /<article\b[^>]*\bdata-blockeditor\b/.test(html) ? html.replace(PAGE_STORE_TAG, (tag) => (isPageStoreTag(tag) ? "" : tag)) : html;
198
+
130
199
  // The copy is what a save would send now, unsaved edits included. A reader has no
131
200
  // unsaved edits, so the saved file is the same document.
132
201
  addItem({
@@ -142,8 +211,10 @@ export function attachActions({ addItem, note, currentMode }) {
142
211
  if (response.ok) html = await response.text();
143
212
  }
144
213
  } catch {}
145
- // Upload paths only resolve on this host, so the copy names them in full.
214
+ // A copy that leaves this host keeps nothing that only resolves on it: the page store
215
+ // goes, and upload paths are named in full. A file: copy has no host to leave.
146
216
  if (typeof html === "string" && /^https?:$/.test(location.protocol)) {
217
+ html = withoutPageStore(html);
147
218
  html = html.replace(/(^|[^\w./:-])\/_\/uploads\//g, `$1${location.origin}/_/uploads/`);
148
219
  }
149
220
  if (typeof html !== "string" || !(await downloadHtml({ html, name: `${documentName()}.html` }))) {
@@ -190,6 +261,7 @@ export function attachActions({ addItem, note, currentMode }) {
190
261
  storage.remove(RESUME_KEY);
191
262
  const run = resumers.get(pending);
192
263
  if (run && currentMode() === "edit") run();
264
+ else if (!run) pendingReceipt = pending;
193
265
  }
194
266
 
195
267
  return { inEditMode, resumable, refresh: renderUndo };
@@ -5,6 +5,7 @@
5
5
  import { applyHead, FIELDS, readHead } from "./seo-head.js";
6
6
  import { lookAt } from "../../ui/page-colour.js";
7
7
  import MENU_CSS, { MENU_SURFACE } from "./style.js";
8
+ import { pageSkin } from "./shell.js";
8
9
  import { mountEditorUI } from "../../ui/editor-ui.js";
9
10
  import { deepActiveElement } from "../../dom/active-element.js";
10
11
  import { hostMeta } from "../../core/host-meta.js";
@@ -60,7 +61,7 @@ const SHEET_CSS = `.clay-seo { box-sizing: border-box; width: 100%; border-botto
60
61
  .clay-seo-count[data-over] { color: var(--bevel-ox); }
61
62
  .clay-seo-derived { padding: 0; border: 0; background: none; color: var(--bevel-brass); font: 500 12px/1.3 var(--bevel-font-sans); text-decoration: underline; text-decoration-color: var(--bevel-line-3); text-underline-offset: 2px; cursor: pointer; }
62
63
  .clay-seo-input { box-sizing: border-box; width: 100%; min-height: 32px; padding: 6px 9px; border: 1px solid var(--bevel-line-2); background: var(--bevel-ground); color: var(--bevel-ink); font: 400 13px/1.45 var(--bevel-font-sans); }
63
- textarea.clay-seo-input { min-height: 52px; resize: vertical; }
64
+ textarea.clay-seo-input { min-height: 52px; resize: none; overflow: hidden; }
64
65
  textarea.clay-seo-input.is-short { min-height: 32px; }
65
66
  .clay-seo-input::placeholder { color: var(--bevel-faint); opacity: 1; }
66
67
  .clay-seo-input:hover { border-color: var(--bevel-faint); }
@@ -115,6 +116,9 @@ textarea.clay-seo-input.is-short { min-height: 32px; }
115
116
  @media (pointer: coarse), (max-width: 680px) { .clay-seo .clay-menu-btn { min-height: 40px; } .clay-seo :is(input, textarea, select) { font-size: 16px; } }
116
117
  @media (prefers-reduced-motion: reduce) { .clay-seo { animation: none; transition: none; } }`;
117
118
 
119
+ // Fields on a skinned sheet sit on the page's own surface.
120
+ const SKINNED_SHEET = `.clay-seo[part~="dock"] { --bevel-ground: var(--bevel-surface); }`;
121
+
118
122
  const text = (value) => (value == null ? "" : String(value));
119
123
 
120
124
  function element(tag, className, content) {
@@ -176,7 +180,7 @@ function pageBase() {
176
180
  }
177
181
 
178
182
  function isDocumentPage() {
179
- return document.querySelectorAll("article[data-page]").length === 1;
183
+ return document.querySelectorAll("article[data-blockeditor]").length === 1;
180
184
  }
181
185
 
182
186
  const ATOM = "\uFFFC";
@@ -199,7 +203,7 @@ function titleItems(view) {
199
203
  }
200
204
  return [];
201
205
  }
202
- const article = document.querySelector("article[data-page]");
206
+ const article = document.querySelector("article[data-blockeditor]");
203
207
  const heading = article && [...article.children].find((node) => node.tagName === "H1");
204
208
  if (!heading) return [];
205
209
  let c = "";
@@ -217,7 +221,7 @@ function titleItems(view) {
217
221
  // The same rule the runtime and the checkpoint use: the first root H1, and Untitled
218
222
  // when it is empty or missing.
219
223
  function firstHeadingText() {
220
- const view = window.clay && window.clay.page && window.clay.page.view;
224
+ const view = window.clay && window.clay.blockeditor && window.clay.blockeditor.view;
221
225
  return documentTitle(titleItems(view));
222
226
  }
223
227
 
@@ -471,11 +475,25 @@ export function open({ returnFocus } = {}) {
471
475
  }
472
476
  }
473
477
 
478
+ // A description box is as tall as its text, or its placeholder while empty: no inner scroll
479
+ // (David, Nov 11).
480
+ function fitBoxes() {
481
+ for (const box of inputs.values()) {
482
+ if (box.localName !== "textarea" || !box.isConnected) continue;
483
+ const stand = !box.value && box.placeholder;
484
+ if (stand) box.value = box.placeholder;
485
+ box.style.height = "auto";
486
+ box.style.height = `${box.scrollHeight + box.offsetHeight - box.clientHeight}px`;
487
+ if (stand) box.value = "";
488
+ }
489
+ }
490
+
474
491
  function refresh() {
475
492
  const titleValue = text(readField("title")).trim();
476
493
  const descriptionValue = text(readField("description")).trim();
477
494
  inputs.get("ogTitle").placeholder = titleValue;
478
495
  inputs.get("ogDescription").placeholder = descriptionValue;
496
+ fitBoxes();
479
497
 
480
498
  for (const [key, entry] of counters) {
481
499
  const length = text(readField(key)).trim().length;
@@ -753,11 +771,19 @@ export function open({ returnFocus } = {}) {
753
771
  });
754
772
 
755
773
  document.addEventListener("clay:modechange", followMode);
774
+ // A page that skins its menu dresses this sheet the same way: its dock rule maps the page's own
775
+ // colours and fonts onto the sheet's tokens, so each look gets its own sheet.
776
+ const skin = pageSkin();
777
+ if (skin) root.setAttribute("part", "dock");
756
778
  lookAt(root, { x: 0, y: 0, width: innerWidth, height: Math.min(200, innerHeight) }, MENU_SURFACE);
757
- ui = mountEditorUI({ name: "seo", content: root, css: [MENU_CSS, SHEET_CSS], before: document.body.firstChild });
779
+ if (skin) root.style.removeProperty("--bevel-surface");
780
+ ui = mountEditorUI({ name: "seo", content: root, css: skin ? [MENU_CSS, SHEET_CSS, skin, SKINNED_SHEET] : [MENU_CSS, SHEET_CSS], before: document.body.firstChild });
758
781
  writeHeight();
759
782
  if (typeof ResizeObserver === "function") {
760
- heightObserver = new ResizeObserver(writeHeight);
783
+ heightObserver = new ResizeObserver(() => {
784
+ fitBoxes();
785
+ writeHeight();
786
+ });
761
787
  heightObserver.observe(root);
762
788
  }
763
789
  refresh();
@@ -2,7 +2,8 @@ export const MENU_SAFETY = `@layer clay-menu-safety {
2
2
  .clay-menu-dock [hidden], .clay-menu-spots[hidden], .clay-menu-dock[hidden] { display: none !important; }
3
3
  .clay-menu :focus-visible:not(.clay-menu-edit) { outline: 2px solid var(--bevel-brass) !important; outline-offset: -2px !important; }
4
4
  @media (pointer: coarse), (max-width: 680px) {
5
- .clay-menu-goo, .clay-menu-list [part~="dismiss"], .clay-menu-list [part~="step"], .clay-menu-list [role^="menuitem"] { min-width: 44px !important; min-height: 44px !important; }
5
+ /* A dismissible row's message and Dismiss sit inline on one line; their reach comes from MENU_TOUCH's pseudo-elements. */
6
+ .clay-menu-goo, .clay-menu-list [part~="step"], .clay-menu-list [role^="menuitem"]:not(.clay-menu-row > *) { min-width: 44px !important; min-height: 44px !important; }
6
7
  }
7
8
  @media (prefers-reduced-motion: reduce) {
8
9
  .clay-menu-dock *, .clay-menu-dock *::before, .clay-menu-dock *::after { transition: none !important; animation: none !important; }
@@ -78,13 +79,20 @@ export const SHELL_C = `/* Popovers: the menu, the people panel, the status note
78
79
  .clay-menu-list [role^="menuitem"] { box-sizing: border-box; display: flex; align-items: center; justify-content: space-between; gap: 12px; width: 100%; margin: 0; border: 0; background: none; text-align: left; text-decoration: none; cursor: pointer; }
79
80
  .clay-menu-list [role^="menuitem"]:hover:not([aria-disabled="true"]), .clay-menu-list [role^="menuitem"]:focus-visible { outline: none; background: var(--bevel-sunk); }
80
81
  .clay-menu-list [role^="menuitem"][aria-disabled="true"] { cursor: default; }
81
- .clay-menu-pair { display: flex; gap: 12px; padding: 3px 9px; }
82
+ .clay-menu-pair { display: flex; flex-wrap: wrap; gap: 12px; padding: 3px 9px; }
82
83
  .clay-menu-list .clay-menu-pair [role="menuitem"] { flex: none; width: auto; justify-content: flex-start; gap: 9px; }
83
84
  .clay-menu-list kbd { flex: none; white-space: nowrap; }
84
- .clay-menu-row { display: flex; align-items: center; gap: 2px; }
85
- .clay-menu-list .clay-menu-row > [part~="item"] { flex: 1; width: auto; min-width: 0; }
86
- .clay-menu-list .clay-menu-row > [part~="dismiss"] { flex: none; width: 28px; justify-content: center; padding: 0; }
87
- .clay-menu-row > [part~="dismiss"] svg { display: block; width: 10px; height: 10px; fill: currentColor; }
85
+ /* The message, then a small Dismiss right after its last word (David, Nov 11). Both are inline, so
86
+ Dismiss follows the text wherever it wraps; the message reaches over the whole row, and Dismiss
87
+ sits above it with its own reach. */
88
+ .clay-menu-row { position: relative; padding: 5px 9px; }
89
+ .clay-menu-row:has(> [part~="item"]:hover, > [part~="item"]:focus-visible) { background: var(--bevel-sunk); }
90
+ .clay-menu-list .clay-menu-row > [part~="item"], .clay-menu-list .clay-menu-row > [part~="item"]:is(:hover, :focus-visible) { display: inline; width: auto; padding: 0; background: none; }
91
+ .clay-menu-list .clay-menu-row > [part~="item"]::before { content: ""; position: absolute; inset: 0; }
92
+ .clay-menu-list .clay-menu-row > [part~="dismiss"], .clay-menu-list .clay-menu-row > [part~="dismiss"]:is(:hover, :focus-visible) { position: relative; z-index: 1; display: inline; width: auto; min-height: 0; margin-left: 6px; padding: 0; background: none; white-space: nowrap; }
93
+ .clay-menu-list .clay-menu-row > [part~="dismiss"]::after { content: ""; position: absolute; inset: -6px -6px; }
94
+ .clay-menu-row [part~="dismiss-label"] { display: inline-block; padding: 2px 7px; border-radius: 4px; background: var(--bevel-sunk); color: var(--bevel-ink-2); font: 500 11px/1.3 var(--bevel-font-sans); }
95
+ .clay-menu-row > [part~="dismiss"]:hover [part~="dismiss-label"], .clay-menu-row > [part~="dismiss"]:focus-visible [part~="dismiss-label"] { color: var(--bevel-ink); }
88
96
  .clay-menu-upgrade { display: inline-flex; align-items: center; gap: 8px; }
89
97
  .clay-menu-upgrade::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--bevel-brass); }
90
98
  .clay-menu-toggle .clay-menu-track { flex: none; }
@@ -125,12 +133,23 @@ export const MENU_SKIN = `.clay-menu { height: 34px; padding: 0 4px 0 2px; borde
125
133
  .clay-menu-list kbd { font: 500 11px/1 var(--bevel-font-mono); padding: 3px 5px; border: 1px solid var(--bevel-line-2); background: var(--bevel-sunk); color: var(--bevel-muted); }
126
134
  .clay-menu-list [part~="quiet"] { font-size: 12px; color: var(--bevel-muted); }
127
135
  .clay-menu-sep { height: 1px; margin: 4px 2px; background: var(--bevel-line); }
128
- .clay-menu-version { font: 400 11.5px/1.4 var(--bevel-font-mono); color: var(--bevel-muted); }
136
+ .clay-menu-version { font: 400 13px/1.35 var(--bevel-font-sans); color: var(--bevel-ink); }
129
137
  `;
130
138
 
131
139
  // Last, so no skin can shrink a touch target or bring motion back.
132
140
  export const MENU_TOUCH = `@media (pointer: coarse), (max-width: 680px) {
141
+ /* A phone menu fits the screen and never scrolls sideways: a little wider than on a desktop, long
142
+ labels wrap, and the shortcut keys a phone cannot press are hidden (David, Nov 11). */
143
+ .clay-menu-list { min-width: min(300px, calc(100vw - 16px)) !important; max-width: calc(100vw - 16px) !important; overflow-x: hidden !important; }
144
+ .clay-menu-list [role^="menuitem"], .clay-menu-list .clay-menu-version { white-space: normal; overflow-wrap: anywhere; }
145
+ .clay-menu-list kbd { display: none !important; }
146
+ .clay-menu-list .clay-menu-row { box-sizing: border-box; min-height: 44px; padding: 11px 9px; }
147
+ /* Dismiss sits inline after the message's last word, so its reach never goes up or left into the
148
+ message's own text: a taller label plus reach to the right and down makes the 44px. */
149
+ .clay-menu-row [part~="dismiss-label"] { padding: 7px 12px; font-size: 13px; }
150
+ .clay-menu-list .clay-menu-row > [part~="dismiss"]::after { inset: -2px -10px -11px 0; }
133
151
  .clay-menu-list [role^="menuitem"] { min-height: 44px; font-size: 15px; }
152
+ .clay-menu-list .clay-menu-version { display: flex; align-items: center; min-height: 44px; font-size: 15px; }
134
153
  .clay-menu-list .clay-menu-pair [role="menuitem"] { min-height: 44px; }
135
154
  .clay-menu-list .clay-menu-stepper [role="menuitem"] { width: 44px; min-height: 44px; }
136
155
  :is(.clay-menu-dock, .clay-seo) :is(input, textarea, select) { font-size: 16px; }
@@ -3,7 +3,7 @@ import { mountEditorUI } from "../../ui/editor-ui.js";
3
3
 
4
4
  const SKIN_VERSION = "1";
5
5
  const EDGE_GAP = 8;
6
- const DISMISS_ICON = '<svg viewBox="0 0 10 10" aria-hidden="true" focusable="false"><path d="M1 0 5 4l4-4 1 1-4 4 4 4-1 1-4-4-4 4-1-1 4-4-4-4z"/></svg>';
6
+ const DISMISS_TEXT = "Dismiss";
7
7
  const ARROW_PREV = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" aria-hidden="true" focusable="false"><path d="M224,128a8,8,0,0,1-8,8H59.31l58.35,58.34a8,8,0,0,1-11.32,11.32l-72-72a8,8,0,0,1,0-11.32l72-72a8,8,0,0,1,11.32,11.32L59.31,120H216A8,8,0,0,1,224,128Z"/></svg>';
8
8
  const ARROW_NEXT = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" aria-hidden="true" focusable="false"><path d="M221.66,133.66l-72,72a8,8,0,0,1-11.32-11.32L196.69,136H40a8,8,0,0,1,0-16H196.69L138.34,61.66a8,8,0,0,1,11.32-11.32l72,72A8,8,0,0,1,221.66,133.66Z"/></svg>';
9
9
  export const TRACK = '<span class="clay-menu-track" aria-hidden="true"><span class="clay-menu-thumb"></span></span>';
@@ -18,7 +18,7 @@ function findSlot() {
18
18
 
19
19
  // A skin the page wrote, in <style clay-menu-skin="1" media="not all">. The media keeps it
20
20
  // off the page itself. An unknown version means the default skin.
21
- function pageSkin() {
21
+ export function pageSkin() {
22
22
  const style = document.querySelector("style[clay-menu-skin]");
23
23
  if (!style) return null;
24
24
  if (style.getAttribute("clay-menu-skin") !== SKIN_VERSION) {
@@ -28,7 +28,8 @@ function pageSkin() {
28
28
  return style.textContent;
29
29
  }
30
30
 
31
- export function createShell({ capsuleStart = "", sections = [null] } = {}) {
31
+ // `icon` and `label` draw the button (the Claygoo by default); a slot's own icon and label still win.
32
+ export function createShell({ capsuleStart = "", sections = [null], icon = CLAYGOO, label = "Clay menu" } = {}) {
32
33
  const slotted = new Set(sections.flat().filter(Boolean));
33
34
  const slot = findSlot();
34
35
  const embedded = Boolean(slot);
@@ -43,9 +44,9 @@ export function createShell({ capsuleStart = "", sections = [null] } = {}) {
43
44
  root.innerHTML =
44
45
  `<div class="clay-menu" part="capsule" clay="editor-ui">` +
45
46
  capsuleStart +
46
- `<button type="button" class="clay-menu-goo" part="button" aria-haspopup="menu" aria-expanded="false" aria-label="Clay menu">${CLAYGOO}</button>` +
47
+ `<button type="button" class="clay-menu-goo" part="button" aria-haspopup="menu" aria-expanded="false" aria-label="${label}">${icon}</button>` +
47
48
  `</div>` +
48
- `<div class="clay-menu-list" part="list" role="menu" aria-label="Clay menu" hidden></div>` +
49
+ `<div class="clay-menu-list" part="list" role="menu" aria-label="${label}" hidden></div>` +
49
50
  `<div class="clay-menu-note" part="note" role="status" hidden></div>`;
50
51
 
51
52
  const goo = root.querySelector(".clay-menu-goo");
@@ -177,6 +178,8 @@ export function createShell({ capsuleStart = "", sections = [null] } = {}) {
177
178
  return itemButton(item);
178
179
  }
179
180
 
181
+ // The item, then a small Dismiss right after its last word. An x at the row's end read as the
182
+ // close for the whole menu. The item's own dismiss label stays as the button's title.
180
183
  function dismissibleRow(item) {
181
184
  const row = document.createElement("div");
182
185
  row.className = "clay-menu-row";
@@ -186,9 +189,11 @@ export function createShell({ capsuleStart = "", sections = [null] } = {}) {
186
189
  dismiss.setAttribute("role", "menuitem");
187
190
  dismiss.setAttribute("part", "dismiss");
188
191
  dismiss.dataset.menuId = `${item.id}.dismiss`;
189
- dismiss.setAttribute("aria-label", item.dismiss.label);
190
192
  dismiss.title = item.dismiss.label;
191
- dismiss.innerHTML = DISMISS_ICON;
193
+ const text = document.createElement("span");
194
+ text.setAttribute("part", "dismiss-label");
195
+ text.textContent = DISMISS_TEXT;
196
+ dismiss.append(text);
192
197
  dismiss.addEventListener("click", async () => {
193
198
  dismiss.disabled = true;
194
199
  let result;
@@ -10,7 +10,6 @@ import { OFF, labelOf, looks, nameOf, paintLook, recordLook } from "./looks.js";
10
10
  export { looks };
11
11
 
12
12
  const RECEIPT = "variants-choose";
13
- const KEEP_ID = "clay.variants.keep";
14
13
  const CHOOSE_LABEL = "Change the page look";
15
14
  const KEEP_LABEL = "Keep one page look";
16
15
 
@@ -234,22 +233,23 @@ export function attachVariants({ root, capsule, addItem, removeItem, note, curre
234
233
  pill = null;
235
234
  removeItem("clay.look");
236
235
  removeItem("clay.look.use");
237
- removeItem(KEEP_ID);
238
236
  return;
239
237
  }
240
238
  if (root.hasAttribute("data-embedded")) {
241
239
  const name = shown();
240
+ // "Theme: Dusk" and two arrows. While editing, each step is the choice and saves like any
241
+ // edit: no confirm, no Keep row (David, Nov 11).
242
242
  if (wantsRow) {
243
243
  addItem({
244
244
  id: "clay.look",
245
245
  section: "start",
246
246
  stepper: {
247
- label: "Switch look",
248
- value: `${labelFor(name)} ${all.indexOf(name) + 1}/${all.length}`,
247
+ label: "Theme:",
248
+ value: labelFor(name),
249
249
  prev: () => step(-1),
250
250
  next: () => step(1),
251
- prevLabel: "Previous look",
252
- nextLabel: "Next look",
251
+ prevLabel: "Previous theme",
252
+ nextLabel: "Next theme",
253
253
  },
254
254
  });
255
255
  } else {
@@ -257,8 +257,6 @@ export function attachVariants({ root, capsule, addItem, removeItem, note, curre
257
257
  }
258
258
  if (!isEdit() && previewing !== null) addItem({ id: "clay.look.use", section: "start", label: "Use this look", run: useShown });
259
259
  else removeItem("clay.look.use");
260
- if (isEdit()) addItem({ id: KEEP_ID, label: `Keep ${labelFor(savedName())}, remove the others…`, run: () => keep() });
261
- else removeItem(KEEP_ID);
262
260
  return;
263
261
  }
264
262
  if (!pill) pill = buildPill();
@@ -271,8 +269,6 @@ export function attachVariants({ root, capsule, addItem, removeItem, note, curre
271
269
  label.append(count);
272
270
  pill.querySelector(".clay-variants-use").hidden = isEdit() || previewing === null;
273
271
  pill.querySelector(".clay-variants-keep").hidden = !isEdit();
274
- if (isEdit()) addItem({ id: KEEP_ID, label: `Keep ${labelFor(savedName())}, remove the others…`, run: () => keep() });
275
- else removeItem(KEEP_ID);
276
272
  }
277
273
 
278
274
  function refresh() {