android: the handoff — the pending rounds, the pipeline, and what is unverified

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.
This commit is contained in:
KeelrLeek committed 2026-09-14 07:57:41 -05:00
1 parent 867fd42972
commit 9f0601027c
400 files changed
+26401 -3

No files matched your search

+51
View File
@@ -0,0 +1,51 @@
/** The rows a record's own artist anchor found no count for — a feature's
* guest, a compilation's stranger — asked by each row's OWN Deezer artist
* id, the rule the desktop's search results follow. A row the backend has
* no count for keeps nothing: unknown is not a zero. */
export function loadTrackListeners(albumId, tracks) {
if (albumId == null || catalog.listeners[albumId]) return;
const want = (tracks || []).filter((t) => t.listeners == null && t.artist_id != null);
if (!want.length) return;
catalog.listeners[albumId] = { status: "loading", counts: {} };
api.catalogTrackListeners(want.map((t) => ({ artistId: t.artist_id, title: t.title }))).then(
(counts) => {
const out = {};
(counts || []).forEach((n, i) => { if (n != null && want[i]) out[want[i].id] = n; });
catalog.listeners[albumId] = { status: "ready", counts: out };
},
() => { catalog.listeners[albumId] = { status: "error", counts: {} }; },
);
}
/** Forget an artist's page and ask for it again — the desktop's
* `invalidateCatalogArtist`, for a hero that has just changed. The seed is
* kept, so the identity search does not run a second time. */
export function forgetArtist(name) {
const k = artistKey(name);
const cur = catalog.artists[k];
if (!cur) return;
delete catalog.artists[k];
lookupArtist(name, cur.seed ?? null);
}
/** A photo from this phone as an artist's hero — the desktop's Choose hero,
* through the phone's own image picker. The page is asked again, so the
* banner, the ground and the accent all follow. */
export async function heroFromPhoto(id, name) {
let r;
try { r = await api.pickImage(); } catch (e) { if (/cancelled/.test(String(e))) return false; throw e; }
if (!r?.path) return false;
const hero = await api.setArtistHero(id, r.path);
forgetArtist(name);
note(`Hero set · ${hero.width}×${hero.height}`);
return true;
}
/** The chosen hero removed — the catalog's own photos come back. */
export async function heroClear(id, name) {
const removed = await api.clearArtistHero(id);
forgetArtist(name);
note(removed ? "The catalog's own photos are back" : "No custom hero here");
}
export function lookupAlbum(id) {