mirror of
https://github.com/davidkaya/aryx.git
synced 2026-08-06 03:38:44 +02:00
feat: batch session archive and delete with multi-select UI
Add multi-select mode to the sidebar session list, enabling users to archive, restore, or delete multiple sessions at once. UX: - Ctrl+Click (Cmd+Click on Mac) any session to enter multi-select mode - Animated checkboxes stagger-reveal across all visible sessions - Click to toggle, Shift+Click for range selection - Running sessions are excluded from selection (safety guard) - Floating action bar slides up with count pill, Archive, Delete, Cancel - Batch delete shows a confirmation dialog with hold-to-confirm for 3+ sessions and a scrollable list of session titles - Batch archive shows an undo toast with 5-second auto-dismiss - Escape key or Cancel button exits multi-select mode Backend: - batchSetSessionsArchived IPC: single load/mutate/persist cycle for N sessions instead of N sequential calls - batchDeleteSessions IPC: parallel cleanup of scratchpad dirs and SDK sessions, then single workspace persist Accessibility: - Action bar: role=toolbar, aria-label - Checkboxes: role=checkbox, aria-checked, keyboard-activatable - Selection count: aria-live=polite - Delete dialog: role=alertdialog, aria-modal, focus trap Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -1149,6 +1149,50 @@ export class AryxAppService extends EventEmitter<AppServiceEvents> {
|
||||
return this.persistAndBroadcast(workspace);
|
||||
}
|
||||
|
||||
async batchSetSessionsArchived(sessionIds: string[], isArchived: boolean): Promise<WorkspaceState> {
|
||||
const workspace = await this.loadWorkspace();
|
||||
const now = nowIso();
|
||||
for (const sessionId of sessionIds) {
|
||||
const session = workspace.sessions.find((s) => s.id === sessionId);
|
||||
if (session) {
|
||||
session.isArchived = isArchived;
|
||||
session.updatedAt = now;
|
||||
}
|
||||
}
|
||||
return this.persistAndBroadcast(workspace);
|
||||
}
|
||||
|
||||
async batchDeleteSessions(sessionIds: string[]): Promise<WorkspaceState> {
|
||||
const workspace = await this.loadWorkspace();
|
||||
const idsToDelete = new Set(sessionIds);
|
||||
|
||||
// Collect cleanup work before mutating the array
|
||||
const cleanupTasks: Promise<void>[] = [];
|
||||
for (const session of workspace.sessions) {
|
||||
if (!idsToDelete.has(session.id)) continue;
|
||||
|
||||
const scratchpadDirectory = this.resolveScratchpadSessionDirectory(session);
|
||||
if (scratchpadDirectory) {
|
||||
cleanupTasks.push(rm(scratchpadDirectory, { recursive: true, force: true }));
|
||||
}
|
||||
cleanupTasks.push(
|
||||
this.sidecar.deleteSession(session.id).then(() => undefined).catch(() => {
|
||||
// Best-effort — don't fail the deletion if SDK cleanup fails
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.allSettled(cleanupTasks);
|
||||
|
||||
workspace.sessions = workspace.sessions.filter((s) => !idsToDelete.has(s.id));
|
||||
|
||||
if (workspace.selectedSessionId && idsToDelete.has(workspace.selectedSessionId)) {
|
||||
workspace.selectedSessionId = workspace.sessions[0]?.id;
|
||||
}
|
||||
|
||||
return this.persistAndBroadcast(workspace);
|
||||
}
|
||||
|
||||
async regenerateSessionMessage(sessionId: string, messageId: string): Promise<void> {
|
||||
const workspace = await this.loadWorkspace();
|
||||
const session = this.requireSession(workspace, sessionId);
|
||||
|
||||
@@ -4,6 +4,8 @@ import type { BrowserWindow } from 'electron';
|
||||
import { ipcChannels } from '@shared/contracts/channels';
|
||||
import type {
|
||||
BranchSessionInput,
|
||||
BatchDeleteSessionsInput,
|
||||
BatchSetSessionsArchivedInput,
|
||||
CancelSessionTurnInput,
|
||||
CommitProjectGitChangesInput,
|
||||
CreateSessionInput,
|
||||
@@ -224,6 +226,12 @@ export function registerIpcHandlers(
|
||||
ipcMain.handle(ipcChannels.deleteSession, (_event, input: DeleteSessionInput) =>
|
||||
service.deleteSession(input.sessionId),
|
||||
);
|
||||
ipcMain.handle(ipcChannels.batchSetSessionsArchived, (_event, input: BatchSetSessionsArchivedInput) =>
|
||||
service.batchSetSessionsArchived(input.sessionIds, input.isArchived),
|
||||
);
|
||||
ipcMain.handle(ipcChannels.batchDeleteSessions, (_event, input: BatchDeleteSessionsInput) =>
|
||||
service.batchDeleteSessions(input.sessionIds),
|
||||
);
|
||||
ipcMain.handle(ipcChannels.regenerateSessionMessage, (_event, input: RegenerateSessionMessageInput) =>
|
||||
service.regenerateSessionMessage(input.sessionId, input.messageId),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user