Fix config save state sync — local config now re-syncs after save
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m46s

The save bar persisted forever because Zod.parse() adds defaults for
new fields, making the server config differ from local state. After
save, the sync effect now picks up the server value. Uses savingRef
to prevent overwriting local edits during the save roundtrip.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Matt
2026-02-17 18:53:51 +01:00
parent cf1508f856
commit 4fa3ca0bb6

View File

@@ -237,16 +237,24 @@ export default function RoundDetailPage() {
{ enabled: round?.roundType === 'FILTERING', refetchInterval: 5_000 },
)
// Initialize config from server once on load (or when round changes externally)
// Initialize config from server on load; re-sync after saves
const serverConfig = useMemo(() => (round?.configJson as Record<string, unknown>) ?? {}, [round?.configJson])
const configInitialized = useRef(false)
if (round && !configInitialized.current) {
const roundConfig = (round.configJson as Record<string, unknown>) ?? {}
if (Object.keys(config).length === 0 || JSON.stringify(roundConfig) !== JSON.stringify(config)) {
setConfig(roundConfig)
const savingRef = useRef(false)
// Sync local config with server: on initial load AND whenever serverConfig
// changes after a save completes (so Zod-applied defaults get picked up)
useEffect(() => {
if (!round) return
if (!configInitialized.current) {
configInitialized.current = true
setConfig(serverConfig)
} else if (!savingRef.current) {
// Server changed (e.g. after save invalidation) — re-sync
setConfig(serverConfig)
}
configInitialized.current = true
}
}, [serverConfig, round])
const hasUnsavedConfig = useMemo(
() => configInitialized.current && JSON.stringify(config) !== JSON.stringify(serverConfig),
[config, serverConfig],
@@ -255,11 +263,13 @@ export default function RoundDetailPage() {
// ── Mutations ──────────────────────────────────────────────────────────
const updateMutation = trpc.round.update.useMutation({
onSuccess: () => {
savingRef.current = false
utils.round.getById.invalidate({ id: roundId })
setAutosaveStatus('saved')
setTimeout(() => setAutosaveStatus('idle'), 2000)
},
onError: (err) => {
savingRef.current = false
setAutosaveStatus('error')
toast.error(err.message)
},
@@ -393,23 +403,27 @@ export default function RoundDetailPage() {
}, [])
const saveConfig = useCallback(() => {
savingRef.current = true
setAutosaveStatus('saving')
updateMutation.mutate({ id: roundId, configJson: config })
}, [config, roundId, updateMutation])
// ── Auto-save: debounce config changes and save automatically ────────
const configJson = JSON.stringify(config)
const serverJson = JSON.stringify(serverConfig)
useEffect(() => {
if (!configInitialized.current) return
if (JSON.stringify(config) === JSON.stringify(serverConfig)) return
if (configJson === serverJson) return
const timer = setTimeout(() => {
savingRef.current = true
setAutosaveStatus('saving')
updateMutation.mutate({ id: roundId, configJson: config })
}, 800)
return () => clearTimeout(timer)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [config])
}, [configJson])
// ── Computed values ────────────────────────────────────────────────────
const projectCount = round?._count?.projectRoundStates ?? 0