Mirrors the operator-actionable subset of the web admin surface to the Flutter client. Closes the admin chunk of Fable umbrella #356 for v1; Integrations + Overview tabs are an explicit carve-out (mobile-low-value, deferred).
What ships
Three new admin surfaces under /admin
Requests (/admin/requests) — pending Lidarr requests with Approve / Reject. Approve fires immediately; Reject confirms via dialog. Both optimistic with rollback. Joins user_id → username client-side via adminUsersProvider (server doesn't denormalize the requester username).
Quarantine (/admin/quarantine) — ExpansionTile per aggregated track row (collapsed: title + report-count + top reason; expanded: per-user reports). Three-dot menu offers Resolve / Delete file / Delete via Lidarr; both deletes route through a TypedConfirmSheet that requires typing "DELETE".
Users (/admin/users) — Users + Invites in one scroll. User rows tap into an edit sheet (admin / auto-approve toggles, admin-supplied password reset, typed-confirm delete). Invites section shows token (monospace, copy button), optional note, redeemed badge; "Generate" opens a small note-input dialog (server hardcodes 24h TTL).
Admin landing + entry points
AdminLandingScreen — list of three section cards with at-a-glance counts driven by adminCountsProvider (parallel fan-out to the three list endpoints).
Settings → Admin card — visible only to admins.
Kebab overflow → "Admin" — sibling entry to Settings, gated on isAdmin. Reach is 2 taps.
Home AppBar simplification
HomeScreen and the other top-level screens (Library, Search, Discover, Playlists, Settings) now adopt a shared MainAppBarActions widget:
Three primary icons (Home / Library / Search; current screen suppressed)
Kebab overflow with Playlists / Discover / Settings / [Admin if isAdmin]
This drops Home from a crowded 5-icon AppBar and gives every screen a consistent shape.
Routing
buildRouter redirect closure now refuses any /admin/* path when user.isAdmin == false (server RequireAdmin middleware remains the actual authority — this is UX).
Server contract — verified, not assumed
Spent the diagnostic time up-front against internal/api/admin_*.go to lock down JSON shapes. Notable client-affecting bits:
GET /admin/users and GET /admin/invites are enveloped ({"users": [...]}, {"invites": [...]}) — not flat lists.
PUT /admin/users/{id}/auto-approve body field is auto_approve (different from the auto_approve_requests response field).
POST /admin/users/{id}/reset-password takes admin-supplied {password: "..."} and returns 204 — there is no server-generated-password mode, so the UI is a typed-input form.
POST /admin/invites takes optional {note: "..."} — server hardcodes 24h TTL, so the UI offers a note input rather than an expiry input.
Quarantine rows are aggregated (report_count, reason_counts, nested reports[]) — UI is an ExpansionTile showing the rollup with per-user reports underneath.
Requests don't include the requester username — UI joins client-side against adminUsersProvider (free since the Users screen warms it).
Spec + plan in docs/superpowers/specs/2026-05-08-flutter-admin-parity-slice-design.md and docs/superpowers/plans/2026-05-08-flutter-admin-parity-slice.md (gitignored, local-only).
Test plan
CI runs flutter analyze + flutter test + APK build on the flutter-ci runner.
CI green
Operator: open the new APK, confirm the kebab on /home shows Admin (only when logged in as admin)
Operator: navigate /admin → tap each section card → verify counts and actions
Operator: register a user via /register, then promote them in /admin/users
Operator: generate an invite, copy the token, register a new user with the token
Notes for next slices
AdminUsersController build-only landed in slice 2 to feed the Requests-screen username join; mutations layered on in slice 3.
Realtime / push updates for the queues, bulk actions, and admin user-creation form remain explicit non-goals (separate tasks).
Mirrors the operator-actionable subset of the web admin surface to the Flutter client. Closes the admin chunk of Fable umbrella **#356** for v1; Integrations + Overview tabs are an explicit carve-out (mobile-low-value, deferred).
## What ships
### Three new admin surfaces under `/admin`
- **Requests** (`/admin/requests`) — pending Lidarr requests with Approve / Reject. Approve fires immediately; Reject confirms via dialog. Both optimistic with rollback. Joins `user_id` → `username` client-side via `adminUsersProvider` (server doesn't denormalize the requester username).
- **Quarantine** (`/admin/quarantine`) — `ExpansionTile` per aggregated track row (collapsed: title + report-count + top reason; expanded: per-user reports). Three-dot menu offers Resolve / Delete file / Delete via Lidarr; both deletes route through a `TypedConfirmSheet` that requires typing "DELETE".
- **Users** (`/admin/users`) — Users + Invites in one scroll. User rows tap into an edit sheet (admin / auto-approve toggles, admin-supplied password reset, typed-confirm delete). Invites section shows token (monospace, copy button), optional note, redeemed badge; "Generate" opens a small note-input dialog (server hardcodes 24h TTL).
### Admin landing + entry points
- **`AdminLandingScreen`** — list of three section cards with at-a-glance counts driven by `adminCountsProvider` (parallel fan-out to the three list endpoints).
- **Settings → Admin card** — visible only to admins.
- **Kebab overflow → "Admin"** — sibling entry to Settings, gated on `isAdmin`. Reach is 2 taps.
### Home AppBar simplification
`HomeScreen` and the other top-level screens (Library, Search, Discover, Playlists, Settings) now adopt a shared **`MainAppBarActions`** widget:
- Three primary icons (Home / Library / Search; current screen suppressed)
- Kebab overflow with Playlists / Discover / Settings / `[Admin if isAdmin]`
This drops Home from a crowded 5-icon AppBar and gives every screen a consistent shape.
### Routing
`buildRouter` redirect closure now refuses any `/admin/*` path when `user.isAdmin == false` (server `RequireAdmin` middleware remains the actual authority — this is UX).
## Server contract — verified, not assumed
Spent the diagnostic time up-front against `internal/api/admin_*.go` to lock down JSON shapes. Notable client-affecting bits:
- `GET /admin/users` and `GET /admin/invites` are **enveloped** (`{"users": [...]}`, `{"invites": [...]}`) — not flat lists.
- `PUT /admin/users/{id}/auto-approve` body field is `auto_approve` (different from the `auto_approve_requests` response field).
- `POST /admin/users/{id}/reset-password` takes admin-supplied `{password: "..."}` and returns 204 — there is no server-generated-password mode, so the UI is a typed-input form.
- `POST /admin/invites` takes optional `{note: "..."}` — server hardcodes 24h TTL, so the UI offers a note input rather than an expiry input.
- Quarantine rows are aggregated (`report_count`, `reason_counts`, nested `reports[]`) — UI is an `ExpansionTile` showing the rollup with per-user reports underneath.
- Requests don't include the requester username — UI joins client-side against `adminUsersProvider` (free since the Users screen warms it).
Spec + plan in `docs/superpowers/specs/2026-05-08-flutter-admin-parity-slice-design.md` and `docs/superpowers/plans/2026-05-08-flutter-admin-parity-slice.md` (gitignored, local-only).
## Test plan
CI runs `flutter analyze` + `flutter test` + APK build on the `flutter-ci` runner.
- [ ] CI green
- [ ] Operator: open the new APK, confirm the kebab on `/home` shows Admin (only when logged in as admin)
- [ ] Operator: navigate `/admin` → tap each section card → verify counts and actions
- [ ] Operator: register a user via `/register`, then promote them in `/admin/users`
- [ ] Operator: generate an invite, copy the token, register a new user with the token
## Notes for next slices
- `AdminUsersController` build-only landed in slice 2 to feed the Requests-screen username join; mutations layered on in slice 3.
- Realtime / push updates for the queues, bulk actions, and admin user-creation form remain explicit non-goals (separate tasks).
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Mirrors the operator-actionable subset of the web admin surface to the Flutter client. Closes the admin chunk of Fable umbrella #356 for v1; Integrations + Overview tabs are an explicit carve-out (mobile-low-value, deferred).
What ships
Three new admin surfaces under
/admin/admin/requests) — pending Lidarr requests with Approve / Reject. Approve fires immediately; Reject confirms via dialog. Both optimistic with rollback. Joinsuser_id→usernameclient-side viaadminUsersProvider(server doesn't denormalize the requester username)./admin/quarantine) —ExpansionTileper aggregated track row (collapsed: title + report-count + top reason; expanded: per-user reports). Three-dot menu offers Resolve / Delete file / Delete via Lidarr; both deletes route through aTypedConfirmSheetthat requires typing "DELETE"./admin/users) — Users + Invites in one scroll. User rows tap into an edit sheet (admin / auto-approve toggles, admin-supplied password reset, typed-confirm delete). Invites section shows token (monospace, copy button), optional note, redeemed badge; "Generate" opens a small note-input dialog (server hardcodes 24h TTL).Admin landing + entry points
AdminLandingScreen— list of three section cards with at-a-glance counts driven byadminCountsProvider(parallel fan-out to the three list endpoints).isAdmin. Reach is 2 taps.Home AppBar simplification
HomeScreenand the other top-level screens (Library, Search, Discover, Playlists, Settings) now adopt a sharedMainAppBarActionswidget:[Admin if isAdmin]This drops Home from a crowded 5-icon AppBar and gives every screen a consistent shape.
Routing
buildRouterredirect closure now refuses any/admin/*path whenuser.isAdmin == false(serverRequireAdminmiddleware remains the actual authority — this is UX).Server contract — verified, not assumed
Spent the diagnostic time up-front against
internal/api/admin_*.goto lock down JSON shapes. Notable client-affecting bits:GET /admin/usersandGET /admin/invitesare enveloped ({"users": [...]},{"invites": [...]}) — not flat lists.PUT /admin/users/{id}/auto-approvebody field isauto_approve(different from theauto_approve_requestsresponse field).POST /admin/users/{id}/reset-passwordtakes admin-supplied{password: "..."}and returns 204 — there is no server-generated-password mode, so the UI is a typed-input form.POST /admin/invitestakes optional{note: "..."}— server hardcodes 24h TTL, so the UI offers a note input rather than an expiry input.report_count,reason_counts, nestedreports[]) — UI is anExpansionTileshowing the rollup with per-user reports underneath.adminUsersProvider(free since the Users screen warms it).Spec + plan in
docs/superpowers/specs/2026-05-08-flutter-admin-parity-slice-design.mdanddocs/superpowers/plans/2026-05-08-flutter-admin-parity-slice.md(gitignored, local-only).Test plan
CI runs
flutter analyze+flutter test+ APK build on theflutter-cirunner./homeshows Admin (only when logged in as admin)/admin→ tap each section card → verify counts and actions/register, then promote them in/admin/usersNotes for next slices
AdminUsersControllerbuild-only landed in slice 2 to feed the Requests-screen username join; mutations layered on in slice 3.