Select fallback session after archive

This commit is contained in:
Federico Jaramillo Martinez
2026-05-08 15:17:39 +02:00
parent 553808095b
commit 6dc8dc6029
3 changed files with 67 additions and 4 deletions
@@ -6,6 +6,7 @@ import { readChatHistoryCache, mergeChatHistory, writeChatHistoryCache, type Raw
import { applyTranscriptEvent } from "../chatTranscript";
import { isShellInput } from "../inputModes";
import { GlobalSessionSocket, SessionSocket, type SessionUiEvent } from "../sessionSocket";
import { markSessionArchived, selectionAfterArchivingSession } from "./sessionSelection";
import type { GetState, SetState, UpdateUrl } from "./types";
export class SessionController {
@@ -158,10 +159,15 @@ export class SessionController {
if (!session) return;
try {
await api.archive(session.id);
this.replaceSession({ ...session, archived: true, archivedAt: new Date().toISOString() });
if (this.getState().selectedSession?.id === session.id) {
this.socket.close();
this.setState({ status: undefined, activity: undefined });
const state = this.getState();
const sessions = markSessionArchived(state.sessions, session.id, new Date().toISOString());
const selectionChange = selectionAfterArchivingSession(sessions, state.selectedSession?.id, session.id);
this.setState({ sessions });
if (selectionChange.type === "select") await this.selectSession(selectionChange.session);
else if (selectionChange.type === "clear") {
this.clearActiveSession();
this.updateUrl();
}
} catch (error) {
this.setState({ error: String(error) });
@@ -0,0 +1,40 @@
import { describe, expect, it } from "vitest";
import type { SessionInfo } from "../api";
import { markSessionArchived, selectionAfterArchivingSession } from "./sessionSelection";
describe("markSessionArchived", () => {
it("marks the matching session archived without mutating the original", () => {
const sessions = [testSession("s1"), testSession("s2")];
const next = markSessionArchived(sessions, "s1", "later");
expect(next).toEqual([{ ...sessions[0], archived: true, archivedAt: "later" }, sessions[1]]);
expect(sessions[0]?.archived).toBeUndefined();
});
});
describe("selectionAfterArchivingSession", () => {
it("leaves selection unchanged when archiving an unselected session", () => {
expect(selectionAfterArchivingSession([testSession("s1"), testSession("s2")], "s1", "s2")).toEqual({ type: "unchanged" });
});
it("selects the first active session when archiving the selected session", () => {
const s2 = testSession("s2");
expect(selectionAfterArchivingSession([testSession("s1"), s2], "s1", "s1")).toEqual({ type: "select", session: s2 });
});
it("skips archived sessions when choosing the next selected session", () => {
const s3 = testSession("s3");
expect(selectionAfterArchivingSession([testSession("s1"), { ...testSession("s2"), archived: true }, s3], "s1", "s1")).toEqual({ type: "select", session: s3 });
});
it("clears selection when no active session remains", () => {
expect(selectionAfterArchivingSession([testSession("s1")], "s1", "s1")).toEqual({ type: "clear" });
});
});
function testSession(id: string): SessionInfo {
return { id, path: `/tmp/project/.pi/sessions/${id}`, cwd: "/tmp/project", created: "now", modified: "now", messageCount: 0, firstMessage: "" };
}
@@ -0,0 +1,17 @@
import type { SessionInfo } from "../api";
export type ArchiveSelectionChange =
| { type: "unchanged" }
| { type: "select"; session: SessionInfo }
| { type: "clear" };
export function markSessionArchived(sessions: SessionInfo[], sessionId: string, archivedAt: string): SessionInfo[] {
return sessions.map((session) => session.id === sessionId ? { ...session, archived: true, archivedAt } : session);
}
export function selectionAfterArchivingSession(sessions: SessionInfo[], selectedSessionId: string | undefined, archivedSessionId: string): ArchiveSelectionChange {
if (selectedSessionId !== archivedSessionId) return { type: "unchanged" };
const nextSession = sessions.find((session) => session.id !== archivedSessionId && session.archived !== true);
return nextSession === undefined ? { type: "clear" } : { type: "select", session: nextSession };
}