Files
SoulStream/handoff/rounds/ins-r37-doc-drag.txt
T
KeelrLeek 9f0601027c android: the handoff — the pending rounds, the pipeline, and what is unverified
Everything written this session that has not been through the device: rounds 43
to 48 (each patch adversarially verified, 43's transient-notice expectation
fixed), a partial round 49, the pipeline scripts, the Android Auto design and
the icon comparison mock. Also the two harness changes that were never A/B'd —
adaptive waits, which may only shorten a declared wait and never lengthen it,
and a ship that runs the mock walk and the deploy at the same time. The A/B and
its 255-step baseline are in handoff/tools; main stays at what the phone has
actually walked.
2026-09-14 07:57:41 -05:00

42 lines
2.9 KiB
Plaintext

- **Drag to reorder** (`src/mobile/reorder.js` — one Svelte action, on the
container, for the playlist page's rows and the player sheet's queue
alike). Press and hold a row for 320ms and it lifts (with a short tap in
the phone's motor), the rows below make room, and letting go lands it.
Release without having moved and the row's sheet opens exactly as a long
press always opened it — which matters most on the queue, where a row has
no ⋯ and `oncontextmenu` is the only door to Play now, Move to next and
Remove from queue. A press that moves more than 8px before the hold lands
is a scroll, and the gesture lets go of it.
Nothing carries `touch-action: none`: a flick that starts on a row still
scrolls the list. `touch-action` is latched at touchstart and cannot be
flipped mid-gesture, so the page's own scroll is refused instead by a
non-passive `touchmove` listener added at the lift — which works because
the finger has been still for 320ms and the browser has not started a
scroll yet. A click and a contextmenu guard sit on the container in the
CAPTURE phase while a lift is live (a released drag would otherwise fire
the row's own click and play the track, and Android's own 500ms long
press would open the sheet in the middle of the drag); both are off when
no lift is live, so a tap and a long press behave as they always have.
Every row is measured once at the lift, in the scroller's CONTENT space,
so the edge pull — within 64px of either edge, ramped, up to 14px a
frame — leaves the snapshot true and the row keeps up with the finger.
Each displaced row moves by the exact difference between its own top and
its neighbour's rather than by one assumed row height (the desktop's
`rowH`), because the queue face puts an album separator between rows.
The drop reuses the mutations the Move rows already use: `moveItem` and
`moveInQueue` each take a numeric target now as well as `"up"` /
`"down"` / `"top"`, so there is still exactly one call to
`playlist_set_order` with the whole id order. It is optimistic — the rows
go where you put them at once, and come back with the error toast if the
backend says no. Two refusals guard the middle of a gesture: the playlist
drops nothing if a refetch has replaced its rows (`pls.dragging` also
defers the page's own re-read while a drag is live, the phone's version
of the desktop's `skippedWhileDrag`), and the queue drops nothing if
`player.qi` has moved, because a track boundary shifts the whole rendered
window and the absolute indices would then address the wrong rows.
Dragging is off while the playlist's filter has anything in it — a
filtered list's visible order is not the stored order — and the note
under the field says so; the sheet's Move rows address rows by id and
stay correct under a filter, which is why they are the way to reorder
without clearing it.