616 lines
31 KiB
Markdown
616 lines
31 KiB
Markdown
# 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.
|