fix: prevent stale planning notifications
This commit is contained in:
7
.changeset/fix-stale-planning-background-task.md
Normal file
7
.changeset/fix-stale-planning-background-task.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Prevent stale Planning notifications from pointing to missing sessions.
|
||||
category: fix
|
||||
dev: Background task sync now defers to the authoritative server session list.
|
||||
@@ -553,6 +553,40 @@ describe("useBackgroundSessions", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("does not materialize an older cross-tab session after the authoritative list is empty", async () => {
|
||||
const { result } = renderHook(() => useBackgroundSessions());
|
||||
const { result: syncResult } = renderHook(() => useAiSessionSync());
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockFetchAiSessions).toHaveBeenCalledWith(undefined);
|
||||
expect(result.current.sessions).toEqual([]);
|
||||
});
|
||||
|
||||
/*
|
||||
* FNXC:BackgroundTasks 2026-07-11-19:08:
|
||||
* A sibling tab can respond after the server has removed its planning
|
||||
* session. Background Tasks must then remain empty because Planning reads
|
||||
* the authoritative server list and cannot resume that stale cache entry.
|
||||
*/
|
||||
act(() => {
|
||||
syncResult.current.broadcastUpdate({
|
||||
sessionId: "late-stale-planning",
|
||||
status: "awaiting_input",
|
||||
needsInput: true,
|
||||
type: "planning",
|
||||
title: "Late stale planning session",
|
||||
updatedAt: "2026-04-08T00:00:01.000Z",
|
||||
timestamp: Date.parse("2026-04-08T00:00:01.000Z"),
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.sessions).toEqual([]);
|
||||
expect(result.current.planningSessions).toEqual([]);
|
||||
expect(result.current.needsInput).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
it("dismissSession calls cancelSubtaskBreakdown for subtask sessions", async () => {
|
||||
mockFetchAiSessions.mockResolvedValueOnce([
|
||||
makeSession({ id: "subtask-session", status: "generating", type: "subtask" }),
|
||||
|
||||
@@ -51,6 +51,7 @@ export function useBackgroundSessions(projectId?: string): UseBackgroundSessions
|
||||
const [sessions, setSessions] = useState<AiSessionSummary[]>([]);
|
||||
const sessionTimestampsRef = useRef<Map<string, number>>(new Map());
|
||||
const dismissedSessionTimestampsRef = useRef<Map<string, number>>(new Map());
|
||||
const lastAuthoritativeRefreshAtRef = useRef(0);
|
||||
|
||||
const {
|
||||
sessions: syncedSessions,
|
||||
@@ -83,6 +84,16 @@ export function useBackgroundSessions(projectId?: string): UseBackgroundSessions
|
||||
for (const session of filtered) {
|
||||
nextTimestampMap.set(session.id, parseTimestamp(session.updatedAt));
|
||||
}
|
||||
|
||||
/*
|
||||
* FNXC:BackgroundTasks 2026-07-11-19:08:
|
||||
* Planning must never advertise a cached cross-tab session that the
|
||||
* server's current project-scoped list does not contain. A late sync
|
||||
* response is only a latency aid, so suppress updates older than this
|
||||
* successful authoritative refresh; newer updates may still represent
|
||||
* a session created immediately after the list request.
|
||||
*/
|
||||
lastAuthoritativeRefreshAtRef.current = Date.now();
|
||||
sessionTimestampsRef.current = nextTimestampMap;
|
||||
setSessions(filtered);
|
||||
})
|
||||
@@ -118,6 +129,10 @@ export function useBackgroundSessions(projectId?: string): UseBackgroundSessions
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!nextById.has(syncState.sessionId) && incomingTimestamp <= lastAuthoritativeRefreshAtRef.current) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const dismissedTimestamp = dismissedSessionTimestampsRef.current.get(syncState.sessionId);
|
||||
if (dismissedTimestamp !== undefined && incomingTimestamp <= dismissedTimestamp) {
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user