@panphora/clayjs 1.15.1 → 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,191 @@
1
+ /**
2
+ * recovery-proof.js — what a checkpoint covers, and what makes a proof a proof.
3
+ *
4
+ * A checkpoint covers a journaled record only when merging that record's owned bytes
5
+ * into the accepted page state changes neither an insertion nor a deletion. A state
6
+ * vector cannot answer that: inserting `abc` and then deleting `b` leaves the state
7
+ * vector where it was while the bytes carry the deletion. So coverage here is a byte
8
+ * comparison, it is sufficient but not necessary, and malformed input is an error that
9
+ * keeps the record rather than retiring it.
10
+ *
11
+ * A proof is made by the save integration from the capture it saved under and the bytes
12
+ * it read back. No room acknowledgement, `requestCheckpoint()` success, state vector or
13
+ * ETag order becomes a proof, and nothing here asks a host anything: the accepted page
14
+ * state and the receipt arrive in the proof itself.
15
+ */
16
+
17
+ import { encodeStateAsUpdateV2, mergeUpdatesV2 } from "../../vendor/yjs.vendor.js";
18
+ import { docFromValue } from "./provider.js";
19
+
20
+ export class PageProofError extends Error {
21
+ constructor(code, message) {
22
+ super(message);
23
+ this.name = "PageProofError";
24
+ this.code = code;
25
+ }
26
+ }
27
+
28
+ const fail = (code, message) => { throw new PageProofError(code, message); };
29
+ const HEX = /^[0-9a-f]{64}$/;
30
+ const encoder = typeof TextEncoder === "function" ? new TextEncoder() : null;
31
+ const bytesOf = (value) => {
32
+ if (value instanceof Uint8Array) return value;
33
+ if (value instanceof ArrayBuffer) return new Uint8Array(value);
34
+ return null;
35
+ };
36
+ const identityKey = (id) => (typeof id === "string" ? id : JSON.stringify(id));
37
+ const copyId = (id) => (Array.isArray(id) ? [...id] : id);
38
+ const copyState = (state) => ({ v: state.v, g: state.g, y: { ...(state.y ?? {}) } });
39
+
40
+ export const equalBytes = (a, b) => a.length === b.length && a.every((value, index) => value === b[index]);
41
+
42
+ /**
43
+ * Whether merging `update` into `checkpoint` adds nothing: no insertion, no deletion.
44
+ *
45
+ * @param {Uint8Array} checkpoint - canonical or merged Yjs V2 update bytes
46
+ * @param {Uint8Array} update - the bytes to look for
47
+ * @param {Object} [Yjs] - the vendored Yjs module
48
+ * @returns {boolean}
49
+ */
50
+ export function containsUpdate(checkpoint, update, Yjs = null) {
51
+ const merge = Yjs?.mergeUpdatesV2 ?? mergeUpdatesV2;
52
+ const canonical = merge([checkpoint]);
53
+ return equalBytes(canonical, merge([canonical, update]));
54
+ }
55
+
56
+ export async function digestHex(bytes) {
57
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
58
+ return [...new Uint8Array(digest)].map((value) => value.toString(16).padStart(2, "0")).join("");
59
+ }
60
+
61
+ /**
62
+ * The accepted page state as one canonical update, decoded strictly.
63
+ *
64
+ * Pending structs, missing part and a delete set naming an unknown item all refuse:
65
+ * a checkpoint nobody can decode proves nothing about a journaled record.
66
+ *
67
+ * @param {Object} state - a version 1 page value
68
+ * @returns {Uint8Array}
69
+ */
70
+ export function canonicalPageBytes(state) {
71
+ let doc;
72
+ try {
73
+ doc = docFromValue(state, { strict: true });
74
+ } catch (error) {
75
+ fail("invalid-state", `The accepted page state does not decode: ${error.message}`);
76
+ }
77
+ try {
78
+ return encodeStateAsUpdateV2(doc);
79
+ } finally {
80
+ doc.destroy();
81
+ }
82
+ }
83
+
84
+ // The same answer `sameAuthority` gives in recovery/identity.js, written here because the
85
+ // edit bundle already pays for this module through the provider: importing the generic
86
+ // recovery layer from the page proof would split it into a chunk every editor load pays.
87
+ const sameAuthority = (a, b) => Boolean(a && b) && Boolean(a.host && a.documentId && a.principalId) && a.host === b.host && a.documentId === b.documentId && a.principalId === b.principalId;
88
+ const sameGeneration = (a, b) => Boolean(a && b) && typeof a.generation === "string" && Boolean(a.generation) && a.generation === b.generation;
89
+
90
+ /**
91
+ * Assemble the proof a checkpoint is retired with.
92
+ *
93
+ * Each named record carries the bytes it owns and the context it was journaled under, so
94
+ * the validator can refuse a record written under another generation without asking the
95
+ * store or a host. `state` must be the exact accepted page state and `article` the final
96
+ * outgoing canonical article; their digests are taken here, over these bytes.
97
+ *
98
+ * @param {Object} options
99
+ * @param {string} options.capture - the capture the save belongs to
100
+ * @param {Array<{id: *, revision: number, update: Uint8Array, context: ?Object}>} options.records
101
+ * @param {{authority: Object, lineage: Object}} options.accepted - the identity and generation it was saved under
102
+ * @param {string} options.article - the final outgoing canonical article
103
+ * @param {Object} options.state - the accepted page state
104
+ * @param {{file: string, pageState: string}} options.receipt - the persisted pair
105
+ * @param {boolean} [options.saved]
106
+ * @returns {Promise<Object>}
107
+ */
108
+ export async function pageProof({ capture, records = [], accepted, article, state, receipt, saved = true } = {}) {
109
+ const entries = [];
110
+ for (const record of records) {
111
+ if (!record || record.id === undefined || typeof record.revision !== "number") fail("invalid-record", "A proof names each record by id and revision");
112
+ const update = bytesOf(record.update);
113
+ if (!update || !update.length) fail("invalid-record", "A proof needs the bytes each record owns");
114
+ entries.push({ id: copyId(record.id), revision: record.revision, update: update.slice(), context: record.context ?? null });
115
+ }
116
+ if (typeof article !== "string") fail("missing-article", "A proof names the final outgoing article");
117
+ const stateBytes = canonicalPageBytes(state);
118
+ return {
119
+ saved: saved === true,
120
+ capture,
121
+ accepted,
122
+ article,
123
+ state: copyState(state),
124
+ stateDigest: await digestHex(stateBytes),
125
+ articleDigest: await digestHex(encoder.encode(article)),
126
+ receipt,
127
+ records: entries,
128
+ };
129
+ }
130
+
131
+ // Checklist items 1, 2, 4 and 5, over the proof alone. Item 3 is the per-record coverage
132
+ // test below, and the projection of the state onto the article is the producer's to make:
133
+ // a proof whose state is not the one it was made over fails its own digest.
134
+ async function checkProof(proof) {
135
+ if (!proof || typeof proof !== "object" || Array.isArray(proof)) fail("missing-proof", "A page checkpoint needs a proof");
136
+ if (proof.saved !== true) fail("not-saved", "Only a save that succeeded under admission makes a proof");
137
+ if (typeof proof.capture !== "string" || !proof.capture) fail("missing-capture", "A proof belongs to the capture it saved");
138
+ const receipt = proof.receipt;
139
+ if (!receipt || typeof receipt.file !== "string" || !receipt.file || typeof receipt.pageState !== "string" || !receipt.pageState) {
140
+ fail("missing-receipt", "A proof names the persisted file and page-state pair");
141
+ }
142
+ const accepted = proof.accepted;
143
+ if (!accepted || !accepted.authority || !accepted.lineage || typeof accepted.lineage.generation !== "string" || !accepted.lineage.generation) {
144
+ fail("missing-identity", "A proof names the authority and generation it was saved under");
145
+ }
146
+ if (typeof proof.article !== "string") fail("missing-article", "A proof names the final outgoing article");
147
+ if (!Array.isArray(proof.records)) fail("missing-records", "A proof names the records it covers");
148
+ if (typeof proof.stateDigest !== "string" || !HEX.test(proof.stateDigest)) fail("missing-digest", "A proof carries the accepted state digest");
149
+ if (typeof proof.articleDigest !== "string" || !HEX.test(proof.articleDigest)) fail("missing-digest", "A proof carries the final article digest");
150
+ const stateBytes = canonicalPageBytes(proof.state);
151
+ if (await digestHex(stateBytes) !== proof.stateDigest) fail("state-digest-mismatch", "The page state is not the one the proof was made over");
152
+ if (await digestHex(encoder.encode(proof.article)) !== proof.articleDigest) fail("article-digest-mismatch", "The article is not the one the proof was made over");
153
+ const entries = new Map();
154
+ for (const entry of proof.records) {
155
+ if (!entry || entry.id === undefined || typeof entry.revision !== "number") fail("invalid-record", "A proof names each record by id and revision");
156
+ const update = bytesOf(entry.update);
157
+ if (!update || !update.length) fail("invalid-record", "A proof needs the bytes each record owns");
158
+ entries.set(identityKey(entry.id), entry);
159
+ }
160
+ return { stateBytes, entries, accepted };
161
+ }
162
+
163
+ // A record is covered only by a state that still carries it, saved under the same
164
+ // authority and the same generation it was journaled under.
165
+ function covers(checked, entry) {
166
+ const context = entry.context;
167
+ if (!context || !sameAuthority(context.authority, checked.accepted.authority)) return false;
168
+ if (!sameGeneration(context.lineage, checked.accepted.lineage)) return false;
169
+ const update = bytesOf(entry.update);
170
+ return Boolean(update) && update.length > 0 && containsUpdate(checked.stateBytes, update);
171
+ }
172
+
173
+ /**
174
+ * The records a page checkpoint proof covers, and no others.
175
+ *
176
+ * Registered as `validateCheckpoint` on the `page.yjs` channel. It runs before the
177
+ * store's transaction, may only narrow the caller's list, and throws on a proof it
178
+ * cannot decode so that an error keeps every record.
179
+ *
180
+ * @param {Object} proof
181
+ * @param {Array<{id: *, revision: number}>} records - what the caller observed
182
+ * @returns {Promise<Array<{id: *, revision: number}>>}
183
+ */
184
+ export async function validatePageCheckpoint(proof, records = []) {
185
+ const checked = await checkProof(proof);
186
+ return (records || []).filter((record) => {
187
+ if (!record || record.id === undefined || typeof record.revision !== "number") return false;
188
+ const entry = checked.entries.get(identityKey(record.id));
189
+ return Boolean(entry) && entry.revision === record.revision && covers(checked, entry);
190
+ });
191
+ }
@@ -0,0 +1,130 @@
1
+ /**
2
+ * recovery-replay.js — rebuild a page from one journaled capsule, and decide whether it
3
+ * may be replayed at all.
4
+ *
5
+ * A capsule is one immutable base and everything the episode put on top of it: the
6
+ * dependencies it received (a peer update a later local insert was built on) and the
7
+ * local work it owns. Replaying all three into a fresh Y.Doc rebuilds the page as this
8
+ * tab had it, read back through the strict decoder — no room, no host, no store.
9
+ *
10
+ * Admission is a separate question from reconstruction: a journal replays only into the
11
+ * authority, principal and generation it was written under, only while the current page
12
+ * still contains the capsule's base, and only into a page sharing its ancestry.
13
+ */
14
+
15
+ import * as Y from "../../vendor/yjs.vendor.js";
16
+ import { toBase64 } from "../../vendor/yjs.vendor.js";
17
+ import { NET } from "./layout.js";
18
+ import { blocksFromYValue } from "./decode.js";
19
+ import { sameAuthority } from "../../recovery/identity.js";
20
+ import { sameLineage, sharesAncestry } from "./lineage.js";
21
+ import { canonicalPageBytes, containsUpdate } from "./recovery-proof.js";
22
+
23
+ export class PageReplayError extends Error {
24
+ constructor(code, message) {
25
+ super(message);
26
+ this.name = "PageReplayError";
27
+ this.code = code;
28
+ }
29
+ }
30
+
31
+ const fail = (code, message) => { throw new PageReplayError(code, message); };
32
+ const identityKey = (id) => (typeof id === "string" ? id : JSON.stringify(id));
33
+ const bytesOf = (value) => {
34
+ if (value instanceof Uint8Array) return value;
35
+ if (value instanceof ArrayBuffer) return new Uint8Array(value);
36
+ return null;
37
+ };
38
+ const sameId = (a, b) => identityKey(a) === identityKey(b);
39
+
40
+ function merge(values) {
41
+ const parts = values.map(bytesOf).filter((bytes) => bytes && bytes.length);
42
+ if (!parts.length) return new Uint8Array(0);
43
+ if (parts.length === 1) return parts[0].slice();
44
+ return Y.mergeUpdatesV2(parts);
45
+ }
46
+
47
+ /**
48
+ * One writer's capsule and its batches as three self-contained updates.
49
+ *
50
+ * Batches are folded in by origin, so a duplicate batch or an update already compacted
51
+ * into the capsule changes nothing: Yjs merges by operation identity. A batch naming
52
+ * another capsule is an error rather than another page's bytes.
53
+ *
54
+ * @param {{capsule: Object, batches?: Array<Object>}} options
55
+ * @returns {Object}
56
+ */
57
+ export function accumulateCapsule({ capsule, batches = [] } = {}) {
58
+ if (!capsule || capsule.kind !== "binary" || !capsule.payload) fail("invalid-capsule", "Replay needs a binary capsule");
59
+ if (capsule.version !== undefined && capsule.version !== 1) fail("unknown-version", `Unknown page recovery capsule version: ${capsule.version}`);
60
+ const dependencies = [capsule.payload.dependencies];
61
+ const owned = [capsule.payload.owned];
62
+ const seen = new Set();
63
+ for (const batch of batches) {
64
+ if (!batch || batch.kind !== "binary-batch" || !batch.payload) fail("invalid-batch", "Replay reads binary batches only");
65
+ if (batch.capsuleId !== undefined && !sameId(batch.capsuleId, capsule.id)) fail("foreign-batch", "A batch belongs to another capsule");
66
+ if (batch.id !== undefined) {
67
+ if (seen.has(identityKey(batch.id))) continue;
68
+ seen.add(identityKey(batch.id));
69
+ }
70
+ if (batch.origin === "dependency") dependencies.push(batch.payload);
71
+ else owned.push(batch.payload);
72
+ }
73
+ return {
74
+ id: capsule.id,
75
+ context: capsule.context ?? null,
76
+ revision: capsule.revision ?? 0,
77
+ acknowledged: capsule.acknowledged ?? null,
78
+ base: merge([capsule.payload.base]),
79
+ dependencies: merge(dependencies),
80
+ owned: merge(owned),
81
+ };
82
+ }
83
+
84
+ // The capsule as one page value: the exact bytes a save would carry, so the strict
85
+ // decoder reads the same state a checkpoint would.
86
+ export function replayPageValue({ capsule, batches = [] } = {}) {
87
+ const parts = accumulateCapsule({ capsule, batches });
88
+ const doc = new Y.Doc();
89
+ try {
90
+ for (const bytes of [parts.base, parts.dependencies, parts.owned]) if (bytes.length) Y.applyUpdateV2(doc, bytes, NET);
91
+ if (doc.store.pendingStructs || doc.store.pendingDs) fail("unresolved-update", "The capsule's updates are missing a prerequisite");
92
+ return { v: 1, g: 0, y: { seed: toBase64(Y.encodeStateAsUpdateV2(doc)) } };
93
+ } finally {
94
+ doc.destroy();
95
+ }
96
+ }
97
+
98
+ /**
99
+ * The whole page a capsule reconstructs, decoded through the strict decoder.
100
+ *
101
+ * @param {{capsule: Object, batches?: Array<Object>}} options
102
+ * @returns {Object} the block model, by block id
103
+ */
104
+ export function reconstructPage({ capsule, batches = [] } = {}) {
105
+ return blocksFromYValue(replayPageValue({ capsule, batches }));
106
+ }
107
+
108
+ /**
109
+ * The capsule's parts, if this page may replay them.
110
+ *
111
+ * `current` is the page as it is now: its own authority, generation and accepted state.
112
+ * A journal from another authority or principal is never applied, a journal from another
113
+ * generation is a page that was replaced while the tab was away, and a page that no
114
+ * longer contains the capsule's base has nothing to replay it onto.
115
+ *
116
+ * @param {{capsule: Object, batches?: Array<Object>, current: {context: Object, state: Object}}} options
117
+ * @returns {Object} the accumulated parts, ready to apply
118
+ */
119
+ export function admitCapsule({ capsule, batches = [], current } = {}) {
120
+ const parts = accumulateCapsule({ capsule, batches });
121
+ if (!parts.context?.authority) fail("missing-identity", "A capsule with no authority is never replayed");
122
+ if (!current?.context?.authority) fail("missing-identity", "Replay needs the page's own authority");
123
+ if (!sameAuthority(parts.context.authority, current.context.authority)) fail("foreign-authority", "This journal belongs to another document or account");
124
+ if (!sameLineage(parts.context.lineage, current.context.lineage)) fail("foreign-generation", "This journal belongs to another generation of this page");
125
+ const stateBytes = canonicalPageBytes(current.state);
126
+ if (parts.base.length && !containsUpdate(stateBytes, parts.base)) fail("missing-base", "The page no longer contains the journal's base");
127
+ if (!parts.owned.length) fail("nothing-owned", "This capsule owns no local work");
128
+ if (!sharesAncestry(current.state, { v: 1, g: 0, y: { seed: toBase64(parts.owned) } })) fail("foreign-lineage", "This journal shares no ancestry with this page");
129
+ return parts;
130
+ }
@@ -1,6 +1,6 @@
1
1
  import { attrKeys, sameJson } from "./layout.js";
2
2
  import { blockJson } from "../model.js";
3
- import { marksKey, marksAttrs } from "./marks.js";
3
+ import { marksKey, marksFields } from "./marks.js";
4
4
  import { createProjectionMap } from "./projection-map.js";
5
5
 
6
6
  export const implicitFlags = (doc, visible) => {
@@ -19,7 +19,16 @@ export const implicitFlags = (doc, visible) => {
19
19
  });
20
20
  return out;
21
21
  };
22
- const pickAttrs = (node) => { const o = {}; for (const k of attrKeys(node.type)) o[k] = node.attrs[k]; if (node.type.name === "table") o.rows = blockJson(node).rows; return o; };
22
+ const opaqueOf = (node) => node.attrs.raw ?? null;
23
+ const pickAttrs = (node) => {
24
+ const raw = opaqueOf(node);
25
+ if (raw) { const o = { ...raw }; delete o.t; delete o.pos; return o; }
26
+ const o = {};
27
+ for (const k of attrKeys(node.type)) o[k] = node.attrs[k];
28
+ if (node.type.name === "table") o.rows = blockJson(node).rows;
29
+ return o;
30
+ };
31
+ const typeOfNode = (node) => opaqueOf(node)?.t ?? node.type.name;
23
32
 
24
33
  export function tokensOf(doc, alias = new Map(), visible = null) {
25
34
  const toks = [];
@@ -27,9 +36,9 @@ export function tokensOf(doc, alias = new Map(), visible = null) {
27
36
  doc.forEach((block, _o, index) => {
28
37
  const implicit = flags[index] && !alias.has(block.attrs.id);
29
38
  const brk = visible ? visible[index]?.brk ?? null : null;
30
- if (!implicit) toks.push({ k: "b", id: flags[index] && alias.has(block.attrs.id) ? alias.get(block.attrs.id) : block.attrs.id, t: block.type.name, attrs: pickAttrs(block), brk });
39
+ if (!implicit) toks.push({ k: "b", id: flags[index] && alias.has(block.attrs.id) ? alias.get(block.attrs.id) : block.attrs.id, t: typeOfNode(block), attrs: pickAttrs(block), brk });
31
40
  if (block.isTextblock) block.content.forEach((n) => {
32
- if (n.isText) { const ms = block.type.name === "code_block" ? [] : n.marks; const mk = marksKey(ms), ma = marksAttrs(ms); for (let i = 0; i < n.text.length; i++) toks.push({ k: "c", ch: n.text[i], mk, ma }); }
41
+ if (n.isText) { const ms = typeOfNode(block) === "code_block" ? [] : n.marks; const mk = marksKey(ms), ma = marksFields(ms); for (let i = 0; i < n.text.length; i++) toks.push({ k: "c", ch: n.text[i], mk, ma }); }
33
42
  else if (n.type.name === "hard_break") toks.push({ k: "h" });
34
43
  });
35
44
  });
@@ -74,8 +83,8 @@ export function textTokenRange(doc, from, to) {
74
83
  const a = Math.max(0, startOffset - cursor), b = Math.min(node.nodeSize, endOffset - cursor);
75
84
  if (a < b) {
76
85
  if (node.isText) {
77
- const marks = block.type.name === "code_block" ? [] : node.marks;
78
- const mk = marksKey(marks), ma = marksAttrs(marks);
86
+ const marks = typeOfNode(block) === "code_block" ? [] : node.marks;
87
+ const mk = marksKey(marks), ma = marksFields(marks);
79
88
  for (let i = a; i < b; i++) tokens.push({ k: "c", ch: node.text[i], mk, ma });
80
89
  } else if (node.type.name === "hard_break") tokens.push({ k: "h" });
81
90
  }
@@ -144,14 +144,21 @@ function walkFlow(pd, index) {
144
144
  const root = recoveredRoot(sourceKey, removalsBySource.get(sourceKey) ?? [], intents);
145
145
  const id = `recovered:${sourceKey}`;
146
146
  const t = root ? root.type : "paragraph";
147
- cur = { kind: "b", id, baseId: id, brk: { client: it.id.client, clock: it.id.clock }, implicit: false, runs: [], len: 1, atom: !schema.nodes[t]?.isTextblock, recovered: true, restored: !moved, sourceKey, t, fields: root ? root.attrs : null };
147
+ const opaque = !Object.hasOwn(schema.nodes, t);
148
+ cur = { kind: "b", id, baseId: id, brk: { client: it.id.client, clock: it.id.clock }, implicit: false, runs: [], len: 1, atom: !opaque && !schema.nodes[t]?.isTextblock, opaque, recovered: true, restored: !moved, sourceKey, t, fields: root ? root.attrs : null };
148
149
  segs.push(cur);
149
150
  prevId = id;
150
151
  };
151
152
  const open = (id, brk, implicit) => {
152
153
  cur = { kind: "b", id, baseId: id, brk, implicit, runs: [], len: implicit ? 0 : 1, atom: false, items: !implicit && (markerCount.get(id) ?? 0) > 1 ? [] : null };
153
- if (!implicit) { cur.t = typeOf(id); cur.atom = !schema.nodes[cur.t]?.isTextblock; }
154
- else cur.t = "paragraph";
154
+ if (!implicit) {
155
+ cur.t = typeOf(id);
156
+ // A type this runtime has no schema for still owns the text and marks up to
157
+ // the next block marker: it is not an atom whose following text becomes an
158
+ // implicit paragraph, and it is never rewritten as one.
159
+ cur.opaque = !Object.hasOwn(schema.nodes, cur.t);
160
+ cur.atom = !cur.opaque && !schema.nodes[cur.t]?.isTextblock;
161
+ } else cur.t = "paragraph";
155
162
  segs.push(cur);
156
163
  prevId = id;
157
164
  };
@@ -8,10 +8,23 @@ import { resolveTableFields, tableSeed, writeTable } from "./table.js";
8
8
  import { resolveBoardFields, writeBoard } from "./board.js";
9
9
  import { createOriginIndex } from "./origins.js";
10
10
 
11
+ const FIELD = (value) => (value === true ? {} : value);
12
+ const sameValue = (a, b) => sameJson(FIELD(a), FIELD(b));
13
+
11
14
  export function setAttrs(pd, id, t, attrs) {
12
15
  let m = pd.attrs.get(id);
13
16
  if (!m) { m = new Y.Map(); pd.attrs.set(id, m); }
14
17
  const type = schema.nodes[t];
18
+ // A block type this runtime has no schema for keeps its original record: no
19
+ // known field list to normalize it against, and never a paragraph in its place.
20
+ if (!type) {
21
+ if (m.get("t") !== t) m.set("t", t);
22
+ for (const [k, v] of Object.entries(attrs ?? {})) {
23
+ if (k === "t" || k === "pos") continue;
24
+ if (!sameJson(m.has(k) ? m.get(k) : undefined, v)) m.set(k, v);
25
+ }
26
+ return;
27
+ }
15
28
  const table = t === "table" && m.get("t") === "table" && !recoveredSource(id);
16
29
  const board = t === "board" && !recoveredSource(id);
17
30
  const before = table ? resolveTableFields(pd.tableFields, id, tableSeed(pd, id)) : board ? resolveBoardFields(pd.boardFields, id) : null;
@@ -46,6 +59,31 @@ export function liveKeys(ctx) {
46
59
  return out;
47
60
  }
48
61
 
62
+ // The live keys this runtime has no schema mark for, including the reserved
63
+ // compact-code bridge. A command never cancels them: they stay byte for byte
64
+ // until something that understands them rewrites them.
65
+ export function opaqueKeys(ctx) {
66
+ const cancelled = new Set();
67
+ for (const [k, v] of ctx) if (isCancel(k) && v) cancelled.add(k.split("!")[0]);
68
+ const out = [];
69
+ for (const [k, v] of ctx) if (!isCancel(k) && v !== null && v !== undefined && !cancelled.has(k) && !schema.marks[markName(k)]) out.push([k, v]);
70
+ return out;
71
+ }
72
+
73
+ // The attributes an inserted unit carries over from its neighbours: every known
74
+ // key as before, and an opaque key only when the editor's mark set still names
75
+ // it, so text at a span boundary does not inherit a format nobody can read.
76
+ export function baseAttributes(ctx, want = {}) {
77
+ const out = {};
78
+ let opaque = false;
79
+ for (const [k, v] of ctx) if (isCancel(k) || schema.marks[markName(k)]) out[k] = v;
80
+ for (const [k, v] of opaqueKeys(ctx)) {
81
+ opaque = true;
82
+ if (want[markName(k)] !== undefined) out[k] = v;
83
+ }
84
+ return { attrs: out, opaque };
85
+ }
86
+
49
87
  export function runAttributes(pd, ctx, want) {
50
88
  const rid = mintOp(pd);
51
89
  const changes = {};
@@ -54,12 +92,17 @@ export function runAttributes(pd, ctx, want) {
54
92
  for (const [k, v] of have) {
55
93
  const n = markName(k);
56
94
  const w = want[n];
57
- if (w !== undefined && sameJson(w === true ? true : w, v)) kept.add(n);
95
+ if (w !== undefined && sameValue(w, v)) kept.add(n);
58
96
  else changes[`${k}!${rid}`] = true;
59
97
  }
98
+ for (const [k, v] of opaqueKeys(ctx)) {
99
+ const n = markName(k);
100
+ if (want[n] !== undefined && sameValue(want[n], v)) kept.add(n);
101
+ }
60
102
  for (const [n, w] of Object.entries(want)) if (!kept.has(n)) changes[`${n}~${mintOp(pd)}`] = w;
61
- if (!Object.keys(changes).length) return undefined;
62
- return { ...Object.fromEntries(ctx), ...changes };
103
+ const base = baseAttributes(ctx, want);
104
+ if (!Object.keys(changes).length && !base.opaque) return undefined;
105
+ return { ...base.attrs, ...changes };
63
106
  }
64
107
 
65
108
  export const CHUNK_CHARS = 2000;
@@ -119,7 +162,7 @@ function insertChunk(pd, yIdx, toks, fences = null) {
119
162
  i += run.length;
120
163
  run = "";
121
164
  };
122
- const embed = (e) => { const attrs = Object.fromEntries(ctxAt(pd, i)); beforeFence(pd, i, fences, () => pd.flow.insertEmbed(i, e, attrs)); i += 1; };
165
+ const embed = (e) => { const attrs = baseAttributes(ctxAt(pd, i)).attrs; beforeFence(pd, i, fences, () => pd.flow.insertEmbed(i, e, attrs)); i += 1; };
123
166
  for (const t of toks) {
124
167
  if (t.k === "c") { if (runMk !== null && t.mk !== runMk) flush(); runMk = t.mk; runMa = t.ma; run += t.ch; }
125
168
  else if (t.k === "h") { flush(); runMk = null; embed({ br: 1 }); }
@@ -249,6 +292,7 @@ export function applyRegion(ctx, ta, oldToks, newToks, oldSurvives = null) {
249
292
  else insertTokens(pd, map.toPhysical(e.at, "before"), e.i.toks, ctx.tx, ctx.fences);
250
293
  }
251
294
  for (const [ob, nb] of attrs) ctx.tx(() => {
295
+ if (!schema.nodes[nb.t]) return;
252
296
  const source = ctx.recovered.get(ob.id);
253
297
  if (source) {
254
298
  if (ob.t !== nb.t) pd.gone.set(attrKey(source, "t"), nb.t);