···43434444#### 2026-03-22
45454646-- Starter packs & lists
4646+- Starter pack & list views
4747+4848+#### 2026-04-11
4949+5050+- Follow hygiene feature to identify and unfollow inactive or problematic accounts
5151+5252+#### 2026-04-12
5353+5454+- Multiple account support (controlled from settings and sidebar/menu)
+4
docs/TODO.md
···11# To-Do/Parking Lot
2233+## Tests
44+55+- Integration test: save a post → verify it appears in semantic search results for a relevant query
66+37## UI
4859- Show feed icons in the feed management UI
···10101111## M14 — Account Switching
12121313-- [x] `AccountSwitcherCubit` exposing account list and active DID
1414-- [x] Account switcher bottom sheet UI — list accounts with avatars and handles
1515-- [x] Store `active_account_did` in Drift `settings` table
1616-- [x] Drift migration: add `account_did` column to `cached_posts` if not present
1717-- [x] All user-scoped queries filter by active account DID
1818-- [x] Broadcast `AccountSwitched` event to all Blocs on switch
1919-- [x] "Add Account" button triggers OAuth flow, inserts new `accounts` row
2020-- [x] Silent token refresh on account switch; navigate to login on failure
1313+Completed [2026-04-12](../../CHANGELOG.md#2026-04-12)
21142215## M15 — Offline Reading & Network Resilience
2316
-1
docs/tasks/phase-7.md
···100100- [x] Unit tests: `SemanticIndexCubit` - backfill progress, reindex trigger
101101- [x] Widget tests: search tab renders, query produces results, scope chips filter, relevance badges display, empty/no-results/unavailable states
102102- [x] Widget tests: settings section renders, toggle enables/disables, progress indicator during backfill, re-index button triggers reindex
103103-- [ ] Integration test: save a post → verify it appears in semantic search results for a relevant query
+1-102
docs/tasks/phase-8.md
···5566## M27 - Follow Hygiene: Detect & Remove Inactive/Problematic Follows
7788-### Core
99-1010-#### Models
1111-1212-- [x] `FollowStatus` enum — `deleted`, `deactivated`, `suspended`, `blockedBy`, `blocking`, `mutualBlock`, `hidden`, `selfFollow`
1313-- [x] `FollowRecord` model — `uri`, `rkey`, `subjectDid`; extracted from `com.atproto.repo.listRecords` response
1414-- [x] `ClassifiedFollow` model — `record` (FollowRecord), `handle`, `status` (FollowStatus), `statusLabel`, `selected` (mutable); `Equatable` for state comparison (excluding `selected`)
1515-1616-#### Repository
1717-1818-- [x] `FollowAuditRepository` — new file `lib/features/profile/data/follow_audit_repository.dart`, depends on authenticated `Bluesky` client
1919-- [x] `fetchAllFollows(String did)` — paginate `atproto.repo.listRecords(repo: did, collection: 'app.bsky.graph.follow', limit: 100)` with cursor until exhausted, return `List<FollowRecord>`
2020-- [x] `classifyFollows(List<FollowRecord>, String ownDid)` — batch `actor.getProfiles` (25/batch, 2 concurrent, 500ms inter-group delay), per-DID `getProfile` fallback for missing entries, classify each by `FollowStatus`, return `(List<ClassifiedFollow> results, int failedCount)`
2121-- [x] `batchUnfollow(List<ClassifiedFollow>)` — extract rkeys, build `applyWrites#delete` operations, chunk into batches of 200, execute sequentially, return count of successfully deleted records
2222-- [x] Retry logic — on 429 or network error during `getProfiles`/`getProfile`, exponential backoff (1s/2s/4s), max 3 retries per batch
2323-2424-#### Cubit
2525-2626-- [x] `FollowAuditState` — `status` (initial/fetching/classifying/ready/unfollowing/complete/error), `results`, `totalFollows`, `progress`, `failedProfiles`, `unfollowedCount`, `errorMessage`, `visibleStatuses`
2727-- [x] `FollowAuditCubit` — depends on `FollowAuditRepository`, authenticated DID
2828-- [x] `audit()` — orchestrates fetch → classify → ready, emits progress updates during each phase
2929-- [x] `toggleSelection(int index)` — toggle individual record selection
3030-- [x] `selectAllByStatus(FollowStatus)` / `deselectAllByStatus(FollowStatus)` — bulk select/deselect by category
3131-- [x] `toggleVisibility(FollowStatus)` — show/hide category in results list
3232-- [x] `confirmUnfollow()` — call `batchUnfollow` with selected records, emit unfollowing → complete, clear unfollowed records from results
3333-3434-### UI
3535-3636-#### Follow Audit Screen
3737-3838-- [x] `FollowAuditScreen` — new file `lib/features/profile/presentation/follow_audit_screen.dart`
3939-- [x] Header — "Clean Follows" title, subtitle with total follow count
4040-- [x] Action bar — "Scan" button (initial) → "Unfollow Selected (N)" button (ready), disabled during loading states
4141-- [x] Linear progress bar — during fetch/classify, shows "Fetching follows: X/Y" or "Classifying: X/Y"
4242-- [x] Failed profiles warning — amber text below progress bar when `failedProfiles > 0`
4343-- [x] Results list — checkbox, handle (tappable → navigate to profile via GoRouter), truncated DID, status badge chip. Selected rows get destructive-red background tint
4444-- [x] Empty state — "No problematic follows found" when audit completes with 0 results
4545-- [x] Complete state — "Unfollowed N account(s)" after successful batch delete
4646-- [x] Error state — error message with "Retry" button
4747-4848-#### Filter Controls
4949-5050-- [x] Responsive layout — horizontal scrollable chip row on narrow screens (`< 600px`), sticky sidebar on wider screens
5151-- [x] Per-status filter tile — visibility toggle (show/hide rows of that status in list) + "Select All" checkbox
5252-- [x] Category count badges — show count of results per status category
5353-- [x] Summary line — "Selected: N/M" count, always visible
5454-5555-#### Navigation & Entry Points
5656-5757-- [x] Settings screen — new "Account Maintenance" section with "Clean Follows" tile, navigates to `FollowAuditScreen`
5858-- [x] Profile screen overflow menu — add "Clean Follows" option when viewing own profile, navigates to `FollowAuditScreen`
5959-- [x] GoRouter route — `/settings/clean-follows`
6060-6161-### Tests
6262-6363-#### Unit Tests — Models
6464-6565-- [x] `FollowRecord` — construction, rkey extraction from AT URI
6666-- [x] `ClassifiedFollow` — construction, statusLabel mapping for each `FollowStatus` value
6767-- [x] `FollowStatus` — verify all enum values exist and labels are correct
6868-6969-#### Unit Tests — Repository
7070-7171-- [x] `fetchAllFollows` — single page (< 100 records), multi-page pagination (cursor handling), empty follows list
7272-- [x] `classifyFollows` — deleted account (getProfile returns "not found"), deactivated account, suspended account
7373-- [x] `classifyFollows` — blocked-by (viewer.blockedBy), blocking (viewer.blocking), mutual block (both), hidden (!hide label), self-follow
7474-- [x] `classifyFollows` — batch hydration: profiles returned in getProfiles are classified correctly, missing profiles fall through to per-DID lookup
7575-- [x] `classifyFollows` — partial failure: some batches fail, returns results for successful batches + failedCount
7676-- [x] `classifyFollows` — rate limit retry: mock 429 response, verify retry with backoff
7777-- [x] `batchUnfollow` — single batch (< 200 records), multi-batch chunking, empty selection (no-op)
7878-- [x] `batchUnfollow` — partial failure: first batch succeeds, second fails, returns partial count
7979-8080-#### Unit Tests — Cubit
8181-8282-- [x] `audit()` — state transitions: initial → fetching → classifying → ready
8383-- [x] `audit()` — progress updates emitted during fetch and classify phases
8484-- [x] `audit()` — error during fetch: initial → fetching → error
8585-- [x] `audit()` — empty results: transitions to ready with empty list
8686-- [x] `toggleSelection` — toggles selected flag on correct index, emits new state
8787-- [x] `selectAllByStatus` / `deselectAllByStatus` — selects/deselects all records matching status
8888-- [x] `toggleVisibility` — adds/removes status from visibleStatuses set
8989-- [x] `confirmUnfollow` — state transitions: ready → unfollowing → complete, unfollowed records removed from results
9090-- [x] `confirmUnfollow` — error during unfollow: ready → unfollowing → error with partial count
9191-9292-#### Widget Tests (FollowAuditScreen)
9393-9494-- [x] initial state renders "Scan" button
9595-- [x] fetching state shows progress bar with count text
9696-- [x] ready state renders results list with correct status badges
9797-- [x] selecting a record changes row background to red tint
9898-- [x] "Unfollow Selected" button shows correct count and is disabled when nothing selected
9999-- [x] filter toggles hide/show rows by status
100100-- [x] "Select All" per category selects all visible records of that status
101101-- [x] complete state shows "Unfollowed N account(s)"
102102-- [x] error state shows message and retry button
103103-- [x] empty results shows "No problematic follows found"
104104-- [x] tapping handle navigates to profile screen
105105-- [x] responsive layout: chips on narrow, sidebar on wide
106106-107107-#### Integration Tests
108108-109109-- [x] End-to-end: scan follows → results displayed → select records → confirm unfollow → success state
88+Completed [2026-04-11](../../CHANGELOG.md#2026-04-11)