@panphora/clayjs 1.15.2 → 1.16.0

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 (506) hide show
  1. package/README.md +4 -5
  2. package/THIRD-PARTY-NOTICES.md +16 -0
  3. package/compat/highlight-11.9.0.min.js +1213 -0
  4. package/compat/highlight-LICENSE +29 -0
  5. package/compat/journal-assets.lock.json +22 -0
  6. package/compat/overtype-2.6.1.min.js +116 -0
  7. package/compat/overtype-LICENSE +21 -0
  8. package/dist/all.js +90 -0
  9. package/dist/clay-dom.js +84 -0
  10. package/dist/clay-events.js +103 -0
  11. package/dist/clay-internals.js +85 -0
  12. package/dist/clay-options.js +93 -0
  13. package/dist/clay-router.js +98 -0
  14. package/dist/clay-ui.js +95 -0
  15. package/dist/clay-utils.js +84 -0
  16. package/dist/clay.js +146 -0
  17. package/dist/clay.standalone.js +83090 -58706
  18. package/dist/m/attrs/add-from-T5LJXFFI.js +1 -0
  19. package/dist/m/attrs/onaftersave-OKSWEJQF.js +1 -0
  20. package/dist/m/attrs/refetch-on-save-NWPPXOCZ.js +1 -0
  21. package/dist/m/attrs/save-freeze-FUP4Q4X3.js +1 -0
  22. package/dist/m/c-2AZDZWCK.js +1 -0
  23. package/dist/m/c-2JL6E75N.js +1 -0
  24. package/dist/m/c-2M73252O.js +1 -0
  25. package/dist/m/c-2SPVQSHC.js +1 -0
  26. package/dist/m/c-33HUYEDI.js +1 -0
  27. package/dist/m/c-33JJ6CBX.js +2 -0
  28. package/dist/m/c-35NBGMZB.js +1 -0
  29. package/dist/m/c-3GJ4SDE2.js +1 -0
  30. package/dist/m/c-3IQRECYS.js +1 -0
  31. package/dist/m/c-3OWDMNJR.js +2 -0
  32. package/dist/m/c-46XYM55L.js +1 -0
  33. package/dist/m/c-4B6H4G5V.js +1 -0
  34. package/dist/m/c-4DRMS4QG.js +9 -0
  35. package/dist/m/c-4FFHZM32.js +1 -0
  36. package/dist/m/c-4JZDZFF6.js +1 -0
  37. package/dist/m/c-4L3BTFRK.js +1 -0
  38. package/dist/m/c-4Q2XTQ2N.js +2 -0
  39. package/dist/m/c-4WKFKEKF.js +1 -0
  40. package/dist/m/c-4YSCRICO.js +1 -0
  41. package/dist/m/c-54RCDCBU.js +1 -0
  42. package/dist/m/c-5M4YEKCX.js +4 -0
  43. package/dist/m/c-5R4PTRG2.js +1 -0
  44. package/dist/m/c-5V3MOVJT.js +1 -0
  45. package/dist/m/c-5VYOWDTI.js +1 -0
  46. package/dist/m/c-5Y6TNX5W.js +34 -0
  47. package/dist/m/c-5YIHJ3TJ.js +1 -0
  48. package/dist/m/c-66O32MV3.js +28 -0
  49. package/dist/m/c-6HMB24EV.js +1 -0
  50. package/dist/m/c-7DJDKPGL.js +1 -0
  51. package/dist/m/c-7O6AFR65.js +1 -0
  52. package/dist/m/c-7SWMYKAI.js +1 -0
  53. package/dist/m/c-7VGZ7RU5.js +1 -0
  54. package/dist/m/c-A7LS5UZW.js +1 -0
  55. package/dist/m/c-ANQTJ3B5.js +2 -0
  56. package/dist/m/c-AQLFEATB.js +1 -0
  57. package/dist/m/c-AYVOREBE.js +1 -0
  58. package/dist/m/c-B2ZXPOS3.js +3 -0
  59. package/dist/m/c-BBVGTQG7.js +1 -0
  60. package/dist/m/c-BMIZKMPV.js +1 -0
  61. package/dist/m/c-BTDI4Y23.js +1 -0
  62. package/dist/m/c-BWFVSBDF.js +1 -0
  63. package/dist/m/c-BYTKL6UX.js +1 -0
  64. package/dist/m/c-CFHW5AMY.js +6 -0
  65. package/dist/m/c-CJZSLMVQ.js +1 -0
  66. package/dist/m/c-CYWLPPUS.js +1 -0
  67. package/dist/m/c-CZUSOB7V.js +1 -0
  68. package/dist/m/c-DBE6ORSI.js +1 -0
  69. package/dist/m/c-DCFSEJZG.js +1 -0
  70. package/dist/m/c-DNZ5IFM6.js +1 -0
  71. package/dist/m/c-EDHUR4MX.js +1 -0
  72. package/dist/m/c-EJTGYSLN.js +1 -0
  73. package/dist/m/c-EY6UOM54.js +1 -0
  74. package/dist/m/c-F3CKJTJS.js +1 -0
  75. package/dist/m/c-F7Y6FLPY.js +1 -0
  76. package/dist/m/c-FA4DZVTC.js +1 -0
  77. package/dist/m/c-FILLTJA7.js +1 -0
  78. package/dist/m/c-G2BUZITR.js +1 -0
  79. package/dist/m/c-GB3JTV6E.js +1 -0
  80. package/dist/m/c-GBGWXTX4.js +1 -0
  81. package/dist/m/c-GHBCCVG4.js +1 -0
  82. package/dist/m/c-GJAEGGXY.js +1 -0
  83. package/dist/m/c-GOWBDM6J.js +1 -0
  84. package/dist/m/c-GQ6F6IDK.js +1 -0
  85. package/dist/m/c-H4LG442Q.js +1 -0
  86. package/dist/m/c-HA6INT6J.js +1 -0
  87. package/dist/m/c-HPURYVZS.js +2 -0
  88. package/dist/m/c-HRDCD3WN.js +67 -0
  89. package/dist/m/c-HT33EQRN.js +1 -0
  90. package/dist/m/c-HVYXIS72.js +1 -0
  91. package/dist/m/c-IB52R237.js +1 -0
  92. package/dist/m/c-IJUK2GPW.js +1 -0
  93. package/dist/m/c-IKWNE4R4.js +1 -0
  94. package/dist/m/c-IU3IYEU6.js +1 -0
  95. package/dist/m/c-IZ2EBRAJ.js +1 -0
  96. package/dist/m/c-J7CA54IP.js +1 -0
  97. package/dist/m/c-JAX2667S.js +6 -0
  98. package/dist/m/c-KI7M4AY3.js +1 -0
  99. package/dist/m/c-KIPQJX4B.js +1 -0
  100. package/dist/m/c-KKWLDOPM.js +1 -0
  101. package/dist/m/c-KR4UW4KW.js +1 -0
  102. package/dist/m/c-KY3VJGUA.js +8 -0
  103. package/dist/m/c-L4WQSC27.js +1 -0
  104. package/dist/m/c-LFPHCXY3.js +1 -0
  105. package/dist/m/c-LR4CGEPT.js +91 -0
  106. package/dist/m/c-LRPUO2MX.js +1 -0
  107. package/dist/m/c-LTGMXXKO.js +1 -0
  108. package/dist/m/c-LXENEBV7.js +1 -0
  109. package/dist/m/c-LZJPNR2W.js +1 -0
  110. package/dist/m/c-MMUIUUZG.js +2 -0
  111. package/dist/m/c-MTQHWQC3.js +1 -0
  112. package/dist/m/c-N326R2JD.js +1 -0
  113. package/dist/m/c-N7JPUPGC.js +1 -0
  114. package/dist/m/c-NMJRXKPC.js +8 -0
  115. package/dist/m/c-NRPJEVJS.js +1 -0
  116. package/dist/m/c-NUHP2XDB.js +1 -0
  117. package/dist/m/c-NZMNXVF6.js +1 -0
  118. package/dist/m/c-OCSZ4QGN.js +1 -0
  119. package/dist/m/c-OIBUTDXI.js +1 -0
  120. package/dist/m/c-OIDRZKAF.js +1 -0
  121. package/dist/m/c-OYTFP5QN.js +1 -0
  122. package/dist/m/c-P7KNDG34.js +1 -0
  123. package/dist/m/c-PPOSCAQD.js +1 -0
  124. package/dist/m/c-PQ5LK6QE.js +993 -0
  125. package/dist/m/c-PZ4XEPUP.js +2 -0
  126. package/dist/m/c-QHIWXHRO.js +1 -0
  127. package/dist/m/c-QHWJ5ZIJ.js +1 -0
  128. package/dist/m/c-QIX5BJYN.js +1 -0
  129. package/dist/m/c-QNKREOL6.js +1 -0
  130. package/dist/m/c-QQHEX6HA.js +1 -0
  131. package/dist/m/c-R7RMANJI.js +9 -0
  132. package/dist/m/c-RI4NT75Y.js +1 -0
  133. package/dist/m/c-RM7J7BV2.js +1 -0
  134. package/dist/m/c-RUJKSUQW.js +1 -0
  135. package/dist/m/c-RVMWGXEJ.js +1 -0
  136. package/dist/m/c-S7H2DRCB.js +1 -0
  137. package/dist/m/c-S7LK4KUE.js +1 -0
  138. package/dist/m/c-SGCCC4FW.js +25 -0
  139. package/dist/m/c-SGIA4IEQ.js +188 -0
  140. package/dist/m/c-SWRJFCNN.js +1 -0
  141. package/dist/m/c-T4VO4OCB.js +1 -0
  142. package/dist/m/c-TCCA5BZN.js +2 -0
  143. package/dist/m/c-TQEMVJCS.js +1 -0
  144. package/dist/m/c-TQZ2JYHD.js +1 -0
  145. package/dist/m/c-TR6RMDQ7.js +3 -0
  146. package/dist/m/c-TRS6TM6O.js +1 -0
  147. package/dist/m/c-UGHH4MB3.js +1 -0
  148. package/dist/m/c-UKHTLWUW.js +1 -0
  149. package/dist/m/c-UL2YUY52.js +1 -0
  150. package/dist/m/c-UNN7LWCT.js +1 -0
  151. package/dist/m/c-UXBMRDZB.js +235 -0
  152. package/dist/m/c-V7PZZRG7.js +1 -0
  153. package/dist/m/c-VAK7SG37.js +1 -0
  154. package/dist/m/c-VDSS4K3Z.js +1 -0
  155. package/dist/m/c-VFX45IZQ.js +1 -0
  156. package/dist/m/c-VHNSBD2M.js +1 -0
  157. package/dist/m/c-VKEDWYDF.js +1 -0
  158. package/dist/m/c-VL52ADDR.js +1 -0
  159. package/dist/m/c-WN5AY3OZ.js +1 -0
  160. package/dist/m/c-WNYBLIOZ.js +1 -0
  161. package/dist/m/c-WRBKEJD4.js +1 -0
  162. package/dist/m/c-WWDBYXLK.js +1 -0
  163. package/dist/m/c-WYFDLITL.js +1 -0
  164. package/dist/m/c-XN6MBKI6.js +67 -0
  165. package/dist/m/c-XNNW2CWK.js +1 -0
  166. package/dist/m/c-XPELB6E5.js +1 -0
  167. package/dist/m/c-XQXZAO7N.js +1 -0
  168. package/dist/m/c-Y6USGTS5.js +5 -0
  169. package/dist/m/c-YDH7Q3NY.js +1 -0
  170. package/dist/m/c-YG2NJYS6.js +135 -0
  171. package/dist/m/c-Z36J63PP.js +1 -0
  172. package/dist/m/c-Z4VRC7GU.js +7 -0
  173. package/dist/m/c-Z6K52OLP.js +19 -0
  174. package/dist/m/c-ZDY2MDAA.js +21 -0
  175. package/dist/m/c-ZE46QDIH.js +1 -0
  176. package/dist/m/c-ZUCOKOHT.js +1 -0
  177. package/dist/m/core/admin-attrs-VGNLAUDK.js +1 -0
  178. package/dist/m/core/autosave-E4PEFPZ6.js +1 -0
  179. package/dist/m/core/conflict-notice-VQ6Y64PH.js +1 -0
  180. package/dist/m/core/draft-mode-WSQAT67S.js +1 -0
  181. package/dist/m/core/host-permission-XN3K2IW3.js +1 -0
  182. package/dist/m/core/is-edit-mode-JXHDVGUL.js +1 -0
  183. package/dist/m/core/persist-HKHA66IY.js +3 -0
  184. package/dist/m/core/recovery-TBBSICE2.js +1 -0
  185. package/dist/m/core/save-WUCHTQBE.js +1 -0
  186. package/dist/m/core/save-core-FJVHT5M3.js +1 -0
  187. package/dist/m/core/snapshot-F3SWDZMM.js +1 -0
  188. package/dist/m/core/unsaved-warning-STKBHE6G.js +1 -0
  189. package/dist/m/dom/all-APS5ZSKP.js +1 -0
  190. package/dist/m/dom/dom-helpers-L7OSR6S4.js +1 -0
  191. package/dist/m/events/index-23DPTUT4.js +1 -0
  192. package/dist/m/internals/index-WOVAP3RU.js +1 -0
  193. package/dist/m/lib/cache-bust-CGWT6ZJI.js +1 -0
  194. package/dist/m/lib/region-policy-SB2AWVG5.js +1 -0
  195. package/dist/m/loader-HXX6YNH5.js +1 -0
  196. package/dist/m/manifest-F22DEDAC.js +1 -0
  197. package/dist/m/manifest-edit-C3C7376B.js +1 -0
  198. package/dist/m/manifest.json +3491 -0
  199. package/dist/m/options/options-2LH76JQ3.js +1 -0
  200. package/dist/m/page/editor-XUTB2HB2.js +189 -0
  201. package/dist/m/plugins/ai-edit-ZQ4CGWIP.js +6 -0
  202. package/dist/m/plugins/bind-text-NX46N5UV.js +1 -0
  203. package/dist/m/plugins/cms-HP3LSTPN.js +2293 -0
  204. package/dist/m/plugins/demo-ZIBFAA3R.js +1 -0
  205. package/dist/m/plugins/derive-UAAA4V4S.js +1 -0
  206. package/dist/m/plugins/indicator-PV4YW2ID.js +1 -0
  207. package/dist/m/plugins/live-NUTTXSBS.js +1 -0
  208. package/dist/m/plugins/menu-HCKO3DSP.js +138 -0
  209. package/dist/m/plugins/message-LQ6EDH4V.js +1 -0
  210. package/dist/m/plugins/page-KSUH7G6J.js +2 -0
  211. package/dist/m/plugins/page-view-W73OW7WI.js +1 -0
  212. package/dist/m/plugins/people-AJJ32YCG.js +1 -0
  213. package/dist/m/plugins/quickcrop-QNY3PKLD.js +26 -0
  214. package/dist/m/plugins/quickcrop-QNY3PKLD.js.LEGAL.txt +7 -0
  215. package/dist/m/plugins/richclay-RC3L2F6X.js +517 -0
  216. package/dist/m/plugins/richclay-RC3L2F6X.js.LEGAL.txt +1 -0
  217. package/dist/m/plugins/sortable-YRMQX3ZU.js +1 -0
  218. package/dist/m/plugins/source-KHK5UUSK.js +1 -0
  219. package/dist/m/plugins/undo-OF2L3CXU.js +1 -0
  220. package/dist/m/plugins/unsent-HJFLQJ2O.js +1 -0
  221. package/dist/m/plugins/upgrade-ZNYODP5Y.js +1 -0
  222. package/dist/m/plugins/upload-XSGNILUY.js +1 -0
  223. package/dist/m/plugins/visitor-drafts-XK5GW55Q.js +21 -0
  224. package/dist/m/plugins/visitor-drafts-owner-KSSH75EV.js +1 -0
  225. package/dist/m/plugins/wire-ZWVWKF76.js +1 -0
  226. package/dist/m/reader-core-ILWLXVMP.js +68 -0
  227. package/dist/m/route/route-KOCZSEGN.js +1 -0
  228. package/dist/m/sync/live-sync-4AUVXVJU.js +1 -0
  229. package/dist/m/ui/index-EVHKMKBL.js +1 -0
  230. package/dist/m/utils/index-4TJCVLT7.js +1 -0
  231. package/dist/m/vendor/Sortable.vendor-A755ITLN.js +3 -0
  232. package/dist/m/vendor/Sortable.vendor-A755ITLN.js.LEGAL.txt +1 -0
  233. package/dist/m/vendor/sap-core.vendor-A7BMUYZT.js +6 -0
  234. package/dist/offline-manifest.json +3800 -0
  235. package/dist/page/themes/light-1.css +406 -0
  236. package/dist/server/core.cjs +23662 -22860
  237. package/dist/server/offline-runtime.cjs +4954 -0
  238. package/dist/server/offline-runtime.mjs +4923 -0
  239. package/dist/server/rooms.mjs +9 -0
  240. package/docs/guide.md +1078 -669
  241. package/entries/all.js +2 -0
  242. package/entries/clay-data.js +1 -1
  243. package/entries/clay-dom.js +2 -0
  244. package/entries/clay-events.js +2 -0
  245. package/entries/clay-internals.js +2 -0
  246. package/entries/clay-options.js +2 -0
  247. package/entries/clay-router.js +2 -0
  248. package/entries/clay-ui.js +2 -0
  249. package/entries/clay-utils.js +2 -0
  250. package/entries/clay.js +2 -0
  251. package/entries/sap.js +6 -6
  252. package/fonts/ibm-plex-mono-LICENSE +93 -0
  253. package/fonts/ibm-plex-mono-latin-300-normal.woff2 +0 -0
  254. package/fonts/ibm-plex-mono-latin-400-italic.woff2 +0 -0
  255. package/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
  256. package/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
  257. package/fonts/ibm-plex-mono-latin-600-normal.woff2 +0 -0
  258. package/fonts/ibm-plex-sans-LICENSE +93 -0
  259. package/fonts/ibm-plex-sans-latin-400-normal.woff2 +0 -0
  260. package/fonts/ibm-plex-sans-latin-500-normal.woff2 +0 -0
  261. package/fonts/ibm-plex-sans-latin-600-normal.woff2 +0 -0
  262. package/fonts/ibm-plex.css +71 -0
  263. package/fonts/inter-LICENSE +93 -0
  264. package/fonts/inter-cyrillic-ext-wght-normal.woff2 +0 -0
  265. package/fonts/inter-cyrillic-wght-normal.woff2 +0 -0
  266. package/fonts/inter-greek-ext-wght-normal.woff2 +0 -0
  267. package/fonts/inter-greek-wght-normal.woff2 +0 -0
  268. package/fonts/inter-latin-ext-wght-normal.woff2 +0 -0
  269. package/fonts/inter-latin-wght-normal.woff2 +0 -0
  270. package/fonts/inter-vietnamese-wght-normal.woff2 +0 -0
  271. package/fonts/inter.css +62 -0
  272. package/fonts/newsreader-LICENSE +93 -0
  273. package/fonts/newsreader-latin-ext-opsz-normal.woff2 +0 -0
  274. package/fonts/newsreader-latin-opsz-normal.woff2 +0 -0
  275. package/fonts/newsreader-vietnamese-opsz-normal.woff2 +0 -0
  276. package/fonts/newsreader.css +26 -0
  277. package/fonts/work-sans-LICENSE +93 -0
  278. package/fonts/work-sans-latin-wght-normal.woff2 +0 -0
  279. package/fonts/work-sans.css +8 -0
  280. package/package.json +20 -3
  281. package/packed-contract.json +1 -0
  282. package/src/attrs/add-from.js +27 -4
  283. package/src/attrs/save-freeze.js +7 -52
  284. package/src/core/admin-attrs.js +119 -12
  285. package/src/core/admin-contenteditable.js +12 -10
  286. package/src/core/admin-inputs.js +14 -15
  287. package/src/core/admin-onclick.js +2 -0
  288. package/src/core/autosave.js +74 -21
  289. package/src/core/conflict-download.js +22 -0
  290. package/src/core/conflict-notice-ui.js +1173 -0
  291. package/src/core/conflict-notice.js +140 -697
  292. package/src/core/edit-mode.js +147 -14
  293. package/src/core/etag.js +30 -12
  294. package/src/core/host-meta.js +72 -8
  295. package/src/core/host-permission.js +29 -0
  296. package/src/core/is-edit-mode.js +145 -12
  297. package/src/core/mode-key.js +125 -0
  298. package/src/core/mode-lifecycle.js +544 -0
  299. package/src/core/mode-receipt.js +30 -0
  300. package/src/core/mode-refusal.js +13 -0
  301. package/src/core/mode-resources.js +52 -0
  302. package/src/core/mode-support.js +82 -0
  303. package/src/core/mode-switch.js +259 -0
  304. package/src/core/mode-transition.js +112 -0
  305. package/src/core/page-data-write.js +305 -0
  306. package/src/core/page-data.js +26 -227
  307. package/src/core/page-format-wire.js +4 -0
  308. package/src/core/page-format.js +58 -0
  309. package/src/core/persist.js +37 -9
  310. package/src/core/preserve-authored.js +114 -0
  311. package/src/core/recovery.js +772 -0
  312. package/src/core/runtime-url.js +105 -0
  313. package/src/core/save-core.js +109 -12
  314. package/src/core/save-replace.js +72 -0
  315. package/src/core/save-size-notice.js +134 -0
  316. package/src/core/save-size.js +140 -0
  317. package/src/core/save.js +805 -118
  318. package/src/core/snapshot.js +121 -11
  319. package/src/core/source-map.js +157 -9
  320. package/src/core/unsaved-warning.js +14 -4
  321. package/src/core/upgrade-run.js +408 -0
  322. package/src/core/version.js +18 -0
  323. package/src/lib/assets.js +30 -0
  324. package/src/lib/autosave-debug-gate.js +50 -0
  325. package/src/lib/autosave-scheduler.js +141 -0
  326. package/src/lib/boot-imports.js +8 -0
  327. package/src/lib/derived.js +3 -1
  328. package/src/lib/dirty-gate.js +15 -0
  329. package/src/lib/doc-storage.js +33 -0
  330. package/src/lib/effect.js +9 -4
  331. package/src/lib/mutation.js +4 -2
  332. package/src/lib/offline-runtime.js +226 -0
  333. package/src/lib/preload.js +48 -0
  334. package/src/lib/root-attrs.js +1 -1
  335. package/src/lib/trash.js +18 -0
  336. package/src/lib/undo/capture-move.js +152 -0
  337. package/src/live/local-channel.js +61 -0
  338. package/src/live/local-lane.js +60 -9
  339. package/src/live/server-lane.js +6 -3
  340. package/src/loader-logic.js +76 -15
  341. package/src/loader.js +218 -30
  342. package/src/page/adopt-boot.js +76 -22
  343. package/src/page/adopt.js +25 -4
  344. package/src/page/awareness.js +230 -0
  345. package/src/page/blocks.js +79 -5
  346. package/src/page/blocksel.js +20 -4
  347. package/src/page/board-face.js +13 -0
  348. package/src/page/board-panel.js +5 -17
  349. package/src/page/board-style.js +2 -13
  350. package/src/page/board-view.js +1 -11
  351. package/src/page/board-views.js +17 -0
  352. package/src/page/board.js +2 -2
  353. package/src/page/boot-pack.js +26 -0
  354. package/src/page/boot.js +237 -0
  355. package/src/page/checklist.js +1 -1
  356. package/src/page/chrome.js +411 -61
  357. package/src/page/csv-import.js +17 -2
  358. package/src/page/document-title-dom.js +40 -0
  359. package/src/page/document-title-runtime.js +76 -0
  360. package/src/page/document-title.js +9 -0
  361. package/src/page/drag.js +48 -13
  362. package/src/page/drop.js +2 -1
  363. package/src/page/editor.js +330 -86
  364. package/src/page/feature-loader.js +72 -0
  365. package/src/page/features/board.js +12 -0
  366. package/src/page/features/code.js +13 -0
  367. package/src/page/features/media.js +290 -0
  368. package/src/page/features/registry.js +23 -0
  369. package/src/page/features/table.js +383 -0
  370. package/src/page/{tool-view.js → features/tool.js} +33 -2
  371. package/src/page/format-codec.js +215 -0
  372. package/src/page/format-gate.js +179 -0
  373. package/src/page/format-level.js +157 -0
  374. package/src/page/highlight-slot.js +23 -0
  375. package/src/page/highlight.js +30 -16
  376. package/src/page/history-intent.js +78 -0
  377. package/src/page/inline.js +18 -25
  378. package/src/page/items.js +52 -11
  379. package/src/page/link.js +1 -0
  380. package/src/page/markdown.js +86 -1
  381. package/src/page/media-views.js +3 -0
  382. package/src/page/media.js +33 -245
  383. package/src/page/outline-cache.js +11 -0
  384. package/src/page/placeholder.js +27 -7
  385. package/src/page/projection.js +196 -50
  386. package/src/page/reader-patch.js +306 -0
  387. package/src/page/recover.js +1 -1
  388. package/src/page/rich/page-tool.js +1 -4
  389. package/src/page/runtime-owner.js +27 -0
  390. package/src/page/save-projection.js +7 -4
  391. package/src/page/schema.js +14 -0
  392. package/src/page/table-rect-key.js +3 -0
  393. package/src/page/table-select.js +4 -3
  394. package/src/page/table-style.js +1 -1
  395. package/src/page/table-views.js +3 -0
  396. package/src/page/table.js +8 -293
  397. package/src/page/themes/light-1.css +139 -2
  398. package/src/page/themes/light-1.source.css +269 -0
  399. package/src/page/tool-capability.js +3 -0
  400. package/src/page/tool-views.js +3 -0
  401. package/src/page/view.js +390 -107
  402. package/src/page/ybind/awareness.js +172 -0
  403. package/src/page/ybind/binding.js +9 -1
  404. package/src/page/ybind/bridge.js +50 -47
  405. package/src/page/ybind/decode.js +52 -19
  406. package/src/page/ybind/doc.js +27 -2
  407. package/src/page/ybind/group.js +12 -3
  408. package/src/page/ybind/index.js +2 -0
  409. package/src/page/ybind/lineage.js +71 -0
  410. package/src/page/ybind/marks.js +25 -1
  411. package/src/page/ybind/migrate.js +48 -13
  412. package/src/page/ybind/moves.js +32 -21
  413. package/src/page/ybind/positions.js +50 -0
  414. package/src/page/ybind/provider.js +84 -22
  415. package/src/page/ybind/recovery-proof.js +191 -0
  416. package/src/page/ybind/recovery-replay.js +130 -0
  417. package/src/page/ybind/tokens.js +15 -6
  418. package/src/page/ybind/walk.js +10 -3
  419. package/src/page/ybind/writer.js +48 -4
  420. package/src/plugins/ai-edit.js +167 -32
  421. package/src/plugins/bind-text.js +218 -0
  422. package/src/plugins/cms.js +93 -0
  423. package/src/plugins/indicator.js +57 -5
  424. package/src/plugins/live.js +282 -34
  425. package/src/plugins/menu/claygoo.svg.js +1 -0
  426. package/src/plugins/menu/seo-head.js +323 -0
  427. package/src/plugins/menu/seo.js +849 -0
  428. package/src/plugins/menu.js +425 -0
  429. package/src/plugins/page-view.js +131 -0
  430. package/src/plugins/page.js +1022 -275
  431. package/src/plugins/people.js +158 -31
  432. package/src/plugins/richclay.js +119 -9
  433. package/src/plugins/sortable.js +370 -60
  434. package/src/plugins/source.js +189 -366
  435. package/src/plugins/undo.js +289 -19
  436. package/src/plugins/unsent.js +132 -0
  437. package/src/plugins/upgrade-run.js +1 -0
  438. package/src/plugins/upgrade.js +210 -0
  439. package/src/plugins/upload-task.js +571 -0
  440. package/src/plugins/upload-ui.js +265 -0
  441. package/src/plugins/upload.js +34 -153
  442. package/src/plugins/visitor-drafts/inert.js +32 -0
  443. package/src/plugins/visitor-drafts/owner-chip.js +3 -0
  444. package/src/plugins/visitor-drafts/prompt.js +2 -2
  445. package/src/plugins/visitor-drafts-owner.js +85 -0
  446. package/src/plugins/visitor-drafts.js +31 -42
  447. package/src/plugins/wire.js +82 -1
  448. package/src/reader-core.js +26 -0
  449. package/src/recovery/conflicts.js +287 -0
  450. package/src/recovery/identity.js +154 -0
  451. package/src/recovery/store.js +792 -0
  452. package/src/recovery/undo.js +257 -0
  453. package/src/server/checkpoint/index.js +68 -4
  454. package/src/server/offline-runtime/index.js +214 -0
  455. package/src/server/rooms/index.js +2 -1
  456. package/src/server/rooms/live-data-rooms.js +2 -2
  457. package/src/server/rooms/page-admission.js +33 -0
  458. package/src/server/rooms/page-lineage.js +56 -0
  459. package/src/server/rooms/page-rebase.js +11 -5
  460. package/src/server/rooms/page-state.js +2 -0
  461. package/src/server/rooms/page-write-admission.js +9 -3
  462. package/src/source/session.js +705 -0
  463. package/src/sync/conflict-revert.js +5 -4
  464. package/src/sync/conflicts-edit.js +119 -0
  465. package/src/sync/conflicts-facade.js +49 -0
  466. package/src/sync/conflicts.js +99 -1
  467. package/src/sync/editing-marker.js +37 -6
  468. package/src/sync/editing-sender.js +8 -1
  469. package/src/sync/live-sync-edit.js +63 -0
  470. package/src/sync/live-sync.js +364 -119
  471. package/src/sync/local-presence.js +187 -0
  472. package/src/sync/merge-declarations.js +10 -0
  473. package/src/sync/merge-engine.js +12 -0
  474. package/src/sync/presence-store.js +412 -0
  475. package/src/sync/presence-target.js +243 -0
  476. package/src/sync/presence.js +72 -41
  477. package/src/sync/saved-handoff.js +200 -0
  478. package/src/sync/saved-stream.js +535 -0
  479. package/src/sync/version-guard.js +92 -0
  480. package/src/sync/wire-profile.js +81 -0
  481. package/src/ui/bevel-controls.js +2 -8
  482. package/src/ui/page-scheme.js +7 -0
  483. package/src/ui/skins/cms.js +1 -1
  484. package/src/ui/theme-config.js +150 -0
  485. package/src/ui/theme.js +6 -138
  486. package/src/ui/toast-lazy.js +11 -0
  487. package/src/vendor/LICENSES-optional.md +253 -0
  488. package/src/vendor/highlight.vendor.js +3 -0
  489. package/src/vendor/hljs-bash.vendor.js +2 -0
  490. package/src/vendor/hljs-css.vendor.js +2 -0
  491. package/src/vendor/hljs-javascript.vendor.js +2 -0
  492. package/src/vendor/hljs-json.vendor.js +2 -0
  493. package/src/vendor/hljs-python.vendor.js +2 -0
  494. package/src/vendor/hljs-xml.vendor.js +2 -0
  495. package/src/vendor/hyper-html-api-data.vendor.js +1 -1
  496. package/src/vendor/hyper-html-api-upgrade.vendor.js +5 -0
  497. package/src/vendor/hyper-morph-identity.vendor.js +2 -0
  498. package/src/vendor/hyper-morph-json.vendor.js +3 -0
  499. package/src/vendor/hyper-morph.vendor.js +6 -6
  500. package/src/vendor/hyper-undo.vendor.js +1 -1
  501. package/src/vendor/hypercms.vendor.js +169 -84
  502. package/src/vendor/marked.vendor.js +68 -0
  503. package/src/vendor/overtype.vendor.js +994 -0
  504. package/src/vendor/richclay.vendor.js +17 -17
  505. package/src/vendor/sap-core.vendor.js +5 -5
  506. package/src/vendor/sap-ownership.vendor.js +11 -7
@@ -0,0 +1,30 @@
1
+ // One-shot proof of the mode this tab was in, written just before a reload and read
2
+ // once, at the top of is-edit-mode.js. It is a hint, like the cookie: the host still
3
+ // authorizes every save.
4
+ import { docKey } from "../lib/doc-storage.js";
5
+
6
+ const key = () => docKey("mode-receipt");
7
+ const MAX_AGE_MS = 30000;
8
+
9
+ function navigationType() {
10
+ try { return performance.getEntriesByType("navigation")[0]?.type ?? null; } catch { return null; }
11
+ }
12
+
13
+ export function takeReceipt() {
14
+ let raw = null;
15
+ try { raw = sessionStorage.getItem(key()); sessionStorage.removeItem(key()); } catch { return null; }
16
+ if (!raw) return null;
17
+ let receipt;
18
+ try { receipt = JSON.parse(raw); } catch { return null; }
19
+ if (receipt?.v !== 1 || receipt.path !== location.pathname) return null;
20
+ if (receipt.mode !== "edit" && receipt.mode !== "view") return null;
21
+ if (Date.now() - receipt.at > MAX_AGE_MS) return null;
22
+ if (!receipt.internal && navigationType() !== "reload") return null;
23
+ return receipt;
24
+ }
25
+
26
+ export function writeReceipt({ mode, canEdit, internal }) {
27
+ try {
28
+ sessionStorage.setItem(key(), JSON.stringify({ v: 1, path: location.pathname, mode, canEdit, at: Date.now(), internal }));
29
+ } catch {}
30
+ }
@@ -0,0 +1,13 @@
1
+ // What a refused mode change tells the person who asked. A request that was joined,
2
+ // superseded or dropped because the page moved on says nothing.
3
+ const TEXT = {
4
+ "permission-denied": "You don't have permission to edit this document.",
5
+ "save-failed": "Couldn't save, so this page stays in edit mode.",
6
+ "host-timeout": "The workspace didn't answer, so this page stays as it was.",
7
+ };
8
+ const QUIET = new Set(["busy", "cancelled"]);
9
+
10
+ export function modeRefusalText(result) {
11
+ if (!result || result.ok !== false || QUIET.has(result.code)) return null;
12
+ return TEXT[result.code] || result.message || "Couldn't switch modes.";
13
+ }
@@ -0,0 +1,52 @@
1
+ // Which authored resource a live switch can start and take back, decided before
2
+ // preparation rather than discovered halfway through it. Which edit module is supported
3
+ // is a separate table (mode-support.js). This module is a dynamic target: a reader never
4
+ // fetches it.
5
+ //
6
+ // A style or a stylesheet link is one attribute, and the existing type flip restores it.
7
+ // A script that has run cannot be un-run, so a document whose served bytes carry an
8
+ // `editmode:resource` script has no reversible entry: its script would have to execute
9
+ // again, and re-executing it on every entry is exactly what the reload path exists to
10
+ // avoid. Such a document keeps the guarded reload.
11
+ import { enableAdminResources, disableAdminResources } from "./admin-resources.js";
12
+
13
+ export const RESOURCE_SELECTOR = '[editmode\\:resource]:is(style, link, script)';
14
+ export const SCRIPT_SELECTOR = 'script[editmode\\:resource]';
15
+
16
+ export function resourceSupport(root = document) {
17
+ const scripts = [...root.querySelectorAll(SCRIPT_SELECTOR)];
18
+ return { reversible: scripts.length === 0, scripts };
19
+ }
20
+
21
+ // The adapter's lease over the authored resources: a reversible flip for the styles and
22
+ // stylesheets. A document whose script could not be taken back never reaches it, because
23
+ // the caller refuses that document before preparation.
24
+ export function resourcesLease(root = document) {
25
+ return {
26
+ enable() {
27
+ enableAdminResources(root);
28
+ },
29
+ quiesce() {},
30
+ resume() {},
31
+ dispose() {
32
+ disableAdminResources();
33
+ },
34
+ };
35
+ }
36
+
37
+ export function resourcesParticipant(root = document) {
38
+ return {
39
+ id: "clay:resources",
40
+ order: 100,
41
+ prepare() {
42
+ const { reversible } = resourceSupport(root);
43
+ if (!reversible) {
44
+ throw Object.assign(
45
+ new Error("clay.modeLifecycle: an editmode:resource script cannot be re-run"),
46
+ { code: "unsupported-plugin" }
47
+ );
48
+ }
49
+ return resourcesLease(root);
50
+ },
51
+ };
52
+ }
@@ -0,0 +1,82 @@
1
+ // Which resolved module a live switch can start and take back, keyed by the path the
2
+ // loader resolves. This module is data only — it imports nothing and evaluates nothing —
3
+ // and it is a dynamic target: a reader never fetches it.
4
+ //
5
+ // A path that is not a row here is unsupported, never assumed supported, so a plugin that
6
+ // arrives through an implication, a default, a `[sap]` root or template metadata is
7
+ // checked exactly like one the URL named. The caller takes the reload path before
8
+ // anything is prepared.
9
+ //
10
+ // "neutral" page-lifetime machinery that acts only under the committed mode or write
11
+ // admission, so a live entry can import it and leave it alone
12
+ // "export" the module's namespace exports `modeParticipant`, prepared as a lease
13
+ // "reader" the view plan's reader, owned by the transition rather than a lease
14
+ // function a reader-loaded module whose participant lives in its edit chunk:
15
+ // `(mod) => Promise<participant>`, so the reader never loads it
16
+ export const MODULE_SUPPORT = {
17
+ // The always wave: state mirrors, a key handler and the facades both modes read.
18
+ "lib/mutation.js": "neutral",
19
+ "core/preserve-authored.js": "neutral",
20
+ "core/edit-mode.js": "neutral",
21
+ "core/mode-key.js": "neutral",
22
+ "core/stale-host-notice.js": "neutral",
23
+ "core/page-data.js": "neutral",
24
+ // Snapshot, save core and the facades over them: functions and transforms read at
25
+ // capture and save time, never at evaluation.
26
+ "core/snapshot.js": "neutral",
27
+ "core/save-core.js": "neutral",
28
+ "core/recovery.js": "neutral",
29
+ "core/unsaved-warning.js": "neutral",
30
+ // These run on a save capture or after a saved event, which only a committed edit
31
+ // produces, and each handler rechecks the mode or admission at event time, so an old
32
+ // edit event is never replayed after an exit.
33
+ "attrs/onaftersave.js": "neutral",
34
+ "attrs/refetch-on-save.js": "neutral",
35
+ "lib/cache-bust.js": "neutral",
36
+ "attrs/save-freeze.js": "neutral",
37
+ "core/persist.js": "neutral",
38
+ "attrs/add-from.js": "neutral",
39
+ "core/etag.js": "neutral",
40
+ // The modules that keep a session and hand the controller a lease.
41
+ "core/save.js": "export",
42
+ "core/conflict-notice.js": "export",
43
+ "core/autosave.js": "export",
44
+ "core/admin-attrs.js": "export",
45
+ "plugins/indicator.js": "export",
46
+ "plugins/richclay.js": "export",
47
+ "plugins/sortable.js": "export",
48
+ "plugins/undo.js": "export",
49
+ "plugins/unsent.js": "export",
50
+ "plugins/upload.js": "export",
51
+ "plugins/people.js": "export",
52
+ "plugins/live.js": "export",
53
+ "plugins/page.js": "export",
54
+ "plugins/visitor-drafts-owner.js": "export",
55
+ // A view boot already loaded these three, and each reads the mode at event time.
56
+ "plugins/menu.js": "neutral",
57
+ "plugins/upgrade.js": "neutral",
58
+ "plugins/derive.js": "neutral",
59
+ // A binding has no import-time work, and its disposal belongs to the application's own
60
+ // participant.
61
+ "plugins/bind-text.js": "neutral",
62
+ // The view plan's reader, owned by the transition rather than a lease: the controller
63
+ // prepares no lease for a `"reader"` row.
64
+ "plugins/page-view.js": "reader",
65
+ // The source map fetches, pairs and installs a save renderer, so it is a session: the
66
+ // lease installs on a live entry and clears the renderer when the page goes back to view.
67
+ "plugins/source.js": "export",
68
+ // Generic sync: the module a reader and an editor both load, and the one whose
69
+ // session starts regardless of mode. Its participant is in the edit chunk.
70
+ "sync/live-sync.js": (mod) => mod.liveSync.editParticipant(),
71
+ // The channel to a local process: its lease holds admission for the edit requests it
72
+ // sends, so a live entry opens it at the commit and an exit closes it again.
73
+ "plugins/wire.js": "export",
74
+ // The AI composer: the lease builds the chrome on a live entry and takes it down again.
75
+ "plugins/ai-edit.js": "export",
76
+ // The CMS: the source owns the session, so the lease that opens it is the one that
77
+ // closes it.
78
+ "plugins/cms.js": "export",
79
+ };
80
+
81
+ export const supportFor = (path) =>
82
+ Object.prototype.hasOwnProperty.call(MODULE_SUPPORT, path) ? MODULE_SUPPORT[path] : null;
@@ -0,0 +1,259 @@
1
+ // The live switch's decision, and the one lease every session starts with.
2
+ //
3
+ // A document that signs `<html clay-mode-switch="live">` is saying its code can enter and
4
+ // leave edit repeatedly. That claim is checked here, before anything is prepared: every
5
+ // module either of its plans resolves must have a support row and nothing it authored may
6
+ // have to run again, or the document keeps the guarded reload. The same goes for a queued
7
+ // participant that is not one: a lease nobody can call is not a lease.
8
+ //
9
+ // The controller reaches this module, and only the controller: a reader pays for the
10
+ // declaration and nothing else.
11
+ import { peekModeParticipants, registerModeParticipant } from "./is-edit-mode.js";
12
+ import { resourceSupport, resourcesParticipant } from "./mode-resources.js";
13
+ import { supportFor } from "./mode-support.js";
14
+
15
+ let resourcesQueued = false;
16
+
17
+ // A participant queued before boot may be a promise; the controller awaits that one
18
+ // itself. Anything else has to have the shape a lease is prepared from, and its id has to
19
+ // be unique across the queue. Validating never drains: a malformed queue is left as it is.
20
+ const participantsReady = () => {
21
+ const ids = new Set();
22
+ for (const entry of peekModeParticipants()) {
23
+ if (typeof entry?.then === "function") continue;
24
+ const id = entry?.id;
25
+ if (typeof id !== "string" || typeof entry?.prepare !== "function") return false;
26
+ if (ids.has(id)) return false;
27
+ ids.add(id);
28
+ }
29
+ return true;
30
+ };
31
+
32
+ // Every module either road resolves: the edit plan with its own core wave, the plugins an
33
+ // implication, a default, a `[sap]` root or template metadata pulled in, and the plan this
34
+ // tab booted. Passing that whole set is the point — a plugin nobody named is checked
35
+ // exactly like one the URL did.
36
+ const resolvedPaths = (handle) => [
37
+ ...(handle?.edit?.core ?? []),
38
+ ...(handle?.edit?.plugins ?? []),
39
+ ...(handle?.edit?.late ?? []),
40
+ ...(handle?.view?.plugins ?? []),
41
+ ...(handle?.view?.late ?? []),
42
+ ];
43
+
44
+ /**
45
+ * Which of the modules this document resolves this release cannot start and take back.
46
+ *
47
+ * @param {{edit?: object, view?: object}} handle - both resolved plans
48
+ * @returns {string[]}
49
+ */
50
+ export function unsupportedPaths(handle) {
51
+ return [...new Set(resolvedPaths(handle))].filter((path) => supportFor(path) === null);
52
+ }
53
+
54
+ /**
55
+ * May this document switch live, through the plans it resolved?
56
+ *
57
+ * @param {{edit?: object, view?: object}} handle - both resolved plans
58
+ * @returns {boolean}
59
+ */
60
+ export function liveAllowed(handle) {
61
+ return participantsReady() && unsupportedPaths(handle).length === 0 && resourceSupport().reversible;
62
+ }
63
+
64
+ // The authored styles and stylesheets are the first lease of an entry: `liveAllowed` proved
65
+ // them reversible, so the entry flips them and the exit flips them back. Registered once,
66
+ // through the queue the dispatcher already holds, while the page is idle in view.
67
+ export function openResources() {
68
+ if (resourcesQueued) return;
69
+ resourcesQueued = true;
70
+ registerModeParticipant(resourcesParticipant());
71
+ }
72
+
73
+ // The abort an entry reports when its own deadline or a newer generation cancelled it:
74
+ // named as the platform names a cancelled import, so a caller can tell it apart from a
75
+ // module that failed.
76
+ const cancelled = () =>
77
+ Object.assign(new Error("clay.modeLifecycle: the import was cancelled"), { name: "AbortError", code: "aborted" });
78
+
79
+ // One deadline covers the whole transition, so the reader's half of it is bounded by the
80
+ // same number the controller uses for everything else.
81
+ const DEADLINE_MS = 15000;
82
+
83
+ const fault = (message, code) => Object.assign(new Error(`clayjs: ${message}`), { code });
84
+
85
+ // The promise, or the entry's cancellation, whichever settles first: an acquisition that
86
+ // never answers is a reload, not a session built on nothing.
87
+ const within = (promise, signal) =>
88
+ new Promise((resolve, reject) => {
89
+ if (signal?.aborted) return reject(cancelled());
90
+ const abort = () => reject(cancelled());
91
+ signal?.addEventListener?.("abort", abort);
92
+ Promise.resolve(promise).then(
93
+ (value) => {
94
+ signal?.removeEventListener?.("abort", abort);
95
+ resolve(value);
96
+ },
97
+ (err) => {
98
+ signal?.removeEventListener?.("abort", abort);
99
+ reject(err);
100
+ }
101
+ );
102
+ });
103
+
104
+ // A handoff answers with the file it was built from, never with a promise of one: a
105
+ // reader that represents no bytes has nothing a session can be built on.
106
+ const noFile = (handoff) => !handoff?.represented || handoff.represented.bytes === null;
107
+
108
+ // The instance inside a `sync/live-sync.js` namespace: the module exports the singleton
109
+ // under its own name and as the default, and both name the same object.
110
+ const syncLane = (mod) => (mod && (mod.liveSync ?? mod.default ?? mod)) || null;
111
+
112
+ /**
113
+ * Ask the reader a declared document already runs for its half of the switch: the file
114
+ * it represents, the queue it is holding, and the calls that apply, give back and retire
115
+ * them.
116
+ *
117
+ * The page-view reader answers first, because a Document's reader owns the article; a
118
+ * generic-sync document's saved lane answers next. A document with neither has no reader
119
+ * and needs no handoff. A reader that represents no file throws `not-acquired`, which is
120
+ * the reload: nothing has been prepared yet, so the page can still be replaced.
121
+ *
122
+ * @param {object} handle - the loader's handle: `{loaded, modules, attach, ...}`
123
+ * @param {{signal?: AbortSignal}} [context] - the entry's context
124
+ * @returns {Promise<?object>}
125
+ */
126
+ export async function prepareReader(handle, context = {}) {
127
+ const signal = context.signal;
128
+ const options = { signal, deadline: DEADLINE_MS };
129
+ const pageView = handle?.loaded?.["plugins/page-view.js"];
130
+ if (pageView?.ready) {
131
+ await within(pageView.acquired, signal);
132
+ const handoff = await pageView.prepareHandoff(options);
133
+ if (noFile(handoff)) throw fault("the reader has no file to hand over", "not-acquired");
134
+ return handoff;
135
+ }
136
+ const lane = syncLane(handle?.loaded?.["sync/live-sync.js"]);
137
+ if (lane && !lane.isDestroyed && lane.lane === "saved") {
138
+ const handoff = await lane.prepareHandoff(options);
139
+ if (noFile(handoff)) throw fault("the reader has no file to hand over", "not-acquired");
140
+ return handoff;
141
+ }
142
+ return null;
143
+ }
144
+
145
+ /**
146
+ * Start the reader a view holds, from the file the session left: the page-view reader
147
+ * paints that receipt at once and asks the host for nothing, and the saved lane starts
148
+ * with it, so a second entry has a file without waiting for a frame. Every reader the page
149
+ * ran before the entry starts again, so a page-view reader that follows generic sync's
150
+ * saved lane gets that lane back too. `accepted` null starts the reader as a view boot does.
151
+ *
152
+ * @param {object} handle - the loader's handle
153
+ * @param {?{bytes: string, etag: ?string, seq: ?number}} accepted - the receipt to start from
154
+ */
155
+ export function startReader(handle, accepted = null) {
156
+ const pageView = handle?.loaded?.["plugins/page-view.js"];
157
+ // The plan this page booted, not what an entry imported: a Document's edit plan
158
+ // carries generic sync as well, and the reader is restarted as the one it was.
159
+ const sync = Array.isArray(handle?.view?.plugins)
160
+ ? handle.view.plugins.includes("sync/live-sync.js")
161
+ : Boolean(handle?.loaded?.["sync/live-sync.js"]);
162
+ if (pageView) {
163
+ const start = pageView.restart ?? pageView.start;
164
+ start({ sync, represented: accepted });
165
+ // Every reader the page ran before the entry comes back, not the first one found:
166
+ // with `plugins=page,sync` the page-view reader follows generic sync's saved lane,
167
+ // so that lane is restarted too and the article keeps following other tabs.
168
+ if (!sync) return undefined;
169
+ }
170
+ const lane = syncLane(handle?.loaded?.["sync/live-sync.js"]);
171
+ if (lane) return lane.start(lane.currentFile, { lane: "saved", represented: accepted });
172
+ return undefined;
173
+ }
174
+
175
+ /**
176
+ * Import an edit plan's missing modules through the loader's own record and import cache.
177
+ *
178
+ * One path at a time, core then plugins then the late tail, in plan order: the same order
179
+ * boot imports them in, for the same reasons. A path boot already loaded is skipped, so
180
+ * nothing evaluates twice; a path in flight is awaited, so boot's late tail and an entry
181
+ * that overlap share one import; and a rejected thunk is dropped from the cache so the
182
+ * next attempt calls it again. It attaches the edit members once the core has landed and
183
+ * each plugin's member as that plugin lands, exactly as boot does, and it announces
184
+ * nothing: the plugin-ready events belong to a committed entry, not to an import.
185
+ *
186
+ * @param {object} handle - the loader's handle: `{loaded, modules, attach, attachEditCore, loadEditHints, late}`
187
+ * @param {{core?: string[], plugins?: string[], late?: string[]}} plan
188
+ * @param {{signal?: AbortSignal}} [options]
189
+ * @returns {Promise<string[]>} the plugin paths this call newly loaded
190
+ */
191
+ export async function importPlan(handle, plan, { signal } = {}) {
192
+ const inflight = (handle.inflight ||= new Map());
193
+ const newlyLoaded = [];
194
+
195
+ const stop = () => { if (signal?.aborted) throw cancelled(); };
196
+
197
+ // The loader's own hint fetch, which preloads what it returns against this plan. A failed
198
+ // fetch defers hints, never an import already mapped.
199
+ const hints = handle.loadEditHints?.();
200
+ if (hints) {
201
+ try {
202
+ await hints;
203
+ } catch {}
204
+ }
205
+
206
+ const importPath = (path) => {
207
+ if (!inflight.has(path)) {
208
+ const promise = Promise.resolve()
209
+ .then(() => handle.modules[path]())
210
+ .catch((err) => {
211
+ inflight.delete(path);
212
+ throw err;
213
+ });
214
+ inflight.set(path, promise);
215
+ }
216
+ return inflight.get(path);
217
+ };
218
+
219
+ for (const path of plan.core ?? []) {
220
+ if (handle.loaded[path]) continue;
221
+ stop();
222
+ const mod = await importPath(path);
223
+ handle.loaded[path] = mod;
224
+ mod.install?.();
225
+ }
226
+ // Once, after the core wave: the members those modules offer, added to the facade
227
+ // boot assembled rather than replacing it.
228
+ handle.attachEditCore(handle.loaded);
229
+
230
+ for (const path of plan.plugins ?? []) {
231
+ if (handle.loaded[path]) continue;
232
+ stop();
233
+ const mod = await importPath(path);
234
+ handle.loaded[path] = mod;
235
+ handle.attach(path, mod);
236
+ newlyLoaded.push(path);
237
+ }
238
+
239
+ const late = (plan.late ?? []).filter((path) => !handle.loaded[path]);
240
+ if (late.length) {
241
+ // Boot's tail may be importing these same paths right now, and it records them in the
242
+ // same table a moment after each import resolves. Waiting for it costs nothing an
243
+ // entry was not already waiting for, and it is what keeps one import per path.
244
+ stop();
245
+ try {
246
+ await handle.late;
247
+ } catch {}
248
+ for (const path of late) {
249
+ if (handle.loaded[path]) continue;
250
+ stop();
251
+ const mod = await importPath(path);
252
+ handle.loaded[path] = mod;
253
+ handle.attach(path, mod);
254
+ newlyLoaded.push(path);
255
+ }
256
+ }
257
+
258
+ return newlyLoaded;
259
+ }
@@ -0,0 +1,112 @@
1
+ // The save barrier: the one write an exit is allowed to make, and the pause around it.
2
+ //
3
+ // A switch has closed input admission before this runs, so an ordinary save cannot go
4
+ // out. The barrier is not an ordinary save: it carries a permit minted for the mode
5
+ // generation it is leaving, it fastens the work no save can write (registered models,
6
+ // uploads, a composer waiting on its person), it writes the page's bytes, and it answers
7
+ // only when the host has accepted bytes that include everything the page still holds.
8
+ //
9
+ // It also takes the document-operation slot, because a flush is only meaningful if
10
+ // nothing else replaces the document while it runs. A caller that already owns that slot
11
+ // (the mode controller's exit) hands its token in, so the barrier does not queue behind
12
+ // its own claim: a standalone call still claims one and releases it.
13
+ //
14
+ // This module is a dynamic target reached from the mode controller: a reader never
15
+ // fetches it.
16
+ import { claimDocumentOperation, releaseDocumentOperation, operationKind, saveFateIsUnknown } from "./save-core.js";
17
+ import { flushSave, isSaveConflicted, openSavePermit, closeSavePermit, saveGeneration } from "./save.js";
18
+ import { flushUnsaved, pendingIds } from "../lib/unsaved-state.js";
19
+ import { bytesUnsaved } from "./unsaved-warning.js";
20
+
21
+ const DEADLINE_MS = 15000;
22
+ const EXPIRED = Symbol("the barrier deadline passed");
23
+ // Two passes at most: the first writes, and a second only covers input that arrived
24
+ // while the first request was on the wire. Anything still unsaved after that refuses.
25
+ const MAX_PASSES = 2;
26
+
27
+ const refusal = (code, extra = {}) => ({ ok: false, code, ...extra });
28
+
29
+ // One wait of the barrier, bounded by its own deadline: a source whose flush never
30
+ // settles is the deadline's business, not an exit that never returns.
31
+ async function bounded(pending, deadline) {
32
+ let timer = null;
33
+ const expired = new Promise((resolve) => {
34
+ timer = setTimeout(() => resolve(EXPIRED), Math.max(0, deadline - Date.now()));
35
+ });
36
+ try {
37
+ return await Promise.race([Promise.resolve(pending), expired]);
38
+ } finally {
39
+ clearTimeout(timer);
40
+ }
41
+ }
42
+
43
+ export { claimDocumentOperation as claimOperation, releaseDocumentOperation as releaseOperation, operationKind };
44
+
45
+ /**
46
+ * Fasten everything this page holds that a save cannot write, then write its bytes.
47
+ *
48
+ * Answers `{ ok: true, state, etag }` only when the host accepted bytes that include
49
+ * every edit and every registered value on the page, under `generation`'s permit. Every
50
+ * other answer is `{ ok: false, code }` from the transition vocabulary: `busy`,
51
+ * `stale-document`, `pending-input`, `conflict`, `save-unknown`, `save-failed`,
52
+ * `timeout`. Nothing about the page's state moves on a refusal, and nothing is aborted:
53
+ * a write already on the wire keeps its own outcome.
54
+ *
55
+ * @param {Object} [options]
56
+ * @param {number} [options.generation] - the mode generation this exit is leaving
57
+ * @param {boolean} [options.keepalive]
58
+ * @param {number} [options.timeoutMs] - the whole barrier, the pending-source flush
59
+ * included, so a source that never settles refuses as `timeout`
60
+ * @param {Object} [options.operation] - the slot its caller already holds; claimed here when absent
61
+ * @returns {Promise<{ok: boolean, code?: string, state?: string, etag?: ?string, pending?: string[]}>}
62
+ */
63
+ export async function runSaveBarrier({ generation = saveGeneration(), keepalive = false, timeoutMs = DEADLINE_MS, operation = null } = {}) {
64
+ const claimed = operation ? null : claimDocumentOperation("mode");
65
+ if (!operation && !claimed) return refusal("busy");
66
+ const deadline = Date.now() + timeoutMs;
67
+ const permit = openSavePermit(generation);
68
+ try {
69
+ for (let pass = 0; ; pass++) {
70
+ if (generation !== saveGeneration()) return refusal("stale-document");
71
+ if (Date.now() >= deadline) return refusal("timeout");
72
+
73
+ // Work that finishes on its own — a checkpoint, a queued upload — is asked to. A
74
+ // composer waiting on its person has no flush and stays pending: that is a refusal,
75
+ // never a blur or a submit on their behalf.
76
+ const sources = await bounded(flushUnsaved(), deadline);
77
+ if (sources === EXPIRED) return refusal("timeout");
78
+ if (!sources.ok) return refusal("pending-input", { pending: sources.pending });
79
+ if (sources.pending.length) return refusal("pending-input", { pending: sources.pending });
80
+
81
+ // Neither a conflict nor an unresolved fate is a failure a retry fixes.
82
+ if (isSaveConflicted()) return refusal("conflict");
83
+ if (saveFateIsUnknown()) return refusal("save-unknown");
84
+
85
+ let result;
86
+ try {
87
+ result = await flushSave({ keepalive, timeoutMs: Math.max(0, deadline - Date.now()), permit });
88
+ } catch (err) {
89
+ if (err?.state === "conflict") return refusal("conflict");
90
+ if (err?.state === "blocked") return refusal("pending-input", { pending: pendingIds() });
91
+ if (saveFateIsUnknown()) return refusal("save-unknown");
92
+ if (Date.now() >= deadline) return refusal("timeout");
93
+ return refusal("save-failed");
94
+ }
95
+
96
+ if (result.state !== "clean" && result.state !== "saved") return refusal("save-failed");
97
+ // The host answered, but an answer is only an exit when nothing is left behind.
98
+ if (isSaveConflicted()) return refusal("conflict");
99
+ if (saveFateIsUnknown()) return refusal("save-unknown");
100
+ const pending = pendingIds();
101
+ if (pending.length) return refusal("pending-input", { pending });
102
+ if (bytesUnsaved()) {
103
+ if (pass + 1 < MAX_PASSES && Date.now() < deadline) continue;
104
+ return refusal("pending-input");
105
+ }
106
+ return { ok: true, state: result.state, etag: result.etag ?? null };
107
+ }
108
+ } finally {
109
+ closeSavePermit(permit);
110
+ if (claimed) releaseDocumentOperation(claimed);
111
+ }
112
+ }