End-to-end encrypted document collaboration for small conclaves — trusted groups that, true to the word's Latin root (cum clave, "with a key"), are defined by holding one. An independent web app: the Signal protocol (X3DH + Double Ratchet) provides the crypto, Nostr relays are dumb transport, and any browser can run it. No accounts live on any server.
Named after the card-rack in Poe's The Purloined Letter — a letter hidden in plain sight, the model for the metadata-privacy design.
Status: research prototype (v0.5). The cryptographic core is real; the system is not audited. Do not use for anything that actually needs to stay secret yet.
- Real Signal protocol in the browser — X3DH key agreement + Double Ratchet,
via
@privacyresearch/libsignal-protocol-typescript. - Documents that merge themselves — documents are Yjs CRDTs: concurrent
edits auto-merge instead of overwriting each other; the owner sequences
and fans out changes (see
docs/model.md). - Rich-text editing — a Tiptap (ProseMirror) editor with headings, lists, and task lists, bound to a local draft replica: typing never touches the shared document, and incoming remote changes merge in under your cursor instead of conflicting with your draft.
- Manual + opt-in real-time editing — by default edits stay on your device until you hit Commit; per document you can switch on real-time sync (edits stream as you type), gated behind a confirmation that spells out the metadata trade-off (relays can see edit timing, though never the text).
- Owner-centric sharing — and unsharing — invite collaborators by their
public key with an
editor/viewerrole (their signed prekey bundle is verified before the handshake; the ACL is enforced on the receiving side), and remove them again: removal rotates the doc key so future epochs exclude them. Honest limit: removal revokes access, not the past — a removed member may keep local copies. - Document management — create and rename documents (click the title to edit it inline); the owner's rename propagates to members.
- Per-document key epochs — owner-minted random content keys with rotation and encrypted distribution; groundwork for the decided key-custody model (the service never holds doc keys).
- Metadata-private transport — every message is sent from a throwaway key to a one-time mailbox address derived from a secret shared inside the encrypted invite. Relays see only ciphertext between unlinkable addresses, and the relay list is yours to configure (with per-relay health probes). The project ships no infrastructure — no official relay, no server, no component you must trust because we run it; conclaves use public relays.
- Self-healing sync — state-vector anti-entropy recovers arbitrary message loss and offline gaps; a decrypt failure triggers an automatic re-handshake. Delivery is hardened against real-world public-relay failures: a publish no relay accepts is queued and retried, subscriptions refresh after sleep or network changes, and snapshot publishing is throttled to stay under relay rate limits.
- Mnemonic accounts — a 12-word BIP39 phrase is the account (create,
restore, and log out — logout wipes the device, and the phrase is the
only way back in). No server login. Existing nostr identities can be
imported: bare
nsec, raw hex, or password-protected NIP-49ncryptsec(an imported account has no phrase; its nsec is the recovery secret). - Stateless client — a new device restores every document from the phrase alone (encrypted account snapshot on the relay); no other member need be online.
- Local-first storage — documents persist in IndexedDB (falling back to
localStoragewhere IndexedDB is unavailable). A single-writer Web Lock makes a second tab of the same account read-only, protecting the Signal ratchet store. - Runs from a single file —
npm run buildemits one self-containeddist/index.htmlyou can open directly (file://), serve, or download as an offline copy from the deployed page itself.
Development is organized into nine modules with 1:1 file seams — demands are
named by module, features branch by module. The full map (what each module
owns, what's done, what's next) lives in docs/modules.md:
| # | Module | Owns |
|---|---|---|
| 1 | Identity | who you are (accounts, import, logout) |
| 2 | Files | document lifecycle |
| 3 | Editor | the editing surface |
| 4 | Conclave | who is in the group, what they may do |
| 5 | Sync | convergence of state (CRDT, anti-entropy) |
| 6 | Keys | cryptography & custody |
| 7 | Transport | wire + metadata privacy |
| 8 | Storage | where bytes rest |
| 9 | Distribution | how the app reaches people |
See ROADMAP.md for positioning, the five design invariants,
and the phased plan; docs/model.md for the settled document
model and key custody; docs/storage.md for the storage
exploration.
app/ current source — Vite + TypeScript workspace
src/core/ DOM-free protocol logic (account, signal, dockey, chains, ydoc, kv, orchestrator)
src/ui/ thin DOM layer (editor, IndexedDB backend + Web Lock, sanitizer)
test/ vitest unit + integration tests (run in Node, no browser)
legacy/ the original single-file HTML proof of concept (v0.3–v0.4) + LAN serve scripts
docs/ design notes (modules.md — module map; model.md — document model; storage.md)
ROADMAP.md design invariants, architecture, phased plan
The core/ modules have no DOM dependency and are tested directly in Node. The
single-file index.html deliverable is produced as a build artifact, not hand-edited.
cd app
npm install
npm test # vitest (sequential files): signal, dockey, epochs, chains, ydoc, kv, collab, remove, import, …
npm run dev # local dev server (recommended way to run it)
npm run build # single-file dist/index.html (vite-plugin-singlefile)Two live probes (live-relay.probe.test.ts, live-friend.probe.test.ts) talk
to real public relays and are skipped unless run with LIVE=1.
The single-file dist/index.html opens directly over file:// (just double-click
it) as well as over HTTP. One caveat on file://: browsers block IndexedDB and Web
Locks there, so storage falls back to localStorage and the multi-tab read-only
lock is unavailable — for multi-tab use or day-to-day development, prefer
npm run dev or serving the build over HTTP.
This is a research prototype. The cryptographic core is real, but the system has
known gaps documented in ROADMAP.md (prekey reuse, unsigned membership,
owner-online requirement, metadata at the invite bootstrap). It has not been
reviewed by anyone. Treat it as a design exploration.
All documents and codes are generated by Claude Fable & Opus4.8 except for THIS sentence.