The queue is a place while it covers the content (#55) #169

Merged
logan merged 4 commits from 55-queue-as-a-screen into main 2026-08-21 03:00:45 +00:00
Collaborator

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.
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.
logan added 4 commits 2026-08-21 02:15:32 +00:00
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.
docs(queue): record why the queue is not a detail view
CI / check (push) Skipped
CI / e2e (push) Skipped
CI / check (pull_request) Successful in 2m31s
CI / e2e (pull_request) Successful in 8m49s
af4b28b0d7
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.
Author
Collaborator

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.

queue at 424x439

390x780.

queue at 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. ![queue at 424x439](https://git.ljones.me/attachments/ec25cf83-31dc-4c5f-a0bb-6e174c69a9f5) **390x780.** ![queue at 390x780](https://git.ljones.me/attachments/84893a58-f86a-4290-90c8-97d812c28079) 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.
Author
Collaborator

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.
  • e2eE2E — 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 main 2026-08-21 03:00:45 +00:00
Sign in to join this conversation.