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:
David Kaya
2026-04-15 07:12:45 +02:00
co-authored by Copilot
parent 0e9d77c745
commit 575aca360a
13 changed files with 1110 additions and 23 deletions
+111
View File
@@ -674,6 +674,108 @@ body {
animation: intent-divider-in 0.2s cubic-bezier(0.16, 1, 0.3, 1) both;
}
/* ── Batch selection animations ──────────────────────────────── */
@keyframes selection-checkbox-in {
from {
opacity: 0;
transform: scale(0);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes selection-checkbox-out {
from {
opacity: 1;
transform: scale(1);
}
to {
opacity: 0;
transform: scale(0);
}
}
.selection-checkbox-enter {
animation: selection-checkbox-in 0.2s cubic-bezier(0.16, 1, 0.3, 1) both;
}
.selection-checkbox-exit {
animation: selection-checkbox-out 0.15s cubic-bezier(0.16, 1, 0.3, 1) both;
}
@keyframes checkbox-check {
0% {
transform: scale(1);
}
40% {
transform: scale(1.2);
}
100% {
transform: scale(1);
}
}
.checkbox-check {
animation: checkbox-check 0.2s cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes batch-action-bar-in {
from {
opacity: 0;
transform: translateY(100%);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.batch-action-bar-enter {
animation: batch-action-bar-in 0.25s cubic-bezier(0.16, 1, 0.3, 1) both;
}
@keyframes undo-toast-in {
from {
opacity: 0;
transform: translateY(8px) scale(0.96);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
@keyframes undo-toast-out {
from {
opacity: 1;
transform: translateY(0) scale(1);
}
to {
opacity: 0;
transform: translateY(8px) scale(0.96);
}
}
.undo-toast-enter {
animation: undo-toast-in 0.25s cubic-bezier(0.16, 1, 0.3, 1) both;
}
.undo-toast-exit {
animation: undo-toast-out 0.2s cubic-bezier(0.16, 1, 0.3, 1) both;
}
@keyframes toast-progress {
from {
transform: scaleX(1);
}
to {
transform: scaleX(0);
}
}
/* ── Respect reduced motion ──────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
@@ -697,6 +799,15 @@ body {
animation: none;
}
.selection-checkbox-enter,
.selection-checkbox-exit,
.checkbox-check,
.batch-action-bar-enter,
.undo-toast-enter,
.undo-toast-exit {
animation: none;
}
.qp-border-streaming {
animation: none;
border-color: var(--color-accent);