Skip to content
All projects

Classics

Aug 2026 – PresentSolo — design, build, infrastructure

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.

Three screens from classics — the hundred-book shelf with reading progress, the current round's pace chart, and a book's verdicts both sealed and opened

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.