diff --git a/docs/plans/2026-02-25-advance-criterion-plan.md b/docs/plans/2026-02-25-advance-criterion-plan.md
new file mode 100644
index 0000000..80d80eb
--- /dev/null
+++ b/docs/plans/2026-02-25-advance-criterion-plan.md
@@ -0,0 +1,844 @@
+# Advance Criterion & Juror Progress Dashboard — Implementation Plan
+
+> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
+
+**Goal:** Add an `advance` criterion type to the evaluation form system, a juror-facing progress dashboard showing past submissions with scores and advance decisions, and admin-facing summary card + table column for advancement votes.
+
+**Architecture:** The `advance` type is added to the existing criterion type union and flows through the same `criteriaJson`/`criterionScoresJson` JSON columns — no Prisma schema migration. A new `showJurorProgressDashboard` field in `EvaluationConfig` gates the juror view. A new tRPC query aggregates the juror's submissions. Admin components get an extra column and a summary card.
+
+**Tech Stack:** TypeScript, tRPC, Prisma (JSON columns), React, shadcn/ui, Tailwind CSS, Zod
+
+---
+
+## Task 1: Add `advance` to CriterionType and Form Builder Types
+
+**Files:**
+- Modify: `src/components/forms/evaluation-form-builder.tsx:57` (CriterionType union)
+- Modify: `src/components/forms/evaluation-form-builder.tsx:96-114` (createDefaultCriterion)
+- Modify: `src/components/forms/evaluation-form-builder.tsx:117-122` (CRITERION_TYPE_OPTIONS)
+
+**Step 1: Update the CriterionType union**
+
+In `evaluation-form-builder.tsx` line 57, change:
+```ts
+export type CriterionType = 'numeric' | 'text' | 'boolean' | 'section_header'
+```
+to:
+```ts
+export type CriterionType = 'numeric' | 'text' | 'boolean' | 'advance' | 'section_header'
+```
+
+**Step 2: Add default creation for `advance` type**
+
+In `createDefaultCriterion` (line 96), add a new case before `section_header`:
+```ts
+case 'advance':
+ return { ...base, label: 'Advance to next round?', trueLabel: 'Yes', falseLabel: 'No', required: true }
+```
+
+**Step 3: Add `advance` to the type options array**
+
+In `CRITERION_TYPE_OPTIONS` (line 117), add an import for a suitable icon (e.g., `ArrowUpCircle` from lucide-react) and add the entry. Note: this button will be rendered separately with disable logic, so do NOT add it to `CRITERION_TYPE_OPTIONS`. Instead, we'll add a standalone button in Task 2.
+
+Actually — to keep things clean, do NOT add `advance` to `CRITERION_TYPE_OPTIONS`. The advance button is rendered separately with one-per-form enforcement. See Task 2.
+
+**Step 4: Commit**
+```bash
+git add src/components/forms/evaluation-form-builder.tsx
+git commit -m "feat: add advance criterion type to CriterionType union and defaults"
+```
+
+---
+
+## Task 2: Add "Advance to Next Round?" Button in Form Builder
+
+**Files:**
+- Modify: `src/components/forms/evaluation-form-builder.tsx:39-54` (imports — add ArrowUpCircle)
+- Modify: `src/components/forms/evaluation-form-builder.tsx:671-690` (add buttons section)
+
+**Step 1: Add the `ArrowUpCircle` icon import**
+
+At line 39 in the lucide-react import block, add `ArrowUpCircle` to the imports.
+
+**Step 2: Add the advance button with one-per-form enforcement**
+
+After the `CRITERION_TYPE_OPTIONS.map(...)` buttons (around line 685), before the PreviewDialog, add:
+```tsx
+
+```
+
+**Step 3: Commit**
+```bash
+git add src/components/forms/evaluation-form-builder.tsx
+git commit -m "feat: add advance criterion button with one-per-form enforcement"
+```
+
+---
+
+## Task 3: Add Edit Mode and Preview for `advance` Criterion
+
+**Files:**
+- Modify: `src/components/forms/evaluation-form-builder.tsx` — edit mode section (around lines 237-414)
+- Modify: `src/components/forms/evaluation-form-builder.tsx` — preview dialog (around lines 787-798)
+- Modify: `src/components/forms/evaluation-form-builder.tsx` — type badge display in list view
+
+**Step 1: Add edit mode fields for `advance` type**
+
+In the edit mode form (after the `boolean` block ending around line 414), add a block for `advance`:
+```tsx
+{(editDraft.type) === 'advance' && (
+
+)}
+```
+
+Note: No `required` toggle (always true), no `weight`, no `condition` fields for advance type.
+
+**Step 2: Add the type badge rendering**
+
+Find where the type badge is shown in list view (around line 237-240). The existing code uses `CRITERION_TYPE_OPTIONS.find(...)`. For `advance`, it won't find a match so will show nothing. Add a fallback or handle it. Where the badge text is resolved, add:
+```ts
+editDraft.type === 'advance' ? 'Advance to Next Round?' : CRITERION_TYPE_OPTIONS.find(...)?.label ?? 'Numeric Score'
+```
+
+**Step 3: Add preview rendering for `advance` type**
+
+In the PreviewDialog (around line 787), after the `boolean` rendering block, add:
+```tsx
+{type === 'advance' && (
+
+
+
+ {criterion.trueLabel || 'Yes'}
+
+
+
+ {criterion.falseLabel || 'No'}
+
+
+)}
+```
+
+**Step 4: Commit**
+```bash
+git add src/components/forms/evaluation-form-builder.tsx
+git commit -m "feat: add edit mode and preview rendering for advance criterion type"
+```
+
+---
+
+## Task 4: Server-Side — Accept `advance` in `upsertForm` and `submit` Validation
+
+**Files:**
+- Modify: `src/server/routers/evaluation.ts:1230` (upsertForm Zod input — add 'advance' to type enum)
+- Modify: `src/server/routers/evaluation.ts:1270-1304` (criteriaJson builder — add advance case)
+- Modify: `src/server/routers/evaluation.ts:238-260` (submit validation — handle advance type)
+
+**Step 1: Add `advance` to the Zod type enum in upsertForm input**
+
+At line 1230, change:
+```ts
+type: z.enum(['numeric', 'text', 'boolean', 'section_header']).optional(),
+```
+to:
+```ts
+type: z.enum(['numeric', 'text', 'boolean', 'advance', 'section_header']).optional(),
+```
+
+**Step 2: Add advance case in criteriaJson builder**
+
+After the `boolean` case (line 1295-1300), add:
+```ts
+if (type === 'advance') {
+ return {
+ ...base,
+ required: true, // always required, override any input
+ trueLabel: c.trueLabel || 'Yes',
+ falseLabel: c.falseLabel || 'No',
+ }
+}
+```
+
+**Step 3: Add server-side one-per-form validation**
+
+In the `upsertForm` mutation, after line 1256 (`const { roundId, criteria } = input`), add:
+```ts
+// Enforce max one advance criterion per form
+const advanceCount = criteria.filter((c) => c.type === 'advance').length
+if (advanceCount > 1) {
+ throw new TRPCError({
+ code: 'BAD_REQUEST',
+ message: 'Only one advance criterion is allowed per evaluation form',
+ })
+}
+```
+
+**Step 4: Handle `advance` in submit validation**
+
+In the `requireAllCriteriaScored` block (line 242-252), the `scorableCriteria` filter excludes `section_header` and `text`. The `advance` type should be treated like `boolean` — it's a required boolean. Update the missing criteria check:
+
+At line 250, change:
+```ts
+if (c.type === 'boolean') return typeof val !== 'boolean'
+```
+to:
+```ts
+if (c.type === 'boolean' || c.type === 'advance') return typeof val !== 'boolean'
+```
+
+**Step 5: Commit**
+```bash
+git add src/server/routers/evaluation.ts
+git commit -m "feat: server-side support for advance criterion type in upsertForm and submit"
+```
+
+---
+
+## Task 5: Juror Evaluation Page — Render `advance` Criterion
+
+**Files:**
+- Modify: `src/app/(jury)/jury/competitions/[roundId]/projects/[projectId]/evaluate/page.tsx:660-703` (boolean rendering — add advance case)
+- Modify: same file, client-side validation (around line 355-360)
+
+**Step 1: Add advance criterion rendering in the evaluation form**
+
+After the boolean rendering block (line 660-703), add a new block for `advance`. It should look similar to boolean but with larger, more prominent buttons and a colored border:
+
+```tsx
+if (criterion.type === 'advance') {
+ const currentValue = criteriaValues[criterion.id]
+ return (
+
+
+
+ {criterion.description && (
+
{criterion.description}
+ )}
+
+
+
+
+
+
+ )
+}
+```
+
+**Step 2: Update client-side validation**
+
+In the client-side submit validation (around line 355-360), where boolean required criteria are checked, ensure `advance` is also handled. Find the block that checks for boolean criteria values and add `|| c.type === 'advance'` to the condition.
+
+**Step 3: Commit**
+```bash
+git add "src/app/(jury)/jury/competitions/[roundId]/projects/[projectId]/evaluate/page.tsx"
+git commit -m "feat: render advance criterion on juror evaluation page with prominent styling"
+```
+
+---
+
+## Task 6: Add `showJurorProgressDashboard` to EvaluationConfig
+
+**Files:**
+- Modify: `src/types/competition-configs.ts:90-141` (EvaluationConfigSchema — add field)
+- Modify: `src/components/admin/rounds/config/evaluation-config.tsx` (add toggle)
+
+**Step 1: Add the field to the Zod schema**
+
+In `EvaluationConfigSchema` (line 90), add after line 103 (`peerReviewEnabled`):
+```ts
+showJurorProgressDashboard: z.boolean().default(false),
+```
+
+**Step 2: Add the toggle in the admin config UI**
+
+In `evaluation-config.tsx`, in the Feedback Requirements card (after the `peerReviewEnabled` switch, around line 176), add:
+```tsx
+
+
+
+
Show jurors a dashboard with their past evaluations, scores, and advance decisions
+
+ update('showJurorProgressDashboard', v)}
+ />
+
+```
+
+**Step 3: Commit**
+```bash
+git add src/types/competition-configs.ts src/components/admin/rounds/config/evaluation-config.tsx
+git commit -m "feat: add showJurorProgressDashboard toggle to EvaluationConfig"
+```
+
+---
+
+## Task 7: New tRPC Query — `evaluation.getMyProgress`
+
+**Files:**
+- Modify: `src/server/routers/evaluation.ts` (add new juryProcedure query at the end of the router)
+
+**Step 1: Add the query**
+
+Add this query to the `evaluationRouter` (before the closing `})` of the router):
+
+```ts
+getMyProgress: juryProcedure
+ .input(z.object({ roundId: z.string() }))
+ .query(async ({ ctx, input }) => {
+ const { roundId } = input
+ const userId = ctx.user.id
+
+ // Get all assignments for this juror in this round
+ const assignments = await ctx.prisma.assignment.findMany({
+ where: { roundId, userId },
+ include: {
+ project: { select: { id: true, title: true } },
+ evaluation: {
+ include: { form: { select: { criteriaJson: true } } },
+ },
+ },
+ })
+
+ const total = assignments.length
+ let completed = 0
+ let advanceYes = 0
+ let advanceNo = 0
+
+ const submissions: Array<{
+ projectId: string
+ projectName: string
+ submittedAt: Date | null
+ advanceDecision: boolean | null
+ criterionScores: Array<{ label: string; value: number }>
+ numericAverage: number | null
+ }> = []
+
+ for (const a of assignments) {
+ const ev = a.evaluation
+ if (!ev || ev.status !== 'SUBMITTED') continue
+ completed++
+
+ const criteria = (ev.form?.criteriaJson ?? []) as Array<{
+ id: string; label: string; type?: string; weight?: number
+ }>
+ const scores = (ev.criterionScoresJson ?? {}) as Record
+
+ // Find the advance criterion
+ const advanceCriterion = criteria.find((c) => c.type === 'advance')
+ let advanceDecision: boolean | null = null
+ if (advanceCriterion) {
+ const val = scores[advanceCriterion.id]
+ if (typeof val === 'boolean') {
+ advanceDecision = val
+ if (val) advanceYes++
+ else advanceNo++
+ }
+ }
+
+ // Collect numeric criterion scores
+ const numericScores: Array<{ label: string; value: number }> = []
+ for (const c of criteria) {
+ if (c.type === 'numeric' || (!c.type && c.weight !== undefined)) {
+ const val = scores[c.id]
+ if (typeof val === 'number') {
+ numericScores.push({ label: c.label, value: val })
+ }
+ }
+ }
+
+ const numericAverage = numericScores.length > 0
+ ? Math.round((numericScores.reduce((sum, s) => sum + s.value, 0) / numericScores.length) * 10) / 10
+ : null
+
+ submissions.push({
+ projectId: a.project.id,
+ projectName: a.project.title,
+ submittedAt: ev.submittedAt,
+ advanceDecision,
+ criterionScores: numericScores,
+ numericAverage,
+ })
+ }
+
+ // Sort by most recent first
+ submissions.sort((a, b) => {
+ if (!a.submittedAt) return 1
+ if (!b.submittedAt) return -1
+ return b.submittedAt.getTime() - a.submittedAt.getTime()
+ })
+
+ return {
+ total,
+ completed,
+ advanceCounts: { yes: advanceYes, no: advanceNo },
+ submissions,
+ }
+ }),
+```
+
+**Step 2: Commit**
+```bash
+git add src/server/routers/evaluation.ts
+git commit -m "feat: add evaluation.getMyProgress tRPC query for juror dashboard"
+```
+
+---
+
+## Task 8: Juror Progress Dashboard Component
+
+**Files:**
+- Create: `src/components/jury/juror-progress-dashboard.tsx`
+
+**Step 1: Create the component**
+
+```tsx
+'use client'
+
+import { useState } from 'react'
+import { trpc } from '@/lib/trpc/client'
+import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
+import { Badge } from '@/components/ui/badge'
+import { Progress } from '@/components/ui/progress'
+import { Button } from '@/components/ui/button'
+import { Skeleton } from '@/components/ui/skeleton'
+import { ChevronDown, ChevronUp, ThumbsUp, ThumbsDown } from 'lucide-react'
+import { cn } from '@/lib/utils'
+
+export function JurorProgressDashboard({ roundId }: { roundId: string }) {
+ const [expanded, setExpanded] = useState(true)
+ const { data, isLoading } = trpc.evaluation.getMyProgress.useQuery(
+ { roundId },
+ { refetchInterval: 30_000 },
+ )
+
+ if (isLoading) {
+ return
+ }
+
+ if (!data || data.total === 0) return null
+
+ const pct = Math.round((data.completed / data.total) * 100)
+
+ return (
+
+
+
+ )}
+
+
+ )
+}
+```
+
+**Step 2: Commit**
+```bash
+git add src/components/jury/juror-progress-dashboard.tsx
+git commit -m "feat: create JurorProgressDashboard component"
+```
+
+---
+
+## Task 9: Wire Juror Progress Dashboard into Round Page
+
+**Files:**
+- Modify: `src/app/(jury)/jury/competitions/[roundId]/page.tsx`
+
+**Step 1: Import the component and add it to the page**
+
+Add import at the top:
+```ts
+import { JurorProgressDashboard } from '@/components/jury/juror-progress-dashboard'
+```
+
+**Step 2: Fetch round config and conditionally render**
+
+The page already fetches `round` via `trpc.round.getById.useQuery`. Use it to check the config:
+
+After the heading `
` (around line 53) and before the `` with "Assigned Projects" (line 56), add:
+```tsx
+{(() => {
+ const config = (round?.configJson as Record) ?? {}
+ if (config.showJurorProgressDashboard) {
+ return
+ }
+ return null
+})()}
+```
+
+**Step 3: Commit**
+```bash
+git add "src/app/(jury)/jury/competitions/[roundId]/page.tsx"
+git commit -m "feat: wire JurorProgressDashboard into jury round detail page"
+```
+
+---
+
+## Task 10: Admin — Add "Advance" Column to Assignments Table
+
+**Files:**
+- Modify: `src/components/admin/assignment/individual-assignments-table.tsx:315-319` (column header)
+- Modify: `src/components/admin/assignment/individual-assignments-table.tsx:325-351` (row rendering)
+
+**Step 1: Add the column header**
+
+At line 315, change the grid from `grid-cols-[1fr_1fr_100px_70px]` to `grid-cols-[1fr_1fr_80px_80px_70px]` and add an "Advance" header:
+```tsx
+
+ Juror
+ Project
+ Status
+ Advance
+ Actions
+
+```
+
+**Step 2: Update row grid and add the advance cell**
+
+At line 325, update the grid class to match: `grid-cols-[1fr_1fr_80px_80px_70px]`.
+
+After the Status cell (line 351 `
`) and before the DropdownMenu (line 352), add:
+```tsx
+
+ {(() => {
+ const ev = a.evaluation
+ if (!ev || ev.status !== 'SUBMITTED') return —
+ const criteria = (ev.form?.criteriaJson ?? []) as Array<{ id: string; type?: string }>
+ const scores = (ev.criterionScoresJson ?? {}) as Record
+ const advCrit = criteria.find((c) => c.type === 'advance')
+ if (!advCrit) return —
+ const val = scores[advCrit.id]
+ if (val === true) return YES
+ if (val === false) return NO
+ return —
+ })()}
+
+```
+
+**Step 3: Ensure the query includes form data**
+
+Check that `trpc.assignment.listByStage` includes `evaluation.form` in its response. If it doesn't, we need to add `form: { select: { criteriaJson: true } }` to the evaluation include in the `listByStage` query in `src/server/routers/assignment.ts`. Look for the `listByStage` procedure and update its evaluation include.
+
+**Step 4: Commit**
+```bash
+git add src/components/admin/assignment/individual-assignments-table.tsx
+git add src/server/routers/assignment.ts # if modified
+git commit -m "feat: add Advance column to admin individual assignments table"
+```
+
+---
+
+## Task 11: Admin — Advancement Summary Card
+
+**Files:**
+- Create: `src/components/admin/round/advancement-summary-card.tsx`
+
+**Step 1: Create the component**
+
+```tsx
+'use client'
+
+import { trpc } from '@/lib/trpc/client'
+import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
+import { Badge } from '@/components/ui/badge'
+import { Skeleton } from '@/components/ui/skeleton'
+import { ThumbsUp, ThumbsDown, Clock } from 'lucide-react'
+
+export function AdvancementSummaryCard({ roundId }: { roundId: string }) {
+ const { data: assignments, isLoading } = trpc.assignment.listByStage.useQuery(
+ { roundId },
+ { refetchInterval: 15_000 },
+ )
+
+ if (isLoading) return
+
+ if (!assignments || assignments.length === 0) return null
+
+ // Check if form has an advance criterion
+ const firstSubmitted = assignments.find(
+ (a: any) => a.evaluation?.status === 'SUBMITTED' && a.evaluation?.form?.criteriaJson
+ )
+ if (!firstSubmitted) return null
+
+ const criteria = ((firstSubmitted as any).evaluation?.form?.criteriaJson ?? []) as Array<{ id: string; type?: string }>
+ const advanceCriterion = criteria.find((c) => c.type === 'advance')
+ if (!advanceCriterion) return null
+
+ let yesCount = 0
+ let noCount = 0
+ let pendingCount = 0
+
+ for (const a of assignments as any[]) {
+ const ev = a.evaluation
+ if (!ev || ev.status !== 'SUBMITTED') {
+ pendingCount++
+ continue
+ }
+ const scores = (ev.criterionScoresJson ?? {}) as Record
+ const val = scores[advanceCriterion.id]
+ if (val === true) yesCount++
+ else if (val === false) noCount++
+ else pendingCount++
+ }
+
+ const total = yesCount + noCount + pendingCount
+ const yesPct = total > 0 ? Math.round((yesCount / total) * 100) : 0
+ const noPct = total > 0 ? Math.round((noCount / total) * 100) : 0
+
+ return (
+
+
+ Advancement Votes
+
+
+
+
+
+
+
+
+
{yesCount}
+
Yes ({yesPct}%)
+
+
+
+
+
+
+
+
{noCount}
+
No ({noPct}%)
+
+
+
+
+
+
+
+
{pendingCount}
+
Pending
+
+
+
+
+ {/* Stacked bar */}
+
+ {yesPct > 0 && }
+ {noPct > 0 && }
+
+
+
+ )
+}
+```
+
+**Step 2: Commit**
+```bash
+git add src/components/admin/round/advancement-summary-card.tsx
+git commit -m "feat: create AdvancementSummaryCard admin component"
+```
+
+---
+
+## Task 12: Wire Advancement Summary Card into Admin Round Detail
+
+**Files:**
+- Modify: `src/app/(admin)/admin/rounds/[roundId]/page.tsx` (overview tab, around line 871)
+
+**Step 1: Import the component**
+
+Add at the imports section:
+```ts
+import { AdvancementSummaryCard } from '@/components/admin/round/advancement-summary-card'
+```
+
+**Step 2: Add it to the overview tab**
+
+In the overview tab content (after the Launch Readiness card, around line 943), add:
+```tsx
+{isEvaluation && }
+```
+
+Where `isEvaluation` is the existing variable that checks `round.roundType === 'EVALUATION'`.
+
+**Step 3: Commit**
+```bash
+git add "src/app/(admin)/admin/rounds/[roundId]/page.tsx"
+git commit -m "feat: wire AdvancementSummaryCard into admin round overview tab"
+```
+
+---
+
+## Task 13: Build and Typecheck
+
+**Step 1: Run typecheck**
+```bash
+npm run typecheck
+```
+Expected: No errors (fix any that appear).
+
+**Step 2: Run build**
+```bash
+npm run build
+```
+Expected: Successful build.
+
+**Step 3: Fix any issues and commit**
+```bash
+git add -A
+git commit -m "fix: resolve any type or build errors from advance criterion feature"
+```
+
+---
+
+## Task 14: Manual QA Checklist
+
+Run `npm run dev` and verify:
+
+1. **Form builder**: Admin can add "Advance to Next Round?" criterion. Button disables after one is added. Edit mode shows trueLabel/falseLabel. Preview renders correctly.
+2. **Juror evaluation**: Advance criterion renders with prominent green/red buttons. Required validation works. Autosave works. Submit stores value in `criterionScoresJson`.
+3. **Juror dashboard**: When `showJurorProgressDashboard` is enabled in round config, the progress card appears with progress bar, YES/NO counts, and submissions table sorted by date.
+4. **Admin config**: The "Juror Progress Dashboard" toggle appears in the Evaluation round config.
+5. **Admin assignments table**: "Advance" column appears with YES/NO/— badges.
+6. **Admin overview**: `AdvancementSummaryCard` renders with correct counts and stacked bar.