Centre the transport, and show the volume inline #155

Merged
logan merged 1 commits from feat/42-inline-volume-and-centred-transport into main 2026-08-20 05:22:04 +00:00
Collaborator

Two issues over one bar, because they are one relayout — #42's own
findings say giving wa-slider a label grows it 6px → 14px and moves
the transport
, which is #23's subject.

Commits

commit what issues
7c3937e the three-column bar, the inline volume + its setting, the specs, CLAUDE.md and two NOTES.md entries #23, #42

#23 — centred means centred in the window

The bar was 320px 1fr auto, so the transport sat in the middle of what
the metadata and the queue button did not use: its centre was ~140px
right of the window's
, at every width. The outer two tracks are the
same expression now, so the middle is centred by construction rather
than by arithmetic that has to be redone whenever a control joins the
bar. Measured offset is 0 at 800, 900, 1100 and 1440, and at 200% text.

The cap, which was a regression before it was a decision

Making both sides --now-playing-width is perfectly centred and costs
the control you drag:

layout seek track @800px @200% text
320px 1fr auto (before) 257 130
both sides --now-playing-width 61 0
both sides min(…, 25%) (shipped) 246 124

Centring is free at 1440 and expensive at 800, so a change checked only
at a comfortable width looks perfect. The right-hand group needs ~141px
and was being handed 320 to keep the arithmetic symmetric.

It is a min() rather than a breakpoint because --now-playing-width
is user state — the metadata panel has a drag handle — which is the
same reasoning the queue panel's overlay mode uses. Tying both sides to
that variable is also what keeps dragging meaningful: a plain
1fr … 1fr centres just as well and silently makes the handle a no-op.

#42 — the slider is simply there

Click open, drag, click closed is three gestures for a control the bar
has room to show. The volume moved out of audio-player into the bar,
because the transport column has to hold the transport and nothing else,
and it joins the queue button in one cell rather than a second
column, since the centring compares columns.

The popup survives as a setting. The stored flag names the popup,
which is backend/config's polarity rule — the zero value has to be the
intended answer, so an existing config.toml gets the new default with
no migration. Verified live in both directions, without a restart.

Inline, the icon becomes the mute toggle and is named after that
action rather than after the state, because with the slider beside it
there is nothing left to disclose. The component tier now covers both
presentations rather than whichever one is default — and drives the
switch through GeneralConfigChanged, the store's own refresh trigger,
rather than a test-only reset.

It stands down below 600px whatever the setting says: that is about the
platform rather than preference, and is why mediacontrols' Android
handler implements no volume callback.

A phone-only bug fixed in passing

Three nested rules in this block began with a bare element selector,
which Chrome 120 relaxed and the phone's Chrome 113 silently drops
including the text-overflow: ellipsis on the bar's own title and
artist, which has therefore never truncated on the device. Same class as
the now-playing marquee whose ellipsis sat on the wrong box. They are
&-prefixed now; #154 carries the class and a proposal to extend
make css-check, since no tier can see this.

Verification

e2e/specs/bottom-bar.spec.ts pins the two halves separately on
purpose
: an uncapped build is perfectly centred and fails only the
seek-bar assertion, so a spec asserting centring alone would have passed
the regression above. Both mutation-tested:

mutation caught by
the old 320px 1fr auto all four centring tests (and the seek-bar one)
side tracks uncapped the seek-bar test only
  • make lint — 0 issues, three configurations
  • make test — green, three passes
  • make ui-test — 949 tests
  • make e2e — 186 passed (was 178), twice against one app
  • make css-check, make bindings-check, make skill-check
  • npx tsc --noEmit in both packages
  • Screenshots read at 1440, 900 and 800×200% text; the setting
    toggled live in both directions

Two existing specs changed, both because the change earned it:
control-names no longer opens a popup to reach the slider (the name
assertion is unchanged; the route got shorter), and phone-shell's
audio-player volume-control locator would have kept passing after
the move — toBeHidden() is satisfied by an element that does not
exist — so it now also asserts the element is there.

Closes #23
Closes #42

Two issues over one bar, because they are one relayout — #42's own findings say giving `wa-slider` a label grows it 6px → 14px and **moves the transport**, which is #23's subject. ## Commits | commit | what | issues | |---|---|---| | `7c3937e` | the three-column bar, the inline volume + its setting, the specs, `CLAUDE.md` and two `NOTES.md` entries | #23, #42 | ## #23 — centred means centred in the window The bar was `320px 1fr auto`, so the transport sat in the middle of what the metadata and the queue button did not use: **its centre was ~140px right of the window's**, at every width. The outer two tracks are the same expression now, so the middle is centred by construction rather than by arithmetic that has to be redone whenever a control joins the bar. Measured offset is 0 at 800, 900, 1100 and 1440, and at 200% text. ## The cap, which was a regression before it was a decision Making both sides `--now-playing-width` is *perfectly centred* and costs the control you drag: | layout | seek track @800px | @200% text | |---|---|---| | `320px 1fr auto` (before) | 257 | 130 | | both sides `--now-playing-width` | **61** | **0** | | both sides `min(…, 25%)` (shipped) | 246 | 124 | Centring is free at 1440 and expensive at 800, so a change checked only at a comfortable width looks perfect. The right-hand group needs ~141px and was being handed 320 to keep the arithmetic symmetric. It is a `min()` rather than a breakpoint because `--now-playing-width` is **user state** — the metadata panel has a drag handle — which is the same reasoning the queue panel's overlay mode uses. Tying both sides to that variable is also what keeps dragging meaningful: a plain `1fr … 1fr` centres just as well and silently makes the handle a no-op. ## #42 — the slider is simply there Click open, drag, click closed is three gestures for a control the bar has room to show. The volume moved out of `audio-player` into the bar, because the transport column has to hold the transport and nothing else, and it joins the queue button in **one cell** rather than a second column, since the centring compares columns. The popup survives as a setting. The stored flag names the **popup**, which is `backend/config`'s polarity rule — the zero value has to be the intended answer, so an existing `config.toml` gets the new default with no migration. Verified live in both directions, without a restart. Inline, the icon becomes the **mute toggle** and is named after that action rather than after the state, because with the slider beside it there is nothing left to disclose. The component tier now covers both presentations rather than whichever one is default — and drives the switch through `GeneralConfigChanged`, the store's own refresh trigger, rather than a test-only reset. It stands down below 600px whatever the setting says: that is about the platform rather than preference, and is why `mediacontrols`' Android handler implements no volume callback. ## A phone-only bug fixed in passing Three nested rules in this block began with a bare element selector, which Chrome 120 relaxed and **the phone's Chrome 113 silently drops** — including the `text-overflow: ellipsis` on the bar's own title and artist, which has therefore never truncated on the device. Same class as the `now-playing` marquee whose ellipsis sat on the wrong box. They are `&`-prefixed now; **#154** carries the class and a proposal to extend `make css-check`, since no tier can see this. ## Verification `e2e/specs/bottom-bar.spec.ts` pins the two halves **separately on purpose**: an uncapped build is perfectly centred and fails only the seek-bar assertion, so a spec asserting centring alone would have passed the regression above. Both mutation-tested: | mutation | caught by | |---|---| | the old `320px 1fr auto` | all four centring tests (and the seek-bar one) | | side tracks uncapped | the seek-bar test **only** | - `make lint` — 0 issues, three configurations - `make test` — green, three passes - `make ui-test` — 949 tests - `make e2e` — 186 passed (was 178), **twice** against one app - `make css-check`, `make bindings-check`, `make skill-check` - `npx tsc --noEmit` in both packages - **Screenshots read** at 1440, 900 and 800×200% text; the setting toggled live in both directions Two existing specs changed, both because the change earned it: `control-names` no longer opens a popup to reach the slider (the name assertion is unchanged; the route got shorter), and `phone-shell`'s `audio-player volume-control` locator would have **kept passing** after the move — `toBeHidden()` is satisfied by an element that does not exist — so it now also asserts the element is there. Closes #23 Closes #42
logan added 1 commit 2026-08-20 04:42:54 +00:00
feat(player): centre the transport and show the volume inline
CI / check (push) Skipped
CI / e2e (push) Skipped
CI / check (pull_request) Successful in 2m32s
CI / e2e (pull_request) Successful in 8m18s
dc8db159f9
Two issues over one bar, because they are one relayout. #42's own
findings say so: giving wa-slider a label grows it 6px to 14px and
moves the transport, which is #23's subject, so doing them in sequence
means measuring the bar twice and throwing the first set away.

The bar was `320px 1fr auto`, so the transport sat in the middle of
what the metadata and the queue button did not use — its centre was
~140px right of the window's at every width. The outer two tracks are
the same expression now, so the middle is centred by construction.

The side width is the metadata's, capped at a quarter of the bar, and
the cap was measured as a regression before it was a decision:
reserving the full `--now-playing-width` on both sides is perfectly
centred and takes the seek bar's track from 257px to 61px at 800px, and
to 0 at 200% text. The control you drag was paying for the symmetry.
With the cap it is 246, which is parity. It is a `min()` rather than a
breakpoint because that variable is user state — the metadata has a
drag handle — and tying both sides to it is also what keeps dragging
meaningful; a plain `1fr … 1fr` centres just as well and silently makes
the handle a no-op.

The volume moved out of `audio-player` into the bar because the
transport column has to hold the transport and nothing else, and it
joins the queue button in one cell rather than a second column, since
the centring compares columns.

It is a slider by default and a popup by setting. The stored flag names
the *popup*, which is this config's polarity rule — the zero value has
to be the intended answer, so an existing config.toml gets the new
default with no migration. Inline, the icon is the mute toggle and is
named after that action rather than the state, because with the slider
beside it there is nothing to disclose; the component tier now covers
both presentations rather than whichever is default.

Three nested rules in this block began with a bare element selector,
which Chrome 120 relaxed and the phone's Chrome 113 **silently drops** —
including the ellipsis on the bar's own title and artist, which has
therefore never truncated on the device. They are `&`-prefixed now.
Filed as #154 for the class and for a check.

`bottom-bar.spec.ts` pins both halves separately on purpose: an
uncapped build is perfectly centred and fails only the seek-bar width,
so a spec asserting centring alone would have passed the regression
above. Both were verified by mutation.

Closes #23
Closes #42
logan force-pushed feat/42-inline-volume-and-centred-transport from 7c3937ec70 to dc8db159f9 2026-08-20 04:42:54 +00:00 Compare
logan merged commit dc6625d33a into main 2026-08-20 05:22:04 +00:00
Sign in to join this conversation.