the same model on the queue and both playlist views; Escape leaves the mode
What the device said, which is most of why this took the shape it did
The plan predicted the class of failure for phase 2 — "works in
Chromium and not on the phone" — and named touch-action: pan-y as the
fix. It is half of it. Driving a real finger with adb shell input swipe across a track row:
touchmove kept firing in all three. Chrome 113's WebView cancels the pointer stream ~16px into any drag whatever touch-action says, so
a swipe recognised from pointermove dies 16px in. The other half is a
non-passive touchmove calling preventDefault(), with which the same
swipe ran to 12 moves and a pointerup at full travel.
Both are required, which was measured rather than assumed: with the preventDefault in place and touch-action back at auto, the gesture
died after one move. none is the value to avoid — it takes the
list's vertical scrolling with it. All of this is correct in Chromium
either way, which is why the module's header carries the measurement and
the component tier asserts the stylesheet.
draggable="true" turned out not to be a competitor: no dragstart
fires from a touch drag on this WebView at all.
And it found a phase 1 defect no tier can see
The native contextmenu arrives in either order and only one was
handled. Our 500ms timer firing first, a component claiming it, and
Chrome delivering its own menu 50–70ms later was suppressed by
nothing:
hold 1 yj-long-press, then contextmenu isTrusted=true menu over the bar
hold 2 yj-long-press clean
hold 3 yj-long-press, then contextmenu isTrusted=true menu over the bar
hold 4 yj-long-press clean
Two in four, on the one surface #63 exists to have changed. Six holds
clean after the fix. Neither browser tier can see it — neither
synthesises a contextmenu from a dispatched press — so the regression
test stands in for the browser with an untrusted one, which the module
already tells from its own by identity.
Decisions worth reading
A swipe is not a selection. It queues the row it was made on,
unless that row is one of several explicitly selected — the context
menu's rule, because a bar reading "40 selected" beside a gesture that
quietly queues one of them is two answers to one question. It never
changes the selection.
A tap on a queue row plays that position, not the list: setting
the queue to the queue reads as a no-op and discards its source, its
shuffle order and anything inserted by hand.
The queue panel has no swipe. A right swipe means add to the
queue everywhere it exists; the only thing it could mean on a queue
row is remove, which is the same gesture with the opposite effect
one screen away. Its rows do not carry data-swipe, and a test says
so.
The affordance is utils/swipe-to-queue.ts, once. Three lists
draw it; three copies of "how far is far enough" is three chances to
disagree.
Escape leaves the mode, back does not. The shell owns the history
stack and four lists reaching for history is four stacks — the fault navStack was deleted for. Filed as #200.
Verification
make lint, make test, make bindings-check, make skill-check, make css-check, and tsc --noEmit in both frontend/ and e2e/:
all clean. The Go half is unchanged, so lint and test are
formalities here.
make ui-test — 1092 pass (99 files), up from 1071. New: touch-swipe.test.ts (8), touch-surfaces.test.ts (8), plus 9 in touch-gestures.test.ts and 3 in touch-selection.test.ts.
make e2e — 239 pass, whole suite, chromium. Two new tests in touch-gestures.spec.ts; they skip on WebKit with the reason stated,
since desktop Safari has no TouchEvent constructor to build the
gesture from.
make ui-visual — untouched; its two failures are #196 and
pre-date this.
The device, a Light Phone III on Chrome 113: a track row queues on
a swipe and does not on a short one; a vertical drag still scrolls the
virtualizer 81px; the reveal tracks the finger exactly and flips its
words and colour at the threshold; a queue row taps to its own index
and refuses a swipe; a playlist row queues on a swipe and plays its
playlist on a tap; six holds raise the bar with no menu behind it.
Every new rule was checked by reverting it: the vertical-veto latch,
the commit threshold, the selection rule, the data-swipe opt-in on
each side, the click swallow and the Escape dismissal each fail a named
test when removed. Two turned out to be vacuous first and are
called out in the plan — one python replacement that silently did not
match, and a tap helper that dispatched no click, so there was nothing
for the swallow to swallow.
Finishes plan 019 (#63): phases 2, 3 and 4 of the Android touch model.
Phase 1 (tap to play, hold to select) landed in #199.
| commit | issue | what |
|---|---|---|
| `4e66775` | #63 (phase 2) | swipe a track row right to queue it |
| `29feb4b` | #63 (phases 3, 4) | the same model on the queue and both playlist views; Escape leaves the mode |
## What the device said, which is most of why this took the shape it did
The plan predicted the *class* of failure for phase 2 — "works in
Chromium and not on the phone" — and named `touch-action: pan-y` as the
fix. It is **half** of it. Driving a real finger with
`adb shell input swipe` across a track row:
```
touch-action: auto pointerdown, 1 move, pointercancel
touch-action: pan-y pointerdown, 2 moves, pointercancel
touch-action: none pointerdown, 2 moves, pointercancel
```
`touchmove` kept firing in all three. Chrome 113's WebView cancels the
**pointer** stream ~16px into any drag whatever `touch-action` says, so
a swipe recognised from `pointermove` dies 16px in. The other half is a
non-passive `touchmove` calling `preventDefault()`, with which the same
swipe ran to 12 moves and a `pointerup` at full travel.
**Both are required**, which was measured rather than assumed: with the
`preventDefault` in place and `touch-action` back at `auto`, the gesture
died after **one** move. `none` is the value to avoid — it takes the
list's vertical scrolling with it. All of this is correct in Chromium
either way, which is why the module's header carries the measurement and
the component tier asserts the stylesheet.
`draggable="true"` turned out **not** to be a competitor: no `dragstart`
fires from a touch drag on this WebView at all.
## And it found a phase 1 defect no tier can see
The native `contextmenu` arrives in either order and only one was
handled. Our 500ms timer firing first, a component claiming it, and
Chrome delivering its own menu 50–70ms *later* was suppressed by
nothing:
```
hold 1 yj-long-press, then contextmenu isTrusted=true menu over the bar
hold 2 yj-long-press clean
hold 3 yj-long-press, then contextmenu isTrusted=true menu over the bar
hold 4 yj-long-press clean
```
Two in four, on the one surface #63 exists to have changed. Six holds
clean after the fix. Neither browser tier can see it — neither
synthesises a `contextmenu` from a dispatched press — so the regression
test stands in for the browser with an untrusted one, which the module
already tells from its own by identity.
## Decisions worth reading
- **A swipe is not a selection.** It queues the row it was made on,
unless that row is one of several *explicitly* selected — the context
menu's rule, because a bar reading "40 selected" beside a gesture that
quietly queues one of them is two answers to one question. It never
changes the selection.
- **A tap on a queue row plays that position**, not the list: setting
the queue to the queue reads as a no-op and discards its source, its
shuffle order and anything inserted by hand.
- **The queue panel has no swipe.** A right swipe means *add to the
queue* everywhere it exists; the only thing it could mean on a queue
row is *remove*, which is the same gesture with the opposite effect
one screen away. Its rows do not carry `data-swipe`, and a test says
so.
- **The affordance is `utils/swipe-to-queue.ts`, once.** Three lists
draw it; three copies of "how far is far enough" is three chances to
disagree.
- **Escape leaves the mode, back does not.** The shell owns the history
stack and four lists reaching for `history` is four stacks — the fault
`navStack` was deleted for. Filed as **#200**.
## Verification
`make lint`, `make test`, `make bindings-check`, `make skill-check`,
`make css-check`, and `tsc --noEmit` in both `frontend/` and `e2e/`:
all clean. The Go half is unchanged, so `lint` and `test` are
formalities here.
- `make ui-test` — **1092 pass** (99 files), up from 1071. New:
`touch-swipe.test.ts` (8), `touch-surfaces.test.ts` (8), plus 9 in
`touch-gestures.test.ts` and 3 in `touch-selection.test.ts`.
- `make e2e` — **239 pass**, whole suite, chromium. Two new tests in
`touch-gestures.spec.ts`; they skip on WebKit with the reason stated,
since desktop Safari has no `TouchEvent` constructor to build the
gesture from.
- `make ui-visual` — untouched; its two failures are **#196** and
pre-date this.
- **The device**, a Light Phone III on Chrome 113: a track row queues on
a swipe and does not on a short one; a vertical drag still scrolls the
virtualizer 81px; the reveal tracks the finger exactly and flips its
words and colour at the threshold; a queue row taps to its own index
and refuses a swipe; a playlist row queues on a swipe and plays its
playlist on a tap; six holds raise the bar with no menu behind it.
Every new rule was checked by reverting it: the vertical-veto latch,
the commit threshold, the selection rule, the `data-swipe` opt-in on
each side, the click swallow and the Escape dismissal each fail a named
test when removed. Two turned out to be **vacuous** first and are
called out in the plan — one `python` replacement that silently did not
match, and a tap helper that dispatched no click, so there was nothing
for the swallow to swallow.
Closes #63
Plan 019 phase 2. A finger on a track row now drags a reveal out from
under it and queues the track on release, with the affordance saying
what it will do before it does it.
Two things the device said that the plan did not predict, and both
change the implementation rather than decorate it.
The gesture runs on touch events, not pointer events. Chrome 113's
WebView cancels the pointer stream ~16px into any drag whatever
touch-action says -- measured at auto, pan-y and none alike -- while
touchmove keeps firing. So touch-action: pan-y is half the fix and a
non-passive touchmove calling preventDefault is the other half, and
neither works alone: with the preventDefault in place and touch-action
back at auto the gesture died after one move. Both are correct in
Chromium either way, which is why the module's header carries the
measurement and the component tier asserts the stylesheet.
And a phase 1 defect the device found on the way past: the native
contextmenu arrives in either order and only one was handled. Our
500ms timer firing first, a component claiming it, and Chrome
delivering its own menu 50-70ms later was suppressed by nothing -- so
the context menu opened over the selection bar, two holds in four, on
the one surface this issue exists to have changed. Six holds clean
after.
draggable="true" is not a competitor: no dragstart fires from a touch
drag on this WebView at all.
Plan 019 phases 3 and 4, which finish #63. The queue panel and both
playlist detail views get tap-to-play and hold-to-select; the playlist
views get swipe-to-queue as well.
Phase 3 was not the pure wiring the plan expected, in two places.
A tap on a queue row plays that position. Copying track-list's tap --
which sets the queue to the list the row is in -- would rebuild the
queue from the queue, discarding its source, its shuffle order and
anything inserted by hand. It reads as a no-op and is not one.
And the queue panel has no swipe, deliberately. A right swipe means add
to the queue everywhere else it exists, and a queue row is already in
the queue; the only thing it could mean there is remove, which is the
same gesture with the opposite effect one screen away. Removing a queue
row is on the row, on its sheet since #60, and now on its selection
bar. The assertion is that its rows do not opt in.
The reveal became utils/swipe-to-queue.ts rather than being copied into
three lists, keyed on a data-swipe attribute so one stylesheet carries
the touch-action half of the device fix to rows that are called two
different things.
Phase 4 was already true and is now asserted: a claimed tap has its
click swallowed, so an explore-link inside a row never sees one and
tap-to-play wins with no rule of its own. Its test was vacuous when
written -- the tap helper sent no click, so there was nothing to
swallow -- which also weakened phase 1's. It sends one now.
Escape leaves selection mode, from selection-bar rather than from each
of the four hosts, since that element exists only while the mode does.
The platform's back gesture deliberately does not reach it: the shell
owns the history stack and four lists reaching for history is four
stacks. That is #200.
Verified on the reference phone: a queue row taps to its own index and
refuses a swipe, a playlist row queues on a swipe and plays its
playlist on a tap, and a hold raises the bar without the menu.
Closes#63
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Finishes plan 019 (#63): phases 2, 3 and 4 of the Android touch model.
Phase 1 (tap to play, hold to select) landed in #199.
4e6677529feb4bWhat the device said, which is most of why this took the shape it did
The plan predicted the class of failure for phase 2 — "works in
Chromium and not on the phone" — and named
touch-action: pan-yas thefix. It is half of it. Driving a real finger with
adb shell input swipeacross a track row:touchmovekept firing in all three. Chrome 113's WebView cancels thepointer stream ~16px into any drag whatever
touch-actionsays, soa swipe recognised from
pointermovedies 16px in. The other half is anon-passive
touchmovecallingpreventDefault(), with which the sameswipe ran to 12 moves and a
pointerupat full travel.Both are required, which was measured rather than assumed: with the
preventDefaultin place andtouch-actionback atauto, the gesturedied after one move.
noneis the value to avoid — it takes thelist's vertical scrolling with it. All of this is correct in Chromium
either way, which is why the module's header carries the measurement and
the component tier asserts the stylesheet.
draggable="true"turned out not to be a competitor: nodragstartfires from a touch drag on this WebView at all.
And it found a phase 1 defect no tier can see
The native
contextmenuarrives in either order and only one washandled. Our 500ms timer firing first, a component claiming it, and
Chrome delivering its own menu 50–70ms later was suppressed by
nothing:
Two in four, on the one surface #63 exists to have changed. Six holds
clean after the fix. Neither browser tier can see it — neither
synthesises a
contextmenufrom a dispatched press — so the regressiontest stands in for the browser with an untrusted one, which the module
already tells from its own by identity.
Decisions worth reading
unless that row is one of several explicitly selected — the context
menu's rule, because a bar reading "40 selected" beside a gesture that
quietly queues one of them is two answers to one question. It never
changes the selection.
the queue to the queue reads as a no-op and discards its source, its
shuffle order and anything inserted by hand.
queue everywhere it exists; the only thing it could mean on a queue
row is remove, which is the same gesture with the opposite effect
one screen away. Its rows do not carry
data-swipe, and a test saysso.
utils/swipe-to-queue.ts, once. Three listsdraw it; three copies of "how far is far enough" is three chances to
disagree.
stack and four lists reaching for
historyis four stacks — the faultnavStackwas deleted for. Filed as #200.Verification
make lint,make test,make bindings-check,make skill-check,make css-check, andtsc --noEmitin bothfrontend/ande2e/:all clean. The Go half is unchanged, so
lintandtestareformalities here.
make ui-test— 1092 pass (99 files), up from 1071. New:touch-swipe.test.ts(8),touch-surfaces.test.ts(8), plus 9 intouch-gestures.test.tsand 3 intouch-selection.test.ts.make e2e— 239 pass, whole suite, chromium. Two new tests intouch-gestures.spec.ts; they skip on WebKit with the reason stated,since desktop Safari has no
TouchEventconstructor to build thegesture from.
make ui-visual— untouched; its two failures are #196 andpre-date this.
a swipe and does not on a short one; a vertical drag still scrolls the
virtualizer 81px; the reveal tracks the finger exactly and flips its
words and colour at the threshold; a queue row taps to its own index
and refuses a swipe; a playlist row queues on a swipe and plays its
playlist on a tap; six holds raise the bar with no menu behind it.
Every new rule was checked by reverting it: the vertical-veto latch,
the commit threshold, the selection rule, the
data-swipeopt-in oneach side, the click swallow and the Escape dismissal each fail a named
test when removed. Two turned out to be vacuous first and are
called out in the plan — one
pythonreplacement that silently did notmatch, and a tap helper that dispatched no click, so there was nothing
for the swallow to swallow.
Closes #63