Android: the Playlists page header buttons are unreachable #69

Closed
opened 2026-08-18 06:00:07 +00:00 by logan · 2 comments
Collaborator

Report

I cannot scroll to reach all the buttons along the top of the Playlists page (import, etc.), and scrolling is probably not the right way to expose playlist controls anyway. These need to be reachable in a sensible way.

Findings

  • Desktop reports the same page: with the queue panel open in a small window, the Playlists header's management buttons get covered (see the small-window-sizes issue). So this is one header with two symptoms.
  • <page-header> is the shared title/count/sort/actions component for the primary views, so the fix lands in one place for all of them.

Direction

Give page-header an overflow rule: actions that do not fit collapse into a single "More actions" menu (a bottom sheet on phone — see the context-menu issue) rather than being clipped or requiring a horizontal scroll. Decide a priority order per host so the most useful action stays visible. Never let an action become unreachable at any supported size.

**Report** I cannot scroll to reach all the buttons along the top of the Playlists page (import, etc.), and scrolling is probably not the right way to expose playlist controls anyway. These need to be reachable in a sensible way. **Findings** - Desktop reports the same page: with the queue panel open in a small window, the Playlists header's management buttons get covered (see the small-window-sizes issue). So this is one header with two symptoms. - `<page-header>` is the shared title/count/sort/actions component for the primary views, so the fix lands in one place for all of them. **Direction** Give `page-header` an overflow rule: actions that do not fit collapse into a single "More actions" menu (a bottom sheet on phone — see the context-menu issue) rather than being clipped or requiring a horizontal scroll. Decide a priority order per host so the most useful action stays visible. Never let an action become unreachable at any supported size.
logan added the Area/Shell-NavKind/BugPlatform/Android
Priority
High
2
labels 2026-08-18 14:36:39 +00:00
Author
Collaborator

Measured while deciding #24, and it changes this issue's shape twice.

Only this page overflows. Sweeping all ten primary views at 900x600 and at
390x780, every other page-header reports scrollWidth == clientWidth; Albums
at 390px renders title, count and sort legibly (checked on a screenshot, not
just the number). The offender is this view's action set specifically — three
text buttons, Import (91px) + New Playlist (122px) + New Smart Playlist
(162px), 390px inside a header that gets 379px at 900x600.

And it clips at the supported minimum with the queue closed, which makes it
independent of #24: at 800x600 — the size window.go enforces — "New Smart
Playlist" loses 4px of its 162. The queue makes it dramatic (all three gone at
900x600), but this is not only a queue symptom.

The finding that decides scope: page-header cannot collapse its own
actions.
They arrive through <slot name="actions"> as arbitrary light-DOM
markup — this view slots a <div class="header-actions"> of three <button>s
carrying click handlers, drag handlers and a conditional class. A component
cannot move another component's light-DOM children into a dropdown and keep
their behaviour; there is nothing generic to render as a menu item.

So "give page-header an overflow rule" needs an actions API first — hosts
declaring {icon, label, handler, priority} that the header can render either
as buttons or as menu items — which is a change to all three hosts that slot
actions (playlist-view, home-view, downloads-view), not a rule added in
one place. That is why #24 does not absorb this: it is a different piece of
work, independently verifiable, and #24 removes the desktop half of the symptom
(the queue stops eating the header's width) without touching the cause.

What #24 owes this issue is the promise in its size matrix — no action is ever
unreachable at any supported size
— which is the sentence this issue asks for
and the one that makes it checkable. Doing this next, after #24.

Note the priority order per host in the Direction is now cheap to decide:
there is one host that needs it.

Measured while deciding #24, and it changes this issue's shape twice. **Only this page overflows.** Sweeping all ten primary views at 900x600 and at 390x780, every other `page-header` reports `scrollWidth == clientWidth`; Albums at 390px renders title, count and sort legibly (checked on a screenshot, not just the number). The offender is this view's action set specifically — three text buttons, `Import` (91px) + `New Playlist` (122px) + `New Smart Playlist` (162px), 390px inside a header that gets 379px at 900x600. **And it clips at the supported minimum with the queue closed**, which makes it independent of #24: at 800x600 — the size `window.go` enforces — "New Smart Playlist" loses 4px of its 162. The queue makes it dramatic (all three gone at 900x600), but this is not only a queue symptom. **The finding that decides scope: `page-header` cannot collapse its own actions.** They arrive through `<slot name="actions">` as arbitrary light-DOM markup — this view slots a `<div class="header-actions">` of three `<button>`s carrying click handlers, drag handlers and a conditional class. A component cannot move another component's light-DOM children into a dropdown and keep their behaviour; there is nothing generic to render as a menu item. So "give `page-header` an overflow rule" needs an **actions API** first — hosts declaring `{icon, label, handler, priority}` that the header can render either as buttons or as menu items — which is a change to all three hosts that slot actions (`playlist-view`, `home-view`, `downloads-view`), not a rule added in one place. That is why #24 does **not** absorb this: it is a different piece of work, independently verifiable, and #24 removes the desktop half of the symptom (the queue stops eating the header's width) without touching the cause. What #24 owes this issue is the promise in its size matrix — *no action is ever unreachable at any supported size* — which is the sentence this issue asks for and the one that makes it checkable. Doing this next, after #24. Note the `priority order per host` in the Direction is now cheap to decide: there is one host that needs it.
logan self-assigned this 2026-08-19 16:57:56 +00:00
logan added the
Status
In Progress
label 2026-08-19 16:57:58 +00:00
Author
Collaborator

Claiming this. Branch: fix/69-page-header-action-overflow.

Approach — the finding on this issue decides the shape, so this is
an actions API first and an overflow rule second.

  1. page-header gains an actions: PageAction[] property —
    {id, label, icon, handler, priority, disabled?, title?, acceptsDrop?}
    — and renders them itself. All three hosts that slot actions
    (playlist-view, home-view, downloads-view) migrate, which also
    normalises the plain-<button>/<wa-button> split between them.
    The <slot name="actions"> survives for markup a data list cannot
    express, documented as not collapsible: the header cannot measure or
    move another component's light-DOM children, which is this issue's own
    finding restated as the slot's cost.
  2. acceptsDrop is what keeps New Playlist a drop target. The header
    owns the dragover/dragleave/drop wiring and the affordance and
    re-dispatches action-drop; a menu item is not a drop target, so the
    affordance simply does not exist in the overflow, which is correct —
    you cannot drag onto a closed menu.
  3. The fit rule is measured, not breakpointed: a ResizeObserver on the
    header, and on each pass the buttons are un-hidden and the
    lowest-priority ones hidden one at a time until
    scrollWidth <= clientWidth. Starting each pass from all-visible is
    what stops it being sticky — the collapsed set is a pure function of
    the width. The sort control and the count are flex-shrink: 0 and are
    therefore inside the budget, which is why the header's own overflow is
    the right thing to measure rather than the actions row's.

On the Direction's "a bottom sheet on phone — see #60": using the
existing menu pattern now and not blocking on #60. #60 is Phase 4 and
its Direction is an <action-sheet> "selected by viewport width so every
existing menu call site gains it without changing" — so a phone-specific
surface built here is either thrown away by #60 or becomes the second one
it exists to prevent. Commenting on #60 so it inherits this surface.

And no phone mode for page-header. PHONE_COLUMN_IDS is the
precedent for "what is drawn and what can be sorted are different
questions", but it exists because the track list's columns cannot be
derived from a width. These can: the fit rule already collapses all three
at 390px, and a second declaration of what a phone shows is a second thing
to keep in step.

Verification: make ui-test, tsc --noEmit in both frontend/ and
e2e/, make e2e, and read screenshots at 900x600, 800x600 and 390x780 —
layout-overflow.spec.ts asserts the shell needs no sideways scrolling
and passes on today's build, which is exactly why this defect survived a
spec named for it.

Claiming this. Branch: `fix/69-page-header-action-overflow`. **Approach** — the finding on this issue decides the shape, so this is an *actions API* first and an overflow rule second. 1. `page-header` gains an `actions: PageAction[]` property — `{id, label, icon, handler, priority, disabled?, title?, acceptsDrop?}` — and renders them itself. All three hosts that slot actions (`playlist-view`, `home-view`, `downloads-view`) migrate, which also normalises the plain-`<button>`/`<wa-button>` split between them. The `<slot name="actions">` survives for markup a data list cannot express, documented as *not collapsible*: the header cannot measure or move another component's light-DOM children, which is this issue's own finding restated as the slot's cost. 2. `acceptsDrop` is what keeps `New Playlist` a drop target. The header owns the `dragover`/`dragleave`/`drop` wiring and the affordance and re-dispatches `action-drop`; a *menu item* is not a drop target, so the affordance simply does not exist in the overflow, which is correct — you cannot drag onto a closed menu. 3. The fit rule is measured, not breakpointed: a ResizeObserver on the header, and on each pass the buttons are un-hidden and the lowest-priority ones hidden one at a time until `scrollWidth <= clientWidth`. Starting each pass from all-visible is what stops it being sticky — the collapsed set is a pure function of the width. The sort control and the count are `flex-shrink: 0` and are therefore inside the budget, which is why the header's own overflow is the right thing to measure rather than the actions row's. **On the Direction's "a bottom sheet on phone — see #60":** using the existing menu pattern now and *not* blocking on #60. #60 is Phase 4 and its Direction is an `<action-sheet>` "selected by viewport width so every existing menu call site gains it without changing" — so a phone-specific surface built here is either thrown away by #60 or becomes the second one it exists to prevent. Commenting on #60 so it inherits this surface. **And no phone mode for `page-header`.** `PHONE_COLUMN_IDS` is the precedent for "what is drawn and what can be sorted are different questions", but it exists because the track list's columns cannot be derived from a width. These can: the fit rule already collapses all three at 390px, and a second declaration of what a phone shows is a second thing to keep in step. Verification: `make ui-test`, `tsc --noEmit` in both `frontend/` and `e2e/`, `make e2e`, and read screenshots at 900x600, 800x600 and 390x780 — `layout-overflow.spec.ts` asserts the *shell* needs no sideways scrolling and passes on today's build, which is exactly why this defect survived a spec named for it.
logan closed this issue 2026-08-19 19:29:15 +00:00
gitea-actions bot removed the
Status
In Progress
label 2026-08-19 19:29:24 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: yonlu/yellowjacket#69