Preserve draft views during collection refreshes

This commit is contained in:
2026-07-25 17:14:39 -07:00
parent 211fad972a
commit 048456b8a5

View File

@@ -93,6 +93,11 @@ type ActiveRunsState = {
chats: Record<string, true>;
searches: Record<string, true>;
};
type RefreshCollectionsOptions = {
preferredSelection?: SidebarSelection;
reportTransientError?: boolean;
selectFallback?: boolean;
};
function readSidebarSelectionFromUrl(): SidebarSelection | null {
if (typeof window === "undefined") return null;
@@ -1125,7 +1130,11 @@ export default function App() {
resetWorkspaceState();
};
const refreshCollections = async (preferredSelection?: SidebarSelection, reportTransientError = true) => {
const refreshCollections = async ({
preferredSelection,
reportTransientError = true,
selectFallback = false,
}: RefreshCollectionsOptions = {}) => {
setIsLoadingCollections(true);
try {
const nextWorkspaceItems = await listWorkspaceItems();
@@ -1146,6 +1155,9 @@ export default function App() {
if (hasItem(current)) {
return current;
}
if (!selectFallback) {
return null;
}
const first = nextWorkspaceItems[0];
return first ? { kind: first.type, id: first.id } : null;
});
@@ -1242,7 +1254,12 @@ export default function App() {
if (!isAuthenticated) return;
const preferredSelection = initialRouteSelectionRef.current;
initialRouteSelectionRef.current = null;
void Promise.all([refreshCollections(preferredSelection ?? undefined), refreshModels(), refreshChatTools(), refreshActiveRuns()]);
void Promise.all([
refreshCollections({ preferredSelection: preferredSelection ?? undefined, selectFallback: true }),
refreshModels(),
refreshChatTools(),
refreshActiveRuns(),
]);
}, [isAuthenticated]);
useEffect(() => {
@@ -1269,8 +1286,8 @@ export default function App() {
}
setError((current) => (current && isRecoverableStreamDisconnect(new Error(current)) ? null : current));
void refreshActiveRuns();
void refreshCollections(selectedItemRef.current ?? undefined, false);
const currentSelection = selectedItemRef.current;
void refreshCollections({ reportTransientError: false });
if (currentSelection?.kind === "chat") {
void refreshChat(currentSelection.id);
} else if (currentSelection?.kind === "search") {
@@ -1385,10 +1402,18 @@ export default function App() {
const selectedKey = selectedItem ? `${selectedItem.kind}:${selectedItem.id}` : null;
const transcriptViewKey = draftKind ? `draft:${draftKind}` : selectedKey ?? "empty";
const selectedChatPendingState = selectedItem?.kind === "chat" ? pendingChatStates[selectedItem.id] ?? null : null;
const selectedSearchRunState = selectedItem?.kind === "search" ? runningSearchStates[selectedItem.id] ?? null : null;
const selectedChatIsActive = selectedItem?.kind === "chat" && (!!selectedChatPendingState || !!activeRuns.chats[selectedItem.id]);
const selectedSearchIsActive = selectedItem?.kind === "search" && (!!selectedSearchRunState || !!activeRuns.searches[selectedItem.id]);
const selectedChatPendingState =
draftKind === null && selectedItem?.kind === "chat" ? pendingChatStates[selectedItem.id] ?? null : null;
const selectedSearchRunState =
draftKind === null && selectedItem?.kind === "search" ? runningSearchStates[selectedItem.id] ?? null : null;
const selectedChatIsActive =
draftKind === null &&
selectedItem?.kind === "chat" &&
(!!selectedChatPendingState || !!activeRuns.chats[selectedItem.id]);
const selectedSearchIsActive =
draftKind === null &&
selectedItem?.kind === "search" &&
(!!selectedSearchRunState || !!activeRuns.searches[selectedItem.id]);
const isSearchMode = draftKind ? draftKind === "search" : selectedItem?.kind === "search";
const isSearchRunning = !!selectedSearchIsActive;
const isSendingActiveChat = draftKind !== "search" && !!selectedChatIsActive;
@@ -1495,7 +1520,10 @@ export default function App() {
};
}, []);
const messages = selectedChat?.messages ?? [];
const messages =
draftKind === null && selectedItem?.kind === "chat" && selectedChat?.id === selectedItem.id
? selectedChat.messages
: [];
useEffect(() => {
if (isSearchMode && pendingAttachments.length) {
@@ -1602,6 +1630,7 @@ export default function App() {
setError(null);
setContextMenu(null);
setDraftKind("chat");
selectedItemRef.current = null;
setSelectedItem(null);
setSelectedChat(null);
setSelectedSearch(null);
@@ -1738,6 +1767,7 @@ export default function App() {
setError(null);
setContextMenu(null);
setDraftKind("search");
selectedItemRef.current = null;
setSelectedItem(null);
setSelectedChat(null);
setSelectedSearch(null);
@@ -2065,7 +2095,7 @@ export default function App() {
} else {
await deleteSearch(target.id);
}
await refreshCollections();
await refreshCollections({ selectFallback: true });
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
if (message.includes("bearer token")) {
@@ -2450,7 +2480,7 @@ export default function App() {
backgroundSuspendedChatStreamsRef.current.delete(chatId);
const persistedChat = await retryAfterAppResume(() => getChat(chatId));
await refreshCollections(target, false);
await refreshCollections({ preferredSelection: target, reportTransientError: false });
if (isCurrentSelection(target)) {
setSelectedChat(persistedChat);
setSelectedSearch(null);
@@ -2664,7 +2694,7 @@ export default function App() {
}
}
await refreshCollections(target);
await refreshCollections({ preferredSelection: target });
return target;
};
@@ -2783,7 +2813,7 @@ export default function App() {
setSelectedChat(persistedChat);
setSelectedSearch(null);
}
void refreshCollections(target);
void refreshCollections({ preferredSelection: target });
return;
} catch (resumeError) {
err = resumeError;
@@ -2889,7 +2919,7 @@ export default function App() {
{ signal: abortController.signal }
);
await refreshCollections(target);
await refreshCollections({ preferredSelection: target });
if (isCurrentSelection(target)) {
await refreshSearch(searchId);
}
@@ -2957,7 +2987,7 @@ export default function App() {
messages: [],
});
setSelectedSearch(null);
await refreshCollections({ kind: "chat", id: chat.id });
await refreshCollections({ preferredSelection: { kind: "chat", id: chat.id } });
await refreshChat(chat.id);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
@@ -3119,7 +3149,7 @@ export default function App() {
messages: [],
});
setSelectedSearch(null);
await refreshCollections({ kind: "chat", id: chat.id });
await refreshCollections({ preferredSelection: { kind: "chat", id: chat.id } });
await refreshChat(chat.id);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);