chat: retire the old saved-session store (Stage 4a)

Deletes all session persistence that isn't Claude's native transcript store, per
the "only harness-native session management survives" rule.

Backend: delete api/pi/storage.ts (meta.json+messages.json file store), the
api/saved-sessions router (+ unmount), the /pi/sessions REST endpoints, and the
storage.save/loadSession calls in the chat WS handler (in-memory session-manager
stays for live turns; no disk persistence — Claude's transcript is the record).
Also drops the Postgres saved_sessions layer: schema/chat.ts, queries/saved-sessions.ts,
its types and re-exports.

Frontend: delete state/useSavedSessions, ChatList, and the ChatHistory Widget
(all pure saved-session UI); slim ChatHeader to a label; strip the auto-load-latest
+ Save wiring from ChatPanelWrapper and ChatDetailPanel; drop the old resume path
from useChat and SessionListPage; remove the /chat/saved/:id route and the
useInitialData prefetch.

Behavior removed (intended): the Save-session button, email/project panels
auto-resuming the last chat, and /chat/saved/:id. /chat itself is unchanged —
already fully on Claude transcripts. The orphaned saved_sessions Postgres table
is dropped on the next `bun db:push`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-24 15:29:26 +00:00
co-authored by Claude Opus 4.8
parent 2e3c45ddfb
commit 70555c8d71
23 changed files with 31 additions and 1813 deletions
-2
View File
@@ -11,8 +11,6 @@ export { useRecentModels } from './useRecentModels';
export { usePlans } from './usePlans';
export { useLandingPage } from './useLandingPage';
export { useServerSettings } from './useServerSettings';
export { useSavedSessions } from './useSavedSessions';
export type { UseSavedSessionsType, SavedSessionEntry } from './useSavedSessions';
export { useUserApps } from './useUserApps';
export type { AppManifest } from './useUserApps';
export { useServerEnvironment } from './useServerEnvironment';
@@ -1,105 +0,0 @@
import { useAuth } from 'hooks/useAuth';
import { useClient } from 'hooks/useClient';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { toast } from 'sonner';
export type SavedSessionEntry = {
id: number;
provider: string;
context: string | null;
contextId: string | null;
title: string;
summary: string;
cwd: string;
cost: { inputTokens: number; outputTokens: number; totalUSD: number };
createdAt: string;
};
type RawMessage = {
id: string;
timestamp: number;
role: 'user' | 'assistant' | 'tool';
text?: string;
model?: string;
cost?: { inputTokens: number; outputTokens: number; totalUSD: number };
toolCallId?: string;
toolName?: string;
toolInput?: Record<string, unknown>;
output?: string;
isError?: boolean;
};
export type ResumeResult = {
context: string | null;
cwd: string;
model: string;
cost: { inputTokens: number; outputTokens: number; totalUSD: number };
rawMessages: RawMessage[];
};
export type { RawMessage };
export function messagesToTranscript(messages: RawMessage[]): string {
const lines: string[] = [];
for (const msg of messages) {
if (msg.role === 'user' && msg.text) {
lines.push(`User: ${msg.text}`);
} else if (msg.role === 'assistant' && msg.text) {
lines.push(`Assistant: ${msg.text}`);
} else if (msg.role === 'tool') {
const outcome = msg.isError ? `Error: ${msg.output}` : msg.output ? 'Success' : 'Pending';
lines.push(`Tool [${msg.toolName}]: ${outcome}`);
}
}
return lines.join('\n\n');
}
export function useSavedSessions() {
const client = useClient();
const queryClient = useQueryClient();
const { isAuthenticated } = useAuth();
const { data: sessions = [], isLoading } = useQuery<SavedSessionEntry[]>({
queryKey: ['SAVED_SESSIONS'],
enabled: isAuthenticated,
queryFn: () => client.get<SavedSessionEntry[]>('/saved-sessions'),
});
async function saveSession(sessionId: string) {
try {
const result = await client.post<SavedSessionEntry>('/saved-sessions', { sessionId });
queryClient.invalidateQueries({ queryKey: ['SAVED_SESSIONS'] });
toast.success('Session saved');
return result;
} catch (err) {
toast.error('Failed to save session');
throw err;
}
}
async function deleteSavedSession(id: number) {
await client.delete(`/saved-sessions/${id}`);
queryClient.setQueryData<SavedSessionEntry[]>(['SAVED_SESSIONS'], (prev) => prev?.filter((s) => s.id !== id) ?? []);
}
async function resumeSession(id: number) {
return client.post<ResumeResult>(`/saved-sessions/${id}/resume`);
}
async function updateSessionMessages(savedId: number, sessionId: string, resumedMessageCount?: number) {
await client.put(`/saved-sessions/${savedId}`, { sessionId, resumedMessageCount });
}
return {
sessions,
isLoading,
saveSession,
deleteSavedSession,
resumeSession,
updateSessionMessages,
};
}
export type UseSavedSessionsType = ReturnType<typeof useSavedSessions>;