Give the mail list the gestures a phone already has #141

Closed
opened 2026-08-31 13:52:49 +00:00 by jcoffey-dev · 0 comments
Owner

The mobile layout had the shape of a mail app and none of the handling. A row is clicked, right-clicked and dragged into a folder — on a touchscreen two of those three do not exist, so the gestures people reach for first did nothing at all.

Four gestures, touch-only

A mouse keeps drag-to-folder untouched; everything here is gated on (pointer: coarse).

  • Swipe a row sideways to act on it. Each direction is a setting (Settings → Appearance → Swiping), defaulting to right = archive, left = delete. The strip revealed behind the row names what will actually happen in the folder it is happening in — "Delete forever" out of Deleted Items, "Not spam" inside Junk Mail — and where an action is a no-op (archiving out of the archive, reporting your own drafts as spam) the row will not move that way at all.
  • Hold a row to select it. Selection was reachable already, by aiming at a checkbox beside an avatar, which is not how anyone selects mail on a phone. The selection toolbar gained an overflow menu at the same time: report spam, mark unread and label are hide-mobile with nowhere else to go, so touch selection could not reach them.
  • Hold a folder for the menu its ⋮ button opens.
  • Pull the list to refresh, and drag in from the left edge of a conversation to go back. The toolbar button and back arrow both stay — a gesture with no visible control is one only the people who already know about it can use.

Haptic confirmation on every threshold crossing, where the device has a motor.

Where the logic lives

web/src/lib/touch.ts holds the arithmetic, away from the components and under test, because the numbers are the whole thing. The axis lock is deliberately biased towards the vertical: a scroll misread as a swipe grabs the list out from under the reader, while a swipe misread as a scroll costs a second attempt. web/src/lib/swipe.ts resolves a setting plus a folder into the label, icon and tone the strip shows.

Two older layout bugs, found on a 390px screen

Both predate this work and are fixed here because they are what a phone actually looks like:

  • .app is a grid with only its rows named, so it took an implicit auto column sized to the top bar's min-content — about 470px. On a 390px phone every message row ran 84px off the right of the glass with its date beyond the edge. The column is now minmax(0, 1fr), and .searchbar gets min-width: 0 so it shrinks instead of setting the width for everything below it.
  • Full-screen surfaces measure in dvh rather than vh (a full-screen composer put Send behind the mobile address bar), and the tab bar, drawer, dialogs and compose button now keep clear of the notch and home indicator.

Checks

  • 407 tests pass, including new coverage of the gesture maths and the swipe descriptors. One of them caught a real defect while being written: the resistance curve past the threshold had no ceiling, so a hard flick accumulated travel with nowhere to show.
  • Typecheck and production build clean.
  • Driven by hand at 390×844 against the mock server, with (pointer: coarse) stubbed: swipe left and right in Inbox, Archive, Junk Mail and Deleted Items; a below-threshold swipe snapping back with no action; a diagonal drag staying a scroll; a direction set to "Nothing" refusing to move; long press opening selection without also opening the conversation; the folder menu; pull-to-refresh; and the edge-swipe back.

Merged 2026-08-31 as coffey-labs/ihasmail@c987ecccf3

Rebuilt from: git history, session transcript.

The mobile layout had the shape of a mail app and none of the handling. A row is clicked, right-clicked and dragged into a folder — on a touchscreen two of those three do not exist, so the gestures people reach for first did nothing at all. ## Four gestures, touch-only A mouse keeps drag-to-folder untouched; everything here is gated on `(pointer: coarse)`. - **Swipe a row sideways to act on it.** Each direction is a setting (Settings → Appearance → Swiping), defaulting to right = archive, left = delete. The strip revealed behind the row names what will actually happen *in the folder it is happening in* — "Delete forever" out of Deleted Items, "Not spam" inside Junk Mail — and where an action is a no-op (archiving out of the archive, reporting your own drafts as spam) the row will not move that way at all. - **Hold a row to select it.** Selection was reachable already, by aiming at a checkbox beside an avatar, which is not how anyone selects mail on a phone. The selection toolbar gained an overflow menu at the same time: report spam, mark unread and label are `hide-mobile` with nowhere else to go, so touch selection could not reach them. - **Hold a folder** for the menu its ⋮ button opens. - **Pull the list to refresh**, and **drag in from the left edge of a conversation** to go back. The toolbar button and back arrow both stay — a gesture with no visible control is one only the people who already know about it can use. Haptic confirmation on every threshold crossing, where the device has a motor. ## Where the logic lives `web/src/lib/touch.ts` holds the arithmetic, away from the components and under test, because the numbers are the whole thing. The axis lock is deliberately biased towards the vertical: a scroll misread as a swipe grabs the list out from under the reader, while a swipe misread as a scroll costs a second attempt. `web/src/lib/swipe.ts` resolves a setting plus a folder into the label, icon and tone the strip shows. ## Two older layout bugs, found on a 390px screen Both predate this work and are fixed here because they are what a phone actually looks like: - `.app` is a grid with only its rows named, so it took an implicit `auto` column sized to the top bar's min-content — about 470px. On a 390px phone every message row ran 84px off the right of the glass with its date beyond the edge. The column is now `minmax(0, 1fr)`, and `.searchbar` gets `min-width: 0` so it shrinks instead of setting the width for everything below it. - Full-screen surfaces measure in `dvh` rather than `vh` (a full-screen composer put Send behind the mobile address bar), and the tab bar, drawer, dialogs and compose button now keep clear of the notch and home indicator. ## Checks - 407 tests pass, including new coverage of the gesture maths and the swipe descriptors. One of them caught a real defect while being written: the resistance curve past the threshold had no ceiling, so a hard flick accumulated travel with nowhere to show. - Typecheck and production build clean. - Driven by hand at 390×844 against the mock server, with `(pointer: coarse)` stubbed: swipe left and right in Inbox, Archive, Junk Mail and Deleted Items; a below-threshold swipe snapping back with no action; a diagonal drag staying a scroll; a direction set to "Nothing" refusing to move; long press opening selection without also opening the conversation; the folder menu; pull-to-refresh; and the edge-swipe back. **Merged** 2026-08-31 as coffey-labs/ihasmail@c987ecccf3e7 <sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.