mobile bluesky app made with flutter lazurite.stormlightlabs.org/
mobile bluesky flutter
3
fork

Configure Feed

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

docs: notifications & app view routing

+646
+289
docs/specs/notification.md
··· 1 + --- 2 + title: Notification Architecture 3 + updated: 2026-04-29 4 + --- 5 + 6 + ## Summary 7 + 8 + Lazurite currently supports in-app notifications (alerts feed + unread badge), but 9 + it does not deliver OS-level notifications when the app is backgrounded or killed. 10 + This spec defines a defensive, staged path from polling-only behavior to robust 11 + push + local notification delivery. 12 + 13 + ## Current State (Lazurite) 14 + 15 + What exists today: 16 + 17 + - `app.bsky.notification.listNotifications` for alerts feed 18 + - `app.bsky.notification.getUnreadCount` polled every 30s in foreground 19 + - `app.bsky.notification.updateSeen` on read/open flows 20 + - `workmanager` already integrated for scheduled posts 21 + 22 + What is missing: 23 + 24 + - No device push token lifecycle 25 + - No `registerPush` / `unregisterPush` integration 26 + - No local notification renderer/channels/categories 27 + - No durable dedupe state for "already notified" events 28 + - No background notification sync worker 29 + 30 + ## Research Findings 31 + 32 + ### Bluesky notification APIs 33 + 34 + Core endpoints (auth required): 35 + 36 + - `app.bsky.notification.listNotifications` 37 + - Params include `cursor`, `limit` (1-100), optional `reasons`, optional `seenAt` 38 + - Response includes `notifications[]`, optional `cursor`, optional `seenAt` 39 + - `app.bsky.notification.getUnreadCount` 40 + - Returns unread `count` 41 + - `app.bsky.notification.updateSeen` 42 + - Marks notifications as seen at a timestamp 43 + - `app.bsky.notification.registerPush` 44 + - Required body: `serviceDid`, `token`, `platform`, `appId` 45 + - Optional body: `ageRestricted` 46 + - `app.bsky.notification.unregisterPush` 47 + - Required body: `serviceDid`, `token`, `platform`, `appId` 48 + - `app.bsky.notification.putPreferencesV2` 49 + - Server-side notification preference controls (follow/like/reply/etc.) 50 + 51 + Important nuance: 52 + 53 + - Official Bluesky app passes an `atproto-proxy` header for push registration: 54 + `did:web:api.bsky.app#bsky_notif` 55 + - Official Bluesky app uses `serviceDid: did:web:api.bsky.app` (or staging DID) 56 + 57 + ### Reference implementation patterns 58 + 59 + Strong patterns worth reusing: 60 + 61 + - Android FCM entrypoint (`FirebaseMessagingService`) parses push payload keys: 62 + `senderDid`, `targetDid`, `recordUri`, `reason` 63 + - Push payload is treated as a trigger, not trusted display content: 64 + - Resolve full record via authenticated API 65 + - Apply moderation/filtering before display 66 + - Defensive processing contract: 67 + - Timeout-bound notification processing (10s) 68 + - Explicit processed/dropped ack state 69 + - Dedup by stable notification identifiers 70 + - Permission UX: 71 + - Runtime request + rationale + settings fallback 72 + - Delivery UX: 73 + - Channel per reason family (likes/replies/follows/etc.) 74 + - Deep links for post/profile targets 75 + 76 + Architecture-specific behavior to avoid coupling to: 77 + 78 + - Routing token registration through a custom backend endpoint can be valid, but 79 + it is optional and not required for a direct Bluesky `registerPush` strategy. 80 + 81 + ### Platform/Flutter constraints 82 + 83 + - Android 13+: `POST_NOTIFICATIONS` runtime permission required 84 + - Android periodic background work minimum interval is 15 minutes 85 + - Android exact-alarm behavior tightened on Android 14 (not suitable as default) 86 + - iOS background execution is system-managed and non-deterministic 87 + (`earliestBeginDate` is not a guarantee) 88 + - iOS background push updates are low priority and can be throttled 89 + - Flutter background handlers must be top-level entry points 90 + (`@pragma('vm:entry-point')` where required) 91 + 92 + ## Assumptions and Open Questions 93 + 94 + Assumptions (to validate during implementation): 95 + 96 + - Lazurite can register directly against Bluesky notification service DID 97 + (`did:web:api.bsky.app`) using `atproto-proxy: ...#bsky_notif`. 98 + - Notification payload and reason mapping from Bluesky are stable enough to map 99 + into local channel/category policy. 100 + 101 + Open questions: 102 + 103 + - Multi-account policy: should each account register separate token records? 104 + - Opt-out semantics: unregister on logout vs keep per-account registration? 105 + - Should we support Android foreground service fallback for tighter latency, or 106 + accept periodic/background best-effort only? 107 + - Do we expose per-reason push toggles locally first, or defer to server-side 108 + `putPreferencesV2` only? 109 + 110 + ## Architecture Options 111 + 112 + ### Option A: Polling only (foreground + background) 113 + 114 + Pros: 115 + 116 + - No FCM/APNs setup required 117 + - Simpler backend story 118 + 119 + Cons: 120 + 121 + - Delayed delivery (>=15 min in background) 122 + - iOS execution unpredictability 123 + - Higher API/battery overhead 124 + 125 + ### Option B: Push only 126 + 127 + Pros: 128 + 129 + - Fastest delivery 130 + - Lower polling load 131 + 132 + Cons: 133 + 134 + - Requires strict token lifecycle and permission handling 135 + - Delivery depends on push transport + payload correctness 136 + 137 + ### Option C: Hybrid (recommended) 138 + 139 + - Push is primary trigger for near-real-time delivery 140 + - Polling remains as fallback and for reconciliation 141 + - Foreground unread badge polling can remain lightweight 142 + 143 + ## Recommended Design 144 + 145 + ### 1. NotificationDomainService 146 + 147 + Create a domain orchestrator that all entry points call: 148 + 149 + - `onForegroundTick()` (badge + optional reconcile) 150 + - `onBackgroundTick()` (reconcile window) 151 + - `onPushPayload(Map<String, String>)` 152 + - `markSeen(DateTime at)` 153 + 154 + Responsibilities: 155 + 156 + - Parse/validate payload defensively 157 + - Fetch canonical notification details from Bluesky 158 + - Filter by moderation + user prefs 159 + - Dedupe and persist delivery state 160 + - Trigger OS local notification display 161 + 162 + ### 2. Push token lifecycle 163 + 164 + Add `PushRegistrationService`: 165 + 166 + - Acquire platform token (FCM/APNs bridge) 167 + - Register with Bluesky `registerPush` 168 + - Re-register on token refresh, login, account switch, app upgrade 169 + - Unregister on logout/account removal (`unregisterPush`) 170 + 171 + Inputs: 172 + 173 + - `serviceDid` (prod/staging aware) 174 + - `platform` (`ios`/`android`) 175 + - `appId` (bundle/package identifier) 176 + - `token` 177 + 178 + ### 3. Local notifications 179 + 180 + Use a local notification adapter abstraction with platform implementations. 181 + 182 + - Android: 183 + - Channel groups by reason family (`mentions`, `replies`, `follows`, `likes`, `misc`) 184 + - Tap routes to `/post?uri=...` or `/profile/view?actor=...` 185 + - iOS: 186 + - Category identifiers for future actions 187 + - Deep link userInfo payload for route restoration 188 + 189 + ### 4. Dedupe + delivery state (Drift) 190 + 191 + Add a new table (with migration): `notification_deliveries` 192 + 193 + Suggested fields: 194 + 195 + - `id` (PK) 196 + - `accountDid` (text) 197 + - `notificationUri` (text, indexed) 198 + - `notificationCid` (text nullable) 199 + - `reason` (text) 200 + - `indexedAt` (datetime) 201 + - `source` (`push|poll`) 202 + - `deliveredAt` (datetime) 203 + - `openedAt` (datetime nullable) 204 + - `dismissedAt` (datetime nullable) 205 + - unique constraint on (`accountDid`, `notificationUri`) 206 + 207 + Use this table to avoid duplicate OS notifications across push and poll paths. 208 + 209 + ### 5. Background execution 210 + 211 + Reuse existing `workmanager` foundation. 212 + 213 + - Android: 214 + - Periodic reconcile task at 15m+ cadence 215 + - Network-connected constraint 216 + - iOS: 217 + - Background fetch / BGTaskScheduler best-effort reconcile 218 + - Keep tasks short and idempotent 219 + 220 + ### 6. Permission UX 221 + 222 + - Ask only after contextual primer (alerts/home), not on first launch 223 + - On deny, show "Open Settings" path 224 + - Keep in-app alerts functional even when OS permission is denied 225 + 226 + ## Rollout Plan 227 + 228 + ### Phase N0 - Foundation hardening 229 + 230 + - Extract notification orchestration service 231 + - Add delivery-state persistence + migrations 232 + - Keep behavior polling-only 233 + 234 + ### Phase N1 - Local notifications from polling 235 + 236 + - Emit OS local notifications for new unseen items discovered via reconcile 237 + - Validate routing, dedupe, and permissions 238 + 239 + ### Phase N2 - Push registration and handling 240 + 241 + - Add token registration/unregistration 242 + - Add background push payload handler -> fetch canonical record -> display 243 + 244 + ### Phase N3 - Preference integration 245 + 246 + - Add server preference sync (`getPreferences` / `putPreferencesV2`) 247 + - Add local controls mapped to server fields 248 + 249 + ### Phase N4 - Reliability/observability 250 + 251 + - Add structured notification logs + debug screen counters 252 + - Add failure metrics (token register failures, dropped payloads, dedupe suppressions) 253 + 254 + ## Testing Strategy 255 + 256 + Required coverage per phase: 257 + 258 + - Unit tests: 259 + - Payload parsing/validation 260 + - Dedupe decisions 261 + - token lifecycle state machine 262 + - Bloc/cubit tests: 263 + - Permission gating 264 + - unread count reconcile behavior 265 + - Integration tests: 266 + - deep link open from notification payload 267 + - background worker reconciliation path 268 + - Manual smoke matrix: 269 + - Android 13+ deny/allow paths 270 + - iOS allow/deny/settings round trip 271 + - multi-account register/unregister correctness 272 + 273 + ## Risks and Mitigations 274 + 275 + - Risk: Duplicate alerts from push + poll race 276 + - Mitigation: persisted dedupe with unique key on notification URI 277 + - Risk: Background handlers killed or delayed 278 + - Mitigation: hybrid model + reconcile worker + idempotent processing 279 + - Risk: API/proxy mismatches for `registerPush` 280 + - Mitigation: stage against test account, log raw request/response status 281 + - Risk: Permission denial degrades trust 282 + - Mitigation: contextual request timing, clear fallback behavior 283 + 284 + ## Deferred (Not in initial rollout) 285 + 286 + - Rich actions (reply/like from notification shade) 287 + - Notification grouping by thread/conversation 288 + - Server-driven quiet hours / digest mode 289 + - Desktop/web parity
+244
docs/specs/routing.md
··· 1 + --- 2 + title: AppView Routing (Bluesky + Blacksky + microcosm Fallbacks) 3 + updated: 2026-04-29 4 + --- 5 + 6 + Introduce explicit AppView routing so Lazurite can target: 7 + 8 + 1. Bluesky (`did:web:api.bsky.app#bsky_appview`) 9 + 2. Blacksky (`did:web:api.blacksky.community#bsky_appview`) 10 + 3. microcosm fallbacks for specific degraded paths (identity and backlink-style enrichments) 11 + 12 + Design goal: route intentionally, fail predictably, and avoid hidden dependence on any single provider. 13 + 14 + ## Product Decisions 15 + 16 + 1. Provider selection is chosen on the login screen. 17 + 2. Provider selection can be changed later in Settings, but change requires app reset. 18 + 3. Cross-provider fallback (provider A -> provider B) is user-controlled, not automatic by default. 19 + 4. Slingshot identity fallback is controlled by a setting. 20 + 5. Provider health/capability probes run at startup, with manual refresh in Settings. 21 + 6. Blacksky must be available by default on login/onboarding (no advanced-toggle gate). 22 + 23 + ## Current State (Lazurite) 24 + 25 + - OAuth entryway is currently hardcoded to `bsky.social` in auth flow. 26 + - App-level settings already support configurable network services in some areas: 27 + - Typeahead provider (`bluesky`/`community`) 28 + - Constellation base URL (default `https://constellation.microcosm.blue`) 29 + - Public profile context lookups already use direct AppView host `public.api.bsky.app` in one path. 30 + - There is no shared AppView routing abstraction and no user-selectable AppView provider. 31 + 32 + ## Research Findings 33 + 34 + ### 1. Official routing expectations 35 + 36 + - Bluesky docs: authenticated `app.bsky.*` requests go through the user's PDS and are proxied to AppView. 37 + - Bluesky docs: public `app.bsky.*` endpoints can be called directly on `https://public.api.bsky.app`. 38 + - AT Protocol roadmap: clients should set `atproto-proxy` explicitly and should not depend on legacy default forwarding. 39 + 40 + ### 2. Live AppView DID documents (verified) 41 + 42 + - `https://api.bsky.app/.well-known/did.json` includes: 43 + - `#bsky_appview` at `https://api.bsky.app` 44 + - `#bsky_notif` at `https://api.bsky.app` 45 + - `https://api.blacksky.community/.well-known/did.json` includes: 46 + - `#bsky_appview` at `https://api.blacksky.community` 47 + - `#bsky_notif` at `https://api.blacksky.community` 48 + 49 + ### 3. Live Blacksky API compatibility (spot checks) 50 + 51 + - `https://api.blacksky.community/xrpc/app.bsky.actor.getProfile?...` returns valid profile payload. 52 + - `https://api.blacksky.community/xrpc/app.bsky.unspecced.getTrends` returns trends payload. 53 + 54 + ### 4. microcosm services (verified) 55 + 56 + - Constellation endpoint is live for backlink-style counts: 57 + - `https://constellation.microcosm.blue/xrpc/blue.microcosm.links.getBacklinksCount` 58 + - Slingshot identity endpoint is live: 59 + - `https://slingshot.microcosm.blue/xrpc/com.bad-example.identity.resolveMiniDoc` 60 + - Returns DID, handle, and PDS. 61 + 62 + ## Design 63 + 64 + ### AppView provider model 65 + 66 + Add settings-backed provider selection: 67 + 68 + - `bluesky` (default) 69 + - `blacksky` 70 + - `custom` (optional advanced path; disabled in UI until validated) 71 + 72 + Selection lifecycle: 73 + 74 + - Initial selection is made on the login screen before auth flow starts. 75 + - Post-login changes are allowed in Settings but must trigger an app reset flow to avoid mixed-session routing state. 76 + 77 + Provider descriptor: 78 + 79 + ```dart 80 + class AppViewProvider { 81 + final String key; // bluesky, blacksky, custom 82 + final String serviceDid; // did:web:...#bsky_appview 83 + final Uri publicBaseUrl; // public unauthenticated app.bsky host 84 + final Uri entrywayUrl; // login/account entryway 85 + } 86 + ``` 87 + 88 + Built-in defaults: 89 + 90 + - Bluesky: 91 + - service DID: `did:web:api.bsky.app#bsky_appview` 92 + - public base: `https://public.api.bsky.app` 93 + - entryway: `https://bsky.social` 94 + - Blacksky: 95 + - service DID: `did:web:api.blacksky.community#bsky_appview` 96 + - public base: `https://api.blacksky.community` 97 + - entryway: `https://blacksky.app` 98 + 99 + ### Router abstraction 100 + 101 + Introduce `AppViewRouter` as a single source of truth: 102 + 103 + - `Map<String, String> appBskyProxyHeaders()` 104 + - `Uri publicEndpoint(String xrpcPath, Map<String, String> query)` 105 + - `Uri entrywayForAuth()` 106 + - `Future<AppViewHealth> probeProvider()` 107 + 108 + This keeps routing policy out of individual repositories. 109 + 110 + ### Request routing policy 111 + 112 + 1. **Authenticated `app.bsky.*`** 113 + - Route through PDS as today. 114 + - Explicitly set `atproto-proxy` to selected provider DID. 115 + 2. **Signed-out/public `app.bsky.*`** 116 + - Call selected provider `publicBaseUrl` directly. 117 + 3. **`com.atproto.*`** 118 + - Never AppView-routed. Resolve target PDS by DID/handle as normal. 119 + 120 + ### Fallback policy (defensive) 121 + 122 + Fallback order must be explicit and bounded: 123 + 124 + 1. Try selected provider. 125 + 2. If user enabled "Cross-provider fallback", then on transient failure (`429`, `5xx`, timeout, DNS): 126 + - Try alternate built-in provider for read-only public endpoints. 127 + 3. For specific non-AppView enrichments: 128 + - Backlink/social graph counts and related index lookups: Constellation. 129 + - Identity mini-doc resolution when handle resolution is flaky: Slingshot `resolveMiniDoc` (only when enabled in settings). 130 + 4. Record failure reason and chosen fallback in logs. 131 + 5. Apply a short circuit-breaker window per failed provider/endpoint to prevent retry storms. 132 + 133 + Do not fallback across write operations. 134 + 135 + ### Capability gating 136 + 137 + Track endpoint support per provider to avoid blind retries: 138 + 139 + - `app.bsky.actor.getProfile` (public read): bluesky + blacksky 140 + - `app.bsky.feed.getPostThread` (public read): bluesky + blacksky 141 + - `app.bsky.unspecced.getTrends` (public read): bluesky + blacksky (verify by probe) 142 + - Custom namespaces: provider-specific only 143 + 144 + ### Auth and account UX implications 145 + 146 + - If user selects Blacksky provider, default login entryway should become `https://blacksky.app`. 147 + - Existing accounts keep current PDS/session behavior; AppView selection changes only request routing. 148 + - Add a warning in settings: provider choice affects content ranking, moderation context, and availability. 149 + - Login/onboarding must show both Bluesky and Blacksky as first-class provider options by default. 150 + - Changing provider in settings must present a reset confirmation flow. 151 + 152 + ### Reset UX contract (recommended) 153 + 154 + Best UX contract for provider switching: 155 + 156 + 1. User selects a new provider in Settings. 157 + 2. App shows a blocking confirmation sheet: 158 + - "Apply and restart now" 159 + - "Cancel" 160 + - Message: "You will remain signed in. No local data will be deleted." 161 + 3. On confirm, app performs a soft restart: 162 + - Persist new provider selection first. 163 + - Cancel in-flight requests. 164 + - Tear down and rebuild app-level DI/blocs/repositories. 165 + - Return to bootstrap/splash and rehydrate from persisted state. 166 + 167 + For this phase, do not log out users and do not wipe local database on provider change. 168 + 169 + ### State safety requirements 170 + 171 + To avoid mixed in-memory routing state: 172 + 173 + 1. `AppViewRouter` is the only runtime source of provider state. 174 + 2. Long-lived repositories/blocs must not cache provider values separately. 175 + 3. Provider switch path must block new requests until rebuild completes. 176 + 4. Use a routing epoch/version so stale pre-reset responses are ignored post-reset. 177 + 178 + ### Login-time persistence ordering 179 + 180 + To ensure provider choice is honored from first network call: 181 + 182 + 1. Persist login-screen provider choice before starting OAuth/app-password calls. 183 + 2. Disable login submission while provider persistence is in flight. 184 + 3. Construct auth/network clients only after persisted provider is available in bootstrap. 185 + 186 + ### Health probes 187 + 188 + - Run provider health/capability probes once at startup. 189 + - Expose a manual "Refresh Provider Health" control in Settings. 190 + - Do not run periodic background probes in this phase. 191 + 192 + ## Adversarial checks (assumptions to challenge) 193 + 194 + 1. A provider advertises `#bsky_appview` but only partially implements endpoints. 195 + 2. A provider endpoint is live but semantically diverges (labels, trends, moderation filters). 196 + 3. Docs may lag live infrastructure (observed for Blacksky roadmap vs live API host). 197 + 4. Transient success can mask long-tail reliability problems without health telemetry. 198 + 199 + Mitigation: 200 + 201 + - Runtime capability probes. 202 + - Endpoint-level fallback gates. 203 + - Structured logs + per-provider failure counters. 204 + 205 + ## Testing Strategy 206 + 207 + ### Unit 208 + 209 + - Provider selection and normalization. 210 + - Login-time provider selection persistence + settings-change reset requirement. 211 + - Bootstrap ordering: no auth/network client creation before provider setting load. 212 + - Routing epoch/version stale-response guard behavior. 213 + - Header injection (`atproto-proxy`) per request class. 214 + - Fallback state machine and circuit breaker behavior. 215 + - Capability matrix enforcement. 216 + 217 + ### Integration 218 + 219 + - Signed-out profile/thread fetch through Bluesky and Blacksky. 220 + - Forced primary failure -> alternate provider fallback (when enabled). 221 + - Forced primary failure -> no cross-provider fallback (when disabled). 222 + - Constellation + Slingshot fallback success path. 223 + 224 + ### Regression 225 + 226 + - Ensure `com.atproto.*` routes are unaffected. 227 + - Ensure OAuth/App Password auth flows still resolve correct PDS. 228 + 229 + ## Non-goals (for this phase) 230 + 231 + - Supporting arbitrary third-party AppViews in UI without validation. 232 + - Automatic provider switching for write endpoints. 233 + - Replacing existing Constellation features. 234 + 235 + ## Sources 236 + 237 + - <https://docs.bsky.app/docs/advanced-guides/api-directory> 238 + - <https://atproto.com/blog/2025-protocol-roadmap-spring> 239 + - <https://api.bsky.app/.well-known/did.json> 240 + - <https://api.blacksky.community/.well-known/did.json> 241 + - <https://docs.blacksky.community/list-of-our-services> 242 + - <https://www.microcosm.blue/> 243 + - <https://constellation.microcosm.blue/> 244 + - <https://slingshot.microcosm.blue/>
+56
docs/tasks/notification.md
··· 1 + --- 2 + title: Notification Milestones 3 + updated: 2026-04-29 4 + --- 5 + 6 + ## M1 - Foundation Hardening (Polling Baseline) 7 + 8 + - [ ] Introduce `NotificationDomainService` orchestration layer 9 + - [ ] Add Drift `notification_deliveries` table with migration 10 + - [ ] Route existing polling paths through orchestration layer 11 + - [ ] Add unit tests for dedupe and state persistence 12 + 13 + ## M2 - Local Notifications from Reconcile 14 + 15 + - [ ] Add local notification adapter abstraction 16 + - [ ] Android channels by reason family 17 + - [ ] iOS category + payload deep-link mapping 18 + - [ ] Show local notifications for newly discovered unseen items 19 + - [ ] Add widget/integration tests for tap -> route behavior 20 + 21 + ## M3 - Push Registration Lifecycle 22 + 23 + - [ ] Add token acquisition and refresh listeners 24 + - [ ] Implement `registerPush` and `unregisterPush` 25 + - [ ] Wire account login/switch/logout paths 26 + - [ ] Add retries/backoff for registration failures 27 + - [ ] Add unit tests for lifecycle transitions 28 + 29 + ## M4 - Push Payload Processing 30 + 31 + - [ ] Add background payload entrypoint (`@pragma('vm:entry-point')`) 32 + - [ ] Parse defensively: `senderDid`, `targetDid`, `recordUri`, `reason` 33 + - [ ] Fetch canonical notification payload before display 34 + - [ ] Apply moderation + preference filtering before display 35 + - [ ] Add timeout-bound processing and drop accounting 36 + 37 + ## M5 - Background Reconciliation 38 + 39 + - [ ] Add periodic background reconcile task (Android 15m+) 40 + - [ ] Add iOS background fetch/BGTaskScheduler integration 41 + - [ ] Ensure tasks are idempotent and dedupe-safe 42 + - [ ] Add test harness for worker entrypoints 43 + 44 + ## M6 - Preferences and UX 45 + 46 + - [ ] Add settings UI for notification controls and permission state 47 + - [ ] Integrate `getPreferences` and `putPreferencesV2` 48 + - [ ] Add contextual permission prompt + denied -> settings flow 49 + - [ ] Add unread badge + seen-state reconciliation tests 50 + 51 + ## M7 - Reliability and Release Readiness 52 + 53 + - [ ] Add structured logs + debug counters for notification flows 54 + - [ ] Add smoke checklist for Android/iOS permission and delivery scenarios 55 + - [ ] Validate multi-account behavior and token cleanup 56 + - [ ] Run full `flutter analyze` and full test suite
+57
docs/tasks/routing.md
··· 1 + --- 2 + title: AppView Routing Milestones 3 + updated: 2026-04-29 4 + --- 5 + 6 + ## M1 - Core Routing Model 7 + 8 + - [ ] Add `appview_provider` setting with defaults and validation 9 + - [ ] Add login-screen provider selector (Bluesky + Blacksky visible by default) 10 + - [ ] Persist login-screen provider choice before any auth/network call starts 11 + - [ ] Add provider descriptor model (`serviceDid`, `publicBaseUrl`, `entrywayUrl`) 12 + - [ ] Add `AppViewRouter` abstraction for endpoint/header resolution 13 + - [ ] Add unit tests for provider normalization/defaults and bootstrap ordering 14 + 15 + ## M2 - Header + Request Integration 16 + 17 + - [ ] Inject explicit `atproto-proxy` for authenticated `app.bsky.*` requests 18 + - [ ] Route signed-out public `app.bsky.*` reads via selected provider host 19 + - [ ] Ensure `com.atproto.*` flows bypass AppView routing changes 20 + - [ ] Add integration tests for Bluesky/Blacksky provider selection 21 + 22 + ## M3 - Fallback Engine 23 + 24 + - [ ] Add user setting for cross-provider fallback (default off) 25 + - [ ] Implement bounded fallback chain for read-only public endpoints 26 + - [ ] Add circuit-breaker window per provider/endpoint 27 + - [ ] Add structured logs for provider/fallback decisions 28 + - [ ] Add tests for timeout/429/5xx transitions with fallback enabled/disabled 29 + 30 + ## M4 - microcosm Fallbacks 31 + 32 + - [ ] Keep Constellation fallback paths first-class for backlink-style enrichments 33 + - [ ] Add setting-gated Slingshot identity fallback for degraded handle resolution 34 + - [ ] Add tests for fallback parsing and failure handling 35 + - [ ] Document opt-in behavior and trust boundaries 36 + 37 + ## M5 - Settings and UX 38 + 39 + - [ ] Add AppView provider controls in Settings (bluesky/blacksky) 40 + - [ ] Add provider-change confirmation that performs app reset 41 + - [ ] Define reset copy: stay signed in, no local data deletion, restart required 42 + - [ ] Show concise warning about moderation/ranking/provider differences 43 + - [ ] Add advanced diagnostics view (active provider, last fallback, last error) 44 + - [ ] Add manual "Refresh Provider Health" action 45 + 46 + ## M6 - Auth Flow Alignment 47 + 48 + - [ ] Tie OAuth entryway default to selected provider (`bsky.social`/`blacksky.app`) 49 + - [ ] Validate app-password and OAuth flows remain backward compatible 50 + - [ ] Add migration behavior for existing saved sessions/accounts 51 + - [ ] Ensure app-level soft restart rebuilds DI/blocs/repositories after provider switch 52 + - [ ] Ensure stale pre-reset responses are ignored (routing epoch/version guard) 53 + 54 + ## M7 - Hardening and Release 55 + 56 + - [ ] Add provider health probes at startup 57 + - [ ] Add capability matrix checks before retries