# S04: Review Queue Admin UI

**Goal:** Admin can review, edit, approve, reject, split, and merge extracted key moments via a web UI. Mode toggle switches between review mode (moments queued for human review) and auto mode (moments publish directly).
**Demo:** After this: Admin views pending key moments, approves/edits/rejects them, toggles between review and auto mode

## Tasks
- [x] **T01: Built 9 review queue API endpoints (queue, stats, approve, reject, edit, split, merge, get/set mode) with Redis mode toggle, error handling, and 24 integration tests — all passing alongside existing suite** — Create the complete review queue backend: new Pydantic schemas for review actions, a review router with 9 endpoints (list queue, stats, approve, reject, edit, split, merge, get mode, set mode), Redis-backed runtime mode toggle, mount in main.py, and comprehensive integration tests. Follow existing async SQLAlchemy patterns from routers/creators.py.

## Steps

1. Add review-specific Pydantic schemas to `backend/schemas.py`: `ReviewQueueItem` (KeyMomentRead + video title + creator name), `ReviewQueueResponse` (paginated), `ReviewStatsResponse` (counts per status), `MomentEditRequest` (editable fields: title, summary, start_time, end_time, content_type, plugins), `MomentSplitRequest` (split_time: float), `ReviewModeResponse` and `ReviewModeUpdate` (mode: bool).

2. Create `backend/routers/review.py` with these async endpoints:
   - `GET /review/queue` — List key moments filtered by `status` query param (pending/approved/edited/rejected/all), paginated with `offset`/`limit`, joined with SourceVideo.filename and Creator.name. Default filter: pending. Order by created_at desc.
   - `GET /review/stats` — Return counts grouped by review_status (pending, approved, edited, rejected) using SQL count + group by.
   - `POST /review/moments/{moment_id}/approve` — Set review_status=approved, return updated moment. 404 if not found.
   - `POST /review/moments/{moment_id}/reject` — Set review_status=rejected, return updated moment. 404 if not found.
   - `PUT /review/moments/{moment_id}` — Update editable fields from MomentEditRequest, set review_status=edited, return updated moment. 404 if not found.
   - `POST /review/moments/{moment_id}/split` — Split moment at `split_time` into two moments. Validate split_time is between start_time and end_time. Original keeps [start_time, split_time), new gets [split_time, end_time]. Both keep same source_video_id and technique_page_id. Return both moments. 400 on invalid split_time.
   - `POST /review/moments/{moment_id}/merge` — Accept `target_moment_id` in body. Merge two moments: combined summary, min(start_time), max(end_time), delete target, return merged result. Both must belong to same source_video. 400 if different videos. 404 if either not found.
   - `GET /review/mode` — Read current mode from Redis key `chrysopedia:review_mode`. If not in Redis, fall back to `settings.review_mode` default.
   - `PUT /review/mode` — Set mode in Redis key `chrysopedia:review_mode`. Return new mode.

3. Add Redis client helper. Create a small `backend/redis_client.py` module with `get_redis()` async function using `redis.asyncio.Redis.from_url(settings.redis_url)`. Import in review router.

4. Mount the review router in `backend/main.py`: `app.include_router(review.router, prefix="/api/v1")`.

5. Add `redis` (async redis client) to `backend/requirements.txt` if not already present.

6. Create `backend/tests/test_review.py` with integration tests using the established conftest patterns (async client, real PostgreSQL):
   - Test list queue returns empty when no moments exist
   - Test list queue returns moments with video/creator info after seeding
   - Test filter by status works (seed moments with different statuses)
   - Test stats endpoint returns correct counts
   - Test approve sets review_status=approved
   - Test reject sets review_status=rejected
   - Test edit updates fields and sets review_status=edited
   - Test split creates two moments with correct timestamps
   - Test split returns 400 for invalid split_time (outside range)
   - Test merge combines two moments correctly
   - Test merge returns 400 for moments from different videos
   - Test approve/reject/edit return 404 for nonexistent moment
   - Test mode get/set (mock Redis)

## Must-Haves

- [ ] All 9 review endpoints return correct HTTP status codes and response bodies
- [ ] Split validates split_time is strictly between start_time and end_time
- [ ] Merge validates both moments belong to same source_video
- [ ] Mode toggle reads/writes Redis, falls back to config default
- [ ] All review tests pass alongside existing test suite
- [ ] Review router mounted in main.py

## Failure Modes

| Dependency | On error | On timeout | On malformed response |
|------------|----------|-----------|----------------------|
| PostgreSQL | SQLAlchemy raises, FastAPI returns 500 | Connection timeout → 500 | N/A (ORM handles) |
| Redis (mode toggle) | Return 503 with error detail | Timeout → fall back to config default | N/A (simple get/set) |

## Negative Tests

- **Malformed inputs**: split_time outside moment range → 400, merge moments from different videos → 400, edit with empty title → validation error
- **Error paths**: approve/reject/edit/split nonexistent moment → 404, merge with nonexistent target → 404
- **Boundary conditions**: split at exact start_time or end_time → 400, merge moment with itself → 400, empty queue → empty list

## Verification

- `cd backend && python -m pytest tests/test_review.py -v` — all tests pass
- `cd backend && python -m pytest tests/ -v` — no regressions (all existing tests still pass)
- `python -c "from routers.review import router; print(len(router.routes))"` — prints 9 (routes registered)

## Observability Impact

- Signals added: INFO log on each review action (approve/reject/edit/split/merge) with moment_id
- How a future agent inspects: `GET /api/v1/review/stats` shows pending/approved/edited/rejected counts
- Failure state exposed: 404 responses include moment_id that was not found, 400 responses include validation details
  - Estimate: 2h
  - Files: backend/schemas.py, backend/routers/review.py, backend/redis_client.py, backend/main.py, backend/requirements.txt, backend/tests/test_review.py
  - Verify: cd backend && python -m pytest tests/test_review.py -v && python -m pytest tests/ -v
- [x] **T02: Bootstrapped React + Vite + TypeScript frontend with typed API client for all 9 review endpoints, React Router shell, and admin CSS foundation** — Replace the placeholder frontend with a real React + Vite + TypeScript application. Install dependencies, configure Vite with API proxy for development, create the app shell with React Router, and build a typed API client module for the review endpoints. Verify `npm run build` produces `dist/index.html` compatible with the existing Docker build pipeline.

## Steps

1. Initialize the React app in `frontend/`. Replace `package.json` with proper dependencies:
   - `react`, `react-dom`, `react-router-dom` for the app
   - `typescript`, `@types/react`, `@types/react-dom` for types
   - `vite`, `@vitejs/plugin-react` for build tooling
   - Scripts: `dev` → `vite`, `build` → `tsc -b && vite build`, `preview` → `vite preview`

2. Create `frontend/vite.config.ts` with React plugin and dev server proxy (`/api` → `http://localhost:8001`) so the frontend dev server can reach the backend during development.

3. Create `frontend/tsconfig.json` and `frontend/tsconfig.app.json` with strict TypeScript config targeting ES2020+ and JSX.

4. Create `frontend/index.html` — Vite entry point with `<div id="root">` and `<script type="module" src="/src/main.tsx">`.

5. Create app shell files:
   - `frontend/src/main.tsx` — ReactDOM.createRoot, render App with BrowserRouter
   - `frontend/src/App.tsx` — Routes: `/admin/review` → ReviewQueue page, `/admin/review/:momentId` → MomentDetail page, `/` → redirect to `/admin/review`. Simple nav header with "Chrysopedia Admin" title.
   - `frontend/src/App.css` — Minimal admin styles: clean sans-serif typography, card-based layout, status badge colors (pending=amber, approved=green, edited=blue, rejected=red)

6. Create `frontend/src/api/client.ts` — Typed API client with functions for all review endpoints:
   - `fetchQueue(params)` → GET /api/v1/review/queue
   - `fetchStats()` → GET /api/v1/review/stats
   - `approveMoment(id)` → POST /api/v1/review/moments/{id}/approve
   - `rejectMoment(id)` → POST /api/v1/review/moments/{id}/reject
   - `editMoment(id, data)` → PUT /api/v1/review/moments/{id}
   - `splitMoment(id, splitTime)` → POST /api/v1/review/moments/{id}/split
   - `mergeMoments(id, targetId)` → POST /api/v1/review/moments/{id}/merge
   - `getReviewMode()` → GET /api/v1/review/mode
   - `setReviewMode(enabled)` → PUT /api/v1/review/mode
   All functions use fetch() with proper error handling. TypeScript interfaces for all request/response types.

7. Create placeholder page components (just enough to verify routing works):
   - `frontend/src/pages/ReviewQueue.tsx` — renders "Review Queue" heading + "Loading..." text
   - `frontend/src/pages/MomentDetail.tsx` — renders "Moment Detail" heading + shows moment ID from URL params

8. Run `npm install` and `npm run build` to verify the build produces `dist/index.html`. Verify the output directory structure matches what `docker/Dockerfile.web` expects.

## Must-Haves

- [ ] `npm run build` succeeds and produces `dist/index.html`
- [ ] `npm run dev` starts Vite dev server
- [ ] React Router routes `/admin/review` and `/admin/review/:momentId` render correctly
- [ ] API client module exports typed functions for all 9 review endpoints
- [ ] TypeScript compilation passes with no errors
- [ ] Build output is compatible with existing `docker/Dockerfile.web` (files in `dist/`)

## Verification

- `cd frontend && npm run build && test -f dist/index.html` — build succeeds
- `cd frontend && npx tsc --noEmit` — TypeScript has no errors
- `grep -q 'fetchQueue\|approveMoment\|getReviewMode' frontend/src/api/client.ts` — API client has key functions
  - Estimate: 1h
  - Files: frontend/package.json, frontend/vite.config.ts, frontend/tsconfig.json, frontend/tsconfig.app.json, frontend/index.html, frontend/src/main.tsx, frontend/src/App.tsx, frontend/src/App.css, frontend/src/api/client.ts, frontend/src/pages/ReviewQueue.tsx, frontend/src/pages/MomentDetail.tsx
  - Verify: cd frontend && npm run build && test -f dist/index.html && npx tsc --noEmit
- [x] **T03: Built complete admin review queue UI: queue list page with stats bar, status filter tabs, pagination, mode toggle; moment detail page with approve/reject/edit/split/merge actions and modal dialogs; reusable StatusBadge and ModeToggle components** — Implement the full admin review queue UI: the queue list page with status filter tabs and stats summary, the moment detail/review page with approve/reject/edit/split/merge actions, and the review mode toggle. Wire all pages to the API client from T02.

## Steps

1. Build `frontend/src/pages/ReviewQueue.tsx` — the main admin page:
   - Stats bar at top showing counts per status (pending, approved, edited, rejected) fetched from `/api/v1/review/stats`
   - Filter tabs: All, Pending, Approved, Edited, Rejected — clicking a tab filters the queue list
   - Queue list: cards showing moment title, summary excerpt (first 150 chars), video filename, creator name, review_status badge, timestamps. Click card → navigate to `/admin/review/{momentId}`
   - Pagination: Previous/Next buttons with offset/limit
   - Mode toggle in header area: switch between Review Mode and Auto Mode, calls `PUT /api/v1/review/mode`. Show current mode with visual indicator (green dot for review, amber for auto)
   - Empty state: show message when no moments match the current filter
   - Use `useEffect` + `useState` for data fetching (no external state library needed for a single-admin tool)

2. Build `frontend/src/pages/MomentDetail.tsx` — individual moment review page:
   - Display full moment data: title, summary, content_type, start_time/end_time (formatted as mm:ss), plugins list, raw_transcript (if available), review_status badge
   - Show source video filename and creator name
   - Action buttons row:
     - Approve (green) — calls `POST .../approve`, navigates back to queue on success
     - Reject (red) — calls `POST .../reject`, navigates back to queue on success
     - Edit — toggles inline edit mode for title, summary, content_type fields. Save button calls `PUT .../` with edited data
     - Split — opens a split dialog: text input for split timestamp (validated between start_time and end_time), calls `POST .../split`
     - Merge — opens a merge dialog: dropdown to select another moment from same video, calls `POST .../merge`
   - Back link to queue
   - Loading and error states for all API calls

3. Create `frontend/src/components/StatusBadge.tsx` — reusable status badge component with color coding (pending=amber, approved=green, edited=blue, rejected=red).

4. Create `frontend/src/components/ModeToggle.tsx` — review/auto mode toggle component extracted from the queue page for reuse in the header.

5. Update `frontend/src/App.tsx` if needed to add the mode toggle to the global nav header.

6. Update `frontend/src/App.css` with styles for:
   - Stats bar (flex row of count cards)
   - Filter tabs (horizontal tab bar with active indicator)
   - Queue cards (bordered cards with hover effect)
   - Status badges (colored pill shapes)
   - Action buttons (colored, with hover/disabled states)
   - Edit form (inline fields with save/cancel)
   - Split/merge dialogs (modal overlays)
   - Responsive layout (single column on narrow screens)

7. Verify `npm run build` still succeeds after all UI changes.

## Must-Haves

- [ ] Queue page loads and displays moments from API with status filter tabs
- [ ] Stats bar shows correct counts per review status
- [ ] Clicking a moment navigates to detail page
- [ ] Approve, reject actions work and navigate back to queue
- [ ] Edit mode allows inline editing of title/summary/content_type with save
- [ ] Split dialog validates split_time and creates two moments
- [ ] Merge dialog shows moments from same video and merges on confirm
- [ ] Mode toggle reads and updates review/auto mode via API
- [ ] Build succeeds with no TypeScript errors

## Verification

- `cd frontend && npm run build && test -f dist/index.html` — build succeeds
- `cd frontend && npx tsc --noEmit` — no TypeScript errors
- `grep -q 'StatusBadge\|ModeToggle' frontend/src/pages/ReviewQueue.tsx` — components integrated
- `grep -q 'approve\|reject\|split\|merge' frontend/src/pages/MomentDetail.tsx` — all actions present
  - Estimate: 2h
  - Files: frontend/src/pages/ReviewQueue.tsx, frontend/src/pages/MomentDetail.tsx, frontend/src/components/StatusBadge.tsx, frontend/src/components/ModeToggle.tsx, frontend/src/App.tsx, frontend/src/App.css
  - Verify: cd frontend && npm run build && test -f dist/index.html && npx tsc --noEmit
