feat(tasks): support shift range selection #1834
feat(tasks): support shift range selection #1834janburzinski wants to merge 3 commits intogeneralaction:mainfrom
Conversation
Greptile SummaryThis PR adds shift+click range selection to the task list, allowing users to select a contiguous block of tasks by clicking one task and shift+clicking another. The implementation correctly wires up The main behavioural gap is in Confidence Score: 4/5Safe to merge; the feature works correctly for the primary use case with no data-loss risk, but shift+click UX deviates from standard multi-select behaviour. Only P2 findings — no crashes, data corruption, or security concerns. The core path (shift+click selects a range) functions as intended. The two P2s are UX/design issues around anchor management and additive range accumulation. src/renderer/features/projects/stores/project-view.ts — anchor advancement logic and missing
|
| Filename | Overview |
|---|---|
| src/renderer/features/projects/stores/project-view.ts | Adds lastSelectedId anchor and selectRange() method; anchor shifts on each shift+click and range is always additive — deviates from standard multi-select UX and anchor is not cleared on tab change. |
| src/renderer/features/projects/components/task-view/task-row.tsx | Uses onClickCapture + useRef to capture shiftKey before Radix Checkbox's onCheckedChange fires — approach is workable and correct for pointer interactions. |
| src/renderer/features/projects/components/task-view/task-list.tsx | Routes shift+click to selectRange and regular click to toggleSelect; passes filteredTasks correctly so virtualised list works end-to-end. |
Sequence Diagram
sequenceDiagram
participant User
participant TaskRow
participant TaskList
participant TaskViewStore
User->>TaskRow: click checkbox (no shift)
TaskRow->>TaskRow: onClickCapture captures shiftKey false
TaskRow->>TaskList: onToggleSelect(id, false)
TaskList->>TaskViewStore: toggleSelect(id)
TaskViewStore->>TaskViewStore: add or remove id, set lastSelectedId
User->>TaskRow: shift and click checkbox
TaskRow->>TaskRow: onClickCapture captures shiftKey true
TaskRow->>TaskList: onToggleSelect(id, true)
TaskList->>TaskViewStore: selectRange(filteredTaskIds, id)
alt anchor found in orderedIds
TaskViewStore->>TaskViewStore: add all ids in range anchor to toId
TaskViewStore->>TaskViewStore: lastSelectedId advances to toId
else anchor missing or equals toId
TaskViewStore->>TaskViewStore: toggleSelect(toId) as fallback
end
Comments Outside Diff (1)
-
src/renderer/features/projects/stores/project-view.ts, line 42-44 (link)lastSelectedIdnot cleared on tab switchsetTabdoes not resetlastSelectedId, so the anchor from a previous tab persists. In practice this is mostly harmless because task IDs are disjoint between active/archived lists (thefromIndex === -1guard falls back totoggleSelect). However, if a user selects a task, switches tabs, then shift+clicks, the stale anchor silently causes a fallback rather than a clean start. Addingthis.lastSelectedId = nullinsidesetTabwould make the behavior explicit and predictable.Prompt To Fix With AI
This is a comment left during a code review. Path: src/renderer/features/projects/stores/project-view.ts Line: 42-44 Comment: **`lastSelectedId` not cleared on tab switch** `setTab` does not reset `lastSelectedId`, so the anchor from a previous tab persists. In practice this is mostly harmless because task IDs are disjoint between active/archived lists (the `fromIndex === -1` guard falls back to `toggleSelect`). However, if a user selects a task, switches tabs, then shift+clicks, the stale anchor silently causes a fallback rather than a clean start. Adding `this.lastSelectedId = null` inside `setTab` would make the behavior explicit and predictable. How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 2
src/renderer/features/projects/stores/project-view.ts:76-80
**Anchor shifts on every shift+click — deviates from standard multi-select UX**
`selectRange` updates `lastSelectedId = toId` at the end, so the anchor moves with every shift+click. It also always *adds* to the existing selection rather than replacing it. This causes the range to grow but never shrink:
1. Click item 1 → anchor = 1, selected: {1}
2. Shift+click item 5 → anchor = **5**, selected: {1,2,3,4,5}
3. Shift+click item 3 → selects 3–5 (anchor → **3**), selected: {1,2,3,4,5} (4 and 5 can't be deselected this way)
Standard UX (Finder, VS Code explorer): the anchor stays at the last *non-shift* click, and shift+click replaces the previous range. To match expectations, `lastSelectedId` should not be updated in `selectRange`, and the loop should clear items that fall outside the new range.
### Issue 2 of 2
src/renderer/features/projects/stores/project-view.ts:42-44
**`lastSelectedId` not cleared on tab switch**
`setTab` does not reset `lastSelectedId`, so the anchor from a previous tab persists. In practice this is mostly harmless because task IDs are disjoint between active/archived lists (the `fromIndex === -1` guard falls back to `toggleSelect`). However, if a user selects a task, switches tabs, then shift+clicks, the stale anchor silently causes a fallback rather than a clean start. Adding `this.lastSelectedId = null` inside `setTab` would make the behavior explicit and predictable.
Reviews (1): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile
summary
i wanted to easily delete some test tasks and noticed we dont have a shift + click thing to select multiple things so maybe this would be cool :))
Screen.Recording.2026-04-30.at.07.44.42.mov