feat(player): show progress on the phone's bar border #178

Merged
logan merged 3 commits from feat/58-mini-player-progress-line into main 2026-08-21 15:16:27 +00:00
Collaborator

What the issue was

#58: the phone's mini player says what is playing and offers no sense of
how far through it is. #59 had taken the seek bar off that bar (4px of
height is not a thumb target), and the full-screen Now Playing view is
where seeking went — so the bar had no way to say it at all.

What changed, and why this shape

commit issue
feat(player): show progress on the phone's bar border #58
  • <player-progress-line> is the shell's element, in its own auto
    grid row between bottom-bar and bottom-nav below 600px. The
    Findings ask for exactly this: the two bars are separate components
    stacked in the shell's grid, so a line on the border between them is a
    row of that grid — either one drawing it means reaching into the
    other's box for two pixels. In flow rather than over it, which is
    job-band's rule one row up.
  • It never counts. The fill is transform: scaleX() off the same
    PlaybackPositionChanged the seek bar renders, with the same
    trackChangeId and seq guards and an interval that interpolates
    only between reports and is stopped and restarted by every one of
    them. That rule exists because a local clock drifted 30 s away from
    the backend across four keyboard seeks.
  • It is not a control and cannot become one: aria-hidden on the
    host, pointer-events: none throughout. Now Playing's seek bar is
    what announces the position, and a 2px strip on the top edge of the
    tab bar is exactly where a thumb aiming at a tab lands.
  • It renders nothing above 600px, from matchMedia rather than a
    media query — job-band's pattern, plus one reason of its own: a
    stylesheet cannot stop a 1 Hz interval running for the life of every
    desktop session about a line nobody can see. It also renders nothing
    with no track loaded, so the auto row costs no height.
  • The phone rule is at the foot of index.css, beside job-band's.
    A media query adds no specificity, so a display: block written above
    the display: none that takes the element out of the desktop grid
    loses to it and the line never appears at any width — silently, which
    is the trap the phone section's own header records.

Verification

  • make ui-test992 passed (91 files), including 8 new in
    frontend/test/components/progress-line.test.ts: the matchMedia
    branch, no track, the reported fraction, resume mid-track,
    interpolation reset by every report, a report about a track that is no
    longer loaded, nothing counted while paused, and that it is neither
    announced nor touchable.
  • make e2e --project=chromium against make dev-headless SEED=default
    223 passed, including 3 new in
    e2e/specs/phone-progress-line.spec.ts: the two adjacencies (bar
    bottom → line top, line bottom → nav top) at the reference device's
    424×439, that elementFromPoint on it never resolves to the line, and
    that it is absent on a desktop. WebKit is CI's half — Playwright's
    Linux WebKit links Ubuntu libraries Arch does not provide.
  • Read a screenshot at 424×439 with a real track playing, and
    watched the real transform advance scaleX(0.288889)
    scaleX(0.377778) over 8 s of a 90 s track. Also confirmed the line
    goes with the bar on now-playing (display: none for both).
  • tsc --noEmit in frontend/ and e2e/. No Go changed, so
    make lint / make test were not run and no bindings were
    regenerated.

Deliberately not done

  • No device tier. This is a browser at the device's viewport, which
    CLAUDE.md is explicit is not a phone; the line's appearance on
    Chrome 113 is unverified. What it uses — a transform and a flat
    background — is nowhere near that engine's gaps (no nested bare
    element selector, no popover, no light-dark()).
  • No transition on the fill. The line steps once a second with the
    reports rather than easing between them: a transition would slide
    across the bar after a seek, and smoothing is not what the issue asks
    for.
  • Nothing on the desktop bar, which already has a real, interactive
    seek bar and no tab bar for a line to sit on the border of.

Closes #58

## What the issue was #58: the phone's mini player says what is playing and offers no sense of *how far through it is*. #59 had taken the seek bar off that bar (4px of height is not a thumb target), and the full-screen Now Playing view is where seeking went — so the bar had no way to say it at all. ## What changed, and why this shape | commit | issue | |---|---| | `feat(player): show progress on the phone's bar border` | #58 | - **`<player-progress-line>` is the shell's element**, in its own `auto` grid row between `bottom-bar` and `bottom-nav` below 600px. The Findings ask for exactly this: the two bars are separate components stacked in the shell's grid, so a line on the border between them is a row of that grid — either one drawing it means reaching into the other's box for two pixels. In flow rather than over it, which is `job-band`'s rule one row up. - **It never counts.** The fill is `transform: scaleX()` off the same `PlaybackPositionChanged` the seek bar renders, with the same `trackChangeId` and `seq` guards and an interval that interpolates only *between* reports and is stopped and restarted by every one of them. That rule exists because a local clock drifted 30 s away from the backend across four keyboard seeks. - **It is not a control and cannot become one**: `aria-hidden` on the host, `pointer-events: none` throughout. Now Playing's seek bar is what announces the position, and a 2px strip on the top edge of the tab bar is exactly where a thumb aiming at a tab lands. - **It renders nothing above 600px**, from `matchMedia` rather than a media query — `job-band`'s pattern, plus one reason of its own: a stylesheet cannot stop a 1 Hz interval running for the life of every desktop session about a line nobody can see. It also renders nothing with no track loaded, so the `auto` row costs no height. - **The phone rule is at the foot of `index.css`, beside `job-band`'s.** A media query adds no specificity, so a `display: block` written above the `display: none` that takes the element out of the desktop grid loses to it and the line never appears at any width — silently, which is the trap the phone section's own header records. ## Verification - `make ui-test` — **992 passed** (91 files), including 8 new in `frontend/test/components/progress-line.test.ts`: the `matchMedia` branch, no track, the reported fraction, resume mid-track, interpolation reset by every report, a report about a track that is no longer loaded, nothing counted while paused, and that it is neither announced nor touchable. - `make e2e --project=chromium` against `make dev-headless SEED=default` — **223 passed**, including 3 new in `e2e/specs/phone-progress-line.spec.ts`: the two adjacencies (bar bottom → line top, line bottom → nav top) at the reference device's 424×439, that `elementFromPoint` on it never resolves to the line, and that it is absent on a desktop. WebKit is CI's half — Playwright's Linux WebKit links Ubuntu libraries Arch does not provide. - **Read a screenshot** at 424×439 with a real track playing, and watched the real transform advance `scaleX(0.288889)` → `scaleX(0.377778)` over 8 s of a 90 s track. Also confirmed the line goes with the bar on `now-playing` (`display: none` for both). - `tsc --noEmit` in `frontend/` and `e2e/`. No Go changed, so `make lint` / `make test` were not run and no bindings were regenerated. ## Deliberately not done - **No device tier.** This is a browser at the device's viewport, which `CLAUDE.md` is explicit is not a phone; the line's appearance on Chrome 113 is unverified. What it uses — a transform and a flat background — is nowhere near that engine's gaps (no nested bare element selector, no popover, no `light-dark()`). - **No transition on the fill.** The line steps once a second with the reports rather than easing between them: a transition would slide across the bar after a seek, and smoothing is not what the issue asks for. - **Nothing on the desktop bar**, which already has a real, interactive seek bar and no tab bar for a line to sit on the border of. Closes #58
Author
Collaborator

CI is green — run 476 (https://git.ljones.me/yonlu/yellowjacket/actions/runs/17380).

  • check — success: lint and test across all three build
    configurations, tsc --noEmit, the CSS-literal sweep, the component
    and store suite (992 tests, 8 of them new here), bindings-check,
    skill-check.
  • e2e — success, both engines: "E2E — chromium" and "E2E —
    webkit" each passed, which is the half this machine cannot run (Arch
    does not provide the Ubuntu libraries Playwright's Linux WebKit links).

Nothing in the run was skipped except the two failure-artifact steps.

**CI is green** — run 476 (`https://git.ljones.me/yonlu/yellowjacket/actions/runs/17380`). - `check` — success: lint and test across all three build configurations, `tsc --noEmit`, the CSS-literal sweep, the component and store suite (992 tests, 8 of them new here), `bindings-check`, `skill-check`. - `e2e` — success, **both engines**: "E2E — chromium" and "E2E — webkit" each passed, which is the half this machine cannot run (Arch does not provide the Ubuntu libraries Playwright's Linux WebKit links). Nothing in the run was skipped except the two failure-artifact steps.
logan added 2 commits 2026-08-21 14:38:13 +00:00
#59 took the seek bar off the phone's transport, so the one thing a
mini player is expected to say without being opened -- how far through
the song it is -- had nowhere left to be said.

It is the shell's element and its own 2px grid row between `bottom-bar`
and `bottom-nav`, because those two are separate components and either
one drawing the line means reaching into the other's box. The fill is
`scaleX()` off the same `PlaybackPositionChanged` the seek bar renders,
with the same `trackChangeId`/`seq` guards and an interval that only
interpolates *between* reports -- never its own clock, which is the
rule that exists because a local counter drifted 30 s away from the
backend across four keyboard seeks.

It is `aria-hidden` and takes no pointer events at any depth: Now
Playing's seek bar is what announces the position, and a 2px strip on
the top edge of the tab bar is exactly where a thumb aiming at a tab
lands. It renders nothing above 600px, from `matchMedia` rather than a
media query, because a stylesheet cannot stop a 1 Hz interval running
for the life of every desktop session about a line nobody can see.

Its phone rule is at the foot of index.css beside `job-band`'s, not in
the phone block above: a media query adds no specificity, so a
`display: block` written before the `display: none` that takes it out
of the desktop grid loses to it and the line never appears at all.

Closes #58
docs(player): attribute the phone seek bar's removal correctly
CI / check (push) Skipped
CI / e2e (push) Skipped
CI / check (pull_request) Successful in 2m26s
CI / e2e (pull_request) Canceled after 0s
fba608fdbd
The paragraph said #59 took the seek bar off the phone's transport.
It was plan 016 B2 — audio-player.ts says so in the comment above the
rule that does it, and CLAUDE.md's own #59 paragraph says #59 removed
shuffle, repeat and the queue button. Wrong provenance in the file
whose whole value is being right about which change did what.

Also stop tracking .pi/journal.md. It is a scheduled run's scratch log,
and this repo's memory is CLAUDE.md and .planning/ — a session log
arriving inside a feature PR is a new convention landing sideways.
logan force-pushed feat/58-mini-player-progress-line from 894b16d2f7 to fba608fdbd 2026-08-21 14:38:13 +00:00 Compare
logan added 1 commit 2026-08-21 14:45:37 +00:00
test(player): pin the desktop timer gate, drop a leaked queue
CI / check (push) Skipped
CI / e2e (push) Skipped
CI / check (pull_request) Successful in 2m32s
CI / e2e (pull_request) Successful in 9m14s
deea6ad06d
Two gaps a review found. The this.phone gate on the interpolation interval is what CLAUDE.md says earns the matchMedia call, and every test passed without it — so it is asserted on the timer count now, since a desktop render is empty either way and cannot tell the two apart. Watched failing with the gate removed.

The e2e spec left LONG_TRACK playing in a workers: 1 suite against one long-lived app, immediately before four other phone-* specs. Nine specs clear the queue in afterEach for that reason and phone-transport.spec.ts records the flake it caused.
logan merged commit 42483c4b61 into main 2026-08-21 15:16:27 +00:00
Sign in to join this conversation.