WIP! A BB-style forum, on the ATmosphere!
We're still working... we'll be back soon when we have something to show off!
node
typescript
hono
htmx
atproto
atBB Monorepo#
atBB is a decentralized BB-style forum built on the AT Protocol. Users own their posts on their own PDS; the forum's AppView indexes and serves them. Lexicon namespace: space.atbb.* (domain atbb.space is owned). License: AGPL-3.0.
The master project plan with MVP phases and progress tracking lives at docs/atproto-forum-plan.md.
Packages#
| Package | Description | Port |
|---|---|---|
@atbb/lexicon |
AT Proto lexicon definitions (YAML) + generated TypeScript types | — |
@atbb/appview |
Hono JSON API server — indexes forum data, serves API | 3000 |
@atbb/web |
Hono JSX + HTMX server-rendered web UI — calls appview API | 3001 |
@atbb/spike |
PDS read/write test script for validating AT Proto operations | — |
Dependency chain: @atbb/lexicon builds first (generates types), then @atbb/appview and @atbb/web build in parallel. Turbo handles this via ^build.
Development#
Setup#
devenv shell # enter Nix dev shell (Node.js, pnpm, turbo)
pnpm install # install all workspace dependencies
cp .env.example .env # configure environment variables
Commands#
pnpm build # build all packages (lexicon → appview + web)
pnpm dev # start all dev servers with hot reload
pnpm clean # remove all dist/ directories
devenv up # start appview + web servers via process manager
pnpm --filter @atbb/appview dev # run a single package
pnpm --filter @atbb/spike spike # run the PDS spike script
Environment Variables#
See .env.example. Key variables:
PORT— server port (appview: 3000, web: 3001)FORUM_DID— the forum's AT Proto DIDPDS_URL— URL of the forum's PDSAPPVIEW_URL— URL the web package uses to reach the appview APIFORUM_HANDLE,FORUM_PASSWORD— forum service account credentials (for spike/writes)
Lexicon Conventions#
- Source of truth is YAML in
packages/lexicon/lexicons/. Never edit generated JSON or TypeScript. - Build pipeline: YAML → JSON (
scripts/build.ts) → TypeScript (@atproto/lex-cli gen-api). - Adding a new lexicon: Create a
.yamlfile underlexicons/space/atbb/, runpnpm --filter @atbb/lexicon build. - Record keys: Use
key: tidfor collections (multiple records per repo). Usekey: literal:selffor singletons. - References: Use
com.atproto.repo.strongRefwrapped in named defs (e.g.,forumRef,subjectRef). - Extensible fields: Use
knownValues(notenum) for strings that may grow (permissions, reaction types, mod actions). - Record ownership:
- Forum DID owns:
forum.forum,forum.category,forum.role,modAction - User DID owns:
post,membership,reaction
- Forum DID owns:
AT Protocol Conventions#
- Unified post model: There is no separate "topic" type. A
space.atbb.postwithout areplyref is a topic starter; one with areplyref is a reply. - Reply chains:
replyRefhas bothroot(thread starter) andparent(direct parent) — same pattern asapp.bsky.feed.post. - MVP trust model: The AppView holds the Forum DID's signing keys directly and writes forum-level records on behalf of admins/mods after verifying their role. This will be replaced by AT Protocol privilege delegation post-MVP.
TypeScript / Hono Gotchas#
@types/nodeis required as a devDependency in every package that usesprocess.envor other Node APIs.tsxdoesn't need it at runtime, buttscbuilds will fail without it.- Hono JSX
children: UsePropsWithChildren<T>fromhono/jsxfor components that accept children. Unlike React, Hono'sFC<T>does not includechildrenimplicitly. - HTMX attributes in JSX: The
typed-htmxpackage provides types forhx-*attributes. Seepackages/web/src/global.d.tsfor the augmentation. - Glob expansion in npm scripts:
@atproto/lex-clineeds file paths, not globs. Usebash -c 'shopt -s globstar && ...'to expand**/*.jsonin npm scripts. .envloading: Dev and spike scripts use Node's--env-file=../../.envflag to load the root.envfile. Nodotenvdependency needed.
Git Conventions#
- Do not include
Co-Authored-Bylines in commit messages. prior-art/contains git submodules (Rust AppView, original lexicons, delegation spec) — reference material only, not used at build time.- Worktrees with submodules need
submodule deinit --all -fthenworktree remove --forceto clean up.