Everything written this session that has not been through the device: rounds 43 to 48 (each patch adversarially verified, 43's transient-notice expectation fixed), a partial round 49, the pipeline scripts, the Android Auto design and the icon comparison mock. Also the two harness changes that were never A/B'd — adaptive waits, which may only shorten a declared wait and never lengthen it, and a ship that runs the mock walk and the deploy at the same time. The A/B and its 255-step baseline are in handoff/tools; main stays at what the phone has actually walked.
114 lines
8.7 KiB
Plaintext
114 lines
8.7 KiB
Plaintext
/* ── round forty: the four steps that could not fail ───────────────
|
|
`library-ambience`, `queue`, `lyrics` and `community` have been
|
|
named phone steps with no key in this map since the rounds that
|
|
wrote them. The driver reads `expect[s.name] ? expect[s.name](info)
|
|
: true`, so each one was a screenshot that always passed — four of
|
|
eighty-eight. All four have a mock expectation; none had a phone
|
|
one. Each is written to hold against Kyle's own library rather than
|
|
a fixture. */
|
|
"library-ambience": (i) => i.title.startsWith("Your library") && i.amb.includes("on:img"),
|
|
queue: (i) => i.sheet.includes("up") && i.qFace,
|
|
/* the walked track may genuinely have no words on the device, so this
|
|
asks for the face and any one of its three honest states */
|
|
lyrics: (i) => i.sheet.includes("up") && (i.lyrLines >= 1 || !!i.lyrNote || i.lyrTools),
|
|
/* tolerant of the sign-in card, which is what a signed-out phone shows
|
|
here — but NOT of a toast: a sign-in that failed raises one on this
|
|
screen, and that is a finding rather than a step to loosen */
|
|
community: (i) => i.title.startsWith("Community") && !i.toast,
|
|
/* ── the walk's own playlist, on real SQLite ─────────────────────
|
|
The row count is stashed by the sheet step rather than assumed: the
|
|
playlist is made out of whichever record the walk picked, so its
|
|
length is his library's business — and a probe that read nothing
|
|
would otherwise pass every clause below it. */
|
|
"playlist-row-sheet": (i) => { globalThis.__plN = i.plrows; return i.half.includes("up") && /Remove from playlist/.test(i.halfText); },
|
|
"playlist-removed": (i) => i.plrows === globalThis.__plN - 1 && /Removed/.test(i.notice) && /Undo/.test(i.announce),
|
|
/* the undo stash round-tripped through the real backend for the first
|
|
time: the row is back, in the place it was taken from. `__pl0` is
|
|
the order `playlist-drag-arm` stashed before the two drags, which
|
|
`playlist-dragged-back` has already put back. */
|
|
"playlist-undone": (i) => i.plrows === globalThis.__plN && !i.notice && (globalThis.__plN < 2 || i.plOrder === globalThis.__pl0),
|
|
/* CONDITIONAL: `lift()` refuses a list of fewer than two rows, so a
|
|
one-track record — which makes a one-row playlist — has no press to
|
|
release. The two drag steps above tolerate exactly the same thing. */
|
|
"playlist-drag-held": (i) => globalThis.__plN < 2 || (i.half.includes("up") && /Remove from playlist/.test(i.halfText)),
|
|
/* CONDITIONAL in its last clause: the drag-refusal note needs more
|
|
than one row to drag. The note itself hangs off the UNFILTERED
|
|
list, so a filter that matched nothing still shows it. */
|
|
"playlist-filter": (i) => i.plrows === 0 && /Nothing here matches/.test(i.stubText) && (globalThis.__plN < 2 || /Clear the search/.test(i.plNote)),
|
|
"playlist-filter-cleared": (i) => i.plrows === globalThis.__plN && !i.plNote,
|
|
/* ── the review's Keep switch ────────────────────────────────────
|
|
CONDITIONAL: the row renders only when something did not resolve,
|
|
and a paste that resolved completely leaves `keepOn` false anyway.
|
|
The second step is what guarantees `import-created` below still
|
|
sees the three rows it has always seen. */
|
|
"import-keep-off": (i) => !i.keepOn && /Create playlist \(\d+ tracks?\)/.test(i.impBtn),
|
|
"import-keep-on": (i) => /Create playlist \(3 tracks\)/.test(i.impBtn) && i.imp === 3,
|
|
/* ── the library's own shape, on the device for the first time ──── */
|
|
/* `a11yBad` is left out for the reason `track-sheet` leaves it out: a
|
|
card's name is [title, artist, badge] off his own tags, and `junk()`
|
|
calls a name of nothing but digits bad — "1999, 311" is a real
|
|
record. The mock asserts it against a fixture, where it belongs.
|
|
The structural three stay: nameless, ghost and mis-stated. */
|
|
library: (i) => i.title.startsWith("Your library") && i.fmtBadges >= 1 && /hours|min/.test(i.countline)
|
|
&& !i.a11yAnon && !i.a11yGhost && !i.a11yState,
|
|
/* the sort lives on the Library entry (`c.sort`), not in the config,
|
|
so it needs no putting back — the chip after it does that anyway.
|
|
What a real library's top format IS cannot be asserted; that the
|
|
grid re-sorted and still badges every card can be. */
|
|
"library-by-format": (i) => !!i.firstBadge && i.fmtBadges >= 1 && !i.toast && !i.a11yState,
|
|
artists: (i) => i.title.startsWith("Your library") && i.rowArt >= 1 && !i.toast,
|
|
/* CONDITIONAL: follows live in the library folder and the phone's own
|
|
folder starts without any, so a phone with no follows can only be
|
|
asked for the stub */
|
|
gaps: (i) => i.title.startsWith("Your library") && !i.toast && (i.gaplines >= 1 || !!i.stubText),
|
|
/* a LOCAL track's sheet: the three things it offers, the file block
|
|
underneath it off a real path, and the layer named and holding
|
|
focus. `a11yBad` is left out deliberately — it reads the names of
|
|
real rows, and a record of his is allowed to be called anything. */
|
|
"track-sheet": (i) => i.half.includes("up") && /Play next/.test(i.halfText) && /Add to queue/.test(i.halfText)
|
|
&& /Add to playlist/.test(i.halfText) && /Format/.test(i.halfText) && /Folder/.test(i.halfText)
|
|
&& /^dialog\/true\/.+\/focus$/.test(i.dlg) && !i.a11yAnon && !i.a11yGhost,
|
|
/* the recents are localStorage on the device, written by the walk's
|
|
own earlier search when that screen was left */
|
|
"search-recent": (i) => i.title.startsWith("Search") && i.sq === "" && i.recents >= 1,
|
|
"search-recent-pick": (i) => i.title.startsWith("Search") && !!i.sq,
|
|
"queue-face": (i) => i.sheet.includes("up") && i.qFace,
|
|
/* CONDITIONAL: the walked track can be the last queueable row, and
|
|
then there is nothing to press. Asserted exactly when there is. */
|
|
"queue-row-sheet": (i) => !i.half.includes("up") || (/Remove from queue/.test(i.halfText) && /Play now/.test(i.halfText)),
|
|
/* Home's feed on a phone whose library folder holds no follows: the
|
|
store answered, and not one of its slots came back with an error */
|
|
"home-feed": (i) => /^(Good |Still)/.test(i.title) && !i.toast && i.homeSlots >= 0 && i.homeErr === 0,
|
|
"home-more": (i) => i.half.includes("up") && /Sleep timer/.test(i.halfText),
|
|
"sleep-sheet": (i) => i.half.includes("up") && /Pause after/.test(i.halfText) && /1½ hours/.test(i.halfText),
|
|
/* every chip in that sheet goes through `act()`, which closes it — so
|
|
this says nothing at all about the timer. The step after it does. */
|
|
"sleep-set": (i) => !i.half.includes("up") && /^(Good |Still)/.test(i.title),
|
|
/* the proof: the More sheet's Sleep-timer subtitle IS `sleepLabel()`,
|
|
and the only thing that can make it read "N min left" is a
|
|
`player.sleepAt` in the future */
|
|
"sleep-armed": (i) => i.half.includes("up") && /min left/.test(i.halfText),
|
|
"sleep-off": (i) => !i.half.includes("up") && /^(Good |Still)/.test(i.title),
|
|
"sleep-cleared": (i) => i.half.includes("up") && /Sleep timer/.test(i.halfText) && !/min left/.test(i.halfText),
|
|
/* the retention chip: read, moved, put back. The harness's stash and
|
|
the page's own (`window.__keep0`) hold the same value for the same
|
|
reason the drag steps keep two — a step's script is fixed before
|
|
the walk starts and cannot carry this one. */
|
|
"you-keep-arm": (i) => { globalThis.__keepWas = i.keep; return !!i.keep; },
|
|
/* CONDITIONAL: the step moves nothing when "Forever" is the chip that
|
|
is on, because the way back writes the flag and not the number. The
|
|
label is what says which case this is. */
|
|
"you-keep": (i) => !!i.keep && (/^Forever/.test(globalThis.__keepWas) ? i.keep === globalThis.__keepWas : i.keep !== globalThis.__keepWas),
|
|
"you-keep-back": (i) => i.keep === globalThis.__keepWas,
|
|
/* the Wi-Fi-only guard, which exists only because this is a phone */
|
|
"wifi-arm": (i) => { globalThis.__wifiWas = i.wifiOnly; return !!i.wifiOnly; },
|
|
"wifi-on": (i) => i.wifiOnly === "on",
|
|
"wifi-refused": (i) => /Wi-Fi/.test(i.toast),
|
|
/* the shadow removed and the sentence taken off the bar, with the
|
|
switch still where the step above put it */
|
|
"wifi-cleared": (i) => !i.toast && i.wifiOnly === "on",
|
|
"wifi-restored": (i) => i.wifiOnly === globalThis.__wifiWas,
|
|
/* a port the core refuses: nothing written, and the number the walk
|
|
already left standing still standing */
|
|
"you-port-refused": (i) => !i.half.includes("up") && /between 1024 and 65535/.test(i.toast) && /^50300 /.test(i.port),
|