From 0b27e8f89ba926488dca19a192a555ed40d7523a Mon Sep 17 00:00:00 2001 From: Matthew Slight Date: Thu, 12 Jun 2025 22:35:54 +0400 Subject: [PATCH 1/3] refactor: stabilize drill history saves --- .../components/hooks/useSaveDrillHistory.ts | 87 ++++++++++--------- 1 file changed, 45 insertions(+), 42 deletions(-) diff --git a/src/pages/drills/components/hooks/useSaveDrillHistory.ts b/src/pages/drills/components/hooks/useSaveDrillHistory.ts index cf14ad7..d60832a 100644 --- a/src/pages/drills/components/hooks/useSaveDrillHistory.ts +++ b/src/pages/drills/components/hooks/useSaveDrillHistory.ts @@ -3,7 +3,6 @@ import { useEffect, useRef } from 'react'; import { mutate } from 'swr'; import { postDrillHistory } from '@/api/drills'; -import { useDebounce } from '@/hooks/useDebounce'; import { useProfile } from '@/hooks/useProfile'; export function useSaveDrillHistory( @@ -17,9 +16,8 @@ export function useSaveDrillHistory( minDepth = 12 ) { const hasPosted = useRef(false); + const timeoutRef = useRef | null>(null); - const debouncedResult = useDebounce(result, delay); - const debouncedReason = useDebounce(reason, delay); const { profile: { username }, } = useProfile(); @@ -27,48 +25,53 @@ export function useSaveDrillHistory( // Reset posted flag when moving to a new drill useEffect(() => { hasPosted.current = false; + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + timeoutRef.current = null; + } }, [drillId, resetKey]); useEffect(() => { - const canSave = - drillId != null && - !hasPosted.current && - currentDepth >= minDepth && - (debouncedResult === 'pass' || debouncedResult === 'fail'); + if ( + drillId == null || + hasPosted.current || + currentDepth < minDepth || + (result !== 'pass' && result !== 'fail') + ) { + return; + } - if (!canSave) return; - - hasPosted.current = true; - - postDrillHistory(drillId, { - result: debouncedResult, - reason: debouncedReason || undefined, - moves, - }) - .then(() => { - mutate(`/drills/${drillId}`); - if (debouncedResult === 'pass' && username) { - try { - const key = `bf:blunders_fixed:${username}`; - const raw = localStorage.getItem(key); - const val = raw ? parseInt(raw, 10) : 0; - localStorage.setItem(key, String(val + 1)); - } catch { - // ignore - } - } + timeoutRef.current = setTimeout(() => { + postDrillHistory(drillId, { + result, + reason: reason || undefined, + moves, }) - .catch((err) => { - console.error('Could not save drill history:', err); - hasPosted.current = false; // retry on failure - }); - }, [ - drillId, - debouncedResult, - debouncedReason, - currentDepth, - minDepth, - moves, - username, - ]); + .then(() => { + mutate(`/drills/${drillId}`); + if (result === 'pass' && username) { + try { + const key = `bf:blunders_fixed:${username}`; + const raw = localStorage.getItem(key); + const val = raw ? parseInt(raw, 10) : 0; + localStorage.setItem(key, String(val + 1)); + } catch { + // ignore + } + } + hasPosted.current = true; + }) + .catch((err) => { + console.error('Could not save drill history:', err); + hasPosted.current = false; // retry on failure + }); + }, delay); + + return () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + timeoutRef.current = null; + } + }; + }, [drillId, result, reason, currentDepth, moves, delay, minDepth, username]); } From f9cfcf353250cb0d890a56dd3f2c5ce7d5608777 Mon Sep 17 00:00:00 2001 From: Matthew Slight Date: Thu, 12 Jun 2025 22:51:04 +0400 Subject: [PATCH 2/3] feat(drills): optimize history save --- .../components/hooks/useSaveDrillHistory.ts | 83 ++++++++++--------- 1 file changed, 46 insertions(+), 37 deletions(-) diff --git a/src/pages/drills/components/hooks/useSaveDrillHistory.ts b/src/pages/drills/components/hooks/useSaveDrillHistory.ts index d60832a..7be158a 100644 --- a/src/pages/drills/components/hooks/useSaveDrillHistory.ts +++ b/src/pages/drills/components/hooks/useSaveDrillHistory.ts @@ -12,11 +12,9 @@ export function useSaveDrillHistory( currentDepth: number, moves: string[], resetKey: number, - delay = 750, minDepth = 12 ) { const hasPosted = useRef(false); - const timeoutRef = useRef | null>(null); const { profile: { username }, @@ -25,10 +23,7 @@ export function useSaveDrillHistory( // Reset posted flag when moving to a new drill useEffect(() => { hasPosted.current = false; - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - timeoutRef.current = null; - } + // reset per drill attempt }, [drillId, resetKey]); useEffect(() => { @@ -41,37 +36,51 @@ export function useSaveDrillHistory( return; } - timeoutRef.current = setTimeout(() => { - postDrillHistory(drillId, { - result, - reason: reason || undefined, - moves, - }) - .then(() => { - mutate(`/drills/${drillId}`); - if (result === 'pass' && username) { - try { - const key = `bf:blunders_fixed:${username}`; - const raw = localStorage.getItem(key); - const val = raw ? parseInt(raw, 10) : 0; - localStorage.setItem(key, String(val + 1)); - } catch { - // ignore - } - } - hasPosted.current = true; - }) - .catch((err) => { - console.error('Could not save drill history:', err); - hasPosted.current = false; // retry on failure - }); - }, delay); + hasPosted.current = true; + + const ts = new Date().toISOString(); - return () => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - timeoutRef.current = null; - } + const optimistic = { + id: Date.now(), + drill_position_id: Number(drillId), + result, + reason: reason ?? null, + moves, + final_eval: null, + timestamp: ts, }; - }, [drillId, result, reason, currentDepth, moves, delay, minDepth, username]); + + mutate( + `/drills/${drillId}`, + (current: any) => + current + ? { ...current, history: [...current.history, optimistic], last_drilled_at: ts } + : current, + { revalidate: false } + ); + + postDrillHistory(drillId, { + result, + reason: reason || undefined, + moves, + }) + .then(() => { + mutate(`/drills/${drillId}`); + if (result === 'pass' && username) { + try { + const key = `bf:blunders_fixed:${username}`; + const raw = localStorage.getItem(key); + const val = raw ? parseInt(raw, 10) : 0; + localStorage.setItem(key, String(val + 1)); + } catch { + // ignore + } + } + }) + .catch((err) => { + console.error('Could not save drill history:', err); + mutate(`/drills/${drillId}`); + hasPosted.current = false; // retry on failure + }); + }, [drillId, result, reason, currentDepth, moves, minDepth, username]); } From 047a3ea59dc1768dbb11df686c20ae3c6dedcdb0 Mon Sep 17 00:00:00 2001 From: Matthew Slight Date: Thu, 12 Jun 2025 23:02:09 +0400 Subject: [PATCH 3/3] Refine history save mutate --- .../components/hooks/useSaveDrillHistory.ts | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/pages/drills/components/hooks/useSaveDrillHistory.ts b/src/pages/drills/components/hooks/useSaveDrillHistory.ts index 7be158a..894f818 100644 --- a/src/pages/drills/components/hooks/useSaveDrillHistory.ts +++ b/src/pages/drills/components/hooks/useSaveDrillHistory.ts @@ -54,7 +54,11 @@ export function useSaveDrillHistory( `/drills/${drillId}`, (current: any) => current - ? { ...current, history: [...current.history, optimistic], last_drilled_at: ts } + ? { + ...current, + history: [...current.history, optimistic], + last_drilled_at: ts, + } : current, { revalidate: false } ); @@ -63,9 +67,22 @@ export function useSaveDrillHistory( result, reason: reason || undefined, moves, + timestamp: ts, }) - .then(() => { - mutate(`/drills/${drillId}`); + .then((saved) => { + mutate( + `/drills/${drillId}`, + (current: any) => + current + ? { + ...current, + history: current.history.map((h: any) => + h.timestamp === optimistic.timestamp ? saved : h + ), + } + : current, + { revalidate: false } + ); if (result === 'pass' && username) { try { const key = `bf:blunders_fixed:${username}`;