Round system redesign: Phases 1-7 complete

Full pipeline/track/stage architecture replacing the legacy round system.

Schema: 11 new models (Pipeline, Track, Stage, StageTransition,
ProjectStageState, RoutingRule, Cohort, CohortProject, LiveProgressCursor,
OverrideAction, AudienceVoter) + 8 new enums.

Backend: 9 new routers (pipeline, stage, routing, stageFiltering,
stageAssignment, cohort, live, decision, award) + 6 new services
(stage-engine, routing-engine, stage-filtering, stage-assignment,
stage-notifications, live-control).

Frontend: Pipeline wizard (17 components), jury stage pages (7),
applicant pipeline pages (3), public stage pages (2), admin pipeline
pages (5), shared stage components (3), SSE route, live hook.

Phase 6 refit: 23 routers/services migrated from roundId to stageId,
all frontend components refitted. Deleted round.ts (985 lines),
roundTemplate.ts, round-helpers.ts, round-settings.ts, round-type-settings.tsx,
10 legacy admin pages, 7 legacy jury pages, 3 legacy dialogs.

Phase 7 validation: 36 tests (10 unit + 8 integration files) all passing,
TypeScript 0 errors, Next.js build succeeds, 13 integrity checks,
legacy symbol sweep clean, auto-seed on first Docker startup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-13 13:57:09 +01:00
parent 8a328357e3
commit 331b67dae0
256 changed files with 29117 additions and 21424 deletions

View File

@@ -85,11 +85,11 @@ const ROLE_SORT_ORDER: Record<string, number> = { LEAD: 0, MEMBER: 1, ADVISOR: 2
function NewProjectPageContent() {
const router = useRouter()
const searchParams = useSearchParams()
const roundIdParam = searchParams.get('round')
const stageIdParam = searchParams.get('stage')
const programIdParam = searchParams.get('program')
const [selectedProgramId, setSelectedProgramId] = useState<string>(programIdParam || '')
const [selectedRoundId, setSelectedRoundId] = useState<string>(roundIdParam || '')
const [selectedStageId, setSelectedStageId] = useState<string>(stageIdParam || '')
// Form state
const [title, setTitle] = useState('')
@@ -113,7 +113,7 @@ function NewProjectPageContent() {
// Fetch programs
const { data: programs, isLoading: loadingPrograms } = trpc.program.list.useQuery({
status: 'ACTIVE',
includeRounds: true,
includeStages: true,
})
// Fetch wizard config for selected program (dropdown options)
@@ -128,7 +128,7 @@ function NewProjectPageContent() {
onSuccess: () => {
toast.success('Project created successfully')
utils.project.list.invalidate()
utils.round.get.invalidate()
utils.program.get.invalidate()
router.push('/admin/projects')
},
onError: (error) => {
@@ -136,9 +136,9 @@ function NewProjectPageContent() {
},
})
// Get rounds for selected program
// Get stages for selected program
const selectedProgram = programs?.find((p) => p.id === selectedProgramId)
const rounds = selectedProgram?.rounds || []
const stages = (selectedProgram?.stages || []) as Array<{ id: string; name: string }>
// Get dropdown options from wizard config
const categoryOptions = wizardConfig?.competitionCategories || []
@@ -216,7 +216,6 @@ function NewProjectPageContent() {
createProject.mutate({
programId: selectedProgramId,
roundId: selectedRoundId || undefined,
title: title.trim(),
teamName: teamName.trim() || undefined,
description: description.trim() || undefined,
@@ -264,12 +263,12 @@ function NewProjectPageContent() {
</div>
</div>
{/* Program & Round selection */}
{/* Program & Stage selection */}
<Card>
<CardHeader>
<CardTitle>Program & Round</CardTitle>
<CardTitle>Program & Stage</CardTitle>
<CardDescription>
Select the program for this project. Round assignment is optional.
Select the program for this project. Stage assignment is optional.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
@@ -287,7 +286,7 @@ function NewProjectPageContent() {
<Label>Program *</Label>
<Select value={selectedProgramId} onValueChange={(v) => {
setSelectedProgramId(v)
setSelectedRoundId('') // Reset round on program change
setSelectedStageId('') // Reset stage on program change
}}>
<SelectTrigger>
<SelectValue placeholder="Select a program" />
@@ -303,16 +302,16 @@ function NewProjectPageContent() {
</div>
<div className="space-y-2">
<Label>Round (optional)</Label>
<Select value={selectedRoundId || '__none__'} onValueChange={(v) => setSelectedRoundId(v === '__none__' ? '' : v)} disabled={!selectedProgramId}>
<Label>Stage (optional)</Label>
<Select value={selectedStageId || '__none__'} onValueChange={(v) => setSelectedStageId(v === '__none__' ? '' : v)} disabled={!selectedProgramId}>
<SelectTrigger>
<SelectValue placeholder="No round assigned" />
<SelectValue placeholder="No stage assigned" />
</SelectTrigger>
<SelectContent>
<SelectItem value="__none__">No round assigned</SelectItem>
{rounds.map((r: { id: string; name: string }) => (
<SelectItem key={r.id} value={r.id}>
{r.name}
<SelectItem value="__none__">No stage assigned</SelectItem>
{stages.map((s) => (
<SelectItem key={s.id} value={s.id}>
{s.name}
</SelectItem>
))}
</SelectContent>