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
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #4 from malpercio-dev/claude/plan-mobile-apps-Gk45D

docs: add mobile apps plan (React Native + Expo)

authored by

Malpercio and committed by
GitHub
43c8564c 0c626e18

+317
+5
docs/atproto-forum-plan.md
··· 207 207 - **Phase C — Federated moderation:** Moderators on different AppView instances (or using third-party tools) can perform mod actions on the Forum DID via delegation, enabling distributed moderation teams. 208 208 - **Phase D — Extract and propose:** Package the delegation spec as a standalone AT Proto proposal with the forum as the reference use case. Contribute upstream. 209 209 210 + ### Mobile Apps (iOS & Android) 211 + 212 + 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). 213 + 210 214 ### Other Future Work 211 215 - Nested/threaded replies 212 216 - Full-text search (maybe Meilisearch) ··· 219 223 - Custom themes & branding 220 224 - Plugin/extension system 221 225 - Email notifications 226 + - Push notifications (mobile + web) 222 227 - RSS feeds per category/topic
+312
docs/mobile-apps-plan.md
··· 1 + # atBB Mobile Apps — Plan 2 + 3 + This 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 + 9 + The 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 + 17 + The web app remains the primary interface. Mobile apps are a complement, not a replacement. 18 + 19 + --- 20 + 21 + ## Architecture Fit 22 + 23 + The 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 + 37 + Mobile 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 + 63 + These 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 + 91 + Add a new package to the existing workspace: 92 + 93 + ``` 94 + packages/ 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 + 101 + The 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 + ``` 124 + Tab Bar 125 + ├── Home (categories → topics → thread) 126 + ├── Notifications 127 + └── Profile / Settings 128 + ``` 129 + 130 + Use 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 + 148 + AT Proto OAuth on mobile follows the standard OAuth 2.0 + PKCE + DPoP flow for native apps: 149 + 150 + 1. User enters their handle or PDS URL 151 + 2. App resolves the user's PDS and authorization server from the user's DID document 152 + 3. App generates a DPoP key pair (stored in secure enclave/keystore) and creates a PKCE challenge 153 + 4. App opens an in-app browser (ASWebAuthenticationSession on iOS, Custom Tab on Android) to the authorization URL 154 + 5. User authenticates on their PDS 155 + 6. PDS redirects back to the app via a custom URI scheme (`atbb://oauth/callback`) or universal link 156 + 7. **App exchanges the authorization code directly with the user's PDS authorization server** (not via the appview) to obtain access/refresh tokens 157 + 8. Tokens stored in `expo-secure-store` (keychain on iOS, keystore on Android) 158 + 9. Subsequent API calls to the appview include a DPoP-bound bearer token 159 + 10. **The appview validates tokens against the user's DID document** — it doesn't broker authentication 160 + 161 + This 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 + 165 + AT 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 + 171 + This 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 + ``` 180 + User posts a reply 181 + 182 + 183 + Firehose event 184 + 185 + 186 + AppView indexes reply 187 + 188 + 189 + Notification service checks: 190 + "Who should be notified?" 191 + 192 + 193 + Sends push via APNs (iOS) 194 + and/or FCM (Android) 195 + 196 + 197 + 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 + 222 + Use a layered caching strategy: 223 + 224 + 1. **HTTP cache** — TanStack Query caches API responses in memory with configurable stale times 225 + 2. **Persistent cache** — TanStack Query's `persistQueryClient` with AsyncStorage or SQLite for across-app-restart caching 226 + 3. **Explicit offline mode** — "Save thread for offline" downloads thread data to SQLite; viewable without network 227 + 4. **Optimistic writes** — compose a reply offline, queue it, send when back online 228 + 229 + MVP 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 + 237 + Prerequisites: 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 + 284 + Mobile 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 + 302 + Each phase can be developed and shipped independently. Phase 1 alone is a useful read-only companion app. 303 + 304 + --- 305 + 306 + ## Open Questions 307 + 308 + 1. **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). 309 + 2. **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. 310 + 3. **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. 311 + 4. **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. 312 + 5. **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.