Skip to content

feat: implement tap & hold to select shelf items#415

Merged
masonfox merged 3 commits into
developfrom
feature/tap-hold-select
Apr 12, 2026
Merged

feat: implement tap & hold to select shelf items#415
masonfox merged 3 commits into
developfrom
feature/tap-hold-select

Conversation

@masonfox
Copy link
Copy Markdown
Owner

Summary

Implements tap & hold gesture to enter select mode on shelf items, eliminating the need to scroll to the top to press the "Select" button.

Key Features:

  • Long-press (500ms) on book card body enters select mode and auto-selects the item
  • Separate touch targets: drag handle (200ms) vs card body (500ms) to avoid conflicts
  • 10px movement tolerance to prevent activation during scrolling
  • Mobile-focused UX improvement

Resolves: #414


Changes

New Files

  • hooks/useLongPress.ts - Reusable long-press detection hook
    • Configurable delay (default 500ms)
    • Touch and mouse event support
    • Movement tolerance to distinguish from scrolling

Modified Files

  • hooks/useBookListView.ts - Added enterSelectModeWithSelection(bookId) function
  • components/Books/BookListItem.tsx - Integrated long-press handlers on card body
  • components/Books/DraggableBookList.tsx - Passes long-press handler to items
  • app/shelves/[id]/page.tsx - Wires up new functionality on mobile list views

Technical Details

Touch Target Separation

  • Drag Handle (left side): Long-press 200ms → Triggers drag-and-drop
  • Book Card Body: Long-press 500ms → Enters select mode + selects item

This eliminates timing conflicts between drag and select gestures.

Behavior

  1. User long-presses on book card (NOT in select mode)
  2. After 500ms: enters select mode + selects that book
  3. BulkActionBar appears at bottom
  4. User can tap other books to select/deselect
  5. Existing "Select" button still works

Safeguards

  • Long-press handlers only active when NOT already in select mode
  • 10px movement tolerance cancels long-press (allows smooth scrolling)
  • Drag handles unchanged (keep existing 200ms drag behavior)
  • Desktop tables unchanged (not needed for desktop UX)

Testing

All 4013 tests pass

Manual Testing Checklist

  • Long-press on book card body enters select mode + selects item
  • Long-press on drag handle still triggers drag-and-drop
  • Quick tap on book card still navigates to detail page
  • Scrolling doesn't accidentally trigger selection (10px tolerance)
  • In select mode, long-press is disabled (tap to select/deselect)
  • BulkActionBar appears/works correctly
  • Desktop tables unaffected

Mobile UX Benefits

Before: User must scroll to top → press "Select" button → scroll back down → select items

After: User long-presses on any item → immediately in select mode with that item selected → continue selecting

Much faster and more intuitive, especially on mobile devices.

- Add useLongPress hook for gesture detection (500ms delay)
- Extend useBookListView with enterSelectModeWithSelection function
- Update BookListItem to support long-press activation
- Wire DraggableBookList and shelves page to new functionality
- Separate touch targets: drag handle (200ms) vs card body (500ms)

Resolves #414

Co-authored-by: OpenCode <assistant@opencode.ai>
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 12, 2026

Codecov Report

❌ Patch coverage is 97.82609% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
hooks/useLongPress.ts 97.82% 0 Missing and 1 partial ⚠️

Impacted file tree graph

@@             Coverage Diff             @@
##           develop     #415      +/-   ##
===========================================
+ Coverage    78.67%   78.79%   +0.11%     
===========================================
  Files          167      168       +1     
  Lines         7559     7605      +46     
  Branches      1850     1860      +10     
===========================================
+ Hits          5947     5992      +45     
  Misses        1127     1127              
- Partials       485      486       +1     
Flag Coverage Δ
unittests 78.79% <97.82%> (+0.11%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
hooks/useLongPress.ts 97.82% <97.82%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

- Add useEffect cleanup to prevent memory leaks on unmount
- Add keyboard accessibility (Space key triggers selection)
- Add ARIA attributes for screen readers
- Add comprehensive test coverage for useLongPress hook (21 tests)

Addresses critical issues:
- Memory leak: timeout not cleared on component unmount
- Accessibility: no keyboard or screen reader support
- Testing: missing test coverage violating constitution

All 4034 tests passing.
@masonfox
Copy link
Copy Markdown
Owner Author

@copilot review this PR

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 12, 2026

@copilot review this PR

Reviewed the PR. The implementation is clean and well-structured. Two issues found and fixed in commit f623bb4:

Bug fix — missing onTouchCancel handler (hooks/useLongPress.ts)
When the browser cancels a touch gesture (e.g., OS notification, scroll handoff), touchcancel fires instead of touchend. The hook wasn't handling this, leaving orphaned setTimeout callbacks. Added onTouchCancel that calls cancel() and wired it into the returned handlers. Test coverage added.

Accessibility fix — Enter key not activating selection (components/Books/BookListItem.tsx)
The handleKeyDown only handled the Space key, but per the ARIA spec, elements with role="button" must respond to both Space and Enter. Updated the handler and the aria-label accordingly.

Everything else looks solid — movement tolerance, cleanup on unmount, prop drilling pattern, conditional handler spreading, and memo comparison all look correct. CodeQL scan and code review both came back clean.

@masonfox masonfox merged commit 3454454 into develop Apr 12, 2026
@masonfox masonfox deleted the feature/tap-hold-select branch April 12, 2026 14:41
masonfox added a commit that referenced this pull request Apr 16, 2026
PR #415 was marked as merged but commits never made it to develop branch.
Cherry-picked the three feature commits to restore functionality:

- Add enterSelectModeWithSelection to useBookListView
- Integrate useLongPress hook in BookListItem
- Wire up long-press handlers in DraggableBookList and shelves page
- Add keyboard accessibility (Space/Enter keys)
- Add comprehensive test coverage (22 tests)

Note: hooks/useLongPress.ts already existed on develop from commit 9f2ca50
and matches the final PR version, so it was kept unchanged.

Original commits:
- b88e3fc feat: implement tap & hold to select shelf items
- 990c8aa fix: implement @review feedback (iteration 1)
- f623bb4 fix: add onTouchCancel handler and Enter key accessibility support

Resolves #414
Fixes #415

Co-authored-by: OpenCode <assistant@opencode.ai>
masonfox added a commit that referenced this pull request Apr 16, 2026
## Summary

Fixes PR #415 which was marked as MERGED in GitHub but the commits never
actually made it into the develop branch. This PR cherry-picks the three
orphaned feature commits to restore the tap & hold to select
functionality.

**Problem:** PR #415 shows as merged, but the feature code is missing
from develop (except for `useLongPress.ts` which was added separately in
commit 9f2ca50).

**Solution:** Cherry-picked the three orphaned commits and squashed them
into a single commit with proper attribution.

---

## Changes

### Modified Files (5)
1. **`hooks/useBookListView.ts`** - Added
`enterSelectModeWithSelection()` function
2. **`components/Books/BookListItem.tsx`** - Integrated useLongPress
hook + keyboard accessibility
3. **`components/Books/DraggableBookList.tsx`** - Added long-press props
and wiring
4. **`app/shelves/[id]/page.tsx`** - Wired up long-press handlers to
list views

### New Files (1)
5. **`__tests__/hooks/useLongPress.test.ts`** - Comprehensive test suite
(22 tests)

**Note:** `hooks/useLongPress.ts` already existed on develop (added by
commit 9f2ca50) and matches the final PR version, so it was kept
unchanged.

---

## Feature: Tap & Hold to Select

Implements long-press gesture to enter select mode on shelf items,
eliminating the need to scroll to the top to press the "Select" button.

**Key Features:**
- Long-press (500ms) on book card body enters select mode and
auto-selects the item
- Separate touch targets: drag handle (200ms) vs card body (500ms) to
avoid conflicts
- 10px movement tolerance to prevent activation during scrolling
- Keyboard accessibility: Space and Enter keys trigger selection
- ARIA attributes for screen readers
- Mobile-focused UX improvement

**Behavior:**
1. User long-presses on book card (NOT in select mode)
2. After 500ms: enters select mode + selects that book
3. BulkActionBar appears at bottom
4. User can tap other books to select/deselect
5. Existing "Select" button still works

---

## Testing

✅ **All 4044 tests pass** (includes 22 new useLongPress tests)
✅ **Build succeeds** with no TypeScript errors

### Test Coverage
- Mouse events (down, up, move, leave)
- Touch events (start, end, cancel, move)
- Keyboard events (Space, Enter)
- Movement tolerance (cancels when exceeding 10px)
- Configurable delay and tolerance options
- Memory leak prevention (cleanup on unmount)
- ARIA attributes for accessibility

---

## Original Commits

This PR incorporates the following orphaned commits:
- `b88e3fc` - feat: implement tap & hold to select shelf items
- `990c8aa` - fix: implement @review feedback (iteration 1)
- `f623bb4` - fix: add onTouchCancel handler and Enter key accessibility
support

---

## References

- **Original PR:** #415
- **Issue:** #414
- **Problem Commit:** 9f2ca50 (added useLongPress.ts but not integration
code)

**Resolves:** #414  
**Fixes:** #415

Co-authored-by: OpenCode <assistant@opencode.ai>
@masonfox masonfox mentioned this pull request Apr 21, 2026
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.

2 participants