board / README.md
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
# board

Multi-user **p2p, end-to-end encrypted** infinite whiteboard PWA at
**https://board.ardegazu.ro** — no origin server, no accounts, no server-side
storage the operator can read.

Public mirror (served from IPFS via git's dumb-HTTP protocol — no git server):

```sh
git clone https://git.ardegazu.ro/board.git
```

MIT licensed. Built on the sueta p2p stack
(`git clone https://git.ardegazu.ro/chat.git`) — the stack's durable core is
`ardegazu-rooms-kit`, sha-pinned and compiled off the classpath (`deps.edn`);
`client/src/board/lib/` holds only board's own layer, chiefly the allow-list
access layer (`wrapbox.cljs`, `policy.cljs`, `epoch.cljs`).

```
the board       an infinite pan/zoom canvas, white or black; pen (Apple Pencil
                pressure), text, sticky notes, rect/ellipse/line/arrow,
                connectors that follow the shapes they attach to, images from
                the clipboard, an eraser, PNG export
the data        every change is one op in an end-to-end-encrypted append-only
                log (OrbitDB), replicated member⇄member and folded identically
                on every device; live strokes/cursors ride sealed WebRTC
                broadcasts and never touch the log
offline         changes made alone reach late joiners through an encrypted
                store-and-forward mailbox the node cannot read (sueta v16);
                a peer who was never online with the author still converges
boards          capability links — the secret lives in the URL fragment and
                never leaves the browser; your board list lives on your device
                and syncs only between devices holding the same identity
private boards  the creator can flip a board to invite-only: content re-homes
                onto a hidden second log whose keys are granted per-identity
                (X25519 sealed boxes); URL holders can only knock — name,
                message, key fingerprint — and any approved member can let
                them in; removing someone re-keys everything after
identities      persistent Ed25519 seeds with emoji fingerprints signing every
                op; TOFU with key-change warnings
```

## The access model, honestly

- **Link mode (default):** the link IS the key. Anyone holding it reads and
  draws. Sharing the link is sharing the board.
- **Invite-only mode:** the URL still finds the board, but content moves to a
  vault log rooted in a secret distributed only inside per-identity sealed
  grants. A URL holder cannot compute the vault's address, its mailbox room,
  or any content key. What they DO see: history from before the switch (they
  could always have read it), and the knocking ritual — who requests and who
  approves. **Removal is not retraction**: an ex-member keeps everything they
  were ever granted, and can watch (but not read) the vault's traffic shape;
  they can never open content sealed after their removal.
- The board link of a strict-capable board is `#<secret>.<creatorPub>` — the
  creator's key rides in the link itself, so policy authority never depends on
  log ordering. Only the creator flips modes and removes members; approvals
  are open to every member (that's the point of a whiteboard).

## Layout

| Path | What |
|---|---|
| `client/` | ClojureScript PWA (shadow-cljs); `src/board/lib/` is board's own layer (the access layer + mailbox/protocol/selftest — the durable core comes from `ardegazu-rooms-kit` on the classpath), `src/board/app/` is the whiteboard |
| `client/test/` | golden vectors extracted from the pre-port TypeScript + the black-box suite that replays them (`npm test`) |
| `client/e2e/` | Playwright suites: mesh convergence, offline mailbox, access layer — each spawns its own relay |
| `deploy/relay/` | ~130-line libp2p dev relay (websocket + circuit-relay-v2 + gossipsub discovery) |
| `deploy/publish-repo.sh` | anonymous source-mirror builder (publish.git toolkit) |
| `site/` | landing page for the git mirror |

## Dev

Needs node >= 22, a JVM >= 17 and the `clojure` CLI (the VPS never builds).

```sh
# terminal 1 — dev relay on :9090 (deterministic PeerId, baked into the client's dev default)
cd deploy/relay && npm install && npm run dev

# terminal 2 — app on :5173 (shadow-cljs watch + :dev-http; the relay's
# origin allowlist covers 4173/5173 ONLY)
cd client && npm install && npm run dev

# unit + golden-vector suite (no ports, no browser)
cd client && npm test

# browser suites — each spawns its own relay and a static server over dist,
# so BUILD FIRST; stop the terminal-1 relay before running them
cd client && npm run build
cd client && node e2e/board.e2e.mjs      # mesh convergence, LWW edits, connectors, tombstones, replay
cd client && node e2e/access.e2e.mjs     # strict boards: stranger-blindness, knock/approve, removal re-key
# the mailbox suite needs the stub endpoints baked into the build — see its header
cd client && node e2e/mailbox.e2e.mjs
```

## Deploy

```sh
cd client && npm run release   # bumps version.json + builds dist/
# then: ird ipfs add client/dist → ird ipfs ipns publish board.ardegazu.ro <cid>
```

The relay + TURN + mailbox are ird's managed p2p service (shared with the
sueta app; the distinct app salt keeps every derivation, room and mailbox id
disjoint). The client re-reads the host's `/.well-known/ap2p` descriptor at
boot, so infrastructure keys can rotate without a client release.

The source mirror republishes via `deploy/publish-repo.sh` (anonymity gate:
single anonymous author, zero identity bytes in any git object) and the
ecosystem's assembler, which re-points `git.ardegazu.ro`.

Boards are a full mesh, comfortable up to ~12 peers. Bigger would need
different architecture, which would no longer be this project.

static mirror of HEAD · about · clone: git clone https://git.ardegazu.ro/board.git