Skip to content

feat(tasks): support shift range selection #1834

Open
janburzinski wants to merge 3 commits intogeneralaction:mainfrom
janburzinski:emdash/shift-to-select-more-task-view-rykli
Open

feat(tasks): support shift range selection #1834
janburzinski wants to merge 3 commits intogeneralaction:mainfrom
janburzinski:emdash/shift-to-select-more-task-view-rykli

Conversation

@janburzinski
Copy link
Copy Markdown
Collaborator

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

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 30, 2026

Greptile Summary

This 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 shiftKey detection through Radix UI's Checkbox via onClickCapture, and the store-level selectRange works safely with the virtualised list by operating on the full filteredTasks id array.

The main behavioural gap is in selectRange: the anchor (lastSelectedId) advances to toId on each shift+click and the loop always adds to the existing selection. This means shift+clicking to narrow a range doesn't deselect the previously added items, diverging from the standard UX (Finder, VS Code) where the anchor stays fixed and the range replaces the previous range selection.

Confidence Score: 4/5

Safe 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 lastSelectedId reset in setTab.

Important Files Changed

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
Loading

Comments Outside Diff (1)

  1. src/renderer/features/projects/stores/project-view.ts, line 42-44 (link)

    P2 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.

    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

Comment thread src/renderer/features/projects/stores/project-view.ts Outdated
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