Skip to content

feat(ui): tune a row's swipe: a trackpad scroll never swipes, lifted fingers let go at once, a flick carries, and the row follows with no render - #3653

Merged
luke-agent[bot] merged 3 commits into
mainfrom
luke/inbox-swipe-tuning
Oct 18, 2026
Merged

luke-agent[bot] merged 3 commits into
mainfrom
luke/inbox-swipe-tuning

Conversation

@dastratakos

@dastratakos dastratakos commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Dean tried the Inbox row swipe (#3641) on his Mac and asked to tune its sensitivity and the smoothness of its animation. The swipe is hand-built in packages/ui/src/components/list-row/use-row-swipe.ts, with no gesture library. This PR tunes that hook and its CSS.

Sensitivity

  • A scroll never swipes a row. A trackpad movement begins a swipe only once it has gone 5px sideways and leans twice as far sideways as up or down. Before, any single wheel event that was more sideways than up or down started one. A movement that begins up or down stays the list's to scroll until its wheel rests, however sideways it turns.
  • Lifting your fingers lets go at once. After the fingers lift, macOS keeps sending wheel events that shrink steadily; these are the coast. The row used to keep following the coast for its whole run, then wait another 140ms, so it overshot into acting and settled late. Now five shrinking deltas in a row count as the fingers lifting: the swipe is let go right then, and the rest of the coast moves nothing.
  • A flick carries. Where a release lands is pushed ahead along the hand's speed: 120ms ahead, by at most 64px. A quick flick right acts, a quick flick back closes, and a slow release lands where it is.
  • The far edge resists. Past 85% of the row, the row moves only a third of the hand's travel, so it never slides fully off.

Smoothness

  • The row follows the hand by writing its position straight onto the item's style, with no React render per pointer or wheel event. It renders only when the row arms or disarms.
  • A layout effect writes the position again after each render, so a render can't leave a stale value behind.
  • While moving, the act's label follows a beat behind (--duration-hover, 70ms), so its jump from the button's middle to the row's edge on arming glides instead of snapping. The armed color also fades.

Thresholds are unchanged: open past 24px, act past 60% of the row.

Evidence

  • Platform-independent checks: pnpm check:affected:
    • typecheck: passed.
    • @luke/ui tests: 79 files passed. @luke/web: 66 files passed.
    • @luke/desktop: 214 of 215 files passed. The one failure is src/main/app-menu.test.ts, which fails in this sandbox because Electron isn't installed; this PR doesn't touch it.
    • discarded-effect ran out of memory in the parallel run. Run alone, pnpm discarded-effect reports no discarded Effect in 2,340 files.
    • pnpm lint and pnpm knip pass when run alone.
  • list-row.test.ts: 32 pass, 5 of them new. The fifth covers Devin's three findings: a trackpad swipe on an open row starts from its rest, the give is read off the hand's whole travel, and a flick carries once the wheel rests.
  • The earlier tests: They cover the trackpad's lean and scroll lock, lifted fingers letting go at once with the coast moving nothing, a flick right acting and a flick back closing while a slow release opens, and the give past 85%.
  • One existing trackpad test now lets its vertical scroll rest before the sideways swipe, because a movement that begins vertical now stays a scroll.
  • desktop-inbox.test.ts: 90 pass.
  • How it feels can only be judged on the Mac with a trackpad; not done yet.

Invariants

  • ./scripts/check.sh green. (CI)
  • JSON Schema goldens unchanged. (CI)
  • Every behavior change has, in this PR, the test that fails without it; no change-detector test added.
  • Gateway envelope goldens unchanged. (CI)
  • No new as type assertion outside the allowlisted files. (CI via anti-slop)
  • No Effect.runPromise/runSync/runFork outside the runtime edges. (CI)
  • No new setTimeout/setInterval/new Promise/AbortController/fs.watch. Timing uses requestAnimationFrame and the events' own timeStamp. (CI)
  • No Effect, Stream, or Layer written as a statement and dropped. (CI)
  • Test count: +5 (list-row.test.ts).
  • No hand-rolled file replaced.
  • No CLAUDE.md/AGENTS.md sentence names a changed part.
  • PRIVACY.md unchanged.
  • Package deps unchanged.
  • Barrel/door rule untouched.

Notes

  • Blockers or follow-up verification: Dean, please try it again on the trackpad. The numbers to adjust are all in SWIPE at the top of use-row-swipe.ts: LEAN, COAST, PROJECT_MS/PROJECT_MAX, GIVE_SHARE/GIVE, OPEN, and ACT_SHARE.

Made with Luke


Devin Review

View guided diff Turn on auto-fix

…fingers let go at once, a flick carries, and the row follows with no render

- A trackpad movement begins a swipe only once it leans sideways 5px and
  twice as far as up or down; one that begins up or down is the list's to
  scroll until its wheel rests.
- Five shrinking deltas in a row are the fingers lifted: the swipe is let
  go then rather than after the coast, which moves nothing.
- A release is carried along the hand's speed (120ms, at most 64px), so a
  flick right acts and one back closes.
- Past 85% of the row it gives, keeping a third of the hand's travel.
- The row follows the hand by writing its place onto the item, rendering
  only as it arms; the label rides a beat behind and the armed colour fades.

Co-authored-by: luke-agent[bot] <339870089+luke-agent[bot]@users.noreply.github.com>
@vercel

vercel Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
luke-web Ready Ready Preview Oct 18, 2026 6:49pm UTC

Request Review

devin-ai-integration[bot]

This comment was marked as resolved.

… the hand's whole travel, and carries its flick when the wheel rests

Co-authored-by: luke-agent[bot] <339870089+luke-agent[bot]@users.noreply.github.com>
devin-ai-integration[bot]

This comment was marked as resolved.

… row never acts on the scroll

Co-authored-by: luke-agent[bot] <339870089+luke-agent[bot]@users.noreply.github.com>
@luke-agent
luke-agent Bot enabled auto-merge October 18, 2026 18:46
@luke-agent
luke-agent Bot added this pull request to the merge queue Oct 18, 2026
Merged via the queue into main with commit dee2edf Oct 18, 2026
11 checks passed
@luke-agent
luke-agent Bot deleted the luke/inbox-swipe-tuning branch October 18, 2026 19:12

This branch was successfully deployed

1 active deployment
Preview — 9130ead6 Deployed Oct 18, 2026 by vercel[bot]
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