Android: every control that is not the transport is below the 44px touch floor #186
Closed
opened 2026-08-21 19:39:55 +00:00 by logan
·
5 comments
No Branch/Tag Specified
main
fix/146-stub-etxtbsy
fix/175-wizard-follows-the-library
fix/231-setter-rollback
fix/197-duplicate-column-label
docs/225-fixtures-wav-tags
docs/220-skill-check-scope
test/217-fixture-names-in-queue-selection
fix/216-riff-parse-allocation
fix/170-queue-header-action-names
fix/210-nav-sheet-scroll-affordance
docs/50-readme-landing-page
feat/65-art-prefetch-ahead
feat/71-more-as-a-bottom-sheet
feat/54-native-touch-feel
feat/67-entity-links-into-menus
test/196-visual-tier-gates
fix/138-ui-test-storage-leak
fix/104-wav-tags-read
fix/207-sheet-scroll-affordance
fix/204-ui-visual-update-filter
pi-agent-backlog-automation
63-touch-model-phase-2
63-android-touch-model
186-touch-targets-settings
186-touch-targets-page-header
187-seek-bar-hit-area
189-190-explore-correctness
135-android-underrun-instrumentation
51-android-small-screens
fix/171-phone-queue-scrim
fix/137-touch-only-affordances
fix/154-nested-css-check
feat/58-mini-player-progress-line
fix/66-album-page-scrolls-as-one
60-context-menu-action-sheet
64-android-system-volume
59-slim-the-mini-player
55-queue-as-a-screen
feat/57-drop-the-android-top-bar
feat/62-jobs-as-a-notification
fix/53-seek-bar-never-moves
fix/159-android-task-app-id
fix/52-android-activity-recreation-restarts-the-process
fix/150-expand-button-under-the-art
feat/42-inline-volume-and-centred-transport
fix/156-queue-selection-fixture-order
fix/151-fuse-the-scroll-guard-and-the-write
fix/43-queue-panel-selection
fix/143-top-bar-fits-its-window
feat/27-jobs-into-settings
feat/25-configurable-sidebar-tabs
feat/6-global-back-forward
fix/72-active-view-broadcast
fix/69-page-header-action-overflow
fix/quick-wins-batch
fix/118-in-library-clear
fix/61-mini-player-plain-text
fix/68-hover-affordances-pointer
fix/119-dev-headless-port
fix/130-issue-claim-user
fix/131-codegen-check-scope
feat/28-autotag-match-on-album
feat/17-demote-version-selector
feat/38-ownership-visibility
ci/115-manual-release
feat/34-icon-language
feat/7-full-tracklist-toggle
fix/16-tagwriter-totals
fix/unclaim-ca-certs
fix/unclaim-shell
ci/unclaim-on-close
docs/closing-keyword
docs/retire-stale-planning-docs
docs/issue-driven-workflow
integration/small-fixes
fix/small-issue-batch
fix/queue-toggle-state
fix/drag-count-badge
fix/album-card-year
fix/album-tracklist-heading
fix/seek-bar-clock-width
fix/explore-art-scanner-requests
chore/workflow-guardrails
v0.7.0
v0.6.0
v0.5.0
v0.4.0
v0.3.1
v0.3.0
v0.2.3
v0.2.2
v0.2.1
v0.2.0
v0.1.0
v0.0.1
v0.0.0
Labels
Clear labels
Area/Design
Area/Downloads
Area/Explore
Area/Library-UI
Area/Metadata
Area/Packaging
Area/Player
Area/Queue
Area/Settings
Area/Shell-Nav
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Platform/Android
Platform/Desktop
Breaking change that won't be backward compatible
Something is not working
Documentation changes
Improve existing functionality
New functionality
This is security issue
Issue or pull request related to testing
Priority
Critical
1
The priority is critical
Priority
High
2
The priority is high
Priority
Medium
3
The priority is medium
Priority
Low
4
The priority is low
Reviewed
Confirmed
1
Issue has been confirmed
Reviewed
Duplicate
2
This issue or pull request already exists
Reviewed
Invalid
3
Invalid issue
Reviewed
Won't Fix
3
This issue won't be fixed
Status
Blocked
1
Something is blocking this issue or pull request
Status
Need More Info
2
Feedback is required to reproduce issue or to continue work
Status
Abandoned
3
Somebody has started to work on this but abandoned work
Status
In Progress
Somebody is actively working on this right now
Milestone
No items
No Milestone
Projects
Clear projects
No projects
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: yonlu/yellowjacket#186
Reference in New Issue
Block a user
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.
Report
Every control in the app that is not the transport is below the 44px
touch floor on a phone. #56 sized the playback controls for a thumb and
nothing else was resized, so the controls a user meets on every
screen — the sort control, the sort direction, the overflow menu, the
page actions — are between a third and two thirds of the floor the same
issue established.
Measured on the reference device (TLP301, Android 14, WebView Chrome
113, 424x439 CSS px) with a real 1,577-track library, by walking every
primary view and reading
getBoundingClientRect()on everythingmatching a control selector.
page-sort-directionpage-sortselectpage-actions-morepage-action-shuffle-suggestionssearch-triggerconfig-fieldselectlibrary-filterselectFindings
018's promise is that no action is unreachable at any supported size,
and it holds: at 424x439 the shell does not overflow on any view
(
documentElement.scrollWidthis 424 against a 424 viewporteverywhere), nothing is stranded outside a scrollable ancestor, and a
hit test at each control's centre reaches the control. They are all
hittable — they are just small enough that hitting them is the
user's problem rather than the app's.
44px, #55 sized the queue header's buttons to it, and the queue panel
measures 44x44 throughout — so this is one rule applied unevenly
rather than a new rule.
search-triggeris the sharpest case, because it exists onlyon a phone: #57 created it as the phone's replacement for the header
search box, and it shipped at 40x40. A control introduced for touch
is the one that should not have to be argued about.
visible box at all — it reads as an icon rather than a button, which
is a second problem on top of the size.
a form, and
config-field's control is the shape every row uses, soone rule there covers 93 controls.
Direction
One pass over
page-headercovers five views at once and is theobvious place to start — the sort control, its direction button, the
overflow trigger and the action buttons all live there, and the
component already measures itself for the overflow rule (#69), so it is
a stylesheet change rather than a layout one. Then
config-fieldforSettings, and
explore-view's own search row.The floor is 44px and should be reached by growing the hit area
rather than the visual weight where the two can differ — a phone header
that becomes three rows of chunky buttons trades this problem for #69's.
Padding on the control, not size on the icon.
Two things to decide rather than assume. Whether this is phone-only:
page-headerhas no phone branch today and CLAUDE.md's stated reasonis that a second declaration of what a phone shows is a second thing to
keep in step — but a 44px sort control on a desktop is merely large,
not wrong, so "grow it everywhere" may be the cheaper answer. And the
seek bar is filed separately (its drag target is 6px) because the fix
there is a hit area that is deliberately larger than the painted
control, which is a different move from padding a button.
The sweep was extended to the detail views, Downloads and Autotag,
which the first table did not cover. Same device, same method. Six more
controls, and two of them are smaller than anything in the original
list.
folders-menu-triggersection-toggleback-buttonpage-action-check-nowfav-btn(mini player)artist-details' back button is the one to look at first, becauseit is not merely small — it is the way out of a detail view, and #55
already settled that case one component over: "the way out is 44px on a
phone", when the queue's close button was 25x21 and the scrim had no
uncovered pixels to tap. An artist page reached from a card has the
platform's back gesture as well, so this is less severe than the queue
was, but it is the same control wearing the same mistake.
Autotag's 15px section toggle is the smallest control measured
anywhere in the app — smaller than the 18x14 favourite #56 called out
as the worst case. Autotag is off by default (#25), which is presumably
why nobody has met it.
Nothing new for the null result: at 424x439 the shell does not overflow
and no control is stranded on any view — the ten primary ones, the
queue,
album-details,artist-details, Downloads or Autotag.Taking this, scoped to the Direction's first step:
page-header(thesort control, its direction button, the overflow trigger and the action
buttons) plus
search-trigger.config-field's 93 Settings controlsand
explore-view's search row are the obvious second pass and aredeliberately not in this one -- Settings is a form with one shape for
every row and wants its own argument.
Both open questions answered by measurement rather than by taste, and
the same answer settles both. Measured on the device at 424x439: the
header is 63px tall while its controls are 20-23px, so the vertical
room is already there; the select and its direction arrow are 6px
apart, so the horizontal room is not.
So the target grows the way #187's did -- padding with the margins
cancelling it -- and that decides the rest:
to key on a breakpoint, so
page-headergrows no phone branch (thething CLAUDE.md declines to add) and no media query that no tier
renders, which is exactly the trap #187 just turned up. A 44px hit
area on a desktop is invisible.
re-tuning: it measures the rendered boxes, and the boxes are the
same.
header-action-overflow.spec.ts's expectations should standas written, which is the check rather than the assumption.
The direction arrow is the one that needs horizontal growth (28x21),
and it will be asymmetric for #187's reason: the select is 6px to its
left and there is free space to its right, so the target takes the side
that has nothing to steal from.
First pass landed (PR #195, merged as
52038dc):page-header'ssort control, its direction arrow, the action buttons and the overflow
trigger, plus
search-trigger. Measured on the device afterwards, everycontrol in the header is now at least 44x44:
page-sortpage-sort-directionpage-actions-moresearch-triggerBoth questions this issue left open are answered, and the answer to
the first is not the one I reached first.
Phone-only or everywhere? Everywhere. A 44px control on a desktop is
merely large, and a second declaration of what a phone shows is a
second thing to keep in step -- which is why this component has never
had one. It also avoids a media query no tier renders, which is exactly
how the seek bar's phone rule came to be dead for months (#187).
Does #69's fit move? It did, and that is the finding worth
recording. Growing the two square controls to 44px as boxes added
22px to the header. It fit at every width Chromium was checked at and
clipped the overflow trigger at 320x600 in WebKit -- caught only by
CI, because Playwright's Linux WebKit cannot run on this machine. I had
stated it as a property on the strength of one engine.
The repair is what this issue's own Direction asked for and I had
deviated from: "reached by growing the hit area rather than the visual
weight ... padding on the control, not size on the icon". #69's pass
measures inline size, so a taller control is free and a wider one is
not. Height is a box; width is padding with a negative margin handing
the space back. The three controls now occupy 29, 38 and 40 px -- what
they occupied before any of this -- so the fit sees an unchanged header.
The general rule, for the second pass and for anything else here:
in a container that measures itself, only the width of a touch target
is contested. Where the two must differ,
getBoundingClientRect()still measures the target directly, because it includes padding while
the negative margin removes the space from layout -- so this stays
checkable in the component tier rather than needing a stylesheet
assertion the way #187 did.
What remains, and why it is not in that PR.
config-field, the 93 Settings controls. Close to a one-rulechange and easier than the header, since a form has no overflow fit
and the width is therefore uncontested -- only height, which is free.
What it needs instead is a density argument: every row on a long page
gets taller on a 439px screen, and it should go through
make ui-visualrather than be asserted only by numbers.explore-view's own search row.folders-menu-trigger(32x18),section-toggle(187x15),back-button(32x32),page-action-check-now(113x29) and theDownloads tabs (85x34, 96x34).
One thing not in either table, so the next pass does not spend time
on it: the skip link is 147x35. It is keyboard-only and unreachable
by touch, so it is not a touch-target defect.
Unclaiming: the header pass is done and the rest is a separate piece of
work that wants the visual suite.
Taking the second pass, scoped to Settings.
explore-view's searchrow and the second table's five one-off controls (
folders-menu-trigger,section-toggle,back-button,page-action-check-now, the Downloadstabs) stay for a third pass unless this one lands small enough to carry
them.
Branch:
186-touch-targets-settings.The sweep found 120 controls below the floor on Settings, not 93, and
config-fieldis 8 of them. Re-measured on the device just now(TLP301, 424x439, main at
52038dc, index build stopped and jobsempty), walking every shadow root with all eleven
config-sectionsexpanded:
column-arrow-btncolumn-toggle(checkbox)shortcut-capturebuttonconfig-fieldselectwa-inputcontrolwa-buttonconfig-fieldcolour swatchlibrary-filterselectoverflow-btnbtn-ghostSo the 16x14 column arrows are the smallest controls in the app --
smaller than autotag's 15px section toggle, and there are 36 of them.
The original table's
config-fieldrow was the visible offender on acollapsed page; the density lives behind the disclosures.
The density argument, measured rather than guessed, and it is
smaller than I expected. The rows are already close to the floor:
.column-itemis 335x36 and.shortcut-rowis 335x37 — it is thecontrols inside them that are 14-25px, not the rows. So raising a row
to 44 costs 8px, and the two column lists (10 and 19 items) grow by 232px
in total. That is about half a screen of extra scroll on a 439px
viewport, on a page that already scrolls, which is a real cost and not
one worth trading a 16x14 target for.
That reframes the approach and makes it cheaper than the header's
was. Where a row is already ~36px and its control is 16px, the fix is
to grow the control into the row it already occupies rather than to
grow the row. Three shapes:
.column-arrow-btnhasbackground: noneand a transparent border, so padding isinvisible until hover — the target grows and the visual weight does
not, which is this issue's own Direction.
native checkbox cannot grow its hit area without growing its paint,
and a 44px checkbox is not what anyone wants. Wrapping the checkbox
and its text in a
<label>makes the row's text the target, whichis ~250x44 — and
config-field's own comment already argues forexactly this ("a real label association also makes the label text a
click target for the control, which is behaviour, not annotation").
on this page, so the header pass's "only width is contested" rule
does not bind here at all.
The
wa-inputandwa-buttonrows are Web Awesome's own shadow roots,so they are the one part that may need
wa-slider-label.css.ts'streatment — the library's API first, and only reach into the shadow
root if it has none.
Second pass landed in PR #198, and it closes this out: Settings,
then the rest of the second table.
Settings was 120 controls, not 93. Swept on the device with all
eleven
config-sections expanded — the first table's seven is what acollapsed page reports, and the density is behind the disclosures.
config-field, which the Direction names, is eight of them..column-arrow-btn.column-toggleshortcut-capturebuttonconfig-fieldselectwa-input/wa-buttonbase238x44library-filterselect.overflow-btnconfig-fieldtoggleThe density argument, since the Direction asked for one. The rows
were already near the floor —
.column-item335x36,.shortcut-row335x37 — and it is the controls inside them that were 14–25px. So
each control grows into the row it already occupies and the row goes
36 → 44: the two column lists went 373→447 and 690→850, +234px
over the page. Half a screen of extra scroll on a page that already
scrolls, against 36 targets of 16x14. Growing each control to its own
44px row would have cost four screens.
Three things worth carrying forward.
A native checkbox is targeted through its label. It cannot grow
its hit area without growing its paint, and a 44px checkbox is not
what anyone wants.
.column-labelis a real<label for>now, so thecolumn's name is the target — which is also a behaviour improvement,
and the argument
config-fieldalready makes for its own labels.Two controls no sweep can see, and both are absent from this
issue's tables for that reason.
config-field's toggle has an<input>that isopacity: 0; width: 0; height: 0, so a walk ofevery
inputskips it as a zero-sized node — what a finger hits isthe
<label>, at 34x19, smaller than anything in either table.And
shortcut-capture's reset button renders only for a shortcutsomebody has already rebound, so a sweep of a fresh install never
meets it.
back-buttonwas six controls, not one. This issue names it inartist-details, because that is the view the sweep opened; the samedeclaration is byte-identical in six components. It is one stylesheet
now and a source sweep fails on a seventh copy — the failure to guard
against is not a size changing but somebody adding a detail view and
writing
.back-buttonout again, which no device sweep would catch,for exactly the reason this one did not.
Both open questions from the first pass held. Grow it everywhere
rather than phone-only, and no media query no tier renders. And the
"only width is contested" rule turned out not to bind at all here:
Settings runs no fit pass, so nothing needed the negative-margin
treatment except the toggle, and there for alignment rather than for
a fit.
One new instance of the same trap, recorded because it is the third
time. The Downloads tabs were given their height as padding, so the
underline would stay against the label — 13 + 13 + 2 + the line box.
That is 44px on Arch and 43px in ubuntu:24.04, whose font gives
13px text a line box one pixel shorter. A height computed from a
font's line box is not a height you control. It has a
min-block-sizeunder the padding now. Caught by the new test in CI rather than by a
person, which is the half of it that worked.
Verified: device sweep 120 → 42 on Settings, every one of the 42
accounted for (37 checkboxes whose labels are 70x44 and 57x44, four
wa-inputinner inputs at 204x42 — the control measured inside itsown 1px border,
part=basebeing 238x44 — and the skip link, alreadyruled out as keyboard-only). Explore, Downloads, Autotag and
artist-details each report one, and it is the skip link.
26 new tests, all failing on
mainwith the device's own numbers.CI green including WebKit, which is the engine #195 needed and
could not run locally.