Docs / console/content-surfaces.md · mirrored from the repo
Back-office — Content Production surfaces (IA + wireframes)
Status: design / brainstorm. Low-fidelity — this fixes information architecture, states, actions, and the API contract each screen implies, not final visual design (the console already has its theme + components in apps/console). Wireframes were produced in the design session; this doc is the durable capture.
Companion to docs/CONTENT-PRODUCTION.md §14 (the surface list) — that doc owns the pipeline; this one owns how an operator sees and drives it.
Why design the UI now
The async OpenAI Batch model (up to ~48h end-to-end, partial failures, eval freshness/staleness) is hard to make legible. The screen is the forcing function for the API contract — sketching it tells us exactly which states + fields the backend must expose before we write a migration. It also honours the project’s first principle: the content pipeline is itself a product surface.
Design principles
- Reuse the existing console (React + Vite, CDS theme,
apps/consolecomponents). New surfaces are new routes, not a new app. - Single + bulk actions are first-class on every list (Production, Staging, Samples, Heimdall flagged, DLQ) — backed by batch endpoints, never per-row loops.
- URL-state: tab / filters / selection persist in the URL and survive refresh
(
useSearchParams) — see memoryui-url-state-preservation. - Make the async waits legible: anywhere a thing sits in a ~24h batch, show elapsed + ETA, not just a spinner.
Information architecture
A new top-level “Content production” section in the sidebar, with a horizontal tab group:
Content production
├─ Production ← batch-centric live pipeline (default)
├─ Staging gallery ← quarantined assets, visual QA + publish
├─ Samples ← versioned sample sets + execute-batch
├─ Heimdall ← quality evals (timeline, scorecards, flagged)
└─ DLQ ← failed shots, retry/reject [badge: count]
RBAC gates visibility per tab (mirrors permissions.ts
- the new perms in CONTENT-PRODUCTION.md §15); MVP =
adminonly.
1. Production board (default)
Question it answers: where is everything right now, and what’s stuck?
Layout
[ tabs ]
[ metric cards: Awaiting batch · Staged · Failed · Spent today ]
[ bulk bar: ☑ N selected · retry failed · hold ] [ filters: brief ▾ · stage ▾ ]
[ batch card ] ← one per content batch
☑ Diwali diyas · hi · 200 shots [ image batch · 6h elapsed · ETA ~18h ]
brief b7 · strategy@v7
▓▓▓▓▓▓ in-batch ▓ rendered ▓ embedding ▓ staged ▮ failed (segmented bar, counts)
[ batch card ] … staged · ready for QA → [run QA] [open in staging] [retry 8]
[ batch card ] … assembling JSONL
[ legend: in batch · rendered · embedding · staged · failed ]
Key states per batch: assembling → image batch (waiting) → rendered → embed batch (waiting) → staged → in QA → published; plus a failed count. The active OpenAI phase shows
elapsed + ETA (ETA ≈ submitted_at + 24h, best-effort).
Actions: select → bulk retry-failed / hold; per-batch run-QA, open-in-staging, retry-N-failed.
API contract this implies (validates generation_batch in CONTENT-PRODUCTION.md §16):
one grouped query returning, per batch — { batch_id, brief_id, strategy_version, total, counts_by_stage{}, active_phase, phase_submitted_at, cost_cents, failed_count }. Bulk
actions → batch endpoints (POST /batches/:id/retry, …/hold). Live updates via the
run-event stream (CONTENT-PRODUCTION.md §17).
2. Staging gallery
Question: what’s quarantined, does it look right, and can I publish it?
Layout: thumbnail grid of STAGED assets; left filters (brief, variety cell,
eval-status: scored / flagged / un-evaluated); multi-select → bulk publish / unpublish /
reject. Each tile shows the thumbnail, a tiny eval badge, and select checkbox; click → asset
detail (variants, metadata, scorecard, provenance).
Contract: paged asset query with facet filters (status, brief_id, variety_cell, eval_status); thumbnail + placeholder served from R2/CF Images; publish/unpublish/reject =
bulk endpoints honouring the veto (refuse UNRELEASABLE). RBAC content:publish.
3. Samples
Question: do the probes look good — ship the batch fresh, or fold the samples in?
Layout: per brief, a list of versioned sample sets (samples@v1, v2…); open one →
side-by-side tiles with per-asset scorecards. Primary action Execute batch with the
toggle: include approved samples (count toward brief.count, generate the remainder) vs
generate fresh (samples were a probe; ignore in count).
Contract: sample_sets / sample_items (CONTENT-PRODUCTION.md §16); samples are
sync-rendered (fast feedback) and quarantine-discarded unless promoted. RBAC
content:sample.
4. Heimdall (quality evals)
Question: is the staged content safe to ship, and is this eval still trustworthy?
Layout
[ eval-run v5 status card ]
84 / 1,200 sampled · stratified + risk-weighted · 95% CI ±5% · 320 staged since
[ 27% stale ] [ extend eval +50 ] [ re-run (fresh) ]
[ composite-score trend across runs ] [ v5 per-metric bars ]
bars per eval-run brand-fit · aesthetic · legibility · …
red dot = veto count (not averaged) ▸ cultural-correctness veto: N unreleasable
[ flagged inbox ] ☑ asset · reason (veto/low) · [view] + bulk reject / override
The novel bits (CONTENT-PRODUCTION.md §12):
- Freshness/staleness front-and-centre —
staleness% = new_staged_since_eval / population; a STALE badge past threshold; CTAs extend eval (+N) (sequential sampling into the same run) vs re-run fresh. - Sampling provenance — “84/1,200 sampled, 95% CI ±5%”; drill-in lists which assets
were evaluated (
eval_samples). - Cultural-correctness veto is broken out, never averaged (CP-4) — a hard gate that blocks publish; shown as a distinct red callout + red veto-count dots on the trend.
- Flagged inbox — only flagged/vetoed/low-sampled assets reach a human; single + bulk
reject / override-veto (
eval:override).
Contract: eval_runs (snapshot, sampling config, freshness), asset_scorecards,
eval_samples (provenance); metrics are Langfuse prompts (CD-3). RBAC eval:read / eval:run
/ eval:override.
5. DLQ inbox
Question: what failed, why, and can I retry or drop it?
Layout: list of failed shots — thumbnail (if any), shot/brief, structured reason (batch
per-request error, expiry, post-processing failure), attempt count; single + bulk retry
(re-batches) / reject. RBAC content:retry.
Contract: shots/generation_batch error_file_id rows surfaced with reason + attempts;
retry → re-assemble a fresh small batch (CONTENT-PRODUCTION.md §5.3).
Open questions
- Live updates: poll vs SSE (the planned notification service) for the Production board’s batch-phase + counts — start with poll, move to SSE when the notification port lands (NOTIFICATION-SERVICE.md).
- Asset detail view depth (variants, full scorecard, lineage) — its own route vs a drawer.
- Whether Samples and Staging share one asset-grid component (likely yes).
- Final visual pass once these IA + contracts are agreed (this doc is structure only).