Take the message you were reading into the selection #188

Closed
opened 2026-09-01 20:41:02 +00:00 by jcoffey-dev · 0 comments
Owner

Fixes #186.

What was wrong

Ctrl-clicking a second message selected only the second. The first stayed highlighted because it was the one open — a different state wearing a similar colour — and was never actually in the selection. So both looked picked, one was, and every action that followed applied to half of what the screen showed, without saying so.

Why

The cause is obvious once the two rules sit next to each other:

shift:  select(ids.slice(start, end + 1), true)   // the whole run, anchor included
ctrl:   select([rowId], !selected[rowId])         // only the row clicked

Two branches of one handler that had drifted apart, with nothing asserting they agreed. mbunkus spotted exactly this — that shift-click follows conventional list semantics and ctrl-click doesn't.

The fix

Both rules are now one tested function (lib/listSelection.ts) rather than two branches and a hope:

  • Ctrl-click brings the current row with it while nothing is selected yet. Once there is a selection it toggles exactly one row, which is the whole point of it.
  • Shift-click is unchanged, including keeping its anchor put so extending a range twice grows it from the same place rather than from wherever it last reached.
  • The mobile rule (a plain tap goes on selecting once a selection exists) moved across unchanged.

Guarded against the cases that would misbehave: an anchor that has scrolled out of the list, an anchor equal to the row clicked, and no anchor at all.

Verification

npm run typecheck, npm test (579 pass, 15 new) and npm run build green.

By hand against dev:mock, reproducing the report first:

  • plain-click a message → opens, nothing checked
  • ctrl-click another → 2 checked, both the one that was open and the one clicked
  • a further ctrl-click → 3; ctrl-click it again → back to 2
  • shift-click over a run of four → 4 checked, contiguous, anchor included

Screenshot of the shift case attached in the conversation.

Note on the last test

It asserts the property that actually failed rather than the branches: whichever modifier begins a selection, the anchor is in it. The individual branches were each defensible on their own — what was missing was anything checking they agreed.

Merged 2026-09-01 as coffey-labs/ihasmail@4912ea8ef4

Rebuilt from: git history, session transcript.

Fixes #186. ## What was wrong Ctrl-clicking a second message selected **only the second**. The first stayed highlighted because it was the one *open* — a different state wearing a similar colour — and was never actually in the selection. So both looked picked, one was, and every action that followed applied to half of what the screen showed, without saying so. ## Why The cause is obvious once the two rules sit next to each other: ``` shift: select(ids.slice(start, end + 1), true) // the whole run, anchor included ctrl: select([rowId], !selected[rowId]) // only the row clicked ``` Two branches of one handler that had drifted apart, with nothing asserting they agreed. mbunkus spotted exactly this — that shift-click follows conventional list semantics and ctrl-click doesn't. ## The fix Both rules are now one tested function (`lib/listSelection.ts`) rather than two branches and a hope: - **Ctrl-click brings the current row with it** while nothing is selected yet. Once there *is* a selection it toggles exactly one row, which is the whole point of it. - **Shift-click is unchanged**, including keeping its anchor put so extending a range twice grows it from the same place rather than from wherever it last reached. - The mobile rule (a plain tap goes on selecting once a selection exists) moved across unchanged. Guarded against the cases that would misbehave: an anchor that has scrolled out of the list, an anchor equal to the row clicked, and no anchor at all. ## Verification `npm run typecheck`, `npm test` (579 pass, **15 new**) and `npm run build` green. By hand against `dev:mock`, reproducing the report first: - plain-click a message → opens, nothing checked - ctrl-click another → **2 checked**, both the one that was open and the one clicked - a further ctrl-click → 3; ctrl-click it again → back to 2 - shift-click over a run of four → 4 checked, contiguous, anchor included Screenshot of the shift case attached in the conversation. ## Note on the last test It asserts the property that actually failed rather than the branches: *whichever modifier begins a selection, the anchor is in it*. The individual branches were each defensible on their own — what was missing was anything checking they agreed. **Merged** 2026-09-01 as coffey-labs/ihasmail@4912ea8ef4c1 <sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.