{escape(title)}
+{escape(subtitle)}
+ {body.replace("__REMEMBER_ME_CHECKED__", remember_me_checked)} + +diff --git a/ADMIN_TRYOUT_RESTRUCTURE_PLAN.md b/ADMIN_TRYOUT_RESTRUCTURE_PLAN.md
new file mode 100644
index 0000000..65a5218
--- /dev/null
+++ b/ADMIN_TRYOUT_RESTRUCTURE_PLAN.md
@@ -0,0 +1,365 @@
+# Admin UI Redesign - Implementation Plan
+
+## Overview
+
+This plan outlines the migration from the current scattered admin structure to a clean, hierarchy-driven navigation centered on **Tryouts**.
+
+### Guiding Principles
+1. **One main page per domain** - Features live under their parent, not as separate menu items
+2. **URL reflects depth** - Path structure shows relationship (`/admin/tryout/{id}/questions`)
+3. **Tree as map** - Hierarchy tree shows structure; drill-down shows details
+4. **Consistent naming** - Use "Tryout" instead of "Exam" throughout
+
+---
+
+## 1. URL Structure
+
+### New URL Scheme
+
+| Old Route | New Route | Description |
+|-----------|-----------|-------------|
+| `/admin/exams` | `/admin/tryouts` | Hierarchy tree (main entry) |
+| `/admin/student-attempts` | `/admin/tryout/{tryout_id}/attempts` | Attempts filtered by tryout |
+| - | `/admin/tryout/{tryout_id}/questions` | Questions filtered by tryout |
+| - | `/admin/tryout/{tryout_id}/questions/{question_id}/workspace` | Question workspace |
+| - | `/admin/tryout/{tryout_id}/questions/{question_id}/workspace/{tab}` | Workspace tabs |
+| - | `/admin/tryout/{tryout_id}/normalization` | Normalization settings for this tryout |
+| `/admin/questions` | `/admin/questions` | Global question list (kept) |
+| (none) | `/admin/import-tryout` | Import tryout modal/page |
+
+> **Note:** Import is tryout-level, not question-level. Import button lives on `/admin/tryouts` page header.
+
+### Hierarchy Depth Convention
+
+```
+/admin/tryouts → Level 0: Root
+/admin/tryout/{tryout_id} → Level 1: Entity
+/admin/tryout/{tryout_id}/attempts → Level 2: Related data
+/admin/tryout/{tryout_id}/questions → Level 2: Related data
+/admin/tryout/{tryout_id}/questions/{id} → Level 3: Specific item
+/admin/tryout/{tryout_id}/questions/{id}/workspace → Level 4: Detail view
+```
+
+---
+
+## 2. Navigation Structure
+
+### Proposed Navigation
+
+```
+Questions
+├── /admin/questions # Global question list
+└── /admin/tryout/*/questions/*/workspace # Direct link from tree
+
+Tryouts
+├── /admin/tryouts # Tree: Website → Tryout → Stat → Actions + Import button
+├── /admin/tryout/*/attempts # Filtered attempts
+├── /admin/tryout/*/questions # Questions in this tryout
+├── /admin/tryout/*/normalization # Normalization settings
+└── /admin/import-tryout # Import modal/page
+
+Reports
+├── /admin/reports # Dashboard
+├── /admin/item-statistics
+└── /admin/calibration-status
+
+Settings
+├── /admin/settings
+├── /admin/websites
+└── /admin/password
+```
+
+### Navigation Item Definition
+
+```python
+ADMIN_NAV_ITEMS = (
+ ("Dashboard", "/admin/dashboard", ("/admin/dashboard",)),
+ ("Questions", "/admin/questions", (
+ "/admin/questions",
+ "/admin/tryout/*/questions/*/workspace", # Pattern for direct links
+ )),
+ ("Tryouts", "/admin/tryouts", (
+ "/admin/tryouts",
+ "/admin/tryout/*/attempts",
+ "/admin/tryout/*/questions",
+ "/admin/tryout/*/normalization",
+ "/admin/import-tryout",
+ )),
+ ("Reports", "/admin/reports", (
+ "/admin/reports",
+ "/admin/item-statistics",
+ "/admin/calibration-status",
+ )),
+ ("Settings", "/admin/settings", (
+ "/admin/settings",
+ "/admin/websites",
+ "/admin/password",
+ )),
+ ("Logout", "/admin/logout", ("/admin/logout",)),
+)
+```
+
+---
+
+## 3. Tryouts Tree Structure
+
+### Visual Design
+
+```
+┌─ Tryouts ───────────────────────────────────────────────────────────────────┐
+│ │
+│ [+ Import Tryout] │
+│ │
+│ 🌐 Website A │
+│ │ │
+│ ├─ 📋 132380 - UTBK 2024 [●] │
+│ │ └─ [Expanded on click] │
+│ │ │
+│ ├─ 📋 132381 - SIMAK UI [✓] │
+│ │ │
+│ └─ 📋 132382 - PAS Semester 1 [○] │
+│ │
+│ 🌐 Website B │
+│ └─ ... │
+│ │
+└──────────────────────────────────────────────────────────────────────────────┘
+
+Expanded Tryout View:
+┌─ 📋 132380 - UTBK 2024 ─────────────────────────────────────────────────────┐
+│ │
+│ ┌─ Stat Card ─────────────────────────────────────────────────────────┐ │
+│ │ 👥 150 participants │ NM: 672 avg │ NN: 505 avg │ │
+│ │ ✓ 98% completion │ 📐 Calibration: ████████░░ 85% (17/20) │ │
+│ └────────────────────────────────────────────────────────────────────┘ │
+│ │
+│ [📝 Questions (20)] [👥 Attempts (150)] [📐 Normalization] [⚙ Settings]│
+│ │
+└──────────────────────────────────────────────────────────────────────────────┘
+
+Legend:
+[●] Partial (50-89% calibrated)
+[✓] Ready (≥90% calibrated)
+[○] Needs Data (<50% calibrated)
+```
+
+### Import Button Location
+
+- **Location:** Header of `/admin/tryouts` page
+- **Label:** "[+ Import Tryout]" or "[Import Tryout JSON]"
+- **Behavior:** Opens import modal/page
+- **Why:** Import is tryout-level operation (imports questions WITH tryout context)
+
+### Stat Card Components
+
+| Field | Source | Display |
+|-------|--------|---------|
+| Participants | `TryoutStats.participant_count` | 👥 {count} |
+| Avg NM | `AVG(Session.NM)` where completed | 📊 {value} avg |
+| Avg NN | `AVG(Session.NN)` where completed | 📈 {value} avg |
+| Completion Rate | `completed / participants * 100` | ✓ {percentage}% |
+| Calibration | `calibrated_items / total_items` | 📐 Progress bar + {count}/{total} |
+
+### Action Buttons
+
+| Action | Target URL | Icon |
+|--------|------------|------|
+| Questions | `/admin/tryout/{id}/questions` | 📝 |
+| Attempts | `/admin/tryout/{id}/attempts` | 👥 |
+| Normalization | `/admin/tryout/{id}/normalization` | 📐 |
+| Settings | `/admin/tryout/{id}/settings` (or modal) | ⚙ |
+
+---
+
+## 4. Page Specifications
+
+### 4.1 `/admin/tryouts` (Main Tree)
+
+**Purpose:** Primary navigation entry, shows structure at a glance
+
+**Default State:**
+- Websites expanded
+- Tryouts collapsed
+- Shows calibration indicator dot next to each tryout
+
+**Interactions:**
+- Click tryout → expand/collapse
+- Expanded tryout shows stat card + action buttons
+- Actions navigate to filtered views
+
+### 4.2 `/admin/tryout/{tryout_id}/questions`
+
+**Purpose:** View all questions in a specific tryout
+
+**Behavior:**
+- Shows only original/imported questions (basis items)
+- Pre-filtered by `tryout_id`
+- Links to workspace for AI variant generation
+
+**Table Columns:**
+
+| Column | Description |
+|--------|-------------|
+| ID | Question internal ID |
+| Stem Preview | First 100 chars of question text |
+| Difficulty | Current difficulty level |
+| Calibration | P-value or IRT-b indicator |
+| Variants | Count of generated variants |
+| Actions | [View] [Workspace] |
+
+### 4.3 `/admin/tryout/{tryout_id}/questions/{question_id}/workspace`
+
+**Purpose:** Generate and manage question variants
+
+**Tabs:**
+
+| Tab | Purpose |
+|-----|---------|
+| Generate | AI variant generation interface |
+| Review | Review generated variants |
+| Batch | Batch generation options |
+
+**Access Pattern:**
+- Opens from question list or tree direct link
+- Context: knows parent tryout, parent question
+
+### 4.4 `/admin/tryout/{tryout_id}/attempts`
+
+**Purpose:** View student attempts for specific tryout
+
+**Current Implementation:** Already exists at `/admin/student-attempts` → migrate URL
+
+**Enhancements:**
+- Pre-filtered by `tryout_id` (no dropdown needed on this page)
+- Stat card from parent tryout shown at top
+
+### 4.5 `/admin/tryout/{tryout_id}/normalization`
+
+**Purpose:** Configure normalization settings for a specific tryout
+
+**Settings (per-tryout):**
+
+| Setting | Type | Default | Description |
+|---------|------|---------|-------------|
+| Mode | Select | Auto | Auto (calculate from data) or Manual (fixed values) |
+| Rataan | Number | 500 | Target mean for normalization |
+| SB | Number | 100 | Target standard deviation |
+| Recalculate | Button | - | Re-run normalization on existing sessions |
+
+**Formula:** `NN = 500 + 100 × ((NM - Rataan) / SB)`
+
+**UI:**
+- Simple form with current values
+- "Recalculate" button triggers normalization job
+- Shows last normalization timestamp
+
+### 4.6 `/admin/import-tryout`
+
+**Purpose:** Import tryout data (questions + metadata) from JSON
+
+**Access:** Via "[+ Import Tryout]" button on `/admin/tryouts` page
+
+**Behavior:**
+- Opens modal or dedicated page
+- Upload JSON file or paste JSON content
+- Preview import before confirming
+- Creates new tryout with questions
+
+**URL Convention:** Not under specific tryout (it's creating a new one)
+
+---
+
+## 5. Deprecations
+
+### Routes to Remove
+
+| Route | Reason |
+|-------|--------|
+| `/admin/exams` | Renamed to `/admin/tryouts` |
+| `/admin/student-attempts` | URL changed to `/admin/tryout/{id}/attempts` |
+| `/admin/templates` | AI uses basis items directly |
+| `/admin/basis-items` | Merge into question workspace |
+| `/admin/hierarchy` | Tree IS the hierarchy |
+| `/admin/question-quality` | Merged into tryout stat card |
+
+### Legacy Redirects
+
+```python
+LEGACY_URL_MAP = {
+ "/admin/exams": "/admin/tryouts",
+ "/admin/student-attempts": "/admin/tryouts", # Or redirect to tryouts with guidance
+ "/admin/hierarchy": "/admin/tryouts",
+ "/admin/question-quality": "/admin/tryouts",
+ # Templates and basis-items: 404 (removed)
+}
+```
+
+---
+
+## 6. Implementation Phases
+
+### Phase 1: Foundation
+- [ ] Rename `/admin/exams` → `/admin/tryouts` (keep old route for now)
+- [ ] Implement tree structure in `/admin/tryouts`
+- [ ] Move `TryoutStats` info into tree stat cards
+- [ ] Add calibration indicator dots
+
+### Phase 2: URL Migration
+- [ ] Create `/admin/tryout/{id}/attempts` (redirect from old route)
+- [ ] Create `/admin/tryout/{id}/questions`
+- [ ] Update navigation items
+
+### Phase 3: Workspace Integration
+- [ ] Create question workspace route
+- [ ] Implement workspace tabs
+- [ ] Connect workspace to tree and question list
+
+### Phase 4: Cleanup
+- [ ] Add legacy redirects
+- [ ] Remove deprecated routes
+- [ ] Update all hardcoded links in views
+
+### Phase 5: Polish
+- [ ] Review all pages for consistency
+- [ ] Update documentation
+- [ ] Test all navigation paths
+
+---
+
+## 7. Open Questions
+
+1. ~~Normalization settings~~ - **RESOLVED**: Move under tryout context as `/admin/tryout/{id}/normalization`
+
+2. ~~Import questions page~~ - **RESOLVED**: Import is tryout-level. Button on `/admin/tryouts` header, not a separate page.
+
+3. **Tryout settings** - What settings are actually needed? (Scoring mode, time limits, selection criteria?)
+
+4. **Global questions page** - Is `/admin/questions` (unfiltered) still useful, or should every question access go through tryout context?
+
+5. **Templates deprecation** - Confirm that `/admin/templates` is truly unused and can be safely removed?
+
+6. **Legacy routes for deleted pages** - Should `/admin/templates` and `/admin/basis-items` redirect somewhere or return 404?
+
+---
+
+## 8. Files to Modify
+
+### Primary Changes
+- `app/admin_web.py` - Major route restructuring
+- Navigation definition in `admin_web.py`
+- Legacy URL map
+
+### Likely Additions
+- Static assets for tree expansion/collapse (if not using existing)
+
+### Documentation Updates
+- `ADMIN_UI_REDESIGN_PLAN.md` - Update to reflect final structure
+- `PROJECT_UNDERSTANDING.md` - Update route documentation
+
+---
+
+## 9. Changelog
+
+| Version | Date | Changes |
+|---------|------|---------|
+| 1.0 | 2026-06-17 | Initial draft based on discussion |
+| 1.1 | 2026-06-17 | - Move normalization to `/admin/tryout/{id}/normalization`
- Move import button to `/admin/tryouts` header
- Add normalization page spec (4.5)
- Rename import page spec (4.6)
- Update navigation and action buttons |
diff --git a/FRONTEND_MIGRATION_AUDIT_REPORT.md b/FRONTEND_MIGRATION_AUDIT_REPORT.md
new file mode 100644
index 0000000..70cb7ab
--- /dev/null
+++ b/FRONTEND_MIGRATION_AUDIT_REPORT.md
@@ -0,0 +1,615 @@
+# Frontend Migration Audit Report
+
+Date: 2026-06-19
+Project: Yellow Bank Soal / IRT Bank Soal
+Scope: Migration from root-level Python/FastAPI admin UI to `backend/` plus new React `frontend/`
+Auditor: Codex
+
+## 1. Executive Summary
+
+The React frontend is scaffolded and builds successfully, but the migration is not yet feature-complete or integration-safe. The biggest risks are API address drift, tenant/website context bugs, missing parity with the legacy Python admin workflows, and placeholder React pages that appear functional but do not call real backend APIs.
+
+Current readiness assessment: **not production-ready as the primary replacement for the Python admin UI**.
+
+Top findings:
+
+| Priority | Finding | Impact |
+|---|---|---|
+| P0 | Local frontend API base URL omits `/api/v1` | Most API calls 404 in `npm run dev` and any environment using `frontend/.env`. |
+| P0 | System admin website scope starts as `website_id=0` and React Query keys ignore website selection | First dashboard loads empty or wrong scoped data; switching websites can show stale data. |
+| P0 | Several React API calls target nonexistent or renamed backend endpoints | Reports, normalization, and Excel import workflows are broken. |
+| P1 | Student tryout portal from the migration plan is absent | Core learner flow is not migrated to React. |
+| P1 | AI generation UI has incomplete save/review/batch behavior | Operators can generate previews, but core review and batch workflow parity is missing. |
+| P1 | Unsafe `dangerouslySetInnerHTML` use without sanitization | Imported or AI-generated HTML can become an admin XSS risk. |
+| P2 | Multiple legacy admin features are missing or placeholders | Hierarchy, question quality, question details, password update, exports, and settings are incomplete. |
+
+The build result is positive: `npm run build` completed successfully. This means the current issues are mainly behavioral and integration defects, not TypeScript compilation blockers.
+
+## 2. Audit Scope And Methodology
+
+Reviewed:
+
+- Repository restructure from root `app/` to `backend/app/` and new `frontend/`.
+- Current React routes, pages, state store, API client, and Docker/Nginx configuration.
+- Current FastAPI router definitions and generated OpenAPI paths.
+- Last committed Python admin surface via `git show HEAD:app/admin_web.py`.
+- Existing planning documents: `REACT_Migration_Plan.md`, `ADMIN_TRYOUT_RESTRUCTURE_PLAN.md`, and `UX_AUDIT_ADMIN_FLOW.md`.
+
+Verification performed:
+
+- `npm run build` inside `frontend/`: passed.
+- FastAPI OpenAPI generation from `backend/app/main.py`: produced 55 paths.
+- Static endpoint comparison between React `api.get/post/put/delete` calls and backend route definitions.
+
+Not performed:
+
+- Full browser E2E against a running backend/database.
+- Live authentication, import, AI generation, or report generation.
+- Full backend test suite run.
+
+## 3. Current Architecture Snapshot
+
+The current repository is in an uncommitted migration state. Git sees the old root-level Python files as deleted and `backend/` plus `frontend/` as new untracked folders.
+
+React frontend:
+
+- Admin-only route shell currently lives in `frontend/src/App.tsx`.
+- API helper is `frontend/src/lib/api.ts`.
+- Global website and auth token state is persisted in `frontend/src/store/useAppStore.ts`.
+- The admin UI has pages for Dashboard, Questions, Tryouts, Reports, Settings, AI Generation, Import, and nested Tryout workspaces.
+
+Backend:
+
+- Main FastAPI app lives in `backend/app/main.py`.
+- JSON APIs are generally under `/api/v1`.
+- Legacy Python admin HTML router is still mounted at `/admin` when `ENABLE_ADMIN=true`.
+- Import/export router hardcodes `/api/v1/import-export` inside the router prefix rather than relying on `settings.API_V1_STR`.
+
+## 4. Verification Results
+
+| Check | Result | Notes |
+|---|---|---|
+| React build | Passed | `tsc -b && vite build` completed. Vite warned that the main JS chunk is larger than 500 kB. |
+| FastAPI OpenAPI paths | Passed | OpenAPI generated 55 paths. |
+| API route parity | Failed | Multiple frontend calls do not map to backend paths or methods. |
+| Feature parity with legacy Python admin | Partial | Several legacy workflows are absent, placeholders, or only implemented as HTML admin routes. |
+| Local development readiness | Failed | `frontend/.env` and backend CORS settings do not match the default Vite dev setup. |
+
+## 5. Backend API Paths Observed
+
+The current OpenAPI schema exposes these relevant JSON paths:
+
+```text
+/api/v1/auth/admin-login
+/api/v1/websites
+/api/v1/admin/dashboard/stats
+/api/v1/admin/questions
+/api/v1/admin/templates
+/api/v1/admin/tryouts/{tryout_id}/questions
+/api/v1/admin/tryouts/{tryout_id}/attempts
+/api/v1/admin/ai/models
+/api/v1/admin/ai/generate-preview
+/api/v1/admin/ai/generate-save
+/api/v1/admin/ai/pending-reviews
+/api/v1/admin/ai/review/{item_id}
+/api/v1/import-export/preview
+/api/v1/import-export/questions
+/api/v1/import-export/export/questions
+/api/v1/import-export/tryout-json/preview
+/api/v1/import-export/tryout-json
+/api/v1/tryout/
+/api/v1/tryout/{tryout_id}/config
+/api/v1/tryout/{tryout_id}/normalization
+/api/v1/tryout/{tryout_id}/calibration-status
+/api/v1/reports/student/performance
+/api/v1/reports/items/analysis
+/api/v1/reports/calibration/status
+```
+
+Legacy HTML-only admin paths still exist under `/admin`, including:
+
+```text
+/admin/hierarchy
+/admin/question-quality
+/admin/calibration-status
+/admin/item-statistics
+/admin/session-overview
+/admin/snapshot-questions
+/admin/snapshot-questions/promote-bulk
+/admin/basis-items
+/admin/basis-items/{basis_item_id}
+/admin/basis-items/{basis_item_id}/generate
+/admin/basis-items/{basis_item_id}/review-bulk
+/admin/questions/{item_id}
+/admin/questions/{item_id}/generate
+/admin/questions/{item_id}/generate/review-bulk
+```
+
+Those HTML paths are not a substitute for React JSON API parity unless the React app intentionally navigates users back into the legacy admin UI.
+
+## 6. Endpoint Compatibility Matrix
+
+This matrix assumes the frontend base URL is configured as `http://localhost:8000/api/v1`, as Docker currently does. If the base URL is `http://localhost:8000`, most rows fail one level earlier because `/api/v1` is missing.
+
+| React call | Backend status | Migration status |
+|---|---|---|
+| `/auth/admin-login` | Exists as `/api/v1/auth/admin-login` | OK when base URL includes `/api/v1`. |
+| `/websites` | Exists as `/api/v1/websites` | OK when base URL includes `/api/v1`. |
+| `/admin/dashboard/stats` | Exists as `/api/v1/admin/dashboard/stats` | Path OK, but website scoping can return empty/stale data. |
+| `/tryout/` | Exists as `/api/v1/tryout/` | OK when base URL includes `/api/v1`. |
+| `/admin/questions` | Exists as `/api/v1/admin/questions` | OK when base URL includes `/api/v1`. |
+| `/admin/templates` | Exists as `/api/v1/admin/templates` | Path OK; verify runtime lazy relationship behavior. |
+| `/admin/tryouts/{id}/questions` | Exists as `/api/v1/admin/tryouts/{tryout_id}/questions` | OK when base URL includes `/api/v1`. |
+| `/admin/tryouts/{id}/attempts` | Exists as `/api/v1/admin/tryouts/{tryout_id}/attempts` | OK when base URL includes `/api/v1`. |
+| `/admin/ai/models` | Exists as `/api/v1/admin/ai/models` | OK when base URL includes `/api/v1`. |
+| `/admin/ai/generate-preview` | Exists as `/api/v1/admin/ai/generate-preview` | Path OK; payload includes unsupported `operator_notes` in one page but Pydantic ignores extras by default. |
+| `/admin/ai/generate-save` | Exists as `/api/v1/admin/ai/generate-save` | Path OK; React passes placeholder slot and can cause duplicate/conflict behavior. |
+| `/import-export/tryout-json/preview` | Exists as `/api/v1/import-export/tryout-json/preview` | OK when base URL includes `/api/v1`. |
+| `/import-export/tryout-json` | Exists as `/api/v1/import-export/tryout-json` | OK when base URL includes `/api/v1`. |
+| `/reports/calibration-status` | Backend has `/api/v1/reports/calibration/status?tryout_id=...` | Broken. Wrong path and missing required `tryout_id`. |
+| `/reports/item-analysis` | Backend has `/api/v1/reports/items/analysis?tryout_id=...` | Broken. Wrong path and missing required `tryout_id`. |
+| `/reports/student-performance` | Backend has `/api/v1/reports/student/performance?tryout_id=...` | Broken. Wrong path and missing required `tryout_id`. |
+| `/tryouts/{id}/config` | Backend has `/api/v1/tryout/{id}/config` | Broken. Uses plural `tryouts`. |
+| `POST /tryouts/{id}/normalization` | Backend has `PUT /api/v1/tryout/{id}/normalization` | Broken. Wrong path, method, and payload schema. |
+| `/tryouts/{id}/normalization/recalculate` | No JSON API found | Broken. |
+| `/import-export/tryout-import` | No JSON API found | Broken. Should likely use `/import-export/preview` or `/import-export/questions`. |
+| `/import-export/snapshot-questions/promote-bulk` | No JSON API found | Broken. Legacy equivalent is HTML form POST `/admin/snapshot-questions/promote-bulk`. |
+
+## 7. Findings
+
+### P0-01: Local API base URL omits `/api/v1`
+
+Severity: P0
+Category: API routing / environment configuration
+Evidence:
+
+- `frontend/src/lib/api.ts:5` defaults to `http://localhost:8000`.
+- `frontend/.env:1` sets `VITE_API_URL=http://localhost:8000`.
+- Backend JSON admin APIs are exposed under `/api/v1/...`.
+- Docker build uses `VITE_API_BASE_URL: "http://localhost:8000/api/v1"` in `docker-compose.yml:62`, so Docker and local dev behave differently.
+
+Impact:
+
+- Running `npm run dev` or using the checked-in `frontend/.env` makes calls such as `/auth/admin-login`, `/websites`, and `/admin/dashboard/stats` hit the wrong backend URLs.
+- Developers can see a compiling app but get login/API failures at runtime.
+- Bugs may be masked in Docker while reappearing in local development or other deployments.
+
+Recommendation:
+
+- Standardize one env var name, preferably `VITE_API_URL`, and set it to the full API root: `http://localhost:8000/api/v1`.
+- Add `frontend/.env.example` with the same value.
+- Add a startup assertion or dev console warning if `VITE_API_URL` does not end in `/api/v1`.
+- Consider making the Axios helper append `/api/v1` itself so pages never depend on a base URL convention.
+
+### P0-02: System-admin website scope and React Query cache can show empty or stale tenant data
+
+Severity: P0
+Category: Multi-tenant data isolation / state management
+Evidence:
+
+- Login issues a system-admin token with `website_id=0` in `backend/app/routers/auth.py:50-55`.
+- The comment says this placeholder should produce global access, but `require_website_auth` returns `auth.website_id` whenever it is not `None` in `backend/app/core/auth.py:147-150`.
+- `WebsiteSelector` auto-selects the first website asynchronously in `frontend/src/components/WebsiteSelector.tsx:25-29`.
+- Dashboard query key is `['dashboard-stats']` and does not include `websiteId` in `frontend/src/pages/admin/Dashboard.tsx:45-50`.
+- Other query keys also omit `websiteId`, including `['tryouts']`, `['admin-questions']`, and `['ai-pending-reviews']`.
+
+Impact:
+
+- First-load dashboard requests can run before the selector sets `X-Website-ID`; backend may interpret the request as website `0` and return empty data.
+- Switching websites can leave cached data from the prior website because React Query keys do not include the website id.
+- Multi-tenant admin data can appear wrong even when the API endpoint is otherwise correct.
+
+Recommendation:
+
+- Fix backend system-admin semantics: use `website_id=None` for global system admin or make `website_id=0` explicitly mean global access.
+- In React, gate website-scoped queries until `websiteId` is set, except for the websites list itself.
+- Include `websiteId` in every website-scoped React Query key, for example `['dashboard-stats', websiteId]`.
+- Invalidate website-scoped queries when `WebsiteSelector` changes.
+
+### P0-03: Reports page calls nonexistent backend paths and omits required filters
+
+Severity: P0
+Category: API contract / reporting
+Evidence:
+
+- React calls `/reports/calibration-status`, `/reports/item-analysis`, and `/reports/student-performance` in `frontend/src/pages/admin/reports/index.tsx:14`, `:58`, and `:88`.
+- Backend exposes `/reports/calibration/status`, `/reports/items/analysis`, and `/reports/student/performance` in `backend/app/routers/reports.py:68-80`, `:172-184`, and `:231-241`.
+- Each backend report endpoint requires `tryout_id`.
+- React report export buttons have no handlers in `frontend/src/pages/admin/reports/index.tsx:29-31`, `:73-75`, and `:103-105`.
+
+Impact:
+
+- All three report tabs fail at runtime.
+- Even after path correction, the page needs tryout selection or route context because backend requires `tryout_id`.
+- Export buttons are misleading because they do not call the export APIs.
+
+Recommendation:
+
+- Use the backend paths:
+ - `/reports/calibration/status?tryout_id={id}`
+ - `/reports/items/analysis?tryout_id={id}`
+ - `/reports/student/performance?tryout_id={id}`
+- Add tryout selector/context to the Reports page.
+- Wire export buttons to `/reports/.../export/{format}` endpoints.
+- Render real report tables from response fields instead of placeholder text.
+
+### P0-04: Tryout normalization page uses wrong paths, method, payload, and silent fallback
+
+Severity: P0
+Category: API contract / scoring configuration
+Evidence:
+
+- React fetches `/tryouts/{id}/config` in `frontend/src/pages/admin/tryouts/Normalization.tsx:22`.
+- Backend route is `/tryout/{tryout_id}/config` in `backend/app/routers/tryouts.py:34-44`.
+- React posts `/tryouts/{id}/normalization` with `{ rataan, sb, mode }` in `frontend/src/pages/admin/tryouts/Normalization.tsx:39-45`.
+- Backend expects `PUT /tryout/{tryout_id}/normalization` with fields `normalization_mode`, `static_rataan`, and `static_sb` in `backend/app/routers/tryouts.py:109-120`.
+- React calls `/tryouts/{id}/normalization/recalculate` in `frontend/src/pages/admin/tryouts/Normalization.tsx:53-56`, but no matching JSON API was found.
+- The page catches config load failures and silently displays defaults in `frontend/src/pages/admin/tryouts/Normalization.tsx:21-26`.
+
+Impact:
+
+- Operators can believe they changed normalization settings when the requests actually failed or hit nonexistent endpoints.
+- Silent defaults can overwrite user trust in scoring configuration by hiding missing data.
+- Normalization is core to NM/NN scoring, so this is a production blocker.
+
+Recommendation:
+
+- Change GET to `/tryout/{id}/config`.
+- Change save to `PUT /tryout/{id}/normalization`.
+- Send backend schema names: `normalization_mode`, `static_rataan`, `static_sb`.
+- Remove silent fallback for API failures; show an error state.
+- Either add a backend recalculation endpoint or remove the button until the API exists.
+
+### P0-05: Excel import page is wired to nonexistent endpoints
+
+Severity: P0
+Category: Import workflow / API contract
+Evidence:
+
+- React posts preview/upload to `/import-export/tryout-import` in `frontend/src/pages/admin/import/index.tsx:17-23`.
+- React posts confirmation to `/import-export/snapshot-questions/promote-bulk` in `frontend/src/pages/admin/import/index.tsx:31-35`.
+- Backend Excel import APIs are `/api/v1/import-export/preview` and `/api/v1/import-export/questions` in `backend/app/routers/import_export.py:53-62` and `:150-160`.
+- Snapshot promotion currently exists only in the legacy HTML admin as `/admin/snapshot-questions/promote-bulk`.
+
+Impact:
+
+- The standalone Excel Import page cannot complete its workflow.
+- Users have two import surfaces: a working JSON import modal under Tryouts and a broken Excel import page under `/admin/import`.
+- The comments in the React file explicitly show uncertainty about endpoint names.
+
+Recommendation:
+
+- Decide whether Excel import remains in the React admin.
+- If yes, wire preview to `/import-export/preview` and confirm to `/import-export/questions` with required `tryout_id`.
+- If snapshot promotion is required in React, add a JSON API for selected snapshot question IDs and update the UI accordingly.
+- Hide `/admin/import` until the contract is implemented.
+
+### P1-01: Student tryout portal is missing from React
+
+Severity: P1
+Category: Feature parity / core business flow
+Evidence:
+
+- `REACT_Migration_Plan.md:73-85` describes Phase 3 Student Portal construction: tryout listing, exam session, async answer submission, state recovery, server timer, and result page.
+- Current `frontend/src/App.tsx:38-66` only defines `/login` and `/admin/*` routes.
+- No student session routes or pages were found under `frontend/src/pages`.
+
+Impact:
+
+- The React migration does not yet cover the learner-facing tryout experience.
+- If the goal is full Python frontend replacement, core user-facing functionality remains unmigrated.
+
+Recommendation:
+
+- Add student routes for tryout listing, active session, answer submission, completion, and result summary.
+- Use existing backend session APIs under `/api/v1/session`.
+- Add E2E coverage for refresh recovery and server-synced timer behavior.
+
+### P1-02: AI generation workflow is incomplete and can save invalid variants
+
+Severity: P1
+Category: AI generation / operator workflow
+Evidence:
+
+- Global AI page uses manual basis item id and comments that a real template selector is missing in `frontend/src/pages/admin/ai/Workspace.tsx:26-27`.
+- Global AI page has "Discard" and "Save & Queue Review" buttons with no handlers in `frontend/src/pages/admin/ai/Workspace.tsx:138-140`.
+- Tryout AI workspace saves generated questions with `slot: basisItem ? 1 : 1` in `frontend/src/pages/admin/tryouts/AIWorkspace.tsx:64-76`.
+- Tryout AI workspace "Review Variants" and "Batch Generation" tabs are placeholder text in `frontend/src/pages/admin/tryouts/AIWorkspace.tsx:233-253`.
+- Legacy Python admin supported batch count, operator notes, note inclusion, run history, filters, review-bulk, and variant detail pages.
+
+Impact:
+
+- Operators cannot reliably save variants with correct slot linkage.
+- Batch generation and review parity are missing.
+- Duplicate slot conflicts are likely because saved AI variants always use slot `1`.
+
+Recommendation:
+
+- Use the selected basis item's real `slot`, `tryout_id`, `website_id`, and source snapshot metadata.
+- Add JSON APIs if needed for batch generation, run history, review filtering, and bulk review.
+- Disable save buttons until all required fields are present.
+- Remove or implement the global AI workspace to avoid two partial AI workflows.
+
+### P1-03: Imported and generated HTML is rendered without sanitization
+
+Severity: P1
+Category: Security / XSS
+Evidence:
+
+- React renders question HTML with `dangerouslySetInnerHTML` in `frontend/src/pages/admin/tryouts/QuestionManagement.tsx`.
+- React renders AI preview stem/options/explanation with `dangerouslySetInnerHTML` in `frontend/src/pages/admin/tryouts/AIWorkspace.tsx:180-200`.
+- The migration plan explicitly calls out HTML sanitization as a security checklist item in `REACT_Migration_Plan.md:204-208`.
+
+Impact:
+
+- Imported Sejoli payloads or AI-generated content could inject scripts or unsafe markup into admin pages.
+- Admin XSS is high impact because admins hold cross-website operational access.
+
+Recommendation:
+
+- Add a sanitizer such as DOMPurify.
+- Create a single `SafeHtml` component and forbid direct `dangerouslySetInnerHTML` in pages.
+- Sanitize on render and consider backend-side validation for stored HTML.
+
+### P1-04: CORS config does not include the default Vite dev origin
+
+Severity: P1
+Category: Local development / environment configuration
+Evidence:
+
+- Backend `.env` allows `http://localhost:3000` and `http://localhost:8000` in `backend/.env:15`.
+- Vite dev normally serves at `http://localhost:5173`.
+- `REACT_Migration_Plan.md:53` explicitly calls out adding frontend dev origins.
+
+Impact:
+
+- Local `npm run dev` can fail with CORS errors even after the API base URL is corrected.
+- Developers may mistakenly debug auth/API code when the root cause is CORS.
+
+Recommendation:
+
+- Add `http://localhost:5173` to `ALLOWED_ORIGINS`.
+- Keep Docker/static origin and Vite dev origin both represented in `.env.example`.
+
+### P2-01: Question quality page is a static placeholder
+
+Severity: P2
+Category: Missing feature / reporting parity
+Evidence:
+
+- `frontend/src/pages/admin/questions/QuestionQuality.tsx:14-47` displays `...` for all metrics.
+- `frontend/src/pages/admin/questions/QuestionQuality.tsx:61-65` says diagnostic charts are coming soon.
+- Legacy Python admin had a real `/admin/question-quality` view that computed calibrated totals and per-tryout readiness.
+
+Impact:
+
+- Operators lose the prior calibration diagnostics workflow.
+- The page appears present but does not provide operational data.
+
+Recommendation:
+
+- Either wire this page to `/reports/calibration/status` per selected tryout or add a dashboard-level quality summary API.
+- Replace placeholder cards with real metrics and loading/error states.
+
+### P2-02: Tryout settings and general/security settings are placeholders
+
+Severity: P2
+Category: Missing feature / admin configuration
+Evidence:
+
+- Tryout settings page contains only placeholder text in `frontend/src/pages/admin/tryouts/TryoutSettings.tsx:14-16`.
+- Security settings form has inputs and button but no mutation in `frontend/src/pages/admin/settings/index.tsx:151-176`.
+- General settings tab is placeholder text in `frontend/src/pages/admin/settings/index.tsx:203-211`.
+- Legacy Python admin had `/admin/password` and website management.
+
+Impact:
+
+- Operators cannot update tryout scoring/selection/AI settings from React.
+- Password update looks available but does nothing.
+
+Recommendation:
+
+- Implement tryout settings using `/tryout/{id}/config` plus update endpoints for scoring mode, selection mode, AI generation, and calibration thresholds.
+- Add or expose a JSON password-change endpoint, or hide Security until implemented.
+- Replace "General" with concrete settings or remove the tab.
+
+### P2-03: Hierarchy/data overview was not migrated
+
+Severity: P2
+Category: Missing feature / operator orientation
+Evidence:
+
+- Legacy Python admin exposed `/admin/hierarchy`.
+- `UX_AUDIT_ADMIN_FLOW.md` and `ADMIN_TRYOUT_RESTRUCTURE_PLAN.md` identified hierarchy visibility as important.
+- Current React sidebar has Dashboard, Questions, Tryouts, Reports, Settings only in `frontend/src/layouts/AdminLayout.tsx:10-16`.
+- No React hierarchy page exists.
+
+Impact:
+
+- Operators lose the data relationship map for Website -> Tryout -> Snapshot -> Basis Item -> AI Run -> Variant.
+- This was specifically identified as important for reducing confusion after import and AI generation.
+
+Recommendation:
+
+- Add a React Data Overview/Hierarchy page.
+- Expose a JSON hierarchy API instead of relying on legacy HTML.
+- Link it from Dashboard or Tryouts, not only Settings.
+
+### P2-04: Route structure deviates from the planned tryout-centric URL model
+
+Severity: P2
+Category: Navigation / route consistency
+Evidence:
+
+- `ADMIN_TRYOUT_RESTRUCTURE_PLAN.md:19-28` planned singular `/admin/tryout/{tryout_id}/...` route depth.
+- Current React uses plural `/admin/tryouts/:id/...` in `frontend/src/App.tsx:55-60`.
+- Planned question workspace route includes question id, but current route is `/admin/tryouts/:id/questions/ai-workspace` without question id.
+- TryoutLayout tabs omit Normalization from the visible tab list even though the route exists.
+
+Impact:
+
+- URL semantics differ from the planned hierarchy.
+- AI workspace lacks clear parent question context.
+- Users navigating to Normalization see a page that is not represented in the tab state.
+
+Recommendation:
+
+- Decide on singular or plural route convention and align docs, React routes, and links.
+- Include `questionId` in AI workspace routes.
+- Add a visible Normalization tab or move normalization under Settings consistently.
+
+### P2-05: Legacy Python admin remains mounted, creating deployment ambiguity
+
+Severity: P2
+Category: Deployment / migration completeness
+Evidence:
+
+- `backend/app/main.py` still includes `admin_web_router` when admin is enabled.
+- Docker serves React at port `3000` and backend at port `8000`.
+- Backend still owns `/admin/*` on port `8000`; React owns `/admin/*` on port `3000`.
+
+Impact:
+
+- If production routing later places frontend and backend behind one host, `/admin` routing can easily point to the wrong application.
+- Operators may accidentally use two different admin UIs with different feature coverage.
+
+Recommendation:
+
+- Define production routing explicitly:
+ - Frontend owns `/admin/*`.
+ - Backend owns `/api/v1/*`, `/docs`, `/health`, and possibly legacy admin only behind a temporary fallback path.
+- Add a migration flag to disable legacy admin once React parity is reached.
+
+### P2-06: Query invalidation and cache keys are not website-aware
+
+Severity: P2
+Category: State management / data freshness
+Evidence:
+
+- Examples: `['dashboard-stats']`, `['tryouts']`, `['admin-questions']`, `['ai-pending-reviews']`.
+- The API interceptor changes `X-Website-ID` based on Zustand state, but React Query cache keys do not reflect that state.
+
+Impact:
+
+- After switching websites, React Query can return prior website data without refetching.
+- The visible WebsiteSelector can imply a different scope than the data currently shown.
+
+Recommendation:
+
+- Include `websiteId` in all website-scoped query keys.
+- Add a small helper for scoped keys to avoid drift.
+- Consider clearing scoped query cache on logout and website switch.
+
+### P3-01: Several buttons look actionable but do nothing
+
+Severity: P3
+Category: UX polish / trust
+Evidence:
+
+- Report export buttons have no click handlers.
+- Excel "Download Template" button has no click handler.
+- Global AI "Discard" and "Save & Queue Review" buttons have no click handlers.
+- Settings "Update Password" button has no click handler.
+
+Impact:
+
+- The UI feels more complete than it is, which can mislead testers and operators.
+
+Recommendation:
+
+- Remove disabled/nonfunctional controls or wire them to real mutations/downloads.
+- Prefer disabled buttons with explanatory tooltip only when the missing backend is intentional.
+
+## 8. Feature Parity Checklist
+
+| Area | Legacy Python admin | React status | Notes |
+|---|---|---|---|
+| Login/logout | Present | Partial | JWT login works by path only if base URL includes `/api/v1`; no remember-me equivalent. |
+| Dashboard | Present | Partial | React has KPI cards, but first-load website scoping and query key issues affect data. |
+| Website management | Present | Partial | React CRUD exists; confirm delete cascade semantics and query invalidation. |
+| Tryout import JSON | Present | Mostly present | Modal maps to real JSON endpoints when base URL includes `/api/v1`. |
+| Excel import | Present via API | Broken | React page calls nonexistent endpoints. |
+| Snapshot question promotion | Present as legacy HTML | Missing JSON/React | React calls nonexistent API. |
+| Global question list | Present with filters/detail | Partial | React list exists, but filters and detail page are missing. |
+| Question detail | Present | Missing | No React route/page. |
+| Question quality | Present | Placeholder | Static cards only. |
+| Tryout list/tree | Present/planned | Partial | Accordion exists; average NM/NN and some plan details missing. |
+| Tryout attempts | Present | Present basic | Filtered table exists. |
+| Normalization | Present | Broken | Wrong API contract. |
+| Tryout settings | Present via backend fields | Placeholder | No real form. |
+| AI basis workspace | Present | Partial | Preview and single save partially exist; batch/review/run history missing. |
+| AI pending review | Present | Partial | List and approve/reject exist; preview/detail missing. |
+| Variant detail | Present | Missing | No React page. |
+| Bulk variant review | Present | Missing | No React workflow. |
+| Hierarchy/data overview | Present | Missing | Important operator context lost. |
+| Reports dashboard | Present | Broken/placeholder | Wrong endpoints and no tryout filters. |
+| Report exports | Present in backend | Missing in React | Buttons not wired. |
+| Password update | Present in legacy HTML | Placeholder | No API/mutation. |
+| Student tryout portal | Planned | Missing | No React student/session routes. |
+
+## 9. Recommended Remediation Plan
+
+### Phase 0: Stop the bleeding
+
+1. Fix `VITE_API_URL` to include `/api/v1` in `frontend/.env`, Docker build args, and `.env.example`.
+2. Add `http://localhost:5173` to backend CORS for Vite dev.
+3. Fix system-admin website scoping so no-header system admin is global or explicitly blocked until a website is selected.
+4. Gate website-scoped React queries until `websiteId` is available.
+5. Add `websiteId` to all scoped query keys.
+
+### Phase 1: Repair broken API contracts
+
+1. Fix Reports paths and require a selected tryout.
+2. Fix Normalization GET/PUT paths and payload schema.
+3. Remove or fix the broken Excel import page.
+4. Decide whether snapshot promotion needs a JSON API and add it if React owns the workflow.
+5. Add API contract tests that compare frontend endpoint constants against OpenAPI paths.
+
+### Phase 2: Recover feature parity
+
+1. Implement real Tryout Settings.
+2. Implement Question Detail and Variant Detail pages.
+3. Implement AI run history, review filters, batch generation, and bulk review.
+4. Implement Question Quality with real metrics.
+5. Implement Data Overview/Hierarchy in React.
+6. Wire report export buttons.
+
+### Phase 3: Student portal
+
+1. Add learner tryout listing.
+2. Add active session page using `/session/{id}/next_item` and `/submit_answer`.
+3. Add server-synced timer from `expires_at`.
+4. Persist session recovery state.
+5. Add completion and result pages.
+
+### Phase 4: Migration hardening
+
+1. Decide the final production routing split between frontend `/admin/*` and backend `/api/v1/*`.
+2. Disable or move legacy Python admin once React parity is complete.
+3. Add Playwright smoke tests for login, website switch, import preview, tryout drilldown, AI preview, normalization save, and reports.
+4. Add a route/API smoke test that verifies every visible navigation target and button either works or is intentionally disabled.
+
+## 10. Suggested Test Plan
+
+Minimum tests before considering the React migration complete:
+
+| Test | Expected result |
+|---|---|
+| `npm run build` | Passes with no TypeScript errors. |
+| Login with local env | Hits `/api/v1/auth/admin-login`, stores token, lands on dashboard. |
+| First dashboard load | Waits for or uses a real selected website, never website `0`. |
+| Website switch | Dashboard, tryouts, questions, reports, and AI pending reviews refetch for the selected website. |
+| Tryout JSON preview/import | Calls `/api/v1/import-export/tryout-json/preview` and `/tryout-json`; new tryouts appear. |
+| Excel import | Calls real preview and import endpoints or the page is hidden. |
+| Normalization save | GET `/api/v1/tryout/{id}/config`, PUT `/api/v1/tryout/{id}/normalization`, visible success/error state. |
+| Reports | Requires tryout context and loads real calibration/item/student data. |
+| AI preview/save | Saves with correct basis slot and displays generated variant in review queue. |
+| AI review/bulk | Approve/reject/archive works and status updates are visible. |
+| XSS smoke | Imported HTML and AI HTML are sanitized before rendering. |
+| Student session | Start/resume/answer/complete/result works with server timer. |
+
+## 11. Final Assessment
+
+The migration has a good foundation: React, routing, TanStack Query, Zustand, shadcn-style components, Docker/Nginx serving, and a number of admin pages are already present. The main risk is that the UI currently looks further along than its backend integration really is.
+
+The highest leverage next move is to stabilize the API boundary: fix the `/api/v1` base URL, align endpoint paths and methods, make website scoping deterministic, and add website-aware query keys. Once that is done, the team can safely fill the larger parity gaps without chasing confusing 404s, empty dashboards, or stale tenant data.
diff --git a/FRONTEND_MIGRATION_CUTOVER.md b/FRONTEND_MIGRATION_CUTOVER.md
new file mode 100644
index 0000000..1d1d29e
--- /dev/null
+++ b/FRONTEND_MIGRATION_CUTOVER.md
@@ -0,0 +1,31 @@
+# Frontend Migration Cutover Notes
+
+## Route Ownership
+
+- React owns browser-facing admin routes under `/admin/*` and student routes under `/student/*`.
+- FastAPI owns JSON APIs under `/api/v1/*`.
+- The legacy Python admin remains available as fallback until React parity smoke tests are accepted.
+
+## Local Development
+
+- React Vite dev server: `http://127.0.0.1:5173`
+- Backend API root: `http://localhost:8000/api/v1`
+- Frontend API config should keep `VITE_API_URL` pointed at the FastAPI v1 root.
+- System-admin tokens may be global with `website_id: null`; React sends `X-Website-ID` only when the website selector has an explicit website.
+
+## Cutover Guardrails
+
+- Do not disable the legacy admin until React covers import, snapshot promotion, question detail, AI review, reports, normalization, settings, and student session smoke tests.
+- Avoid adding new frontend calls to legacy or nonexistent API paths. New React API calls should map to OpenAPI paths.
+- Website-scoped React Query keys must include the selected website ID and should be gated until a website is selected.
+- Any page rendering question HTML must use the shared `SafeHtml` component.
+
+## Smoke Coverage Used During Migration Fix
+
+- Admin dashboard
+- Global questions list and question detail
+- Data overview hierarchy
+- AI review, variants, and run history
+- Excel import
+- Tryout questions, snapshot promotion, settings, normalization, and AI workspace
+- Student tryout list, session start, next item, answer submission, completion, and result summary
diff --git a/UX_AUDIT_ADMIN_FLOW.md b/UX_AUDIT_ADMIN_FLOW.md
new file mode 100644
index 0000000..57007ff
--- /dev/null
+++ b/UX_AUDIT_ADMIN_FLOW.md
@@ -0,0 +1,439 @@
+# UX Audit: Admin Flow - IRT Bank Soal
+
+> **Audit Date:** 2026-06-17
+> **Auditor:** Dev Agent
+> **Focus:** Login → First-time experience → Navigation discoverability → Hierarchy visibility
+
+---
+
+## Table of Contents
+
+1. [Executive Summary](#executive-summary)
+2. [Login Flow Analysis](#login-flow-analysis)
+3. [Post-Login Experience](#post-login-experience)
+4. [Navigation & Discoverability](#navigation--discoverability)
+5. [Hierarchy Visibility](#hierarchy-visibility)
+6. [Issue Summary & Priority Matrix](#issue-summary--priority-matrix)
+7. [Recommended Improvements](#recommended-improvements)
+8. [Appendix: Current vs Proposed Flow](#appendix-current-vs-proposed-flow)
+
+---
+
+## Executive Summary
+
+The current admin flow has significant UX gaps that make it difficult for new administrators to orient themselves and complete tasks efficiently. The main issues are:
+
+| Category | Severity | Count |
+|----------|----------|-------|
+| Critical (blocks usage) | 🔴 High | 4 |
+| Medium (confuses users) | 🟡 Medium | 6 |
+| Low (minor friction) | 🟢 Low | 5 |
+
+### Key Findings
+
+1. **No onboarding guidance** after login - users land on Dashboard with no context
+2. **Hierarchy is hidden** in Settings submenu - should be prominently visible
+3. **Navigation labels are inconsistent** - mixed technical and human terms
+4. **Login page lacks branding** - no visual connection to the product
+5. **No breadcrumb navigation** - users get lost in deep pages
+
+---
+
+## Login Flow Analysis
+
+### Current State
+
+The login page (`/admin/login`) presents:
+- Simple username/password form
+- "Remember me" checkbox
+- Minimal error messaging
+- Help button (bottom-right corner)
+
+```python
+# Current login form elements
+- Username field
+- Password field
+- Remember me checkbox
+- Sign in button
+```
+
+### Issues Found
+
+| # | Issue | Impact | Severity |
+|---|-------|--------|----------|
+| 1.1 | **No product branding/logo** | Users don't know what system they're logging into | 🟡 Medium |
+| 1.2 | **No error state distinction** | Failed login looks same as rate limiting | 🟡 Medium |
+| 1.3 | **"Remember me" is unclear** | Doesn't explain session duration or implications | 🟢 Low |
+| 1.4 | **No "forgot password" path** | No recovery mechanism exists | 🟡 Medium |
+| 1.5 | **Help button is discoverable** | Good: floating help exists but underutilized | 🟢 Positive |
+
+### Login → Dashboard Redirect
+
+**Current behavior:** After successful login → `/admin/dashboard`
+
+**What users see:**
+```
+┌─────────────────────────────────────────┐
+│ Good Morning, admin! 👋 │
+│ Here's what's happening today. │
+│ │
+│ ⚠️ 25 questions need calibration │
+│ 📝 3 AI-generated questions pending │
+│ 💡 Tip: Start by importing questions... │
+│ │
+│ 📊 System Overview │
+│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
+│ │ 5 │ │ 150 │ │ 890 │ │ 2 │ │
+│ │Exams │ │Quest │ │Tests │ │Sites │ │
+│ └──────┘ └──────┘ └──────┘ └──────┘ │
+└─────────────────────────────────────────┘
+```
+
+### Problems After Login
+
+| # | Issue | Why It's a Problem |
+|---|-------|-------------------|
+| 2.1 | **No welcome message explaining the system** | First-time users don't know what IRT Bank Soal does |
+| 2.2 | **"5 Exams" is meaningless without context** | Users don't know what an Exam/Tryout means |
+| 2.3 | **Alerts are action-oriented but not instructive** | "Import questions" - but where? How? |
+| 2.4 | **Quick Actions use technical language** | "Generate AI Questions" doesn't explain what happens |
+| 2.5 | **No first-time setup wizard** | Empty state users have no guidance |
+
+---
+
+## Navigation & Discoverability
+
+### Current Navigation Structure
+
+```
+Sidebar Navigation (collapsed view):
+┌─────────────────────────┐
+│ IRT Bank Soal Admin │
+├─────────────────────────┤
+│ 📊 Dashboard │ ← Always first
+│ 📝 Questions │ ← What is this?
+│ 📥 Import Questions │ ← Separate from Questions?
+│ 🤖 AI Generator │ ← Is this part of Questions?
+│ 📋 Exams │ ← Tryout = Exam?
+│ 📈 Reports │
+│ ⚙️ Settings │ ← Hierarchy buried here
+│ ─────────────────────── │
+│ 🚪 Logout │
+└─────────────────────────┘
+```
+
+### Label Analysis
+
+| Current Label | User Interpretation | Issue |
+|---------------|---------------------|-------|
+| Questions | "Where I view questions?" | ✅ Clear |
+| Import Questions | "Is this separate from Questions?" | ⚠️ Unclear relationship |
+| AI Generator | "What does AI Generate?" | ⚠️ Vague |
+| Exams | "Same as Tryout?" | ⚠️ Mismatch with backend term |
+| Reports | "Student scores?" | ✅ Clear |
+| Settings → Hierarchy | "What is hierarchy?" | 🔴 Wrong place + wrong term |
+
+### Missing Navigation Features
+
+| # | Missing Feature | Impact |
+|---|-----------------|--------|
+| 3.1 | **No breadcrumbs** | Users can't trace their path back |
+| 3.2 | **No "back to parent" links** | Deep pages have no escape route |
+| 3.3 | **No search/global nav** | Can't jump to specific pages |
+| 3.4 | **No recent pages** | Can't quickly return to work in progress |
+| 3.5 | **Settings is a catch-all** | Mixes Website management, Hierarchy, Password |
+
+---
+
+## Hierarchy Visibility
+
+### Current Hierarchy Location
+
+Hierarchy is located at: **Settings → Data Structure** (`/admin/hierarchy`)
+
+### Problems with Current Hierarchy Placement
+
+| # | Issue | Why It Matters |
+|---|-------|----------------|
+| 4.1 | **Buried 2 levels deep** | First-time users never find it |
+| 4.2 | **Label is technical** | "Data Structure" vs "How data connects" |
+| 4.3 | **No explanation of the hierarchy concept** | Users don't know Website → Tryout → Questions → Variants |
+| 4.4 | **No visual flowchart on Dashboard** | Users should see the big picture immediately |
+
+### Expected Mental Model
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ USER'S EXPECTED FLOW │
+├─────────────────────────────────────────────────────────────┤
+│ │
+│ 1. Website (where exams are hosted) │
+│ │ │
+│ ▼ │
+│ 2. Tryout/Exam (the test itself) │
+│ │ │
+│ ▼ │
+│ 3. Questions (individual items in the test) │
+│ │ │
+│ ├── Original/Basis Question ──────────────────────┐ │
+│ │ │ │ │
+│ │ ▼ │ │
+│ │ AI Variant (different version) │ │
+│ │ │ │
+│ └── (repeated for each question slot) │ │
+│ │
+└─────────────────────────────────────────────────────────────┘
+```
+
+### Where Users Expect Hierarchy Info
+
+| Location | User Expectation |
+|----------|------------------|
+| **Dashboard** | "Show me the big picture" - visual overview |
+| **First-time tooltip** | "Here's how things connect" |
+| **Help/Docs** | "Explain the data model" |
+| **Settings sidebar** | ❌ Too late - user already lost |
+
+---
+
+## Issue Summary & Priority Matrix
+
+### Priority Matrix
+
+```
+ │ High Value │ Low Value │
+────────────────────┼──────────────┼──────────────┤
+High Effort │ [A] Refactor │ [B] Nice to │
+ │ Navigation │ have │
+────────────────────┼──────────────┼──────────────┤
+Low Effort │ [C] Quick │ [D] Ignore │
+ │ Wins │ │
+────────────────────┼──────────────┼──────────────┤
+```
+
+### Cell [A] - High Value, High Effort (Do First)
+
+| Issue ID | Description | Notes |
+|----------|-------------|-------|
+| P1 | **Add Dashboard onboarding section** | Explain the system + show hierarchy flow |
+| P2 | **Move Hierarchy to prominent location** | Dashboard or separate nav item |
+| P3 | **Redesign navigation labels** | Human-friendly, consistent terminology |
+| P4 | **Add breadcrumbs** | Across all pages |
+
+### Cell [C] - High Value, Low Effort (Quick Wins)
+
+| Issue ID | Description | Effort |
+|----------|-------------|--------|
+| Q1 | Add product logo to login page | 15 min |
+| Q2 | Improve dashboard welcome message | 10 min |
+| Q3 | Add "How it works" section to Dashboard | 30 min |
+| Q4 | Rename "Data Structure" → "Data Overview" in Settings | 5 min |
+| Q5 | Add contextual tooltips to Quick Actions | 20 min |
+
+### Cell [B] - Low Value, High Effort (Consider Later)
+
+| Issue ID | Description |
+|----------|-------------|
+| L1 | Global search across all pages |
+| L2 | Recent pages sidebar widget |
+| L3 | Full first-time setup wizard |
+
+### Cell [D] - Low Value, Low Effort (Ignore)
+
+| Issue ID | Description |
+|----------|-------------|
+| N1 | Custom "Remember me" tooltip |
+| N2 | Login page background gradient (cosmetic only) |
+
+---
+
+## Recommended Improvements
+
+### Phase 1: Critical Fixes (Same Session)
+
+#### 1. Login Page Enhancement
+
+```html
+
+
+ Adaptive Question Bank System
+Get started in 3 simple steps:
+ +Adaptive Question Bank System
+{escape(subtitle)}
+ {body.replace("__REMEMBER_ME_CHECKED__", remember_me_checked)} + +{escape(subtitle)}
- {body.replace("__REMEMBER_ME_CHECKED__", remember_me_checked)} -