Decide the supported sizes, and stop the queue taking the page's width #132

Merged
logan merged 4 commits from feat/24-supported-sizes-queue-model into main 2026-08-19 15:22:40 +00:00
Collaborator

#73 puts #24 first in Phase 2 and hangs the rest of the phase off it, and its
own Direction asks for a design pass — so the first deliverable is a plan
document, not a patch. .planning/plans/active/018-supported-sizes-and-the-queue-model.md
is the decision; the code is the part of it that could land now.

Commits

commit what issue
cee43e5 the plan: size matrix, queue model, and the two scope decisions #24
637bb61 the queue overlays the content instead of taking its width #24
0f080ae CLAUDE.md gains the bands; NOTES.md gains the measurements #24
d9d3b63 900×600 added to the shell specs; MinWidth's stale comment replaced #24

What was measured

On Playlists, against the running app, sweeping the viewport with the queue
open — five things that are not in the issue:

  • The header clips at the enforced minimum with the queue closed. 800×600 is
    the only size this app promises, and "New Smart Playlist" loses 4px of 162
    there. The queue makes it dramatic; it is not the cause.
  • 900×600 is worse than 800×600. The sidebar collapses to icons below 900,
    so the main panel is 843px at 899 and 700px at 900. The worst desktop case is
    the top of the Compact band, not the enforced floor — so every viewport list
    that stopped at "the minimum" was missing its own worst case.
  • At 320px the main panel was 0px. The panel is flex-shrink: 0 in the flow
    of .content-area, so an open queue was paid for by the content. Not
    degraded — gone. 69px at 390px.
  • Only Playlists overflows — all ten primary views swept at two widths.
  • Both reasons in MinWidth's comment had expired (the subtitle is
    display: none from 899 down; the sidebar host scrolls).

Main panel width with the queue open, before → after:

viewport before after mode
1280×800 759 759 inline
1100×720 (default window) 579 579 inline
1024×768 503 503 inline
900×600 379 700 overlay
390×780 69 390 overlay
320×600 0 320 overlay

The decision worth reviewing

The mode is computed, not breakpointedavailable - panelWidth < 480,
where available is .content-area's width. A media query cannot express it:
the panel is drag-resizable 200–500px and persisted, so a viewport breakpoint
assumes the default 320 and is wrong by up to 180px for a user who widened it,
in the direction that hurts. Widening the panel at a fixed window size must flip
it, and that is the component test that matters.

480 is a judgement and says so. There is no cliff — the track list rescales
continuously (213px → 124px columns, no row overflow) — so it is anchored to
keep the default 1100px window inline and put every measured-broken case on the
overlay side.

What this deliberately does not fix

#69 is still live, and the plan's first draft was wrong to claim otherwise.
At 900×600 open and closed are now identical (main 700, one action clipped)
where open used to be main 379 with all three clipped — so the queue's
contribution is gone, but the header still overflows on its own. It cannot be
fixed in page-header alone: actions arrive through <slot name="actions"> as
arbitrary light-DOM markup with their own handlers, so a "More actions" menu
needs an actions API (data, not markup) across all three hosts that slot
them. That is its own branch, next.

#55 is unblocked (Status/Blocked dropped, with the measurement on it). The
overlay is a presentation of the same component, not a fork, so its roving tab
stop, Alt+Arrow reorder, drag reorder and selection semantics are untouched —
which is what its "one component, two mount points" Direction asks for.

#12 is not coupled to this. A very small window becomes the phone layout,
which already exists and is already tested, rather than a mini-player: #12 is a
second always-on-top window, and making it a mode of the main window would
discard navigation state on a resize and put its own process-level MPRIS
question on a path a drag can trigger.

Verification actually run

  • make lint — 0 issues, all three build configurations.
  • make test — green, all three configurations.
  • make ui-test — 80 files, 904 tests (6 new).
  • frontend/node_modules/.bin/tsc --noEmit and e2e tsc --noEmit — clean.
    The frontend one caught four nullability errors in the new component test that
    no test tier runs.
  • make e2e — 126 passed, 1 failed: view-lifecycle.spec.ts's autotag skip,
    which is the documented state-spending flake. Verified pre-existing by
    restarting the app and re-running that spec alone: 4/4 pass.
  • make css-check, make skill-check, make bindings-check — clean.
  • Screenshots read at each band, which is the half none of the above can
    see: layout-overflow.spec.ts asserts the shell needs no sideways scrolling
    and passes on a build whose header clips its own buttons. The scrim was
    checked by sampling pixels (33,37,41 → 18,20,23) rather than by eye, because
    it is subtle on a dark ramp by arithmetic.

New specs: e2e/specs/queue-overlay.spec.ts (11) and
frontend/test/components/queue-overlay-mode.test.ts (6). The component ones
were checked against the old behaviour — 4 of 6 fail on it, including the drag
case; the 2 that pass are the inline ones, correctly.

Filed along the way

  • #130issue.sh claim is unusable: it needs read:user for one lookup
    nothing else needs, so the one step required before the first edit is blocked
    by its own tooling.
  • #131codegen-check fails on any unstaged change and blames the code
    generators.
  • Commented on #128 with a pre-push sighting of the ui-test flake (it
    rejected this push once, then passed unchanged).

Closes #24

#73 puts #24 first in Phase 2 and hangs the rest of the phase off it, and its own Direction asks for a design pass — so the first deliverable is a plan document, not a patch. `.planning/plans/active/018-supported-sizes-and-the-queue-model.md` is the decision; the code is the part of it that could land now. ## Commits | commit | what | issue | |---|---|---| | `cee43e5` | the plan: size matrix, queue model, and the two scope decisions | #24 | | `637bb61` | the queue overlays the content instead of taking its width | #24 | | `0f080ae` | CLAUDE.md gains the bands; NOTES.md gains the measurements | #24 | | `d9d3b63` | 900×600 added to the shell specs; `MinWidth`'s stale comment replaced | #24 | ## What was measured On Playlists, against the running app, sweeping the viewport with the queue open — five things that are **not** in the issue: - **The header clips at the enforced minimum with the queue closed.** 800×600 is the only size this app promises, and "New Smart Playlist" loses 4px of 162 there. The queue makes it dramatic; it is not the cause. - **900×600 is worse than 800×600.** The sidebar collapses to icons *below* 900, so the main panel is 843px at 899 and 700px at 900. The worst desktop case is the top of the Compact band, not the enforced floor — so every viewport list that stopped at "the minimum" was missing its own worst case. - **At 320px the main panel was 0px.** The panel is `flex-shrink: 0` in the flow of `.content-area`, so an open queue was paid for by the content. Not degraded — gone. 69px at 390px. - **Only Playlists overflows** — all ten primary views swept at two widths. - **Both reasons in `MinWidth`'s comment had expired** (the subtitle is `display: none` from 899 down; the sidebar host scrolls). Main panel width with the queue open, before → after: | viewport | before | after | mode | |---|---|---|---| | 1280×800 | 759 | 759 | inline | | 1100×720 (default window) | 579 | 579 | inline | | 1024×768 | 503 | 503 | inline | | 900×600 | **379** | **700** | overlay | | 390×780 | **69** | **390** | overlay | | 320×600 | **0** | **320** | overlay | ## The decision worth reviewing **The mode is computed, not breakpointed** — `available - panelWidth < 480`, where `available` is `.content-area`'s width. A media query cannot express it: the panel is drag-resizable 200–500px and persisted, so a viewport breakpoint assumes the default 320 and is wrong by up to 180px for a user who widened it, in the direction that hurts. Widening the panel at a fixed window size must flip it, and that is the component test that matters. **480 is a judgement and says so.** There is no cliff — the track list rescales continuously (213px → 124px columns, no row overflow) — so it is anchored to keep the default 1100px window inline and put every measured-broken case on the overlay side. ## What this deliberately does not fix **#69 is still live**, and the plan's first draft was wrong to claim otherwise. At 900×600 open and closed are now *identical* (main 700, one action clipped) where open used to be main 379 with all three clipped — so the queue's contribution is gone, but the header still overflows on its own. It cannot be fixed in `page-header` alone: actions arrive through `<slot name="actions">` as arbitrary light-DOM markup with their own handlers, so a "More actions" menu needs an actions **API** (data, not markup) across all three hosts that slot them. That is its own branch, next. **#55 is unblocked** (`Status/Blocked` dropped, with the measurement on it). The overlay is a presentation of the same component, not a fork, so its roving tab stop, Alt+Arrow reorder, drag reorder and selection semantics are untouched — which is what its "one component, two mount points" Direction asks for. **#12 is not coupled to this.** A very small window becomes the phone layout, which already exists and is already tested, rather than a mini-player: #12 is a second always-on-top window, and making it a mode of the main window would discard navigation state on a resize and put its own process-level MPRIS question on a path a drag can trigger. ## Verification actually run - `make lint` — 0 issues, all three build configurations. - `make test` — green, all three configurations. - `make ui-test` — 80 files, **904** tests (6 new). - `frontend/node_modules/.bin/tsc --noEmit` and `e2e` `tsc --noEmit` — clean. The frontend one caught four nullability errors in the new component test that no test tier runs. - `make e2e` — 126 passed, 1 failed: `view-lifecycle.spec.ts`'s autotag skip, which is the documented state-spending flake. Verified pre-existing by restarting the app and re-running that spec alone: 4/4 pass. - `make css-check`, `make skill-check`, `make bindings-check` — clean. - **Screenshots read at each band**, which is the half none of the above can see: `layout-overflow.spec.ts` asserts the *shell* needs no sideways scrolling and passes on a build whose header clips its own buttons. The scrim was checked by sampling pixels (33,37,41 → 18,20,23) rather than by eye, because it is subtle on a dark ramp by arithmetic. New specs: `e2e/specs/queue-overlay.spec.ts` (11) and `frontend/test/components/queue-overlay-mode.test.ts` (6). The component ones were checked against the old behaviour — 4 of 6 fail on it, including the drag case; the 2 that pass are the inline ones, correctly. ## Filed along the way - **#130** — `issue.sh claim` is unusable: it needs `read:user` for one lookup nothing else needs, so the one step required before the first edit is blocked by its own tooling. - **#131** — `codegen-check` fails on any unstaged change and blames the code generators. - Commented on **#128** with a pre-push sighting of the `ui-test` flake (it rejected this push once, then passed unchanged). Closes #24
logan added 4 commits 2026-08-19 14:55:52 +00:00
#24 asks for a design pass, and #73 hangs the rest of Phase 2 off the
answer, so the decision is written down before any CSS moves.

Measured against the running app, and five things are not in the issue:
the Playlists header clips at 800x600 with the queue *closed* — the
minimum window is the only size this app promises; 900x600 is worse
than 800x600, because the sidebar expands at 900, so the worst desktop
case is not the minimum and every test that stops at the minimum misses
it; at 320px with the queue open the main panel is 0px wide, because
the panel is in the flow rather than over it; only Playlists overflows,
so #69 is one view's action set and not a systemic header failure; and
both reasons in MinWidth's comment describe mechanisms that no longer
exist.

The queue's mode cannot be a media query: its width is drag-resizable
between 200 and 500px and persisted, so a fixed breakpoint assumes the
default 320 and is wrong by 180px in the direction that hurts. It is
computed from the measured widths instead.

#69 stays its own PR on a finding rather than an estimate: page-header
cannot collapse actions that arrive as arbitrary light-DOM markup
through a slot, so the fix needs an actions API across all three hosts.

A very small window becomes the phone layout, which already exists and
is already tested, rather than the mini-player: #12 is a second
always-on-top window, and making it a mode of the main window would
discard navigation state on a resize and put the process-level MPRIS
question on a path a drag can trigger.
The panel is flex-shrink: 0 in the flow of .content-area, so an open
queue was paid for by the main panel rather than covering it. Measured
on Playlists: 379px of content left at 900x600 with all three of the
page header's actions clipped, 69px at 390px, and 0px at 320px — where
the content was not degraded but gone.

It goes to an overlay with a scrim when the content cannot spare the
width, and the rule is computed rather than breakpointed:
`available - panelWidth < 480`, where available is .content-area's
width and so already accounts for the sidebar's collapse at 900. A
media query cannot express this, which is the reason for the property:
the panel is drag-resizable between 200 and 500px and persisted, so a
viewport breakpoint silently assumes the default 320 and is wrong by up
to 180px for a user who widened it — in the direction that hurts, since
a wider queue is exactly when the content can least afford it.

480 is a judgement and the comment says so: there is no cliff to derive
it from (the track list rescales continuously, 213px to 124px columns
with no row overflow), so it is anchored to keep the default 1100px
window inline while putting every measured-broken case on the overlay
side.

The overlay is a presentation and not a fork — #55 asks for one
component with two mount points — so the roving tab stop, Alt+Arrow
reorder, drag reorder and selection semantics are untouched. Escape
closes it and returns focus, attached only while the overlay is up: it
is a dismissal rather than a shortcut, which is why it is not a
panel-scoped binding. The scrim covers the content area only, not the
sidebar or the transport, because the queue is not modal.

Refs #24
CLAUDE.md gains the three bands as a promise (Phone <600, Compact
600-899, Desktop >=900, and "no action is ever unreachable at any
supported size"), the computed queue rule and why it cannot be a media
query, and the correction that 900 — not the 800x600 minimum — is the
worst desktop width.

NOTES.md gets the measurements, including two things worth more than
the fix. My first probe for the sidebar's scroller searched
shadowRoot.querySelectorAll('*') and reported "no scroller, items are
unreachable", which reads exactly like a live Settings-unreachable bug;
the scroller is the host, and a host is not inside its own shadow root.
And the plan's first draft claimed the overlay "removes the desktop
half of #69", which the screenshot disproved: open and closed are now
identical at 900x600, so the queue's contribution is gone, but the
header's own overflow remains and is still a live defect.

Refs #24
test(shell): check 900x600, which is narrower than the minimum
CI / check (push) Skipped
CI / e2e (push) Skipped
CI / check (pull_request) Successful in 2m34s
CI / e2e (pull_request) Successful in 6m42s
ede183d026
The sidebar collapses to icons *below* 900, so the main panel is 843px
at 899 and 700px at 900: the narrowest content area any desktop width
produces is at the top of the Compact band, not at the enforced floor.
A viewport list that stopped at "the minimum" was missing its own worst
case.

MinWidth's comment loses both reasons it used to give, because neither
mechanism can happen any more — the subtitle is display:none from 899
down, and the sidebar host is overflow-y:auto (at 600x460 its
scrollHeight is 434 against a 332px client, and Settings is reachable
after scrolling). The value does not change: 800x600 is where desktop
chrome stops being comfortable, not where the app breaks, and below
600 the phone layout takes over. A floor defended by two expired
mechanisms is a number nobody can argue with, which is worse than
either answer.

Closes #24
logan force-pushed feat/24-supported-sizes-queue-model from d9d3b6370f to ede183d026 2026-08-19 14:55:52 +00:00 Compare
logan merged commit 4e3c953acf into main 2026-08-19 15:22:40 +00:00
logan deleted branch feat/24-supported-sizes-queue-model 2026-08-19 15:22:40 +00:00
Sign in to join this conversation.