Files
MOPC-Portal/docs/superpowers/specs/2026-06-09-finale-doc-curation-optional-uploads-design.md
2026-06-09 21:59:48 +02:00

7.0 KiB
Raw Blame History

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 (57 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:

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 "<requirement name> — <round name>" 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:

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.