fix(web): retire session archive UI and API, restore archived rows
Independent Staging Quality Gate / validate (push) Failing after 6m35s
Independent Staging Quality Gate / publish (push) Skipped

Remove archive/restore from the session menu and drop archived=1 listing. PATCH still accepts archived_at from old bundles then ignores it. A forward migration clears archived_at without bumping updated_at. Delete stays unchanged.
This commit is contained in:
jesse-ux
2026-09-21 18:49:40 +08:00
parent f8cb1dcdee
commit 5a4b87383f
29 changed files with 227 additions and 198 deletions
@@ -40,10 +40,7 @@ type HomeShellRegistrationParams = {
setPendingSessionDeletion: Dispatch<SetStateAction<ChatSession | null>>;
setSessionMenuId: Dispatch<SetStateAction<string | null>>;
shareSession: (session: ChatSession) => void | Promise<void>;
showArchivedSessions: boolean;
startNewChat: () => void | Promise<unknown>;
toggleArchivedSession: (sessionId: string) => void;
toggleArchivedView: () => void | Promise<void>;
togglePinnedSession: (sessionId: string) => void;
visibleSessions: readonly ChatSession[];
};
@@ -78,10 +75,7 @@ export function useHomeShellRegistration(params: HomeShellRegistrationParams) {
setPendingSessionDeletion,
setSessionMenuId,
shareSession,
showArchivedSessions,
startNewChat,
toggleArchivedSession,
toggleArchivedView,
togglePinnedSession,
visibleSessions,
} = params;
@@ -115,13 +109,10 @@ export function useHomeShellRegistration(params: HomeShellRegistrationParams) {
accountMenuOpen,
accountTriggerRef: accountTrigger,
sessionControls: {
archivedCount: sessions.filter((session) => session.archivedAt).length,
showingArchived: showArchivedSessions,
hasMore: Boolean(sessionsCursor),
onLoadMore: loadMoreSessions,
menuSessionId: sessionMenuId,
disabled: Boolean(pendingSessionId) || cancellationPending,
onToggleArchivedView: () => { void toggleArchivedView(); setSessionMenuId(null); },
onMenuSessionChange: setSessionMenuId,
onTogglePinned: togglePinnedSession,
onRename: (sessionId) => {
@@ -132,7 +123,6 @@ export function useHomeShellRegistration(params: HomeShellRegistrationParams) {
const session = sessions.find((candidate) => candidate.id === sessionId);
if (session) void shareSession(session);
},
onToggleArchived: toggleArchivedSession,
onDelete: (sessionId) => {
const session = sessions.find((candidate) => candidate.id === sessionId);
if (session) setPendingSessionDeletion(session);
@@ -155,8 +145,7 @@ export function useHomeShellRegistration(params: HomeShellRegistrationParams) {
rectificationErrorSessionId, rectificationOpeningSessionId, rectificationSurfaceOpen,
registerShellControls, renameSession, selectSession, sessionMenuId, sessions,
sessionsCursor, setAccountMenuOpen, setPendingSessionDeletion, setSessionMenuId,
shareSession, showArchivedSessions, startNewChat, toggleArchivedSession,
toggleArchivedView, togglePinnedSession, visibleSessions,
shareSession, startNewChat, togglePinnedSession, visibleSessions,
]);
useEffect(() => () => registerShellControls(null), [registerShellControls]);
+1 -53
View File
@@ -127,7 +127,6 @@ export function useSessionManagement(params: SessionManagementParams) {
// Owned here since 2026-09-16 (state lowering batch 2). Home reads back only
// what its own JSX renders; the rest never leaves this hook.
const [sessionsCursor, setSessionsCursor] = useState<string | null>(null);
const [showArchivedSessions, setShowArchivedSessions] = useState(false);
const [sessionMenuId, setSessionMenuId] = useState<string | null>(null);
const [pendingSessionDeletion, setPendingSessionDeletion] = useState<ChatSession | null>(null);
const [sessionDetailLoadingId, setSessionDetailLoadingId] = useState<string | null>(null);
@@ -140,15 +139,7 @@ export function useSessionManagement(params: SessionManagementParams) {
if (!isUnsavedEmptyConsultation(session)) cloudCreatedIds.current.add(session.id);
}
const visibleSessions = sortSessions(sessions.filter((session) => {
if (showArchivedSessions) {
return Boolean(session.archivedAt)
&& (session.sessionType === "birth_time_rectification"
|| session.messages.length > 0
|| !session.messagesHydrated);
}
return isListedSidebarSession(session);
}));
const visibleSessions = sortSessions(sessions.filter(isListedSidebarSession));
function updateSession(sessionId: string, change: (session: ChatSession) => ChatSession) {
setSessions((current) => current.map((session) => (session.id === sessionId ? change(session) : session)));
@@ -273,27 +264,6 @@ export function useSessionManagement(params: SessionManagementParams) {
});
}
function toggleArchivedSession(sessionId: string) {
const session = sessions.find((item) => item.id === sessionId);
if (!session) return;
const restoring = Boolean(session.archivedAt);
const previousActiveId = activeSessionId;
const nextArchivedAt = restoring ? null : new Date().toISOString();
updateSession(sessionId, (current) => ({ ...current, archivedAt: nextArchivedAt }));
if (!restoring && activeSessionId === sessionId) {
activateFallbackSession(visibleSessions.filter((item) => item.id !== sessionId));
}
setComposerNotice(restoring ? "已恢复到聊天记录。" : "已归档,可在左侧归档中恢复。");
void writeChatSession(sessionId, { archived_at: nextArchivedAt }, "update").catch((caught) => {
updateSession(sessionId, (current) => ({ ...current, archivedAt: session.archivedAt }));
if (!restoring && previousActiveId === sessionId) {
setActiveSessionId(previousActiveId);
if (!uiPreview.current) writeSessionUrl(previousActiveId || null, "replace");
}
setComposerNotice(caught instanceof Error ? caught.message : "归档同步失败");
});
}
async function shareSession(session: ChatSession) {
const sharePayload = {
share_payload_version: 1,
@@ -404,7 +374,6 @@ export function useSessionManagement(params: SessionManagementParams) {
try {
const page = await fetchSessions(undefined, {
before: sessionsCursor,
archived: showArchivedSessions,
});
const incoming = readSessions(page.sessions, modelCatalog).sessions;
for (const session of incoming) cloudCreatedIds.current.add(session.id);
@@ -418,24 +387,6 @@ export function useSessionManagement(params: SessionManagementParams) {
}
}
async function toggleArchivedView() {
const nextArchived = !showArchivedSessions;
setShowArchivedSessions(nextArchived);
try {
const page = await fetchSessions(undefined, { archived: nextArchived });
const parsed = readSessions(page.sessions, modelCatalog).sessions;
for (const session of parsed) cloudCreatedIds.current.add(session.id);
const active = sessionsRef.current.find((session) => session.id === activeSessionId);
setSessions(active && !parsed.some((session) => session.id === active.id)
? mergeHydratedSession(parsed, active)
: parsed);
setSessionsCursor(page.nextCursor);
} catch (caught) {
if (caught instanceof LoginRedirectError) return;
setComposerNotice(caught instanceof Error ? caught.message : "暂时无法读取聊天记录");
}
}
applySessionPopStateRef.current = (search: string) => {
if (uiPreview.current) return;
const listed = sessionsRef.current;
@@ -543,7 +494,6 @@ export function useSessionManagement(params: SessionManagementParams) {
visibleSessions,
sessionsCursor,
setSessionsCursor,
showArchivedSessions,
sessionMenuId,
setSessionMenuId,
pendingSessionDeletion,
@@ -556,12 +506,10 @@ export function useSessionManagement(params: SessionManagementParams) {
renameSession,
deleteSession,
togglePinnedSession,
toggleArchivedSession,
shareSession,
startNewChat,
selectSession,
selectSessionModel,
loadMoreSessions,
toggleArchivedView,
};
}