@panphora/clayjs 1.23.0 → 1.24.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/all.js +1 -1
- package/dist/blockeditor/themes/light-1.css +431 -0
- package/dist/clay-dom.js +1 -1
- package/dist/clay-events.js +1 -1
- package/dist/clay-internals.js +1 -1
- package/dist/clay-options.js +1 -1
- package/dist/clay-router.js +1 -1
- package/dist/clay-ui.js +1 -1
- package/dist/clay-utils.js +1 -1
- package/dist/clay.js +1 -1
- package/dist/clay.standalone.js +30285 -26613
- package/dist/m/c-277QP5ET.js +1 -0
- package/dist/m/c-2BLVA6IN.js +5 -0
- package/dist/m/c-2OJMOUPC.js +1 -0
- package/dist/m/c-2PVGJFI5.js +1 -0
- package/dist/m/{c-AYVGD6A6.js → c-3UW5EJ77.js} +1 -1
- package/dist/m/{c-T5NK2TVY.js → c-42ZNV7LI.js} +1 -1
- package/dist/m/{c-3TLG3OLG.js → c-46LJAZZJ.js} +1 -1
- package/dist/m/c-47IBJGPQ.js +1 -0
- package/dist/m/c-5FXEKU4Y.js +1 -0
- package/dist/m/c-5KXDKC5G.js +1 -0
- package/dist/m/{c-EUGJVHDD.js → c-5RZNFTWD.js} +1 -1
- package/dist/m/c-65LJU2H2.js +72 -0
- package/dist/m/{c-NQJRVJ56.js → c-6IJEVE6B.js} +1 -1
- package/dist/m/{c-NW54NHKE.js → c-6LEYVQJK.js} +1 -1
- package/dist/m/{c-GSRLXYWL.js → c-6Q6RGFBF.js} +1 -1
- package/dist/m/c-6RGEQNVV.js +1 -0
- package/dist/m/c-7CM4GMTS.js +6 -0
- package/dist/m/c-7NCWENHV.js +1 -0
- package/dist/m/c-7V6MGMX2.js +1 -0
- package/dist/m/{c-LKSL4AUJ.js → c-7WZSUTYP.js} +1 -1
- package/dist/m/c-A4L7APIU.js +1 -0
- package/dist/m/c-APGHGDZM.js +1 -0
- package/dist/m/c-AT7GLQGV.js +34 -0
- package/dist/m/{c-ZRXJD4G6.js → c-B7GOFGQR.js} +1 -1
- package/dist/m/{c-KDXIJEEF.js → c-BGN7RCTJ.js} +1 -1
- package/dist/m/c-BKM46LUJ.js +1 -0
- package/dist/m/c-BOMQIQ3V.js +1 -0
- package/dist/m/{c-62HZW5DH.js → c-C5AKJVRH.js} +1 -1
- package/dist/m/{c-BXYSDKAN.js → c-CJ7QYEWI.js} +1 -1
- package/dist/m/c-CTGRDT6E.js +1 -0
- package/dist/m/c-CV4T65JE.js +6 -0
- package/dist/m/c-CVSWLX3X.js +1 -0
- package/dist/m/c-CZHTXGJS.js +1 -0
- package/dist/m/c-D7NT5S4F.js +1 -0
- package/dist/m/c-DD26VXAP.js +1 -0
- package/dist/m/{c-ANNFZFLN.js → c-DDS23EY6.js} +1 -1
- package/dist/m/{c-ALDVFPWL.js → c-DFKBGSN2.js} +1 -1
- package/dist/m/c-DIKR7H57.js +147 -0
- package/dist/m/c-DXPOFLEJ.js +1 -0
- package/dist/m/c-EDE45NM7.js +1 -0
- package/dist/m/c-EILLN2H2.js +1 -0
- package/dist/m/c-EODTTNVA.js +1 -0
- package/dist/m/{c-G6COVIZB.js → c-ESK4S67I.js} +1 -1
- package/dist/m/c-FDWR3FL7.js +1 -0
- package/dist/m/{c-WQZTI5P2.js → c-FEC6LGFF.js} +1 -1
- package/dist/m/c-FLEIU6CG.js +1 -0
- package/dist/m/c-FQAQRXGK.js +1 -0
- package/dist/m/{c-W4JCVLAB.js → c-FSQXFG5T.js} +1 -1
- package/dist/m/c-FVPNX2GO.js +135 -0
- package/dist/m/c-FYDKYW6E.js +1 -0
- package/dist/m/c-GNI7WHKF.js +32 -0
- package/dist/m/c-GO635CRU.js +3 -0
- package/dist/m/c-H3FNLBHN.js +2 -0
- package/dist/m/c-HDVH3MVC.js +72 -0
- package/dist/m/c-HIX5H5L7.js +1 -0
- package/dist/m/c-HKSLOVU4.js +1 -0
- package/dist/m/{c-SGRIYOAJ.js → c-HL6BLJNA.js} +1 -1
- package/dist/m/c-HTLYXCUH.js +1 -0
- package/dist/m/{c-QIR5CAKM.js → c-IUL7PU4P.js} +1 -1
- package/dist/m/c-IXO7EI2Q.js +1 -0
- package/dist/m/{c-W5E5P2TZ.js → c-JQF2EUCG.js} +1 -1
- package/dist/m/{c-NEL7HQW3.js → c-JWDCQL6J.js} +1 -1
- package/dist/m/c-KQKKIVLW.js +20 -0
- package/dist/m/c-LATNQ6Q5.js +1 -0
- package/dist/m/c-LD6UVV5R.js +1 -0
- package/dist/m/c-LS7IVQVW.js +1 -0
- package/dist/m/{c-JCOQVZYS.js → c-LSVN3OLG.js} +1 -1
- package/dist/m/{c-JDZDF2QM.js → c-LTSPHVAK.js} +1 -1
- package/dist/m/c-MFOSMAVM.js +3 -0
- package/dist/m/c-MJ7GVE46.js +1 -0
- package/dist/m/c-MP6S4AZU.js +6 -0
- package/dist/m/c-MQKSKQHG.js +2 -0
- package/dist/m/{c-UHUNXWKN.js → c-MQYEDF3Q.js} +1 -1
- package/dist/m/{c-IHUWUKIU.js → c-MR75YPRT.js} +1 -1
- package/dist/m/c-ND3Q2G2I.js +1 -0
- package/dist/m/c-NEQRFC4O.js +1 -0
- package/dist/m/{c-5FKRZNJV.js → c-NFQFV7OP.js} +1 -1
- package/dist/m/c-NHXOBD6A.js +1 -0
- package/dist/m/{c-QIKT6SKM.js → c-NICICT4P.js} +1 -1
- package/dist/m/c-NQAPWM7O.js +1 -0
- package/dist/m/{c-QUGCKMWF.js → c-NVU3DORS.js} +1 -1
- package/dist/m/{c-LWIDDWAR.js → c-OCYG4NBQ.js} +22 -22
- package/dist/m/c-OH2FPYSY.js +19 -0
- package/dist/m/c-OYHI67B6.js +1 -0
- package/dist/m/{c-R2XUODVY.js → c-OYW5MFPZ.js} +1 -1
- package/dist/m/{c-5B4YBZXC.js → c-P2HKMJAW.js} +1 -1
- package/dist/m/{c-TSBVMWAH.js → c-PCNJETUA.js} +1 -1
- package/dist/m/c-PDBNCJCT.js +1 -0
- package/dist/m/c-PGSBXHUE.js +1 -0
- package/dist/m/{c-5TIZ2I64.js → c-PYG2NGWU.js} +1 -1
- package/dist/m/c-Q26XHBLQ.js +1 -0
- package/dist/m/c-Q6BG3ZLO.js +1 -0
- package/dist/m/c-QENKN7QI.js +1 -0
- package/dist/m/c-QQFHEODI.js +1 -0
- package/dist/m/c-QTEUO2K6.js +1 -0
- package/dist/m/c-R23UHMXG.js +1 -0
- package/dist/m/c-RETTCOWO.js +10 -0
- package/dist/m/c-SE3SIY7Y.js +7 -0
- package/dist/m/{c-XX75FFNL.js → c-SGGGCIVD.js} +1 -1
- package/dist/m/c-SGH3UTF3.js +1 -0
- package/dist/m/c-SIM7Z3U7.js +1 -0
- package/dist/m/{c-X5LNHET5.js → c-SKMIR5HQ.js} +1 -1
- package/dist/m/{c-R27DWQKX.js → c-STRI73GQ.js} +1 -1
- package/dist/m/c-SUXHUL7U.js +1 -0
- package/dist/m/c-SXGEWPFC.js +10 -0
- package/dist/m/c-SXNSSHPP.js +1 -0
- package/dist/m/c-T3P3AXEW.js +1 -0
- package/dist/m/{c-PXLTUYZC.js → c-TFLRT5JX.js} +1 -1
- package/dist/m/{c-YCLU4UNT.js → c-TGIMBAWE.js} +1 -1
- package/dist/m/c-TGSHHFO6.js +1 -0
- package/dist/m/c-U4NTUVDZ.js +1 -0
- package/dist/m/c-UOOIF2AL.js +1 -0
- package/dist/m/{c-6TYGD5AB.js → c-UOUUI265.js} +1 -1
- package/dist/m/c-V2U3F4QX.js +1 -0
- package/dist/m/c-V2V5FKXX.js +1 -0
- package/dist/m/c-VBXRXTVS.js +26 -0
- package/dist/m/c-VHSMPITY.js +1 -0
- package/dist/m/c-VPXCXBDT.js +1 -0
- package/dist/m/c-WCG6AKA7.js +1 -0
- package/dist/m/c-WNOLMHI2.js +1 -0
- package/dist/m/{c-CRPIDGXC.js → c-X6MOYATI.js} +1 -1
- package/dist/m/{c-FQGHR32O.js → c-X75G43SZ.js} +1 -1
- package/dist/m/c-XB6EAIAR.js +13 -0
- package/dist/m/c-XMIDHEKF.js +1 -0
- package/dist/m/c-XUZMRQ7O.js +4 -0
- package/dist/m/{c-2Z22FCFK.js → c-XW5TIYYB.js} +1 -1
- package/dist/m/c-YDW4VCHP.js +1 -0
- package/dist/m/{c-QGAPG7OM.js → c-YLHTD3KX.js} +1 -1
- package/dist/m/c-YNIEIPBS.js +1 -0
- package/dist/m/c-Z4G2UK6T.js +4 -0
- package/dist/m/c-Z6MROEQ4.js +1 -0
- package/dist/m/c-ZFO5RW6G.js +2 -0
- package/dist/m/c-ZGCO4J3H.js +8 -0
- package/dist/m/c-ZKKAF4UT.js +3 -0
- package/dist/m/c-ZRGEN6QQ.js +4 -0
- package/dist/m/core/{admin-attrs-U7IJNYBG.js → admin-attrs-ONRWVXPK.js} +1 -1
- package/dist/m/core/{autosave-F5YQCKVL.js → autosave-TBKAAAFB.js} +1 -1
- package/dist/m/core/conflict-notice-FOEE6I7E.js +1 -0
- package/dist/m/core/draft-mode-4MCL3LYI.js +1 -0
- package/dist/m/core/{host-permission-6EVJGNPL.js → host-permission-X4BENZEU.js} +1 -1
- package/dist/m/core/{is-edit-mode-GCX6767E.js → is-edit-mode-FFHCQ5DR.js} +1 -1
- package/dist/m/core/{persist-CJXIL7D4.js → persist-6ILUR34S.js} +1 -1
- package/dist/m/core/{recovery-DRFZRJED.js → recovery-APWADU4K.js} +1 -1
- package/dist/m/core/save-6HT2LABE.js +1 -0
- package/dist/m/core/save-core-EVMNZQ4V.js +1 -0
- package/dist/m/core/site-edits-G4BOZMV4.js +1 -0
- package/dist/m/core/{unsaved-warning-HNQVTA2D.js → unsaved-warning-ITZDIZ2I.js} +1 -1
- package/dist/m/internals/{index-NRPNDTSA.js → index-HS3CQDFW.js} +1 -1
- package/dist/m/loader-GLLVDUZA.js +1 -0
- package/dist/m/manifest-50FF1C45.js +1 -0
- package/dist/m/manifest-edit-BE1A621D.js +1 -0
- package/dist/m/manifest.json +2072 -1811
- package/dist/m/page/editor-KAZDJNND.js +307 -0
- package/dist/m/plugins/ai-edit-LABECCSN.js +6 -0
- package/dist/m/plugins/{cms-H7DTMUQX.js → cms-T2HLI7JG.js} +16 -16
- package/dist/m/plugins/{demo-42BSYQ7T.js → demo-T7B4RIKL.js} +1 -1
- package/dist/m/plugins/{derive-QVI3LI3A.js → derive-6LPJOCAV.js} +1 -1
- package/dist/m/plugins/indicator-J4ABFARX.js +1 -0
- package/dist/m/plugins/{live-DCUBKXY4.js → live-PGBIEMGB.js} +1 -1
- package/dist/m/plugins/menu-YZL3X2AJ.js +1 -0
- package/dist/m/plugins/page-WOCNX2IN.js +2 -0
- package/dist/m/plugins/page-view-SJODOOGE.js +1 -0
- package/dist/m/plugins/{people-5XRNRP5U.js → people-C4TCWFOQ.js} +1 -1
- package/dist/m/plugins/{quickcrop-WWF5I422.js → quickcrop-X5MRIBAP.js} +2 -2
- package/dist/m/plugins/richclay-Y2NV44NA.js +549 -0
- package/dist/m/plugins/sortable-XCHQG65Z.js +1 -0
- package/dist/m/plugins/{source-6BKMK4PO.js → source-YK7MO23W.js} +1 -1
- package/dist/m/plugins/{undo-3JEYK5QY.js → undo-VLRBQNPN.js} +1 -1
- package/dist/m/plugins/upgrade-NNTJBSK2.js +1 -0
- package/dist/m/plugins/{upload-TTCQRIM5.js → upload-ZKKJV76X.js} +1 -1
- package/dist/m/plugins/visitor-drafts-RTZNONHT.js +20 -0
- package/dist/m/plugins/{visitor-drafts-owner-Z5B2OQBR.js → visitor-drafts-owner-LHUHFU3L.js} +1 -1
- package/dist/m/plugins/{visitor-menu-4LQMKLPP.js → visitor-menu-FY4KM2SW.js} +1 -1
- package/dist/m/plugins/wire-M2NR354P.js +1 -0
- package/dist/m/{reader-core-POFQCKRV.js → reader-core-T2S3JC4E.js} +2 -2
- package/dist/m/sync/live-sync-Z64YREWQ.js +1 -0
- package/dist/m/ui/index-MGTWOW2C.js +1 -0
- package/dist/m/vendor/{sap-core.vendor-OA5SKL7Q.js → sap-core.vendor-QAEPMETX.js} +1 -1
- package/dist/offline-manifest.json +1250 -1082
- package/dist/server/core.cjs +132 -109
- package/dist/server/page-compaction-worker.cjs +5 -5
- package/dist/server/rooms.mjs +3 -1
- package/docs/guide.md +89 -44
- package/entries/clay-data.js +1 -1
- package/entries/sap.js +1 -1
- package/package.json +5 -3
- package/src/blockeditor/themes/content.css +70 -0
- package/src/blockeditor/themes/light-1.css +431 -0
- package/src/blockeditor/themes/light-1.source.css +270 -0
- package/src/blockeditor/themes/light-2.css +211 -0
- package/src/blockeditor/themes/light-3.css +130 -0
- package/src/blockeditor/themes/light-4.css +299 -0
- package/src/core/conflict-notice.js +28 -0
- package/src/core/draft-mode.js +6 -0
- package/src/core/host-meta.js +6 -6
- package/src/core/is-edit-mode.js +9 -3
- package/src/core/mode-support.js +3 -0
- package/src/core/mode-switch.js +4 -4
- package/src/core/mode-transition.js +3 -1
- package/src/core/page-format-wire.js +1 -1
- package/src/core/page-format.js +8 -8
- package/src/core/save-accepted.js +6 -7
- package/src/core/save-core.js +52 -23
- package/src/core/save-failure-notice.js +37 -0
- package/src/core/save-feedback.js +6 -0
- package/src/core/save-replace.js +1 -1
- package/src/core/save-size.js +2 -2
- package/src/core/save.js +78 -15
- package/src/core/site-edits.js +166 -0
- package/src/core/unsaved-warning.js +2 -2
- package/src/core/upgrade-run.js +4 -4
- package/src/lib/ai-comment.js +23 -0
- package/src/lib/effect.js +8 -0
- package/src/lib/idb-error-code.js +20 -0
- package/src/live/fast-lane.js +10 -10
- package/src/live/local-lane.js +10 -10
- package/src/live/page-compaction.js +8 -8
- package/src/live/server-lane.js +1 -1
- package/src/loader-logic.js +48 -18
- package/src/loader.js +11 -6
- package/src/page/adopt-boot.js +1 -1
- package/src/page/adopt.js +2 -2
- package/src/page/ai-area.js +182 -0
- package/src/page/ai-preview.js +80 -0
- package/src/page/ai-surface.js +257 -0
- package/src/page/ai-tool.js +308 -145
- package/src/page/announce.js +2 -2
- package/src/page/awareness.js +18 -18
- package/src/page/blocks.js +35 -34
- package/src/page/blocksel.js +2 -2
- package/src/page/board-drag.js +11 -11
- package/src/page/board-face.js +1 -1
- package/src/page/board-panel.js +26 -26
- package/src/page/board-style.js +127 -127
- package/src/page/board-view.js +48 -48
- package/src/page/chart.js +29 -29
- package/src/page/checkpoint-proof.js +19 -19
- package/src/page/chrome.js +74 -65
- package/src/page/clipboard-shape.js +1 -1
- package/src/page/csv-import.js +4 -4
- package/src/page/drag.js +5 -5
- package/src/page/editor.js +301 -296
- package/src/page/export-html.js +8 -8
- package/src/page/features/checklist.js +36 -36
- package/src/page/features/export-html.js +19 -19
- package/src/page/features/export-markdown.js +10 -10
- package/src/page/features/import-markdown.js +13 -13
- package/src/page/features/media.js +50 -50
- package/src/page/features/table.js +17 -17
- package/src/page/features/title-cover.js +17 -17
- package/src/page/features/title-icon.js +7 -7
- package/src/page/features/toc.js +1 -1
- package/src/page/features/tool.js +42 -18
- package/src/page/format-command.js +4 -4
- package/src/page/format-gate.js +6 -6
- package/src/page/format-host.js +6 -6
- package/src/page/format-level.js +3 -3
- package/src/page/format-native-commands.js +2 -2
- package/src/page/highlight-slot.js +1 -1
- package/src/page/highlight.js +4 -4
- package/src/page/history-intent.js +2 -2
- package/src/page/image-actions.js +4 -4
- package/src/page/link.js +10 -10
- package/src/page/media.js +3 -3
- package/src/page/mention-picker.js +7 -7
- package/src/page/merge.js +1 -1
- package/src/page/placeholder.js +3 -3
- package/src/page/projection.js +11 -11
- package/src/page/reader-patch.js +2 -2
- package/src/page/recover.js +26 -26
- package/src/page/recovery-ui.js +28 -28
- package/src/page/rich/chart.js +1 -1
- package/src/page/rich/page-tool.js +21 -6
- package/src/page/rich/tool-runtime.js +3 -2
- package/src/page/save-projection.js +2 -2
- package/src/page/save-states.js +9 -9
- package/src/page/schema.js +9 -9
- package/src/page/table-style.js +21 -21
- package/src/page/toc-link.js +1 -1
- package/src/page/tool-capability.js +8 -3
- package/src/page/tool-transport.js +52 -0
- package/src/page/tool-views.js +4 -0
- package/src/page/tools.js +6 -1
- package/src/page/view.js +6 -6
- package/src/page/ybind/binding.js +1 -1
- package/src/page/ybind/conversion.js +1 -1
- package/src/page/ybind/layout.js +4 -4
- package/src/page/ybind/migrate.js +1 -1
- package/src/page/ybind/origins.js +2 -2
- package/src/page/ybind/provider.js +2 -2
- package/src/page/ybind/recovery-proof.js +3 -3
- package/src/page/ybind/recovery-replay.js +2 -2
- package/src/plugins/ai-edit/picker.js +235 -0
- package/src/plugins/ai-edit.js +220 -34
- package/src/plugins/indicator-chip.js +39 -34
- package/src/plugins/indicator.js +13 -4
- package/src/plugins/live.js +21 -13
- package/src/plugins/menu/actions.js +89 -17
- package/src/plugins/menu/seo.js +32 -6
- package/src/plugins/menu/shell-style.js +26 -7
- package/src/plugins/menu/shell.js +12 -7
- package/src/plugins/menu/variants.js +6 -10
- package/src/plugins/menu/visitor.js +44 -34
- package/src/plugins/menu.js +3 -2
- package/src/plugins/page-view.js +2 -2
- package/src/plugins/page.js +257 -98
- package/src/plugins/sortable.js +7 -0
- package/src/plugins/upgrade.js +2 -1
- package/src/plugins/visitor-drafts/commit.js +303 -0
- package/src/plugins/visitor-drafts/idb.js +221 -0
- package/src/plugins/visitor-drafts/intro-style.js +1 -1
- package/src/plugins/visitor-drafts/intro.js +8 -25
- package/src/plugins/visitor-drafts/prompt.js +30 -4
- package/src/plugins/visitor-drafts/session.js +345 -0
- package/src/plugins/visitor-drafts/store.js +351 -32
- package/src/plugins/visitor-drafts.js +158 -27
- package/src/server/checkpoint/index.js +8 -8
- package/src/server/rooms/index.js +2 -2
- package/src/server/rooms/live-data-rooms.js +35 -35
- package/src/server/rooms/live-page-history.js +2 -2
- package/src/server/rooms/page-admission.js +34 -6
- package/src/server/rooms/page-conversion.js +1 -1
- package/src/server/rooms/page-lineage.js +4 -4
- package/src/server/rooms/page-rebase.js +4 -4
- package/src/server/rooms/page-room-conformance.js +14 -14
- package/src/server/rooms/page-state.js +2 -2
- package/src/server/rooms/page-write-admission.js +7 -7
- package/src/server/rooms/page-write-guard.js +3 -3
- package/src/sync/data-guard-notice.js +265 -0
- package/src/sync/live-sync.js +54 -11
- package/src/sync/page-relay.js +4 -4
- package/src/sync/presence-store.js +1 -1
- package/src/sync/presence-target.js +3 -3
- package/src/sync/presence.js +1 -1
- package/src/sync/stream.js +54 -13
- package/src/sync/wire-profile.js +8 -3
- package/src/ui/bevel.js +1 -1
- package/src/ui/index.js +9 -10
- package/src/ui/skins/cms.js +1 -1
- package/src/ui/skins/quickcrop.js +1 -1
- package/src/ui/skins/richclay.js +2 -2
- package/src/ui/toast-lazy.js +12 -3
- package/src/ui/toast.js +9 -3
- package/src/vendor/hyper-html-api-data.vendor.js +1 -1
- package/src/vendor/hyper-html-api-upgrade.vendor.js +2 -2
- package/src/vendor/hypercms.vendor.js +16 -16
- package/src/vendor/richclay.vendor.js +48 -20
- package/src/vendor/sap-core.vendor.js +1 -1
- package/tool-generation/context.md +44 -0
- package/tool-generation/system.md +23 -0
- package/dist/m/c-24PMUJIV.js +0 -2
- package/dist/m/c-2AYVYEI4.js +0 -1
- package/dist/m/c-2AZDZWCK.js +0 -1
- package/dist/m/c-2L6O5PDS.js +0 -6
- package/dist/m/c-2RYOGUQM.js +0 -2
- package/dist/m/c-2ZVOEU4M.js +0 -1
- package/dist/m/c-37NAONZA.js +0 -1
- package/dist/m/c-3GJ4SDE2.js +0 -1
- package/dist/m/c-3O7OBDCM.js +0 -1
- package/dist/m/c-5OJ7YF2N.js +0 -1
- package/dist/m/c-5WMXEKFH.js +0 -135
- package/dist/m/c-6RDOKLN7.js +0 -1
- package/dist/m/c-A4IP6MBC.js +0 -1
- package/dist/m/c-A6L3FMUS.js +0 -1
- package/dist/m/c-ATKAOH23.js +0 -1
- package/dist/m/c-B6O7AJYG.js +0 -32
- package/dist/m/c-BCGWJYXW.js +0 -1
- package/dist/m/c-BGKAT2FL.js +0 -7
- package/dist/m/c-BYTKL6UX.js +0 -1
- package/dist/m/c-DHBRA6DL.js +0 -1
- package/dist/m/c-DTMZ3ZHI.js +0 -1
- package/dist/m/c-E3ZXARWA.js +0 -1
- package/dist/m/c-EK63G3CG.js +0 -1
- package/dist/m/c-ELYIIYRS.js +0 -1
- package/dist/m/c-EXKHPOVC.js +0 -3
- package/dist/m/c-FCSZ3DC5.js +0 -20
- package/dist/m/c-FTWGYI5E.js +0 -1
- package/dist/m/c-G4PWUDD5.js +0 -1
- package/dist/m/c-GBKB5WDL.js +0 -2
- package/dist/m/c-GBTVCJXC.js +0 -1
- package/dist/m/c-GC2HICFJ.js +0 -1
- package/dist/m/c-GRWMD6O5.js +0 -1
- package/dist/m/c-HCNWJE4A.js +0 -4
- package/dist/m/c-HG2C77BM.js +0 -4
- package/dist/m/c-JGHEACGK.js +0 -1
- package/dist/m/c-JGLX6TBK.js +0 -1
- package/dist/m/c-JNRKHYWO.js +0 -1
- package/dist/m/c-KBS3UV6B.js +0 -1
- package/dist/m/c-KQ7E3PYY.js +0 -1
- package/dist/m/c-L3F5ZLIV.js +0 -1
- package/dist/m/c-L4KMT7FO.js +0 -6
- package/dist/m/c-L5VFATT6.js +0 -1
- package/dist/m/c-LCYPMULG.js +0 -1
- package/dist/m/c-LIGQYZ7M.js +0 -6
- package/dist/m/c-LKIWEPAV.js +0 -1
- package/dist/m/c-MPSKIU2I.js +0 -1
- package/dist/m/c-N7JPUPGC.js +0 -1
- package/dist/m/c-NK33MMYF.js +0 -1
- package/dist/m/c-NQLWOHLH.js +0 -10
- package/dist/m/c-NTHUTABN.js +0 -1
- package/dist/m/c-NWX3MFT4.js +0 -1
- package/dist/m/c-OKXI52N7.js +0 -1
- package/dist/m/c-OPONJ2UK.js +0 -128
- package/dist/m/c-OWQWOGYI.js +0 -1
- package/dist/m/c-P6Q2ZTFQ.js +0 -1
- package/dist/m/c-QEN6HZCA.js +0 -1
- package/dist/m/c-QIXT3Q7C.js +0 -28
- package/dist/m/c-QM5LXRDT.js +0 -9
- package/dist/m/c-QYYUVLVG.js +0 -1
- package/dist/m/c-RQRMPZXD.js +0 -1
- package/dist/m/c-SBCBXKOZ.js +0 -26
- package/dist/m/c-SKF5HL4I.js +0 -4
- package/dist/m/c-SRPC3VBE.js +0 -1
- package/dist/m/c-TANBH7FI.js +0 -1
- package/dist/m/c-THGQQDYY.js +0 -1
- package/dist/m/c-UD5JZTDL.js +0 -1
- package/dist/m/c-UDGEFWNU.js +0 -1
- package/dist/m/c-UGNV5MFB.js +0 -1
- package/dist/m/c-VIRFF5HG.js +0 -8
- package/dist/m/c-VJYEVRYK.js +0 -1
- package/dist/m/c-VQCEOGKH.js +0 -13
- package/dist/m/c-VYJ7O4RI.js +0 -34
- package/dist/m/c-W7IPVCAA.js +0 -1
- package/dist/m/c-WBGWGOM7.js +0 -1
- package/dist/m/c-WJ5DRBHT.js +0 -72
- package/dist/m/c-X5WC4PX5.js +0 -1
- package/dist/m/c-XCFQEWW6.js +0 -3
- package/dist/m/c-XGH4GS2T.js +0 -1
- package/dist/m/c-XLQ3BDMM.js +0 -1
- package/dist/m/c-ZQXNHZZT.js +0 -72
- package/dist/m/core/conflict-notice-42H4ASLA.js +0 -1
- package/dist/m/core/draft-mode-WSQAT67S.js +0 -1
- package/dist/m/core/save-ESPSQPLV.js +0 -1
- package/dist/m/core/save-core-6SQHOR6W.js +0 -1
- package/dist/m/loader-FBDE2UAB.js +0 -1
- package/dist/m/manifest-1D2F5CA5.js +0 -1
- package/dist/m/manifest-edit-F7CC5CFD.js +0 -1
- package/dist/m/page/editor-OSKOW6RS.js +0 -304
- package/dist/m/plugins/ai-edit-VV4QTINH.js +0 -6
- package/dist/m/plugins/indicator-WVPGMOZ7.js +0 -1
- package/dist/m/plugins/menu-Q4MFFQ6E.js +0 -1
- package/dist/m/plugins/page-OAWUJQEL.js +0 -2
- package/dist/m/plugins/page-view-DZ26ZDDQ.js +0 -1
- package/dist/m/plugins/richclay-PGOOJ4OK.js +0 -517
- package/dist/m/plugins/sortable-266HY6TR.js +0 -1
- package/dist/m/plugins/upgrade-BNZVFHMZ.js +0 -1
- package/dist/m/plugins/visitor-drafts-2AUYKAIJ.js +0 -20
- package/dist/m/plugins/wire-W2EV36KZ.js +0 -1
- package/dist/m/sync/live-sync-XGUPHI3I.js +0 -1
- package/dist/m/ui/index-VGYAKAKI.js +0 -1
- package/dist/page/themes/light-1.css +0 -431
- package/src/page/themes/content.css +0 -70
- package/src/page/themes/light-1.css +0 -431
- package/src/page/themes/light-1.source.css +0 -270
- package/src/page/themes/light-2.css +0 -211
- package/src/page/themes/light-3.css +0 -130
- package/src/page/themes/light-4.css +0 -299
- /package/dist/m/{c-FILLTJA7.js → c-QTTJYJMA.js} +0 -0
- /package/dist/m/plugins/{quickcrop-WWF5I422.js.LEGAL.txt → quickcrop-X5MRIBAP.js.LEGAL.txt} +0 -0
- /package/dist/m/plugins/{richclay-PGOOJ4OK.js.LEGAL.txt → richclay-Y2NV44NA.js.LEGAL.txt} +0 -0
|
@@ -3654,12 +3654,12 @@ var COMPACTION = Object.freeze({
|
|
|
3654
3654
|
});
|
|
3655
3655
|
var ROOM_ERROR_STATUS = Object.freeze({
|
|
3656
3656
|
"checkpoint-required": 503,
|
|
3657
|
-
"
|
|
3657
|
+
"blockeditor-full": 409,
|
|
3658
3658
|
"compaction-failed": 409,
|
|
3659
|
-
"
|
|
3660
|
-
"
|
|
3661
|
-
"
|
|
3662
|
-
"
|
|
3659
|
+
"blockeditor-immutable": 409,
|
|
3660
|
+
"blockeditor-batch": 409,
|
|
3661
|
+
"blockeditor-update-invalid": 422,
|
|
3662
|
+
"blockeditor-upgrade": 404
|
|
3663
3663
|
});
|
|
3664
3664
|
function buildCandidate({ kind, sources, encode = Ic }) {
|
|
3665
3665
|
const updates = sources.map(([, v2]) => Lo(v2));
|
package/dist/server/rooms.mjs
CHANGED
|
@@ -19,6 +19,7 @@ export const {
|
|
|
19
19
|
applyOps,
|
|
20
20
|
applyTracked,
|
|
21
21
|
assignDeep,
|
|
22
|
+
blockeditorFormatFloor,
|
|
22
23
|
captureFormat,
|
|
23
24
|
checkOp,
|
|
24
25
|
checkPageConversion,
|
|
@@ -47,10 +48,11 @@ export const {
|
|
|
47
48
|
isYPageValue,
|
|
48
49
|
liveTags,
|
|
49
50
|
makePageTransition,
|
|
51
|
+
oldBlockeditorFormat,
|
|
52
|
+
oldFormatRefusal,
|
|
50
53
|
overlaps,
|
|
51
54
|
pageBaseline,
|
|
52
55
|
pageCompactionWorkerUrl,
|
|
53
|
-
pageFormatFloor,
|
|
54
56
|
pageLineageLost,
|
|
55
57
|
pageRev,
|
|
56
58
|
pageTransitionState,
|
package/docs/guide.md
CHANGED
|
@@ -326,9 +326,9 @@ Plugins add editing, sync, and UI features to `clay.js`. Most are off by default
|
|
|
326
326
|
| `ai-edit` | off | no | select text, ask an agent to rewrite it; implies `wire` |
|
|
327
327
|
| `demo` | off | yes | fakes saving into `localStorage` for public demos |
|
|
328
328
|
| `visitor-drafts` | off | yes | `clay.visitorDrafts`; in draft mode, the draft store, download prompt and intro area; outside it, only the owner chip, fetched when the owner edits ([Free documents for visitors](https://clayjs.com/docs/guide/free-documents/)) |
|
|
329
|
-
| `visitor-menu` | off | no (draft mode only) | `
|
|
329
|
+
| `visitor-menu` | off | no (draft mode only) | a `"Settings"` gear menu (`clay.menu`) in draft mode: **Undo** and **Redo**, **Download copy** and **Make your own copy**, plus `clay.variants` for [looks](https://clayjs.com/docs/plugins/menu/#looks), with no page items, no look row and no owner actions ([The visitor menu](https://clayjs.com/docs/plugins/menu/#the-visitor-menu)) |
|
|
330
330
|
| `derive` | on for any page with a `[sap]` root | yes | the sapjs core: records and paints, treated as derived ([sap.js: records, views and totals in markup](https://clayjs.com/docs/guide/components/#sap-js-records-views-and-totals-in-markup)) |
|
|
331
|
-
| `
|
|
331
|
+
| `blockeditor` | off | reader half only (`blockeditor-view`) | the block editor, `clay.blockeditor`; implies `live`, `sync`, `upload`, `wire` and `ai-edit`, drops `richclay` unless named, and leaves `undo` out if both are named |
|
|
332
332
|
| `unsent` | off | no | `unsent="<name>"`: unsent text in chrome survives a redraw and a reload, `clay.unsent` |
|
|
333
333
|
| `bind-text` | off | no | `clay.bindText`: an editor textarea outside the records, bound to one record's text |
|
|
334
334
|
| `people` | off | yes | who is editing and who wrote what |
|
|
@@ -339,8 +339,8 @@ Plugins add editing, sync, and UI features to `clay.js`. Most are off by default
|
|
|
339
339
|
- `exclude` wins over implications. `exclude=wire` also drops `ai-edit`.
|
|
340
340
|
- Load order is fixed regardless of how you list them.
|
|
341
341
|
- Use `exclude=richclay` when the document has no rich text. `exclude=source` saves one request and about 48 KB, at the cost of saves reprinting the whole document instead of keeping hand formatting.
|
|
342
|
-
- Nothing implies `undo`, on purpose: name it only when the document has discrete DOM commands that should undo as whole-page steps. It leaves Cmd+Z to text fields ([undo: whole-page undo](https://clayjs.com/docs/guide/components/#undo-whole-page-undo)).
|
|
343
|
-
- In view mode, `
|
|
342
|
+
- Nothing implies `undo`, on purpose: name it only when the document has discrete DOM commands that should undo as whole-page steps. It leaves Cmd+Z to text fields ([undo: whole-page undo](https://clayjs.com/docs/guide/components/#undo-whole-page-undo)), and it cannot load with `blockeditor`, which keeps its own Yjs undo history on `clay.blockeditor.history`. Naming both `blockeditor` and `undo` leaves `undo` out with a console warning.
|
|
343
|
+
- In view mode, `blockeditor` loads only its reader half and nothing it implies. A Document on `plugins=blockeditor` resolves to `blockeditor-view`, with no live data service, no generic `sync`, and no `upload`. Name `sync` as well (`plugins=blockeditor,sync`) when the reader should take its saved files from sync's lane instead of its own.
|
|
344
344
|
- `derive` loads automatically when the page has a `[sap]` root at boot, and stays idle when full sapjs is on the page. `exclude=derive` opts out; `plugins=derive` loads it for a page that adds its `[sap]` root later.
|
|
345
345
|
- `visitor-drafts` loads its draft store only in draft mode. A reader or an owner editing the original gets a small module that publishes an inert `clay.visitorDrafts` and, for the owner, the owner chip. On a page with no `visitor-drafts` slot, no `api` rules tag, or a rules tag this version cannot read (not `data-rules-version="1"`, or not JSON), it warns in the console and draws nothing. [Free documents for visitors](https://clayjs.com/docs/guide/free-documents/) covers the opt in.
|
|
346
346
|
- `visitor-menu` loads only in draft mode (`draftOnly` and `editOnly`). A reader in view mode and an owner editing the original skip it and download zero bytes ([The visitor menu](https://clayjs.com/docs/plugins/menu/#the-visitor-menu)).
|
|
@@ -350,7 +350,7 @@ Measure what each mode you ship downloads, and keep a byte budget and a test tha
|
|
|
350
350
|
**Defaults by document kind:**
|
|
351
351
|
|
|
352
352
|
- **Owned document:** `?plugins=sync,indicator,menu&exclude=richclay`. It gives a plain-text document live sync, a save status chip, and an Edit switch (`source` is on by default in edit mode, and `derive` turns on when `[sap]` is present). A document opens in view even for its owner, which is why `menu` is in the default; declare `<html opens-in="edit">` on a tool document that must edit as soon as it opens.
|
|
353
|
-
- **Free document:** `?plugins=visitor-drafts,visitor-menu,menu&exclude=richclay`. In draft mode, ClayJS drops `menu`, `sync`, `upload`, `people`, `source`, and the other host plugins while `visitor-drafts` and `visitor-menu` run; when the owner edits the original, `visitor-menu` is skipped and `menu` and `source` run;
|
|
353
|
+
- **Free document:** `?plugins=visitor-drafts,visitor-menu,menu&exclude=richclay`. In draft mode, ClayJS drops `menu`, `sync`, `upload`, `people`, `source`, and the other host plugins while `visitor-drafts` and `visitor-menu` run; when the owner edits the original, `visitor-menu` is skipped and `menu` and `source` run; a reader in view mode on someone else's document gets no ClayJS menu at all (`visitor-menu` loads only in draft mode, and `menu` is for people who can edit).
|
|
354
354
|
- **Add by task on either kind:** `sortable` for `[sortable]` lists, `upload` for attachments, `people` for collaborator names, `bind-text` for a bound `<textarea>`, `unsent` for `[unsent]` inputs, `indicator` when a free document has no `savestatus` UI of its own, `sync` when the owner of a free document co-edits across tabs, and `undo` only when the document runs discrete DOM commands (adding, removing, or moving records) that should undo as whole-page steps. Leave `undo` off when editing happens inside text inputs, textareas, or `contenteditable`, which already use the browser's native undo; otherwise `undo` also groups idle DOM mutations into page steps on Cmd+Z outside the field. For formatted rich text, drop `exclude=richclay` and use `editable`.
|
|
355
355
|
|
|
356
356
|
### 2.3 Satellites
|
|
@@ -381,11 +381,11 @@ In Hyperclay Local and HTML Clay, a document needs no special loading to work of
|
|
|
381
381
|
|
|
382
382
|
`clay.plugins` is the frozen list of plugin names in the plan the loader resolved, late plugins included. It says what the page asked for, not what has finished loading.
|
|
383
383
|
|
|
384
|
-
`clay.ready` means the loader is done, not that a plugin has finished its own startup. The `
|
|
384
|
+
`clay.ready` means the loader is done, not that a plugin has finished its own startup. The `blockeditor` plugin keeps going: it exports no `ready`, so `clay.ready` fires while the person still sees the article's saved content. `clay.blockeditor.ready` is that plugin's boot, and `clay.blockeditor.status` is its current result, `{ phase, reason, installId, at }`. Await it before touching `clay.blockeditor`:
|
|
385
385
|
|
|
386
386
|
```js
|
|
387
387
|
clay.start("document-ui", async () => {
|
|
388
|
-
const boot = await clay.
|
|
388
|
+
const boot = await clay.blockeditor?.ready;
|
|
389
389
|
if (boot?.phase !== "ready") return;
|
|
390
390
|
document.documentElement.setAttribute("document-ready", "true");
|
|
391
391
|
});
|
|
@@ -400,7 +400,7 @@ Boot moves through `loading` (the saved article is read and validated), `joining
|
|
|
400
400
|
| `document:authority-ready` | the capability and first-save gates passed, the page handle is admitted and the binding is created |
|
|
401
401
|
| `document:input-ready` | adoption succeeded and editable was enabled on the current mounted view |
|
|
402
402
|
|
|
403
|
-
A failed or cancelled installation never emits `document:input-ready`. Blocked readiness still resolves: `clay.
|
|
403
|
+
A failed or cancelled installation never emits `document:input-ready`. Blocked readiness still resolves: `clay.blockeditor.ready` settles, `clay.blockeditor.status.phase` is `blocked` with a `reason`, the saved article stays as the host served it, and no editable page appears. [When the page editor is ready](https://clayjs.com/docs/guide/edit-mode/#when-the-page-editor-is-ready) covers the reasons and what a page can offer instead.
|
|
404
404
|
|
|
405
405
|
**The two waits overlap.** The editor's code downloads while authority is prepared, so the boot waits for the longer of the two plus adoption, never for their sum. Neither branch touches the page: only the coordinator mounts, adopts, and then enables input.
|
|
406
406
|
|
|
@@ -817,7 +817,7 @@ Use `contenteditable="plaintext-only"` for titles, labels, and cells to prevent
|
|
|
817
817
|
|
|
818
818
|
### 3.11 When the page editor is ready
|
|
819
819
|
|
|
820
|
-
The mode this tab starts in is decided at load, but a `
|
|
820
|
+
The mode this tab starts in is decided at load, but a `blockeditor` document is ready to edit later. `clay.blockeditor.ready` resolves when its editor is mounted and accepts typing. `clay.blockeditor.status` holds that boot result: `{ phase, reason, installId, at }`.
|
|
821
821
|
|
|
822
822
|
`clay.ready` does not wait for the page editor. Because `plugins/page.js` exports no `ready`, `await clay.ready` can resolve while the saved article is still on screen. Late plugins do wait, so a page that ships with them needs nothing extra.
|
|
823
823
|
|
|
@@ -828,14 +828,14 @@ Each boot stage records a `performance.mark` with `{ detail: { installId } }`:
|
|
|
828
828
|
- `document:authority-ready`: the capability and first-save gates passed, the page handle is admitted, and the binding is created. Writer election and the article's feature views are still ahead.
|
|
829
829
|
- `document:input-ready`: adoption succeeded and editing was enabled on the mounted view. A failed or cancelled installation never emits it.
|
|
830
830
|
|
|
831
|
-
When boot is blocked, `clay.
|
|
831
|
+
When boot is blocked, `clay.blockeditor.ready` still resolves rather than throwing an error. `clay.blockeditor.status.phase` is `blocked`, and `reason` names where boot stopped (`missing-capability`, `first-save-refused`, `first-save-conflict`, `admission-failed`, `editor-assets-failed`, `mount-failed`, `adoption-refused`, `adoption-timeout`, `adoption-error`, `blockeditor-replaced`, `view-detached`, `no-blockeditor`, `ambiguous-blockeditor`, `invalid-blockeditor-source`, `invalid-blockeditor-json`, `unsupported-blockeditor-format`, `missing-blockeditor-store`, `boot-incomplete`). Nothing accepts keystrokes: the saved article stays as the host served it, or an already-mounted editor is left read-only because the page was replaced. Base your fallback on `phase`, not on a caught exception:
|
|
832
832
|
|
|
833
833
|
```js
|
|
834
|
-
const boot = await clay.
|
|
834
|
+
const boot = await clay.blockeditor?.ready;
|
|
835
835
|
if (boot?.phase !== "ready") document.documentElement.setAttribute("read-only", "true");
|
|
836
836
|
```
|
|
837
837
|
|
|
838
|
-
`clay.
|
|
838
|
+
`clay.blockeditor.install()` disposes the last installation and starts a fresh one with its own id, letting a page retry after a failure without reloading.
|
|
839
839
|
|
|
840
840
|
### 3.12 The page editor on a phone
|
|
841
841
|
|
|
@@ -1274,7 +1274,9 @@ html[unsaved="true"]:not([savestatus="error"]):not([savestatus="offline"]) .chip
|
|
|
1274
1274
|
|
|
1275
1275
|
In draft mode, nothing is saved on purpose, and `html[draftmode="true"]` indicates why the page is not marked saved.
|
|
1276
1276
|
|
|
1277
|
-
Put `#status` in chrome, or load the `indicator` plugin for a ready-made chip. The chip's drawing arrives on demand, at the first input or the first save state, so it costs nothing at edit ready.
|
|
1277
|
+
Put `#status` in chrome, or load the `indicator` plugin for a ready-made chip. The chip's drawing arrives on demand, at the first input or the first save state, so it costs nothing at edit ready. ClayJS's own save feedback stays hidden while a save is on its way and after it lands, and shows words only when a save does not go through: **Couldn't save**, or **Offline, will retry**. The chip shows them when it is loaded; with `clay-ui.js` loaded, they are persistent toasts that stay up until the next save lands. Set `clay.saveToast = true` to also get a "Saved" toast after every save.
|
|
1278
|
+
|
|
1279
|
+
A failed save is never silent. If a save fails on a page with neither the `indicator` plugin nor `clay-ui.js`, ClayJS downloads its toast code at that moment and shows the toast `clay-ui.js` would have shown, until a later save lands. A page that draws its own failure notice from the `clay:save-error` and `clay:save-offline` events opts out with `window.clay = { saveFeedback: "own" }` before clayjs loads, or `clay.saveFeedback = "own"` at any time before a save fails.
|
|
1278
1280
|
|
|
1279
1281
|
Events, all on `document`:
|
|
1280
1282
|
|
|
@@ -1334,12 +1336,12 @@ Collaborators see each other's work a few seconds after each pause. Without `aut
|
|
|
1334
1336
|
|
|
1335
1337
|
#### Sync in the view-mode reader
|
|
1336
1338
|
|
|
1337
|
-
A Document in view mode loads its reader (`
|
|
1339
|
+
A Document in view mode loads its reader (`blockeditor-view`), not the editor. The reader has no write half and sees saved files only:
|
|
1338
1340
|
|
|
1339
1341
|
- **Saved, not live.** It uses its own receive-only transport (`src/sync/saved-stream.js`): a startup GET once the stream opens, then frames from the saved lane, and a refetch and repair after a reconnect or gap. It never POSTs, never sends a live relay, and needs no write credentials.
|
|
1340
1342
|
- **One receipt.** The reader holds the bytes on screen, their ETag, and the sequence they cover as one `{ bytes, etag, seq }` receipt. Duplicate or older frames are dropped without advancing it, a stamp alone never repairs missing bytes, and a refused or failed read keeps the existing article and receipt.
|
|
1341
1343
|
- **Keyed replacement.** An accepted file replaces only the blocks whose saved form changed (`src/page/reader-patch.js`). Unchanged blocks keep their nodes, so open toggles, mounted tools, and find-in-page survive.
|
|
1342
|
-
- **Explicit `sync` keeps one owner.** On `plugins=
|
|
1344
|
+
- **Explicit `sync` keeps one owner.** On `plugins=blockeditor,sync` in view mode, the `sync` plugin already owns the saved lane. The reader opens no stream of its own and takes the files that lane applied (`clay:sync-applied` with `source: "disk"`). Neither case adopts a peer's unsaved preview as a saved file.
|
|
1343
1345
|
|
|
1344
1346
|
#### A frame from another template version
|
|
1345
1347
|
|
|
@@ -1493,7 +1495,7 @@ clay.presence.setTarget({ kind: "record", id: "card-1", field: "title" });
|
|
|
1493
1495
|
people: [{
|
|
1494
1496
|
id: "p_ada", name: "Ada Lovelace", you: false, canEdit: true,
|
|
1495
1497
|
editing: { kind: "record", id: "card-1", field: "title" },
|
|
1496
|
-
sessions: [{ id: "s1", you: false, n: 12, editing: { kind: "
|
|
1498
|
+
sessions: [{ id: "s1", you: false, n: 12, editing: { kind: "blockeditor", v: 1, epoch, generation, selection } }],
|
|
1497
1499
|
}],
|
|
1498
1500
|
anonymous: 0,
|
|
1499
1501
|
}
|
|
@@ -1501,7 +1503,7 @@ clay.presence.setTarget({ kind: "record", id: "card-1", field: "title" });
|
|
|
1501
1503
|
|
|
1502
1504
|
Before discovery answers, the snapshot is unsupported with no people. The host decides who appears: each recipient gets a roster filtered by its own access, and nothing in the page can widen it (`src/sync/live-sync.js`, the `presence` event).
|
|
1503
1505
|
|
|
1504
|
-
**Targets.** A target is `null`, a legacy dotted path string, a record target `{ kind: "record", id, field }` with `field` optional, or a
|
|
1506
|
+
**Targets.** A target is `null`, a legacy dotted path string, a record target `{ kind: "record", id, field }` with `field` optional, or a block editor target `{ kind: "blockeditor", v: 1, epoch, generation, selection }`. A block editor selection is `text` (`anchor` and `head`, each `{ block, rel }`), `blocks` (`{ ids }`), or `cell` (`{ block, row, col }`).
|
|
1505
1507
|
|
|
1506
1508
|
The bounds come from `src/sync/presence-target.js`:
|
|
1507
1509
|
|
|
@@ -1514,7 +1516,7 @@ The bounds come from `src/sync/presence-target.js`:
|
|
|
1514
1516
|
|
|
1515
1517
|
An invalid target is refused, never repaired. Updates are coalesced to one every 250 ms, a stable target renews every 5 seconds, and it expires 15 seconds after the host receives it.
|
|
1516
1518
|
|
|
1517
|
-
**A record target from a focused control.** Publish a target only while the control that writes the record has focus, and publish `null` the moment it stops: on blur, when the open record changes, when the page is hidden, when the record goes to Trash, and when the tab leaves edit mode or enters draft mode. After a blur, check again on the next microtask, so focus moving inside the same control does not flash `null`. A record with one writing control sends the fieldless shape, `{ kind: "record", id }`, where `id` is the record's `data-id`. A target never carries a title, text or date. The Journal template does this in `RecordPresence` (`
|
|
1519
|
+
**A record target from a focused control.** Publish a target only while the control that writes the record has focus, and publish `null` the moment it stops: on blur, when the open record changes, when the page is hidden, when the record goes to Trash, and when the tab leaves edit mode or enters draft mode. After a blur, check again on the next microtask, so focus moving inside the same control does not flash `null`. A record with one writing control sends the fieldless shape, `{ kind: "record", id }`, where `id` is the record's `data-id`. A target never carries a title, text or date. The Journal template does this in `RecordPresence` (`apps/unreleased/00-journal/journal.html`):
|
|
1518
1520
|
|
|
1519
1521
|
```js
|
|
1520
1522
|
function reconcile() {
|
|
@@ -1538,9 +1540,9 @@ function reconcile() {
|
|
|
1538
1540
|
|
|
1539
1541
|
In view mode the reader draws the roster too. It takes the roster from the stream it already follows, as a named `presence` event, so it opens no second connection. The roster UI is a first-use download that comes with the first presence frame, and a reader that receives no frame downloads none of it. A reader publishes no target and joins no writable room, because its lease has no write half: a target a page sets in view mode sends nothing. Its roster goes away when its stream closes.
|
|
1540
1542
|
|
|
1541
|
-
Each part needs its own extension: `presence` for the roster, `presence-editing` for the generic editing marker, `record-awareness-v1` for record targets, and `
|
|
1543
|
+
Each part needs its own extension: `presence` for the roster, `presence-editing` for the generic editing marker, `record-awareness-v1` for record targets, and `blockeditor-awareness-v1` for remote carets.
|
|
1542
1544
|
|
|
1543
|
-
**Runtime only.** The roster UI is marked `clay="no-save no-watch no-snapshot"` (`RUNTIME_ONLY` in `src/ui/bevel-controls.js`, applied in `src/sync/presence.js`). It never reaches a save, a snapshot, or the page's own change detection, and a roster change is not an edit. Remote carets are editor decorations, not document content ([Remote carets](https://clayjs.com/docs/plugins/
|
|
1545
|
+
**Runtime only.** The roster UI is marked `clay="no-save no-watch no-snapshot"` (`RUNTIME_ONLY` in `src/ui/bevel-controls.js`, applied in `src/sync/presence.js`). It never reaches a save, a snapshot, or the page's own change detection, and a roster change is not an edit. Remote carets are editor decorations, not document content ([Remote carets](https://clayjs.com/docs/plugins/blockeditor/#remote-carets)). All three tokens are needed: `no-save` alone only keeps a name out of the file, and the snapshot clone that peers receive is emitted before the save-only regions are stripped, so `no-snapshot` is what keeps a name out of that clone, while `no-watch` is what keeps a roster change from counting as an edit (`src/sync/presence.js:15-28`).
|
|
1544
1546
|
|
|
1545
1547
|
Presence is not for app data. For state that must be shared and is not presence, use a live store ([Live data: `clay="live"`](https://clayjs.com/docs/guide/live-sync/#live-data-clay-live)).
|
|
1546
1548
|
|
|
@@ -1549,20 +1551,20 @@ Presence is not for app data. For state that must be shared and is not presence,
|
|
|
1549
1551
|
A document records the lowest page format a writer must understand, in document metadata outside the page value:
|
|
1550
1552
|
|
|
1551
1553
|
```html
|
|
1552
|
-
<meta name="clay-
|
|
1554
|
+
<meta name="clay-blockeditor-format" content="1">
|
|
1553
1555
|
```
|
|
1554
1556
|
|
|
1555
1557
|
No marker means floor 0, and a floor only ever rises: a later save cannot remove the marker or lower it. The value is protocol capability, not permission. The host still applies every access check it applied before, and the marker cannot be used to discover a private document.
|
|
1556
1558
|
|
|
1557
|
-
**Every
|
|
1559
|
+
**Every write declares its capability.** A save and a live write carry `X-Clay-Blockeditor-Format: 1`, the format the runtime knows rather than the format this document uses, so even an ordinary document's save declares it.
|
|
1558
1560
|
|
|
1559
|
-
**A host refuses a write below the floor.** Nothing is mutated. The answer is HTTP `409` with a body carrying the code `
|
|
1561
|
+
**A host refuses a write below the floor.** Nothing is mutated. The answer is HTTP `409` with a body carrying the code `blockeditor-format-upgrade-required`, the same string as its `reason` on the save lanes, and `minimum`, the floor the document requires. That covers the ordinary save, the live data lanes, a checkpoint, a version restore and the relay, on all three hosts, and a refused live request ends that tab's stream so it receives no newer content. The stored file, its version history and its block editor state stay as they were, and the refused tab keeps its own unsaved work.
|
|
1560
1562
|
|
|
1561
1563
|
**A writer raises the floor on its first new format, through one real save.** The gate reads the floor the document already has, asks the host, and adds the marker to an otherwise equivalent capture. Activation is idempotent. It is refused while any admitted live page editor declares a capability below the floor it would set, and that check happens under the host's per-document write lock, so an editor that registers between the check and the save is refused rather than missed. A refused activation leaves ordinary writing available and every unsaved store as it was, and says **This document uses a newer page format; update the editor in the other tabs to keep writing**. The command that asked applies only if the same editor installation and document revision still exist once activation succeeds; otherwise the answer is retryable and the content is untouched.
|
|
1562
1564
|
|
|
1563
|
-
**A host announces `
|
|
1565
|
+
**A host announces `blockeditor-format-v1` only when it can hold the floor**, meaning its enforcement and its tolerant checkpoint renderer are installed. On a host that does not announce it, a new format command is refused as `blockeditor-format-unsupported` and writes nothing.
|
|
1564
1566
|
|
|
1565
|
-
**Who reads what.** ClayJS 1.16.0 and later keep floor 1 content, so those tabs are the cohort that can stay open while a floor 1 document is written. The published 1.15.x tabs cannot be changed: once a document's floor rises they are refused writes with `
|
|
1567
|
+
**Who reads what.** ClayJS 1.16.0 and later keep floor 1 content, so those tabs are the cohort that can stay open while a floor 1 document is written. The published 1.15.x tabs cannot be changed: once a document's floor rises they are refused writes with `blockeditor-format-upgrade-required` instead of saving a version that would drop the new content, and their own unsaved work stays in the tab. A floor 0 document keeps working on all of them.
|
|
1566
1568
|
|
|
1567
1569
|
Opening a document that is already at floor 1 asks for nothing: only a command that writes a newer format raises a floor, and a document that never uses one never records one.
|
|
1568
1570
|
|
|
@@ -1572,7 +1574,7 @@ An agent edits a malleable file the way a person in another tab does:
|
|
|
1572
1574
|
|
|
1573
1575
|
- **Editing the file on disk** (HTML Clay, Hyperclay Local): the host notices the change and pushes a `disk` frame. The open tab merges it three ways, so the user's unsaved typing in other paragraphs survives, and an overlap goes to the ledger. Tell agents to address elements by `data-id`, never by position, and to leave chrome alone (it is not in the file anyway).
|
|
1574
1576
|
- **`clay.wire`** (`?plugins=wire`, HTML Clay and Hyperclay Local): `clay.wire.send(payload)` sends a request to a local process started with `htmlclay wire serve file.htmlclay -- my-agent`. The page saves first so the agent reads what the user sees, and autosave pauses until the agent's edit lands as a disk frame. `run.done` resolves then.
|
|
1575
|
-
- **`ai-edit`** (`?plugins=ai-edit`): select text, press Cmd+J, and describe the change. The rewrite is previewed in place; Keep saves it,
|
|
1577
|
+
- **`ai-edit`** (`?plugins=ai-edit`): select text, press Cmd+J, and describe the change. The rewrite is previewed in place; Keep saves it, Discard drops it. Available where the host offers the `ai-edit` helper: HTML Clay, Hyperclay Local, and hyperclay.com (for the owner and team editors on a paid plan, billed to the document's AI credits).
|
|
1576
1578
|
- **The data API**: a host that announces `data-read`/`data-write` serves `/_/api/<file>`, a JSON read and write interface over the file defined by a rules tag ([Import and export](https://clayjs.com/docs/guide/architecture/#import-and-export)). `clay.readData()` and `clay.writeData()` call it from the page; `writeData` is for agents and other processes, and refuses while the page has unsaved edits.
|
|
1577
1579
|
|
|
1578
1580
|
### 6.10 Live data: `clay="live"`
|
|
@@ -1636,7 +1638,7 @@ Two rules for the proxy:
|
|
|
1636
1638
|
|
|
1637
1639
|
`game.status.mode` is `fast` on the first two lanes and `file` on the third. In view mode, a store reads normally, but writes stay in the tab and are never sent or saved.
|
|
1638
1640
|
|
|
1639
|
-
**The
|
|
1641
|
+
**The block editor's own store.** A Document's blocks are a live store named `blockeditor`, and its merge state is not in the file. Saved files and downloads never carry it. On hyperclay.com and Hyperclay Local, the host keeps the merge state beside the file and hands it back to an editor only while it matches the article's `data-rev`. HTML Clay keeps none on disk: open tabs share it in memory, and a fresh open rebuilds it from the article. A page changed outside the editor (by the code editor, an agent, a version restore, another device, or a text editor) merges into open page tabs the way a peer's edit does: each block merges three ways against the article the change started from, so edits on both sides survive. An edit beats a delete. Where both sides changed the same words, the outside change wins and the tab offers a download of what it had. Only a change with no article this host can trace it back to shows a reload notice and stops the tab from saving over the new article.
|
|
1640
1642
|
|
|
1641
1643
|
**When a room is lost.** A room lives in memory: a server restart or two minutes with nobody connected ends it. The next room starts from the last checkpoint, so up to 8 seconds of play can be lost. A change that the old room may already have taken is not resent to the new one, even when its acknowledgment was lost: if the old room's last checkpoint lands afterward, it brings that change back, and resending it would count it twice. A change that never left the tab is sent as usual. Closing the tab that runs a local room is not a loss: the next tab takes over the room with every move.
|
|
1642
1644
|
|
|
@@ -1727,7 +1729,7 @@ Traps:
|
|
|
1727
1729
|
|
|
1728
1730
|
#### 7.1.1 The page editor's Markdown shortcuts
|
|
1729
1731
|
|
|
1730
|
-
`editable` regions and the page editor are different editors with different Markdown input rules. The page editor (`plugins/
|
|
1732
|
+
`editable` regions and the page editor are different editors with different Markdown input rules. The page editor (`plugins/blockeditor`) converts these as you type:
|
|
1731
1733
|
|
|
1732
1734
|
| Typed at the start of a block | Becomes |
|
|
1733
1735
|
|---|---|
|
|
@@ -2344,8 +2346,8 @@ Use `clay="editor-ui"` instead for CSS that viewers must not receive or that rep
|
|
|
2344
2346
|
| Presence (`clay.presence`) | tabs of this browser, shown as "This computer" | tabs on this computer, relayed by the app | one person per account and one session per tab, filtered per recipient |
|
|
2345
2347
|
| `by` on frames, editing marker | no | no | yes |
|
|
2346
2348
|
| Live stores ([Live data: `clay="live"`](https://clayjs.com/docs/guide/live-sync/#live-data-clay-live)) | tabs of one browser at full speed | tabs of one browser at full speed; other devices at save speed | every editor at full speed |
|
|
2347
|
-
|
|
|
2348
|
-
| Wire
|
|
2349
|
+
| Block editor merge state ([Live data: `clay="live"`](https://clayjs.com/docs/guide/live-sync/#live-data-clay-live)) | kept beside the file: `document.blockeditorState.href` returns the state stored with the exact bytes on disk | kept beside the file | kept beside the file |
|
|
2350
|
+
| Wire, ai-edit and blockeditor-tool | yes | yes | ai-edit and blockeditor-tool, for owners and team editors on a paid plan |
|
|
2349
2351
|
| Data API write | yes | yes | read only |
|
|
2350
2352
|
| Uploads | 25 MB, in `~/htmlclay/uploads/` | 25 MB, in the root's `uploads/` | 10, 20 or 100 MB by plan, in the account's `uploads/` |
|
|
2351
2353
|
| Upload access (`document.upload.access`) | `local` | `local` | `document` once the account's uploaded files are protected; no field before that |
|
|
@@ -2354,11 +2356,13 @@ Use `clay="editor-ui"` instead for CSS that viewers must not receive or that rep
|
|
|
2354
2356
|
|
|
2355
2357
|
The `/_/meta` answer carries `document.maxBytes`: the maximum uncompressed UTF-8 HTML save body, inclusive. All three hosts send it, and ClayJS measures a save against it before sending, so a document that is over the limit is refused instead of lost ([Size limits](https://clayjs.com/docs/guide/saving/#size-limits)). A missing or unusable value means the limit is unknown, and the host's own refusal is the only answer.
|
|
2356
2358
|
|
|
2357
|
-
On hyperclay.com only, the same block carries `document.maxBytesMeasures: "file"`. It means the limit is measured without the `<script merge="
|
|
2359
|
+
On hyperclay.com only, the same block carries `document.maxBytesMeasures: "file"`. It means the limit is measured without the `<script merge="blockeditor">` tag that holds the document's live state. HTML Clay and Hyperclay Local send no such field.
|
|
2358
2360
|
|
|
2359
2361
|
A plain static server or a file opened from disk can show the file in view mode, but cannot save it. Use the `demo` plugin for a public demo that saves into each visitor's browser.
|
|
2360
2362
|
|
|
2361
|
-
On hyperclay.com, every tab holds its own sync
|
|
2363
|
+
On hyperclay.com, every tab holds its own sync stream. The site serves HTTP/2, which carries every tab's stream over one connection, so the HTTP/1.1 limit of six connections per origin does not apply.
|
|
2364
|
+
|
|
2365
|
+
Hyperclay Local serves plain HTTP/1.1, where a browser opens at most six connections per origin and every open sync stream holds one of them. When the host announces `sync`, `live-data` and `sync-worker-data`, ClayJS edit tabs on that origin share one connection through a shared worker, which opens one stream for all of them. A document pinned to an older ClayJS version and a page built on hyperclayjs do not share, so each of their tabs still holds its own stream, and six such tabs can still keep a seventh from loading. HTML Clay announces `sync-worker` instead, and its tabs already share one stream. See [Live updates between tabs](https://hyperclaylocal.com/docs/live-updates/#one-connection-for-every-edit-tab) for the details.
|
|
2362
2366
|
|
|
2363
2367
|
### 9.2 Getting started on each
|
|
2364
2368
|
|
|
@@ -2376,7 +2380,43 @@ The saved file is the data. Everyone who can download it can read everything in
|
|
|
2376
2380
|
- HTML Clay and Hyperclay Local report `local`, meaning their own file-serving rules, not a sharing ACL. A file there is as private as the folder on that machine.
|
|
2377
2381
|
- Password inputs are never persisted. An authored `value` on them is still in the file.
|
|
2378
2382
|
|
|
2379
|
-
### 9.4
|
|
2383
|
+
### 9.4 Data-loss recovery
|
|
2384
|
+
|
|
2385
|
+
When a browser save or an outside edit overwrites saved data, ClayJS shows a persistent `Saved data was overwritten` warning. It reports the write source, the removed items or erased values, and a preview of the first changed field. Only editors in edit mode see it. Readers and draft visitors never see it. An owner in view mode sees it on entering edit mode. HTML Clay also shows its own bar in view mode, in edit mode until ClayJS's notice takes over, and always on documents pinned to an older ClayJS that has no notice.
|
|
2386
|
+
|
|
2387
|
+
The notice has three buttons:
|
|
2388
|
+
|
|
2389
|
+
- `Restore`, when `restorable` is `true`, puts the lost data back into the current page and reloads. When `droppedAdditions` is above `0`, the notice says `Restoring removes N items added or changed since.` The field counts the current list items that have no identical item in the saved data, so a `Restore` removes or overwrites each of them: items added since the loss and items changed since the loss.
|
|
2390
|
+
- `Undo the change`, when `canRevert` is `true`, restores the whole earlier file and reloads.
|
|
2391
|
+
- `Keep as is` posts `"dismiss"` to adopt the current page as the baseline and closes the notice without reloading. The toast's `X` only hides it in this tab.
|
|
2392
|
+
|
|
2393
|
+
A host announces `data-guard-v1` in `/_/meta` `extensions` and gives each editor a same-origin `document.dataGuard.href` per document. A client never builds that address.
|
|
2394
|
+
|
|
2395
|
+
- `GET <href>` answers `200` with `Cache-Control: no-store` and `{"event": null}` or `{"event": {...}}`:
|
|
2396
|
+
|
|
2397
|
+
```json
|
|
2398
|
+
{
|
|
2399
|
+
"event": {
|
|
2400
|
+
"id": "uuid",
|
|
2401
|
+
"firstDetectedAt": 1762700000000,
|
|
2402
|
+
"lastWriteAt": 1762700000000,
|
|
2403
|
+
"lossSummary": { "provenance": "external", "destroyedAtoms": 4, "removedItems": 2, "modifiedAtoms": 0, "paths": ["api.items[2]"] },
|
|
2404
|
+
"fieldCount": 1,
|
|
2405
|
+
"preview": [{ "key": "items", "now": "2 items", "yours": "4 items" }],
|
|
2406
|
+
"droppedAdditions": 0,
|
|
2407
|
+
"restorable": true,
|
|
2408
|
+
"canRevert": true,
|
|
2409
|
+
"status": "pending"
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
```
|
|
2413
|
+
|
|
2414
|
+
- `POST <href>` takes `{"id", "choice"}` (`"restore"`, `"revert"` or `"dismiss"`). It answers `200` `{"ok": true, "choice", "status"}`, or `{"error"}` with `400` for an unknown choice, `404` when no event is pending, `409` when another event id is current, and `422` when that choice cannot be applied to the page.
|
|
2415
|
+
- A host with `sync` also sends `{"type": "notification", "msgType": "data-loss", "action": "raised" | "resolved", "msg", "data"}` on the document's live lane, with the event in `data` for `"raised"` and `{"id"}` for `"resolved"`.
|
|
2416
|
+
|
|
2417
|
+
A host should announce `data-guard-v1` only after the conformance rows for this capability pass.
|
|
2418
|
+
|
|
2419
|
+
### 9.5 Offline in a local host
|
|
2380
2420
|
|
|
2381
2421
|
Hyperclay Local and HTML Clay each bundle one exact, published ClayJS release, chosen in their `offline-runtime.lock.json`. They ship the whole release listed in `dist/offline-manifest.json` in the npm package, not only what one document boots with. That index lists about 550 files: every module, plugin, satellite such as `sap.js` and `clay-router.js`, vendored library, theme sheet and font, plus the exact library builds older Journals load from other CDNs. A plugin, the merge engine, a drag library or a font that a document uses for the first time offline still loads.
|
|
2382
2422
|
|
|
@@ -2401,7 +2441,7 @@ ClayJS gives you one file per document that edits and saves itself. Here is what
|
|
|
2401
2441
|
|
|
2402
2442
|
Two plugins replace the old advice to hand-build a [block writer](https://clayjs.com/docs/guide/recipes/#block-writer) or wait for autosave:
|
|
2403
2443
|
|
|
2404
|
-
- **The [
|
|
2444
|
+
- **The [block editor](https://clayjs.com/docs/plugins/blockeditor/)** (`?plugins=blockeditor`) is a Notion-like editor for one `article[data-blockeditor]`, backed by Yjs, where typing reaches other people as they type. It includes headings, toggle headings, lists, checklists, toggles, callouts, code, images, files, typed tables, boards, charts from a table, mentions of people and dates, emoji, a page icon and cover, CSV and Markdown import, and Markdown and self-contained HTML export.
|
|
2405
2445
|
- **[Live stores](https://clayjs.com/docs/plugins/live-sync/)** (`clay="live"` and `clay.live()`) move fast-changing state at network speed (25 frames per second) instead of save speed. Use [live data](https://clayjs.com/docs/guide/live-sync/#live-data-clay-live) for games, scores, and shared state.
|
|
2406
2446
|
|
|
2407
2447
|
The old limits apply elsewhere. Ordinary `editable` regions and DOM records have no CRDT and merge at save time. Rich text outside the page editor has no tables, and without `live-data`, the page editor falls back to file saves.
|
|
@@ -2410,11 +2450,11 @@ The old limits apply elsewhere. Ordinary `editable` regions and DOM records have
|
|
|
2410
2450
|
|
|
2411
2451
|
| What you are comparing to | What ClayJS gives | What is missing | What to build instead |
|
|
2412
2452
|
|---|---|---|---|
|
|
2413
|
-
| **Google Docs** | Keystroke co-editing, tables, code, callouts, and images in the [page editor](https://clayjs.com/docs/plugins/
|
|
2453
|
+
| **Google Docs** | Keystroke co-editing, tables, code, callouts, and images in the [page editor](https://clayjs.com/docs/plugins/blockeditor/); word-level save merge in `editable`; recoverable conflicts; version history; `ai-edit` | Comments, suggestion mode, live cursors outside the page editor, pagination, footnotes, `.docx` export, tables in `editable` rich text | The [page editor](https://clayjs.com/docs/plugins/blockeditor/) or a [block writer](https://clayjs.com/docs/guide/recipes/#block-writer); comments as `<aside data-id data-anchor>`; print CSS for PDF |
|
|
2414
2454
|
| **Google Sheets** | Persisted inputs, sap formulas over named fields, aggregates, formats, CSV, page tables and charts from a table, 10,000-row pages saving in about 400 ms | A1 references, ranges, fill-down, recalculation graph, virtualization, range selection, pivots | A page table and chart, or a [typed table](https://clayjs.com/docs/guide/recipes/#typed-table-a-spreadsheet-you-can-be-honest-about) of a few thousand rows, one file per year |
|
|
2415
2455
|
| **Google Slides** | Slides as sections, sortable, uploads, view mode as the published deck | Free positioning, guides, masters, presenter view in a second window, transitions, PPTX export | A flow-layout deck with fixed layouts per slide ([slides](https://clayjs.com/docs/guide/recipes/#slides)) |
|
|
2416
2456
|
| **Trello and Linear** | Page boards where cards have an owner and a due date; sortable columns, inline editing, checklists, attachments, undo, per-card sync | Notifications, reminders, cross-board search, mentions, integrations, boards past a few thousand cards | A page board, or the [kanban with card detail](https://clayjs.com/docs/guide/recipes/#kanban-with-card-detail) with labels, due dates, and a due-soon projection |
|
|
2417
|
-
| **Notion** | One document per file in the [page editor](https://clayjs.com/docs/plugins/
|
|
2457
|
+
| **Notion** | One document per file in the [page editor](https://clayjs.com/docs/plugins/blockeditor/) with real-time co-editing, Markdown shortcuts, derived titles, blocks, tables, CSV import, boards, and charts | A workspace of many pages, databases with relations and rollups, backlinks, sub-pages, workspace search, page permissions | A folder of files, one per page or database, plus an index file; links are `<a href>` |
|
|
2418
2458
|
| **CRM** | Records, pipeline board, card owners and due dates, notes, CSV, totals, contact forms via `message` | Email logging, reminders, dedupe, per-row permissions | The [CRM follow-up list](https://clayjs.com/docs/guide/recipes/#crm-follow-up-list), computing due rows on open |
|
|
2419
2459
|
| **Finance apps** | Rows with amounts, budgets, sap totals painted in view mode by `derive`, CSV import of bank exports, charts from a table | Bank sync, learned categorization, multi-currency, scheduled transactions, encryption at rest | A monthly or yearly budget file with CSV import ([finance tracker](https://clayjs.com/docs/guide/recipes/#finance-tracker)) |
|
|
2420
2460
|
| **Full CMS** | Per-page [CMS](https://clayjs.com/docs/plugins/cms/) sidebar, per-page style group for colors, fonts, and variants, uploads with crop, view mode as the site, JSON data API | Multi-page collections, shared layouts, drafts and scheduled publishing, roles, media library, i18n | A [content site](https://clayjs.com/docs/guide/recipes/#content-site) with one file per page, a shared stylesheet, and the [CMS](https://clayjs.com/docs/plugins/cms/) per page |
|
|
@@ -2422,7 +2462,7 @@ The old limits apply elsewhere. Ordinary `editable` regions and DOM records have
|
|
|
2422
2462
|
### 10.3 Limits that apply to every document
|
|
2423
2463
|
|
|
2424
2464
|
- **No comments.** ClayJS has no comment model (`ai-edit` is an AI prompt, not stored comments). Store comments as `<aside data-id data-anchor>` elements.
|
|
2425
|
-
- **Live cursors only in the
|
|
2465
|
+
- **Live cursors only in the block editor.** The block editor draws other people's carets and selections where the host announces `blockeditor-awareness-v1`. Elsewhere, `clay.presence` tells a page who is here and which record they are writing in, and the page draws its own badges ([Presence](https://clayjs.com/docs/guide/live-sync/#presence)).
|
|
2426
2466
|
- **No suggestion mode, pagination, footnotes, or `.docx` and PPTX export.** Edits apply directly without tracked changes, page breaks, footnotes, or Office exporters. Use print CSS for PDF.
|
|
2427
2467
|
- **Per-file edit access.** There are no per-row permissions. Split data for different audiences into separate files.
|
|
2428
2468
|
- **No background work in a closed file.** Nothing runs in a closed file, and a live room ends after two minutes with nobody connected. Compute due states on open.
|
|
@@ -2453,7 +2493,7 @@ With sapjs, the counts and progress badges become paints, the filters an `each=`
|
|
|
2453
2493
|
|
|
2454
2494
|
### 11.2 Block writer
|
|
2455
2495
|
|
|
2456
|
-
For a Notion-like document, use the [page editor](https://clayjs.com/docs/plugins/
|
|
2496
|
+
For a Notion-like document, use the [page editor](https://clayjs.com/docs/plugins/blockeditor/) (`?plugins=blockeditor`) instead. This recipe is for a block app whose blocks are your own records.
|
|
2457
2497
|
|
|
2458
2498
|
[`examples/writer.html`](https://clayjs.com/examples/writer.html). Plugins `sync,sortable,undo,upload,indicator`, satellite `clay-ui`.
|
|
2459
2499
|
|
|
@@ -2562,10 +2602,13 @@ The rung is where a free document lives, so the details matter:
|
|
|
2562
2602
|
|
|
2563
2603
|
`clay.isDraftMode` is true, and everything else follows from it:
|
|
2564
2604
|
|
|
2565
|
-
- **
|
|
2566
|
-
- **`menu` stays dropped for visitors, and `visitor-menu` runs instead.** `menu` carries the Edit switch, SEO settings, presence, look deletion (`keep()`), and `/_/meta` capability checks, all of which belong to the owner. When the loader tag names `visitor-menu`, draft mode loads `visitor-menu` in its place and mounts `clay.menu` with **Download
|
|
2605
|
+
- **Draft mode drops the plugins that talk to the host.** It drops `sync`, `upload`, `quickcrop`, `cms`, `people`, `menu`, `upgrade`, `wire`, `ai-edit`, `demo` and `source`, even when the loader tag names them. A document that does not name [`blockeditor`](https://clayjs.com/docs/plugins/blockeditor/) also drops `blockeditor` and `live`. A block editor document names `blockeditor` on its loader tag and has an `article[data-blockeditor]`. On a block editor document, draft mode keeps `blockeditor` and `live` so a visitor can write in the editor. `clay.upload`, `clay.cms`, `clay.conflicts` and `clay.source` are absent, and code that guards them (`clay.upload?.(...)`) behaves as it does for a viewer. Draft mode drops the `[refetch-on-save]` attribute too, since it would fetch its element from the host again after every save.
|
|
2606
|
+
- **`menu` stays dropped for visitors, and `visitor-menu` runs instead.** `menu` carries the Edit switch, SEO settings, presence, look deletion (`keep()`), and `/_/meta` capability checks, all of which belong to the owner. When the loader tag names `visitor-menu`, draft mode loads `visitor-menu` in its place and mounts a `"Settings"` gear button (`clay.menu`) with **Undo** and **Redo** (when the page has `clay.blockeditor.history` or `clay.undo`), **Download copy**, and **Make your own copy** (`/_/copy/<document path>`), with no owner code and no `/_/meta` request ([The visitor menu](https://clayjs.com/docs/plugins/menu/#the-visitor-menu)). It shows no `[clay-menu-item]` items from the page (so no appreciation link), no look row even with `clay-menu-looks`, no **Show intro**, and no **Start over**; page scripts may still add rows with `clay.menu.addItem`, and `clay.variants` is still published when two or more [looks](https://clayjs.com/docs/plugins/menu/#looks) exist. A reader in view mode on someone else's document gets no ClayJS menu at all: `visitor-menu` loads only in draft mode, and `menu` is for people who can edit.
|
|
2607
|
+
- **The page editor runs with no host.** In draft mode the page editor opens no journal, asks the host nothing (no `/_/meta`), and opens no live connection.
|
|
2608
|
+
- **Visitors cannot add pictures or files in the page editor.** The slash menu has no Image or File, and the more menu has no Add cover. The page editor refuses any dropped or pasted picture. The menu shows the note "Download your copy to add pictures and files."
|
|
2567
2609
|
- **The save lane is the browser.** `clay.save()` and autosave both run, `savestatus` moves as usual, and the save answers `msgType: "success"` with `msg: "Saved in this browser"`. A storage failure answers the 507 path with `code: "draft-storage"` and fires `clay:drafts-store-failed`; the next good write clears it.
|
|
2568
|
-
- **
|
|
2610
|
+
- **The draft record keeps the block editor state as well as the rules data.** The stored record is `{ v: 1, savedAt, id, ancestry, data, live, blockeditor }`. `data` holds the rules data. `live` holds the values of the document's live stores: `<script type="application/json" merge="blockeditor" clay="live">` and the others such as `blockeditor.tools` and `blockeditor.settings`. The `blockeditor` store's value holds base64 Yjs updates. `blockeditor` is `{ rev, html }`, holding the article's `data-rev` and its HTML. On restore, ClayJS writes the stores and the article into the document before the block editor reads them. When this tab has never edited the article and it still matches the served one, ClayJS does not record it, so a visitor who never wrote in the block editor gets the owner's later article. Once a tab has recorded the article, it keeps recording it, even when the visitor deletes back to the served text. If none of the saved `data` fields fit the current rules, ClayJS still restores the article and live stores and keeps the whole record under the `:unreadable:` key, as for a partial fit.
|
|
2611
|
+
- **No request reaches the host.** The download link goes to the host's download page, which holds the email sign-up. Hyperclay's download page sends the host only the rules data. In the visitor's browser, it adds the stored page to the file, writing the page's store tags and article into the file the host returns. The prompt's own stylesheet is a Google Fonts link.
|
|
2569
2612
|
- **`clay.readData()` and `clay.writeData()` refuse.** Both reject with `code: "DataUnavailableInDraft"`: the saved file belongs to the owner, and this visitor's work is in their browser, so there is nothing for them to read or write there. A document that calls them in draft mode gets that refusal instead of a request ([Import and export](https://clayjs.com/docs/guide/architecture/#import-and-export)).
|
|
2570
2613
|
- **`clay.visitorDrafts`** is the plugin's whole surface:
|
|
2571
2614
|
|
|
@@ -2599,7 +2642,9 @@ A draft that no longer fits the page is never overwritten. When the stored JSON
|
|
|
2599
2642
|
|
|
2600
2643
|
### 12.6 Two tabs
|
|
2601
2644
|
|
|
2602
|
-
Each save merges what the other tab saved.
|
|
2645
|
+
Each save merges what the other tab saved. For rules data, fields merge one by one, and lists of rows with ids merge row by row: the merge keeps a row one tab added, removes a row one tab removed, and keeps both changes when different tabs changed different rows. When both tabs changed the same field, reordered a list, or moved one row to different places, ClayJS saves this tab's value and keeps the other tab's whole record under `<key>:conflict`.
|
|
2646
|
+
|
|
2647
|
+
On a page document, when another tab records a page, this tab's page editor merges that page into its own. Both are Yjs documents, so the result holds both tabs' writing, and the next save records the union. The merge also runs when the page editor starts listening, for an event it missed, and when the tab returns from the back-forward cache. A tab that merged another tab's page follows `clay.visitorDrafts.reset()` there. If the page editor refuses the merge, ClayJS keeps the other tab's whole record under `<key>:conflict`, as it does for rules data.
|
|
2603
2648
|
|
|
2604
2649
|
### 12.7 The download prompt and the intro area
|
|
2605
2650
|
|
|
@@ -2610,7 +2655,7 @@ Both remember themselves per slot and path in `localStorage["clay:visitor-drafts
|
|
|
2610
2655
|
**The intro** is authored per document as `<template visitor-intro>` (the default) or `<template visitor-intro="manual">`. The template holds the author's whole strip: its markup and any `<style>` it needs. The plugin clones it into `<section class="clay-intro">` at the very top of `document.body`, before the document's own content.
|
|
2611
2656
|
|
|
2612
2657
|
- **Default (`<template visitor-intro>`):** adds a quiet `x` button (`Hide this introduction`) in the corner and dismisses for good when the visitor clicks `x` or scrolls the strip past the top of the viewport. UI memory records `"closed"` or `"scrolled"`, and no menu item is added.
|
|
2613
|
-
- **Manual (`<template visitor-intro="manual">`):** adds a **Hide intro** button in the corner instead of `x`, and never
|
|
2658
|
+
- **Manual (`<template visitor-intro="manual">`):** adds a **Hide intro** button in the corner instead of `x`, and is never hidden by scrolling. Clicking **Hide intro** (or `[visitor-intro-dismiss]`) records `"closed"` in UI memory, removes the section, and focuses the document. Hiding is remembered for that visitor, so it stays hidden, and there is no menu item to bring it back (`?intro=1` in the address still opens it). A returning visitor with a saved draft still sees it until they hide it.
|
|
2614
2659
|
|
|
2615
2660
|
A `<script type="module">` inside the template runs when the intro first mounts. Animate it with `clay.assets.load('uimotion')`, never an engine copied into the file, so a visitor the intro skips loads no motion code.
|
|
2616
2661
|
|
|
@@ -2626,7 +2671,7 @@ When there is a release video, add it as the first element of the template, and
|
|
|
2626
2671
|
<video src="/_/uploads/assets/kanban/release.mp4" poster="/_/uploads/assets/kanban/release-poster.jpg" controls playsinline preload="metadata"></video>
|
|
2627
2672
|
```
|
|
2628
2673
|
|
|
2629
|
-
An element inside the template with `[visitor-intro-start]` scrolls smoothly to the document below the intro, and one with `[visitor-intro-dismiss]` closes the intro the same way the corner button does. A deep link (a URL with a `#...`) skips the intro on load so a shared link opens where it points, an existing draft skips it
|
|
2674
|
+
An element inside the template with `[visitor-intro-start]` scrolls smoothly to the document below the intro, and one with `[visitor-intro-dismiss]` closes the intro the same way the corner button does. A deep link (a URL with a `#...`) skips the intro on load so a shared link opens where it points, an existing draft skips the default `<template visitor-intro>` (while `<template visitor-intro="manual">` still shows until the visitor hides it), and `?intro=1` forces it open.
|
|
2630
2675
|
|
|
2631
2676
|
**The prompt** needs nothing from the document but its name: a small tab at the bottom right ("This app is free to keep") that opens into a card holding the download link and "Don't show again", with a minimize button in its corner. The tab's X and the card's minimize button leave a small "Keep" button with the download icon that reopens the download card. That choice survives reload. It opens itself once after the visitor has touched three fields. "Don't show again" stops those reminders for that slot but keeps the icon, including for previously dismissed notices. A storage failure opens it in its error state, and the next good save clears it. In that error state the download link does not go to the host, whose download page would read the stored copy that just failed to be written: the plugin builds the file from the live page in the browser instead, named after the page file with `.htmlclay` at the end.
|
|
2632
2677
|
|