From cab311fbbbba2317988e5d1791c32f89ff6b0abf Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 18 Feb 2026 14:59:23 +0100 Subject: [PATCH] Fix advancement targets stripped by Zod, remove redundant save bar - Add general settings fields (startupAdvanceCount, conceptAdvanceCount, notifyOnEntry, notifyOnAdvance) to ALL round config schemas, not just FilteringConfig. Zod was stripping them on save for other round types. - Replace floating save bar with error-only bar since autosave handles all config persistence (800ms debounce) Co-Authored-By: Claude Opus 4.6 --- .../(admin)/admin/rounds/[roundId]/page.tsx | 48 +++++++------------ src/types/competition-configs.ts | 19 ++++++-- 2 files changed, 32 insertions(+), 35 deletions(-) diff --git a/src/app/(admin)/admin/rounds/[roundId]/page.tsx b/src/app/(admin)/admin/rounds/[roundId]/page.tsx index fbdcd37..dc06194 100644 --- a/src/app/(admin)/admin/rounds/[roundId]/page.tsx +++ b/src/app/(admin)/admin/rounds/[roundId]/page.tsx @@ -2006,40 +2006,26 @@ export default function RoundDetailPage() { )} - {/* Floating save bar — appears when config has unsaved changes */} - {hasUnsavedConfig && ( -
+ {/* Autosave error bar — only shows when save fails */} + {autosaveStatus === 'error' && ( +
-
-
- You have unsaved changes +
+ + Auto-save failed
-
- {autosaveStatus === 'error' && ( - Save failed — try again + - -
+
)} diff --git a/src/types/competition-configs.ts b/src/types/competition-configs.ts index 59bb98a..9d7336f 100644 --- a/src/types/competition-configs.ts +++ b/src/types/competition-configs.ts @@ -8,9 +8,18 @@ import type { RoundType, AwardEligibilityMode, AwardScoringMode, AwardStatus } f // These replace the loosely-typed pipeline-wizard.ts configs with // Zod-validated, compile-time-safe contracts. +// Shared fields that appear in "General Settings" for all round types +const generalSettingsFields = { + startupAdvanceCount: z.number().int().nonnegative().optional(), + conceptAdvanceCount: z.number().int().nonnegative().optional(), + notifyOnEntry: z.boolean().default(false), + notifyOnAdvance: z.boolean().default(false), +} + // ─── 1. IntakeConfig ───────────────────────────────────────────────────────── export const IntakeConfigSchema = z.object({ + ...generalSettingsFields, allowDrafts: z.boolean().default(true), draftExpiryDays: z.number().int().positive().default(30), @@ -43,6 +52,7 @@ export type IntakeConfig = z.infer // ─── 2. FilteringConfig ────────────────────────────────────────────────────── export const FilteringConfigSchema = z.object({ + ...generalSettingsFields, rules: z .array( z.object({ @@ -71,10 +81,6 @@ export const FilteringConfigSchema = z.object({ batchSize: z.number().int().positive().default(20), aiParseFiles: z.boolean().default(false), - startupAdvanceCount: z.number().int().nonnegative().optional(), - conceptAdvanceCount: z.number().int().nonnegative().optional(), - notifyOnEntry: z.boolean().default(false), - notifyOnAdvance: z.boolean().default(false), }) export type FilteringConfig = z.infer @@ -82,6 +88,7 @@ export type FilteringConfig = z.infer // ─── 3. EvaluationConfig ───────────────────────────────────────────────────── export const EvaluationConfigSchema = z.object({ + ...generalSettingsFields, requiredReviewsPerProject: z.number().int().positive().default(3), scoringMode: z.enum(['criteria', 'global', 'binary']).default('criteria'), @@ -121,6 +128,7 @@ export type EvaluationConfig = z.infer // ─── 4. SubmissionConfig ───────────────────────────────────────────────────── export const SubmissionConfigSchema = z.object({ + ...generalSettingsFields, eligibleStatuses: z .array( z.enum([ @@ -143,6 +151,7 @@ export type SubmissionConfig = z.infer // ─── 5. MentoringConfig ────────────────────────────────────────────────────── export const MentoringConfigSchema = z.object({ + ...generalSettingsFields, eligibility: z .enum(['all_advancing', 'requested_only', 'admin_selected']) .default('requested_only'), @@ -161,6 +170,7 @@ export type MentoringConfig = z.infer // ─── 6. LiveFinalConfig ────────────────────────────────────────────────────── export const LiveFinalConfigSchema = z.object({ + ...generalSettingsFields, juryVotingEnabled: z.boolean().default(true), votingMode: z.enum(['simple', 'criteria']).default('simple'), @@ -193,6 +203,7 @@ export type LiveFinalConfig = z.infer // ─── 7. DeliberationConfig ─────────────────────────────────────────────────── export const DeliberationConfigSchema = z.object({ + ...generalSettingsFields, juryGroupId: z.string(), mode: z