docs: record the size bands and what the queue model cost to find

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
This commit is contained in:
2026-08-19 10:41:36 -04:00
parent 637bb611f8
commit 0f080aefc4
3 changed files with 190 additions and 5 deletions
+84
View File
@@ -3580,3 +3580,87 @@ per-card flag can answer at all.
The general point: **two columns that agree today are not one column.**
Which of them a new surface reads should be decided by which one has
something that can un-set it.
## The queue panel was a column that could not afford to be one (measured 2026-08-19)
Plan 018, issue #24. Measured against the running app (`make
dev-headless SEED=default`, Chromium) on Playlists, sweeping the
viewport with the queue open and closed. Main panel width, and how much
of the page header survived:
| viewport | sidebar | main (queue open) | actions clipped |
|---|---|---|---|
| 1280×800 | 200 | 759 | — |
| 1000×700 | 200 | 479 | 2 of 3 |
| **900×600** | 200 | **379** | all three |
| 800×600 | 56 | 423 | all three |
| 390×780 | — | **69** | all three |
| 320×600 | — | **0** | all three |
| 800×600 | 56 | 744 *(closed)* | New Smart Playlist, 158/162px |
Five things came out of it that the issue did not say.
- **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 its 162 there. The queue makes it dramatic; it is not
the cause.
- **900×600 is worse than 800×600.** `AUTO_COLLAPSE_VIEWPORT` collapses
the sidebar *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. `layout-overflow.spec.ts`
carries 900 now.
- **At 320px the main panel was 0px.** The panel is `flex-shrink: 0` in
the flow of `.content-area`, so an open queue is paid for by the
content rather than covering it. Not degraded — gone. That is the
measurement #55 wanted and did not have.
- **Only Playlists overflows.** All ten primary views swept at 900×600
and 390×780; every other header reports `scrollWidth ==
clientWidth`, and Albums at 390 renders title, count and sort legibly
(checked on a screenshot, not just the number). So #69 is one view's
action set — three text buttons totalling 390px — and not a systemic
header failure.
- **Both reasons in `MinWidth`'s comment had expired.** The subtitle is
`display: none` from 899 down, and the sidebar host is
`overflow-y: auto` (at 600×460, `scrollHeight` 434 against a 332px
client, Settings reachable after scrolling). The floor is right; its
stated defence was two mechanisms that can no longer happen, which is
worse than either answer because nobody can argue with it.
**A correction worth keeping, because it nearly went in the plan.** 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. CLAUDE.md was right and the probe was wrong.
**And one claim in the plan's first draft was too strong**: that the
overlay "removes the desktop half of #69". After phase 2, 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. The queue's
contribution is gone; the header's own overflow remains and is still a
live defect at a supported size.
### The mode cannot be a media query
The panel is drag-resizable 200500px 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, since a wider queue
is exactly when the content can least afford it. It is computed from
`.content-area`'s width instead (which already accounts for the
sidebar's collapse), and the component test that matters widens the
panel at a *fixed* parent width and asserts the flip.
The floor (480) is a judgement, and the measurement is why: there is no
cliff. The track list rescales its columns continuously — 213px down to
124px between main widths of 900 and 544, `rowOverflow=0` at every step
— and the album grid steps 3 columns to 2 somewhere between 564 and 644
without breaking. So 480 is anchored at both ends instead: it keeps the
default 1100px window inline, and puts every measured-broken case on
the overlay side.
The scrim is perceptible but subtle on a dark ramp, which is worth
knowing before someone "fixes" it as broken: sampled from screenshots at
900×600, the main panel's background goes 33,37,41 → 18,20,23 and a
row's text 242 → 133. It covers the content area only — not the sidebar
or the transport — because the queue is not modal.
@@ -183,6 +183,22 @@ follows immediately after this. What *this* plan owes it is the
promise in the matrix — no action unreachable at any supported size —
and the measurement that the only offender today is Playlists.
**And the promise is not kept yet, which is the honest version of a
claim this document made in its first draft.** "Decision 2 removes the
desktop half of #69's symptom" was too strong. Measured after phase 2,
at 900×600 on Playlists:
| | before | after |
|---|---|---|
| queue open | main 379px, **all three** actions clipped | main 700px, **one** clipped |
| queue closed | main 700px, one clipped | unchanged |
So the queue's *contribution* is gone — open and closed are now
identical, which is the whole of what this decision owed — and the
residual "New Smart Playlist: 114/162px" is the header overflowing on
its own, at a size the queue never touched. #69 is still a live defect
at a supported size, and the matrix's promise is what will close it.
---
## Decision 4 — a very small window becomes the phone layout, not the mini-player
@@ -219,16 +235,39 @@ correctness one.
## Phases
1. **This document**, linked from #24, with the matrix reported on the
issue and #55 told whether it is unblocked. *(no code)*
issue and #55 told whether it is unblocked. *(no code)***done**
2. **The queue's overlay mode** — computed mode attribute, scrim,
Escape and scrim-click close, focus return. The inline path is
unchanged above the threshold.
unchanged above the threshold.**done**
3. **The window minimum's comment** — replace both stale reasons with
the measured ones. No value change.
the measured ones. No value change.**done**
4. **Verification**, below. Including the specs that must change
because they assert the old behaviour.
because they assert the old behaviour.**done**
#69 follows as its own branch; #55 becomes unblocked at phase 2.
#69 follows as its own branch; #55 became unblocked at phase 2.
## What landed, measured
Main panel width with the queue open, before and 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 |
| 800×600 | 423 | 744 | overlay |
| 390×780 | **69** | **390** | overlay |
| 320×600 | **0** | **320** | overlay |
The scrim is perceptible but subtle on a dark ramp, which is worth
knowing before someone "fixes" it: sampled from the screenshots at
900×600, the main panel's background goes 33,37,41 → 18,20,23 and a
row's text 242 → 133. It covers the **content area only** — not the
sidebar or the transport — on purpose: the queue is not modal, and
leaving the navigation live means the scrim reads as "this is over the
content" (which is what #24 asked for) without pretending the rest of
the app is unavailable.
## Verification, and what each tier cannot see
+62
View File
@@ -1328,6 +1328,68 @@ is 32px each. Which four is plan 016's committed subset, and everything
else — Settings included, because a phone still needs it — is behind
"More".
**There are three supported size bands, and the queue is part of the
promise.** Plan 018 (#24) wrote them down: **Phone** below 600 (bottom
nav, reflows, fits 320px exactly), **Compact** 600899 (icon sidebar),
**Desktop** from 900 (labelled sidebar) — plus one sentence across all
three, *no action is ever unreachable at any supported size*. The bands
themselves already existed; what was new is that they are a promise and
that the queue panel is inside it.
**900 is the worst desktop width, not the 800×600 minimum.** 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.
Every viewport list that stopped at "the minimum" was therefore missing
its own worst case, which is why `layout-overflow.spec.ts` carries 900
now. And **both reasons in `MinWidth`'s comment had expired** — the
subtitle is `display: none` from 899 down and the sidebar host scrolls
(`overflow-y: auto`; at 600×460 its `scrollHeight` is 434 against a
332px client) — so 800×600 is a *comfort* floor for desktop chrome and
not a correctness one. Below it the phone layout takes over, which is
also why a very small window reflows rather than becoming a
mini-player: **#12 is a second always-on-top window, not a mode of this
one**, and making it a mode would discard navigation state on a resize
and put the process-level MPRIS question on a path a drag can trigger.
**The queue panel is a column only while the content can spare the
width, and that cannot be a media query.** In flow the host is
`flex-shrink: 0`, so an open queue is paid for by the main panel: it
left 379px at 900×600 (with all three of the Playlists header's actions
clipped), 69px at 390, and **0px** at 320 — the content was not
degraded but gone. It goes to an overlay with a scrim when
`available - panelWidth < 480`, where `available` is
`.content-area`'s width and therefore already accounts for the
sidebar's collapse.
Four things about it are load-bearing. **The mode is computed, not
breakpointed**, because the panel's width is user state — drag-resizable
200500px and persisted — so a viewport breakpoint silently assumes the
default 320 and is wrong by up to 180px in the direction that hurts;
widening the panel at a fixed window size must flip it, and
`queue-overlay-mode.test.ts` is written around exactly that. **480 is a
judgement and 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
and put every measured-broken case on the overlay side. **The scrim
covers the content area only** — not the sidebar or the transport —
because the queue is not modal, and it is subtle on a dark ramp by
arithmetic rather than by accident (33,37,41 → 18,20,23). And **the
overlay is a presentation, not a fork**: #55 asks for one component
with two mount points, so the roving tab stop, Alt+Arrow reorder, drag
reorder, selection semantics and `virtualizer.requestUpdate()` all come
along untouched. Escape closes it and returns focus, and is attached
only while the overlay is up — it is a dismissal, not a shortcut, which
is why it is not a panel-scoped binding.
What this does **not** fix is `page-header` overflowing on its own:
at 900×600 "New Smart Playlist" is still clipped to 114 of 162px with
the queue *closed*. That is #69, and it cannot be fixed in
`page-header` alone — actions arrive through `<slot name="actions">` as
arbitrary light-DOM markup with their own handlers, so collapsing them
into a "More actions" menu needs an actions *API* (data, not markup)
across all three hosts that slot them.
**The phone section of `index.css` is last on purpose.** A media query
adds no specificity, so a `@media (max-width: 599px)` block placed
above the plain rules it overrides loses to them — which is how phase 1