One engine, two apps: porting to the browser
The browser build of p2pbuilders (web/) runs as a PearBrowser P2P site —
same permissionless board, no terminal required. The interesting part of the
port is what we did not write.
The reuse bet
Our earlier app peerit (a P2P Reddit) had already been hardened into a
generic gossip engine: Ed25519 identity, canonical signing, the
signature-authority verification gauntlet
(The transport carries no authority), per-user
outboxes, and a last-write-wins gossip merge. That layer is app-agnostic —
seven modules reused verbatim: crypto.js, verify.js, sync.js,gossip.js, identity.js, markdown.js, util.js.
What p2pbuilders added on top: an HN record schema, proof-of-work, reputation
weighting, HN ranking, the social graph, and the UI. The engine did not know
what a "post" was before, and still does not.
The one change the engine needed
PoW gating has to happen at ingest — un-worked records must never enter
the view, no matter who relays them. But hashcash is an app policy; baking it
into the engine would have ended the genericity. The resolution: a singlevalidate admit hook — the engine calls it on every record before
merge, the app supplies the PoW check. One seam, both properties kept. Where
you put the seam is the difference between "shared library" and "fork".
Browser realities
- Same schema, different spelling: records keyed like
post!<board>!<cid>, vote!<target>!<voter> — LWW where the terminal
app has an append-only log, converging on the same shapes
(Data patterns).
- Crypto: SubtleCrypto Ed25519 where available; a loudly-labeled insecure
dev fallback where not (the status chip tells you which world you are in).
- Transport: on PearBrowser,
window.pear.syncbridges to the real
swarm; on a plain dev server, localStorage cooperation simulates peers —
open two tabs, switch users, watch gossip.
Lesson
Port the engine boundary, not the app. The terminal and web builds share
almost no code — they share invariants: signed records, verify-on-ingest,
fold-to-view. That is the part worth keeping identical, because it is the
part that holds the security model.