docs(player): record who owns the volume, and what it gave back
CLAUDE.md's volume paragraph ended "#64 asks for it to be gone on Android outright, which is a platform question the frontend cannot currently ask", which is no longer true -- it can, and the paragraph now says why the answer is a capability rather than a viewport and what that costs. The mediacontrols entry gains the corollary: on that platform "the user's level" is a constant, and the duck is the one thing that may still move the output. NOTES.md carries the measurements: the per-element budget at 424x439 before and after, the :host([hidden]) specificity trap, the fact that the bar's centring survives the control going away, and what no tier here could check.
This commit is contained in:
@@ -4573,3 +4573,79 @@ fault in it, and it is filed as #172 with the per-element budget. #64
|
||||
umbrella; folding shuffle and repeat back onto the primary row was
|
||||
considered and rejected — it buys 52px, leaves the art at 91px, and
|
||||
costs a third arrangement of the same five buttons.
|
||||
|
||||
## The volume is not ours on Android, and the predicate could not be a width (measured 2026-08-21)
|
||||
|
||||
#64 asked for the in-app volume control to be absent on Android. Its
|
||||
first Finding said `volume-control` "already stands down at narrow
|
||||
widths", which was true of one of its two copies and is why the issue
|
||||
had been read as nearly done. The bar's copy goes by width; the
|
||||
full-screen view's copy was deliberately kept, with a comment saying a
|
||||
slider does belong there.
|
||||
|
||||
**The crux was platform versus width, and three options were on the
|
||||
issue.** What settled it is that the *backend* half of the same issue —
|
||||
pin the level at 1.0 — makes a width rule wrong on the platform the
|
||||
issue is about: an Android tablet at >=600px gets the bottom bar, and
|
||||
the bar's slider would then move a level that is pinned. That is a
|
||||
control that cannot act, which `library-status-indicator` already
|
||||
settled is worse than none. The same rule is wrong the other way below
|
||||
600px, where a narrow desktop window has no hardware keys.
|
||||
|
||||
So the frontend asks the player — `SystemOwnsVolume` — and the answer
|
||||
is right at every width in both mount points. **The predicate is named
|
||||
after the capability rather than the platform**, which is what makes it
|
||||
testable: only `platformOwnsVolume` is behind a build tag, in two files
|
||||
that declare nothing else, and everything else is decided against a
|
||||
field a Go test sets either way. `frontend/test/components/
|
||||
volume-ownership.test.ts` stubs the binding and so exercises the
|
||||
*Android* rendering on an ordinary Linux runner; both of its tests were
|
||||
confirmed to fail on the build before the change.
|
||||
|
||||
**Measured at 424x439, by flipping `platformOwnsVolume` to true in the
|
||||
`!android` file and rebuilding** — the real binding, the real store, the
|
||||
real component, everything except the tag:
|
||||
|
||||
| element | before | after |
|
||||
|---|---|---|
|
||||
| header | 48 | 48 |
|
||||
| **album art** | **39** | **68** |
|
||||
| title / artist / album | 63 | 63 |
|
||||
| transport (seek + controls + volume) | **172** | **143** |
|
||||
| — seek bar | 19 | 19 |
|
||||
| — player-controls | 116 | 116 |
|
||||
| — volume-control | 21 | **0** |
|
||||
|
||||
29px, which is the 21px control plus the 8px flex gap it stops drawing:
|
||||
a gap is only painted between boxes, so `:host([hidden])` costs the
|
||||
transport nothing rather than leaving a hole. That is #172's "~30px of
|
||||
pure gain" confirmed, and the art is 74% larger. It is still the
|
||||
second-smallest thing on the screen, which is #51's evidence.
|
||||
|
||||
Three smaller things worth keeping.
|
||||
|
||||
**`:host([hidden])` has to be written down.** The UA's `[hidden]`
|
||||
rule is `display: none`, but `volume-control`'s own `:host` sets
|
||||
`display: inline-flex` and outranks it — so setting `hidden` alone
|
||||
hides nothing. Same family as the nested-`#queue-button` specificity
|
||||
trap from the session before.
|
||||
|
||||
**Rendering `nothing` and hiding the host are two different
|
||||
assertions**, and the component test makes both: an empty shadow root
|
||||
is what stops a by-role or positional query finding a button that
|
||||
cannot act, and `hidden` is what stops the host occupying space. Either
|
||||
alone passes on a build that gets the other wrong.
|
||||
|
||||
**The bar's centring survives the control going away.** #23's outer
|
||||
columns are the same `min()` expression rather than content-sized, so
|
||||
at 900px with the volume gone the bar's centre, `audio-player`'s centre
|
||||
and `player-controls`' centre are all 450 — checked, because "the
|
||||
transport is centred with a slider bolted to one side" is the fault
|
||||
that rule exists for and removing the slider is the obvious way to
|
||||
re-break it.
|
||||
|
||||
**What no tier here can check**: the constant itself, and ducking
|
||||
against a real audio-focus change. The first is a source sweep
|
||||
(`TestPlatformVolumeOwnershipIsDeclaredOncePerPlatform`), the second is
|
||||
`TestSystemVolumeStillDucks` against the arithmetic. Neither is a
|
||||
device, and no device was attached.
|
||||
|
||||
Reference in New Issue
Block a user