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])