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:

lua
-- 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.

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. There are two proof skins so far, a dark slate BBS and a bright teal crafts board, to keep me honest that the theming actually reaches everything. 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.

It’s the most fun I’ve had building on atproto since Pollen. Forums were social software before we called it that, and it turns out the protocol wants to be a forum more than it wants to be a feed.