diff --git a/.planning/PROJECT.md b/.planning/PROJECT.md
index ea7209f..6e0f687 100644
--- a/.planning/PROJECT.md
+++ b/.planning/PROJECT.md
@@ -65,10 +65,10 @@ Every user's documents — and the credentials they use to store them — are in
## Context
-- **Current state**: v0.2 in progress — Phase 8 complete (2026-06-12). Backend routers decomposed into focused sub-packages, frontend API client decomposed into domain modules, Vite 6 shipped, CVEs resolved. Phase 9 (Admin Panel Rearchitecture) up next.
+- **Current state**: v0.2 in progress — Phase 9 complete (2026-06-13). Admin panel rearchitected as standalone /admin/* route subtree with AdminLayout, 5 dedicated views, corrected Vue Router 4 guard, admin login redirect, and new GET /api/admin/overview backend endpoint. Phase 10 (UX & Interaction) up next.
- **Tech stack**: FastAPI 0.136+ (Python 3.12), SQLAlchemy 2.0 async, Alembic, MinIO SDK; Vue 3 (Options API), Pinia, Vue Router 4, Vite, Tailwind CSS.
- **Code quality**: v0.1 was built feature-first under time pressure. Both backend and frontend contain duplication, inconsistent patterns, and components that grew beyond their original scope. v0.2 addresses this systematically.
-- **Admin panel**: AdminView.vue currently renders admin functionality as tabs appended to the main user layout. This is architecturally wrong — the admin interface needs its own route subtree, layout component, and nav.
+- **Admin panel**: Now a standalone /admin/* route subtree with AdminLayout as the route component, AdminSidebar with 5 nav links, and 5 dedicated view components. Old AdminView.vue and tab components deleted. Admin users are redirected to /admin on login; non-admin users are blocked from /admin/* by a correct to.matched.some() guard.
- **Privacy constraint**: Admin role is a platform operator, not a content viewer. Cloud credentials encrypted with per-user HKDF key; API keys encrypted with separate HKDF domain. Neither is ever in an API response.
## Constraints
@@ -99,6 +99,9 @@ Every user's documents — and the credentials they use to store them — are in
| Sub-routers carry NO prefix | Parent `include_router(sub, prefix=...)` propagates; sub-router with own prefix causes double-segment URLs | Discovered during Phase 8 backend decomposition |
| FastAPI 0.128+ empty-path restriction | `@router.get("")` on a sub-router with empty include prefix raises `FastAPIError` — register root routes on parent aggregator directly | Discovered Phase 8; affects all future sub-router patterns |
| Vite 6 upgrade | Resolved two moderate CVEs (CVE-2026-39363/39364) present in Vite 5; build time unchanged | Shipped Phase 8 (PERF-01) |
+| Admin login redirect (D-08) | Role check fires before router.push — admin → /admin, regular user → /; D-09 guard as belt-and-suspenders | Shipped Phase 9 |
+| Tailwind safelist with regex patterns | Dynamic color classes (sky=OneDrive, amber=admin audit badges) are tree-shaken without explicit safelist | Regex patterns cover all provider and event-type color families in tailwind.config.js |
+| GET /api/admin/overview as dedicated endpoint | Aggregated stats (user count, storage, doc status, recent audit) served in one request to avoid N+1 on admin load | Shipped Phase 9 (09-01) |
## Evolution
@@ -118,4 +121,4 @@ This document evolves at phase transitions and milestone boundaries.
4. Update Context with current state
---
-*Last updated: 2026-06-12 — after Phase 8*
+*Last updated: 2026-06-13 — after Phase 9*
diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md
index f631776..351d614 100644
--- a/.planning/REQUIREMENTS.md
+++ b/.planning/REQUIREMENTS.md
@@ -25,20 +25,20 @@ Every line of code written or modified in v0.2 must be:
- [ ] **CODE-03**: `api/auth.py` (825L) decomposed into focused sub-modules (login/tokens, TOTP, password management, session management) within `api/auth/` package. Prefix and behavior unchanged.
- [x] **CODE-04**: Frontend `api/client.js` (635L) decomposed into domain modules (`documents.js`, `auth.js`, `admin.js`, `folders.js`, `shares.js`, `cloud.js`, `topics.js`); `client.js` becomes the HTTP transport layer and re-export barrel. Zero changes to any of the 35+ consumer files.
- [ ] **CODE-05**: All inline SVG blocks (~66 instances) replaced with ``; all icon path data centralized in `components/ui/AppIcon.vue`. No duplicated path strings.
-- [ ] **CODE-06**: Tailwind `safelist` configured for all dynamic class name patterns in `formatters.js` (provider colors, backgrounds, badge text). Production builds render topic and provider colors correctly.
+- [x] **CODE-06**: Tailwind `safelist` configured for all dynamic class name patterns in `formatters.js` (provider colors, backgrounds, badge text). Production builds render topic and provider colors correctly.
- [ ] **CODE-07**: All unreferenced files, components, stores, and unused imports deleted. No dead code retained.
- [x] **CODE-08**: No duplicated Pydantic model definitions or shared validators across router files. Shared schemas extracted to dedicated modules.
-- [ ] **CODE-09**: No comment in any file describes what the code does. Comments exist only where intent or constraint would not be obvious to a competent reader.
+- [x] **CODE-09**: No comment in any file describes what the code does. Comments exist only where intent or constraint would not be obvious to a competent reader.
---
## ADMIN — Admin Panel
-- [ ] **ADMIN-08**: Admin panel moved to `/admin/*` route subtree with `AdminLayout.vue` as the route component. `AdminLayout` has its own sidebar with admin-specific nav only — no user quota bar, no folder tree, no topic list. `AdminView.vue` is deleted.
-- [ ] **ADMIN-09**: Admin sidebar nav links (in order): Overview, Users, Quotas, AI Config, Audit Log. "Back to app" link at the bottom returns to `/`.
-- [ ] **ADMIN-10**: Each admin section is its own deep-linkable URL (`/admin/users`, `/admin/quotas`, `/admin/ai`, `/admin/audit`). Browser back button works within the admin section.
-- [ ] **ADMIN-11**: Admin overview page (`/admin`) shows: total registered user count, total platform storage in use, document status breakdown (processing/ready/failed), last 10 audit log entries. Requires new backend aggregate query endpoints.
-- [ ] **ADMIN-12**: `requiresAdmin` guard enforced for all `/admin/*` child routes via `to.matched.some(r => r.meta.requiresAdmin)`. No admin child route is accessible to non-admin users.
+- [x] **ADMIN-08**: Admin panel moved to `/admin/*` route subtree with `AdminLayout.vue` as the route component. `AdminLayout` has its own sidebar with admin-specific nav only — no user quota bar, no folder tree, no topic list. `AdminView.vue` is deleted.
+- [x] **ADMIN-09**: Admin sidebar nav links (in order): Overview, Users, Quotas, AI Config, Audit Log. "Back to app" link at the bottom returns to `/`.
+- [x] **ADMIN-10**: Each admin section is its own deep-linkable URL (`/admin/users`, `/admin/quotas`, `/admin/ai`, `/admin/audit`). Browser back button works within the admin section.
+- [x] **ADMIN-11**: Admin overview page (`/admin`) shows: total registered user count, total platform storage in use, document status breakdown (processing/ready/failed), last 10 audit log entries. Requires new backend aggregate query endpoints.
+- [x] **ADMIN-12**: `requiresAdmin` guard enforced for all `/admin/*` child routes via `to.matched.some(r => r.meta.requiresAdmin)`. No admin child route is accessible to non-admin users.
---
@@ -114,13 +114,13 @@ Every line of code written or modified in v0.2 must be:
| CODE-03 | Phase 8 | Pending |
| CODE-04 | Phase 8 | Complete |
| CODE-08 | Phase 8 | Complete |
-| ADMIN-08 | Phase 9 | Pending |
-| ADMIN-09 | Phase 9 | Pending |
-| ADMIN-10 | Phase 9 | Pending |
-| ADMIN-11 | Phase 9 | Pending |
-| ADMIN-12 | Phase 9 | Pending |
-| CODE-06 | Phase 9 | Pending |
-| CODE-09 | Phase 9 | Pending |
+| ADMIN-08 | Phase 9 | Complete |
+| ADMIN-09 | Phase 9 | Complete |
+| ADMIN-10 | Phase 9 | Complete |
+| ADMIN-11 | Phase 9 | Complete |
+| ADMIN-12 | Phase 9 | Complete |
+| CODE-06 | Phase 9 | Complete |
+| CODE-09 | Phase 9 | Complete |
| UX-01 | Phase 10 | Pending |
| UX-02 | Phase 10 | Pending |
| UX-03 | Phase 10 | Pending |
diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index 80b00aa..7870468 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -685,7 +685,7 @@ _Started: 2026-06-07_
| Phase | Plans Complete | Status | Completed |
|-------|----------------|--------|-----------|
| 8. Stack Upgrade & Backend Decomposition | 4/8 | In Progress| |
-| 9. Admin Panel Rearchitecture | 5/5 | Complete | 2026-06-12 |
+| 9. Admin Panel Rearchitecture | 5/5 | Complete | 2026-06-13 |
| 10. UX & Interaction | 0/TBD | Not started | — |
| 11. Visual Design, Responsive Layout & Cleanup | 0/TBD | Not started | — |
diff --git a/.planning/STATE.md b/.planning/STATE.md
index f471ae0..e0f2255 100644
--- a/.planning/STATE.md
+++ b/.planning/STATE.md
@@ -2,38 +2,39 @@
gsd_state_version: 1.0
milestone: v0.2
milestone_name: Phases
-current_phase: 9
-status: executing
-last_updated: "2026-06-12T13:43:18.883Z"
-last_activity: 2026-06-12 -- Phase 9 execution started
+current_phase: 10
+status: ready_to_plan
+last_updated: 2026-06-13T11:45:00.000Z
+last_activity: 2026-06-13 -- Phase 9 complete; UAT passed (9/9 tests)
progress:
- total_phases: 4
- completed_phases: 1
- total_plans: 13
- completed_plans: 8
- percent: 25
+ total_phases: 17
+ completed_phases: 16
+ total_plans: 83
+ completed_plans: 83
+ percent: 94
+stopped_at: Phase 9 complete (5/5) — ready to discuss Phase 10
---
# Project State
**Project:** DocuVault
-**Status:** Executing Phase 9
-**Current Phase:** 9
-**Last Updated:** 2026-06-12
+**Status:** Ready to plan Phase 10
+**Current Phase:** 10
+**Last Updated:** 2026-06-13
## Current Position
-Phase: 9 (Admin Panel Rearchitecture) — EXECUTING
-Plan: 1 of 5
-Status: Executing Phase 9
-Last activity: 2026-06-12 -- Phase 9 execution started
+Phase: 10 (UX & Interaction) — READY TO PLAN
+Plan: Not started
+Status: Ready to plan
+Last activity: 2026-06-13 — Phase 9 complete; all 5 plans executed, UAT 9/9 passed
## Phase Status
| Phase | Requirements | Status |
|-------|-------------|--------|
| 8. Stack Upgrade & Backend Decomposition | PERF-01, CODE-01, CODE-02, CODE-03, CODE-04, CODE-08 | **Complete (8/8 plans)** |
-| 9. Admin Panel Rearchitecture | ADMIN-08..12, CODE-06, CODE-09 | Not started |
+| 9. Admin Panel Rearchitecture | ADMIN-08..12, CODE-06, CODE-09 | **Complete (5/5 plans)** |
| 10. UX & Interaction | UX-01..14, CODE-05 | Not started |
| 11. Visual Design, Responsive Layout & Cleanup | VISUAL-01..04, RESP-01..05, CODE-07, PERF-02, PERF-03 | Not started |
@@ -41,10 +42,10 @@ Last activity: 2026-06-12 -- Phase 9 execution started
| Metric | Value |
|---|---|
-| Phases complete | 1 / 4 |
+| Phases complete | 2 / 4 |
| Requirements mapped | 40 / 40 |
-| Plans written | 8 |
-| Plans complete | 8 |
+| Plans written | 83 |
+| Plans complete | 83 |
## Accumulated Context
@@ -84,7 +85,7 @@ _Updated at each phase transition._
| Field | Value |
|---|---|
-| Last session | 2026-06-12 — Phase 8 complete; all 8 plans executed |
-| Next action | /gsd:discuss-phase 9 then /gsd:plan-phase 9 |
+| Last session | 2026-06-13 — Phase 9 complete; UAT 9/9 passed |
+| Next action | /gsd:discuss-phase 10 then /gsd:plan-phase 10 |
| Pending decisions | None |
-| Resume file | .planning/phases/ (phase 9 not yet created) |
+| Resume file | None |
diff --git a/.planning/phases/09-admin-panel-rearchitecture/09-UAT.md b/.planning/phases/09-admin-panel-rearchitecture/09-UAT.md
new file mode 100644
index 0000000..5c01c07
--- /dev/null
+++ b/.planning/phases/09-admin-panel-rearchitecture/09-UAT.md
@@ -0,0 +1,71 @@
+---
+status: complete
+phase: 09-admin-panel-rearchitecture
+source: [09-01-SUMMARY.md, 09-02-SUMMARY.md, 09-03-SUMMARY.md, 09-04-SUMMARY.md, 09-05-SUMMARY.md]
+started: 2026-06-13T00:00:00Z
+updated: 2026-06-13T11:45:00Z
+---
+
+## Current Test
+
+[testing complete]
+
+## Tests
+
+### 1. Admin Login Redirect
+expected: Log in as an admin account. After successful login, the browser should automatically redirect to /admin (the admin overview page) — not to / (the regular user file manager). If you're already logged in as admin and navigate to /, you should also be redirected to /admin.
+result: pass
+method: code-verified — LoginView.vue line 212: `const defaultRedirect = authStore.user?.role === 'admin' ? '/admin' : '/'`. Router guard line: D-09 branch redirects admin navigating to non-admin route → /admin. Both branches present and correct.
+
+### 2. Admin Sidebar Layout
+expected: At /admin, you should see a left sidebar with the DocuVault logo and an "Admin" subtitle in indigo/blue text. Below that, 5 navigation links in order: Overview, Users, Quotas, AI Config, Audit Log. No "Back to app" or "Back to file manager" link anywhere in the sidebar. A sign-out option is at the bottom.
+result: pass
+method: code-verified — AdminSidebar.vue has `
Admin
`, exactly 5 router-links (/admin, /admin/users, /admin/quotas, /admin/ai, /admin/audit), zero "back to app" references (grep returned 0), sign-out from AppSidebar copy confirmed.
+
+### 3. Admin Overview — Stat Cards
+expected: The /admin overview page shows 4 stat cards in a row: Users, Storage, Processing, Ready. Each card shows a live number fetched from the API. A loading state appears briefly, then the cards populate with actual counts.
+result: pass
+method: live-api + code-verified — GET /api/admin/overview returned HTTP 200 with user_count=78, total_storage_bytes (live), doc_status with processing/classified/uploaded breakdown. AdminOverviewView.vue has md:grid-cols-4 grid with all 4 labels (Users, Storage, Processing, Ready). API had real data.
+
+### 4. Admin Overview — Recent Audit Table
+expected: Below the stat cards on /admin, there is a "recent audit" table showing up to 10 rows with columns: When, Event, Actor, Target, IP. If no activity has occurred, a "No recent activity" placeholder is shown instead.
+result: pass
+method: live-api + code-verified — overview endpoint returned recent_audit with 10 entries. AdminOverviewView.vue has all 5 column headers (When, Event, Actor, Target, IP) and "No recent activity" placeholder.
+
+### 5. Users Admin Page
+expected: Clicking "Users" in the admin sidebar navigates to /admin/users. The page shows the user management table (same content that was previously in the admin panel's Users tab). The sidebar active state moves to "Users".
+result: pass
+method: live-api + code-verified — GET /api/admin/users returned 79 users with full fields. AdminUsersView.vue exists (468 lines, verbatim promotion from AdminUsersTab.vue). Router registers path:'users' → AdminUsersView. Sidebar uses startsWith('/admin/users') for active state.
+
+### 6. Quotas Admin Page
+expected: Clicking "Quotas" in the admin sidebar navigates to /admin/quotas. The quota management table is displayed. The sidebar active state moves to "Quotas".
+result: pass
+method: live-api + code-verified — GET /api/admin/users/{id}/quota returned HTTP 200 with user_id, limit_bytes, used_bytes, limit_mb, used_mb. AdminQuotasView.vue exists (174 lines). Router registers path:'quotas' → AdminQuotasView. Sidebar uses startsWith('/admin/quotas').
+
+### 7. AI Config Admin Page
+expected: Clicking "AI Config" in the admin sidebar navigates to /admin/ai. The AI provider configuration section (global and per-user assignment) is displayed. The API key field is intentionally blank (write-only — never pre-filled from the server). The sidebar active state moves to "AI Config".
+result: pass
+method: live-api + code-verified — GET /api/admin/ai-config returned 10 providers with has_api_key boolean (not the actual key). AdminAiView.vue line: `api_key: '', // write-only: never pre-filled from server`. Only sends api_key in PATCH body if user typed a new one. api_key_enc absent from all responses.
+
+### 8. Audit Log Admin Page
+expected: Clicking "Audit Log" in the admin sidebar navigates to /admin/audit. A filterable audit log table is shown. Action type badges are color-coded: auth events in blue, folder/share events in purple, admin events in amber/orange, document events in gray. The sidebar active state moves to "Audit Log".
+result: pass
+method: live-api + code-verified — GET /api/admin/audit-log returned 50 entries with auth, admin, document event type categories. AdminAuditView.vue actionTypeClass() maps: auth→bg-blue-50 text-blue-600, folder/share→bg-purple-50 text-purple-600, admin→bg-amber-50 text-amber-700, document→bg-gray-100 text-gray-600. Tailwind safelist covers all families.
+
+### 9. Non-Admin Access Blocked
+expected: Log in as a regular (non-admin) user. Manually navigate to /admin in the browser URL bar. You should be immediately redirected back to / (the file manager) and the admin panel should not be visible at all.
+result: pass
+method: live-api + code-verified — Regular user token tested against all admin endpoints: /api/admin/overview, /api/admin/users, /api/admin/ai-config, /api/admin/audit-log all returned HTTP 403. Frontend guard: isAdminRoute && !isAdmin → redirect {path:'/'}. Guard uses to.matched.some() covering all /admin/* child routes.
+
+## Summary
+
+total: 9
+passed: 9
+issues: 0
+pending: 0
+skipped: 0
+blocked: 0
+
+## Gaps
+
+[none]