From dd76bd2fa7af4ff712ed3eba28aaa7c6f3bd023a Mon Sep 17 00:00:00 2001 From: Logan Date: Fri, 21 Aug 2026 15:42:15 -0400 Subject: [PATCH] docs(player): record the crop, the reflow, and the audit's null result The audit #51 asks for, at 424x439 on the reference device with a real 1,577-track library: all ten primary views plus the queue. What it did *not* find is worth recording, because it is the promise plan 018 makes -- the shell does not overflow on any view, nothing is stranded outside a scrollable ancestor, and a hit test at each control's centre reaches the control. The width work of #57, #62, #55 and #59 holds; what was left was vertical. What it found is filed rather than fixed here: #186, every control that is not the transport is under the 44px floor, and #187, the seek bar's drag target is 6px. Also the two device traps that cost time despite being written down -- a fresh install downloads the real catalog and the job band then eats 103px of a 439px screen, and it *restarts* after being stopped; and the first-run wizard does not re-check for a library it did not create, so adding one over the bridge leaves it up with a correctly disabled button. Closes #51 --- .planning/NOTES.md | 106 +++++++++++++++++++++++++++++++++++++++++++++ CLAUDE.md | 48 ++++++++++++++++++++ 2 files changed, 154 insertions(+) diff --git a/.planning/NOTES.md b/.planning/NOTES.md index b51bde7..d588e1d 100644 --- a/.planning/NOTES.md +++ b/.planning/NOTES.md @@ -4785,3 +4785,109 @@ broken build. The specs assert the *mechanism* — that the surface is a native `` at phone width — which is the same move `queue-as-a-screen.spec.ts` makes about containment and for the same reason. + +## Now Playing was not drawing a small square, it was drawing a crop (measured 2026-08-21, TLP301 / Chrome 113 / 424x439) + +#172 handed #51 a design question — whether the album art gets a floor +with the block scrolling, or whether the screen reflows below some +height. Measuring it first turned up a defect underneath the question, +and the defect is bigger than the phone. + +**The art was never square.** `aspect-ratio` is specified not to +re-derive the width when `max-height` clamps the height — unlike an +intrinsic ratio, which CSS2.1 10.4 preserves under both bounds. So +`width: min(100%, 60vh)` made the width definite, the ratio derived a +height from it, `max-height: 100%` clipped that height, and the width +stayed where it was. `object-fit: cover` then cropped a square cover +into the band. On the device: **264x53**, a 5:1 strip. #172's own table +called it "39px of art" and the missing half is that those 39px were +263 wide. + +**And it is not only the phone.** The leftover exceeds the width only +above ~843px of viewport, so every height from ~500 to ~843 drew a +crop too — most phones, and any short window. The e2e spec written for +this fails on the old build at 424x439 (263x39), 390x700 (358x315) and +900x500 (300x36), and *passes* at 412x869, which is the boundary +falling exactly where the arithmetic says it should. + +**Both maxes with auto sizes is the whole fix**, and it was chosen by +asking Chrome 113 rather than by reasoning: a probe shadow root at +column heights of 288, 300, 451, 600 and 800 measured four candidate +rules. `max-width/max-height: 100%` with `width/height: auto` is square +at all five; the shipped rule cropped at four; `aspect-ratio` on the +box cropped at the tallest. A corollary that makes it free: `auto` will +not upscale past the natural size, and the largest tier `saveCoverArt` +keeps is 400px, so nothing is lost by never exceeding it. + +**The placeholder cannot use that rule and needed its own**, which is +the part that would have shipped broken. It is not a replaced element, +so with no intrinsic size auto/auto collapses it to its icon — +measured at **13x58**, neither square nor the art's size. Three things +about the rule it did get: + +- It is driven from the **height**, which is the axis that binds + everywhere this view is reached from. +- A flex item's automatic minimum is its content, so without + `min-width: 0` the icon's own width becomes a floor and the box goes + wider than it is tall the moment the row is shorter than the icon — + which is exactly the state a job band puts this screen in. +- **A non-replaced box cannot express "the largest square that fits" at + all**, because whichever max clamps does not re-derive the other. The + height-driven rule alone went **380x484** at 412x869 — a tall phone, + #51's other named device — and `max-height: calc(100vw - 2rem)` is + what closes it. That is sound here for the reason `60vh` was not: this + is a phone-width detail view, so its content box really is the + viewport less the host's own gutters, and it is a *max*, so if that + ever stopped being true the failure is a square bounded early rather + than a crop. `rem` and not `em` — the box sets `font-size: 3rem` for + the icon, so `2em` there is 96px. + +**Then the design question, and the reflow is the answer.** The +stacked layout's budget is fixed — 48px of header, 143px of transport +since #64, 78px of names, 68px of padding and gaps — so the art gets +`height - 386`, which is 53px at 439. A floor on the art scrolls the +transport off the bottom, and "controls never scroll off" is #51's own +Direction and plan 018's promise. So below 500px the art and the names +share a row, where the art is bounded by the row's height rather than +by the column's leftover: **53px to 143px on the device**, measured on +the shipped build, with nothing scrolling and the transport untouched. + +**500 is where the two layouts cross, not a round number.** In a row +the art is `height - 296` and the names get what is left of 392px, so +the names hold 176px at exactly 500 and less above it; stacked, the art +is `height - 386`, which passes 176px at 562. It is keyed on height +alone rather than on the phone's width because it is an answer to +vertical room — a 900x450 window has the same problem and the same fix. + +Two things the audit found that are *not* this, and are filed: +**#186**, every control that is not the transport is under the 44px +floor (the sort direction arrow is 28x21, and `search-trigger` — which +exists only on a phone — is 40x40), and **#187**, the seek bar's drag +target is 6px tall. + +**What the audit did not find is a reachability failure**, which is +worth recording because it is the promise plan 018 makes. At 424x439, +on all ten primary views plus the queue, `documentElement.scrollWidth` +is 424 against a 424 viewport, no control sits outside a scrollable +ancestor, and a hit test at each control's centre reaches the control. +The width work of #57, #62, #55 and #59 holds; what was left was +vertical, and it was this screen. + +## Two traps that cost time on the device, both already written down (2026-08-21) + +Recorded because both are in `android-tier.md` and I met them anyway. + +**A fresh install downloads the real catalog**, so `job-band` is 103px +of a 439px screen and every vertical measurement is wrong. Worse, it +**restarts**: `explore.Service.StopIndexBuild` returns cleanly and the +job is `running` again within seconds, so it has to be stopped again +immediately before a measurement rather than once at the start. +`YJ_CORE_INDEX_URL` is stubbed in `dev-headless.sh` and in CI and is +real on a device. + +**The first-run wizard does not re-check for a library it did not +create.** Adding one through `library.Library.AddLibrary` over the +bridge leaves the wizard up with its "Get Started" button correctly +disabled — it gates on a directory chosen *in the wizard*, and the +existing-library check runs once, on mount. A reload clears it. Nothing +is broken; it cost twenty minutes of believing a tap had been swallowed. diff --git a/CLAUDE.md b/CLAUDE.md index 1f59fd7..d50d04e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2120,6 +2120,54 @@ toggled from `index.ts` would be a second expression of the same fact. The view therefore carries its own queue button, because that button lives in the bar it hides. +**And below 500px of height its art and its names share a row** (#51). +The stacked arrangement's budget is fixed — 48px of header, 143px of +transport since #64, 78px of names, 68px of padding and gaps — so the +art gets `height - 386`, which at the reference device's 424x439 is +**53px**: the one thing a Now Playing screen exists to show, smallest +on it. #172 named the two ways out and this is the second, because the +first — a floor on the art with the block scrolling — scrolls the +transport off the bottom, and *controls never scroll off* is #51's own +Direction and plan 018's promise. Sideways the art is bounded by the +row's height instead of by the column's leftover: **53px to 143px**, +measured on the device, nothing scrolling, the transport untouched. + +Three things about it are load-bearing. + +**500 is where the two layouts cross rather than a round number.** In a +row the art is `height - 296` and the names get what is left of 392px, +so the names hold 176px at exactly 500 and less above it; stacked, the +art is `height - 386`, which passes 176px at 562. Below 500 the row is +the bigger art *and* the readable one — above it the column is, which +is why a tall phone keeps the arrangement it has. It is keyed on height +alone and not on the phone's width, because it answers vertical room: a +900x450 window has the same problem and the same fix. + +**The art was not a small square, it was a crop, and that was never +only the phone.** `aspect-ratio` is specified not to re-derive the +width when `max-height` clamps the height — unlike an intrinsic ratio, +which is preserved under both bounds — so a definite `width: min(100%, +60vh)` kept its width while the height was clipped, and `object-fit: +cover` cropped a square cover into the band: **264x53** on the device. +The leftover exceeds the width only above ~843px of viewport, so every +height from ~500 to ~843 drew one too. `max-width`/`max-height: 100%` +with `width`/`height: auto` is the fix and is the replaced-element +path; it also never upscales past the natural size, and the largest +tier `saveCoverArt` keeps is 400px, so nothing is lost. + +**The placeholder needs its own rule, and a non-replaced box cannot +express this one.** With no intrinsic size, auto/auto collapses it to +its icon (13x58, measured). It is driven from the height instead, with +`min-width: 0` because a flex item's automatic minimum is its content — +without it the icon's width becomes a floor the moment the row is +shorter than the icon, which is exactly what a job band does to this +screen. And since whichever max clamps does not re-derive the other, a +height-driven box goes **380x484** on a tall phone; `max-height: +calc(100vw - 2rem)` closes it, which is sound here for the reason +`60vh` was not — this is a phone-width detail view, so its content box +really is the viewport less the host's gutters, and it is a *max*, so +the failure mode is a square bounded early rather than a crop. + **The playing row is a shape, not a hue.** `track-list` and `queue-panel` draw a `::before` triangle in each row's own left padding, plus `aria-current` — before, both rows were a background tint