# S04 — Review Queue Admin UI — Research

**Date:** 2026-03-29

## Summary

S04 builds the admin review queue for Chrysopedia — the UI and API that let an administrator review, edit, approve, reject, split, and merge extracted key moments before they're published. The spec (§8) defines two modes: **review mode** (all moments queued for human review) and **auto mode** (moments publish directly, queue becomes an audit log). A system-level mode toggle switches between them. The `review_mode: bool = True` setting already exists in `config.py`.

The work naturally splits into two halves: **backend API endpoints** for review queue operations (list/filter moments, approve/reject/edit, split/merge, mode toggle, status counts) and a **React frontend** admin UI. The frontend is currently a bare placeholder (`package.json` with no framework) but the Docker build pipeline (Node build → nginx SPA serving with `/api/` proxy) is already wired. The backend has established patterns for async SQLAlchemy endpoints, Pydantic schemas, and test fixtures.

This is medium-complexity CRUD + admin UI work. The data model already has `KeyMoment.review_status` (pending/approved/edited/rejected) and `SourceVideo.processing_status`. The riskiest parts are: (1) split/merge operations on key moments (modifying timestamps and creating/deleting rows), and (2) bootstrapping the React app from zero. No novel technology or unfamiliar APIs involved.

## Recommendation

Build the backend review API first (new `routers/review.py`), then initialize the React frontend with a minimal admin UI. Use the existing async SQLAlchemy patterns from `routers/creators.py` and `routers/videos.py`. For the frontend, use React + React Router + a lightweight fetching approach (fetch or a small library). No heavy framework needed — this is a single-user admin tool, not a high-traffic public site.

The mode toggle should be a runtime-mutable setting, not just the config file default. Since there's only one admin, store the toggle in Redis (like stage 4's classification data) or add a `system_settings` table. Redis is simpler and already used by the project.

## Implementation Landscape

### Key Files

**Existing (read/extend):**
- `backend/models.py` — Has `KeyMoment` (with `review_status: ReviewStatus`), `SourceVideo` (with `processing_status`), `TechniquePage`. All the DB models needed for review are already defined.
- `backend/schemas.py` — Has `KeyMomentRead`, `KeyMomentBase`. Needs new schemas for review actions (approve, edit, split, merge responses).
- `backend/config.py` — Has `review_mode: bool = True`. This is the default; runtime toggle needs a separate mechanism.
- `backend/database.py` — `get_session` async dependency. Used by all routers.
- `backend/main.py` — Mount point for new review router.
- `backend/tests/conftest.py` — Test infrastructure with async/sync fixtures, `pre_ingested_video`.
- `backend/pipeline/stages.py` — `stage5_synthesis` reads `settings.review_mode` to set processing_status. The mode toggle affects new pipeline runs, not existing moments.
- `backend/routers/pipeline.py` — `POST /pipeline/trigger/{video_id}` for re-processing after prompt edits.

**New files to create:**
- `backend/routers/review.py` — Review queue API endpoints:
  - `GET /api/v1/review/queue` — List key moments with filter (status), pagination, grouped by video
  - `GET /api/v1/review/stats` — Counts by review_status (pending, approved, edited, rejected)
  - `POST /api/v1/review/moments/{moment_id}/approve` — Set review_status=approved
  - `POST /api/v1/review/moments/{moment_id}/reject` — Set review_status=rejected
  - `PUT /api/v1/review/moments/{moment_id}` — Edit fields + set review_status=edited
  - `POST /api/v1/review/moments/{moment_id}/split` — Split into two moments
  - `POST /api/v1/review/moments/{moment_id}/merge` — Merge with adjacent moment
  - `GET /api/v1/review/mode` — Get current review/auto mode
  - `PUT /api/v1/review/mode` — Toggle review/auto mode
- `backend/tests/test_review.py` — Integration tests for review API
- `frontend/src/` — React app source (App, Router, pages)
- `frontend/package.json` — Updated with React, build tooling
- `frontend/vite.config.ts` — Vite config for React build
- `frontend/index.html` — SPA entry point
- `frontend/src/pages/ReviewQueue.tsx` — Queue view with filter tabs, status counts
- `frontend/src/pages/MomentReview.tsx` — Individual moment review with actions
- `frontend/src/components/` — Shared UI components

### Build Order

1. **Backend review API + tests** — Build `routers/review.py` with all endpoints, add schemas, mount in `main.py`, write integration tests. This is the foundation — the frontend is just a consumer of these endpoints. Prove the API works with tests before touching the frontend.

2. **React app bootstrap** — Initialize the React app in `frontend/` with Vite + TypeScript. Get `npm run dev` and `npm run build` working. Verify the Docker build pipeline produces a working SPA.

3. **Review queue UI pages** — Build the queue view (list moments, filter tabs, status counts) and the moment review page (display moment + raw transcript, action buttons). Wire to the backend API.

4. **Mode toggle + integration** — Add the mode toggle UI, connect to the backend toggle endpoint. Verify the full flow: pipeline produces moments → admin reviews/approves → status updates correctly.

### Verification Approach

**Backend:**
- `cd backend && python -m pytest tests/test_review.py -v` — All review API tests pass
- `cd backend && python -m pytest tests/ -v` — All existing tests still pass (no regressions)
- Manual curl: `GET /api/v1/review/stats` returns counts, `POST .../approve` changes status

**Frontend:**
- `cd frontend && npm run build` — Build succeeds, produces `dist/index.html`
- `cd frontend && npm run dev` — Dev server starts, admin UI renders
- Navigate to admin review queue page, see list of moments (requires seeded data or mocked API)

**Integration:**
- Docker compose builds both services successfully
- Nginx proxies `/api/` to backend, serves frontend SPA on `/`

## Constraints

- **Async-only in FastAPI handlers** — All review endpoints must use async SQLAlchemy (`AsyncSession`), following the pattern in existing routers. The sync engine/session pattern is only for Celery tasks.
- **No auth** — The spec doesn't mention authentication for S04. This is a single-admin internal tool. Auth can be added later if needed.
- **Mode toggle persistence** — `config.py`'s `review_mode` comes from environment variable and is cached via `lru_cache`. A runtime toggle needs Redis or a DB table; changing env vars at runtime is fragile. Redis is the simpler choice — the project already uses it for stage 4 classification data.
- **KeyMoment lacks topic_tags/topic_category columns** — Classification data is in Redis (24h TTL). The review UI should display tags from Redis if available, or show "not classified" if Redis data has expired. This is a read-only concern for S04.
- **Existing Docker build expects `npm run build` to produce `dist/`** — The frontend build must output to `frontend/dist/` for the nginx Dockerfile to work.

## Common Pitfalls

- **Split/merge moment complexity** — Splitting a moment requires creating a new `KeyMoment` row and adjusting timestamps on both the original and new row. Merging requires combining summaries and extending timestamp ranges, then deleting one row. Both operations must handle the `technique_page_id` foreign key — split moments should keep the same page link, merged moments should keep one.
- **Redis mode toggle vs config.py** — If the mode toggle is stored in Redis but `pipeline/stages.py` reads `settings.review_mode` from config, changing the toggle won't affect running pipeline tasks. The pipeline needs to read the toggle from the same source as the admin UI. Either the pipeline reads from Redis too, or the toggle updates the Settings object.
- **Frontend build from zero** — Installing React + Vite + TypeScript from scratch in the existing `frontend/` directory. The `package.json` exists but has no deps. Need to be careful not to break the Docker build — the Dockerfile runs `npm ci` then `npm run build`.
