From 4638e600431ec1cf7bced86ab15985634ccc8105 Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 14 Apr 2026 13:54:06 +0200 Subject: [PATCH] =?UTF-8?q?fix(v2):=20inventory=20no=20longer=20flickers?= =?UTF-8?q?=20=E2=80=94=20debounced=20re-fetch,=20no=20loading=20flash?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit inventory_delta WS messages were triggering immediate full re-fetch with setLoading(true), causing content to flash blank. Now: - Initial load shows loading state (once) - Subsequent deltas debounced to 2s (batches rapid changes) - Re-fetch runs silently without clearing existing items Co-Authored-By: Claude Opus 4.6 (1M context) --- .../components/windows/InventoryWindow.tsx | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/windows/InventoryWindow.tsx b/frontend/src/components/windows/InventoryWindow.tsx index fda806e4..51454847 100644 --- a/frontend/src/components/windows/InventoryWindow.tsx +++ b/frontend/src/components/windows/InventoryWindow.tsx @@ -163,19 +163,33 @@ export const InventoryWindow: React.FC = ({ id, charName, zIndex, invento const [activePack, setActivePack] = useState(null); const [tooltip, setTooltip] = useState<{ item: any; x: number; y: number } | null>(null); const [charStats, setCharStats] = useState(null); - const [cantripState, setCantripState] = useState(null); + const debounceRef = useRef(0); + const initialLoadDone = useRef(false); + // Initial fetch useEffect(() => { setLoading(true); Promise.all([ apiFetch(`/inventory/${encodeURIComponent(charName)}?limit=1000`).catch(() => ({ items: [] })), apiFetch(`/character-stats/${encodeURIComponent(charName)}`).catch(() => null), ]).then(([inv, stats]) => { - const rawItems = inv.items ?? []; - setItems(rawItems.map(normalizeItem)); + setItems((inv.items ?? []).map(normalizeItem)); setCharStats(stats); + initialLoadDone.current = true; }).finally(() => setLoading(false)); - }, [charName, inventoryVersion]); // re-fetch when inventory_delta arrives + }, [charName]); + + // Debounced re-fetch on inventory_delta (no loading flash) + useEffect(() => { + if (!initialLoadDone.current || !inventoryVersion) return; + clearTimeout(debounceRef.current); + debounceRef.current = window.setTimeout(() => { + apiFetch(`/inventory/${encodeURIComponent(charName)}?limit=1000`) + .then(inv => setItems((inv.items ?? []).map(normalizeItem))) + .catch(() => {}); + }, 2000); // 2s debounce — batch rapid deltas + return () => clearTimeout(debounceRef.current); + }, [charName, inventoryVersion]); const handleHover = useCallback((item: any | null, e?: React.MouseEvent) => { if (item && e) setTooltip({ item, x: e.clientX, y: e.clientY });