mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-06 05:49:12 +02:00
fix(chat): refresh message caches on reconnect (MUL-3831) (#4677)
Invalidate per-session chat message caches (messages, messages-page, pending-task, task-messages) on websocket reconnect / WS instance change so a chat that missed chat/task events while disconnected recovers without a full reload, matching the existing per-issue recovery pattern. Co-authored-by: Ryan <1141524679@qq.com>
This commit is contained in:
@@ -14,13 +14,17 @@ export const chatKeys = {
|
||||
/** Full sessions list (active + archived); the dropdown splits locally. */
|
||||
sessions: (wsId: string) => [...chatKeys.all(wsId), "sessions"] as const,
|
||||
session: (wsId: string, id: string) => [...chatKeys.all(wsId), "session", id] as const,
|
||||
messages: (sessionId: string) => ["chat", "messages", sessionId] as const,
|
||||
messagesPage: (sessionId: string) => ["chat", "messages-page", sessionId] as const,
|
||||
pendingTask: (sessionId: string) => ["chat", "pending-task", sessionId] as const,
|
||||
messagesAll: () => ["chat", "messages"] as const,
|
||||
messages: (sessionId: string) => [...chatKeys.messagesAll(), sessionId] as const,
|
||||
messagesPageAll: () => ["chat", "messages-page"] as const,
|
||||
messagesPage: (sessionId: string) => [...chatKeys.messagesPageAll(), sessionId] as const,
|
||||
pendingTaskAll: () => ["chat", "pending-task"] as const,
|
||||
pendingTask: (sessionId: string) => [...chatKeys.pendingTaskAll(), sessionId] as const,
|
||||
/** Aggregate of in-flight chat tasks for the current user — FAB reads this. */
|
||||
pendingTasks: (wsId: string) => [...chatKeys.all(wsId), "pending-tasks"] as const,
|
||||
/** Per-task execution messages — shared with issue agent cards. */
|
||||
taskMessages: (taskId: string) => ["task-messages", taskId] as const,
|
||||
taskMessagesAll: () => ["task-messages"] as const,
|
||||
taskMessages: (taskId: string) => [...chatKeys.taskMessagesAll(), taskId] as const,
|
||||
};
|
||||
|
||||
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
|
||||
@@ -102,9 +102,9 @@ describe("useRealtimeSync — ws instance change", () => {
|
||||
rerender({ ws: ws2 });
|
||||
|
||||
// Should have called invalidateQueries for all workspace-scoped keys
|
||||
// (15 workspace-scoped + 6 per-issue prefixes + 1 workspaceKeys.list()
|
||||
// + 1 cross-workspace inbox unread summary = 23 calls)
|
||||
expect(invalidateSpy).toHaveBeenCalledTimes(23);
|
||||
// (15 workspace-scoped + 6 per-issue prefixes + 4 per-chat prefixes
|
||||
// + 1 workspaceKeys.list() + 1 cross-workspace inbox unread summary = 27 calls)
|
||||
expect(invalidateSpy).toHaveBeenCalledTimes(27);
|
||||
});
|
||||
|
||||
it("does not re-invalidate when rerendered with the same ws instance", () => {
|
||||
@@ -164,4 +164,26 @@ describe("useRealtimeSync — ws instance change", () => {
|
||||
expect(calls).toContainEqual(["issues", "attachments"]);
|
||||
expect(calls).toContainEqual(["issues", "tasks"]);
|
||||
});
|
||||
|
||||
it("invalidates per-chat-session caches (no wsId in key) on ws instance change", () => {
|
||||
// These keys are not under the ["chat", wsId] prefix, so they need their
|
||||
// own recovery invalidation when reconnecting after missed chat/task events.
|
||||
const ws1 = createMockWs();
|
||||
const { rerender } = renderHook(
|
||||
({ ws }) => useRealtimeSync(ws, stores),
|
||||
{ initialProps: { ws: ws1 as WSClient | null }, wrapper: createWrapper(qc) },
|
||||
);
|
||||
|
||||
invalidateSpy.mockClear();
|
||||
rerender({ ws: null });
|
||||
|
||||
const ws2 = createMockWs();
|
||||
rerender({ ws: ws2 });
|
||||
|
||||
const calls = invalidateSpy.mock.calls.map((call: [{ queryKey?: unknown }, ...unknown[]]) => call[0].queryKey);
|
||||
expect(calls).toContainEqual(["chat", "messages"]);
|
||||
expect(calls).toContainEqual(["chat", "messages-page"]);
|
||||
expect(calls).toContainEqual(["chat", "pending-task"]);
|
||||
expect(calls).toContainEqual(["task-messages"]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -340,6 +340,14 @@ function invalidateWorkspaceScopedQueries(qc: QueryClient): void {
|
||||
qc.invalidateQueries({ queryKey: issueKeys.usageAll() });
|
||||
qc.invalidateQueries({ queryKey: issueKeys.attachmentsAll() });
|
||||
qc.invalidateQueries({ queryKey: issueKeys.tasksAll() });
|
||||
// Per-chat-session caches are also keyed without wsId, so the
|
||||
// chatKeys.all(wsId) prefix above only reaches session lists / aggregates.
|
||||
// Message streams rely on WS invalidation with staleTime: Infinity; recover
|
||||
// sessions that missed chat/task events while the socket was disconnected.
|
||||
qc.invalidateQueries({ queryKey: chatKeys.messagesAll() });
|
||||
qc.invalidateQueries({ queryKey: chatKeys.messagesPageAll() });
|
||||
qc.invalidateQueries({ queryKey: chatKeys.pendingTaskAll() });
|
||||
qc.invalidateQueries({ queryKey: chatKeys.taskMessagesAll() });
|
||||
qc.invalidateQueries({ queryKey: workspaceKeys.list() });
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user