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.
73 lines
3.8 KiB
Plaintext
73 lines
3.8 KiB
Plaintext
/* ── how much of this the phone may hold ────────────────────────────────
|
|
The desktop bounds the identical map with these two numbers
|
|
(src/lib/state.svelte.js: ART_THUMB_CACHE_MAX, ART_THUMB_CACHE_MAX_CHARS,
|
|
trimmed in insertion order by trimResolvedCache). Kept exactly as they
|
|
are rather than invented here: `album_art` answers with base64 JPEG of a
|
|
360px thumbnail on both, so 512 covers is the same budget either side.
|
|
Until now nothing on the phone gave a byte of it back — one browse of a
|
|
114-cover library retains every one of them for the WebView's lifetime,
|
|
and a catalog crawl adds to the same map without limit. */
|
|
const ART_MAX = 512;
|
|
const ART_MAX_CHARS = 16 * 1024 * 1024;
|
|
|
|
/* The desktop may drop any cold entry, because every caller there asks
|
|
through getArt(path). The phone's templates INDEX this map — art[path] in
|
|
Cover, Release, Sheet, Viewer, Artist, Discography, Home and the media
|
|
card — so a key something is painting would blank on the spot, and
|
|
Cover's `visible` action re-arms only when its path PROP changes, so an
|
|
evicted cover that is still mounted would never come back at all. Hence
|
|
two rules the desktop does not need: never the entry that has just
|
|
landed, and never one that a connected <img> is showing. Oldest first
|
|
otherwise — Object.keys is insertion order here, because every key is a
|
|
file path, an https URL or a "stream:" string and never integer-like,
|
|
which would be hoisted to the front and go first. A remembered failure
|
|
("" so it is not retried) is evictable like anything else: past the cap
|
|
a retry is the cheaper of the two.
|
|
|
|
The map is allowed to sit OVER its cap for as long as one screen needs
|
|
all of it — a mounted grid of 600 covers loses none of them — and it
|
|
comes down the moment that screen goes.
|
|
|
|
Called from pump() and nowhere else, which is never inside an $effect:
|
|
Object.keys on a $state proxy reads the version signal, so an effect
|
|
that called this would re-run on every cover that lands. The two
|
|
writers that do not go through pump() — loadArt's data: short-circuit
|
|
and a stream's tag art in player.svelte.js — are therefore bounded at
|
|
the next cover that does, which on any screen worth the name is the
|
|
next one. Under the cap
|
|
it is a key count and a char sum and returns; the pass over the screen's
|
|
images is built once, lazily, and only when there is something to drop,
|
|
because reading a base64 src is a copy of the whole data URL. */
|
|
function trimArt(justAdded) {
|
|
const keys = Object.keys(art);
|
|
let chars = 0;
|
|
for (const k of keys) { const v = art[k]; if (typeof v === "string") chars += v.length; }
|
|
let live = keys.length;
|
|
if (live <= ART_MAX && chars <= ART_MAX_CHARS) return;
|
|
let painted = null;
|
|
for (const k of keys) {
|
|
if (live <= ART_MAX && chars <= ART_MAX_CHARS) break;
|
|
if (k === justAdded) continue;
|
|
const v = art[k];
|
|
if (v) {
|
|
if (!painted) {
|
|
painted = new Set();
|
|
/* the attribute, not the .src property: for a data URL they
|
|
are the same string and the property re-serializes it.
|
|
The fallback is for an <img> whose src was set as a
|
|
property instead — none today, and a miss here is the one
|
|
way this could blank a cover. */
|
|
for (const img of document.images) {
|
|
if (!img.isConnected) continue;
|
|
const src = img.getAttribute("src") || img.src;
|
|
if (src) painted.add(src);
|
|
}
|
|
}
|
|
if (painted.has(v)) continue;
|
|
}
|
|
delete art[k];
|
|
if (typeof v === "string") chars -= v.length;
|
|
live--;
|
|
}
|
|
}
|