···11+# phpBB Research: Features, Complaints & Lessons for atBB
22+33+*Research conducted 2026-02-07*
44+55+---
66+77+## Table of Contents
88+99+1. [phpBB Overview & History](#phpbb-overview--history)
1010+2. [Complete Feature Inventory](#complete-feature-inventory)
1111+3. [Common Complaints & Pain Points](#common-complaints--pain-points)
1212+4. [Why Traditional Forums Declined](#why-traditional-forums-declined)
1313+5. [Lessons from Modern Competitors](#lessons-from-modern-competitors)
1414+6. [Gap Analysis: atBB vs phpBB](#gap-analysis-atbb-vs-phpbb)
1515+7. [Recommendations for atBB](#recommendations-for-atbb)
1616+1717+---
1818+1919+## phpBB Overview & History
2020+2121+phpBB (PHP Bulletin Board) is the most widely deployed open-source forum software in history, first released in 2000. It's written in PHP and available under the GPL.
2222+2323+### Version History
2424+2525+| Version | Codename | Released | Key Changes |
2626+|---------|----------|----------|-------------|
2727+| 1.x | — | ~2000 | Original release |
2828+| 2.x | — | ~2002–2008 | Made phpBB mainstream |
2929+| 3.0 | Olympus | Dec 2007 | Complete rewrite, prosilver theme, security audit |
3030+| 3.1 | Ascraeus | Oct 2014 | Symfony adoption, extension system, AJAX |
3131+| 3.2 | Rhea | Jan 2017 | PHP 7 support, emoji, Font Awesome, new installer |
3232+| 3.3 | Proteus | Jan 2020 | PHP 7.4/8.x, extended emoji, MySQL 8 (current stable) |
3333+| 4.0 | Triton | Alpha Sep 2025 | Symfony 6.4, PHP 8.1+, @mentions, Cloudflare Turnstile |
3434+3535+### Architecture
3636+3737+- **Backend:** PHP on Symfony (since 3.1), custom template engine
3838+- **Database:** Supports MySQL/MariaDB, PostgreSQL, SQLite, MSSQL, Oracle
3939+- **Frontend:** Server-rendered HTML with prosilver theme (HTML5/CSS3), minimal JS
4040+- **Extension system:** Self-contained extensions installed via Admin Control Panel
4141+- **Search:** Multiple backends — native full-text index, MySQL FULLTEXT, PostgreSQL FULLTEXT, Sphinx
4242+4343+---
4444+4545+## Complete Feature Inventory
4646+4747+### 1. Forum Structure & Organization
4848+4949+| Feature | Details | atBB Status |
5050+|---------|---------|-------------|
5151+| **Hierarchical categories** | Unlimited nesting of categories containing forums | Partial — `forum.category` is flat (single level) |
5252+| **Unlimited subforums** | Forums can contain subforums to any depth | Missing — no subforum concept |
5353+| **Password-protected forums** | Individual forums can require a password | Missing |
5454+| **Forum-specific styles** | Different visual themes per forum | Missing |
5555+| **Forum links** | A "forum" entry that links to an external URL | Missing |
5656+| **Forum rules display** | Per-forum or global rules shown at top | Missing |
5757+| **Topic pruning** | Auto-delete old/inactive topics by configurable criteria | Missing |
5858+| **Active topics display** | Most active topics shown at top of forum | Missing |
5959+6060+### 2. Topic & Post Features
6161+6262+| Feature | Details | atBB Status |
6363+|---------|---------|-------------|
6464+| **Topic types: Normal** | Standard topic | Implemented (post without reply ref) |
6565+| **Topic types: Sticky** | Pinned to top of first page | Partial — `pin` mod action exists but no display logic |
6666+| **Topic types: Announcement** | Pinned above stickies, shown on all pages | Missing |
6767+| **Topic types: Global** | Shown at top of every forum on the board | Missing |
6868+| **Time-limited sticky/announcement** | Auto-revert to normal after N days | Missing |
6969+| **Topic subscriptions** | Email/notification on new replies | Missing |
7070+| **Forum subscriptions** | Email/notification on new topics in forum | Missing |
7171+| **Topic bookmarks** | Users save topics for quick access | Missing |
7272+| **Post ordering** | Sort by date, alphabetical, reply count | Missing |
7373+| **"My posts" indicator** | Mark topics where user has posted | Missing |
7474+| **Printer-friendly view** | Clean print layout for topics | Missing |
7575+| **Email a topic** | Send topic link to friend | Missing |
7676+| **Unread tracking** | Track which topics/posts user has read, even across sessions | Missing |
7777+| **Topic split** | Moderator splits a topic into two separate topics | Missing |
7878+| **Topic merge** | Moderator merges two topics into one | Missing |
7979+| **Topic move** | Move topic to different forum (with shadow link) | Missing |
8080+| **Topic lock** | Prevent further replies | Partial — `lock` mod action exists |
8181+| **Post lock** | Lock individual posts from editing | Missing |
8282+| **Topic copy** | Duplicate a topic to another forum | Missing |
8383+8484+### 3. Post Composition
8585+8686+| Feature | Details | atBB Status |
8787+|---------|---------|-------------|
8888+| **BBCode formatting** | Bold, italic, lists, links, images, etc. | Missing — plain text only (300 grapheme limit) |
8989+| **Rich text / Markdown** | Formatted text in posts | Missing (noted as future work) |
9090+| **Smilies / Emoticons** | Inline emoji/emoticon insertion | Missing |
9191+| **Post preview** | Preview post before submitting | Missing |
9292+| **File attachments** | Upload files attached to posts | Missing (noted as future work) |
9393+| **Inline attachments** | Place attachments within post body with BBCode | Missing |
9494+| **Polls** | Create polls with multiple options, voting, time limits | Missing |
9595+| **Topic icons** | Visual icon next to topic title | Missing |
9696+| **Drafts** | Save unfinished posts for later | Missing |
9797+| **Post editing** | Edit your own posts after submission | Architecturally feasible (AT Proto supports updates via `putRecord` — CID changes, firehose emits update event) |
9898+| **Post edit history** | Track edit reasons and history | Missing |
9999+| **Word censoring** | Auto-replace banned words | Missing |
100100+| **Custom BBCodes** | Admin-defined formatting codes | Missing |
101101+| **@Mentions** | Mention and notify other users (phpBB 4.0 feature) | Missing |
102102+| **Direct media playback** | Play video/audio in post body (phpBB 4.0) | Missing |
103103+104104+### 4. User Profiles & Identity
105105+106106+| Feature | Details | atBB Status |
107107+|---------|---------|-------------|
108108+| **User registration** | Account creation flow | Planned — AT Proto OAuth |
109109+| **Social login** | Google, Facebook, etc. | N/A — AT Proto identity replaces this |
110110+| **Avatars** | Gallery, uploaded, remote URL | Missing — could pull from AT Proto profile |
111111+| **Signatures** | Text/images appended to every post | Missing |
112112+| **Custom profile fields** | Admin-defined extra fields (location, website, etc.) | Missing — AT Proto profiles have some |
113113+| **User ranks** | Title based on post count or special assignment | Missing |
114114+| **User badges** | Visual indicators (verified, special roles) | Missing |
115115+| **Post count display** | Show post count next to username | Missing |
116116+| **Join date display** | Show when user joined | Partial — `membership.joinedAt` exists |
117117+| **User online status** | Show if user is currently online | Missing |
118118+| **User profile page** | Dedicated page showing user info, recent posts | Missing (noted as future work) |
119119+| **Friends & Foes list** | Add users to friends/ignore list | Missing |
120120+| **User post history** | View all posts by a user | Missing (noted as future work) |
121121+122122+### 5. Private Messaging
123123+124124+| Feature | Details | atBB Status |
125125+|---------|---------|-------------|
126126+| **Direct messages** | Private messages between users | Missing entirely |
127127+| **Group messaging** | PM multiple users or groups | Missing |
128128+| **BCC support** | Blind carbon copy on PMs | Missing |
129129+| **PM attachments** | Attach files to PMs | Missing |
130130+| **PM drafts** | Save PM drafts | Missing |
131131+| **PM export** | Export/archive PMs | Missing |
132132+| **PM reply tracking** | Chronological PM thread history | Missing |
133133+| **PM notifications** | Notify on new PMs | Missing |
134134+135135+**Note:** Private messaging may not be in scope for atBB since AT Proto has its own direct messaging infrastructure (Bluesky DMs via `chat.bsky.*` lexicons). This could be an intentional omission — users already have a DM channel through their AT Proto identity.
136136+137137+### 6. Moderation Tools
138138+139139+| Feature | Details | atBB Status |
140140+|---------|---------|-------------|
141141+| **Ban by username** | Ban specific user | Partial — `ban` mod action exists |
142142+| **Ban by IP** | Block IP address | Missing — not applicable (AT Proto is DID-based) |
143143+| **Ban by email** | Block email address | Missing — not applicable |
144144+| **Time-limited bans** | Temporary bans with auto-expiry | Partial — `expiresAt` field exists on modAction |
145145+| **Warning system** | Issue warnings before bans | Missing |
146146+| **Warning auto-escalation** | Auto-ban after N warnings | Missing |
147147+| **Moderation queue** | Pre-approval for new users' posts | Missing |
148148+| **Post approval workflow** | Require mod approval before publish | Missing |
149149+| **Post reporting** | Users flag posts for mod review | Missing |
150150+| **Report reasons** | Configurable list of report reasons | Missing |
151151+| **Moderator notes** | Private notes on users visible to mods | Missing |
152152+| **Topic split/merge/move** | Reorganize discussions | Missing |
153153+| **Shadow topics** | Leave redirect link when moving topics | Missing |
154154+| **Edit/delete any post** | Moderator override on post editing | Partial — `delete` mod action hides from index |
155155+| **Change post author** | Reassign post ownership | Missing — not possible with AT Proto |
156156+| **Moderator log** | Audit trail of all mod actions | Missing |
157157+| **Admin log** | Audit trail of admin actions | Missing |
158158+| **User action log** | Track user behavior (IP logs, etc.) | Missing |
159159+160160+### 7. Permission System
161161+162162+| Feature | Details | atBB Status |
163163+|---------|---------|-------------|
164164+| **Granular ACL** | YES/NO/NEVER per-permission per-user/group | Simplified — role hierarchy only |
165165+| **Permission types** | f_* (forum), m_* (mod), u_* (user), a_* (admin) | Simplified — `forum.role` with permission tokens |
166166+| **Role-based permissions** | Pre-defined permission bundles | Partial — role definitions exist |
167167+| **Per-forum permissions** | Different permissions per forum | Missing |
168168+| **Per-user overrides** | Override group permissions for individuals | Missing |
169169+| **Group-based permissions** | Assign permissions to groups, users inherit | Simplified — single role per membership |
170170+| **"Newly registered" group** | Restricted permissions for new users | Missing |
171171+| **Global vs local permissions** | Board-wide vs per-forum scoping | Missing |
172172+173173+**Note:** phpBB's permission system is arguably over-engineered and a frequent source of admin confusion. atBB's simpler model (role hierarchy: Owner → Admin → Moderator → Member → Guest) is actually a feature, not a bug. The key missing piece is **per-category permissions** — the ability to restrict certain categories to certain roles.
174174+175175+### 8. Search
176176+177177+| Feature | Details | atBB Status |
178178+|---------|---------|-------------|
179179+| **Full-text search** | Search post content | Missing (noted as future work — Meilisearch) |
180180+| **Multiple search backends** | Native, MySQL FULLTEXT, PostgreSQL, Sphinx | Missing |
181181+| **Search by author** | Find posts by specific user | Missing |
182182+| **Search by forum** | Scope search to specific forum/category | Missing |
183183+| **Search by date range** | Filter results by post date | Missing |
184184+| **Unanswered topics** | Find topics with no replies | Missing |
185185+| **Active topics** | Find topics with recent activity | Missing |
186186+| **"New posts since last visit"** | Show unread content | Missing |
187187+188188+### 9. Notifications & Feeds
189189+190190+| Feature | Details | atBB Status |
191191+|---------|---------|-------------|
192192+| **Email notifications** | Notify on replies, PMs, etc. | Missing (noted as future work) |
193193+| **Jabber/XMPP notifications** | IM-based alerts | Missing |
194194+| **ATOM feeds** | Board-wide, per-forum, per-topic feeds | Missing (noted as future work — RSS) |
195195+| **In-app notifications** | On-site notification system | Missing |
196196+| **Digest emails** | Periodic summary emails | Missing |
197197+198198+### 10. Administration
199199+200200+| Feature | Details | atBB Status |
201201+|---------|---------|-------------|
202202+| **Admin Control Panel** | Comprehensive web-based admin UI | Missing — admin panel is Phase 4 |
203203+| **Board statistics** | User count, post count, activity graphs | Missing |
204204+| **Database backup/restore** | Built-in DB management | Missing |
205205+| **Extension management** | Install/enable/disable extensions from UI | Missing |
206206+| **Style/theme management** | Upload and switch themes | Missing |
207207+| **Language packs** | Multi-language support | Missing |
208208+| **User management** | Search, edit, ban, delete users | Missing |
209209+| **Group management** | Create/edit groups, assign members | Partial — roles exist |
210210+| **Mass email** | Send email to all users or groups | Missing |
211211+| **Forum pruning** | Auto-cleanup old content | Missing |
212212+| **Cron/scheduled tasks** | Background maintenance jobs | Missing |
213213+| **Board configuration** | Extensive settings (registration, posting, etc.) | Missing |
214214+215215+### 11. Anti-Spam & Security
216216+217217+| Feature | Details | atBB Status |
218218+|---------|---------|-------------|
219219+| **CAPTCHA** | Multiple CAPTCHA options (visual, reCAPTCHA) | N/A — AT Proto OAuth handles registration |
220220+| **Cloudflare Turnstile** | Modern anti-bot (phpBB 4.0) | N/A |
221221+| **Two-factor authentication** | 2FA for admins/mods | N/A — delegated to AT Proto identity |
222222+| **IP logging** | Track IPs per post | N/A — AT Proto is DID-based |
223223+| **Flood control** | Rate limiting on posts | Missing but important |
224224+| **New user restrictions** | Limited permissions for new accounts | Missing — could use Discourse-style trust levels |
225225+| **Word censoring** | Auto-filter content | Missing |
226226+| **Post approval queue** | Pre-moderate new user content | Missing |
227227+228228+---
229229+230230+## Common Complaints & Pain Points
231231+232232+These are the most frequent criticisms of phpBB that atBB should learn from and avoid:
233233+234234+### 1. Spam Bots (Critical)
235235+phpBB is notorious for spam. Within days of installing a phpBB forum, admins report hundreds of bot registrations. The default anti-spam measures are insufficient. **atBB advantage:** AT Proto OAuth means users must have a real AT Proto identity (a PDS account), which is a significant barrier to spam bots. This is one of atBB's biggest inherent strengths.
236236+237237+### 2. Outdated User Interface
238238+phpBB's default prosilver theme looks dated. The Admin Control Panel is considered user-unfriendly. Modern users expect mobile-first, clean design. **atBB lesson:** The HTMX + server-rendered approach is good, but invest in modern CSS and responsive design from the start. Don't let the UI become an afterthought.
239239+240240+### 3. Difficult Extension/Theme Customization
241241+phpBB's old MOD system required editing core files, making upgrades painful. Even the newer extension system is considered harder to work with than competitors. **atBB lesson:** If an extension/plugin system is ever built, design it as a first-class API from day one. The AT Proto lexicon system already provides some natural extensibility.
242242+243243+### 4. Overly Complex Permission System
244244+phpBB's YES/NO/NEVER ACL system with four permission types (forum, mod, user, admin), per-group and per-user overrides, global vs local scopes — is powerful but confusing. Admins frequently misconfigure permissions. **atBB advantage:** The simpler role hierarchy is actually better UX. Don't over-complicate it. Per-category permissions (Phase 2+) should be the ceiling of complexity.
245245+246246+### 5. Slow Development & Modernization
247247+phpBB is developed entirely by volunteers. Major versions take years. phpBB 4.0 has been in development since ~2016 and is still in alpha as of 2025. **atBB lesson:** Ship MVP fast, iterate. The AT Proto foundation means the core protocol handles hard problems (identity, data ownership) and atBB can focus on forum UX.
248248+249249+### 6. Poor Mobile Experience
250250+phpBB's mobile experience, while improved with prosilver's responsive updates, still feels like a desktop forum crammed into a phone. **atBB lesson:** HTMX enables progressive enhancement. Design mobile-first from the beginning.
251251+252252+### 7. Search is Weak
253253+phpBB's native search indexes words, not phrases. Full-text search is basic. Large forums struggle with search performance. **atBB lesson:** Plan for Meilisearch or similar from the start (already noted as future work). Make search a core feature, not an afterthought.
254254+255255+### 8. No Real-Time Features
256256+phpBB is entirely request-response. No live updates, no real-time notifications, no "someone is typing" indicators. **atBB lesson:** The AT Proto firehose subscription already provides a real-time data stream. Use this to power live topic updates via HTMX SSE or WebSocket integration.
257257+258258+---
259259+260260+## Why Traditional Forums Declined
261261+262262+Understanding why phpBB-style forums lost market share is critical for atBB's positioning:
263263+264264+### The Migration Pattern
265265+1. **~2005–2010:** Social media (Facebook, Twitter) drew casual discussion away from forums
266266+2. **~2010–2015:** Reddit centralized niche communities under one roof with a single account
267267+3. **~2015–present:** Discord replaced forums for real-time community interaction
268268+4. **~2020–present:** Growing backlash against centralized platforms, renewed interest in self-hosted/decentralized alternatives
269269+270270+### Why People Left Forums
271271+- **Friction:** Creating new accounts per forum, managing credentials, email notifications
272272+- **Discovery:** No way to find new communities from within the forum
273273+- **Single login:** Reddit/Discord offer one account for all communities
274274+- **Real-time:** Discord offers instant chat; forums feel slow
275275+- **Mobile:** Traditional forums were desktop-first
276276+277277+### Why People Want Forums Back
278278+- **Searchability:** Discord conversations are ephemeral and unsearchable by outsiders
279279+- **Ownership:** Reddit's API changes (2023) and platform enshittification
280280+- **Structure:** Forums organize knowledge better than chat or feeds
281281+- **Permanence:** Forum threads remain useful for years; Discord messages disappear
282282+- **Decentralization:** Growing desire to own your community and data
283283+284284+### atBB's Unique Position
285285+atBB directly addresses the core reasons forums declined:
286286+- **Single identity:** AT Proto DID = one identity across all forums (like Reddit/Discord)
287287+- **Data ownership:** Posts live on your PDS, not the forum's database
288288+- **Discovery:** Federation enables "common forums" features (like Discord's mutual servers)
289289+- **Portability:** Leave a forum, keep your content
290290+- **Decentralization:** Self-hosted, no central authority
291291+292292+This is a genuinely compelling value proposition that phpBB never had.
293293+294294+---
295295+296296+## Lessons from Modern Competitors
297297+298298+### Discourse: What They Got Right
299299+- **Trust levels:** Automated progressive permissions (0=New → 4=Leader) reduce mod burden and scale community self-governance. New users are sandboxed; proven users earn privileges automatically.
300300+- **Gamification:** Built-in badges, points, leaderboards drive engagement
301301+- **Real-time:** Live updates, notifications, typing indicators
302302+- **Infinite scroll:** Modern UX pattern vs. pagination
303303+- **Mobile-first:** Responsive by default
304304+- **Markdown:** Modern text formatting instead of BBCode
305305+- **One-click setup:** Docker-based deployment
306306+307307+### Flarum: What They Got Right
308308+- **Two-pane interface:** Browse discussions without leaving current thread
309309+- **Lightweight:** Fast, minimal resource usage
310310+- **Modern stack:** Laravel + Mithril.js
311311+- **Tags instead of categories:** More flexible organization
312312+- **Infinite scroll with memory:** Remembers scroll position
313313+314314+### XenForo: What They Got Right
315315+- **Social features:** User status updates, activity feed
316316+- **Trophy/achievement system:** Gamification built into core
317317+- **Resource manager:** File/download sharing per forum
318318+- **Built-in media gallery:** Image/video sharing
319319+- **Robust search:** Elasticsearch integration
320320+321321+---
322322+323323+## Gap Analysis: atBB vs phpBB
324324+325325+### Features atBB Has That phpBB Doesn't
326326+327327+| Feature | Description |
328328+|---------|-------------|
329329+| **Decentralized identity** | AT Proto DID — portable, user-owned identity |
330330+| **Data ownership** | Posts live on user's PDS, not forum's database |
331331+| **Portability** | Content persists even if you leave a forum |
332332+| **Federation** | Multiple forums can share identity namespace |
333333+| **Cross-forum discovery** | "Common forums" feature possible via membership records |
334334+| **Spam resistance** | AT Proto identity is a natural spam barrier |
335335+| **Protocol-level reactions** | Reactions are first-class AT Proto records |
336336+| **Real-time data stream** | Firehose subscription for live updates |
337337+338338+### Critical Features atBB is Missing (Priority Order)
339339+340340+#### P0 — Essential for MVP Parity
341341+These are features without which the forum would feel fundamentally broken:
342342+343343+1. **Rich text / Markdown in posts** — Plain text only is not viable. Users expect at minimum bold, italic, links, code blocks, and lists. Markdown is the modern standard. Note: The current `maxGraphemes: 300` limit in `post.yaml` is closer to a tweet than a forum post — this constraint may need revisiting before implementing rich text (phpBB posts typically support thousands of characters).
344344+345345+2. **Unread tracking** — phpBB tracks read/unread state per-user across sessions. Without this, users can't efficiently follow conversations. This is the #1 quality-of-life feature for forum users.
346346+347347+3. **Topic subscription / notifications** — Users must be notified when someone replies to their topic or a topic they're watching. Without this, the forum is "post and forget."
348348+349349+4. **User profile pages** — Display name, avatar (from AT Proto profile), post count, join date, recent posts. The building block for community identity.
350350+351351+5. **Search** — Meilisearch integration. Forums are fundamentally knowledge bases; without search, old content is lost.
352352+353353+#### P1 — Important for Competitive Parity
354354+These make the forum feel complete and usable for serious communities:
355355+356356+6. **Topic types: Sticky/Announcement** — Admins need to pin important topics. The `pin` mod action exists but needs display logic and proper announcement/global support.
357357+358358+7. **Post reporting** — Users need to flag rule-breaking content for mod review. This is the feedback loop that makes moderation scale.
359359+360360+8. **Moderation queue / post approval** — New users' posts can require mod approval. Critical for preventing spam/abuse in growing communities.
361361+362362+9. **Warning system** — Graduated enforcement before banning. Warnings → temporary ban → permanent ban.
363363+364364+10. **Moderation log / audit trail** — Record of all mod actions with timestamps and reasons. Essential for accountability and mod team coordination.
365365+366366+11. **Polls** — Simple voting mechanism within topics. High engagement, low implementation cost.
367367+368368+12. **Drafts** — Save unfinished posts. Prevents lost work, improves UX.
369369+370370+13. **Topic split/merge/move** — Core moderator workflow for organizing discussions. Without these, moderators can only lock or delete.
371371+372372+#### P2 — Nice to Have / Differentiators
373373+These would make atBB stand out:
374374+375375+14. **Trust levels (Discourse-style)** — Automated progressive permissions based on participation. Reduces mod burden, encourages good behavior.
376376+377377+15. **ATOM/RSS feeds** — Per-forum, per-topic feeds. Low cost, high value for power users and integration.
378378+379379+16. **Gamification** — Post count ranks, badges, achievements. Drives engagement.
380380+381381+17. **File attachments / images** — Upload and embed media in posts. Already noted as future work.
382382+383383+18. **@Mentions** — Notify specific users in posts. phpBB 4.0 is adding this — it's becoming table stakes.
384384+385385+19. **Real-time updates** — Use the firehose to push new replies to open topic pages via HTMX SSE. Would be a significant differentiator over traditional forums.
386386+387387+20. **Subforum / nested categories** — Currently categories are flat. Some communities need hierarchy.
388388+389389+#### P3 — Future Considerations
390390+391391+21. **Private messaging** — May be handled by AT Proto's native DM system instead
392392+22. **Nested/threaded replies** — Already noted as future work
393393+23. **Custom themes/branding** — Already noted as future work
394394+24. **Plugin/extension system** — Already noted as future work
395395+25. **Per-category permissions** — Restrict categories to certain roles
396396+397397+---
398398+399399+## Recommendations for atBB
400400+401401+### 1. Lean Into Your Strengths
402402+atBB's decentralized identity and data ownership model solves the fundamental problems that killed traditional forums (friction, lock-in, centralization). Don't bury this — make it the primary selling point. The "bring your identity, own your posts" story is compelling.
403403+404404+### 2. Avoid phpBB's Complexity Trap
405405+phpBB's permission system is a cautionary tale. The YES/NO/NEVER ACL with four permission types is powerful but confusing. atBB's role hierarchy (Owner → Admin → Moderator → Member → Guest) is the right level of simplicity for MVP. Add per-category permissions later if needed, but resist the urge to build a full ACL system.
406406+407407+### 3. Learn from Discourse's Trust Levels
408408+Discourse's automated trust system (where users earn permissions through participation) is one of the best innovations in forum software. Consider implementing a simplified version:
409409+- **Level 0 (New):** Just joined, posts may require approval
410410+- **Level 1 (Basic):** Has read some topics, can post freely
411411+- **Level 2 (Member):** Active participant, can flag posts
412412+- **Level 3 (Regular):** Trusted member, can recategorize topics, edits are instant
413413+- This could integrate with the AT Proto reputation model — a user's reputation across the ATmosphere could bootstrap their trust level.
414414+415415+### 4. Markdown, Not BBCode
416416+BBCode is a relic. Markdown is the universal formatting language of the modern web (GitHub, Reddit, Discourse all use it). Implement Markdown with a live preview. Consider a common Markdown library that works both server-side (for rendering) and client-side (for preview).
417417+418418+### 5. Real-Time is Your Secret Weapon
419419+You already have the AT Proto firehose. Use it to push real-time updates to open pages via HTMX Server-Sent Events (SSE). When someone replies to a topic you're viewing, the reply should appear without a page refresh. This would be a genuine differentiator over every traditional forum including phpBB.
420420+421421+### 6. Don't Neglect Search
422422+Implement Meilisearch (already planned) early, not late. Forums are knowledge bases. The value of a forum compounds over time as content accumulates — but only if that content is findable.
423423+424424+### 7. Mobile-First Design
425425+Design the HTMX web UI mobile-first. The majority of web traffic is mobile. phpBB's biggest UX failure is feeling like a desktop application on phones.
426426+427427+### 8. Unread Tracking is Non-Negotiable
428428+Implement per-user read state tracking from the beginning. This is the single most important UX feature for forums. Without it, users have no way to efficiently follow conversations across multiple topics and categories. phpBB's read tracking system (storing last-read timestamps per topic per user) has been refined over 20+ years — study it.
429429+430430+### 9. Moderation Must Scale
431431+Build moderation features that can scale with the community:
432432+- Post reporting from day one
433433+- Mod action audit log from day one
434434+- Warning system before permanent bans
435435+- Post approval queue for new users
436436+- Consider how AT Proto's decentralized nature affects moderation — users can always post to their PDS even if banned, but the AppView simply won't index their content. This is actually a cleaner model than phpBB's.
437437+438438+### 10. The "Forum Revival" Opportunity
439439+There is genuine renewed interest in forum-style communities driven by:
440440+- Discord's poor searchability and content ephemerality
441441+- Reddit's hostile API changes and platform enshittification
442442+- Growing desire for data ownership and decentralization
443443+444444+atBB is uniquely positioned at the intersection of "forums done right" and "decentralized social." The combination of structured long-form discussion (forums), user-owned data (AT Proto), and portable identity (DIDs) is a compelling story that neither phpBB nor Discourse can tell.
445445+446446+---
447447+448448+## Sources
449449+450450+- [phpBB Official Site](https://www.phpbb.com/)
451451+- [phpBB Features](https://www.phpbb.com/about/features/)
452452+- [phpBB History](https://www.phpbb.com/about/history/)
453453+- [phpBB Wikipedia](https://en.wikipedia.org/wiki/PhpBB)
454454+- [phpBB Permission System Docs](https://area51.phpbb.com/docs/dev/3.3.x/extensions/permission_system.html)
455455+- [phpBB Admin Guide](https://www.phpbb.com/support/docs/en/3.2/ug/adminguide/)
456456+- [phpBB 4.0 Alpha Announcement](https://blog.phpbb.com/2025/09/27/phpbb-4-0-0-a1-the-first-alpha-release-is-here/)
457457+- [phpBB Community: "phpBB is a very bad choice"](https://www.phpbb.com/community/viewtopic.php?t=2155257)
458458+- [phpBB Community: "What is the Future of phpBB?"](https://www.phpbb.com/community/viewtopic.php?t=2607651)
459459+- [HN: Why did phpBB forums go out of fashion?](https://news.ycombinator.com/item?id=33726098)
460460+- [HN: Are forum platforms dead?](https://news.ycombinator.com/item?id=32171283)
461461+- [Discourse vs Flarum vs phpBB Comparison](https://www.accuwebhosting.com/blog/discourse-vs-flarum-vs-phpbb/)
462462+- [Discourse Trust Levels](https://blog.discourse.org/2018/06/understanding-discourse-trust-levels/)
463463+- [Discourse Gamification Plugin](https://meta.discourse.org/t/discourse-gamification/225916)
464464+- [phpBB Moderation Guide](https://www.phpbb.com/support/docs/en/3.0/ug/moderatorguide/moderator_modtools/)
465465+- [phpBB Search Indexing](https://www.phpbb.com/support/docs/en/3.0/ug/adminguide/maintenance_search/)
466466+- [phpBB ATOM Feeds FAQ](https://www.phpbb.com/support/docs/en/3.0/kb/article/faq-phpbb-atom-feeds/)
467467+- [LowEndTalk: How did phpBB go from biggest to obscurity?](https://lowendtalk.com/discussion/208407/how-did-phpbb-go-from-the-biggest-forum-platform-on-the-block-to-relative-obscurity)
468468+- [Quora: Pros and cons of phpBB](https://www.quora.com/What-are-the-pros-and-cons-of-using-phpBB-for-web-forums)
+623
docs/research/realtime-architecture.md
···11+# Real-Time Architecture: Firehose → SSE → HTMX
22+33+*Deep-dive research conducted 2026-02-07*
44+55+---
66+77+## The Opportunity
88+99+Every traditional forum (phpBB, Discourse, Flarum) works on a request-response model. You load a topic page. It's a snapshot. If someone replies while you're reading, you don't see it until you manually refresh. Discourse has real-time features via WebSockets, but it's a bolt-on — a separate infrastructure layer they built on top of their standard Rails app.
1010+1111+atBB is different. The AT Protocol *already has* a real-time event stream (the firehose/Jetstream). The AppView *already needs* to subscribe to it for indexing. The web UI *already uses* HTMX, which has declarative SSE support. The entire pipeline exists — it just needs to be connected.
1212+1313+**The data flow:**
1414+```
1515+User writes post AT Proto Jetstream atBB Browser
1616+to their PDS ───▶ Relay ───▶ (JSON WS) ───▶ AppView ───▶ (HTMX SSE)
1717+ indexes +
1818+ broadcasts
1919+```
2020+2121+End-to-end latency: ~1–2 seconds from PDS write to browser update. That's fast enough to feel "live" without feeling like chat.
2222+2323+---
2424+2525+## Architecture Overview
2626+2727+### Three Layers
2828+2929+```
3030+┌─────────────────────────────────────────────────────┐
3131+│ Browser (HTMX) │
3232+│ │
3333+│ ┌────────────┐ ┌──────────┐ ┌─────────────────┐ │
3434+│ │ Topic View │ │ Category │ │ Notification │ │
3535+│ │ sse-swap= │ │ View │ │ Badge │ │
3636+│ │ "newReply" │ │ sse-swap=│ │ sse-swap= │ │
3737+│ │ │ │ "newTopic│ │ "notification" │ │
3838+│ └─────┬──────┘ └────┬─────┘ └───────┬─────────┘ │
3939+│ │ │ │ │
4040+│ └──────────────┴────────────────┘ │
4141+│ │ SSE │
4242+└───────────────────────┼──────────────────────────────┘
4343+ │
4444+┌───────────────────────┼──────────────────────────────┐
4545+│ @atbb/web (Hono) │
4646+│ │ │
4747+│ GET /sse/thread/:id ──────┐ │
4848+│ GET /sse/category/:id ────┤ SSE │
4949+│ GET /sse/global ──────────┤ Endpoints │
5050+│ │ │
5151+│ ┌─────────────────────────┘ │
5252+│ │ Subscribe to AppView event bus │
5353+│ │ Render HTML fragments │
5454+│ │ Stream as SSE events │
5555+└────────────┼─────────────────────────────────────────┘
5656+ │
5757+┌────────────┼─────────────────────────────────────────┐
5858+│ │ @atbb/appview (Hono) │
5959+│ │ │
6060+│ ┌─────────▼──────────┐ ┌────────────────────────┐ │
6161+│ │ Event Bus │ │ Jetstream Consumer │ │
6262+│ │ (in-process) │◀──│ │ │
6363+│ │ │ │ space.atbb.* │ │
6464+│ │ topic:abc → │ │ filter + index │ │
6565+│ │ [subscriber1] │ │ │ │
6666+│ │ [subscriber2] │ │ Cursor persistence │ │
6767+│ │ category:xyz → │ │ Reconnection logic │ │
6868+│ │ [subscriber3] │ └────────────┬───────────┘ │
6969+│ └────────────────────┘ │ │
7070+│ │ │
7171+│ ┌─────────────────────────────────────▼───────────┐ │
7272+│ │ PostgreSQL │ │
7373+│ │ posts | categories | users | memberships │ │
7474+│ └─────────────────────────────────────────────────┘ │
7575+└──────────────────────────────────────────────────────┘
7676+ │
7777+ │ WebSocket (JSON)
7878+ ▼
7979+┌──────────────────────────────────────────────────────┐
8080+│ Jetstream │
8181+│ wss://jetstream2.us-east.bsky.network/subscribe │
8282+│ ?wantedCollections=space.atbb.* │
8383+└──────────────────────────────────────────────────────┘
8484+```
8585+8686+### Why SSE Over WebSocket for the Browser Connection
8787+8888+| Factor | SSE | WebSocket |
8989+|--------|-----|-----------|
9090+| Direction | Server → Client only | Bidirectional |
9191+| Forum fit | Perfect — users read far more than they write | Overkill |
9292+| User writes | Standard HTMX POST/PUT (already works) | Would need `ws-send` |
9393+| Infrastructure | Works through all proxies, CDNs, load balancers | Needs sticky sessions, special proxy config |
9494+| Reconnection | Browser `EventSource` auto-reconnects natively | Extension handles it |
9595+| Graceful degradation | If SSE breaks, forum still works as normal HTTP | Same |
9696+| HTMX integration | `sse-swap` maps events to DOM targets declaratively | OOB swap by ID only |
9797+| HTTP/2 concern | Uses one connection per stream (H/2 multiplexes) | Separate TCP connection |
9898+9999+**Forum interactions are fundamentally asymmetric.** Users spend 95% of their time reading. SSE handles the high-volume server→client push (new replies, presence, typing indicators). Standard HTMX POST handles the low-volume client→server actions (submitting replies, reacting). WebSocket's bidirectionality is wasted here.
100100+101101+---
102102+103103+## Layer 1: Jetstream Consumer (AppView)
104104+105105+### Connection Setup
106106+107107+Use `@skyware/jetstream` for MVP — it provides typed JSON events with cursor management. The full `@atproto/sync` firehose (CBOR + signatures) is available for post-MVP hardening.
108108+109109+```typescript
110110+// packages/appview/src/firehose/consumer.ts
111111+112112+import { Jetstream } from "@skyware/jetstream";
113113+import type { EventEmitter } from "node:events";
114114+115115+interface FirehoseConsumer {
116116+ start(): void;
117117+ stop(): void;
118118+ events: EventEmitter; // broadcast bus
119119+}
120120+121121+function createFirehoseConsumer(db: Database): FirehoseConsumer {
122122+ const events = new EventEmitter();
123123+124124+ const jetstream = new Jetstream({
125125+ endpoint: "wss://jetstream2.us-east.bsky.network/subscribe",
126126+ wantedCollections: ["space.atbb.*"],
127127+ cursor: loadCursorFromDb(db), // microsecond timestamp
128128+ });
129129+130130+ // Index new posts and broadcast for SSE
131131+ jetstream.onCreate("space.atbb.post", async (event) => {
132132+ const { did, commit, time_us } = event;
133133+ const post = await indexPost(db, did, commit.rkey, commit.cid, commit.record);
134134+ saveCursor(db, time_us);
135135+136136+ // Determine broadcast channel
137137+ if (post.rootPostId) {
138138+ // It's a reply — broadcast to the thread channel
139139+ events.emit(`topic:${post.rootPostId}`, { type: "newReply", post });
140140+ } else {
141141+ // It's a new topic — broadcast to the category channel
142142+ // NOTE: Current schema has `forumUri` not `categoryId`. To route
143143+ // "new topic in category X" events, need to either: (a) resolve
144144+ // category from forum metadata, or (b) add categoryUri to posts table.
145145+ // For now, broadcast to forum-level channel:
146146+ events.emit(`forum:${post.forumUri}`, { type: "newTopic", post });
147147+ }
148148+149149+ // Always broadcast to global (for notification badges, etc.)
150150+ events.emit("global", { type: "newPost", post });
151151+ });
152152+153153+ jetstream.onDelete("space.atbb.post", async (event) => {
154154+ const { did, commit, time_us } = event;
155155+ const post = await softDeletePost(db, did, commit.rkey);
156156+ saveCursor(db, time_us);
157157+158158+ if (post) {
159159+ events.emit(`topic:${post.rootPostId}`, { type: "postDeleted", post });
160160+ }
161161+ });
162162+163163+ // Index other record types similarly...
164164+ jetstream.onCreate("space.atbb.forum.category", async (event) => {
165165+ await indexCategory(db, event.did, event.commit.rkey, event.commit.record);
166166+ saveCursor(db, event.time_us);
167167+ events.emit("global", { type: "categoryUpdate" });
168168+ });
169169+170170+ jetstream.onCreate("space.atbb.reaction", async (event) => {
171171+ const reaction = await indexReaction(db, event.did, event.commit);
172172+ saveCursor(db, event.time_us);
173173+ events.emit(`topic:${reaction.topicId}`, { type: "newReaction", reaction });
174174+ });
175175+176176+ return {
177177+ start: () => jetstream.start(),
178178+ stop: () => jetstream.close(),
179179+ events,
180180+ };
181181+}
182182+```
183183+184184+### Jetstream Filtering
185185+186186+Jetstream supports NSID prefix wildcards:
187187+188188+```
189189+?wantedCollections=space.atbb.*
190190+```
191191+192192+This catches `space.atbb.post`, `space.atbb.forum.forum`, `space.atbb.forum.category`, `space.atbb.membership`, `space.atbb.reaction`, `space.atbb.modAction` — everything in the atBB namespace.
193193+194194+**Note:** Wildcard syntax is supported as long as the prefix (`space.atbb`) passes NSID validation, which it does. Jetstream allows up to 100 collection filters per connection. If wildcard filtering proves problematic in practice, the seven collections can be enumerated explicitly.
195195+196196+### Cursor Management
197197+198198+Jetstream events have a `time_us` field (Unix microseconds). Persist this as a cursor:
199199+200200+```typescript
201201+// Save every 100 events (not every event — that would hammer the DB)
202202+let eventsSinceSave = 0;
203203+function saveCursor(db: Database, cursor: number) {
204204+ eventsSinceSave++;
205205+ if (eventsSinceSave >= 100) {
206206+ db.execute("UPDATE firehose_state SET cursor = $1", [cursor]);
207207+ eventsSinceSave = 0;
208208+ }
209209+}
210210+211211+// On reconnect, rewind 5 seconds for safety (process events idempotently)
212212+function loadCursorFromDb(db: Database): number | undefined {
213213+ const row = db.queryOne("SELECT cursor FROM firehose_state");
214214+ if (!row?.cursor) return undefined;
215215+ return row.cursor - 5_000_000; // 5 seconds in microseconds
216216+}
217217+```
218218+219219+**Backfill window:** Jetstream retains ~24 hours of events. If the AppView is offline longer, fall back to `com.atproto.sync.getRepo` for known DIDs.
220220+221221+---
222222+223223+## Layer 2: Event Bus → SSE Endpoints (Web Package)
224224+225225+The web package subscribes to the AppView's event bus and renders HTML fragments streamed to the browser.
226226+227227+### Option A: Internal Event Bus (Single-Process)
228228+229229+If appview and web run in the same process (or web calls an appview SSE endpoint):
230230+231231+```typescript
232232+// packages/appview/src/routes/events.ts
233233+234234+import { Hono } from "hono";
235235+import { streamSSE } from "hono/streaming";
236236+237237+const app = new Hono();
238238+239239+// SSE endpoint for a specific thread
240240+app.get("/api/events/topic/:id", async (c) => {
241241+ const topicId = c.req.param("id");
242242+243243+ return streamSSE(c, async (stream) => {
244244+ // Send initial connection confirmation
245245+ await stream.writeSSE({ event: "connected", data: "ok" });
246246+247247+ // Heartbeat to prevent proxy timeouts
248248+ const heartbeat = setInterval(async () => {
249249+ await stream.writeSSE({ event: "heartbeat", data: "" });
250250+ }, 30_000);
251251+252252+ // Subscribe to topic events
253253+ const handler = async (event: ForumEvent) => {
254254+ // Hono's streamSSE accepts JSX directly in the data field
255255+ await stream.writeSSE({
256256+ event: event.type, // "newReply", "newReaction", "postDeleted"
257257+ data: renderEventComponent(event), // Returns JSX element
258258+ });
259259+ };
260260+261261+ firehoseConsumer.events.on(`topic:${topicId}`, handler);
262262+263263+ // Cleanup on disconnect
264264+ stream.onAbort(() => {
265265+ clearInterval(heartbeat);
266266+ firehoseConsumer.events.off(`topic:${topicId}`, handler);
267267+ });
268268+ });
269269+});
270270+271271+// SSE endpoint for a category (new topics)
272272+app.get("/api/events/category/:id", async (c) => {
273273+ const categoryId = c.req.param("id");
274274+275275+ return streamSSE(c, async (stream) => {
276276+ const heartbeat = setInterval(async () => {
277277+ await stream.writeSSE({ event: "heartbeat", data: "" });
278278+ }, 30_000);
279279+280280+ const handler = async (event: ForumEvent) => {
281281+ await stream.writeSSE({
282282+ event: event.type,
283283+ data: renderEventComponent(event)
284284+ });
285285+ };
286286+287287+ firehoseConsumer.events.on(`category:${categoryId}`, handler);
288288+289289+ stream.onAbort(() => {
290290+ clearInterval(heartbeat);
291291+ firehoseConsumer.events.off(`category:${categoryId}`, handler);
292292+ });
293293+ });
294294+});
295295+```
296296+297297+### Option B: AppView Exposes SSE, Web Proxies It
298298+299299+If appview and web are separate processes, the web package can either:
300300+1. Proxy the SSE stream from appview directly
301301+2. Consume appview SSE internally and re-emit with HTML rendering
302302+303303+Option 1 is simpler — the appview SSE endpoint returns HTML fragments, and the web package's JSX templates include `sse-connect` pointing at the appview.
304304+305305+### HTML Fragment Rendering
306306+307307+The key insight: SSE events carry **pre-rendered HTML fragments**, not JSON. This is what makes HTMX SSE zero custom client-side JS.
308308+309309+```typescript
310310+// packages/web/src/components/ReplyCard.tsx
311311+312312+import type { FC } from "hono/jsx";
313313+314314+interface ReplyCardProps {
315315+ author: string;
316316+ authorDid: string;
317317+ text: string;
318318+ createdAt: string;
319319+ replyCount?: number;
320320+}
321321+322322+export const ReplyCard: FC<ReplyCardProps> = (props) => (
323323+ <article class="reply" id={`reply-${props.authorDid}-${props.createdAt}`}>
324324+ <header class="reply-meta">
325325+ <a href={`/user/${props.authorDid}`} class="reply-author">
326326+ {props.author}
327327+ </a>
328328+ <time datetime={props.createdAt}>
329329+ {new Date(props.createdAt).toLocaleString()}
330330+ </time>
331331+ </header>
332332+ <div class="reply-body">
333333+ {props.text}
334334+ </div>
335335+ </article>
336336+);
337337+338338+// Render JSX component for SSE
339339+function renderReplyComponent(post: IndexedPost) {
340340+ // Hono's streamSSE accepts JSX directly — no manual string conversion needed
341341+ return <ReplyCard
342342+ author={post.authorHandle}
343343+ authorDid={post.authorDid}
344344+ text={post.text}
345345+ createdAt={post.createdAt}
346346+ />;
347347+ // Alternative if string needed: component.toString()
348348+}
349349+```
350350+351351+---
352352+353353+## Layer 3: HTMX SSE in the Browser
354354+355355+### Topic View (Thread Page)
356356+357357+```tsx
358358+// packages/web/src/routes/topic.tsx
359359+360360+export const TopicView: FC<TopicViewProps> = ({ topic, replies }) => (
361361+ <BaseLayout title={topic.title}>
362362+ {/* SSE connection scoped to this thread */}
363363+ <div hx-ext="sse" sse-connect={`/api/events/topic/${topic.id}`}>
364364+365365+ {/* Thread header */}
366366+ <article class="topic-op">
367367+ <h1>{topic.title}</h1>
368368+ <div class="post-body">{topic.text}</div>
369369+ <div class="post-meta">
370370+ by <a href={`/user/${topic.authorDid}`}>{topic.author}</a>
371371+ {" · "}
372372+ <time datetime={topic.createdAt}>{topic.createdAt}</time>
373373+ </div>
374374+ </article>
375375+376376+ {/* Reply list — new replies streamed in at the bottom */}
377377+ <section id="replies">
378378+ {replies.map((reply) => (
379379+ <ReplyCard {...reply} />
380380+ ))}
381381+382382+ {/* This target receives new replies via SSE */}
383383+ <div sse-swap="newReply" hx-swap="beforebegin"></div>
384384+ </section>
385385+386386+ {/* Reaction updates swap into specific post elements via OOB */}
387387+ {/* (The SSE "newReaction" event sends OOB-targeted HTML) */}
388388+389389+ {/* Reply count badge — updated in real-time */}
390390+ <span id="reply-count" sse-swap="replyCount">
391391+ {replies.length} replies
392392+ </span>
393393+394394+ {/* Typing indicator */}
395395+ <div id="typing-indicator" sse-swap="typing"></div>
396396+397397+ </div>
398398+399399+ {/* Reply form — standard HTMX POST (not SSE) */}
400400+ <form hx-post={`/api/topics/${topic.id}/reply`}
401401+ hx-swap="none"
402402+ hx-on::after-request="this.reset()">
403403+ <textarea name="text" placeholder="Write a reply..."
404404+ required minlength="1" maxlength="3000"></textarea>
405405+ <button type="submit">Reply</button>
406406+ </form>
407407+ </BaseLayout>
408408+);
409409+```
410410+411411+### Category View (Topic List)
412412+413413+```tsx
414414+export const CategoryView: FC<CategoryViewProps> = ({ category, topics }) => (
415415+ <BaseLayout title={category.name}>
416416+ <div hx-ext="sse" sse-connect={`/api/events/category/${category.id}`}>
417417+418418+ <h1>{category.name}</h1>
419419+ <p>{category.description}</p>
420420+421421+ <table class="topic-list">
422422+ <thead>
423423+ <tr>
424424+ <th>Topic</th>
425425+ <th>Author</th>
426426+ <th>Replies</th>
427427+ <th>Last Post</th>
428428+ </tr>
429429+ </thead>
430430+ <tbody id="topic-list-body"
431431+ sse-swap="newTopic"
432432+ hx-swap="afterbegin">
433433+ {topics.map((topic) => <TopicRow {...topic} />)}
434434+ </tbody>
435435+ </table>
436436+437437+ </div>
438438+ </BaseLayout>
439439+);
440440+```
441441+442442+### Global Notification Badge
443443+444444+This could be placed in the base layout so it works on every page:
445445+446446+```tsx
447447+// packages/web/src/layouts/base.tsx
448448+449449+export const BaseLayout: FC<BaseLayoutProps> = (props) => (
450450+ <html>
451451+ <head>
452452+ <script src="https://unpkg.com/htmx.org@2.0.4" />
453453+ <script src="https://unpkg.com/htmx-ext-sse@2.2.3/sse.js" />
454454+ </head>
455455+ <body hx-boost="true">
456456+ <header hx-ext="sse" sse-connect="/api/events/global">
457457+ <nav>
458458+ <a href="/">atBB Forum</a>
459459+ {/* Notification badge — updated live */}
460460+ <span id="notification-badge" sse-swap="notification"></span>
461461+ </nav>
462462+ </header>
463463+ <main>
464464+ {props.children}
465465+ </main>
466466+ </body>
467467+ </html>
468468+);
469469+```
470470+471471+---
472472+473473+## What This Enables (Concrete Features)
474474+475475+### Tier 1: Easy Wins (MVP-compatible)
476476+477477+| Feature | SSE Event | Behavior |
478478+|---------|-----------|----------|
479479+| **Live replies** | `newReply` | New reply appears at bottom of thread without refresh |
480480+| **Live new topics** | `newTopic` | New topic appears at top of category view |
481481+| **Reply count** | `replyCount` | Reply count badge updates in real-time |
482482+| **Post deletion** | `postDeleted` | Deleted post fades out or shows "[deleted]" |
483483+| **Mod actions** | `modAction` | Locked/pinned status updates live |
484484+485485+### Tier 2: Enhanced UX (Post-MVP)
486486+487487+| Feature | SSE Event | Behavior |
488488+|---------|-----------|----------|
489489+| **Typing indicator** | `typing` | "User X is typing..." shown below reply list |
490490+| **Online presence** | `presence` | "12 users viewing this topic" |
491491+| **Reaction animations** | `newReaction` | Like/upvote count increments with animation |
492492+| **Unread badges** | `notification` | Global nav shows unread count for subscribed topics |
493493+| **Topic bumping** | `topicBumped` | Topic list reorders when a topic gets a new reply |
494494+495495+### Tier 3: Differentiators (Future)
496496+497497+| Feature | Description |
498498+|---------|-------------|
499499+| **Cross-forum activity feed** | Since AT Proto identities span forums, show activity from all forums a user participates in |
500500+| **Live moderation dashboard** | Stream mod queue events in real-time |
501501+| **"Someone replied to your post" toasts** | Non-intrusive notification popups |
502502+503503+---
504504+505505+## Scaling Considerations
506506+507507+### Single Instance (MVP)
508508+509509+For MVP, an in-process `EventEmitter` is sufficient:
510510+511511+```
512512+Jetstream → AppView process (index + EventEmitter) → SSE streams to browsers
513513+```
514514+515515+No external infrastructure needed. The EventEmitter holds subscriber lists in memory.
516516+517517+**Capacity:** A single Node.js process can comfortably hold 1,000+ SSE connections. For a self-hosted forum, this is more than enough.
518518+519519+### Multi-Instance (Production)
520520+521521+If atBB needs horizontal scaling:
522522+523523+```
524524+Jetstream → AppView Instance 1 ──┐
525525+ ├── Redis Pub/Sub ──┬── Web Instance 1 → SSE
526526+Jetstream → AppView Instance 2 ──┘ └── Web Instance 2 → SSE
527527+```
528528+529529+- Only one AppView instance should consume Jetstream (use leader election or a single indexer process)
530530+- That instance publishes events to Redis Pub/Sub
531531+- All web instances subscribe to Redis and stream to their connected clients
532532+- **Alternative:** Use PostgreSQL `LISTEN/NOTIFY` instead of Redis — one fewer dependency
533533+534534+### HTTP/2 Requirement
535535+536536+SSE connections hold an HTTP connection open. Under HTTP/1.1, browsers limit to 6 connections per domain — a user with multiple tabs could exhaust this. **HTTP/2 multiplexes all streams over a single TCP connection**, eliminating this issue.
537537+538538+**Action:** Ensure the deployment setup (Docker Compose with nginx/Caddy) uses HTTP/2. Caddy enables HTTP/2 by default.
539539+540540+### Proxy Configuration
541541+542542+SSE requires long-lived connections. Configure reverse proxy timeouts:
543543+544544+```nginx
545545+# nginx — for SSE routes only
546546+location /api/events/ {
547547+ proxy_pass http://appview:3000;
548548+ proxy_http_version 1.1;
549549+ proxy_set_header Connection "";
550550+ proxy_buffering off;
551551+ proxy_cache off;
552552+ proxy_read_timeout 86400s; # 24 hours
553553+}
554554+```
555555+556556+Or with Caddy (no special config needed — it handles streaming correctly by default).
557557+558558+---
559559+560560+## Implementation Roadmap
561561+562562+### Phase 1 (Part of AppView Core milestone)
563563+1. **Add Jetstream consumer** — `@skyware/jetstream` with `space.atbb.*` filter
564564+2. **Add in-process EventEmitter** — broadcast indexed events by channel
565565+3. **Add SSE endpoint** — `/api/events/topic/:id` using Hono's `streamSSE`
566566+4. **Persist cursor** — store `time_us` in PostgreSQL, reload on restart
567567+568568+### Phase 2 (Part of Web UI milestone)
569569+5. **Add `htmx-ext-sse`** — include SSE extension alongside HTMX in base layout
570570+6. **Add `sse-connect` to topic view** — connect to thread-specific SSE stream
571571+7. **Render reply HTML fragments** — reuse existing JSX components for SSE payloads
572572+8. **Add `sse-connect` to category view** — connect to category-specific SSE stream
573573+574574+### Phase 3 (Post-MVP polish)
575575+9. **Typing indicators** — debounced POST from textarea keyup, broadcast via SSE
576576+10. **Presence tracking** — track SSE connections per topic, broadcast count
577577+11. **Notification badges** — global SSE stream for subscribed topic updates
578578+12. **Graceful degradation** — ensure forum works perfectly without SSE (progressive enhancement)
579579+580580+### Dependencies
581581+582582+| Step | Depends On | New Packages |
583583+|------|-----------|--------------|
584584+| Jetstream consumer | Phase 1 AppView Core | `@skyware/jetstream`, `ws` |
585585+| EventEmitter | Jetstream consumer | None (Node.js built-in) |
586586+| SSE endpoint | EventEmitter + Hono | None (`hono/streaming` built-in) |
587587+| HTMX SSE extension | Web UI + SSE endpoint | `htmx-ext-sse` (CDN) |
588588+589589+---
590590+591591+## Why This Matters
592592+593593+No other forum software has this architecture. Here's the comparison:
594594+595595+| Forum | Real-Time Approach |
596596+|-------|-------------------|
597597+| **phpBB** | None. Pure request-response. Must refresh to see new replies. |
598598+| **Discourse** | Custom WebSocket implementation via MessageBus gem. Bolt-on architecture, requires Redis, sticky sessions. |
599599+| **Flarum** | Pusher.com integration (third-party SaaS). Adds external dependency and cost. |
600600+| **NodeBB** | Socket.io (WebSocket). Heavy client-side JS framework. |
601601+| **atBB** | Protocol-native firehose → in-process event bus → SSE → HTMX declarative swap. Zero *custom* client-side JS (HTMX itself is ~14KB). The real-time stream is architecturally intrinsic, not bolted on. |
602602+603603+The AT Protocol firehose means atBB doesn't *add* real-time — it *is* real-time. The firehose consumer that indexes posts is the same component that powers live updates. There's no separate infrastructure, no Redis, no WebSocket server, no client-side framework. Just HTML attributes.
604604+605605+This is atBB's strongest architectural differentiator and should be a first-class feature from day one.
606606+607607+---
608608+609609+## Sources
610610+611611+- [HTMX SSE Extension Docs](https://htmx.org/extensions/sse/)
612612+- [htmx-ext-sse npm package](https://www.npmjs.com/package/htmx-ext-sse)
613613+- [HTMX WebSocket vs SSE comparison](https://htmx.org/extensions/ws/)
614614+- [Hono SSE Streaming API](https://hono.dev/docs/helpers/streaming)
615615+- [Bluesky Jetstream GitHub](https://github.com/bluesky-social/jetstream)
616616+- [Jetstream: Shrinking the Firehose by >99%](https://jazco.dev/2024/09/24/jetstream/)
617617+- [@skyware/jetstream](https://www.npmjs.com/package/@skyware/jetstream)
618618+- [@atproto/sync](https://www.npmjs.com/package/@atproto/sync)
619619+- [@atproto/tap (backfill tool)](https://docs.bsky.app/blog/introducing-tap)
620620+- [Bluesky Firehose Guide](https://docs.bsky.app/docs/advanced-guides/firehose)
621621+- [benc-uk/htmx-go-chat (SSE chat example)](https://github.com/benc-uk/htmx-go-chat)
622622+- [Live Website Updates with Go, SSE, and HTMX](https://threedots.tech/post/live-website-updates-go-sse-htmx/)
623623+- [SSE vs WebSockets](https://www.smashingmagazine.com/2018/02/sse-websockets-data-flow-http2/)