Files
kite/.planning/milestones/v0.2-phases/09-admin-panel-rearchitecture/09-04-SUMMARY.md
T
curo1305andClaude Sonnet 4.6 123ae5b29b chore: archive v0.2 phase directories to milestones/v0.2-phases/
Moves phases 08–11 execution artifacts from .planning/phases/ to
.planning/milestones/v0.2-phases/ to keep .planning/phases/ clean
for the next milestone.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 14:34:52 +02:00

14 KiB

phase, plan, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics
phase plan subsystem tags dependency_graph tech_stack key_files decisions metrics
09 04 frontend-router-auth
admin
router
guard
tailwind
cleanup
security
requires provides affects
09-01
09-02
09-03
frontend/src/router/index.js (nested /admin subtree + corrected guard)
frontend/src/views/auth/LoginView.vue (admin login redirect)
frontend/tailwind.config.js (safelist)
frontend/src/router/__tests__/router.guard.test.js
frontend/src/components/admin/__tests__/ (3 test files migrated)
frontend/src/api/admin.js (JSDoc updated)
frontend/src/views/SettingsView.vue (stale comment removed)
added patterns
Vue Router 4 nested route subtree — AdminLayout as /admin component, 5 lazy-loaded children
to.matched.some(r => r.meta.requiresAdmin) — only correct pattern for child-route meta inheritance in Vue Router 4
D-09/D-10 strict admin role separation — two guard branches, one per direction
Tailwind safelist with regex patterns — covers all dynamic color families from formatters.js + AuditLogTab.actionTypeClass()
created modified deleted
frontend/src/router/index.js
frontend/src/views/auth/LoginView.vue
frontend/tailwind.config.js
frontend/src/router/__tests__/router.guard.test.js
frontend/src/components/admin/__tests__/AdminUsersTab.test.js
frontend/src/components/admin/__tests__/AdminQuotasTab.test.js
frontend/src/components/admin/__tests__/AdminAiConfigTab.test.js
frontend/src/api/admin.js
frontend/src/views/SettingsView.vue
frontend/src/views/AdminView.vue
frontend/src/components/admin/AdminUsersTab.vue
frontend/src/components/admin/AdminQuotasTab.vue
frontend/src/components/admin/AdminAiConfigTab.vue
frontend/src/components/admin/AuditLogTab.vue
to.matched.some(r => r.meta.requiresAdmin) is the guard pattern — flat to.meta.requiresAdmin silently bypasses all /admin/* child routes in Vue Router 4
D-09 admin-on-user-route redirect added to beforeEach — isAdminRoute + isAdmin combination covers both directions cleanly
Tailwind safelist extended to include sky (OneDrive providerBg) and amber (AuditLogTab actionTypeClass admin badge) — correcting the original D-14 which omitted these families
Tab tests migrated to import from views/admin/Admin*View.vue — behavior identical, no test logic changed
router.guard.test.js: AdminView mock replaced with AdminLayout + 5 view mocks; D-09 redirect test added
duration completed tasks_completed tasks_total files_created files_modified files_deleted
18 minutes 2026-06-12 3 3 0 9 5

Phase 09 Plan 04: Router Rewire, Login Redirect, Cleanup Summary

One-liner: Nested /admin route subtree wired with AdminLayout as component, to.matched.some() guard fixed, D-08/D-09/D-10 strict admin separation enforced, Tailwind safelist installed for sky+amber color families, and five legacy files deleted with tests migrated.

Tasks Completed

Task Name Commit Files
1 Rewire router/index.js + Tailwind safelist 5dbfb6c frontend/src/router/index.js, frontend/tailwind.config.js
2 Wire admin login redirect in LoginView.vue bdd68b2 frontend/src/views/auth/LoginView.vue
3 Delete AdminView.vue + 4 tab files; migrate tests e6467d1 5 deletions, 6 modified (tests + api comment + settings comment)

What Was Built

Task 1 — Nested /admin Route + Guard + Tailwind Safelist

Router rewire: The flat /admin route pointing to the deleted AdminView.vue is replaced with a nested route whose component is the lazy-loaded AdminLayout.vue. Five children are registered:

{ path: '',       AdminOverviewView.vue }   → /admin
{ path: 'users',  AdminUsersView.vue }      → /admin/users
{ path: 'quotas', AdminQuotasView.vue }     → /admin/quotas
{ path: 'ai',     AdminAiView.vue }         → /admin/ai
{ path: 'audit',  AdminAuditView.vue }      → /admin/audit

Only the parent carries meta: { requiresAdmin: true }. Child routes intentionally do not repeat it — the guard uses to.matched.some(r => r.meta.requiresAdmin) which walks the full matched ancestors array, making the parent's meta apply to every child.

Guard fix: The old if (to.meta.requiresAdmin && ...) check is replaced with a 5-step guard:

  1. Silent refresh on non-public routes with no access token
  2. Compute isAdminRoute = to.matched.some(r => r.meta.requiresAdmin)
  3. Compute isAdmin = authStore.user?.role === 'admin'
  4. D-10a: isAdminRoute && !isAdmin → redirect { path: '/' }
  5. D-09: !isAdminRoute && !to.meta.public && isAdmin → redirect { path: '/admin' }

The !to.meta.public clause in step 5 ensures auth routes (/login, /register, /password-reset, /password-reset/confirm) remain reachable for admin users — this is the critical escape hatch that prevents redirect loops when an admin session expires and the guard sends them to /login.

Tailwind safelist: Two regex patterns added covering all dynamic class families:

  • bg-(blue|sky|green|purple|orange|amber|gray|indigo|red)-(50|100|500|600) — covers providerBg() (OneDrive=sky, Google Drive=blue, Nextcloud=orange, WebDAV=gray) and actionTypeClass() (auth=blue, folder/share=purple, admin=amber, document=gray)
  • text-(blue|sky|green|purple|orange|amber|gray|indigo|red)-(400|500|600|700) — covers providerColor() and actionTypeClass() text variants

sky and amber were the two families missing from the original D-14 draft. RESEARCH.md Pattern 7 (confirmed in CONTEXT.md) corrects this — both are included.

Task 2 — Admin Login Redirect (D-08)

The handleLoginResult(!result) branch in LoginView.vue is updated:

const defaultRedirect = authStore.user?.role === 'admin' ? '/admin' : '/'
const redirect = route.query.redirect || defaultRedirect
await router.push(redirect)

authStore.user.role is synchronously populated inside authStore.login() before the store action returns (verified by reading auth.js line 82: user.value = data.user), so the role check fires with a fully populated user object.

The ?redirect= query param is still honored for non-admin users. For admin users, the D-09 guard in the router would intercept any ?redirect=/ attempt and redirect back to /admin anyway — the D-08 check here is belt-and-suspenders.

The requires_totp and requires_password_change branches are byte-for-byte unchanged.

Task 3 — Delete Legacy Files + Migrate Tests

Five files deleted via git rm:

  • frontend/src/views/AdminView.vue — old tab-container view, replaced by AdminLayout + nested routes
  • frontend/src/components/admin/AdminUsersTab.vue — promoted to views/admin/AdminUsersView.vue in 09-03
  • frontend/src/components/admin/AdminQuotasTab.vue — promoted to views/admin/AdminQuotasView.vue in 09-03
  • frontend/src/components/admin/AdminAiConfigTab.vue — promoted to views/admin/AdminAiView.vue in 09-03
  • frontend/src/components/admin/AuditLogTab.vue — promoted to views/admin/AdminAuditView.vue in 09-03

Test migrations (import path rewrite only — no test logic changed):

  • __tests__/AdminUsersTab.test.js: import AdminUsersTab from '../AdminUsersTab.vue'from '../../../views/admin/AdminUsersView.vue'
  • __tests__/AdminQuotasTab.test.js: import AdminQuotasTab from '../AdminQuotasTab.vue'from '../../../views/admin/AdminQuotasView.vue'
  • __tests__/AdminAiConfigTab.test.js: import AdminAiConfigTab from '../AdminAiConfigTab.vue'from '../../../views/admin/AdminAiView.vue'

Router guard test updated:

  • Replaced vi.mock('../../views/AdminView.vue', ...) with mocks for layouts/AdminLayout.vue and all five views/admin/Admin*View.vue files
  • Added D-09 redirect test: admin navigating to / is redirected to /admin

Two stale comments updated (Rule 2 — correctness):

  • src/api/admin.js JSDoc consumer list updated from AdminXxxTab.vue names to Admin*View.vue names
  • src/views/SettingsView.vue stale <!-- Tab strip (copy AdminView pattern verbatim) --> comment simplified to <!-- Tab strip -->

Acceptance Criteria Results

Criterion Result
grep -c "to.matched.some(r => r.meta.requiresAdmin)" router/index.js ≥ 1 1 — PASS
grep -c "to.meta.requiresAdmin" router/index.js = 0 0 — PASS
grep -c "AdminLayout" router/index.js ≥ 1 2 — PASS
All 5 Admin*View imports present in router PASS
grep -c "AdminView" router/index.js = 0 0 — PASS
grep -c "safelist" tailwind.config.js = 1 1 — PASS
grep -c "amber" tailwind.config.js ≥ 1 2 — PASS
grep -c "sky" tailwind.config.js ≥ 1 2 — PASS
grep -c "authStore.user?.role === 'admin'" LoginView.vue = 1 1 — PASS
grep -c "defaultRedirect" LoginView.vue ≥ 2 2 — PASS
None of 5 deleted files exist PASS
No stale AdminXxxTab import references remain PASS
npm run build succeeds 881 ms, 0 errors — PASS
npm test passes 137/137 tests — PASS

Smoke Check (Build Artifacts)

npm run build produced separate lazy-loaded chunks for all 5 admin routes:

  • AdminLayout-BbuVmUU8.js (4.46 kB) — AdminLayout entry chunk
  • admin-BFKH-0jn.js (3.04 kB) — shared admin chunk
  • AdminOverviewView-DUqzmtNA.js (3.49 kB)
  • AdminQuotasView-Cao0PSry.js (4.53 kB)
  • AdminAuditView-DDh7qqQi.js (9.24 kB)
  • AdminUsersView-BLLys8CV.js (12.62 kB)
  • AdminAiView-CZ8yB8IZ.js (16.43 kB)

AdminAuditView chunk contains the full actionTypeClass() logic with bg-amber-50 text-amber-700 and bg-blue-50 text-blue-600 classes — confirmed present in build output. Tailwind safelist ensures these survive tree-shaking in production.

Deviations from Plan

Auto-fixed Issues

[Rule 2 - Missing Critical Functionality] Added D-09 redirect test to router guard test

  • Found during: Task 3 (router guard test update)
  • Issue: The test file verified the non-admin → / redirect (D-10a) but had no test for the admin → /admin redirect (D-09). D-09 is a security-relevant guard branch — omitting it from the test suite is a missing correctness invariant.
  • Fix: Added it('redirects an admin user away from / to /admin (D-09)', ...) test with admin role mock navigating to / and asserting router.currentRoute.value.path === '/admin'.
  • Files modified: frontend/src/router/__tests__/router.guard.test.js
  • Commit: e6467d1

[Rule 1 - Bug] Stale JSDoc consumer list in api/admin.js

  • Found during: Task 3 (grep scan for stale references)
  • Issue: src/api/admin.js JSDoc listed the deleted AdminXxxTab.vue and AuditLogTab.vue names as consumers. Post-deletion, these filenames no longer exist — an inaccurate comment is a correctness issue.
  • Fix: Updated JSDoc consumer list to AdminUsersView.vue, AdminQuotasView.vue, AdminAiView.vue, AdminAuditView.vue.
  • Files modified: frontend/src/api/admin.js
  • Commit: e6467d1

[Rule 1 - Bug] Stale AdminView reference in SettingsView.vue comment

  • Found during: Task 3 (grep scan for AdminView references)
  • Issue: src/views/SettingsView.vue contained <!-- Tab strip (copy AdminView pattern verbatim) -->. After AdminView.vue is deleted, this comment references a non-existent file.
  • Fix: Simplified to <!-- Tab strip -->.
  • Files modified: frontend/src/views/SettingsView.vue
  • Commit: e6467d1

[Rule 3 - Blocking] npm not installed in worktree frontend — installed deps locally

  • Found during: Task 1 verification (npm run build)
  • Issue: The worktree's frontend/ directory had no node_modules/. npm run build failed with ERR_MODULE_NOT_FOUND for vite.
  • Fix: Ran npm install + npm approve-scripts --allow-scripts-pending in the worktree's frontend/. Build succeeded after this.
  • Files modified: None (dev-time infrastructure only — node_modules/ is gitignored).

Known Stubs

None — no hardcoded data or placeholder content introduced. All admin routes resolve to the real view components from 09-02 and 09-03.

Threat Flags

None — no new network endpoints, API routes, or trust boundaries introduced. This plan is purely frontend routing and build configuration.

Threat mitigations from the plan's threat register:

  • T-09-04-01 (Elevation of Privilege): to.matched.some(r => r.meta.requiresAdmin) guard covers all /admin/* child routes — IMPLEMENTED. Backend get_current_admin dependency remains the authoritative second gate.
  • T-09-04-02 (Privilege Escalation via ?redirect=): D-08 puts role check FIRST; even ?redirect=/ for an admin routes through the D-09 guard — IMPLEMENTED.
  • T-09-04-03 (CSS purge of dynamic classes): Tailwind safelist covers sky (OneDrive) and amber (audit admin badge) — IMPLEMENTED. Build output confirms separate admin chunks exist.
  • T-09-04-04 (Redirect loop): isAdminRoute for /admin/* short-circuits D-09 so admins on admin routes are not redirected; auth routes have !to.meta.public guard — IMPLEMENTED.

Self-Check: PASSED

  • frontend/src/router/index.js — MODIFIED (nested /admin + corrected guard)
  • frontend/tailwind.config.js — MODIFIED (safelist added)
  • frontend/src/views/auth/LoginView.vue — MODIFIED (admin login redirect)
  • frontend/src/views/AdminView.vue — DELETED
  • frontend/src/components/admin/AdminUsersTab.vue — DELETED
  • frontend/src/components/admin/AdminQuotasTab.vue — DELETED
  • frontend/src/components/admin/AdminAiConfigTab.vue — DELETED
  • frontend/src/components/admin/AuditLogTab.vue — DELETED
  • Commit 5dbfb6c (Task 1) — FOUND
  • Commit bdd68b2 (Task 2) — FOUND
  • Commit e6467d1 (Task 3) — FOUND
  • npm run build succeeds (0 errors, 5 admin chunks in output) — PASSED
  • npm test — 137/137 tests passed — PASSED
  • No stale AdminView / AdminXxxTab imports remain — PASSED