feat(desktop): multi-session selection and bulk delete in timeline#3909
feat(desktop): multi-session selection and bulk delete in timeline#3909ComputelessComputer merged 5 commits intomainfrom
Conversation
Adjust context menu visibility when items are selected and update selected item background color for better visual consistency.
✅ Deploy Preview for hyprnote-storybook canceled.
|
✅ Deploy Preview for hyprnote canceled.
|
| const capturedData = captureSessionData(store, indexes, sessionId); | ||
|
|
||
| invalidateResource("sessions", sessionId); | ||
| void deleteSessionCascade(store, indexes, sessionId); | ||
|
|
||
| if (capturedData) { | ||
| const performDelete = () => { | ||
| invalidateResource("sessions", sessionId); | ||
| void deleteSessionCascade(store, indexes, sessionId); | ||
| }; | ||
|
|
||
| setDeletedSession(capturedData, performDelete); | ||
| const timeoutId = setTimeout(() => { | ||
| useUndoDelete.getState().confirmDelete(); | ||
| }, 5000); | ||
| setTimeoutId(timeoutId); | ||
| addDeletion(capturedData); | ||
| } |
There was a problem hiding this comment.
🔴 Immediate deletion makes undo unable to restore audio files (data loss)
The refactored SessionItem.handleDelete now calls deleteSessionCascade immediately, which permanently deletes the audio file via fsSyncCommands.audioDelete. The old code deferred the actual deletion by passing a performDelete callback to the undo store, only executing it after the 5-second undo timeout expired. Now, even if the user undoes within the timeout, restoreSessionData only restores store rows (sessions, transcripts, etc.) but NOT the audio recording.
Root Cause and Impact
Previously in item.tsx, the deletion was deferred:
const performDelete = () => {
invalidateResource("sessions", sessionId);
void deleteSessionCascade(store, indexes, sessionId);
};
setDeletedSession(capturedData, performDelete);
// performDelete only called after 5s timeout via confirmDelete()Now the deletion is immediate:
invalidateResource("sessions", sessionId);
void deleteSessionCascade(store, indexes, sessionId); // audio deleted NOW
if (capturedData) {
addDeletion(capturedData); // only stores row data for undo
}deleteSessionCascade at apps/desktop/src/store/tinybase/store/deleteSession.ts:234 fires fsSyncCommands.audioDelete(sessionId) which permanently removes the audio file from the filesystem. restoreSessionData (deleteSession.ts:131-188) only restores TinyBase store rows and has no mechanism to recover the deleted audio.
Impact: When a user deletes a session from the timeline and then presses Ctrl+Z or clicks "Restore" within the 5-second undo window, the session's store data is restored but the audio recording is permanently lost. This is a silent data loss regression.
The same issue affects handleDeleteSelected in index.tsx:140-148 for batch deletion.
Prompt for agents
Restore the deferred deletion pattern for the timeline SessionItem delete and handleDeleteSelected. Instead of calling deleteSessionCascade immediately, pass a performDelete callback to addDeletion (via the onConfirm parameter) so the actual deletion (including audio file removal) only happens after the undo timeout expires. Specifically:
1. In item.tsx SessionItem handleDelete (around line 399-412), change to:
- Capture the data first
- Call invalidateResource to close the tab immediately
- Do NOT call deleteSessionCascade yet
- Call addDeletion(capturedData, performDelete) where performDelete calls deleteSessionCascade
- The store rows can be deleted immediately for UI responsiveness, but fsSyncCommands.audioDelete should be deferred
2. In index.tsx handleDeleteSelected (around line 131-151), apply the same pattern for each session in the loop.
3. Alternatively, split deleteSessionCascade into two parts: one that deletes store rows (immediate) and one that deletes the audio file (deferred via onConfirm callback).
The key invariant is: audio files must NOT be deleted until the undo timeout expires, so that restoreSessionData can fully restore the session including its audio.
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary
Adds multi-session selection to the timeline sidebar (shift-click range, ctrl/cmd-click toggle) with bulk delete via context menu, and refactors the undo-delete system from a single-session model to support multiple concurrent pending deletions.
Changes
Timeline Selection (
timeline-selection.ts,timeline/index.tsx,timeline/item.tsx)useTimelineSelectionZustand store with anchor-based range selection (shift-click) and individual toggle (ctrl/cmd-click)InteractiveButtongainsonShiftClickprop to support shift-click interactionsMulti-Session Undo-Delete (
undo-delete.ts,undo-delete-toast.tsx,dissolving-container.tsx)deletedSession: DeletedSessionData | nulltopendingDeletions: Record<string, PendingDeletion>pause(sessionId),resume(sessionId),clearDeletion(sessionId),confirmDeletion(sessionId)actionsaddedAttimestamp)Delete Flow (
delete.tsx)setDeletedSession+setTimeoutId+clearto singleaddDeletion(capturedData)callSession Tab Behavior (
sessions/index.tsx)running_batchmodeReview & Testing Checklist for Human
Suggested test plan: Open the app with several sessions in the timeline. Use shift-click and ctrl-click to select multiple items. Right-click and delete selected. Verify dissolving animations appear for each. Hover one to pause, let others expire. Press Cmd+Z to undo the paused one. Confirm all sessions restore correctly.
Notes
useUndoDeleteHandlerpicks the latest pending deletion byaddedAtfor Cmd+Z. This means undo always targets the most recently deleted session, not necessarily the one the user is looking at.timeline-selectionstore or the refactoredundo-deletestore.This is part 1 of 2 in a stack made with GitButler: