···11---
22title: Phase 5 Spec
33-updated: 2026-03-23
33+updated: 2026-03-25
44---
5566## Feature Parity
7788-- Endpoints to build UI around:
99- - In search screen: `/xrpc/app.bsky.graph.searchStarterPacks`
1010- - In profile screen: `/xrpc/app.bsky.graph.getSuggestedFollowsByActor`, should be a
1111- sheet accessible via overflow menu
1212- - In settings screen: `/xrpc/app.bsky.video.getUploadLimits` to show remaining daily
1313- video upload limits
88+Three new endpoint integrations to round out UI coverage.
99+1010+---
1111+1212+### 1. Starter Pack Search (Search Screen)
1313+1414+**Endpoint:** `GET /xrpc/app.bsky.graph.searchStarterPacks`
1515+**Auth:** Not required
1616+1717+**Request:**
1818+1919+| Param | Type | Required | Default | Notes |
2020+|----------|--------|----------|---------|-------------------------------|
2121+| `q` | string | yes | — | Lucene-style query |
2222+| `limit` | int | no | 25 | 1–100 |
2323+| `cursor` | string | no | — | Pagination cursor |
2424+2525+**Response:**
2626+2727+```json
2828+{
2929+ "cursor": "string?",
3030+ "starterPacks": "StarterPackViewBasic[]"
3131+}
3232+```
3333+3434+`StarterPackViewBasic` includes: `uri`, `cid`, `record`, `creator` (ProfileViewBasic),
3535+`listItemCount?`, `joinedWeekCount?`, `joinedAllTimeCount?`, `labels?`, `indexedAt`.
3636+3737+**SDK:** `bluesky.graph.searchStarterPacks(q:, limit:, cursor:)`
3838+→ `XRPCResponse<GraphSearchStarterPacksOutput>`
3939+4040+**UI:** Add a third "Starter Packs" tab to the search screen alongside Posts and People.
4141+Tapping a result navigates to the existing starter pack detail screen. Infinite scroll
4242+pagination via cursor. Reuse the existing `StarterPackViewBasic` tile pattern from
4343+the profile starter packs tab.
4444+4545+---
4646+4747+### 2. Suggested Follows (Profile Screen)
4848+4949+**Endpoint:** `GET /xrpc/app.bsky.graph.getSuggestedFollowsByActor`
5050+**Auth:** Not required
5151+5252+**Request:**
5353+5454+| Param | Type | Required | Notes |
5555+|---------|--------|----------|-------------------|
5656+| `actor` | string | yes | DID or handle |
5757+5858+**Response:**
5959+6060+```json
6161+{
6262+ "suggestions": "ProfileView[]",
6363+ "isFallback": "bool (default false)",
6464+ "recIdStr": "string?"
6565+}
6666+```
6767+6868+No pagination — returns all suggestions in one response.
6969+7070+**SDK:** `bluesky.graph.getSuggestedFollowsByActor(actor:)`
7171+→ `XRPCResponse<GraphGetSuggestedFollowsByActorOutput>`
7272+7373+**UI:** New "Suggested Follows" entry in the profile screen's overflow (more options)
7474+bottom sheet. Opens a draggable scrollable sheet listing `ProfileView` tiles with
7575+follow/unfollow buttons. Each tile navigates to the user's profile on tap. Show empty
7676+state if `suggestions` is empty. Hide the menu entry when viewing own profile.
7777+7878+---
7979+8080+### 3. Video Upload Limits (Settings Screen)
8181+8282+**Endpoint:** `GET /xrpc/app.bsky.video.getUploadLimits`
8383+**Auth:** Required
8484+8585+**Request:** None
8686+8787+**Response:**
8888+8989+```json
9090+{
9191+ "canUpload": "bool",
9292+ "remainingDailyVideos": "int?",
9393+ "remainingDailyBytes": "int?",
9494+ "message": "string?",
9595+ "error": "string?"
9696+}
9797+```
9898+9999+**SDK:** `bluesky.video.getUploadLimits()`
100100+→ `XRPCResponse<VideoGetUploadLimitsOutput>`
101101+102102+**UI:** New tile in the settings screen's Account section showing daily video upload
103103+quota. Display remaining video count and remaining bytes (formatted as MB/GB).
104104+Show `canUpload` status and any server `message`. Fetch on screen load; show
105105+loading indicator while fetching. If the endpoint returns an error or `canUpload`
106106+is false, show the reason.
+6-5
docs/tasks/phase-4.md
···2222## M15 — Offline Reading & Network Resilience
23232424- [x] `ConnectivityCubit` via **connectivity_plus** — expose network state stream
2525-- [ ] Cache last-fetched feed page as serialised JSON in Drift
2626-- [ ] Display cached data immediately on launch, refresh in background
2727-- [ ] "You're offline" banner when connectivity is lost
2828-- [ ] Disable network-dependent actions (compose, like, repost, follow) when offline with tooltip
2929-- [ ] Notifications and DM screens show "No connection" empty state when offline with no cache
2525+- [x] Cache last-fetched feed page as serialised JSON in Drift
2626+- [x] Display cached data immediately on launch, refresh in background
2727+- [x] "You're offline" banner when connectivity is lost
2828+- [x] Disable network-dependent actions (compose, like, repost, follow) when offline with tooltip
2929+- [x] Notifications and DM screens show "No connection" empty state when offline with no cache
3030+- [x] In Debug/Dev mode, add "Simulate Offline" toggle in settings to test offline UI
30313132## M16 — Jump to Profile
3233
+72-1
docs/tasks/phase-5.md
···11---
22title: Phase 5 Task Breakdown
33-updated: 2026-03-23
33+updated: 2026-03-25
44---
55+66+# Phase 5 Milestones
77+88+## M20 — Starter Pack Search
99+1010+### Core
1111+1212+- [ ] `SearchRepository.searchStarterPacks()` — call `bluesky.graph.searchStarterPacks(q:, limit:, cursor:)`, return result with `List<StarterPackViewBasic>` and cursor
1313+- [ ] Add `starterPacks` value to `SearchTab` enum, update `SearchTabLabel` extension
1414+1515+### Cubit
1616+1717+- [ ] `SearchBloc` — handle starter packs tab: dispatch search on tab switch if query present, handle `LoadMoreRequested` with cursor pagination
1818+- [ ] `SearchState` — add `starterPacks` list and `starterPacksCursor` fields
1919+2020+### UI
2121+2222+- [ ] Search screen UI — add third "Starter Packs" tab pill in `_buildTab` row
2323+- [ ] Starter pack result tile widget — show name, creator handle, member count, joined stats; reuse pattern from profile starter packs tab
2424+- [ ] Tap result → navigate to existing starter pack detail screen (`/starter-pack?uri=`)
2525+- [ ] Infinite scroll pagination for starter packs tab
2626+2727+### Tests
2828+2929+- [ ] Unit tests: `SearchRepository.searchStarterPacks`, bloc events for new tab, pagination
3030+- [ ] Widget tests: third tab renders, results display, empty state, tap navigation
3131+3232+## M21 — Suggested Follows Sheet
3333+3434+### Core
3535+3636+- [ ] `ProfileRepository.getSuggestedFollows()` — call `bluesky.graph.getSuggestedFollowsByActor(actor:)`, return `List<ProfileView>`
3737+3838+### Cubit
3939+4040+- [ ] `SuggestedFollowsCubit` — `load(actor:)` fetches suggestions, exposes loaded/loading/error states
4141+4242+### UI
4343+4444+- [ ] Suggested follows sheet widget — `DraggableScrollableSheet` listing `ProfileView` tiles with follow/unfollow toggle buttons
4545+- [ ] Profile screen overflow menu — add "Suggested Follows" `ListTile` entry; hide when viewing own profile
4646+- [ ] Tap entry → create cubit, show sheet with `BlocProvider.value`, close cubit on sheet dismiss via `.whenComplete`
4747+- [ ] Tap profile tile → pop sheet, navigate to profile screen
4848+- [ ] Empty state when no suggestions returned
4949+5050+### Tests
5151+5252+- [ ] Unit tests: repository method, cubit state transitions
5353+- [ ] Widget tests: sheet renders profiles, follow button toggles, own-profile menu hides entry, empty state
5454+5555+## M22 — Video Upload Limits
5656+5757+### Core
5858+5959+- [ ] `VideoRepository` (or extend settings repository) — `getUploadLimits()` calling `bluesky.video.getUploadLimits()`, return typed result
6060+6161+### Cubit
6262+6363+- [ ] `VideoUploadLimitsCubit` — fetch on init, expose `canUpload`, remaining counts, message/error
6464+6565+### UI
6666+6767+- [ ] Settings screen — new tile in Account section: "Video Upload Limits"
6868+- [ ] Tile UI — show remaining daily video count, remaining bytes formatted as MB/GB, `canUpload` status badge
6969+- [ ] Loading state while fetching, error state if request fails
7070+- [ ] Display server `message` if present; show `error` text with warning styling if `canUpload` is false
7171+7272+### Tests
7373+7474+- [ ] Unit tests: repository method, cubit state transitions and formatting
7575+- [ ] Widget tests: tile renders limits, loading indicator, error state, message display