Merged tasks are endlessly re-rendering (vibe-kanban) (#938)
* ## ✅ Fix Complete **Problem:** Merged tasks caused endless WebSocket reconnections because `useJsonPatchWsStream` incorrectly treated `{finished: true}` messages as signals to reconnect. **Solution:** Modified [useJsonPatchWsStream.ts](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/0f2d-merged-tasks-are/frontend/src/hooks/useJsonPatchWsStream.ts) to treat "finished" as terminal: 1. Added `finishedRef` to track when stream completes 2. On `{finished: true}`: set flag, close cleanly (code 1000), **no reconnect** 3. On socket close: skip reconnection if finished flag is set or clean close 4. Reset flag on cleanup and new connections **Result:** - Merged tasks connect once, receive final state, and stop cleanly - Active tasks still reconnect on network errors - Aligned behavior with existing `streamJsonPatchEntries` utility - All type checks pass ✅ * Cleanup script changes for task attempt 0f2d0086-1de2-4517-a023-1ee8cf133181
This commit is contained in:
committed by
GitHub
parent
fef06cf00e
commit
73f49cae9f
@@ -40,6 +40,7 @@ export const useJsonPatchWsStream = <T>(
|
|||||||
const retryTimerRef = useRef<number | null>(null);
|
const retryTimerRef = useRef<number | null>(null);
|
||||||
const retryAttemptsRef = useRef<number>(0);
|
const retryAttemptsRef = useRef<number>(0);
|
||||||
const [retryNonce, setRetryNonce] = useState(0);
|
const [retryNonce, setRetryNonce] = useState(0);
|
||||||
|
const finishedRef = useRef<boolean>(false);
|
||||||
|
|
||||||
function scheduleReconnect() {
|
function scheduleReconnect() {
|
||||||
if (retryTimerRef.current) return; // already scheduled
|
if (retryTimerRef.current) return; // already scheduled
|
||||||
@@ -64,6 +65,7 @@ export const useJsonPatchWsStream = <T>(
|
|||||||
retryTimerRef.current = null;
|
retryTimerRef.current = null;
|
||||||
}
|
}
|
||||||
retryAttemptsRef.current = 0;
|
retryAttemptsRef.current = 0;
|
||||||
|
finishedRef.current = false;
|
||||||
setData(undefined);
|
setData(undefined);
|
||||||
setIsConnected(false);
|
setIsConnected(false);
|
||||||
setError(null);
|
setError(null);
|
||||||
@@ -85,6 +87,9 @@ export const useJsonPatchWsStream = <T>(
|
|||||||
|
|
||||||
// Create WebSocket if it doesn't exist
|
// Create WebSocket if it doesn't exist
|
||||||
if (!wsRef.current) {
|
if (!wsRef.current) {
|
||||||
|
// Reset finished flag for new connection
|
||||||
|
finishedRef.current = false;
|
||||||
|
|
||||||
// Convert HTTP endpoint to WebSocket endpoint
|
// Convert HTTP endpoint to WebSocket endpoint
|
||||||
const wsEndpoint = endpoint.replace(/^http/, 'ws');
|
const wsEndpoint = endpoint.replace(/^http/, 'ws');
|
||||||
const ws = new WebSocket(wsEndpoint);
|
const ws = new WebSocket(wsEndpoint);
|
||||||
@@ -124,13 +129,12 @@ export const useJsonPatchWsStream = <T>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle finished messages ({finished: true})
|
// Handle finished messages ({finished: true})
|
||||||
|
// Treat finished as terminal - do NOT reconnect
|
||||||
if ('finished' in msg) {
|
if ('finished' in msg) {
|
||||||
ws.close();
|
finishedRef.current = true;
|
||||||
|
ws.close(1000, 'finished');
|
||||||
wsRef.current = null;
|
wsRef.current = null;
|
||||||
setIsConnected(false);
|
setIsConnected(false);
|
||||||
// Treat finished as terminal and schedule reconnect; servers may rotate
|
|
||||||
retryAttemptsRef.current += 1;
|
|
||||||
scheduleReconnect();
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to process WebSocket message:', err);
|
console.error('Failed to process WebSocket message:', err);
|
||||||
@@ -142,9 +146,16 @@ export const useJsonPatchWsStream = <T>(
|
|||||||
setError('Connection failed');
|
setError('Connection failed');
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.onclose = () => {
|
ws.onclose = (evt) => {
|
||||||
setIsConnected(false);
|
setIsConnected(false);
|
||||||
wsRef.current = null;
|
wsRef.current = null;
|
||||||
|
|
||||||
|
// Do not reconnect if we received a finished message or clean close
|
||||||
|
if (finishedRef.current || (evt?.code === 1000 && evt?.wasClean)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, reconnect on unexpected/error closures
|
||||||
retryAttemptsRef.current += 1;
|
retryAttemptsRef.current += 1;
|
||||||
scheduleReconnect();
|
scheduleReconnect();
|
||||||
};
|
};
|
||||||
@@ -170,6 +181,7 @@ export const useJsonPatchWsStream = <T>(
|
|||||||
window.clearTimeout(retryTimerRef.current);
|
window.clearTimeout(retryTimerRef.current);
|
||||||
retryTimerRef.current = null;
|
retryTimerRef.current = null;
|
||||||
}
|
}
|
||||||
|
finishedRef.current = false;
|
||||||
dataRef.current = undefined;
|
dataRef.current = undefined;
|
||||||
setData(undefined);
|
setData(undefined);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user