From 81352d7bd28af6a54e1e5dcff1ef797960a1c127 Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 9 Jun 2026 21:59:48 +0200 Subject: [PATCH] docs(final-docs): spec for judge-doc curation + optional revised uploads Co-Authored-By: Claude Opus 4.8 (1M context) --- ...le-doc-curation-optional-uploads-design.md | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 docs/superpowers/specs/2026-06-09-finale-doc-curation-optional-uploads-design.md diff --git a/docs/superpowers/specs/2026-06-09-finale-doc-curation-optional-uploads-design.md b/docs/superpowers/specs/2026-06-09-finale-doc-curation-optional-uploads-design.md new file mode 100644 index 0000000..2990d2a --- /dev/null +++ b/docs/superpowers/specs/2026-06-09-finale-doc-curation-optional-uploads-design.md @@ -0,0 +1,93 @@ +# Grand Final: judge-visible document curation + optional revised uploads + +**Date:** 2026-06-09 +**Status:** Approved (Matt, this session) +**Builds on:** `2026-06-09-grand-final-documents-design.md` and the same-day pivot (commits `f8f2d77`, `8a4184d`) + +## Problem + +Feedback from the other program admin: + +> Jury actually need to see BP + Exec summary + 1min video — the ones they uploaded already. And candidates should be able to upload their PDF pres + video — optional, as some sent it another way. + +Two gaps against what is deployed: + +1. **Judges see too much.** `listFinalistDocumentsForReview` returns *every* file each finalist team ever submitted (5–7 per team on prod: Pitch Deck, Intro Video, Executive Summary, Business Plan, Promotional Video, plus any Grand Final uploads). The admin wants judges to see a curated subset (BP + exec summary + 1-min video). There is no way to choose which prior documents are surfaced. +2. **"Optional uploads" mode renders wrong.** The three modes the admin wants are: no new uploads (toggle OFF — works), mandatory uploads (toggle ON + slots required — works), and optional uploads (toggle ON + slots marked not-required). In the all-optional case, `FinalDocumentStatus.allRequiredUploaded` is hardcoded `false` when zero slots are required, so the finalist banner/panel never reach a settled state and the copy implies the docs are mandatory. + +Prod facts (verified 2026-06-09 via read-only query): 9 finalist teams, 48 prior files + 3 Grand Final uploads. Every team has all 5 prior doc types. The Business Plan and the 1-minute promo video live under *two different* `FileRequirement` rows depending on the team's path (Semi-Finals Document Submission for 8 teams, Spotlight on Africa Submission Round for 1 team — Blue Fields Company). + +## Part 1 — Admin curation of judge-visible documents + +### Storage + +New optional key on the LIVE_FINAL round's `configJson`: + +```ts +reviewVisibleRequirementIds?: string[] // FileRequirement ids from prior rounds +``` + +Semantics: +- **absent / null** → show all prior files (current behavior; safe default, no migration needed) +- **non-empty array** → show only prior files whose `requirementId` is in the list +- **empty array** → hide all prior files (only Grand Final uploads remain visible) +- **Grand Final round uploads are always shown**, regardless of the selection — they are what the team explicitly submitted for the finale +- Prior files with no `requirementId` (fileType-only) are excluded whenever a selection is active. (All 48 prod files have a requirement, so nothing is lost in practice.) + +### Service (`src/server/services/final-documents.ts`) + +`listFinalistDocumentsForReview` adds `requirementId` to its file select and applies the filter above using the finale round's `configJson`. No signature change; `ReviewPayload` unchanged. + +New helper to power the admin picker: list the distinct prior-round requirement slots referenced by the finalist teams' files — `{ requirementId, name, roundName, fileCount }`, ordered by round sort then name. Derived from the same file query, so the picker only offers slots that actually have files. + +### tRPC (`src/server/routers/finalist.ts`, adminProcedure) + +- `getReviewDocSettings` → `{ options: Slot[], selectedIds: string[] | null }` (null = "all" mode) +- `setReviewVisibleRequirements({ requirementIds: string[] | null })` → writes/clears the configJson key (null clears back to "show all"). Audited like `setRevisedUploadSetting`. + +### Admin UI + +New card "Documents shown to judges" placed next to the existing revised-uploads toggle (`src/components/admin/grand-finale/final-docs-uploads-toggle.tsx`, rendered on the LIVE_FINAL round admin page `src/app/(admin)/admin/rounds/[roundId]/page.tsx`): + +- A "Show all submitted documents" master state (the default), and beneath it a checkbox per slot labeled `""` with the file count (e.g. "Business Plan — Semi-Finals Document Submission (8 files)"). +- Unchecking the master switches to curated mode with all boxes ticked; the admin then unticks what judges shouldn't see. Re-checking the master clears the selection (back to null/"all"). +- Copy notes that Grand Final uploads are always visible to judges. + +For the admin's stated goal, they'd switch to curated mode and leave 5 boxes ticked: Executive Summary (Intake), Business Plan (Semi-Finals + Spotlight), Promotional Video (Semi-Finals) and 1 Minute Promotional Video (Spotlight) — judges then see exactly BP + exec summary + 1-min video per team, plus any finale uploads. + +## Part 2 — All-optional upload mode fix + +`FinalDocumentStatus` (in `final-documents.ts`) gains: + +```ts +hasRequired: boolean // any slot with isRequired +allUploaded: boolean // requirements.length > 0 && every slot has a file, required or not +``` + +`allRequiredUploaded` keeps its current semantics (meaningful only when `hasRequired`). Edge case: if the toggle is ON but no slots are defined at all (`requirements.length === 0`), the banner and panel render nothing — no vacuous "(0 of 0)" complete state. + +UI changes: +- **Banner** (`src/components/applicant/final-documents-banner.tsx`): when `hasRequired` is false — title "Upload updated Grand Final documents (optional)", same neutral blue styling, keep per-doc checklist/count/deadline/upload button; green settled state ("Grand Final documents uploaded") only when `allUploaded`. +- **Panel** (`src/components/applicant/final-documents-panel.tsx`, team + mentor variants): "Submitted" badge driven by `hasRequired ? allRequiredUploaded : allUploaded`; description gains "(optional)" when nothing is required. + +Reminders need **no change** — verified: the cron and the untargeted manual blast already skip teams with no missing *required* docs, so all-optional mode never nags; an explicitly targeted manual reminder still sends (intentional admin override). + +## Out of scope (admin actions in existing UI, not code) + +- Flipping `allowFinalistRevisedUploads` ON +- Creating/adjusting the finale upload slots (PDF presentation + 1-min video, "Required" off) in the round's file-requirements editor +- Ticking the curation checkboxes +- Populating the Finals Jury group (still open from the previous ship) + +## Testing + +Vitest service tests (extend `tests/` final-documents coverage): +- Curation: null selection → all files; selection → only matching prior files + finale uploads always; empty array → finale uploads only; file without requirementId excluded under a selection. +- Picker helper returns distinct slots with correct counts. +- `setReviewVisibleRequirements` round-trips null/array through configJson without clobbering other keys (`allowFinalistRevisedUploads`). +- Status: `hasRequired`/`allUploaded` across mixed, all-optional (0 required), and fully-uploaded fixtures. + +## Risks + +- **configJson clobbering:** both toggles write the same JSON column — read-modify-write must preserve sibling keys (existing `setRevisedUploadSetting` pattern already does this; reuse it). +- **Stale selection:** if a selected requirement is later deleted, its files simply stop matching; "all" fallback never breaks. No cleanup needed.