FN-5884: preserve quick chat session across reopens

Keep quick chat warm and restore the last opened conversation per project.

- persist the active quick chat session id in per-project local storage and restore it before falling back to latest activity
- retain in-memory quick chat state across close/reopen while resetting chat state correctly when the project changes
- add storage, hook, and FAB coverage for restored sessions, warm reopen behavior, and project switching; document the updated quick chat behavior

Files changed:
 docs/dashboard-guide.md                            |   3 +-
 packages/dashboard/app/components/QuickChatFAB.tsx |  29 ++++--
 .../app/components/__tests__/QuickChatFAB.test.tsx | 112 +++++++++++++++++++--
 .../__tests__/quickChatLastSessionStorage.test.ts  |  55 ++++++++++
 .../app/hooks/__tests__/useQuickChat.test.ts       |  70 +++++++++++++
 .../app/hooks/quickChatLastSessionStorage.ts       |  41 ++++++++
 packages/dashboard/app/hooks/useQuickChat.ts       |  25 +++++
 7 files changed, 319 insertions(+), 16 deletions(-)

Fusion-Task-Id: FN-5884

Fusion-Task-Lineage: 4d689611-1824-41db-8fc2-d543407316ff
This commit is contained in:
gsxdsm
2026-06-02 09:34:19 -07:00
parent cf3b9a575e
commit 9e00a72d7d
7 changed files with 319 additions and 16 deletions

View File

@@ -26,6 +26,7 @@ import {
removePersistedPendingChatMessage,
setPersistedPendingChatMessage,
} from "./chatPendingMessageStorage";
import { setPersistedLastQuickChatSessionId } from "./quickChatLastSessionStorage";
import { isLikelyTabSuspensionError, useTabVisibilitySuspension } from "./visibilitySuspension";
interface ModelSelection {
@@ -251,6 +252,14 @@ export function useQuickChat(
lastAttachedGenerationRef.current = null;
}, [projectId]);
useEffect(() => {
if (!activeSession?.id) {
return;
}
setPersistedLastQuickChatSessionId(projectId, activeSession.id);
}, [activeSession?.id, projectId]);
const refreshSessions = useCallback(async () => {
setSessionsLoading(true);
try {
@@ -532,6 +541,22 @@ export function useQuickChat(
setIsStreaming(false);
}, []);
useEffect(() => {
if (streamRef.current) {
streamRef.current.close();
streamRef.current = null;
}
lastAttachedGenerationRef.current = null;
currentSessionKeyRef.current = "";
currentSessionTargetRef.current = null;
activeSessionRef.current = null;
setActiveSession(null);
setMessages([]);
setSessions([]);
resetTransientComposerState();
}, [projectId, resetTransientComposerState]);
// Switch to a different chat target session
const switchSession = useCallback(
async (agentId: string, modelProvider?: string, modelId?: string) => {