I miss forums. Not the abstract idea of them, the actual texture of them: a signature block with a quote you picked out in 2006, a postcount that meant you’d been around, a board index you checked every morning to see what was new since last night. Discord threw all of that out and called it an upgrade. atmoBB is me trying to build phpBB again, except this time the posts belong to the people who wrote them.
A forum is an account
The thing that makes this work is that a forum is its own atproto account. It has a DID and a handle like @retrocomputing.example.com, and the boards, the rules, and the moderation log all live in that account’s repo. What doesn’t live there is the posts. When you reply to a thread, the reply gets written to your repo, on your PDS. The forum doesn’t own your words. It points at them.
So one login carries everywhere. Your display name, avatar, and signature come from your account, and they show up the same on every atmoBB forum you post to. Ranks are the one thing that stay local: each forum has its own postcount-to-title ladder, so you can be a nobody on one board and an elder on another, the way it always was.
There’s a stranger feature that falls out of this model almost for free. A board can be marked as sharing a topic with the whole atmosphere. Post a thread in a ⁂ Music board here and it shows up in every other forum’s Music board too, and theirs show up in yours, because the underlying records are the same records and the topic is just a tag they all agree to read. Your posts stay in your account either way. It’s federation you get by not building walls in the first place.
The part I didn’t build
Reading a forum means gathering records scattered across hundreds of personal repos and stitching them back into a thread that renders in order. That aggregation is the expensive, fiddly infrastructure, and I didn’t want to run a firehose consumer and a Postgres index I’d have to babysit just to see who replied.
So atmoBB reads from HappyView, a general-purpose appview for the atmosphere. I write my lexicons, HappyView indexes anything that matches them, and I describe my read logic in Lua. Every XRPC endpoint the app calls is one small Lua file with a SQL query against the records HappyView already has. getThreadPage, getBoardIndex, getDirectory, each one is a handful of lines:
-- xrpc.query:app.atmobb.forum.getDirectory
-- Every forum the index has seen, in founding order.
local rows = db.raw([[
SELECT did, record, created_at FROM happyview_records
WHERE collection = $1 AND rkey = 'self'
ORDER BY created_at ASC
]], { "app.atmobb.forum.profile" })Writes hook the same way. onThreadCreate and onReplyCreate fire when a matching record lands, and they’re where postcounts get bumped and shared-topic threads get fanned out. I got to spend my time on the forum instead of on a pipeline.
The old furniture, rebuilt
The point was never to make something that looks like 2004. Silkscreen and tiny pixel fonts got tried and retired early. The point was the furniture that made forums feel like places: gradient category bars, boxy panels, sunken post bodies, ranks under avatars, “who’s online” that’s actually live and never stored as a record because presence isn’t something you should be able to subpoena later. There’s a userpic maker in there too, which crops a photo down to a 100 × 100 icon with an optional border. It writes an ordinary avatar blob, so the icon follows you to forums that have never heard of the maker.
Each forum owns its skin through a --forum-* token set, so a sysop can reskin the whole place while the chrome and behavior stay shared. The house skin is a cool greige with a coral accent. All the built-in CSS sits in a low-priority cascade layer, which means a sysop pastes plain CSS into Admin → Appearance and it wins without a single !important. The one skin I ship as an example is called keith’s cute forum, and it is pink and teal and a little bit sparkly. The composer is TipTap, the front end is SvelteKit, and handles and DIDs are always shown in mono, never hidden, because on the atmosphere your address is real and pretending otherwise is the dishonest move.
You can run your own
atmobb went up on GitHub on September 1st, MIT licensed. Self-host it and you publish nothing to me. Your forum shows up in the directory, your members arrive carrying the profiles and postcounts they already have, and any board you give a topic slug merges with every other board in the atmosphere on that topic. Skip the slug and you’re just a place that happens to share members with everyone else.
The forum is really eleven record types under app.atmobb.*, published from their own authority account, and anyone can read or write them. Build a phone client, a text-mode reader, a bot that mirrors a board to email. If it writes an app.atmobb.discussion.thread record, its posts show up here. Or take your own namespace, publish your own schemas, and run a network that has never heard of atmobb.
The directory turns into a real webring, too, which I did not plan and am delighted by: /ring/next, /ring/prev, /ring/random. Every deployment serves its forum DID at /.well-known/atproto-did, so a forum claims its own domain as its handle with no DNS fiddling, and https://<forum-handle> is just the forum.
There is still a lot to build! There’s no search, no notifications, no RSS, no private messages (☠️ do I just lean on germ for this???), and no reactions.