From 537de07245596c06945b03fb3e0b6935b9848fe4 Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 9 Jun 2026 14:31:49 +0200 Subject: [PATCH] docs(grand-final): spec for final-document upload, mentor surfacing, judge review, notifications Design for surfacing the already-live Grand Final document upload (PDF + 1-min video) to the 9 confirmed finalists via a dashboard banner, a read-only judge review page (Finals Jury group + admins), a Final Documents panel on both the team and mentor views, and email + in-app reminders (auto cron + manual blast). Reuses the existing legacy FileRequirement anchor, upload mechanics, and notification pipeline. Grounded in verified prod state. Co-Authored-By: Claude Opus 4.8 (1M context) --- ...2026-06-09-grand-final-documents-design.md | 141 ++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 docs/superpowers/specs/2026-06-09-grand-final-documents-design.md diff --git a/docs/superpowers/specs/2026-06-09-grand-final-documents-design.md b/docs/superpowers/specs/2026-06-09-grand-final-documents-design.md new file mode 100644 index 0000000..f7cd133 --- /dev/null +++ b/docs/superpowers/specs/2026-06-09-grand-final-documents-design.md @@ -0,0 +1,141 @@ +# Grand-Final Documents — upload visibility, mentor surfacing, judge review, notifications + +**Date:** 2026-06-09 +**Status:** Design — pending review +**Edition:** MOPC 2026 (program "Monaco Ocean Protection Challenge", competition `MOPC 2026`) + +## Problem + +Nine finalist teams must submit two final deliverables ahead of the Grand Final — a **final PDF presentation** and a **~1-minute video** — and the upcoming Grand-Final judges need to review those documents. Today there is no discoverable upload prompt, no consolidated judge review surface, and no notifications driving teams to upload. + +## Current state (verified against prod, 2026-06-09) + +The data layer already exists and is correctly set up: + +- **"Grand Final" (LIVE_FINAL) round is `ROUND_ACTIVE`** with `windowCloseAt = 2026-06-11 21:00 UTC` and the empty **"Finals Jury"** group attached (`juryGroupId` set, **0 members**). +- **Two `FileRequirement` rows already exist on the Grand Final round** (legacy per-round system): **"PDF presentation support"** (`application/pdf`) and **"1 minute video"** (`video/*`). Both currently `required = false`, `maxSizeMB = null`, **0 files uploaded**. +- **All 9 finalist teams are correctly enrolled**: each has a `ProjectRoundState` in both the (closed) Mentoring round and the (active) Grand Final round, and all 9 have `FinalistConfirmation.status = CONFIRMED`. No mismatches (confirmed↔enrolled↔in-mentor all aligned). All 9 share one mentor, **Camille Lopez**. Attendee counts 1–4 (program `defaultAttendeeCap = 4`). +- Auto-enroll (confirmed + in mentor round → Grand Final round) is working via `finalist.enrollFinalists`; the **admin override already exists** (`finalist.adminConfirm` to mark attending without a token; `finalist.unenroll` to remove) in the `/admin/logistics` **Confirmations tab** + attendance dialog. + +### What this means + +The **upload already works today**: `/applicant/documents` renders an upload section for every round in `openRounds`, where `openRounds` = program rounds that are `ROUND_ACTIVE` **and** the project is a member of (`applicant.getMyDashboard`). The Grand Final round qualifies, so all 9 teams can upload the PDF + video right now. The upload procedure (`applicant.getUploadUrl`) permits any team member to upload against a requirement as long as the round is `ROUND_ACTIVE` (it is). The `windowCloseAt` deadline is **advisory only** (display, not blocking). + +The gaps are therefore: **discoverability** (no banner/notification), **judge review** (no consolidated surface; jury can only see files for projects they are individually assigned to, and the Finals Jury group is empty), and **mentor-section surfacing** (the final documents never appear in the mentor area). + +## Goals + +1. Make the existing finalist upload **discoverable** via a dashboard banner and notifications. +2. Give the upcoming Grand-Final judges a **read-only review page** of all finalists' documents. +3. Surface the final documents (and a pre-deadline cue) inside **the mentor section**, on both the team's and the mentor's views. +4. Add **email + in-app notifications**, triggered **automatically** (pre-deadline reminder cron) and **manually** (admin blast). + +## Non-goals (YAGNI) + +- Admin approve / needs-changes review workflow on documents. +- Migrating to the heavier `SubmissionWindow` system (the legacy `FileRequirement` anchor is already set up and proven). +- A mentor-milestone tracker UI (`MentorMilestone`/`MentorMilestoneCompletion` models exist but have no UI; "last steps of the mentor round" is treated as descriptive framing, surfaced as a read-only Final Documents panel, not a milestone system). +- Comments/threads on the judge review page. +- New admin enrollment/override controls (`adminConfirm` + `unenroll` already exist; we only ensure they are reachable from the finale round overview). + +## Architecture decision + +Build thin additions on the **existing legacy `FileRequirement` → `ProjectFile` anchor** that is already configured on the Grand Final round. Reuse: + +- Upload mechanics (`applicant.getUploadUrl` / `saveFileMetadata`, presigned MinIO PUT, `RequirementUploadList`). +- File preview/download (`FilePreview` / `file-viewer`, `file.getDownloadUrl`). +- The notification pipeline (`createNotification`, `notifyProjectTeam`/`notifyProjectMentors`, `NotificationEmailSetting`, `NOTIFICATION_EMAIL_TEMPLATES`, `sendStyledNotificationEmail`) and the reminder-cron pattern (`sendDueConfirmationReminders`). + +**The deadline everywhere** (banner, mentor cue, reminder cron) is the Grand Final round's single `windowCloseAt` field, edited by admins in round settings. All deadline displays use **browser-local time + zone label** (`Intl.DateTimeFormat`), never UTC/fixed Monaco time, per the locked grand-finale timezone rule. Deadline behavior is **soft/advisory** — uploads stay open while the round is active; past the date, files are flagged "late" in the UI but still accepted. + +## Shared service: `src/server/services/final-documents.ts` + +A new service centralizes the logic, wrapped by thin tRPC procedures: + +- `getFinalDocumentStatusForProject(prisma, projectId)` → `{ roundId, roundName, deadline, deadlinePassed, requirements: [{ id, name, acceptedMimeTypes, uploaded, file? }], allRequiredUploaded }` or `null` when the project is not a CONFIRMED finalist in an active LIVE_FINAL round. The single source of truth for the banner, the mentor panels, and reminder targeting. +- `listFinalistDocumentsForReview(prisma, programId)` → `{ round: { name, deadline }, totalCount, submittedCount, teams: [{ projectId, teamName, category, confirmStatus, documents: [{ requirementId, requirementName, file? }] }] }`. File metadata only; presigned URLs are fetched per-file on demand by the client via existing `file.getDownloadUrl`. +- `sendDueFinalDocReminders(prisma)` → cron entry. Targets CONFIRMED finalists in the active LIVE_FINAL round with at least one required document missing, whose `finalDocsReminderSentAt` is null and whose deadline is within the reminder window; creates `GRAND_FINAL_DOCS_REMINDER` notifications and stamps `finalDocsReminderSentAt`. Best-effort per row. +- `sendManualFinalDocReminders(prisma, { programId, projectIds?, actorId })` → admin blast. For the given projects (default: all CONFIRMED finalists with missing required docs), create `GRAND_FINAL_DOCS_REMINDER` notifications regardless of `finalDocsReminderSentAt`. Returns `{ sent }`. + +## Components + +### 1. Finalist upload banner (applicant dashboard) + +- New auto-hiding banner component (pattern of `LunchBanner`/`MyLogisticsCard`: returns `null` when not applicable) on `src/app/(applicant)/applicant/page.tsx`. +- Backed by a new query **`applicant.getFinalDocumentStatus`** (wraps `getFinalDocumentStatusForProject` for the caller's project). +- Shows: heading ("Upload your Grand Final documents"), the two deliverables each with a ✓ / empty state, deadline in browser-local time + zone, and a CTA button → `/applicant/documents`. Collapses to a "✓ Submitted" confirmation once all required documents are uploaded. Non-dismissible while incomplete. + +### 2. Mentor-section "Final Documents" panel (team + mentor) + +A new read-only `FinalDocumentsPanel` component rendered on **both** mentor surfaces: + +- **Team view** — `src/app/(applicant)/applicant/mentor/page.tsx` (adds a panel below the existing mentor cards / chat / workspace-files). Uses `applicant.getFinalDocumentStatus`. +- **Mentor view** — `src/app/(mentor)/mentor/workspace/[projectId]/page.tsx` (adds a "Final Documents" section or tab for the viewed project). Uses a new **`mentor.getProjectFinalDocuments`** procedure (mentor/team access check) wrapping `getFinalDocumentStatusForProject`. + +Behavior: before upload + as the deadline nears, a **visual cue** ("Final grand-final documents due [date] — upload now", with an upload CTA on the team view); after upload, the PDF + video appear as the team's read-only "final documents" (inline preview / video player / download), visible to both the team and their mentor. Same underlying `ProjectFile`s — no duplicate storage. + +### 3. Judge review page + +- New read-only page (e.g. `src/app/(jury)/jury/finals-documents/page.tsx`) listing all finalist teams grouped by category, each with its two documents: PDF via inline `FilePreview`, video via inline `