Classics
A book club for six working through Penguin's hundred must-read classics, where nobody's writing on a book is visible to anybody else until the last member has finished it.

At a glance
- Members rank the books by pairwise comparison (binary insertion); rankings are aggregated into a club canon and compared against Penguin's reader vote
- Spoiler protection as the core requirement: verdicts stay sealed until every member has finished a book, and notes appear to each reader only once they reach that point in the text
- Built with Go and SQLite, server-rendered with no framework; self-contained binary with embedded assets
- Self-hosted in Docker behind Caddy on a Hetzner VPS (infrastructure managed with Terraform), with a strict Content-Security-Policy and Argon2 password hashing
Stack
- Go
- SQLite
- html/template
- argon2id
- Docker
- Caddy
- Terraform
- Hetzner Cloud
The problem
Six of us decided to read Penguin’s hundred must-read classics. The reading was never the hard part; the talking about it was. A group chat has no memory and no sense of who is where, so whoever finishes first either says nothing for a fortnight or spoils the book for everybody still halfway through. It also keeps no list, which leaves the question the whole exercise is about — is Penguin’s order any good? — unanswerable.
So the app has one rule it is not allowed to break: nothing it shows may reveal any part of a book to somebody who has not read that far. Not a note, not a verdict, not a count. Everything else in it is negotiable.
What I built
A single Go binary with SQLite behind it, server-rendered, no framework and no build step. Three things it does:
- A sealed vault per book. You write your verdict when you finish, and nobody can read anybody else’s until the last member is done. Giving up counts as done — otherwise one person abandoning Ulysses would seal that discussion permanently.
- Marginal notes anchored in the text. A note is pinned at a position rather than a page, and surfaces to each reader only once they pass it. Six people read six editions, so a note written at page 120 of 300 shows up at “p.240” for somebody with a 600-page copy, and as a percentage on a Kindle.
- A ranking built by argument. Finishing a book drops you into placing it: binary insertion, about seven comparisons to place the hundredth. Everyone’s list is aggregated into the club’s own canon and set against Penguin’s reader vote.
What I took from it
The club is five other people, and they are in the room for everything. A change does not land in an analytics dashboard here — it lands in the group chat, and somebody has an opinion about it the same evening. Watching them get genuinely excited about a pace chart, or argue over where a book belongs in the ranking, is a much shorter feedback loop than any amount of instrumentation, and a much harder one to ignore.
It is also what makes the thing fun to work on. Building for people who actually want it — and who will say so, or say the opposite, over dinner — is a different job from building to a spec, and it keeps the backlog honest: nothing gets built here that nobody asked for.