Compare commits
base: yonlu/yellowjacket:63-android-touch-model
yonlu/yellowjacket:main
yonlu/yellowjacket:fix/249-unbounded-growth
yonlu/yellowjacket:fix/248-artist-metadata-sweep
yonlu/yellowjacket:fix/247-cover-art-orphans
yonlu/yellowjacket:fix/246-incremental-scan-phantoms
yonlu/yellowjacket:fix/183-phantom-across-retire
yonlu/yellowjacket:fix/243-model-fallback
yonlu/yellowjacket:feat/31-play-all-shuffle-all
yonlu/yellowjacket:fix/240-loop-operational-fixes
yonlu/yellowjacket:docs/220-skill-check-scope
yonlu/yellowjacket:fix/231-setter-rollback
yonlu/yellowjacket:fix/197-duplicate-column-label
yonlu/yellowjacket:fix/175-wizard-follows-the-library
yonlu/yellowjacket:fix/146-stub-etxtbsy
yonlu/yellowjacket:docs/225-fixtures-wav-tags
yonlu/yellowjacket:feat/238-merge-leg-refresh-watch
yonlu/yellowjacket:feat/236-autonomous-backlog-loop
yonlu/yellowjacket:test/217-fixture-names-in-queue-selection
yonlu/yellowjacket:fix/216-riff-parse-allocation
yonlu/yellowjacket:fix/170-queue-header-action-names
yonlu/yellowjacket:fix/210-nav-sheet-scroll-affordance
yonlu/yellowjacket:docs/50-readme-landing-page
yonlu/yellowjacket:feat/65-art-prefetch-ahead
yonlu/yellowjacket:feat/71-more-as-a-bottom-sheet
yonlu/yellowjacket:feat/54-native-touch-feel
yonlu/yellowjacket:feat/67-entity-links-into-menus
yonlu/yellowjacket:test/196-visual-tier-gates
yonlu/yellowjacket:fix/138-ui-test-storage-leak
yonlu/yellowjacket:fix/104-wav-tags-read
yonlu/yellowjacket:fix/207-sheet-scroll-affordance
yonlu/yellowjacket:fix/204-ui-visual-update-filter
yonlu/yellowjacket:pi-agent-backlog-automation
yonlu/yellowjacket:63-touch-model-phase-2
yonlu/yellowjacket:63-android-touch-model
yonlu/yellowjacket:186-touch-targets-settings
yonlu/yellowjacket:186-touch-targets-page-header
yonlu/yellowjacket:187-seek-bar-hit-area
yonlu/yellowjacket:189-190-explore-correctness
yonlu/yellowjacket:135-android-underrun-instrumentation
yonlu/yellowjacket:51-android-small-screens
yonlu/yellowjacket:fix/171-phone-queue-scrim
yonlu/yellowjacket:fix/137-touch-only-affordances
yonlu/yellowjacket:fix/154-nested-css-check
yonlu/yellowjacket:feat/58-mini-player-progress-line
yonlu/yellowjacket:fix/66-album-page-scrolls-as-one
yonlu/yellowjacket:60-context-menu-action-sheet
yonlu/yellowjacket:64-android-system-volume
yonlu/yellowjacket:59-slim-the-mini-player
yonlu/yellowjacket:55-queue-as-a-screen
yonlu/yellowjacket:feat/57-drop-the-android-top-bar
yonlu/yellowjacket:feat/62-jobs-as-a-notification
yonlu/yellowjacket:fix/53-seek-bar-never-moves
yonlu/yellowjacket:fix/159-android-task-app-id
yonlu/yellowjacket:fix/52-android-activity-recreation-restarts-the-process
yonlu/yellowjacket:fix/150-expand-button-under-the-art
yonlu/yellowjacket:feat/42-inline-volume-and-centred-transport
yonlu/yellowjacket:fix/156-queue-selection-fixture-order
yonlu/yellowjacket:fix/151-fuse-the-scroll-guard-and-the-write
yonlu/yellowjacket:fix/43-queue-panel-selection
yonlu/yellowjacket:fix/143-top-bar-fits-its-window
yonlu/yellowjacket:feat/27-jobs-into-settings
yonlu/yellowjacket:feat/25-configurable-sidebar-tabs
yonlu/yellowjacket:feat/6-global-back-forward
yonlu/yellowjacket:fix/72-active-view-broadcast
yonlu/yellowjacket:fix/69-page-header-action-overflow
yonlu/yellowjacket:fix/quick-wins-batch
yonlu/yellowjacket:fix/118-in-library-clear
yonlu/yellowjacket:fix/61-mini-player-plain-text
yonlu/yellowjacket:fix/68-hover-affordances-pointer
yonlu/yellowjacket:fix/119-dev-headless-port
yonlu/yellowjacket:fix/130-issue-claim-user
yonlu/yellowjacket:fix/131-codegen-check-scope
yonlu/yellowjacket:feat/28-autotag-match-on-album
yonlu/yellowjacket:feat/17-demote-version-selector
yonlu/yellowjacket:feat/38-ownership-visibility
yonlu/yellowjacket:ci/115-manual-release
yonlu/yellowjacket:feat/34-icon-language
yonlu/yellowjacket:feat/7-full-tracklist-toggle
yonlu/yellowjacket:fix/16-tagwriter-totals
yonlu/yellowjacket:fix/unclaim-ca-certs
yonlu/yellowjacket:fix/unclaim-shell
yonlu/yellowjacket:ci/unclaim-on-close
yonlu/yellowjacket:docs/closing-keyword
yonlu/yellowjacket:docs/retire-stale-planning-docs
yonlu/yellowjacket:docs/issue-driven-workflow
yonlu/yellowjacket:integration/small-fixes
yonlu/yellowjacket:fix/small-issue-batch
yonlu/yellowjacket:fix/queue-toggle-state
yonlu/yellowjacket:fix/drag-count-badge
yonlu/yellowjacket:fix/album-card-year
yonlu/yellowjacket:fix/album-tracklist-heading
yonlu/yellowjacket:fix/seek-bar-clock-width
yonlu/yellowjacket:fix/explore-art-scanner-requests
yonlu/yellowjacket:chore/workflow-guardrails
yonlu/yellowjacket:v0.7.0
yonlu/yellowjacket:v0.6.0
yonlu/yellowjacket:v0.5.0
yonlu/yellowjacket:v0.4.0
yonlu/yellowjacket:v0.3.1
yonlu/yellowjacket:v0.3.0
yonlu/yellowjacket:v0.2.3
yonlu/yellowjacket:v0.2.2
yonlu/yellowjacket:v0.2.1
yonlu/yellowjacket:v0.2.0
yonlu/yellowjacket:v0.1.0
yonlu/yellowjacket:v0.0.1
yonlu/yellowjacket:v0.0.0
..
compare: yonlu/yellowjacket:6cf19b92e16a814cdfab7903576b00ecba3239d8
yonlu/yellowjacket:fix/249-unbounded-growth
yonlu/yellowjacket:fix/248-artist-metadata-sweep
yonlu/yellowjacket:fix/247-cover-art-orphans
yonlu/yellowjacket:fix/246-incremental-scan-phantoms
yonlu/yellowjacket:main
yonlu/yellowjacket:fix/183-phantom-across-retire
yonlu/yellowjacket:fix/243-model-fallback
yonlu/yellowjacket:feat/31-play-all-shuffle-all
yonlu/yellowjacket:fix/240-loop-operational-fixes
yonlu/yellowjacket:docs/220-skill-check-scope
yonlu/yellowjacket:fix/231-setter-rollback
yonlu/yellowjacket:fix/197-duplicate-column-label
yonlu/yellowjacket:fix/175-wizard-follows-the-library
yonlu/yellowjacket:fix/146-stub-etxtbsy
yonlu/yellowjacket:docs/225-fixtures-wav-tags
yonlu/yellowjacket:feat/238-merge-leg-refresh-watch
yonlu/yellowjacket:feat/236-autonomous-backlog-loop
yonlu/yellowjacket:test/217-fixture-names-in-queue-selection
yonlu/yellowjacket:fix/216-riff-parse-allocation
yonlu/yellowjacket:fix/170-queue-header-action-names
yonlu/yellowjacket:fix/210-nav-sheet-scroll-affordance
yonlu/yellowjacket:docs/50-readme-landing-page
yonlu/yellowjacket:feat/65-art-prefetch-ahead
yonlu/yellowjacket:feat/71-more-as-a-bottom-sheet
yonlu/yellowjacket:feat/54-native-touch-feel
yonlu/yellowjacket:feat/67-entity-links-into-menus
yonlu/yellowjacket:test/196-visual-tier-gates
yonlu/yellowjacket:fix/138-ui-test-storage-leak
yonlu/yellowjacket:fix/104-wav-tags-read
yonlu/yellowjacket:fix/207-sheet-scroll-affordance
yonlu/yellowjacket:fix/204-ui-visual-update-filter
yonlu/yellowjacket:pi-agent-backlog-automation
yonlu/yellowjacket:63-touch-model-phase-2
yonlu/yellowjacket:63-android-touch-model
yonlu/yellowjacket:186-touch-targets-settings
yonlu/yellowjacket:186-touch-targets-page-header
yonlu/yellowjacket:187-seek-bar-hit-area
yonlu/yellowjacket:189-190-explore-correctness
yonlu/yellowjacket:135-android-underrun-instrumentation
yonlu/yellowjacket:51-android-small-screens
yonlu/yellowjacket:fix/171-phone-queue-scrim
yonlu/yellowjacket:fix/137-touch-only-affordances
yonlu/yellowjacket:fix/154-nested-css-check
yonlu/yellowjacket:feat/58-mini-player-progress-line
yonlu/yellowjacket:fix/66-album-page-scrolls-as-one
yonlu/yellowjacket:60-context-menu-action-sheet
yonlu/yellowjacket:64-android-system-volume
yonlu/yellowjacket:59-slim-the-mini-player
yonlu/yellowjacket:55-queue-as-a-screen
yonlu/yellowjacket:feat/57-drop-the-android-top-bar
yonlu/yellowjacket:feat/62-jobs-as-a-notification
yonlu/yellowjacket:fix/53-seek-bar-never-moves
yonlu/yellowjacket:fix/159-android-task-app-id
yonlu/yellowjacket:fix/52-android-activity-recreation-restarts-the-process
yonlu/yellowjacket:fix/150-expand-button-under-the-art
yonlu/yellowjacket:feat/42-inline-volume-and-centred-transport
yonlu/yellowjacket:fix/156-queue-selection-fixture-order
yonlu/yellowjacket:fix/151-fuse-the-scroll-guard-and-the-write
yonlu/yellowjacket:fix/43-queue-panel-selection
yonlu/yellowjacket:fix/143-top-bar-fits-its-window
yonlu/yellowjacket:feat/27-jobs-into-settings
yonlu/yellowjacket:feat/25-configurable-sidebar-tabs
yonlu/yellowjacket:feat/6-global-back-forward
yonlu/yellowjacket:fix/72-active-view-broadcast
yonlu/yellowjacket:fix/69-page-header-action-overflow
yonlu/yellowjacket:fix/quick-wins-batch
yonlu/yellowjacket:fix/118-in-library-clear
yonlu/yellowjacket:fix/61-mini-player-plain-text
yonlu/yellowjacket:fix/68-hover-affordances-pointer
yonlu/yellowjacket:fix/119-dev-headless-port
yonlu/yellowjacket:fix/130-issue-claim-user
yonlu/yellowjacket:fix/131-codegen-check-scope
yonlu/yellowjacket:feat/28-autotag-match-on-album
yonlu/yellowjacket:feat/17-demote-version-selector
yonlu/yellowjacket:feat/38-ownership-visibility
yonlu/yellowjacket:ci/115-manual-release
yonlu/yellowjacket:feat/34-icon-language
yonlu/yellowjacket:feat/7-full-tracklist-toggle
yonlu/yellowjacket:fix/16-tagwriter-totals
yonlu/yellowjacket:fix/unclaim-ca-certs
yonlu/yellowjacket:fix/unclaim-shell
yonlu/yellowjacket:ci/unclaim-on-close
yonlu/yellowjacket:docs/closing-keyword
yonlu/yellowjacket:docs/retire-stale-planning-docs
yonlu/yellowjacket:docs/issue-driven-workflow
yonlu/yellowjacket:integration/small-fixes
yonlu/yellowjacket:fix/small-issue-batch
yonlu/yellowjacket:fix/queue-toggle-state
yonlu/yellowjacket:fix/drag-count-badge
yonlu/yellowjacket:fix/album-card-year
yonlu/yellowjacket:fix/album-tracklist-heading
yonlu/yellowjacket:fix/seek-bar-clock-width
yonlu/yellowjacket:fix/explore-art-scanner-requests
yonlu/yellowjacket:chore/workflow-guardrails
yonlu/yellowjacket:v0.7.0
yonlu/yellowjacket:v0.6.0
yonlu/yellowjacket:v0.5.0
yonlu/yellowjacket:v0.4.0
yonlu/yellowjacket:v0.3.1
yonlu/yellowjacket:v0.3.0
yonlu/yellowjacket:v0.2.3
yonlu/yellowjacket:v0.2.2
yonlu/yellowjacket:v0.2.1
yonlu/yellowjacket:v0.2.0
yonlu/yellowjacket:v0.1.0
yonlu/yellowjacket:v0.0.1
yonlu/yellowjacket:v0.0.0
1
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6cf19b92e1 |
feat(android): tap to play, hold to select
Phase 1 of #63, and the design the issue asks for as one piece is .planning/plans/active/019-android-touch-model.md. **A finger has no second button and no modifier keys**, so the primary action has to be the primary gesture: tap plays the row, and the hold that opened a context menu now enters selection mode with that row selected. Three decisions in it, and two diverge from the report. **The predicate is the pointer, not the platform or the viewport.** `pointerType === 'touch'`, per event, which is already how long-press.ts decided and is the only such test in the frontend. This is #64's rule -- named after the capability -- and it carries #64's warning: keyed on a width, an Android *tablet* at 600px gets click-selects/double-click-plays on a touchscreen, which is the inversion this issue exists to fix, on the platform it exists for. A touchscreen laptop cannot be described by a width at all. Per event, a mouse keeps desktop semantics on the very same row, and there is no second declaration of what a phone does. **There is no double-tap, and the number is why.** The report asks for single tap to play *and* double tap for the menu. Those cannot both be honoured: the first tap of a double tap is indistinguishable from a single tap until the interval expires, so "tap plays" becomes "tap waits". Measured on the device, the play command to TrackChanged is 155/123/85/56/91 ms -- median ~100 -- and the app's own DOUBLE_CLICK_GRACE_MS is 250. That is 3.5x the primary interaction, 250ms of it spent deliberately doing nothing, on every track anyone plays, to reach a menu the hold already reaches. So the menu and the selection action bar are the same surface, which is also the platform's convention and removes a concept rather than adding one. **Tap-to-play and selection mode ship together**, because splitting them is a regression dressed as an increment: a touch user selects by tapping today and acts through the long-press menu, so moving tap to play on its own would leave a window with no way to select forty tracks at all. **What lets this reassign the hold without touching one of the fourteen context menus**: the layer announces `yj-tap` / `yj-long-press` (composed, cancelable) and acts on nothing. A component claims one with preventDefault. An **unclaimed long press still becomes a `contextmenu`**, so the card grids, Explore, the playlist rows and every other menu behave exactly as they did, and only lists that opt in get selection mode. An unclaimed *tap* does nothing at all and the click follows normally, which is what leaves every button in the app alone -- only a claimed tap has its click swallowed, or playing a track would also select it. **And the device found the one thing no browser tier can see.** Chrome 113's Android WebView fires its own `contextmenu` on a long press. long-press.ts stood down when a trusted one arrived, which was right while both paths ended in a context menu; they no longer do, so standing down means the gesture silently does the *old* thing. Measured, before the fix, holding a track row: {"log":["contextmenu isTrusted=true"], "state":{"bar":null,"menuActive":true,"selected":1}} `yj-long-press` was never announced, the menu opened, and all 26 tests passed -- dispatched pointer events do not make a browser synthesise one. So the native event is a **trigger, not a competitor**: the gesture is announced from it and only a claim suppresses it. Unclaimed it propagates untouched, which is the same "browser wins" outcome reached by asking instead of assuming. The tier could not find that and can hold it, because this module has always told its own events apart by identity rather than isTrusted, so an untrusted one from a test takes exactly the browser's path. Verified on the device by *performing* the gestures rather than describing the page -- `adb shell input tap` and `input swipe x y x y 700` reach the WebView as real pointer events, which is new here and is written down in the plan with the pixel mapping. Tap plays; a hold raises the bar with one selected and no menu; a tap toggles to two, back to one, and the mode ends with the last row; an album card still opens its context menu. 29 new tests. The e2e spec is rewritten to assert **both** halves -- the row selects, and a card elsewhere still opens the real menu -- because a spec that only checked the row would pass on a build that had silently broken the other thirteen. Phases 2-4 (swipe to queue, the other three surfaces, and what #67 inherits) are in the plan and not in this commit. |