From d8a23310387999421cebd6b3814f41e855c2cf53 Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Sun, 12 Apr 2026 09:32:02 +0200 Subject: [PATCH] Fix IPA/syllable mode change requiring double-click The useEffect for mode changes called buildGrid() which was a useCallback closing over stale ipaMode/syllableMode values due to React's asynchronous state batching. The first click triggered a rebuild with the OLD mode; only the second click used the new one. Now inlines the API call directly in the useEffect, reading ipaMode and syllableMode from the effect's closure which always has the current values. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../components/grid-editor/useGridEditor.ts | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/admin-lehrer/components/grid-editor/useGridEditor.ts b/admin-lehrer/components/grid-editor/useGridEditor.ts index 9936ddd..a689d98 100644 --- a/admin-lehrer/components/grid-editor/useGridEditor.ts +++ b/admin-lehrer/components/grid-editor/useGridEditor.ts @@ -101,16 +101,42 @@ export function useGridEditor(sessionId: string | null) { } }, [sessionId, buildGrid]) - // Auto-rebuild when IPA or syllable mode changes (skip initial mount) + // Auto-rebuild when IPA or syllable mode changes (skip initial mount). + // We call the API directly with the new values instead of going through + // the buildGrid callback, which may still close over stale state due to + // React's asynchronous state batching. const initialLoadDone = useRef(false) useEffect(() => { if (!initialLoadDone.current) { - // Mark as initialized once the first grid is loaded if (grid) initialLoadDone.current = true return } - // Mode changed after initial load — rebuild - buildGrid() + if (!sessionId) return + const rebuild = async () => { + setLoading(true) + setError(null) + try { + const params = new URLSearchParams() + params.set('ipa_mode', ipaMode) + params.set('syllable_mode', syllableMode) + const res = await fetch( + `${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/build-grid?${params}`, + { method: 'POST' }, + ) + if (!res.ok) { + const data = await res.json().catch(() => ({})) + throw new Error(data.detail || `HTTP ${res.status}`) + } + const data: StructuredGrid = await res.json() + setGrid(data) + setDirty(false) + } catch (e) { + setError(e instanceof Error ? e.message : String(e)) + } finally { + setLoading(false) + } + } + rebuild() // eslint-disable-next-line react-hooks/exhaustive-deps }, [ipaMode, syllableMode])