android: the page ends at the clock — the bars go, the hero keeps the top edge

The bar was anonymous until you scrolled, then faded in carrying the title the
page already had in 27px type two centimetres below it. Kyle's rule for what
replaces it: the page is cut off where it meets the Android status bar and
dissolves there, and — the part that decides the whole shape of this —
**backgrounds may pass under the clock, words may not**.

A mask on the scroller cannot tell a photograph from a sentence. It takes both.
So the artist hero is painted twice: the page's own copy inside the masked
scroller, cut away in that band with everything else, and a second copy behind
the scroller (heroart.svelte.js), unmasked, showing through exactly where the
mask took the first one away. Same image, same box, same focal point, same
scale, offset by the same scroll — one picture to the eye, and only the name
dissolves. The second copy carries `overflow: hidden` for the same reason .hero
does: without it the 1.04 scale renders 268px in a 258px box and five pixels of
photograph appear under the hero's foot, below the black the scrim fades to.

That also retires an attempt that did not survive contact. The mask first
stepped aside for the hero on a scroll-driven `--fade-on`, which meant
rebuilding a mask-image on the scroller every frame; measured at 60 Hz median
and p90 either way, but it is work for nothing, and the flip that replaced it
put the artist's name out of existence in one step. With the photograph
protected by being OUTSIDE the mask, neither is needed: the mask is a constant
and nothing recomputes it.

No page has a bar now. A tab page's ⋯ — which is the app's own menu, the queue
and the sleep timer and Settings — moved beside that page's own large title in
`.phead`; Search and You grew the row they did not have, Community kept the one
it had. A pushed page floats a back chevron and a ⋯ over the page instead, on
flat discs with no backdrop blur, because the bar's own note records what three
blurred bands did to a 60 Hz scroll on the Pixel 6a and these would have been a
fourth.

Three numbers now come from one place. `--chrome-d` and `--chrome-gap` are the
disc and its margin; `.screen.has-chrome` reserves exactly that much at the top
of a pushed page, and `--fade-top` reaches exactly the chrome's foot. They were
independent for one build and it showed immediately: the page began underneath
its own buttons, so "Transfers" sat behind the back chevron before you had
scrolled at all, and the fade stopped 17px short of the discs so they stood on
crisp text and a record's floating ⋯ landed on a track row's own ⋯.

The walk followed. Nineteen steps reached a page's overflow through
`.gbar .iconbtn`, which now matches nothing: the four on a tab page go to
`.phead .iconbtn.muted`, the rest to `.fchrome.more`, and the album's back
chevron to `.fchrome.back`. The `title` field read the bar's own text, which is
a worse source than it looks — it only existed when chrome happened to be
drawn. The screen carries its meta as data attributes now and the three probes
read those, spelled exactly as `.gb-t`'s textContent was (the title, then the
kicker when it differs) so every expectation written against it still holds.

Checked on the device rather than argued: no `.gbar` and no `.gb-t` anywhere,
`.phead .iconbtn.muted` present on all four tab pages and opening "SoulStream ·
Queue · Sleep timer · Settings", both `.fchrome` controls present on a record,
and the title probe answering "Random Access MemoriesDaft Punk".

One thing this leaves, and it is a real cost rather than an oversight: a tab
page's ⋯ scrolls away with its header. The bar used to carry it the whole way
down, so on Home the sleep timer now wants a scroll back to the top.
This commit is contained in:
KeelrLeek committed 2026-09-14 15:30:55 -05:00
1 parent cdb861fcbd
commit 1f9b30c51f
6 files changed
+140 -76

No files matched your search

+25 -25
View File
@@ -169,7 +169,7 @@ const PROBE = `(function () {
function inside(b) { return !!b && b.height > 0 && b.top >= -1 && b.bottom <= innerHeight + 1 && b.left >= -1 && b.right <= innerWidth + 1; }
return {
screen: e ? e.className : '',
title: (document.querySelector('.gb-t') || {}).textContent || '',
title: (function () { var q = document.querySelector('.screen'); return q ? (q.dataset.title || '') + (q.dataset.kicker || '') : ''; })(),
mini: !!document.querySelector('.mini'),
sheet: (document.querySelector('.sheet') || {}).className || '',
half: (document.querySelector('.halfsheet') || {}).className || '',
@@ -465,7 +465,7 @@ const PROBE = `(function () {
actsIn: inside(box('.screen .hero-acts') || box('.screen .rec-acts')),
recCols: (function () { var c = box('.screen .rec-cover'), m = box('.screen .rec-meta'); return c && m ? (c.bottom <= m.top + 1 ? 'stacked' : 'beside') : ''; })(),
/* the glass bar above plus the floating bars below, as a share of the screen */
chromePct: (function () { var g = box('.gbar'), low = box('.mini') || box('.nav'); return Math.round((((g ? g.height : 0) + (low ? innerHeight - low.top : 0)) / innerHeight) * 100); })(),
chromePct: (function () { var g = box('.fchrome.back'), low = box('.mini') || box('.nav'); return Math.round((((g ? g.height : 0) + (low ? innerHeight - low.top : 0)) / innerHeight) * 100); })(),
miniLeft: Math.round((box('.mini') || { left: -1 }).left),
/* ── the long session: what the app is still HOLDING ─────────────
Minutes is all a walk has ever been. These are the numbers that
@@ -707,9 +707,9 @@ const MOCK_STEPS = [
{ do: `history.back()`, wait: 900 },
/* the artist's photo: the page's ⋯ → a picture from this phone becomes
the hero, the page is asked again, then the catalog's own come back */
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, name: "artist-more", wait: 700 },
{ do: `document.querySelector('.fchrome.more').click()`, name: "artist-more", wait: 700 },
{ do: `__click('.halfsheet .row', 'Choose hero photo')`, name: "artist-hero-set", wait: 1400 },
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, name: "artist-more-custom", wait: 800 },
{ do: `document.querySelector('.fchrome.more').click()`, name: "artist-more-custom", wait: 800 },
{ do: `__click('.halfsheet .row', 'Remove custom hero')`, name: "artist-hero-cleared", wait: 1400 },
/* an artist whose exhaustive page the mock keeps for four seconds: what
is on screen a second and a half in can only be the two fast lanes */
@@ -769,7 +769,7 @@ const MOCK_STEPS = [
/* the two guest rows the record's own anchor had no count for */
{ name: "cat-album-listeners", wait: 900 },
/* the record page's own sheet from the bar */
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, name: "cat-album-sheet", wait: 700 },
{ do: `document.querySelector('.fchrome.more').click()`, name: "cat-album-sheet", wait: 700 },
{ do: `document.querySelector('.scrim').click()`, wait: 500 },
{ do: `document.querySelectorAll('.tracks .trk')[1].click()`, name: "cat-album-row", wait: 5500 },
{ do: `__ss.next()`, name: "cat-album-next", wait: 7000 },
@@ -833,10 +833,10 @@ const MOCK_STEPS = [
{ do: `(function () { __ssMock.fail('playlist_set_order', ''); __ss.player.error = ''; return true; })()`, wait: 400 },
/* the bulk actions from the page's sheet: Add to library files the
streamed row (its cache is complete); Download all finds it cached too */
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, wait: 600 },
{ do: `document.querySelector('.fchrome.more').click()`, wait: 600 },
{ do: `__click('.halfsheet .row', 'Add to library')`, name: "playlist-bulk-cached", wait: 1500 },
{ do: `__click('.bulkline .bulkx', null)`, wait: 400 },
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, wait: 600 },
{ do: `document.querySelector('.fchrome.more').click()`, wait: 600 },
{ do: `__click('.halfsheet .row', 'Download all tracks')`, name: "playlist-bulk-download", wait: 1500 },
{ do: `__click('.bulkline .bulkx', null)`, wait: 400 },
/* the repair: the unknown row finds a library track and takes its identity */
@@ -844,14 +844,14 @@ const MOCK_STEPS = [
{ do: `__click('.halfsheet .row', 'Find a local match')`, name: "playlist-match", wait: 500 },
{ do: `(function () { var i = document.querySelector('.plmatch input'); i.value = 'caribou track 4'; i.dispatchEvent(new Event('input', { bubbles: true })); })()`, name: "playlist-match-results", wait: 700 },
{ do: `__click('.halfsheet .sheet-rows .row', 'Track 4')`, name: "playlist-matched", wait: 1000 },
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, name: "playlist-more", wait: 700 },
{ do: `document.querySelector('.fchrome.more').click()`, name: "playlist-more", wait: 700 },
{ do: `__click('.halfsheet .row', 'Unpin')`, name: "playlist-unpinned", wait: 900 },
/* the cover: the custom one removed (the mosaic), then a photo chosen */
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, wait: 600 },
{ do: `document.querySelector('.fchrome.more').click()`, wait: 600 },
{ do: `__click('.halfsheet .row', 'Remove custom cover')`, name: "cover-removed", wait: 1000 },
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, wait: 600 },
{ do: `document.querySelector('.fchrome.more').click()`, wait: 600 },
{ do: `__click('.halfsheet .row', 'Choose cover photo')`, name: "cover-set", wait: 1000 },
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, wait: 600 },
{ do: `document.querySelector('.fchrome.more').click()`, wait: 600 },
{ do: `__click('.halfsheet .row', 'Rename')`, name: "playlist-rename", wait: 700 },
{ do: `(function () { var i = document.querySelector('.halfsheet .prompt .field'); i.value = 'Late night drive II'; i.dispatchEvent(new Event('input', { bubbles: true })); })()`, wait: 300 },
{ do: `__click('.halfsheet .prompt-acts .btn-fill', 'Rename')`, name: "playlist-renamed", wait: 1000 },
@@ -865,7 +865,7 @@ const MOCK_STEPS = [
{ do: `__click('.halfsheet .row', 'New Playlist')`, name: "playlist-added", wait: 1200 },
{ do: `__click('.segtabs button', 'Playlists')`, wait: 700 },
{ do: `__click('.plgrid .release', 'New Playlist')`, name: "playlist-filled", wait: 1200 },
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, wait: 600 },
{ do: `document.querySelector('.fchrome.more').click()`, wait: 600 },
{ do: `__click('.halfsheet .row', 'Delete playlist')`, name: "playlist-delete-confirm", wait: 700 },
{ do: `__click('.halfsheet .prompt-acts .btn-fill', 'Delete')`, name: "playlist-deleted", wait: 1200 },
/* the import doors: a pasted tracklist reviewed and created with the
@@ -1209,7 +1209,7 @@ const MOCK_STEPS = [
{ do: `document.querySelectorAll('.nav button')[2].click()`, wait: 800 },
{ do: `__click('.segtabs button', 'Playlists')`, wait: 800 },
{ do: `__click('.plgrid .release', 'Late night drive')`, name: "export-open", wait: 1200 },
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, wait: 700 },
{ do: `document.querySelector('.fchrome.more').click()`, wait: 700 },
{ do: `__click('.halfsheet .row', 'Export')`, name: "export-sheet", wait: 800 },
{ do: `__click('.halfsheet .row', 'CSV')`, name: "export-written", wait: 1000 },
/* a sheet dismissed: the command answers null and the phone says
@@ -1218,13 +1218,13 @@ const MOCK_STEPS = [
than any wait here, and a wait that had to outlast it would be the
fragile kind. */
{ do: `__ssMock.answer('playlist_export', null); __ss.notice.text = ''; true`, wait: 300 },
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, wait: 700 },
{ do: `document.querySelector('.fchrome.more').click()`, wait: 700 },
{ do: `__click('.halfsheet .row', 'Export')`, wait: 700 },
{ do: `__click('.halfsheet .row', 'M3U8')`, name: "export-cancelled", wait: 1000 },
/* and a refusal — what Android answers when the document is somewhere
the app cannot write — read back as a sentence rather than as Rust */
{ do: `__ssMock.forget('playlist_export'); __ssMock.fail('playlist_export', 'permission denied (os error 13)'); true`, wait: 300 },
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, wait: 700 },
{ do: `document.querySelector('.fchrome.more').click()`, wait: 700 },
{ do: `__click('.halfsheet .row', 'Export')`, wait: 700 },
{ do: `__click('.halfsheet .row', 'Backup (JSON)')`, name: "export-refused", wait: 1200 },
{ do: `__ssMock.clear(); __ss.player.error = ''; __ss.lib.error = ''; history.back(); true`, wait: 900 },
@@ -1821,7 +1821,7 @@ const PHONE_STEPS = [
{ do: `document.querySelectorAll('.segtabs button')[0].click()`, wait: 400 },
{ do: `__click('.grid2 .release', ${albumText})`, name: "album", wait: 1400 },
{ do: `document.querySelector('.rec-acts .disc').click()`, name: "album-playing", wait: 3200 },
{ do: `document.querySelector('.gbar .iconbtn').click()`, name: "library-ambience", wait: 1400 },
{ do: `document.querySelector('.fchrome.back').click()`, name: "library-ambience", wait: 1400 },
{ do: `document.querySelectorAll('.nav button')[0].click()`, name: "home-ambience", wait: 1200 },
{ do: `document.querySelector('.mini').click()`, name: "sheet", wait: 1400 },
{ do: `document.querySelector('.np-art').click()`, name: "viewer", wait: 1200 },
@@ -1910,11 +1910,11 @@ const PHONE_STEPS = [
cancel path is what gets walked, and NOTHING is written to this
phone's storage. The playlist is the "Walk test" one the walk itself
makes, and deletes three steps below. */
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, wait: 800 },
{ do: `document.querySelector('.fchrome.more').click()`, wait: 800 },
{ do: `__click('.halfsheet .row', 'Export')`, name: "export-door", wait: 800 },
{ do: `__click('.halfsheet .row', 'M3U8')`, name: "export-picker", wait: 2600 },
{ adb: ["shell", "input", "keyevent", "4"], name: "export-dismissed", wait: 2000 },
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, name: "playlist-more", wait: 800 },
{ do: `document.querySelector('.fchrome.more').click()`, name: "playlist-more", wait: 800 },
{ do: `__click('.halfsheet .row', 'Delete playlist')`, wait: 700 },
{ do: `__click('.halfsheet .prompt-acts .btn-fill', 'Delete')`, name: "playlist-deleted", wait: 1500 },
/* the paste door on the real core: two lines that resolve to a record
@@ -1937,7 +1937,7 @@ const PHONE_STEPS = [
{ do: `__click('.halfsheet .row', 'Find a local match')`, wait: 600 },
{ do: `(function () { var i = document.querySelector('.plmatch input'); i.value = 'aerodynamic'; i.dispatchEvent(new Event('input', { bubbles: true })); })()`, name: "import-match-results", wait: 1200 },
{ do: `__click('.halfsheet .sheet-rows .row', 'Aerodynamic')`, name: "import-matched", wait: 1500 },
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, wait: 800 },
{ do: `document.querySelector('.fchrome.more').click()`, wait: 800 },
{ do: `__click('.halfsheet .row', 'Delete playlist')`, wait: 700 },
{ do: `__click('.halfsheet .prompt-acts .btn-fill', 'Delete')`, name: "import-deleted", wait: 1500 },
/* the pasted-links door on the real core: one track link, and the wait
@@ -2009,7 +2009,7 @@ const PHONE_STEPS = [
{ name: "artist", wait: 4500 },
/* the page's ⋯ on the real core: the hero door is there and knows the
artist's id — it opens the system picker, so the walk only looks */
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, name: "artist-more", wait: 900 },
{ do: `document.querySelector('.fchrome.more').click()`, name: "artist-more", wait: 900 },
{ do: `document.querySelector('.scrim').click()`, wait: 600 },
/* a discography card the library does NOT hold opens the catalog record */
{ do: `(function () { var c = [].slice.call(document.querySelectorAll('.grid2 .release')); if (!c.length) throw new Error('no discography grid'); var pick = c.find(function (x) { return x.textContent.indexOf('in your library') < 0; }) || c[c.length - 1]; pick.click(); })()`, name: "catalog-album", wait: 3500 },
@@ -2040,7 +2040,7 @@ const PHONE_STEPS = [
{ do: `__click('.screen button.row', null)`, wait: 500 },
{ wait: 2500 },
{ do: `__click('.sec .all', 'All')`, name: "dg-page", wait: 1600 },
{ do: `document.querySelectorAll('.gbar .iconbtn')[1].click()`, name: "dg-sheet", wait: 1000 },
{ do: `document.querySelector('.fchrome.more').click()`, name: "dg-sheet", wait: 1000 },
{ do: `__click('.halfsheet .chip', 'A–Z')`, name: "dg-az", wait: 1000 },
{ do: `document.querySelector('.scrim').click()`, wait: 700 },
{ do: `history.back()`, wait: 800 },
@@ -3043,7 +3043,7 @@ async function main() {
/* the chrome that is actually on screen: a mini bar under a raised
sheet and a nav under one are translated away on purpose */
var out = [];
['.gbar', '.screen', '.mini:not(.hidden)', '.nav:not(.gone)'].forEach(function (sel) {
['.fchrome.back', '.screen', '.mini:not(.hidden)', '.nav:not(.gone)'].forEach(function (sel) {
var b = box(sel);
if (b && b.height > 0 && !inside(b)) out.push(sel);
});
@@ -3076,7 +3076,7 @@ async function main() {
and a check of the left alone would pass by the inset being 0 */
miniLeft: Math.round((box('.mini') || { left: -1 }).left),
miniRight: Math.round(innerWidth - (box('.mini') || { right: innerWidth + 1 }).right),
chromePct: (function () { var g = box('.gbar'), low = box('.mini') || box('.nav'); return Math.round((((g ? g.height : 0) + (low ? innerHeight - low.top : 0)) / innerHeight) * 100); })(),
chromePct: (function () { var g = box('.fchrome.back'), low = box('.mini') || box('.nav'); return Math.round((((g ? g.height : 0) + (low ? innerHeight - low.top : 0)) / innerHeight) * 100); })(),
outside: out.join('|'),
sheet: !!document.querySelector('.sheet.up'),
npOut: [].filter.call(document.querySelectorAll('.sheet.up .np-frame, .sheet.up .np-meta, .sheet.up .np-scrub, .sheet.up .np-transport, .sheet.up .np-foot'), function (x) { var b = x.getBoundingClientRect(); return b.height > 0 && !inside(b); }).length,
@@ -3322,7 +3322,7 @@ async function main() {
if (resOk) {
/* the hero's page: an artist's (tabs, Popular) or a record's */
const HERO = `(function () { var e = document.querySelector('.screen'); return { screen: e ? e.className : '', title: (document.querySelector('.gb-t') || {}).textContent || '', tabs: document.querySelectorAll('.disco-tabs button').length, pop: document.querySelectorAll('.row.pop').length, socs: document.querySelectorAll('.stats .soc').length, top: (document.querySelector('.top .top-name') || {}).textContent || '' }; })()`;
const HERO = `(function () { var e = document.querySelector('.screen'); return { screen: e ? e.className : '', title: (function () { var q = document.querySelector('.screen'); return q ? (q.dataset.title || '') + (q.dataset.kicker || '') : ''; })(), tabs: document.querySelectorAll('.disco-tabs button').length, pop: document.querySelectorAll('.row.pop').length, socs: document.querySelectorAll('.stats .soc').length, top: (document.querySelector('.top .top-name') || {}).textContent || '' }; })()`;
const topName = (await cdp.evaluate(HERO)).top;
await cdp.evaluate(HELPERS);
await cdp.evaluate(`__click('.top .btn-ghost', null)`);
@@ -3384,7 +3384,7 @@ async function main() {
await sleep(700);
await cdp.evaluate(HELPERS);
await cdp.evaluate(`__click('.halfsheet .row', 'Browse their files')`);
const BRW = `(function () { return { title: (document.querySelector('.gb-t') || {}).textContent || '', dirs: document.querySelectorAll('.brw-dir').length, busy: !!document.querySelector('.imp-wait'), err: [].some.call(document.querySelectorAll('.screen .stub'), function (x) { return /shares/.test(x.textContent); }), head: (document.querySelector('.brw-head .m') || {}).textContent || '' }; })()`;
const BRW = `(function () { return { title: (function () { var q = document.querySelector('.screen'); return q ? (q.dataset.title || '') + (q.dataset.kicker || '') : ''; })(), dirs: document.querySelectorAll('.brw-dir').length, busy: !!document.querySelector('.imp-wait'), err: [].some.call(document.querySelectorAll('.screen .stub'), function (x) { return /shares/.test(x.textContent); }), head: (document.querySelector('.brw-head .m') || {}).textContent || '' }; })()`;
let brw = null;
for (let i = 0; i < 40; i++) { await sleep(500); brw = await cdp.evaluate(BRW); if (brw.dirs > 0 || brw.err) break; }
await still(path.join(OUT, `${prefix}net-browse.png`));