#55 asks for the queue to be "a real screen instead of a pop-open
sidebar", and its Direction asks for a DETAIL_LOADERS mount. The
pixels were already right and the navigation model was not, which is
the finding this PR is built on. Measured at the reference device's real
viewport (424x439), with #24's overlay open:
box
rect
.main-panel
424 x 318
queue-panel host
424 x 318
.panel-content
424 x 318
.scrim
424 x 318, entirely underneath the panel
A detail-view mount draws the same rectangle in the same place. What was
genuinely missing, and is fixed here:
Back navigated the page underneath and left the queue up. Opened
the queue on Artists, pressed back: data-active-view went artists
-> albums, open stayed true. A press that changes something the
user cannot see, and costs them their place.
The scrim has zero reachable pixels at phone width, so the close
button is the only pointer route out of a full-screen surface — and it
was 25x21px.
The rule: the queue is a place exactly while it is an overlay, and a
control while it is a column. A column is a thing the user docked, so
back must not undock it and a navigation must not take it away. That
reuses #24's computed mode rather than adding a breakpoint, so the
drag-resizable panel width keeps deciding it — the property queue-overlay-mode.test.ts is written around.
Why the Direction was not followed
.main-panel > * computes contain: content under a .main-panel that
does too, and contain: paint clips the position: fixed a wa-popup
falls back to on the reference device's Chrome 113 (#60, .planning/NOTES.md). queue-panel has a context menu, and its
ancestry today is paint-free to body:
element
computed contain
queue-panel (open, overlay)
layout style
.content-area
layout style
.main-panel
content
.main-panel > * (a view)
content
So the mount asked for would have regressed a working menu on the one
device the issue is about, and no tier here can see it — CI's
Chromium and WebKit both have the Popover API, so a spec asserting "the
menu is not clipped" is green on the broken build. The spec asserts the mechanism instead: the panel is not under a paint-contained ancestor.
Two smaller costs avoided: a detail view is ephemeral, so selection, focusedIndex and scroll position would be discarded on every visit;
and a second <queue-panel> in the DOM is the bottom-nav drawer's
"resolved to 2 elements" trap by construction, with four e2e specs
locating the panel by tag.
This is also the answer to the #60-before-#55 recommendation left on
the last session's handover — argued on #73 and on #55 before starting,
not silently. The constraint only existed because #55-as-specified moves
the menu into the contained region. Nothing moves, so it dissolves. #60
keeps its place and its priority.
Commits
commit
issue
what
de2cb26feat(queue): give an overlaid queue a place in the back stack
the model: utils/open-queue.ts, the queue navigation branch, unwinding the entry from the panel's own attribute, and now-playing-view's copy of the button
a70a7edfix(queue): size the queue screen's way out for a thumb
210 passed (whole suite, not the spec written — the last two changes each broke a spec about something else)
make ui-test
968 passed (964 before; +4 from open-queue.test.ts)
tsc --noEmit in frontend/ and e2e/
clean, both
make css-check
142 files, no broken literals
make lint
0 issues, all three tag sets — formality, no Go changed
make test
green, all three tag sets — formality
make bindings-check
current — formality
make skill-check
47 targets present — formality
--project=webkit is not configured locally and cannot be, so the
WebKit half is CI's; check that step actually ran (if: !cancelled()
has silently skipped it before).
The specs were shown to bite
Reverted the four source files, rebuilt, re-ran: 3 of 9 fail, and
the file's header says which and why the other six cannot. "The entry is
not orphaned" and "a docked column is not in the stack" are vacuously
true of a build that pushes no entry at all; the containment assertion
pins the mount that was not taken. The three that reproduce are the
two back-press tests and the touch target.
Measured after, at 424x439
action
before
after
open the queue, press back
page goes artists -> albums, queue stays open
queue closes, page stays on albums
press back again
—
page navigates
close by button / Escape / toggle, then back
—
page navigates (entry unwound, not orphaned)
header actions
25x21
44x44
desktop 1280x800: open column, navigate, back
column survives, no entry
unchanged
Screenshots at 424x439 and 390x780 in a comment below.
What this deliberately does not do
The entry point is unchanged: the bottom bar's queue button and now-playing-view's copy, both now through one helper. #59 is where
"reachable only from Now Playing" belongs, and slimming the mini
player is its work.
The queue is not a tab. It is not a destination, and bottom-nav's four are plan 016's committed subset.
Resizing an open overlay queue into a column leaves a dangling
entry, so back then navigates the page and leaves the column open.
That is the right reading — it is a column now, and back does what
back does — and it is commented rather than guarded.
Closes #55.
## What this is
#55 asks for the queue to be "a real screen instead of a pop-open
sidebar", and its Direction asks for a `DETAIL_LOADERS` mount. **The
pixels were already right and the navigation model was not**, which is
the finding this PR is built on. Measured at the reference device's real
viewport (424x439), with #24's overlay open:
| box | rect |
|---|---|
| `.main-panel` | 424 x 318 |
| `queue-panel` host | 424 x 318 |
| `.panel-content` | 424 x 318 |
| `.scrim` | 424 x 318, **entirely underneath the panel** |
A detail-view mount draws the same rectangle in the same place. What was
genuinely missing, and is fixed here:
- **Back navigated the page underneath and left the queue up.** Opened
the queue on Artists, pressed back: `data-active-view` went `artists`
-> `albums`, `open` stayed `true`. A press that changes something the
user cannot see, and costs them their place.
- **The scrim has zero reachable pixels at phone width**, so the close
button is the only pointer route out of a full-screen surface — and it
was **25x21px**.
The rule: **the queue is a place exactly while it is an overlay, and a
control while it is a column.** A column is a thing the user docked, so
back must not undock it and a navigation must not take it away. That
reuses #24's *computed* mode rather than adding a breakpoint, so the
drag-resizable panel width keeps deciding it — the property
`queue-overlay-mode.test.ts` is written around.
## Why the Direction was not followed
`.main-panel > *` computes `contain: content` under a `.main-panel` that
does too, and `contain: paint` clips the `position: fixed` a `wa-popup`
falls back to on the reference device's Chrome 113 (#60,
`.planning/NOTES.md`). `queue-panel` has a context menu, and its
ancestry today is paint-free to `body`:
| element | computed `contain` |
|---|---|
| `queue-panel` (open, overlay) | `layout style` |
| `.content-area` | `layout style` |
| `.main-panel` | `content` |
| `.main-panel > *` (a view) | `content` |
So the mount asked for would have regressed a working menu on the one
device the issue is about, and **no tier here can see it** — CI's
Chromium and WebKit both have the Popover API, so a spec asserting "the
menu is not clipped" is green on the broken build. The spec asserts the
*mechanism* instead: the panel is not under a paint-contained ancestor.
Two smaller costs avoided: a detail view is ephemeral, so selection,
`focusedIndex` and scroll position would be discarded on every visit;
and a second `<queue-panel>` in the DOM is the `bottom-nav` drawer's
"resolved to 2 elements" trap by construction, with four e2e specs
locating the panel by tag.
**This is also the answer to the #60-before-#55 recommendation** left on
the last session's handover — argued on #73 and on #55 before starting,
not silently. The constraint only existed because #55-as-specified moves
the menu into the contained region. Nothing moves, so it dissolves. #60
keeps its place and its priority.
## Commits
| commit | issue | what |
|---|---|---|
| `de2cb26` `feat(queue): give an overlaid queue a place in the back stack` | #55 | the model: `utils/open-queue.ts`, the `queue` navigation branch, unwinding the entry from the panel's own attribute, and `now-playing-view`'s copy of the button |
| `a70a7ed` `fix(queue): size the queue screen's way out for a thumb` | #55 | 25x21 -> 44x44, in overlay mode only |
| `4ee5b4b` `test(queue): pin the back stack and the mount that was not taken` | #55 | `e2e/specs/queue-as-a-screen.spec.ts` |
| `af4b28b` `docs(queue): record why the queue is not a detail view` | #55 | `CLAUDE.md` model, `.planning/NOTES.md` measurements |
## Verification actually run
| gate | result |
|---|---|
| `make e2e` | **210 passed** (whole suite, not the spec written — the last two changes each broke a spec about something else) |
| `make ui-test` | **968 passed** (964 before; +4 from `open-queue.test.ts`) |
| `tsc --noEmit` in `frontend/` and `e2e/` | clean, both |
| `make css-check` | 142 files, no broken literals |
| `make lint` | 0 issues, all three tag sets — **formality**, no Go changed |
| `make test` | green, all three tag sets — **formality** |
| `make bindings-check` | current — **formality** |
| `make skill-check` | 47 targets present — **formality** |
**`--project=webkit` is not configured locally and cannot be**, so the
WebKit half is CI's; check that step actually ran (`if: !cancelled()`
has silently skipped it before).
### The specs were shown to bite
Reverted the four source files, rebuilt, re-ran: **3 of 9 fail**, and
the file's header says which and why the other six cannot. "The entry is
not orphaned" and "a docked column is not in the stack" are vacuously
true of a build that pushes no entry at all; the containment assertion
pins the mount that was *not* taken. The three that reproduce are the
two back-press tests and the touch target.
### Measured after, at 424x439
| action | before | after |
|---|---|---|
| open the queue, press back | page goes `artists` -> `albums`, queue stays open | queue closes, page stays on `albums` |
| press back again | — | page navigates |
| close by button / Escape / toggle, then back | — | page navigates (entry unwound, not orphaned) |
| header actions | 25x21 | 44x44 |
| desktop 1280x800: open column, navigate, back | column survives, no entry | **unchanged** |
Screenshots at 424x439 and 390x780 in a comment below.
## What this deliberately does not do
- **The entry point is unchanged**: the bottom bar's queue button and
`now-playing-view`'s copy, both now through one helper. #59 is where
"reachable only from Now Playing" belongs, and slimming the mini
player is its work.
- **The queue is not a tab.** It is not a destination, and
`bottom-nav`'s four are plan 016's committed subset.
- Resizing an *open* overlay queue into a column leaves a dangling
entry, so back then navigates the page and leaves the column open.
That is the right reading — it is a column now, and back does what
back does — and it is commented rather than guarded.
The queue's pixels were already right. Measured at the reference
device's 424x439, #24's overlay is 424x318 -- `.main-panel`'s rect
exactly -- so the `DETAIL_LOADERS` mount the issue's Direction asks for
would draw the same rectangle in the same place. What was missing was
the navigation model: opening the queue on Artists and pressing back
moved the page *underneath* to Albums and left the queue up, which is a
press that changes something the user cannot see and costs them their
place.
So the queue is a *place* exactly while it is an overlay, and a
*control* while it is a column. A column is a thing the user docked --
back must not undock it and a navigation must not take it away -- and
that reuses #24's computed mode rather than adding a breakpoint, so the
drag-resizable panel width keeps deciding it.
It is in neither `VIEW_TAGS` nor `DETAIL_LOADERS`, because there is
nothing to mount and moving it would cost something. `.main-panel > *`
computes `contain: content` under a `.main-panel` that does too, and
paint containment clips the `position: fixed` a `wa-popup` falls back
to on Chrome 113 (#60) -- so the detail-view mount would have broken
`queue-panel`'s working context menu on the one device this is about.
The panel's ancestry today is paint-free to `body`.
Two details that fail silently otherwise. The entry is unwound from the
panel's `open` attribute in the observer that already ran for
`aria-expanded`, not at each of the four ways out -- without that the
entry is orphaned and the *next* back press is the one that closes the
queue, which is this defect moved one press later. And the navigation
writes neither `dataset.activeView` nor `searchStore.setCurrentView`,
because both describe what is *in* the main panel and the queue covers
that panel without replacing it.
`now-playing-view`'s copy of the button went through the helper too: it
set `open` directly, so on a phone it produced exactly the queue with no
entry behind it that this removes.
Closes#55
Measured at 424x439: the three header actions were 25x21px. That
matters more than it looks, because with the panel spanning the whole
width the scrim underneath it has no uncovered pixels at all -- so the
close button is the only pointer route out of a full-screen surface,
and it was below the 24x24 floor in one dimension.
Sized only in overlay mode. Inline these sit in a 320px column beside
the content, where a mouse is what reaches them and 44px of header is
44px the queue does not get.
Nine tests, and the header says which of them reproduce the defect:
three do, and the other six cannot. "The entry is not orphaned" and "a
docked column is not in the stack" are both vacuously true of a build
that pushes no entry at all. That was established by reverting the
source and re-running, not assumed.
The containment assertion is the one worth reading twice. It asks
where the panel *is* rather than whether a menu is clipped, because
CI's Chromium and WebKit both have the Popover API -- so the symptom is
invisible here and a spec asserting "not clipped" is green on the
broken build. `.planning/NOTES.md` states the mechanism.
The rest assert the entry rather than `aria-expanded`, which is the
shell's own bookkeeping and was right throughout the defect: what has
to be true is that one back press closes the queue and the *next* one
navigates.
The measurement that decided it, dated, in NOTES.md -- the overlay's
rect against the main panel's, the three things that were genuinely
missing, and the computed containment of both candidate mounts. The
model itself goes in CLAUDE.md beside the overlay rule it extends.
424x439 — the reference device's real viewport. The queue open over
Albums: a header with three thumb-sized actions, the list, the mini
player and the tab bar. Albums is still lit, which is deliberate —
the queue is published with isPrimary: false, so it leaves the
destination it was opened from highlighted exactly as a detail view
does.
390x780.
One thing visible in the second and not fixed here: the mini player's
metadata is clipped to "02 To..." / "Unkno..." at 390px. That is #59's
work, not this PR's.
## Screenshots
**424x439 — the reference device's real viewport.** The queue open over
Albums: a header with three thumb-sized actions, the list, the mini
player and the tab bar. `Albums` is still lit, which is deliberate —
the queue is published with `isPrimary: false`, so it leaves the
destination it was opened from highlighted exactly as a detail view
does.

**390x780.**

One thing visible in the second and not fixed here: the mini player's
metadata is clipped to "02 To..." / "Unkno..." at 390px. That is #59's
work, not this PR's.
Run 17339, both jobs success, checked per step rather than by the
run's conclusion:
check — commit messages, lint (three tag sets), test (three tag
sets), tsc, css literals, the component suite, bindings-check,
skill-check. All green.
e2e — E2E — webkit completed success, not skipped. It carries if: ${{ !cancelled() }} and has silently produced no signal before,
so this is the half worth naming: chromium and WebKit both passed.
Two issues filed from measuring adjacent things while doing this:
#170 — the queue header's two older actions are named only by title, which is a weak name a getFullAXTree sweep reports clean
and which is not exposed on touch at all. Not fixed here; not this
PR's surface and a rename wants its own assertion.
#171 — the queue's scrim has zero reachable pixels below 600px, so #24's tap-outside-to-close does not exist on a phone. This PR makes it
survivable (back closes the queue; the close button is 44x44) rather
than fixing it, and the fix is a design decision #59 also touches.
## CI
Run 17339, both jobs **success**, checked per step rather than by the
run's conclusion:
- `check` — commit messages, lint (three tag sets), test (three tag
sets), `tsc`, css literals, the component suite, bindings-check,
skill-check. All green.
- `e2e` — **`E2E — webkit` completed success**, not skipped. It carries
`if: ${{ !cancelled() }}` and has silently produced no signal before,
so this is the half worth naming: chromium and WebKit both passed.
Two issues filed from measuring adjacent things while doing this:
- #170 — the queue header's two older actions are named only by
`title`, which is a weak name a `getFullAXTree` sweep reports clean
and which is not exposed on touch at all. Not fixed here; not this
PR's surface and a rename wants its own assertion.
- #171 — the queue's scrim has zero reachable pixels below 600px, so
#24's tap-outside-to-close does not exist on a phone. This PR makes it
survivable (back closes the queue; the close button is 44x44) rather
than fixing it, and the fix is a design decision #59 also touches.
logan
merged commit 56a5ff99fe into main2026-08-21 03:00:45 +00:00
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.
Closes #55.
What this is
#55 asks for the queue to be "a real screen instead of a pop-open
sidebar", and its Direction asks for a
DETAIL_LOADERSmount. Thepixels were already right and the navigation model was not, which is
the finding this PR is built on. Measured at the reference device's real
viewport (424x439), with #24's overlay open:
.main-panelqueue-panelhost.panel-content.scrimA detail-view mount draws the same rectangle in the same place. What was
genuinely missing, and is fixed here:
the queue on Artists, pressed back:
data-active-viewwentartists->
albums,openstayedtrue. A press that changes something theuser cannot see, and costs them their place.
button is the only pointer route out of a full-screen surface — and it
was 25x21px.
The rule: the queue is a place exactly while it is an overlay, and a
control while it is a column. A column is a thing the user docked, so
back must not undock it and a navigation must not take it away. That
reuses #24's computed mode rather than adding a breakpoint, so the
drag-resizable panel width keeps deciding it — the property
queue-overlay-mode.test.tsis written around.Why the Direction was not followed
.main-panel > *computescontain: contentunder a.main-panelthatdoes too, and
contain: paintclips theposition: fixedawa-popupfalls back to on the reference device's Chrome 113 (#60,
.planning/NOTES.md).queue-panelhas a context menu, and itsancestry today is paint-free to
body:containqueue-panel(open, overlay)layout style.content-arealayout style.main-panelcontent.main-panel > *(a view)contentSo the mount asked for would have regressed a working menu on the one
device the issue is about, and no tier here can see it — CI's
Chromium and WebKit both have the Popover API, so a spec asserting "the
menu is not clipped" is green on the broken build. The spec asserts the
mechanism instead: the panel is not under a paint-contained ancestor.
Two smaller costs avoided: a detail view is ephemeral, so selection,
focusedIndexand scroll position would be discarded on every visit;and a second
<queue-panel>in the DOM is thebottom-navdrawer's"resolved to 2 elements" trap by construction, with four e2e specs
locating the panel by tag.
This is also the answer to the #60-before-#55 recommendation left on
the last session's handover — argued on #73 and on #55 before starting,
not silently. The constraint only existed because #55-as-specified moves
the menu into the contained region. Nothing moves, so it dissolves. #60
keeps its place and its priority.
Commits
de2cb26feat(queue): give an overlaid queue a place in the back stackutils/open-queue.ts, thequeuenavigation branch, unwinding the entry from the panel's own attribute, andnow-playing-view's copy of the buttona70a7edfix(queue): size the queue screen's way out for a thumb4ee5b4btest(queue): pin the back stack and the mount that was not takene2e/specs/queue-as-a-screen.spec.tsaf4b28bdocs(queue): record why the queue is not a detail viewCLAUDE.mdmodel,.planning/NOTES.mdmeasurementsVerification actually run
make e2emake ui-testopen-queue.test.ts)tsc --noEmitinfrontend/ande2e/make css-checkmake lintmake testmake bindings-checkmake skill-check--project=webkitis not configured locally and cannot be, so theWebKit half is CI's; check that step actually ran (
if: !cancelled()has silently skipped it before).
The specs were shown to bite
Reverted the four source files, rebuilt, re-ran: 3 of 9 fail, and
the file's header says which and why the other six cannot. "The entry is
not orphaned" and "a docked column is not in the stack" are vacuously
true of a build that pushes no entry at all; the containment assertion
pins the mount that was not taken. The three that reproduce are the
two back-press tests and the touch target.
Measured after, at 424x439
artists->albums, queue stays openalbumsScreenshots at 424x439 and 390x780 in a comment below.
What this deliberately does not do
now-playing-view's copy, both now through one helper. #59 is where"reachable only from Now Playing" belongs, and slimming the mini
player is its work.
bottom-nav's four are plan 016's committed subset.entry, so back then navigates the page and leaves the column open.
That is the right reading — it is a column now, and back does what
back does — and it is commented rather than guarded.
Screenshots
424x439 — the reference device's real viewport. The queue open over
Albums: a header with three thumb-sized actions, the list, the mini
player and the tab bar.
Albumsis still lit, which is deliberate —the queue is published with
isPrimary: false, so it leaves thedestination it was opened from highlighted exactly as a detail view
does.
390x780.
One thing visible in the second and not fixed here: the mini player's
metadata is clipped to "02 To..." / "Unkno..." at 390px. That is #59's
work, not this PR's.
CI
Run 17339, both jobs success, checked per step rather than by the
run's conclusion:
check— commit messages, lint (three tag sets), test (three tagsets),
tsc, css literals, the component suite, bindings-check,skill-check. All green.
e2e—E2E — webkitcompleted success, not skipped. It carriesif: ${{ !cancelled() }}and has silently produced no signal before,so this is the half worth naming: chromium and WebKit both passed.
Two issues filed from measuring adjacent things while doing this:
title, which is a weak name agetFullAXTreesweep reports cleanand which is not exposed on touch at all. Not fixed here; not this
PR's surface and a rename wants its own assertion.
#24's tap-outside-to-close does not exist on a phone. This PR makes it
survivable (back closes the queue; the close button is 44x44) rather
than fixing it, and the fix is a design decision #59 also touches.