···11---
22-title: AppView Routing (Bluesky + Blacksky + microcosm Fallbacks)
22+title: AppView Routing + Trending (Bluesky + Blacksky + microcosm Fallbacks)
33updated: 2026-04-29
44---
55···77881. Bluesky (`did:web:api.bsky.app#bsky_appview`)
992. Blacksky (`did:web:api.blacksky.community#bsky_appview`)
1010-3. microcosm fallbacks for specific degraded paths (identity and backlink-style enrichments)
1010+3. microcosm fallbacks for specific degraded paths (identity and backlink enrichments)
11111212-Design goal: route intentionally, fail predictably, and avoid hidden dependence on any single provider.
1212+Design goal: route intentionally, fail predictably, and avoid hidden dependence on one provider.
13131414## Product Decisions
151516161. Provider selection is chosen on the login screen.
1717-2. Provider selection can be changed later in Settings, but change requires app reset.
1818-3. Cross-provider fallback (provider A -> provider B) is user-controlled, not automatic by default.
1919-4. Slingshot identity fallback is controlled by a setting.
1717+2. Provider selection can be changed in Settings, but change requires app reset.
1818+3. Cross-provider fallback (A -> B) is user-controlled (default off).
1919+4. Slingshot identity fallback is setting-gated.
20205. Provider health/capability probes run at startup, with manual refresh in Settings.
2121-6. Blacksky must be available by default on login/onboarding (no advanced-toggle gate).
2121+6. Blacksky is available by default on login/onboarding.
2222+7. Trending is first-class: Home/Feed includes a Trending action, and app has a dedicated Trending screen.
22232324## Current State (Lazurite)
24252525-- OAuth entryway is currently hardcoded to `bsky.social` in auth flow.
2626-- App-level settings already support configurable network services in some areas:
2727- - Typeahead provider (`bluesky`/`community`)
2828- - Constellation base URL (default `https://constellation.microcosm.blue`)
2929-- Public profile context lookups already use direct AppView host `public.api.bsky.app` in one path.
2626+- OAuth entryway is hardcoded to `bsky.social` in auth flow.
3027- There is no shared AppView routing abstraction and no user-selectable AppView provider.
2828+- Home feed app bar has feed-management action only; no Trending action.
2929+- Router has no `/trending` route.
31303231## Research Findings
33323434-### 1. Official routing expectations
3333+### 1. Routing and proxy expectations
35343636-- Bluesky docs: authenticated `app.bsky.*` requests go through the user's PDS and are proxied to AppView.
3737-- Bluesky docs: public `app.bsky.*` endpoints can be called directly on `https://public.api.bsky.app`.
3838-- AT Protocol roadmap: clients should set `atproto-proxy` explicitly and should not depend on legacy default forwarding.
3535+- `app.bsky.*` should route via user PDS for authenticated requests, with explicit `atproto-proxy` toward selected AppView.
3636+- Public `app.bsky.*` reads can call AppView host directly (`public.api.bsky.app` for Bluesky).
3737+- Clients should not rely on legacy default forwarding behavior.
3838+3939+### 2. Verified AppViews and compatibility (live checks, 2026-04-29)
4040+4141+- Bluesky DID/service:
4242+ - `did:web:api.bsky.app#bsky_appview`
4343+ - public host: `https://public.api.bsky.app`
4444+- Blacksky DID/service:
4545+ - `did:web:api.blacksky.community#bsky_appview`
4646+ - public host: `https://api.blacksky.community`
4747+- Both hosts currently respond for:
4848+ - `app.bsky.actor.getProfile`
4949+ - `app.bsky.unspecced.getTrends`
5050+ - `app.bsky.unspecced.getTrendingTopics`
39514040-### 2. Live AppView DID documents (verified)
5252+### 3. Trending endpoint contract (official lexicons)
41534242-- `https://api.bsky.app/.well-known/did.json` includes:
4343- - `#bsky_appview` at `https://api.bsky.app`
4444- - `#bsky_notif` at `https://api.bsky.app`
4545-- `https://api.blacksky.community/.well-known/did.json` includes:
4646- - `#bsky_appview` at `https://api.blacksky.community`
4747- - `#bsky_notif` at `https://api.blacksky.community`
5454+- `app.bsky.unspecced.getTrends`
5555+ - params: `limit` (default 10, min 1, max 25)
5656+ - output: `trends[]` (`trendView`)
5757+- `app.bsky.unspecced.getTrendingTopics`
5858+ - params: `viewer?` DID, `limit` (default 10, min 1, max 25)
5959+ - output: `topics[]` + `suggested[]` (`trendingTopic`)
48604949-### 3. Live Blacksky API compatibility (spot checks)
6161+### 4. Live provider divergence relevant to UI
50625151-- `https://api.blacksky.community/xrpc/app.bsky.actor.getProfile?...` returns valid profile payload.
5252-- `https://api.blacksky.community/xrpc/app.bsky.unspecced.getTrends` returns trends payload.
6363+- Bluesky `getTrendingTopics` currently returns both `topics` and non-empty `suggested`.
6464+- Blacksky `getTrendingTopics` currently returns `topics` and often empty `suggested`.
6565+- Link formats differ:
6666+ - Bluesky trend links often `/profile/.../feed/...`
6767+ - Blacksky trend links often `/topic/<id>`
53685454-### 4. microcosm services (verified)
6969+### 5. Other AppViews in ecosystem
55705656-- Constellation endpoint is live for backlink-style counts:
5757- - `https://constellation.microcosm.blue/xrpc/blue.microcosm.links.getBacklinksCount`
5858-- Slingshot identity endpoint is live:
5959- - `https://slingshot.microcosm.blue/xrpc/com.bad-example.identity.resolveMiniDoc`
6060- - Returns DID, handle, and PDS.
7171+- There are additional self-hosted/experimental AppView implementations in the ecosystem.
7272+- For this phase, treat these as `custom` providers only (advanced path), not default onboarding options, until each candidate is validated for DID/service health and endpoint parity.
61736274## Design
6375···67796880- `bluesky` (default)
6981- `blacksky`
7070-- `custom` (optional advanced path; disabled in UI until validated)
7171-7272-Selection lifecycle:
7373-7474-- Initial selection is made on the login screen before auth flow starts.
7575-- Post-login changes are allowed in Settings but must trigger an app reset flow to avoid mixed-session routing state.
8282+- `custom` (advanced, validation-gated)
76837784Provider descriptor:
7885···8087class AppViewProvider {
8188 final String key; // bluesky, blacksky, custom
8289 final String serviceDid; // did:web:...#bsky_appview
8383- final Uri publicBaseUrl; // public unauthenticated app.bsky host
9090+ final Uri publicBaseUrl; // public app.bsky host
8491 final Uri entrywayUrl; // login/account entryway
9292+ final Uri webBaseUrl; // provider web base for relative trend links
8593}
8694```
87958896Built-in defaults:
89979090-- Bluesky:
9191- - service DID: `did:web:api.bsky.app#bsky_appview`
9292- - public base: `https://public.api.bsky.app`
9393- - entryway: `https://bsky.social`
9494-- Blacksky:
9595- - service DID: `did:web:api.blacksky.community#bsky_appview`
9696- - public base: `https://api.blacksky.community`
9797- - entryway: `https://blacksky.app`
9898+- Bluesky: `public.api.bsky.app`, `bsky.social`, `https://bsky.app`
9999+- Blacksky: `api.blacksky.community`, `blacksky.app`, `https://blacksky.app`
9810099101### Router abstraction
100102101101-Introduce `AppViewRouter` as a single source of truth:
103103+Introduce `AppViewRouter` as single source of truth:
102104103105- `Map<String, String> appBskyProxyHeaders()`
104106- `Uri publicEndpoint(String xrpcPath, Map<String, String> query)`
105107- `Uri entrywayForAuth()`
108108+- `Uri resolveWebLink(String relativeOrAbsolute)`
106109- `Future<AppViewHealth> probeProvider()`
107110108108-This keeps routing policy out of individual repositories.
111111+### Request routing policy
112112+113113+1. Authenticated `app.bsky.*`
114114+- Route through PDS.
115115+- Set explicit `atproto-proxy` to selected provider DID.
116116+117117+2. Signed-out/public `app.bsky.*`
118118+- Call selected provider `publicBaseUrl` directly.
119119+120120+3. `com.atproto.*`
121121+- Never AppView-routed; resolve PDS by DID/handle as normal.
122122+123123+### Trending UX and routing
124124+125125+1. Home app bar adds `Trending` action button.
126126+- Route target: `/trending`.
127127+128128+2. Add dedicated `TrendingScreen`.
129129+- Primary data source: `getTrendingTopics(limit=10)`.
130130+- Required enrichment (initial implementation): `getTrends(limit=10)` for richer metadata (actors, postCount, status/category).
131131+- UI sections:
132132+ - `Topics`
133133+ - `Suggested` (hidden when empty)
134134+135135+Implementation note:
136136+137137+- The first shipped Trending screen must join `getTrendingTopics` + `getTrends` data in one load flow.
138138+- If `getTrends` fails and cross-provider fallback is disabled or unavailable, render `topics` with a degraded metadata state and explicit non-blocking error indicator.
139139+140140+Deterministic join contract:
141141+142142+- Build a stable join key for both `topics[]` and `trends[]` before matching.
143143+- Key precedence (in order):
144144+1. Parsed link key (preferred):
145145+ - `/topic/<id>` -> `topic:<id>`
146146+ - `/profile/<actor>/feed/<rkey>` -> `feed:<actor>:<rkey>`
147147+2. Normalized topic string fallback:
148148+ - lowercase
149149+ - trim
150150+ - collapse internal whitespace
151151+ - drop leading `#`
152152+- Matching algorithm:
153153+1. Try exact parsed-link-key match.
154154+2. If absent, try normalized-topic-string match.
155155+3. If multiple trend candidates match, pick the candidate with newest `startedAt`.
156156+4. If still tied, pick lexicographically smallest `link` for deterministic output.
157157+5. If no match, keep topic row and mark metadata as unavailable.
158158+159159+Trending UI state contract:
160160+161161+- `topics` load success + `trends` load success:
162162+ - render fully enriched rows (actors/postCount/status/category when present).
163163+- `topics` load success + `trends` degraded/failure:
164164+ - render topic rows without metadata fields.
165165+ - show non-blocking banner/chip: `Metadata temporarily unavailable`.
166166+ - keep row navigation actions enabled.
167167+- `topics` failure:
168168+ - render blocking error state for Trending screen.
109169110110-### Request routing policy
170170+3. Trend row actions:
171171+- Use provider-aware `resolveWebLink` for relative links.
172172+- If link maps to supported internal route, deep-link internally.
173173+- If unsupported, open external browser to provider `webBaseUrl + link`.
111174112112-1. **Authenticated `app.bsky.*`**
113113- - Route through PDS as today.
114114- - Explicitly set `atproto-proxy` to selected provider DID.
115115-2. **Signed-out/public `app.bsky.*`**
116116- - Call selected provider `publicBaseUrl` directly.
117117-3. **`com.atproto.*`**
118118- - Never AppView-routed. Resolve target PDS by DID/handle as normal.
175175+4. Link parsing safety:
176176+- Never assume one provider link format.
177177+- Support at least:
178178+ - `/profile/<actor>/feed/<rkey>`
179179+ - `/topic/<id>`
180180+- Unknown path formats degrade to external open.
119181120182### Fallback policy (defensive)
121121-122122-Fallback order must be explicit and bounded:
1231831241841. Try selected provider.
125125-2. If user enabled "Cross-provider fallback", then on transient failure (`429`, `5xx`, timeout, DNS):
126126- - Try alternate built-in provider for read-only public endpoints.
127127-3. For specific non-AppView enrichments:
128128- - Backlink/social graph counts and related index lookups: Constellation.
129129- - Identity mini-doc resolution when handle resolution is flaky: Slingshot `resolveMiniDoc` (only when enabled in settings).
130130-4. Record failure reason and chosen fallback in logs.
131131-5. Apply a short circuit-breaker window per failed provider/endpoint to prevent retry storms.
185185+2. If cross-provider fallback setting is ON, then on transient read failures (`429`, `5xx`, timeout, DNS):
186186+- Try alternate built-in provider for read-only public endpoints.
187187+3. For non-AppView enrichments:
188188+- Backlink/index enrichments: Constellation.
189189+- Identity fallback: Slingshot `resolveMiniDoc` only when enabled.
190190+4. Log fallback reason/provider and apply endpoint-level circuit breaker.
132191133192Do not fallback across write operations.
134193···136195137196Track endpoint support per provider to avoid blind retries:
138197139139-- `app.bsky.actor.getProfile` (public read): bluesky + blacksky
140140-- `app.bsky.feed.getPostThread` (public read): bluesky + blacksky
141141-- `app.bsky.unspecced.getTrends` (public read): bluesky + blacksky (verify by probe)
142142-- Custom namespaces: provider-specific only
143143-144144-### Auth and account UX implications
145145-146146-- If user selects Blacksky provider, default login entryway should become `https://blacksky.app`.
147147-- Existing accounts keep current PDS/session behavior; AppView selection changes only request routing.
148148-- Add a warning in settings: provider choice affects content ranking, moderation context, and availability.
149149-- Login/onboarding must show both Bluesky and Blacksky as first-class provider options by default.
150150-- Changing provider in settings must present a reset confirmation flow.
198198+- `app.bsky.actor.getProfile`
199199+- `app.bsky.feed.getPostThread`
200200+- `app.bsky.unspecced.getTrends`
201201+- `app.bsky.unspecced.getTrendingTopics`
151202152203### Reset UX contract (recommended)
153204154154-Best UX contract for provider switching:
205205+Best contract for provider switching:
155206156156-1. User selects a new provider in Settings.
157157-2. App shows a blocking confirmation sheet:
158158- - "Apply and restart now"
159159- - "Cancel"
160160- - Message: "You will remain signed in. No local data will be deleted."
161161-3. On confirm, app performs a soft restart:
162162- - Persist new provider selection first.
163163- - Cancel in-flight requests.
164164- - Tear down and rebuild app-level DI/blocs/repositories.
165165- - Return to bootstrap/splash and rehydrate from persisted state.
207207+1. User selects provider in Settings.
208208+2. Blocking confirmation sheet:
209209+- `Apply and restart now`
210210+- `Cancel`
211211+- Copy: user stays signed in; no local DB wipe.
212212+3. On confirm, perform soft restart:
213213+- Persist provider first.
214214+- Stop new requests and cancel in-flight work.
215215+- Tear down and rebuild app-level DI/blocs/services.
216216+- Return to bootstrap and rehydrate from persisted state.
166217167167-For this phase, do not log out users and do not wipe local database on provider change.
218218+This avoids mixed in-memory routing state while preserving session continuity.
168219169220### State safety requirements
170170-171171-To avoid mixed in-memory routing state:
1722211732221. `AppViewRouter` is the only runtime source of provider state.
174174-2. Long-lived repositories/blocs must not cache provider values separately.
175175-3. Provider switch path must block new requests until rebuild completes.
176176-4. Use a routing epoch/version so stale pre-reset responses are ignored post-reset.
223223+2. Long-lived repos/blocs must not cache provider independently.
224224+3. Provider switch blocks new requests until rebuild completes.
225225+4. Use routing epoch/version so stale pre-reset responses are dropped.
177226178227### Login-time persistence ordering
179228180180-To ensure provider choice is honored from first network call:
181181-182182-1. Persist login-screen provider choice before starting OAuth/app-password calls.
183183-2. Disable login submission while provider persistence is in flight.
184184-3. Construct auth/network clients only after persisted provider is available in bootstrap.
229229+1. Persist login-screen provider selection before any auth/network request.
230230+2. Disable login submission while persistence is in-flight.
231231+3. Construct auth/network clients only after provider setting loads at bootstrap.
185232186233### Health probes
187234188188-- Run provider health/capability probes once at startup.
189189-- Expose a manual "Refresh Provider Health" control in Settings.
190190-- Do not run periodic background probes in this phase.
235235+- Run once at startup.
236236+- Expose manual `Refresh Provider Health` in Settings.
237237+- No periodic background probes in this phase.
191238192192-## Adversarial checks (assumptions to challenge)
239239+## Adversarial checks
193240194194-1. A provider advertises `#bsky_appview` but only partially implements endpoints.
195195-2. A provider endpoint is live but semantically diverges (labels, trends, moderation filters).
196196-3. Docs may lag live infrastructure (observed for Blacksky roadmap vs live API host).
197197-4. Transient success can mask long-tail reliability problems without health telemetry.
241241+1. Provider advertises `#bsky_appview` but partially implements endpoints.
242242+2. Semantics diverge even when endpoint exists (moderation, trends, topic links).
243243+3. Docs can lag live infrastructure.
244244+4. Trend link paths can drift by provider/version.
198245199246Mitigation:
200247201201-- Runtime capability probes.
248248+- Startup capability probes.
202249- Endpoint-level fallback gates.
203203-- Structured logs + per-provider failure counters.
250250+- Structured logs + provider failure counters.
251251+- Defensive link resolver with safe external fallback.
204252205253## Testing Strategy
206254207255### Unit
208256209209-- Provider selection and normalization.
210210-- Login-time provider selection persistence + settings-change reset requirement.
211211-- Bootstrap ordering: no auth/network client creation before provider setting load.
212212-- Routing epoch/version stale-response guard behavior.
213213-- Header injection (`atproto-proxy`) per request class.
214214-- Fallback state machine and circuit breaker behavior.
215215-- Capability matrix enforcement.
257257+- Provider selection/normalization.
258258+- Login-time provider persistence before auth call.
259259+- Bootstrap ordering (no client creation before provider load).
260260+- Routing epoch stale-response guard.
261261+- Header injection (`atproto-proxy`).
262262+- Fallback + circuit-breaker transitions.
263263+- Trending limit clamping (1..25).
264264+- Trend link parsing and resolver fallback behavior.
265265+- Deterministic topic/trend join precedence and tie-break behavior.
266266+- Topic-string normalization behavior for join fallback.
216267217268### Integration
218269219219-- Signed-out profile/thread fetch through Bluesky and Blacksky.
220220-- Forced primary failure -> alternate provider fallback (when enabled).
221221-- Forced primary failure -> no cross-provider fallback (when disabled).
222222-- Constellation + Slingshot fallback success path.
270270+- `/trending` route reachable from Home button.
271271+- Bluesky and Blacksky trending fetch paths.
272272+- Empty `suggested` renders cleanly.
273273+- `topics` success + `trends` failure renders degraded metadata indicator with usable navigation.
274274+- Forced primary failure with fallback ON/OFF.
275275+- Provider switch soft restart fully rebuilds routing consumers.
223276224277### Regression
225278226226-- Ensure `com.atproto.*` routes are unaffected.
227227-- Ensure OAuth/App Password auth flows still resolve correct PDS.
279279+- `com.atproto.*` unaffected.
280280+- OAuth/App Password flows keep correct PDS behavior.
281281+- Provider switch does not mix stale in-memory routing state.
228282229229-## Non-goals (for this phase)
283283+## Non-goals (this phase)
230284231231-- Supporting arbitrary third-party AppViews in UI without validation.
232232-- Automatic provider switching for write endpoints.
285285+- Auto-switch providers for write operations.
286286+- Unvalidated public listing of arbitrary third-party AppViews in onboarding.
233287- Replacing existing Constellation features.
234288235289## Sources
236290237291- <https://docs.bsky.app/docs/advanced-guides/api-directory>
238238-- <https://atproto.com/blog/2025-protocol-roadmap-spring>
292292+- <https://docs.bsky.app/blog/2025-protocol-roadmap-spring>
239293- <https://api.bsky.app/.well-known/did.json>
240294- <https://api.blacksky.community/.well-known/did.json>
241241-- <https://docs.blacksky.community/list-of-our-services>
295295+- <https://raw.githubusercontent.com/bluesky-social/atproto/main/lexicons/app/bsky/unspecced/getTrends.json>
296296+- <https://raw.githubusercontent.com/bluesky-social/atproto/main/lexicons/app/bsky/unspecced/getTrendingTopics.json>
297297+- <https://raw.githubusercontent.com/bluesky-social/atproto/main/lexicons/app/bsky/unspecced/defs.json>
298298+- <https://raw.githubusercontent.com/bluesky-social/atproto/main/lexicons/app/bsky/unspecced/getTrendsSkeleton.json>
242299- <https://www.microcosm.blue/>
243300- <https://constellation.microcosm.blue/>
244301- <https://slingshot.microcosm.blue/>
302302+- <https://tangled.org/why.bsky.team/konbini>
303303+- <https://sdk.blue/>
+51-23
docs/tasks/routing.md
···11---
22-title: AppView Routing Milestones
22+title: AppView Routing + Trending Milestones
33updated: 2026-04-29
44---
55+66+## M0 - Research + Planning (Complete)
77+88+- [x] Validate Bluesky and Blacksky AppView DID/service identities
99+- [x] Validate live compatibility for trends/trending-topics endpoints
1010+- [x] Capture provider divergence (link formats, `suggested` behavior)
1111+- [x] Update routing spec with provider-switch UX contract and state safety rules
1212+- [x] Update routing/task plan to include Trending button + `/trending` screen
513614## M1 - Core Routing Model
715816- [ ] Add `appview_provider` setting with defaults and validation
917- [ ] Add login-screen provider selector (Bluesky + Blacksky visible by default)
1010-- [ ] Persist login-screen provider choice before any auth/network call starts
1111-- [ ] Add provider descriptor model (`serviceDid`, `publicBaseUrl`, `entrywayUrl`)
1212-- [ ] Add `AppViewRouter` abstraction for endpoint/header resolution
1818+- [ ] Persist login-screen provider choice before any auth/network call
1919+- [ ] Add provider descriptor (`serviceDid`, `publicBaseUrl`, `entrywayUrl`, `webBaseUrl`)
2020+- [ ] Add `AppViewRouter` abstraction for endpoint/header/link resolution
1321- [ ] Add unit tests for provider normalization/defaults and bootstrap ordering
14221523## M2 - Header + Request Integration
16241717-- [ ] Inject explicit `atproto-proxy` for authenticated `app.bsky.*` requests
2525+- [ ] Inject explicit `atproto-proxy` for authenticated `app.bsky.*`
1826- [ ] Route signed-out public `app.bsky.*` reads via selected provider host
1919-- [ ] Ensure `com.atproto.*` flows bypass AppView routing changes
2727+- [ ] Ensure `com.atproto.*` bypasses AppView routing
2028- [ ] Add integration tests for Bluesky/Blacksky provider selection
21292222-## M3 - Fallback Engine
3030+## M3 - Trending Surface
3131+3232+- [ ] Add Home app bar `Trending` action button
3333+- [ ] Add `/trending` route and `TrendingScreen`
3434+- [ ] Implement `getTrendingTopics(limit=10)` fetch path
3535+- [ ] Implement required `getTrends(limit=10)` enrichment path for richer metadata
3636+- [ ] Hide `Suggested` section when provider returns empty list
3737+- [ ] Add loading/empty/error states for trending screen
3838+- [ ] Add analytics/logging for provider and fallback used on trending requests
3939+4040+## M4 - Trend Link Routing
4141+4242+- [ ] Add provider-aware trend link resolver (`resolveWebLink`)
4343+- [ ] Support `/profile/<actor>/feed/<rkey>` links
4444+- [ ] Support `/topic/<id>` links
4545+- [ ] Degrade unknown link formats to safe external open
4646+- [ ] Add unit tests for link parsing and fallback resolution
4747+4848+## M5 - Fallback Engine
23492450- [ ] Add user setting for cross-provider fallback (default off)
2551- [ ] Implement bounded fallback chain for read-only public endpoints
···2753- [ ] Add structured logs for provider/fallback decisions
2854- [ ] Add tests for timeout/429/5xx transitions with fallback enabled/disabled
29553030-## M4 - microcosm Fallbacks
5656+## M6 - microcosm Fallbacks
31573232-- [ ] Keep Constellation fallback paths first-class for backlink-style enrichments
5858+- [ ] Keep Constellation fallback paths first-class for backlink enrichments
3359- [ ] Add setting-gated Slingshot identity fallback for degraded handle resolution
3460- [ ] Add tests for fallback parsing and failure handling
3561- [ ] Document opt-in behavior and trust boundaries
36623737-## M5 - Settings and UX
6363+## M7 - Settings and UX
38643939-- [ ] Add AppView provider controls in Settings (bluesky/blacksky)
4040-- [ ] Add provider-change confirmation that performs app reset
4141-- [ ] Define reset copy: stay signed in, no local data deletion, restart required
6565+- [ ] Add AppView provider controls in Settings (Bluesky/Blacksky)
6666+- [ ] Add provider-change confirmation that performs app soft restart
6767+- [ ] Confirm reset copy: stay signed in, no local data deletion
4268- [ ] Show concise warning about moderation/ranking/provider differences
4343-- [ ] Add advanced diagnostics view (active provider, last fallback, last error)
4444-- [ ] Add manual "Refresh Provider Health" action
6969+- [ ] Add diagnostics view (active provider, last fallback, last error)
7070+- [ ] Add manual `Refresh Provider Health` action
45714646-## M6 - Auth Flow Alignment
7272+## M8 - Auth + Reset Safety
47734848-- [ ] Tie OAuth entryway default to selected provider (`bsky.social`/`blacksky.app`)
4949-- [ ] Validate app-password and OAuth flows remain backward compatible
7474+- [ ] Tie OAuth entryway default to selected provider (`bsky.social` / `blacksky.app`)
7575+- [ ] Ensure app-password and OAuth flows remain backward compatible
5076- [ ] Add migration behavior for existing saved sessions/accounts
5151-- [ ] Ensure app-level soft restart rebuilds DI/blocs/repositories after provider switch
5252-- [ ] Ensure stale pre-reset responses are ignored (routing epoch/version guard)
7777+- [ ] Ensure provider switch rebuilds DI/blocs/services before new requests
7878+- [ ] Add routing epoch/version guard to drop stale pre-reset responses
53795454-## M7 - Hardening and Release
8080+## M9 - Hardening + Release
55815656-- [ ] Add provider health probes at startup
5757-- [ ] Add capability matrix checks before retries
8282+- [ ] Run provider health probes at startup
8383+- [ ] Gate retries by capability matrix (`getTrends`, `getTrendingTopics`, etc.)
8484+- [ ] Add end-to-end regression coverage for routing + trending + fallback flows
8585+- [ ] Stage rollout behind feature flag if telemetry indicates instability