# User Dashboard Restructure — Non-Admin View

## TL;DR

> **Quick Summary**: Restructure KentutAI/9Router dashboard so non-admin users see only essential items — dashboard summary (credit, token, plan), API key management, CLI Tools, and slim settings. Add auto API key creation on registration, per-user RTK/Caveman config, and email field.
> 
> **Deliverables**:
> - Role-based sidebar (non-admin sees minimal menu)
> - New dashboard summary page for non-admin (credit, token, plan)
> - Auto-created API key on registration
> - Per-user RTK/Caveman settings (not global)
> - Slim settings page (username, password, email, plan only)
> - Database migration (email field, creditRemaining, userSettings)
> - Plan auto-detection from creditLimit
> 
> **Estimated Effort**: Large
> **Parallel Execution**: YES - 4 waves
> **Critical Path**: DB Migration → API Routes → Frontend Sidebar + Dashboard → Integration

---

## Context

### Original Request
User wants to restructure the dashboard for non-admin users since the app is transitioning from self-hosted to server-to-client model. Non-admin users should only see: dashboard usage, remaining credit, token/plan info, API key management, and CLI Tools.

### Interview Summary
**Key Discussions**:
- **Plan system**: Auto-detected from creditLimit (<=5 = Free, >5 = Pro)
- **Credit calculation**: New field `creditRemaining` in rateLimits, tracked real-time
- **Email**: Optional field in users table, editable in Settings
- **Endpoint page**: Tetap tampil, per-user scoped (API key sendiri, rate limit sendiri)
- **RTK/Caveman**: Per-user setting, bukan global
- **CLI Tools**: Tetap tampil untuk non-admin (ralat dari user)
- **Usage page**: Redesign jadi dashboard summary khusus non-admin

**Research Findings**:
- Users table: id, username, password_hash, role, is_active, is_approved, created_at (NO email, NO plan)
- rateLimits table: creditLimit, tokenLimitMonthly, rtpMinute, rtpDay, maxConcurrent (NO creditRemaining)
- apiKeys table: linked to userId, NO auto-create on registration
- Sidebar: src/shared/components/Sidebar.js — single sidebar, admin check only for "Users" link
- Settings: src/app/(dashboard)/dashboard/profile/page.js — ~1000 lines, too complex for non-admin
- Register: src/app/api/auth/register/route.js — no post-register key creation
- Endpoint: src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.js — RTK/Caveman are global settings
- 53 test files exist with Vitest, no Playwright setup

### Self-Identified Gaps (Metis unavailable)
1. **Migration complexity**: Adding email to users + creditRemaining to rateLimits + new userSettings table for per-user config
2. **Auto-create API key on register**: Needs machineId — will generate server-side using existing machineId utility
3. **Per-user RTK/Caveman storage**: Need new userSettings table or extend rateLimits with config JSON
4. **Dashboard summary page**: New page vs. modified usage page — decided: modify usage page with role-based content
5. **Approval flow**: Non-admin users need admin approval before they can login — auto-created API key should work regardless of approval status for the key itself

---

## Work Objectives

### Core Objective
Restructure the non-admin user dashboard to show only essential items, auto-create API keys on registration, and add per-user RTK/Caveman settings.

### Concrete Deliverables
- Modified `src/shared/components/Sidebar.js` with role-based nav filtering
- New dashboard summary component for non-admin users
- Modified `src/app/api/auth/register/route.js` with auto API key creation
- New `userSettings` table in SQLite for per-user RTK/Caveman config
- New `email` column in `users` table
- New `creditRemaining` column in `rateLimits` table
- Slim settings page component for non-admin users
- Modified EndpointPageClient to scope per-user

### Definition of Done
- [ ] Non-admin user logs in → sees only: Dashboard, Usage, CLI Tools, Settings (slim), API Keys
- [ ] Admin user logs in → sees ALL menus (unchanged)
- [ ] New user registers → API key auto-created and visible immediately
- [ ] Non-admin toggles RTK/Caveman → only affects their own requests
- [ ] Non-admin opens Settings → sees only username, password, email, plan info
- [ ] Plan auto-detected from creditLimit and displayed on dashboard

### Must Have
- Role-based sidebar filtering for non-admin users
- Auto-create default API key on user registration
- Per-user RTK/Caveman settings storage and UI
- Email field (optional) in users table with edit capability
- creditRemaining field in rateLimits table
- Plan auto-detection logic (Free/Pro based on creditLimit)
- Non-admin dashboard summary (credit remaining, token usage, plan, API key preview)
- Admin view remains completely unchanged

### Must NOT Have (Guardrails)
- Do NOT modify admin sidebar or admin-facing pages
- Do NOT change the login/register flow UI (only add API key creation logic)
- Do NOT add automated unit tests (Agent QA only)
- Do NOT create new pages for admin (no scope creep)
- Do NOT modify existing RTK/Caveman backend logic (only add per-user override)
- Do NOT change the V1 API endpoints (chat completions, etc.)
- Do NOT add email verification flow (just optional field)
- Do NOT modify the approval workflow for non-admin registration

---

## Verification Strategy

> **ZERO HUMAN INTERVENTION** — ALL verification is agent-executed. No exceptions.

### Test Decision
- **Infrastructure exists**: YES (Vitest, 53 test files)
- **Automated tests**: NONE (Agent QA only per user decision)
- **Framework**: Vitest exists but not required for this task

### QA Policy
Every task MUST include agent-executed QA scenarios.
Evidence saved to `.omo/evidence/task-{N}-{scenario-slug}.{ext}`.

- **Frontend/UI**: Use Playwright — Navigate, interact, assert DOM, screenshot
- **API/Backend**: Use Bash (curl) — Send requests, assert status + response fields
- **Database**: Use Bash (sqlite3 / node REPL) — Query tables, verify schema

---

## Execution Strategy

### Parallel Execution Waves

```
Wave 1 (Start Immediately — Database & Schema):
├── Task 1: DB migration — email, creditRemaining, userSettings table [quick]
├── Task 2: Update usersRepo — email CRUD operations [quick]
├── Task 3: Update rateLimitsRepo — creditRemaining CRUD [quick]
└── Task 4: Create userSettingsRepo — per-user RTK/Caveman config [quick]

Wave 2 (After Wave 1 — Backend API):
├── Task 5: Auto-create API key on registration (depends: 1) [unspecified-high]
├── Task 6: Per-user settings API endpoints (depends: 4) [unspecified-high]
├── Task 7: Plan auto-detection logic (depends: 3) [quick]
└── Task 8: Update /api/auth/me to include plan + credit info (depends: 3, 7) [quick]

Wave 3 (After Wave 2 — Frontend UI):
├── Task 9: Sidebar role-based filtering (depends: 8) [unspecified-high]
├── Task 10: Dashboard summary for non-admin (depends: 8) [visual-engineering]
├── Task 11: Slim settings page for non-admin (depends: 8) [visual-engineering]
└── Task 12: Endpoint page — per-user API key scoping (depends: 5, 6) [unspecified-high]

Wave 4 (After Wave 3 — Integration & Polish):
├── Task 13: Integration testing — full non-admin flow [unspecified-high]
└── Task 14: Admin view verification — ensure nothing broken [quick]

Wave FINAL (After ALL tasks):
├── Task F1: Plan compliance audit (oracle)
├── Task F2: Code quality review (unspecified-high)
├── Task F3: Real manual QA (unspecified-high)
└── Task F4: Scope fidelity check (deep)
→ Present results → Get explicit user okay

Critical Path: Task 1 → Task 5 → Task 12 → Task 13 → F1-F4 → user okay
Parallel Speedup: ~60% faster than sequential
Max Concurrent: 4 (Waves 2 & 3)
```

### Dependency Matrix

| Task | Depends On | Blocks |
|------|-----------|--------|
| 1 | — | 2, 3, 4, 5 |
| 2 | 1 | 8 |
| 3 | 1 | 7, 8 |
| 4 | 1 | 6 |
| 5 | 1 | 12 |
| 6 | 4 | 12 |
| 7 | 3 | 8 |
| 8 | 3, 7 | 9, 10, 11 |
| 9 | 8 | 13 |
| 10 | 8 | 13 |
| 11 | 8 | 13 |
| 12 | 5, 6 | 13 |
| 13 | 9, 10, 11, 12 | F1-F4 |
| 14 | 13 | F1-F4 |

### Agent Dispatch Summary

- **Wave 1**: **4 tasks** — T1-T4 → `quick`
- **Wave 2**: **4 tasks** — T5 → `unspecified-high`, T6 → `unspecified-high`, T7 → `quick`, T8 → `quick`
- **Wave 3**: **4 tasks** — T9 → `unspecified-high`, T10 → `visual-engineering`, T11 → `visual-engineering`, T12 → `unspecified-high`
- **Wave 4**: **2 tasks** — T13 → `unspecified-high`, T14 → `quick`
- **FINAL**: **4 tasks** — F1 → `oracle`, F2 → `unspecified-high`, F3 → `unspecified-high`, F4 → `deep`

---

## TODOs

- [ ] 1. DB Migration — Add email, creditRemaining, userSettings schema

  **What to do**:
  - Create a new migration file in `src/lib/db/migrations/` (check existing migrations for naming convention)
  - Add `email TEXT` column to `users` table (nullable, optional)
  - Add `creditRemaining REAL DEFAULT 0` column to `rateLimits` table
  - Create new `userSettings` table:
    - `id TEXT PRIMARY KEY`
    - `userId TEXT UNIQUE REFERENCES users(id) ON DELETE CASCADE`
    - `rtkEnabled INTEGER DEFAULT 1`
    - `cavemanEnabled INTEGER DEFAULT 0`
    - `cavemanLevel TEXT DEFAULT 'full'`
    - `createdAt TEXT NOT NULL`
    - `updatedAt TEXT NOT NULL`
  - Update `src/lib/db/schema.js` TABLES definition to include new table and columns
  - Increment SCHEMA_VERSION

  **Must NOT do**:
  - Do NOT modify existing migration files
  - Do NOT change any existing column types or constraints
  - Do NOT drop any existing tables or columns

  **Recommended Agent Profile**:
  - **Category**: `quick`
    - Reason: Schema changes are straightforward ALTER TABLE / CREATE TABLE
  - **Skills**: []
    - No special skills needed for SQL migrations

  **Parallelization**:
  - **Can Run In Parallel**: NO (foundation task)
  - **Parallel Group**: Wave 1 (first task, unblocks others)
  - **Blocks**: Tasks 2, 3, 4, 5
  - **Blocked By**: None (can start immediately)

  **References**:

  **Pattern References**:
  - `src/lib/db/schema.js` — Current TABLES definition, SCHEMA_VERSION, buildCreateTableSql pattern
  - `src/lib/db/migrations/` — Existing migration files for naming convention and structure
  - `src/lib/db/migrate.js` — Migration runner to understand how migrations are applied

  **API/Type References**:
  - `src/lib/db/schema.js:TABLES.users` — Current users table schema (line 87-100)
  - `src/lib/db/schema.js:TABLES.rateLimits` — Current rateLimits table schema (line 101-116)

  **WHY Each Reference Matters**:
  - `schema.js`: The executor MUST understand the current TABLES structure to add new columns/table correctly
  - `migrations/`: Follow existing migration file naming and structure patterns
  - `migrate.js`: Understand how migrations are chained to ensure new migration runs after existing ones

  **Acceptance Criteria**:

  **QA Scenarios (MANDATORY):**

  ```
  Scenario: Migration applies successfully on fresh database
    Tool: Bash
    Preconditions: Fresh SQLite database (no existing data)
    Steps:
      1. Run `npm run test` or execute migration directly
      2. Query: `SELECT sql FROM sqlite_master WHERE name='users'` — verify email column exists
      3. Query: `SELECT sql FROM sqlite_master WHERE name='rateLimits'` — verify creditRemaining column exists
      4. Query: `SELECT sql FROM sqlite_master WHERE name='userSettings'` — verify table exists with all columns
    Expected Result: All three schema changes present in database
    Failure Indicators: Migration error, missing columns/tables
    Evidence: .omo/evidence/task-1-migration-applies.txt

  Scenario: Existing data preserved after migration
    Tool: Bash
    Preconditions: Database with existing users and rateLimits data
    Steps:
      1. Run migration
      2. Query: `SELECT * FROM users` — all existing users still present
      3. Query: `SELECT * FROM rateLimits` — all existing rate limits still present
    Expected Result: Zero data loss, new columns have default values
    Failure Indicators: Missing rows, NULL where default expected
    Evidence: .omo/evidence/task-1-data-preserved.txt
  ```

  **Commit**: YES
  - Message: `feat(db): add email, creditRemaining, userSettings schema migration`
  - Files: `src/lib/db/schema.js`, `src/lib/db/migrations/`, new migration file
  - Pre-commit: `npm run test`

- [ ] 2. Update usersRepo — Email CRUD Operations

  **What to do**:
  - Update `src/lib/db/repos/usersRepo.js`:
    - Update `rowToUser()` to include `email` field
    - Add `findByEmail(email)` function
    - Add `updateEmail(userId, email)` function
    - Update `createUser()` to accept optional email parameter
  - Ensure `findById()`, `listUsers()`, `findByUsername()` all return email in response

  **Must NOT do**:
  - Do NOT change existing function signatures in breaking ways
  - Do NOT modify the password hash logic
  - Do NOT add email validation beyond basic null/type check

  **Recommended Agent Profile**:
  - **Category**: `quick`
    - Reason: Simple CRUD additions to existing repo pattern
  - **Skills**: []

  **Parallelization**:
  - **Can Run In Parallel**: YES (with Tasks 3, 4)
  - **Parallel Group**: Wave 1 (after Task 1)
  - **Blocks**: Task 8
  - **Blocked By**: Task 1

  **References**:

  **Pattern References**:
  - `src/lib/db/repos/usersRepo.js` — Current user CRUD pattern (rowToUser, createUser, findById, etc.)
  - `src/lib/db/repos/apiKeysRepo.js` — Similar repo pattern for reference

  **API/Type References**:
  - `src/lib/db/schema.js:TABLES.users` — Column definitions including new email column

  **WHY Each Reference Matters**:
  - `usersRepo.js`: Must follow existing `rowToUser()` pattern and function naming conventions
  - `schema.js`: Must match the new email column name and type

  **Acceptance Criteria**:

  **QA Scenarios (MANDATORY):**

  ```
  Scenario: createUser with optional email
    Tool: Bash (node REPL)
    Preconditions: Fresh DB with migration applied
    Steps:
      1. Import createUser from usersRepo
      2. Call createUser("testuser", "hash123", "test@example.com")
      3. Call findById() on returned id
      4. Verify .email === "test@example.com"
      5. Call createUser("noemail", "hash456") — without email
      6. Verify .email === null or undefined
    Expected Result: User created with email, user created without email both work
    Evidence: .omo/evidence/task-2-create-user-email.txt

  Scenario: findByEmail returns correct user
    Tool: Bash (node REPL)
    Preconditions: User with email "test@example.com" exists
    Steps:
      1. Call findByEmail("test@example.com")
      2. Verify returns correct user object
      3. Call findByEmail("nonexistent@example.com")
      4. Verify returns null
    Expected Result: Finds user by email, returns null for missing
    Evidence: .omo/evidence/task-2-find-by-email.txt

  Scenario: updateEmail changes user email
    Tool: Bash (node REPL)
    Preconditions: User exists with email "old@example.com"
    Steps:
      1. Call updateEmail(userId, "new@example.com")
      2. Call findById(userId)
      3. Verify .email === "new@example.com"
    Expected Result: Email updated successfully
    Evidence: .omo/evidence/task-2-update-email.txt
  ```

  **Commit**: NO (groups with Task 1)

- [ ] 3. Update rateLimitsRepo — creditRemaining CRUD

  **What to do**:
  - Update `src/lib/db/repos/rateLimitRepo.js`:
    - Update all SELECT queries to include `creditRemaining`
    - Add `deductCredit(userId, amount)` — subtracts from creditRemaining
    - Add `resetCredit(userId)` — resets creditRemaining to creditLimit
    - Add `getCreditStatus(userId)` — returns { creditRemaining, creditLimit, percentUsed }
  - Update rate limit enforcement in `src/lib/auth/rateLimitMiddleware.js` to check creditRemaining before allowing requests

  **Must NOT do**:
  - Do NOT change existing rate limit logic (rtpMinute, rtpDay, etc.)
  - Do NOT break existing API key validation flow

  **Recommended Agent Profile**:
  - **Category**: `quick`
    - Reason: Adding columns to existing repo, similar pattern to existing CRUD
  - **Skills**: []

  **Parallelization**:
  - **Can Run In Parallel**: YES (with Tasks 2, 4)
  - **Parallel Group**: Wave 1 (after Task 1)
  - **Blocks**: Tasks 7, 8
  - **Blocked By**: Task 1

  **References**:

  **Pattern References**:
  - `src/lib/db/repos/rateLimitRepo.js` — Current rate limit CRUD and enforcement pattern
  - `src/lib/auth/rateLimitMiddleware.js` — Current rate limit enforcement middleware

  **API/Type References**:
  - `src/lib/db/schema.js:TABLES.rateLimits` — Column definitions including new creditRemaining

  **WHY Each Reference Matters**:
  - `rateLimitRepo.js`: Must follow existing function patterns for rate limits
  - `rateLimitMiddleware.js`: Must integrate creditRemaining check into existing enforcement flow

  **Acceptance Criteria**:

  **QA Scenarios (MANDATORY):**

  ```
  Scenario: creditRemaining initialized and deducted correctly
    Tool: Bash (node REPL)
    Preconditions: User with creditLimit=10, creditRemaining=10
    Steps:
      1. Call deductCredit(userId, 3.5)
      2. Call getCreditStatus(userId)
      3. Verify creditRemaining === 6.5
      4. Call deductCredit(userId, 7.0) — exceeds remaining
      5. Verify returns error or creditRemaining === 0
    Expected Result: Credit deducted correctly, cannot go negative
    Evidence: .omo/evidence/task-3-credit-deduct.txt

  Scenario: resetCredit restores to limit
    Tool: Bash (node REPL)
    Preconditions: User with creditRemaining=2.5, creditLimit=10
    Steps:
      1. Call resetCredit(userId)
      2. Call getCreditStatus(userId)
      3. Verify creditRemaining === 10
    Expected Result: Credit reset to limit value
    Evidence: .omo/evidence/task-3-credit-reset.txt
  ```

  **Commit**: NO (groups with Task 1)

- [ ] 4. Create userSettingsRepo — Per-User RTK/Caveman Config

  **What to do**:
  - Create `src/lib/db/repos/userSettingsRepo.js`:
    - `getByUserId(userId)` — returns user settings or default values
    - `upsert(userId, settings)` — creates or updates user settings
    - `updateRtk(userId, enabled)` — toggles RTK for specific user
    - `updateCaveman(userId, enabled, level)` — toggles Caveman for specific user
  - Default values: rtkEnabled=1, cavemanEnabled=0, cavemanLevel='full'

  **Must NOT do**:
  - Do NOT modify existing global settings in settingsRepo
  - Do NOT break existing global RTK/Caveman behavior

  **Recommended Agent Profile**:
  - **Category**: `quick`
    - Reason: New repo following existing repo patterns
  - **Skills**: []

  **Parallelization**:
  - **Can Run In Parallel**: YES (with Tasks 2, 3)
  - **Parallel Group**: Wave 1 (after Task 1)
  - **Blocks**: Task 6
  - **Blocked By**: Task 1

  **References**:

  **Pattern References**:
  - `src/lib/db/repos/settingsRepo.js` — Current global settings pattern (RTK/Caveman stored here)
  - `src/lib/db/repos/rateLimitRepo.js` — Per-user repo pattern with upsert logic

  **API/Type References**:
  - `src/lib/db/schema.js:TABLES.userSettings` — New table schema from Task 1

  **WHY Each Reference Matters**:
  - `settingsRepo.js`: Understand how global RTK/Caveman is currently stored to avoid conflicts
  - `rateLimitRepo.js`: Follow per-user CRUD pattern

  **Acceptance Criteria**:

  **QA Scenarios (MANDATORY):**

  ```
  Scenario: getByUserId returns defaults for new user
    Tool: Bash (node REPL)
    Preconditions: User exists but no userSettings row
    Steps:
      1. Call getByUserId(userId)
      2. Verify rtkEnabled === true (default)
      3. Verify cavemanEnabled === false (default)
      4. Verify cavemanLevel === 'full' (default)
    Expected Result: Default settings returned for user without explicit settings
    Evidence: .omo/evidence/task-4-default-settings.txt

  Scenario: upsert creates and updates settings
    Tool: Bash (node REPL)
    Preconditions: User exists
    Steps:
      1. Call upsert(userId, { rtkEnabled: false, cavemanEnabled: true, cavemanLevel: 'ultra' })
      2. Call getByUserId(userId)
      3. Verify rtkEnabled === false, cavemanEnabled === true, cavemanLevel === 'ultra'
      4. Call updateRtk(userId, true)
      5. Verify rtkEnabled === true, other fields unchanged
    Expected Result: Settings created and updated correctly
    Evidence: .omo/evidence/task-4-upsert-settings.txt
  ```

  **Commit**: NO (groups with Task 1)

- [ ] 5. Auto-Create API Key on Registration

  **What to do**:
  - Modify `src/app/api/auth/register/route.js`:
    - After successful user creation, auto-create 1 default API key
    - Use `createApiKey()` from `apiKeysRepo` with userId
    - For machineId: use `getConsistentMachineId()` from `src/shared/utils/machineId` or generate a default
    - Default key name: "Default" or "My API Key"
    - The key should be created even if user is not yet approved (key exists, user just can't login yet)
  - Update register response to include the created API key info (key string, name)

  **Must NOT do**:
  - Do NOT change the registration form UI
  - Do NOT change the admin approval workflow
  - Do NOT expose the full API key in the response (show only the key string once, for the user to copy)

  **Recommended Agent Profile**:
  - **Category**: `unspecified-high`
    - Reason: Involves understanding registration flow + API key generation logic
  - **Skills**: []

  **Parallelization**:
  - **Can Run In Parallel**: YES (with Tasks 6, 7, 8)
  - **Parallel Group**: Wave 2
  - **Blocks**: Task 12
  - **Blocked By**: Task 1

  **References**:

  **Pattern References**:
  - `src/app/api/auth/register/route.js` — Current registration flow (createUser, no key creation)
  - `src/lib/db/repos/apiKeysRepo.js:createApiKey()` — API key creation pattern (requires userId, machineId)
  - `src/shared/utils/machineId` — Machine ID generation for API keys
  - `src/app/api/keys/route.js` — Existing API key CRUD endpoints for reference

  **API/Type References**:
  - `src/lib/db/repos/apiKeysRepo.js:createApiKey(name, userId, machineId, comment)` — Function signature

  **WHY Each Reference Matters**:
  - `register/route.js`: Must understand current flow to insert key creation at the right point
  - `apiKeysRepo.js`: Must use existing createApiKey function correctly
  - `machineId`: Need to understand how machineId is generated for the key

  **Acceptance Criteria**:

  **QA Scenarios (MANDATORY):**

  ```
  Scenario: New user registration creates default API key
    Tool: Bash (curl)
    Preconditions: Server running, no user "newuser123" exists
    Steps:
      1. POST /api/auth/register with { username: "newuser123", password: "Test1234!" }
      2. Verify response includes API key info (key string, name)
      3. Query DB: SELECT * FROM apiKeys WHERE userId = <newUserId>
      4. Verify 1 key exists with name="Default" and isActive=true
    Expected Result: User created AND default API key auto-created
    Failure Indicators: No key in DB, key not linked to user
    Evidence: .omo/evidence/task-5-auto-create-key.txt

  Scenario: Registration failure does not create orphan key
    Tool: Bash (curl)
    Preconditions: User "existinguser" already exists
    Steps:
      1. POST /api/auth/register with { username: "existinguser", password: "Test1234!" }
      2. Verify response is 409 Conflict
      3. Query DB: SELECT COUNT(*) FROM apiKeys WHERE name='Default' — count unchanged
    Expected Result: No new API key created on failed registration
    Evidence: .omo/evidence/task-5-no-orphan-key.txt
  ```

  **Commit**: YES
  - Message: `feat(auth): auto-create default API key on user registration`
  - Files: `src/app/api/auth/register/route.js`
  - Pre-commit: `npm run test`

- [ ] 6. Per-User Settings API Endpoints

  **What to do**:
  - Create new API route `src/app/api/users/me/settings/route.js`:
    - `GET /api/users/me/settings` — returns current user's RTK/Caveman settings
    - `PATCH /api/users/me/settings` — updates current user's RTK/Caveman settings
  - These endpoints are for non-admin users to manage their own settings
  - Read userId from JWT token (via `verifyToken` from auth)
  - Use `userSettingsRepo` for CRUD operations

  **Must NOT do**:
  - Do NOT modify existing global settings endpoints (`/api/settings`)
  - Do NOT allow users to modify other users' settings
  - Do NOT expose admin-level settings (requireLogin, tunnel, etc.)

  **Recommended Agent Profile**:
  - **Category**: `unspecified-high`
    - Reason: New API route with JWT auth integration
  - **Skills**: []

  **Parallelization**:
  - **Can Run In Parallel**: YES (with Tasks 5, 7, 8)
  - **Parallel Group**: Wave 2
  - **Blocks**: Task 12
  - **Blocked By**: Task 4

  **References**:

  **Pattern References**:
  - `src/app/api/auth/me/route.js` — JWT token verification pattern (how to get userId from cookie)
  - `src/app/api/settings/route.js` — Existing settings PATCH pattern
  - `src/app/api/keys/route.js` — Per-user CRUD pattern for API keys

  **API/Type References**:
  - `src/lib/auth/jwt.js:verifyToken()` — Returns { userId, username, role }
  - `src/lib/db/repos/userSettingsRepo.js` — CRUD functions from Task 4

  **WHY Each Reference Matters**:
  - `auth/me/route.js`: Must follow same JWT verification pattern
  - `settings/route.js`: Follow existing PATCH request handling pattern
  - `keys/route.js`: Follow per-user scoping pattern (filter by userId from JWT)

  **Acceptance Criteria**:

  **QA Scenarios (MANDATORY):**

  ```
  Scenario: User can read their own settings
    Tool: Bash (curl)
    Preconditions: Authenticated non-admin user
    Steps:
      1. GET /api/users/me/settings with auth cookie
      2. Verify response includes rtkEnabled, cavemanEnabled, cavemanLevel
      3. Verify values match defaults (rtkEnabled=true, cavemanEnabled=false)
    Expected Result: User settings returned correctly
    Evidence: .omo/evidence/task-6-read-settings.txt

  Scenario: User can update their RTK/Caveman settings
    Tool: Bash (curl)
    Preconditions: Authenticated non-admin user
    Steps:
      1. PATCH /api/users/me/settings with { rtkEnabled: false, cavemanEnabled: true, cavemanLevel: 'ultra' }
      2. Verify response status 200
      3. GET /api/users/me/settings — verify new values
      4. Query DB: SELECT * FROM userSettings WHERE userId = <userId> — verify persisted
    Expected Result: Settings updated and persisted
    Evidence: .omo/evidence/task-6-update-settings.txt

  Scenario: User cannot access other users' settings
    Tool: Bash (curl)
    Preconditions: Two authenticated users (userA, userB)
    Steps:
      1. As userA, PATCH /api/users/me/settings with { cavemanEnabled: true }
      2. As userB, GET /api/users/me/settings
      3. Verify userB's settings unchanged (cavemanEnabled=false default)
    Expected Result: Settings scoped per user, no cross-user access
    Evidence: .omo/evidence/task-6-no-cross-user.txt
  ```

  **Commit**: YES
  - Message: `feat(api): add per-user settings endpoints for RTK/Caveman`
  - Files: `src/app/api/users/me/settings/route.js`
  - Pre-commit: `npm run test`

- [ ] 7. Plan Auto-Detection Logic

  **What to do**:
  - Add utility function `detectPlan(creditLimit)` in `src/lib/db/repos/rateLimitRepo.js` or `src/shared/utils/plan.js`:
    - creditLimit <= 5 → return "Free"
    - creditLimit > 5 → return "Pro"
  - Add `getPlanForUser(userId)` function that reads creditLimit from rateLimits and returns plan name
  - Plan detection should be a pure function, easy to test

  **Must NOT do**:
  - Do NOT store plan as a separate field in the database
  - Do NOT add complex plan tier logic (keep it simple: Free/Pro only)
  - Do NOT modify creditLimit values

  **Recommended Agent Profile**:
  - **Category**: `quick`
    - Reason: Simple utility function with clear logic
  - **Skills**: []

  **Parallelization**:
  - **Can Run In Parallel**: YES (with Tasks 5, 6, 8)
  - **Parallel Group**: Wave 2
  - **Blocks**: Task 8
  - **Blocked By**: Task 3

  **References**:

  **Pattern References**:
  - `src/lib/db/repos/rateLimitRepo.js` — Current rate limit functions, where to add getPlanForUser

  **API/Type References**:
  - `src/lib/db/schema.js:TABLES.rateLimits:creditLimit` — The field used for plan detection

  **WHY Each Reference Matters**:
  - `rateLimitRepo.js`: The plan logic should live alongside rate limit functions since it derives from creditLimit

  **Acceptance Criteria**:

  **QA Scenarios (MANDATORY):**

  ```
  Scenario: Plan detection returns correct tier
    Tool: Bash (node REPL)
    Preconditions: None (pure function test)
    Steps:
      1. Call detectPlan(5.0) → verify returns "Free"
      2. Call detectPlan(3.0) → verify returns "Free"
      3. Call detectPlan(10.0) → verify returns "Pro"
      4. Call detectPlan(100.0) → verify returns "Pro"
      5. Call detectPlan(0) → verify returns "Free"
    Expected Result: All creditLimit values correctly mapped to plan tiers
    Evidence: .omo/evidence/task-7-plan-detection.txt

  Scenario: getPlanForUser returns correct plan from DB
    Tool: Bash (node REPL)
    Preconditions: User with creditLimit=10 in rateLimits
    Steps:
      1. Call getPlanForUser(userId)
      2. Verify returns "Pro"
    Expected Result: Plan correctly derived from user's creditLimit
    Evidence: .omo/evidence/task-7-plan-for-user.txt
  ```

  **Commit**: NO (groups with Task 8)

- [ ] 8. Update /api/auth/me to Include Plan + Credit Info

  **What to do**:
  - Modify `src/app/api/auth/me/route.js`:
    - After verifying JWT, fetch user's rateLimits and userSettings
    - Enrich response with:
      - `plan`: "Free" or "Pro" (from getPlanForUser)
      - `creditRemaining`: from rateLimits
      - `creditLimit`: from rateLimits
      - `tokenLimitMonthly`: from rateLimits
      - `rtkEnabled`: from userSettings (or default)
      - `cavemanEnabled`: from userSettings (or default)
      - `cavemanLevel`: from userSettings (or default)
    - Keep existing fields: id, username, role

  **Must NOT do**:
  - Do NOT change the response structure for unauthenticated users
  - Do NOT expose sensitive data (password hash, etc.)

  **Recommended Agent Profile**:
  - **Category**: `quick`
    - Reason: Adding fields to existing API response
  - **Skills**: []

  **Parallelization**:
  - **Can Run In Parallel**: YES (with Tasks 5, 6, 7)
  - **Parallel Group**: Wave 2
  - **Blocks**: Tasks 9, 10, 11
  - **Blocked By**: Tasks 3, 7

  **References**:

  **Pattern References**:
  - `src/app/api/auth/me/route.js` — Current /auth/me response structure
  - `src/lib/db/repos/rateLimitRepo.js` — How to fetch rate limits for a user
  - `src/lib/db/repos/userSettingsRepo.js` — How to fetch user settings

  **API/Type References**:
  - `src/lib/auth/jwt.js:verifyToken()` — Returns { userId, username, role }
  - `src/lib/db/repos/rateLimitRepo.js` — getCreditStatus, getPlanForUser from Task 7

  **WHY Each Reference Matters**:
  - `auth/me/route.js`: Must understand current response to add new fields without breaking existing consumers
  - `rateLimitRepo.js`: Need to call getCreditStatus and getPlanForUser
  - `userSettingsRepo.js`: Need to call getByUserId for RTK/Caveman settings

  **Acceptance Criteria**:

  **QA Scenarios (MANDATORY):**

  ```
  Scenario: /api/auth/me returns enriched user data
    Tool: Bash (curl)
    Preconditions: Authenticated user with creditLimit=10, creditRemaining=8.5
    Steps:
      1. GET /api/auth/me with auth cookie
      2. Verify response includes: id, username, role, plan, creditRemaining, creditLimit, rtkEnabled, cavemanEnabled, cavemanLevel
      3. Verify plan === "Pro" (creditLimit=10 > 5)
      4. Verify creditRemaining === 8.5
    Expected Result: All enriched fields present and correct
    Failure Indicators: Missing fields, incorrect values
    Evidence: .omo/evidence/task-8-enriched-me.txt

  Scenario: /api/auth/me returns defaults for user without rateLimits
    Tool: Bash (curl)
    Preconditions: Authenticated user WITHOUT rateLimits row
    Steps:
      1. GET /api/auth/me with auth cookie
      2. Verify plan === "Free" (default)
      3. Verify creditRemaining === 0 or default
    Expected Result: Graceful defaults when rateLimits missing
    Evidence: .omo/evidence/task-8-default-me.txt
  ```

  **Commit**: YES
  - Message: `feat(auth): enrich /api/auth/me with plan, credit, and per-user settings`
  - Files: `src/app/api/auth/me/route.js`
  - Pre-commit: `npm run test`

- [ ] 9. Sidebar — Role-Based Navigation Filtering

  **What to do**:
  - Modify `src/shared/components/Sidebar.js`:
    - Fetch user data from `/api/auth/me` (already done in existing useEffect)
    - When `user.role !== "admin"`, hide these nav items:
      - ❌ Providers (navItems)
      - ❌ Combos (navItems)
      - ❌ Quota Tracker (navItems)
      - ❌ MITM (navItems)
      - ❌ Media Providers (system section)
      - ❌ Proxy Pools (system section)
      - ❌ Skills (system section)
      - ❌ Console Log (debugItems)
      - ❌ Translator (debugItems)
      - ❌ Shutdown button
    - Keep visible for non-admin:
      - ✅ Endpoint (navItems)
      - ✅ Usage (navItems)
      - ✅ CLI Tools (navItems)
      - ✅ Settings (system section)
    - The Users link already has admin-only check (keep as-is)

  **Must NOT do**:
  - Do NOT change admin sidebar at all
  - Do NOT remove the navItems array (just filter it for non-admin)
  - Do NOT change mobile sidebar behavior

  **Recommended Agent Profile**:
  - **Category**: `unspecified-high`
    - Reason: UI component modification with conditional rendering logic
  - **Skills**: []

  **Parallelization**:
  - **Can Run In Parallel**: YES (with Tasks 10, 11, 12)
  - **Parallel Group**: Wave 3
  - **Blocks**: Task 13
  - **Blocked By**: Task 8

  **References**:

  **Pattern References**:
  - `src/shared/components/Sidebar.js` — Current sidebar with navItems, debugItems, systemItems arrays
  - `src/shared/components/Sidebar.js:206` — Existing admin-only check pattern (`user?.role === "admin"`)

  **API/Type References**:
  - `/api/auth/me` response: `{ authenticated, user: { id, username, role } }`

  **WHY Each Reference Matters**:
  - `Sidebar.js`: Must understand the full component structure to filter correctly without breaking layout
  - Line 206: Follow the existing admin-only pattern for consistency

  **Acceptance Criteria**:

  **QA Scenarios (MANDATORY):**

  ```
  Scenario: Non-admin user sees filtered sidebar
    Tool: Playwright
    Preconditions: Logged in as non-admin user
    Steps:
      1. Navigate to /dashboard
      2. Assert: Endpoint link visible
      3. Assert: Usage link visible
      4. Assert: CLI Tools link visible
      5. Assert: Settings link visible
      6. Assert: Providers link NOT visible
      7. Assert: Combos link NOT visible
      8. Assert: Quota Tracker link NOT visible
      9. Assert: MITM link NOT visible
      10. Assert: Media Providers NOT visible
      11. Assert: Skills NOT visible
      12. Assert: Console Log NOT visible
      13. Assert: Users link NOT visible
      14. Assert: Shutdown button NOT visible
    Expected Result: Only allowed items visible for non-admin
    Failure Indicators: Any hidden item still visible, any visible item hidden
    Evidence: .omo/evidence/task-9-non-admin-sidebar.png

  Scenario: Admin user sees full sidebar (unchanged)
    Tool: Playwright
    Preconditions: Logged in as admin user
    Steps:
      1. Navigate to /dashboard
      2. Assert: ALL nav items visible (Endpoint, Providers, Combos, Usage, Quota, MITM, CLI Tools)
      3. Assert: Users link visible
      4. Assert: System section visible (Media Providers, Skills)
      5. Assert: Debug section visible (Console Log)
      6. Assert: Shutdown button visible
    Expected Result: Admin sidebar completely unchanged
    Failure Indicators: Any missing item that was previously visible
    Evidence: .omo/evidence/task-9-admin-sidebar.png
  ```

  **Commit**: NO (groups with Task 10)

- [ ] 10. Dashboard Summary Page for Non-Admin

  **What to do**:
  - Modify or create a new dashboard component for non-admin users
  - When non-admin user visits `/dashboard`, show a summary page with:
    - **Credit Card**: Credit Remaining / Credit Limit (with progress bar)
    - **Plan Card**: Auto-detected plan (Free/Pro) with badge
    - **Token Usage Card**: Current month token usage vs tokenLimitMonthly
    - **API Key Preview**: Show the user's default API key (masked, with copy button)
    - **Quick Actions**: Link to full Usage page, CLI Tools, Settings
  - Data source: `/api/auth/me` response (plan, creditRemaining, creditLimit, tokenLimitMonthly)
  - For API key preview: fetch from `/api/keys` (already scoped per-user)
  - Use existing Card, Badge, Button components from shared components
  - Admin users continue to see the Endpoint page as their dashboard (no change)

  **Must NOT do**:
  - Do NOT modify the admin dashboard/endpoint page
  - Do NOT add complex charts (keep it simple cards)
  - Do NOT duplicate the full usage page content

  **Recommended Agent Profile**:
  - **Category**: `visual-engineering`
    - Reason: Dashboard UI with cards, progress bars, layout design
  - **Skills**: []

  **Parallelization**:
  - **Can Run In Parallel**: YES (with Tasks 9, 11, 12)
  - **Parallel Group**: Wave 3
  - **Blocks**: Task 13
  - **Blocked By**: Task 8

  **References**:

  **Pattern References**:
  - `src/app/(dashboard)/dashboard/page.js` — Current dashboard page (renders EndpointPageClient)
  - `src/app/(dashboard)/dashboard/usage/page.js` — Usage page structure for reference
  - `src/shared/components/UsageStats.js` — Usage stats component for reference
  - `src/shared/components/Card.js` — Card component for layout

  **API/Type References**:
  - `/api/auth/me` response: `{ plan, creditRemaining, creditLimit, tokenLimitMonthly, rtkEnabled, cavemanEnabled }`
  - `/api/keys` response: `{ keys: [{ id, key, name, isActive, createdAt }] }`

  **WHY Each Reference Matters**:
  - `dashboard/page.js`: Must understand how dashboard currently renders to add role-based routing
  - `Card.js`: Must use existing Card component for consistent UI
  - `UsageStats.js`: Reference for how usage data is displayed

  **Acceptance Criteria**:

  **QA Scenarios (MANDATORY):**

  ```
  Scenario: Non-admin user sees dashboard summary
    Tool: Playwright
    Preconditions: Logged in as non-admin user with creditLimit=10, creditRemaining=7.5
    Steps:
      1. Navigate to /dashboard
      2. Assert: "Credit" card visible with "7.5 / 10" or similar
      3. Assert: "Plan" card visible showing "Pro" badge
      4. Assert: API key section visible with masked key
      5. Assert: Copy button for API key visible
      6. Assert: Quick links to Usage, CLI Tools visible
    Expected Result: Dashboard summary page renders correctly for non-admin
    Failure Indicators: Missing cards, wrong plan, no API key shown
    Evidence: .omo/evidence/task-10-non-admin-dashboard.png

  Scenario: Admin user still sees Endpoint page
    Tool: Playwright
    Preconditions: Logged in as admin user
    Steps:
      1. Navigate to /dashboard
      2. Assert: Endpoint page content visible (API URL, tunnel settings, etc.)
      3. Assert: NOT the dashboard summary cards
    Expected Result: Admin dashboard unchanged
    Evidence: .omo/evidence/task-10-admin-dashboard.png
  ```

  **Commit**: YES
  - Message: `feat(ui): add dashboard summary page for non-admin users`
  - Files: `src/app/(dashboard)/dashboard/page.js`, new component files
  - Pre-commit: `npm run build`

- [ ] 11. Slim Settings Page for Non-Admin

  **What to do**:
  - Modify `src/app/(dashboard)/dashboard/profile/page.js`:
    - When user.role !== "admin", show only:
      - ✅ Username (read-only display)
      - ✅ Email (editable input)
      - ✅ Password change form
      - ✅ Plan info (read-only, auto-detected from creditLimit)
      - ✅ Theme toggle (light/dark/system)
    - Hide for non-admin:
      - ❌ Local Mode / Database section
      - ❌ Security (requireLogin toggle)
      - ❌ OIDC settings
      - ❌ Routing Strategy
      - ❌ Network (outbound proxy)
      - ❌ Observability
      - ❌ App Info
  - Add email update functionality (PATCH /api/settings with email field)
  - Plan display: show "Free" or "Pro" based on creditLimit (read-only)

  **Must NOT do**:
  - Do NOT modify admin settings view
  - Do NOT remove the existing admin settings sections
  - Do NOT add email verification flow

  **Recommended Agent Profile**:
  - **Category**: `visual-engineering`
    - Reason: UI component modification with conditional rendering and form design
  - **Skills**: []

  **Parallelization**:
  - **Can Run In Parallel**: YES (with Tasks 9, 10, 12)
  - **Parallel Group**: Wave 3
  - **Blocks**: Task 13
  - **Blocked By**: Task 8

  **References**:

  **Pattern References**:
  - `src/app/(dashboard)/dashboard/profile/page.js` — Current settings page (~1000 lines, complex)
  - `src/shared/components/Card.js` — Card component for section layout
  - `src/shared/components/Input.js` — Input component for form fields

  **API/Type References**:
  - `GET /api/settings` — Returns all settings including new email field
  - `PATCH /api/settings` — Updates settings (existing pattern)
  - `PATCH /api/settings` with `{ email: "new@example.com" }` — New email update

  **WHY Each Reference Matters**:
  - `profile/page.js`: Must understand the full page structure to add role-based conditional rendering
  - `Input.js`: Must use existing Input component for consistent form design

  **Acceptance Criteria**:

  **QA Scenarios (MANDATORY):**

  ```
  Scenario: Non-admin sees slim settings
    Tool: Playwright
    Preconditions: Logged in as non-admin user
    Steps:
      1. Navigate to /dashboard/profile
      2. Assert: Username displayed (read-only)
      3. Assert: Email input visible and editable
      4. Assert: Password change form visible
      5. Assert: Plan info visible (e.g., "Pro" or "Free")
      6. Assert: Theme toggle visible
      7. Assert: NO "Local Mode" section
      8. Assert: NO "Security" section (requireLogin)
      9. Assert: NO "OIDC" section
      10. Assert: NO "Routing Strategy" section
      11. Assert: NO "Network" section
      12. Assert: NO "Observability" section
    Expected Result: Only slim settings visible for non-admin
    Failure Indicators: Any hidden section still visible
    Evidence: .omo/evidence/task-11-slim-settings.png

  Scenario: Admin sees full settings (unchanged)
    Tool: Playwright
    Preconditions: Logged in as admin user
    Steps:
      1. Navigate to /dashboard/profile
      2. Assert: ALL sections visible (Local Mode, Security, OIDC, Routing, Network, Observability)
    Expected Result: Admin settings page unchanged
    Evidence: .omo/evidence/task-11-admin-settings.png

  Scenario: Non-admin can update email
    Tool: Playwright
    Preconditions: Logged in as non-admin user
    Steps:
      1. Navigate to /dashboard/profile
      2. Clear email input, type "newemail@example.com"
      3. Click save/update button
      4. Verify success message appears
      5. Reload page
      6. Verify email field shows "newemail@example.com"
    Expected Result: Email updated and persisted
    Evidence: .omo/evidence/task-11-update-email.png
  ```

  **Commit**: NO (groups with Task 9)

- [ ] 12. Endpoint Page — Per-User API Key Scoping

  **What to do**:
  - Modify `src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.js`:
    - When user.role !== "admin":
      - Show only the user's own API keys (fetch from `/api/keys` which is already user-scoped)
      - Show Endpoint URL (read-only)
      - Show per-user RTK/Caveman toggles (from `/api/users/me/settings`)
      - Hide: tunnel settings, Tailscale, requireApiKey toggle, requireLogin toggle
      - Hide: global RTK/Caveman section (replace with per-user version)
    - When user.role === "admin": no changes (full page as-is)
  - Add new section for per-user RTK/Caveman settings in the non-admin view
  - The per-user RTK/Caveman section should:
    - Toggle RTK on/off (PATCH /api/users/me/settings)
    - Toggle Caveman on/off + select level (PATCH /api/users/me/settings)

  **Must NOT do**:
  - Do NOT modify admin endpoint page view
  - Do NOT change the global RTK/Caveman logic (only add per-user override)
  - Do NOT modify the V1 API endpoints

  **Recommended Agent Profile**:
  - **Category**: `unspecified-high`
    - Reason: Complex existing component with conditional rendering based on role
  - **Skills**: []

  **Parallelization**:
  - **Can Run In Parallel**: YES (with Tasks 9, 10, 11)
  - **Parallel Group**: Wave 3
  - **Blocks**: Task 13
  - **Blocked By**: Tasks 5, 6

  **References**:

  **Pattern References**:
  - `src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.js` — Current endpoint page (~1500 lines, complex)
  - `src/shared/components/Toggle.js` — Toggle component for RTK/Caveman
  - `src/shared/components/SegmentedControl.js` — For Caveman level selection

  **API/Type References**:
  - `GET /api/keys` — Returns user's own API keys (already scoped)
  - `GET /api/users/me/settings` — Returns user's RTK/Caveman settings
  - `PATCH /api/users/me/settings` — Updates user's RTK/Caveman settings

  **WHY Each Reference Matters**:
  - `EndpointPageClient.js`: Must understand the full component to add role-based conditional sections
  - `Toggle.js`: Must use existing Toggle component for consistent UI

  **Acceptance Criteria**:

  **QA Scenarios (MANDATORY):**

  ```
  Scenario: Non-admin sees scoped endpoint page
    Tool: Playwright
    Preconditions: Logged in as non-admin user with 1 API key
    Steps:
      1. Navigate to /dashboard/endpoint
      2. Assert: Endpoint URL visible (read-only)
      3. Assert: User's API key visible (masked)
      4. Assert: RTK toggle visible (per-user)
      5. Assert: Caveman toggle + level visible (per-user)
      6. Assert: NO tunnel settings
      7. Assert: NO Tailscale settings
      8. Assert: NO requireApiKey toggle
      9. Assert: NO global RTK/Caveman section
    Expected Result: Per-user scoped endpoint page
    Failure Indicators: Global settings visible, tunnel settings shown
    Evidence: .omo/evidence/task-12-non-admin-endpoint.png

  Scenario: Admin sees full endpoint page (unchanged)
    Tool: Playwright
    Preconditions: Logged in as admin user
    Steps:
      1. Navigate to /dashboard/endpoint
      2. Assert: ALL sections visible (tunnel, Tailscale, global RTK/Caveman, requireApiKey)
    Expected Result: Admin endpoint page unchanged
    Evidence: .omo/evidence/task-12-admin-endpoint.png

  Scenario: Non-admin toggles per-user RTK
    Tool: Playwright
    Preconditions: Logged in as non-admin user, RTK enabled by default
    Steps:
      1. Navigate to /dashboard/endpoint
      2. Click RTK toggle to disable
      3. Verify toggle state changes
      4. Reload page
      5. Verify RTK toggle still disabled (persisted)
      6. As another user, verify their RTK still enabled (not affected)
    Expected Result: Per-user RTK setting works independently
    Evidence: .omo/evidence/task-12-toggle-rtk.png
  ```

  **Commit**: YES
  - Message: `feat(ui): scope endpoint page per-user with RTK/Caveman settings`
  - Files: `src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.js`
  - Pre-commit: `npm run build`

- [ ] 13. Integration Testing — Full Non-Admin Flow

  **What to do**:
  - Execute end-to-end flow as a non-admin user:
    1. Register new user → verify API key auto-created
    2. Login as non-admin → verify sidebar filtered
    3. Visit /dashboard → verify summary page (credit, plan, API key)
    4. Visit /dashboard/usage → verify usage page works
    5. Visit /dashboard/cli-tools → verify CLI tools accessible
    6. Visit /dashboard/endpoint → verify per-user scoping
    7. Visit /dashboard/profile → verify slim settings
    8. Toggle RTK/Caveman → verify per-user persistence
    9. Update email → verify persistence
    10. Visit /dashboard/providers → should redirect or show 403
    11. Visit /dashboard/combos → should redirect or show 403
  - Fix any integration issues found

  **Must NOT do**:
  - Do NOT modify admin flow
  - Do NOT add automated tests

  **Recommended Agent Profile**:
  - **Category**: `unspecified-high`
    - Reason: End-to-end integration testing across multiple components
  - **Skills**: [`playwright`]
    - `playwright`: Browser-based UI testing for full flow verification

  **Parallelization**:
  - **Can Run In Parallel**: NO (depends on all previous tasks)
  - **Parallel Group**: Wave 4
  - **Blocks**: F1-F4
  - **Blocked By**: Tasks 9, 10, 11, 12

  **References**:

  **Pattern References**:
  - `src/app/(dashboard)/dashboard/` — All dashboard pages that need integration testing
  - `src/shared/components/Sidebar.js` — Sidebar filtering

  **WHY Each Reference Matters**:
  - All dashboard pages: Must verify the full non-admin flow works end-to-end

  **Acceptance Criteria**:

  **QA Scenarios (MANDATORY):**

  ```
  Scenario: Full non-admin registration-to-dashboard flow
    Tool: Playwright
    Preconditions: Server running, no user "integrationtest" exists
    Steps:
      1. Navigate to /register
      2. Register user "integrationtest" with password "Test1234!"
      3. Wait for admin approval (or skip if auto-approved)
      4. Login as "integrationtest"
      5. Assert: Sidebar shows only Dashboard, Usage, CLI Tools, Settings
      6. Navigate to /dashboard
      7. Assert: Dashboard summary visible with credit, plan, API key
      8. Navigate to /dashboard/usage
      9. Assert: Usage page loads correctly
      10. Navigate to /dashboard/endpoint
      11. Assert: Per-user API key visible, RTK/Caveman toggles visible
      12. Navigate to /dashboard/profile
      13. Assert: Slim settings (username, email, password, plan)
    Expected Result: Complete non-admin flow works
    Failure Indicators: Any page fails to load, wrong items visible
    Evidence: .omo/evidence/task-13-full-flow.png

  Scenario: Non-admin cannot access hidden pages
    Tool: Playwright
    Preconditions: Logged in as non-admin user
    Steps:
      1. Navigate to /dashboard/providers
      2. Assert: Redirect to /dashboard or 403 error
      3. Navigate to /dashboard/combos
      4. Assert: Redirect to /dashboard or 403 error
      5. Navigate to /dashboard/quota
      6. Assert: Redirect to /dashboard or 403 error
    Expected Result: Hidden pages not accessible
    Evidence: .omo/evidence/task-13-hidden-pages.png
  ```

  **Commit**: YES
  - Message: `fix: integration fixes for non-admin dashboard flow`
  - Files: various (based on issues found)
  - Pre-commit: `npm run build`

- [ ] 14. Admin View Verification — Ensure Nothing Broken

  **What to do**:
  - Execute full admin flow to verify no regressions:
    1. Login as admin
    2. Visit all dashboard pages — verify all visible and functional
    3. Verify sidebar shows ALL items
    4. Verify settings page shows ALL sections
    5. Verify endpoint page shows ALL settings (tunnel, Tailscale, global RTK/Caveman)
    6. Verify Users management page still works
  - Fix any regressions found

  **Must NOT do**:
  - Do NOT modify non-admin flow
  - Do NOT add new features

  **Recommended Agent Profile**:
  - **Category**: `quick`
    - Reason: Verification task, quick scan of admin pages
  - **Skills**: [`playwright`]
    - `playwright`: Browser verification of admin pages

  **Parallelization**:
  - **Can Run In Parallel**: NO (sequential after Task 13)
  - **Parallel Group**: Wave 4
  - **Blocks**: F1-F4
  - **Blocked By**: Task 13

  **References**:

  **Pattern References**:
  - `src/app/(dashboard)/dashboard/` — All dashboard pages
  - `src/shared/components/Sidebar.js` — Full admin sidebar

  **WHY Each Reference Matters**:
  - Must verify all admin pages still work after our changes

  **Acceptance Criteria**:

  **QA Scenarios (MANDATORY):**

  ```
  Scenario: Admin dashboard fully functional
    Tool: Playwright
    Preconditions: Logged in as admin user
    Steps:
      1. Navigate to /dashboard
      2. Assert: Endpoint page visible (not dashboard summary)
      3. Assert: ALL sidebar items visible
      4. Navigate to /dashboard/providers
      5. Assert: Providers page loads
      6. Navigate to /dashboard/combos
      7. Assert: Combos page loads
      8. Navigate to /dashboard/profile
      9. Assert: ALL settings sections visible (Local Mode, Security, OIDC, Routing, Network, Observability)
      10. Navigate to /dashboard/users
      11. Assert: Users management page loads
    Expected Result: Admin view completely unchanged
    Failure Indicators: Any missing section, broken page
    Evidence: .omo/evidence/task-14-admin-verification.png
  ```

  **Commit**: YES
  - Message: `fix: ensure admin view unaffected by non-admin restructure`
  - Files: various (based on issues found)
  - Pre-commit: `npm run build`

---

## Final Verification Wave

- [ ] F1. **Plan Compliance Audit** — `oracle`
  Read the plan end-to-end. For each "Must Have": verify implementation exists (read file, curl endpoint, run command). For each "Must NOT Have": search codebase for forbidden patterns — reject with file:line if found. Check evidence files exist in .omo/evidence/. Compare deliverables against plan.
  Output: `Must Have [N/N] | Must NOT Have [N/N] | Tasks [N/N] | VERDICT: APPROVE/REJECT`

- [ ] F2. **Code Quality Review** — `unspecified-high`
  Run `npm run test` + linter. Review all changed files for: `as any`/`@ts-ignore`, empty catches, console.log in prod, commented-out code, unused imports. Check AI slop: excessive comments, over-abstraction, generic names (data/result/item/temp).
  Output: `Build [PASS/FAIL] | Lint [PASS/FAIL] | Tests [N pass/N fail] | Files [N clean/N issues] | VERDICT`

- [ ] F3. **Real Manual QA** — `unspecified-high`
  Start from clean state. Execute EVERY QA scenario from EVERY task — follow exact steps, capture evidence. Test cross-task integration (features working together, not isolation). Test edge cases: empty state, invalid input, rapid actions. Save to `.omo/evidence/final-qa/`.
  Output: `Scenarios [N/N pass] | Integration [N/N] | Edge Cases [N tested] | VERDICT`

- [ ] F4. **Scope Fidelity Check** — `deep`
  For each task: read "What to do", read actual diff (git log/diff). Verify 1:1 — everything in spec was built (no missing), nothing beyond spec was built (no creep). Check "Must NOT do" compliance. Detect cross-task contamination: Task N touching Task M's files. Flag unaccounted changes.
  Output: `Tasks [N/N compliant] | Contamination [CLEAN/N issues] | Unaccounted [CLEAN/N files] | VERDICT`

---

## Commit Strategy

| Tasks | Commit Message | Files | Pre-commit |
|-------|---------------|-------|------------|
| 1-4 | `feat(db): add email, creditRemaining, userSettings schema` | schema.js, repos/*.js, migrations/* | `npm run test` |
| 5-6 | `feat(api): auto-create key on register, per-user settings endpoints` | register/route.js, api/settings/*, api/users/* | `npm run test` |
| 7-8 | `feat(api): plan auto-detection, enriched /auth/me response` | usersRepo.js, auth/me/route.js | `npm run test` |
| 9-12 | `feat(ui): role-based sidebar, dashboard summary, slim settings` | Sidebar.js, usage/page.js, profile/page.js, endpoint/* | `npm run build` |
| 13-14 | `fix: integration polish and admin view verification` | various | `npm run test` |

---

## Success Criteria

### Verification Commands
```bash
npm run build    # Expected: Build successful, no errors
npm run test     # Expected: All existing tests pass (53 files)
```

### Final Checklist
- [ ] Non-admin user sees only: Dashboard, Usage, CLI Tools, API Keys, Settings (slim)
- [ ] Admin user sees ALL menus (unchanged)
- [ ] New user registers → default API key auto-created
- [ ] Non-admin toggles RTK/Caveman → only their own setting changes
- [ ] Settings page for non-admin: username, password, email, plan info only
- [ ] Plan auto-detected and displayed (Free/Pro)
- [ ] Credit remaining shown on dashboard
- [ ] All existing tests still pass
- [ ] No admin-facing pages modified
