Skip to content

feat(web): add shift-click multi-select for sidebar threads#651

Merged
juliusmarminge merged 5 commits intopingdotgg:mainfrom
binbandit:feat/sidebar-thread-multi-select
Mar 10, 2026
Merged

feat(web): add shift-click multi-select for sidebar threads#651
juliusmarminge merged 5 commits intopingdotgg:mainfrom
binbandit:feat/sidebar-thread-multi-select

Conversation

@binbandit
Copy link
Contributor

@binbandit binbandit commented Mar 9, 2026

Summary

Adds multi-select support for sidebar thread items, enabling bulk actions on threads.

  • Cmd/Ctrl+Click toggles individual thread selection
  • Shift+Click range-selects from the last anchor to the clicked thread (within the same project)
  • Right-click on selection shows a bulk context menu with "Mark unread (N)" and "Delete (N)"
  • Escape or plain click clears the selection
  • Selected threads share the same highlight style as the active/navigated thread

Implementation

New: threadSelectionStore.ts

Ephemeral Zustand store (no persistence — selection clears on reload) managing:

  • selectedThreadIds: ReadonlySet<ThreadId> — the selected set
  • anchorThreadId — stable anchor for shift-click range computation
  • Actions: toggleThread, rangeSelectTo, clearSelection, removeFromSelection
  • No-op guards to avoid unnecessary re-renders

Refactored: Sidebar.tsx

  • Extracted deleteThread helper from handleThreadContextMenu for reuse by bulk delete. Accepts an optional deletedThreadIds set so bulk delete correctly finds fallback navigation targets.
  • Platform-aware modifier detection — uses isMacPlatform(navigator.platform) (same pattern as terminal-links.ts) instead of the naive metaKey || ctrlKey approach, so Ctrl+Click on Mac doesn't conflict with context menu.
  • Context menu routing — right-clicking a selected thread shows the bulk menu; right-clicking an unselected thread clears the selection and shows the normal single-thread menu.
  • Escape key integrated into the existing keydown effect.

New: threadSelectionStore.test.ts

22 unit tests covering:

  • Toggle add/remove/preserve behavior
  • Anchor management (set on add, preserve on deselect, clear on removal)
  • Range selection: forward, backward, anchor stability, cross-project fallback, same-thread range
  • clearSelection no-op guard (referential stability)
  • removeFromSelection with anchor cleanup and no-op when nothing matches

Video

Screen.Recording.2026-03-10.at.11.50.03.am.mov

Verification

  • bun lint — 0 errors (2 pre-existing warnings in server package, unrelated)
  • bun typecheck — 7/7 packages pass
  • bun run test — all 22 new tests pass; 27 pre-existing failures in composerDraftStore.test.ts and terminalStateStore.test.ts (localStorage unavailability in test env, confirmed identical before this change)

Note

Add shift-click and cmd/ctrl-click multi-select for sidebar threads

  • Introduces a new Zustand store (threadSelectionStore.ts) tracking selected thread IDs and an anchor thread for range selection.
  • Cmd/Ctrl+Click toggles individual thread selection; Shift+Click range-selects within the current project; plain click clears selection and navigates normally.
  • When multiple threads are selected, right-clicking opens a bulk context menu supporting mark-unread and delete for all selected threads.
  • Pressing Escape or clicking outside thread items clears the active selection; project header clicks also clear selection.
  • Selected threads are visually distinguished from the active thread in the sidebar render.

Macroscope summarized 970066c.

Add support for selecting multiple threads in the sidebar via
Cmd/Ctrl+Click (toggle) and Shift+Click (range select), with
bulk context menu actions for Delete and Mark Unread.

- New threadSelectionStore (Zustand) for ephemeral selection state
  with anchor-based range selection and no-op guards
- Platform-aware modifier detection (Cmd on Mac, Ctrl elsewhere)
  matching existing isMacPlatform pattern in terminal-links
- Extracted deleteThread helper from handleThreadContextMenu for
  reuse by bulk delete, with deletedThreadIds set for correct
  fallback navigation during batch operations
- Selected threads share the active thread highlight style
- Escape key clears selection; plain click clears and navigates
- Right-click on selected thread shows bulk menu; right-click on
  unselected thread clears selection and shows single-thread menu
- 22 unit tests covering toggle, range select, clear, remove,
  and edge cases (cross-project fallback, anchor stability)
@coderabbitai
Copy link

coderabbitai bot commented Mar 9, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 6889df85-68cc-4fb4-acc9-90960c4b2c10

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added the vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. label Mar 9, 2026
…lk delete

When bulk-deleting threads that share a worktree, the stale threads
closure caused getOrphanedWorktreePathForThread to see the other
threads-being-deleted as still alive, suppressing the orphaned
worktree cleanup prompt. Filter out deletedThreadIds (keeping the
current thread) so the check correctly identifies worktrees that
will have no surviving references.
@juliusmarminge
Copy link
Member

video pls

@juliusmarminge
Copy link
Member

shift click to me usually does range selection, but this one works the same as cmd clicking by just selecting the one you click?

also it uses the same style as the currently active thread so looks like i have more selected than it actually is:

CleanShot.2026-03-09.at.17.32.22.mp4

@binbandit
Copy link
Contributor Author

binbandit commented Mar 10, 2026

shift click to me usually does range selection, but this one works the same as cmd clicking by just selecting the one you click?

also it uses the same style as the currently active thread so looks like i have more selected than it actually is:

CleanShot.2026-03-09.at.17.32.22.mp4

I used the same color, so as to not introduce another design style. Can change. Got a color you would like me to use?
As for the range selection, yes that was the intention. It did work, but appears it no longer works. Let me debug and update ☺️

P.S. If you reach out to me on x: @0x1f99d -- It would be good if we can arrange a better form of communication (eg slack). So we can communicate easier on these

@binbandit
Copy link
Contributor Author

Here is the latest video.

Screen.Recording.2026-03-10.at.11.50.03.am.mov

Fixed the range select, also added a nice little blue highlight color.

@juliusmarminge

binbandit and others added 2 commits March 10, 2026 11:53
- avoid clearing selection on mousedown for thread items and marked safe controls
- centralize the selection-safe selector and add logic tests for clear vs preserve behavior
- simplify thread selection store typing by merging state/actions interface
@juliusmarminge juliusmarminge merged commit afb6e89 into pingdotgg:main Mar 10, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants