Skip to content

feat(desktop): multi-session selection and bulk delete in timeline#3909

Merged
ComputelessComputer merged 5 commits intomainfrom
c-branch-2
Feb 12, 2026
Merged

feat(desktop): multi-session selection and bulk delete in timeline#3909
ComputelessComputer merged 5 commits intomainfrom
c-branch-2

Conversation

@ComputelessComputer
Copy link
Collaborator

@ComputelessComputer ComputelessComputer commented Feb 12, 2026

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)

  • New useTimelineSelection Zustand store with anchor-based range selection (shift-click) and individual toggle (ctrl/cmd-click)
  • Visual selection state on timeline items (highlight + checkmark indicator)
  • Right-click context menu shows "Delete Selected (N)" when items are selected, falls back to existing "Show/Hide Ignored Events" otherwise
  • Keyboard shortcut (Backspace/Delete) to delete selected sessions
  • InteractiveButton gains onShiftClick prop to support shift-click interactions

Multi-Session Undo-Delete (undo-delete.ts, undo-delete-toast.tsx, dissolving-container.tsx)

  • Refactored from single deletedSession: DeletedSessionData | null to pendingDeletions: Record<string, PendingDeletion>
  • Each pending deletion has its own independent timeout, pause/resume state, and confirm callback
  • Per-session pause(sessionId), resume(sessionId), clearDeletion(sessionId), confirmDeletion(sessionId) actions
  • Cmd+Z undoes the most recently added deletion (by addedAt timestamp)
  • Dissolving container hover pause/resume now scoped to individual sessions

Delete Flow (delete.tsx)

  • Simplified from manual setDeletedSession + setTimeoutId + clear to single addDeletion(capturedData) call
  • Timeout management is now handled internally by the store

Session Tab Behavior (sessions/index.tsx)

  • Shows spinner when session is in running_batch mode
  • Auto-switches tab view to transcript during batch processing

Review & Testing Checklist for Human

  • Concurrent undo timers: Delete two sessions rapidly, hover one to pause its timer, verify the other continues counting down and auto-confirms independently — this is the highest-risk area given the refactor from single to multi-deletion state
  • Bulk delete + undo: Select 3+ sessions via shift-click, bulk delete from context menu, then Cmd+Z — verify each session's dissolving animation and undo work independently
  • Selection edge cases: Shift-click range selection across day boundaries, ctrl-click deselect last item (should clear anchor), right-click on non-selected area (should show default menu)
  • Batch mode spinner: Start a batch transcription, verify the tab shows a spinner and auto-switches to transcript view

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

  • The useUndoDeleteHandler picks the latest pending deletion by addedAt for Cmd+Z. This means undo always targets the most recently deleted session, not necessarily the one the user is looking at.
  • No unit tests added for the new timeline-selection store or the refactored undo-delete store.
  • Link to Devin run
  • Requested by @ComputelessComputer

This is part 1 of 2 in a stack made with GitButler:


Open with Devin

@netlify
Copy link

netlify bot commented Feb 12, 2026

Deploy Preview for hyprnote-storybook canceled.

Name Link
🔨 Latest commit e39ad25
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote-storybook/deploys/698d83722daf2a0008a2b272

@netlify
Copy link

netlify bot commented Feb 12, 2026

Deploy Preview for hyprnote canceled.

Name Link
🔨 Latest commit e39ad25
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote/deploys/698d83727d74a000088b170a

@devin-ai-integration devin-ai-integration bot changed the title c-branch-2 feat(desktop): multi-session selection and bulk delete in timeline Feb 12, 2026
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 7 additional findings in Devin Review.

Open in Devin Review

Comment on lines 404 to 411
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);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@ComputelessComputer ComputelessComputer merged commit 4463b94 into main Feb 12, 2026
18 checks passed
@ComputelessComputer ComputelessComputer deleted the c-branch-2 branch February 12, 2026 08:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant