diff --git a/.changeset/delete-new-session-action.md b/.changeset/delete-new-session-action.md new file mode 100644 index 0000000..9481d7e --- /dev/null +++ b/.changeset/delete-new-session-action.md @@ -0,0 +1,5 @@ +--- +"@jmfederico/pi-web": patch +--- + +Add an action-palette command for deleting browser-cached new sessions, while keeping archive and delete session actions context-specific. diff --git a/src/client/src/components/PiWebApp.ts b/src/client/src/components/PiWebApp.ts index 92329dd..be43dd9 100644 --- a/src/client/src/components/PiWebApp.ts +++ b/src/client/src/components/PiWebApp.ts @@ -862,6 +862,7 @@ export class PiWebApp extends LitElement { deleteWorkspace: (workspace) => this.deleteWorkspace(workspace), startSession: () => this.withChatScrollTransition(() => this.sessions.startSession()), archiveSession: () => this.sessions.archiveSession(), + deleteCachedNewSession: () => this.sessions.deleteCachedNewSession(), stopActiveWork: () => this.sessions.stopActiveWork(), }, createContext); return createContext("core"); diff --git a/src/client/src/plugins/core/actions.ts b/src/client/src/plugins/core/actions.ts index e8fd181..219e178 100644 --- a/src/client/src/plugins/core/actions.ts +++ b/src/client/src/plugins/core/actions.ts @@ -1,5 +1,6 @@ import { isSessionActive } from "../../../../shared/activity"; import type { AppState } from "../../appState"; +import { isCachedNewSessionInfo } from "../../cachedNewSessions"; import { isWorkspaceDeletionPending } from "../../workspaceDeletion"; import type { PluginAction } from "../types"; @@ -138,9 +139,17 @@ export function createCoreActions(): PluginAction[] { title: "Archive Session", description: "Archive the selected session", group: "Session", - enabled: (context) => context.state.selectedSession !== undefined && context.state.selectedSession.archived !== true, + enabled: hasArchivableSession, run: (context) => context.archiveSession(), }, + { + id: "session.delete", + title: "Delete New Session", + description: "Delete the selected browser-cached new session", + group: "Session", + enabled: hasCachedNewSession, + run: (context) => context.deleteCachedNewSession(), + }, { id: "session.stop", title: "Stop Active Work", @@ -164,3 +173,12 @@ function hasDeletableWorkspace(context: { state: AppState }): boolean { const workspace = context.state.selectedWorkspace; return workspace !== undefined && workspace.isGitWorktree && !workspace.isMain && !isWorkspaceDeletionPending(context.state, workspace); } + +function hasArchivableSession(context: { state: AppState }): boolean { + const session = context.state.selectedSession; + return session !== undefined && session.archived !== true && !isCachedNewSessionInfo(session); +} + +function hasCachedNewSession(context: { state: AppState }): boolean { + return isCachedNewSessionInfo(context.state.selectedSession); +} diff --git a/src/client/src/plugins/registry.test.ts b/src/client/src/plugins/registry.test.ts index 03508de..ac8d12c 100644 --- a/src/client/src/plugins/registry.test.ts +++ b/src/client/src/plugins/registry.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it, vi } from "vitest"; -import type { Workspace } from "../api"; +import type { SessionInfo, Workspace } from "../api"; import { initialAppState, type AppState } from "../appState"; +import { markCachedNewSessionInfo } from "../cachedNewSessions"; import { corePlugin } from "./core"; import { PluginRegistry } from "./registry"; import { themePackPlugin } from "./themes"; @@ -34,6 +35,7 @@ function createContext(statePatch: Partial = {}) { deleteWorkspace: vi.fn(() => { calls.push("deleteWorkspace"); }), startSession: vi.fn(() => { calls.push("startSession"); }), archiveSession: vi.fn(() => { calls.push("archiveSession"); }), + deleteCachedNewSession: vi.fn(() => { calls.push("deleteCachedNewSession"); }), stopActiveWork: vi.fn(() => { calls.push("stopActiveWork"); }), }; return { context, calls }; @@ -98,6 +100,34 @@ describe("PluginRegistry", () => { expect(calls).toEqual(["deleteWorkspace"]); }); + it("offers archive only for persisted sessions and delete only for browser-cached new sessions", () => { + const registry = new PluginRegistry(); + registry.register({ id: "core", plugin: corePlugin }); + + const persistedActions = registry.getActions(createContext({ selectedSession: testSession() }).context); + expect(persistedActions.find((action) => action.id === "core:session.archive")?.enabled).toBe(true); + expect(persistedActions.find((action) => action.id === "core:session.delete")?.enabled).toBe(false); + + const cachedActions = registry.getActions(createContext({ selectedSession: markCachedNewSessionInfo(testSession()) }).context); + expect(cachedActions.find((action) => action.id === "core:session.archive")?.enabled).toBe(false); + expect(cachedActions.find((action) => action.id === "core:session.delete")?.enabled).toBe(true); + + const archivedActions = registry.getActions(createContext({ selectedSession: { ...testSession(), archived: true, archivedAt: "2026-05-20T00:00:00.000Z" } }).context); + expect(archivedActions.find((action) => action.id === "core:session.archive")?.enabled).toBe(false); + expect(archivedActions.find((action) => action.id === "core:session.delete")?.enabled).toBe(false); + }); + + it("routes browser-cached new session delete through the runtime context", () => { + const registry = new PluginRegistry(); + registry.register({ id: "core", plugin: corePlugin }); + const { context, calls } = createContext({ selectedSession: markCachedNewSessionInfo(testSession()) }); + const action = registry.getActions(context).find((candidate) => candidate.id === "core:session.delete"); + + if (action !== undefined) void action.run(); + + expect(calls).toEqual(["deleteCachedNewSession"]); + }); + it("routes refresh current to the active core workspace panel", () => { const registry = new PluginRegistry(); registry.register({ id: "core", plugin: corePlugin }); @@ -233,6 +263,19 @@ function testWorkspace(patch: Partial = {}): Workspace { return { id: "w1", projectId: "p1", path: "/tmp/project", label: "main", isMain: true, isGitRepo: true, isGitWorktree: false, ...patch }; } +function testSession(patch: Partial = {}): SessionInfo { + return { + id: "s1", + path: "/tmp/s1.jsonl", + cwd: "/tmp/project", + created: "2026-05-20T00:00:00.000Z", + modified: "2026-05-20T00:00:00.000Z", + messageCount: 1, + firstMessage: "Hello", + ...patch, + }; +} + function testThemeTokens(): ThemeTokens { return { "--pi-bg": "#000000", diff --git a/src/client/src/plugins/types.ts b/src/client/src/plugins/types.ts index ed2c779..3071c0e 100644 --- a/src/client/src/plugins/types.ts +++ b/src/client/src/plugins/types.ts @@ -66,6 +66,7 @@ export interface PluginRuntimeContext { deleteWorkspace: (workspace?: Workspace) => void | Promise; startSession: () => void | Promise; archiveSession: () => void | Promise; + deleteCachedNewSession: () => void | Promise; stopActiveWork: () => void | Promise; }