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

docs: sync atproto-forum-plan.md with codebase and Linear

Audit of plan doc against Linear issues and codebase state as of 2026-02-24.

Completed work added:
- ATB-25: separate bannedByMod column from deleted (Phase 3 bug fix, PR #56)
- ATB-35: strip title from reply records at index time (Phase 3 bug fix, PR #55)
- ATB-26: neobrutal design system, shared components, route stubs (Phase 4, PR #39)
- ATB-33: server-side offset/limit pagination for GET /api/topics/:id (Phase 4, PR #57)
- Fix ATB-30/31 attribution: compose forms are ATB-31, login/logout is ATB-30

Key Risks section:
- Mark PDS write path resolved (OAuth 2.1 + PKCE, ATB-14)
- Mark record deletion resolved (tombstone handling + bannedByMod split, ATB-25)

New Known Issues / Active Backlog section:
- ATB-39 (High): upgradeBootstrapMembership writes PDS record without role field
- ATB-38 (High): seedDefaultRoles partial failure should fail fast
- ATB-41 (Medium): missing $type on forumRef/boardRef in PDS writes
- ATB-34 (Low): axe-core WCAG AA automated tests
- Notes ATB-39/40 are duplicates of ATB-37/38

Future Roadmap:
- Add SQLite support (design approved, docs/plans/2026-02-24-sqlite-support-design.md)
- Update user self-deletion note: deleted_by_user column already in schema (ATB-25)

+43 -4
+43 -4
docs/atproto-forum-plan.md
··· 220 220 - Files: `apps/appview/src/lib/ban-enforcer.ts` (new), `apps/appview/src/lib/indexer.ts` (3 handler overrides), `apps/appview/src/lib/__tests__/indexer-ban-enforcer.test.ts` (new), `apps/appview/src/lib/__tests__/indexer.test.ts` (additions) 221 221 - [x] Document the trust model: operators must trust their AppView instance, which is acceptable for self-hosted single-server deployments 222 222 - ATB-22 | `docs/trust-model.md` (new) — covers operator responsibilities, user data guarantees, security implications, and future delegation path; referenced from deployment guide 223 + - [x] **ATB-25: Separate ban enforcement from user-initiated deletes (Bug Fix)** — **Complete:** 2026-02-24 224 + - `liftBan` was silently resurrecting user-deleted posts on unban because both deletion sources shared the `deleted` column 225 + - Added `bannedByMod` boolean column to `posts` table; `applyBan`/`liftBan` now toggle `bannedByMod` only, leaving `deleted` for user-initiated deletes 226 + - All API read queries filter `eq(posts.bannedByMod, false)` in addition to `eq(posts.deleted, false)` 227 + - Also adds `deleted_by_user` column for future self-deletion endpoint (groundwork laid) 228 + - Files: new migration, `apps/appview/src/lib/ban-enforcer.ts`, `packages/db/src/schema.ts` (ATB-25, PR #56) 229 + - [x] **ATB-35: Strip title from reply records at index time (Bug Fix)** — **Complete:** 2026-02-24 230 + - Indexer was unconditionally storing `title: record.title ?? null` on all posts, including replies — violating the lexicon invariant that titles belong only to thread starters 231 + - Fixed: `toInsertValues`/`toUpdateValues` set `title: null` when `Post.isReplyRef(record.reply)` is true 232 + - File: `apps/appview/src/lib/indexer.ts` (ATB-35, PR #55) 223 233 - [x] **ATB-13: Backfill & Repo Sync** — **Complete:** 2026-02-23 224 234 - Automatic gap detection on startup: if cursor is stale (>48h) → CatchUp; no cursor → FullSync; healthy cursor → skip 225 235 - `BackfillManager` orchestrates full-repo sync via `com.atproto.sync.listRepos` + `com.atproto.repo.listRecords` for each member DID ··· 236 246 - Bruno collection: `bruno/AppView API/Admin/` (3 .bru files) 237 247 238 248 #### Phase 4: Web UI (Week 7–9) 249 + - [x] Web UI foundation: neobrutal design system, shared components, and route stubs 250 + - ATB-26 | CSS architecture with custom properties (`reset.css`, `theme.css`, neobrutal light token preset); shared components: `Card`, `Button`, `PageHeader`, `ErrorDisplay`, `EmptyState`; static file serving via Hono `serveStatic`; route stubs for all Phase 4 pages. PR #39 (2026-02-18) 239 251 - [x] Forum homepage: category list, recent topics 240 252 - ATB-27 | `apps/web/src/routes/home.tsx` — server-renders forum name/description, categories as section headers, boards as cards with links; two-stage parallel fetch (forum+categories, then per-category boards); error display on network (503) or API (500) failures; 12 integration tests in `home.test.tsx` 241 253 - [x] Board view: topic listing with pagination ··· 243 255 - [ ] Category view: paginated topic list, sorted by last reply — **Deferred:** boards hierarchy (ATB-23) replaced per-category pages; boards view serves this purpose 244 256 - [x] Topic view: OP + flat replies, pagination 245 257 - ATB-29 | `apps/web/src/routes/topics.tsx` — OP card (#1) + reply cards (#2, #3, …) with post numbers and `timeAgo` dates; breadcrumb (Home → Category → Board → Topic) with graceful degradation on breadcrumb fetch failures; locked-topic banner + reply-slot gating (unauthenticated/authenticated/locked); HTMX "Load More" with `hx-push-url` for bookmarkable offsets; `?offset=N` bookmark support renders all replies 0→N+pageSize inline; three-stage sequential fetch (topic fatal, board/category non-fatal); 35 integration tests in `topics.test.tsx` 258 + - [x] Server-side offset/limit pagination for topic replies 259 + - ATB-33 | `GET /api/topics/:id` now accepts `?offset=N&limit=N` query params; web `topics.tsx` passes these to AppView instead of fetching all replies and slicing client-side. PR #57 (2026-02-24) 246 260 - [x] Compose: new topic form, reply form 247 - - ATB-30 | `apps/web/src/routes/new-topic.tsx` — new topic form with board selector, character counter, validation; `apps/web/src/routes/topics.tsx` — inline reply form with HTMX submission; both forms write to AppView API which proxies to user's PDS 261 + - ATB-31 | `apps/web/src/routes/new-topic.tsx` — new topic form with board selector, character counter, validation; `apps/web/src/routes/topics.tsx` — inline reply form with HTMX submission; both forms write to AppView API which proxies to user's PDS 248 262 - [x] Login/logout flow 249 263 - ATB-30 | `apps/web/src/routes/login.tsx` — handle input form; `apps/web/src/routes/auth.tsx` — OAuth callback + session management; BaseLayout shows login/logout based on auth state 250 264 - [x] Admin panel: manage categories, view members, mod actions ··· 264 278 265 279 1. **~~Lexicon registration~~** ✅ RESOLVED — `atbb.space` domain is owned and controlled. Existing lexicon definitions in `atBB-Community/lexicon` repo may be reusable. 266 280 2. **Firehose filtering:** The AT Proto firehose is *all* records from *all* users. Filtering for your Lexicon types at scale requires thought. For MVP (small user base), naive filtering is fine. 267 - 3. **PDS write path:** Writing records to a user's PDS on their behalf requires proper OAuth scopes. Verify the current state of the AT Proto OAuth spec — it's been evolving. 268 - 4. **Record deletion:** If a user deletes a post from their PDS, the firehose emits a tombstone. Your indexer needs to handle this (soft-delete from index). 281 + 3. **~~PDS write path~~** ✅ RESOLVED — OAuth 2.1 with PKCE + `atproto transition:generic` scope implemented (ATB-14). Write path validated in spike (ATB-6) and production (ATB-12). Token refresh and DPoP handled by `@atproto/oauth-client-node`. 282 + 4. **~~Record deletion~~** ✅ RESOLVED — Indexer handles PDS tombstones: `handlePostDelete` sets `deleted = true` on the post row; all API read queries filter `deleted = false`. User-delete and mod-hide are tracked with separate columns (`deleted` vs `bannedByMod`) since ATB-25. 269 283 5. **~~Backfill~~** ✅ RESOLVED — ATB-13 implemented full backfill & repo sync. `BackfillManager` handles gap detection, CatchUp vs FullSync, interrupt recovery, rate-limited repo crawl, and admin trigger API (`POST /api/admin/backfill`). See Phase 3 ATB-13 entry for details. 270 284 271 285 --- 272 286 287 + ## Known Issues / Active Backlog 288 + 289 + Open bugs and deferred improvements against the current implementation, by priority: 290 + 291 + | Issue | Priority | Title | Notes | 292 + |---|---|---|---| 293 + | ATB-39 | High | `upgradeBootstrapMembership` writes PDS record without role field | Forum Owner loses all permissions on first real login; role field must be read from existing DB row and included in the PDS write | 294 + | ATB-38 | High | `seedDefaultRoles` partial failure should fail fast | Server starts silently broken if Member role PDS write fails at startup; fix: throw after seeding if Member role absent in DB | 295 + | ATB-41 | Medium | Missing `$type` discriminator on `forumRef`/`boardRef` objects written to PDS | Inconsistent with `replyRef` fix (PR #61); safe today via optional chaining in indexer but fragile if typed guards are adopted | 296 + | ATB-34 | Low | Axe-core automated accessibility testing | Deferred from ATB-32; add `@axe-core/playwright` + WCAG AA integration tests in CI pipeline | 297 + 298 + > **Linear cleanup:** ATB-39 duplicates ATB-37; ATB-40 duplicates ATB-38 (filed 4 minutes apart, same bugs). The later-filed pair (ATB-39/40) should be closed as duplicates. 299 + 300 + --- 301 + 273 302 ## Future Roadmap (Post-MVP) 274 303 275 304 ### Privilege Delegation (`at.delegation` integration) ··· 284 313 285 314 React Native + Expo cross-platform apps consuming the same `/api/*` endpoints as the web UI. Phased rollout: read-only browse → write/interact → push notifications → offline support & app store release. Full plan in [`docs/mobile-apps-plan.md`](mobile-apps-plan.md). 286 315 316 + ### SQLite Support 317 + 318 + Design approved 2026-02-24 (`docs/plans/2026-02-24-sqlite-support-design.md`). Enables lightweight single-file deployments without a separate Postgres process: 319 + 320 + - URL-prefix detection in `createDb()` factory (`postgresql://` vs `file:`) for automatic dialect selection 321 + - Separate `schema.sqlite.ts` with integer IDs and unix timestamp column helpers 322 + - `role_permissions` join table to replace Postgres-specific array column (improves both dialects) 323 + - Separate Drizzle config files (`drizzle.postgres.config.ts`, `drizzle.sqlite.config.ts`) and migration directories per dialect 324 + - TypeScript types identical for both dialects via Drizzle `mode` specifiers 325 + 287 326 ### Other Future Work 288 327 - **Setup wizard for first-time initialization** — Interactive web-based wizard for administrators to initialize a new forum instance (create forum record on PDS, configure categories, set admin roles). Currently requires manual PDS API calls. 289 - - **User self-deletion endpoint** — Allow users to delete their own posts with two modes: soft-delete (hide from forum index but keep on PDS) and hard-delete (actually remove from their PDS via deleteRecord). Complements moderation soft-delete which only hides content. 328 + - **User self-deletion endpoint** — Allow users to delete their own posts with two modes: soft-delete (hide from forum index but keep on PDS) and hard-delete (actually remove from their PDS via deleteRecord). Complements moderation soft-delete which only hides content. **Groundwork laid:** `deleted_by_user` column added to `posts` table (ATB-25); indexer already handles post tombstones from the firehose. Endpoint implementation is the remaining work. 290 329 - Nested/threaded replies 291 330 - Full-text search (maybe Meilisearch) 292 331 - User profiles & post history