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
at main 312 lines 15 kB view raw view rendered
1# atBB Mobile Apps — Plan 2 3This document outlines the strategy for building mobile applications for atBB. The existing architecture — a clean JSON API (appview) separate from the server-rendered web UI — makes this straightforward. 4 5--- 6 7## Why Mobile Apps? 8 9The web UI (`@atbb/web`) will be responsive and mobile-friendly, but dedicated apps offer: 10 11- **Push notifications** for replies, mentions, and moderation events 12- **Native performance** — smooth scrolling through long threads, instant navigation 13- **Offline reading** — cache threads and categories for subway/airplane use 14- **Deep OS integration** — share sheets, AT Proto URI handling, biometric auth 15- **Better compose experience** — native keyboard handling, image picker, draft persistence 16 17The web app remains the primary interface. Mobile apps are a complement, not a replacement. 18 19--- 20 21## Architecture Fit 22 23The existing system already separates concerns in a way that supports mobile clients: 24 25``` 26┌─────────────┐ 27│ Forum UI │──────┐ 28│ (Web App) │ │ 29└─────────────┘ │ ┌──────────────┐ ┌─────────────────┐ 30 ├────▶│ AppView │────▶│ Firehose / │ 31┌─────────────┐ │ │ (JSON API) │◀────│ User PDS nodes │ 32│ Mobile Apps │──────┘ └──────────────┘ └─────────────────┘ 33│ (iOS/Andrd) │ 34└─────────────┘ 35``` 36 37Mobile apps consume the **same `/api/*` endpoints** as the web UI. No new backend is needed — just the existing appview. 38 39### What the appview already provides 40 41| Endpoint | Purpose | Mobile use | 42|---|---|---| 43| `GET /api/forum` | Forum metadata | App title, description, branding | 44| `GET /api/categories` | Category list | Home screen / tab bar | 45| `GET /api/categories/:id/topics` | Topic list (paginated) | Category view with pull-to-refresh | 46| `GET /api/topics/:id` | Thread (OP + replies) | Thread view | 47| `POST /api/topics` | Create topic | Compose screen | 48| `POST /api/posts` | Create reply | Reply sheet | 49 50### What needs to be added to the appview for mobile 51 52| Endpoint / Feature | Purpose | 53|---|---| 54| `GET /api/users/:did` | User profile / post history | 55| `GET /api/notifications` | Notification feed (replies to your posts, mentions, mod actions) | 56| `POST /api/reactions` | Add reaction to a post (requires `reactions` table + `space.atbb.reaction` lexicon) | 57| `DELETE /api/reactions/:id` | Remove reaction from a post | 58| `POST /api/devices` | Register push notification token (APNs / FCM) | 59| `DELETE /api/devices/:id` | Unregister push token | 60| Pagination headers / cursors | Consistent cursor-based pagination across all list endpoints | 61| `ETag` / `Last-Modified` headers | Conditional requests for efficient caching | 62 63These additions benefit the web UI too — they aren't mobile-only concerns. 64 65--- 66 67## Technology Choice: React Native + Expo 68 69**Recommendation:** React Native with Expo for cross-platform iOS and Android from a single codebase. 70 71### Why React Native + Expo 72 73- **Single codebase** for iOS and Android — critical for a small team / solo developer 74- **TypeScript** — same language as the rest of the monorepo; can share types from `@atbb/lexicon` 75- **Expo** simplifies builds, OTA updates, push notifications, and app store submissions 76- **Mature ecosystem** — navigation (React Navigation / Expo Router), state management, networking 77- **AT Proto libraries work** — `@atproto/api` runs in React Native with minor polyfills 78- **AGPL-3.0 compatible** — React Native's MIT license is compatible with the project license 79 80### Why not other options 81 82| Option | Reason to skip | 83|---|---| 84| Flutter | Dart — different language from the rest of the stack, can't share types | 85| Native (Swift/Kotlin) | Two codebases to maintain, slower iteration for a small team | 86| PWA only | iOS Web Push requires add-to-home-screen with constrained UX, no app store presence, weaker offline | 87| Capacitor/Ionic | WebView wrapper — won't feel native, performance ceiling | 88 89### Monorepo integration 90 91Add a new package to the existing workspace: 92 93``` 94packages/ 95 lexicon/ # shared types (already exists) 96 appview/ # JSON API (already exists) 97 web/ # server-rendered UI (already exists) 98 mobile/ # NEW — React Native + Expo app 99``` 100 101The mobile package imports `@atbb/lexicon` for type safety against AT Protocol records at dev/typecheck time (ensuring the mobile app stays in sync with lexicon changes). However, the actual app builds via Expo's Metro bundler (`expo start`, `eas build`), not via `pnpm build` — Turborepo handles the `lexicon``appview`/`web` build chain, but mobile has its own separate build tooling. 102 103--- 104 105## Mobile App Structure 106 107### Screens 108 109| Screen | Description | API | 110|---|---|---| 111| **Login** | AT Proto OAuth flow via in-app browser (exchanges tokens with user's PDS) | `@atproto/oauth-client` | 112| **Home** | Category list, forum branding | `GET /api/categories` | 113| **Category** | Topic list with pull-to-refresh, infinite scroll | `GET /api/categories/:id/topics` | 114| **Topic/Thread** | OP + flat replies, pagination | `GET /api/topics/:id` | 115| **Compose** | New topic form (select category, write post) | `POST /api/topics` | 116| **Reply** | Reply sheet (bottom sheet or modal) | `POST /api/posts` | 117| **Notifications** | Reply/mention/mod action feed | `GET /api/notifications` | 118| **Profile** | User info, post history | `GET /api/users/:did` | 119| **Settings** | Push notification prefs, theme, logout | Local + `/api/devices` | 120 121### Navigation 122 123``` 124Tab Bar 125├── Home (categories → topics → thread) 126├── Notifications 127└── Profile / Settings 128``` 129 130Use Expo Router (file-based routing) or React Navigation with a bottom tab + stack pattern. 131 132### Key Libraries 133 134| Concern | Library | 135|---|---| 136| Navigation | Expo Router or React Navigation | 137| HTTP client | Standard `fetch` or `ky` (lightweight) | 138| State / cache | TanStack Query (React Query) — handles caching, pagination, background refetch | 139| Push notifications | `expo-notifications` + server-side APNs/FCM | 140| Secure storage | `expo-secure-store` (for auth tokens) | 141| AT Proto OAuth | `@atproto/oauth-client` (client-side OAuth + DPoP) + `expo-auth-session` (in-app browser) | 142| Offline storage | SQLite via `expo-sqlite` (cache threads for offline reading) | 143 144--- 145 146## Authentication on Mobile 147 148AT Proto OAuth on mobile follows the standard OAuth 2.0 + PKCE + DPoP flow for native apps: 149 1501. User enters their handle or PDS URL 1512. App resolves the user's PDS and authorization server from the user's DID document 1523. App generates a DPoP key pair (stored in secure enclave/keystore) and creates a PKCE challenge 1534. App opens an in-app browser (ASWebAuthenticationSession on iOS, Custom Tab on Android) to the authorization URL 1545. User authenticates on their PDS 1556. PDS redirects back to the app via a custom URI scheme (`atbb://oauth/callback`) or universal link 1567. **App exchanges the authorization code directly with the user's PDS authorization server** (not via the appview) to obtain access/refresh tokens 1578. Tokens stored in `expo-secure-store` (keychain on iOS, keystore on Android) 1589. Subsequent API calls to the appview include a DPoP-bound bearer token 15910. **The appview validates tokens against the user's DID document** — it doesn't broker authentication 160 161This preserves AT Proto's decentralized model: users authenticate with their own PDS, then present credentials to the appview. The mobile app needs to implement AT Proto OAuth client logic directly (the `@atproto/oauth-client` library can help, though mobile support is still maturing). 162 163### DPoP Key Management on Mobile 164 165AT Proto uses DPoP (Demonstrating Proof of Possession) to bind access tokens to a specific client key, preventing token theft/replay attacks. On mobile, this requires: 166 167- **Secure key storage:** The DPoP private key must be stored in platform secure storage — iOS Keychain (accessed via Secure Enclave on supported devices) or Android Keystore. Use `expo-secure-store` or platform-specific crypto APIs. 168- **Key lifecycle:** Generate a new DPoP key pair on first login. The key should persist across app sessions but be revoked/regenerated on logout or token refresh failure. 169- **Proof generation:** For each API request, generate a DPoP proof (signed JWT) using the private key. The `@atproto/oauth-client` library handles this, but mobile-specific integration with secure storage may require custom bindings. 170 171This is a mobile-specific concern that doesn't exist in the web UI (where DPoP keys can be ephemeral or stored in localStorage for less-critical use cases). 172 173--- 174 175## Push Notifications 176 177### Architecture 178 179``` 180User posts a reply 181182183 Firehose event 184185186 AppView indexes reply 187188189 Notification service checks: 190 "Who should be notified?" 191192193 Sends push via APNs (iOS) 194 and/or FCM (Android) 195196197 Mobile device shows notification 198``` 199 200### Implementation 201 202- Mobile app registers its push token with `POST /api/devices` on login 203- Appview maintains a `devices` table: `(id, user_did, platform, push_token, created_at)`**this is a purely local/appview-managed table** (not backed by an AT Proto record), unlike the `(did, rkey, cid, indexed_at)` pattern used for AT Proto record tables 204- When the indexer processes a new post that is a reply, it checks if the parent post's author or thread participants have registered devices 205- A lightweight push service (can be part of the appview or a separate worker) sends the notification payload 206- Start simple: notify on direct replies only. Expand to mentions, mod actions, thread subscriptions later 207 208### Notification types (phased) 209 210| Phase | Notification | 211|---|---| 212| Initial | Direct reply to your post | 213| Later | @mention in a post | 214| Later | Mod action on your post (locked, deleted) | 215| Later | New topic in a subscribed category | 216| Later | Thread subscription (get notified on any reply in a thread) | 217 218--- 219 220## Offline Support 221 222Use a layered caching strategy: 223 2241. **HTTP cache** — TanStack Query caches API responses in memory with configurable stale times 2252. **Persistent cache** — TanStack Query's `persistQueryClient` with AsyncStorage or SQLite for across-app-restart caching 2263. **Explicit offline mode** — "Save thread for offline" downloads thread data to SQLite; viewable without network 2274. **Optimistic writes** — compose a reply offline, queue it, send when back online 228 229MVP mobile app only needs layer 1 (in-memory cache). Layers 2-4 come later. 230 231--- 232 233## Implementation Phases 234 235### Mobile Phase 0: Scaffold (after web MVP Phase 4+) 236 237Prerequisites: Appview API is stable with read endpoints working, AT Proto OAuth is implemented. 238 239- [ ] Add `packages/mobile` with Expo + TypeScript template 240- [ ] Configure workspace — `@atbb/lexicon` as dependency for shared types 241- [ ] Set up Expo Router navigation structure (tabs + stacks) 242- [ ] Implement API client layer using `fetch` + TanStack Query 243- [ ] Create basic UI shell: tab bar, placeholder screens 244 245### Mobile Phase 1: Read-Only Browse 246 247- [ ] Login screen — AT Proto OAuth client (`@atproto/oauth-client` + `expo-auth-session`) with direct PDS token exchange 248- [ ] Home screen — category list from `GET /api/categories` 249- [ ] Category screen — topic list with pull-to-refresh and infinite scroll 250- [ ] Thread screen — OP + flat replies with pagination 251- [ ] Basic theming (light/dark mode following system preference) 252- [ ] Loading states, error states, empty states 253 254### Mobile Phase 2: Write & Interact 255 256- [ ] Compose screen — create new topic (select category, write text) 257- [ ] Reply sheet — reply to a post (bottom sheet UX) 258- [ ] Reactions — tap to react on posts 259- [ ] Profile screen — view your own posts and membership info 260- [ ] Pull-to-refresh and background data sync 261 262### Mobile Phase 3: Notifications & Polish 263 264- [ ] Push notification registration (`expo-notifications` + APNs/FCM) 265- [ ] Notification feed screen 266- [ ] Appview: `GET /api/notifications` endpoint + push delivery worker 267- [ ] Appview: `POST /api/devices` and `DELETE /api/devices/:id` endpoints 268- [ ] Deep linking — tap notification to open relevant thread 269- [ ] Universal links / custom URI scheme for `at://` URIs 270 271### Mobile Phase 4: Offline & Release 272 273- [ ] Persistent query cache (survive app restarts) 274- [ ] Offline thread reading (SQLite cache) 275- [ ] Optimistic reply queueing 276- [ ] App store assets (icon, screenshots, descriptions) 277- [ ] TestFlight (iOS) and internal testing track (Android) release 278- [ ] App Store and Google Play submission 279 280--- 281 282## API Stability & Compatibility 283 284Mobile clients can't be force-updated instantly, so API stability matters: 285 286- **Additive changes only** — new fields are always optional, never remove existing fields 287- **Client version header** — mobile app can send `X-ATBB-Client-Version: 1.2.0`; appview can respond with upgrade-required if the client is too old 288- **API versioning** (e.g., `/api/v1/*`) can be introduced later when there's an actual breaking change to warrant it — deferring until post-v1 avoids unnecessary complexity while the API is still evolving 289 290--- 291 292## Estimated Effort 293 294| Phase | Scope | Notes | 295|---|---|---| 296| Phase 0 | Scaffold + navigation shell | Straightforward Expo setup | 297| Phase 1 | Read-only browsing + auth | Bulk of the mobile work — screens, auth flow, caching | 298| Phase 2 | Write path + interactions | Compose UX, reply sheets, reactions | 299| Phase 3 | Push notifications | Requires appview additions (notification service, device registration) | 300| Phase 4 | Offline + app store release | Polish, testing, store submission process | 301 302Each phase can be developed and shipped independently. Phase 1 alone is a useful read-only companion app. 303 304--- 305 306## Open Questions 307 3081. **Expo vs bare React Native?** Start with Expo managed workflow for speed. Eject to bare only if a native module requires it (unlikely for a forum app). 3092. **Code sharing between web and mobile?** The web (server-rendered hypermedia with Hono JSX + HTMX) and mobile (client-side React Native SPA) are fundamentally different paradigms. Component sharing is unlikely to be practical regardless of future web tech choices. Best to share types from `@atbb/lexicon` and API contracts, not UI components. 3103. **Moderation UI on mobile?** Admins/mods may want to moderate from their phone. This could be a separate "admin" tab that appears based on role, or deferred to web-only initially. 3114. **Multiple forum support?** The app could support connecting to multiple atBB instances (different appview URLs). This aligns with the decentralized nature of AT Protocol but adds complexity. Defer to post-v1. 3125. **AT Proto OAuth on mobile maturity?** The OAuth spec for AT Protocol is still evolving. Verify the state of native app support (PKCE, custom URI schemes) and the `@atproto/oauth-client` library's mobile compatibility before starting implementation. DPoP key management specifics covered in Authentication section above.