Files
SoulStream/handoff/rounds/patch-round41.cjs
KeelrLeek 9f0601027c 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.
2026-09-14 07:57:41 -05:00

141 lines
8.2 KiB
JavaScript

// Round forty-one: the long session. Every walk this project has ever run is
// minutes long; a phone runs the app for hours, and nothing in the port had
// been looked at for a leak. Driving a real build of this tree for eleven
// laps says the listeners and the intervals are dead flat — the naive
// add-minus-remove counter climbs twelve a lap and every one of them is a
// `.screen` element {#key nav.seq} threw away — and that exactly two things
// grow without a limit: the thumbnail map, which kept every cover it had
// ever fetched for the WebView's lifetime, and the playlist cover table,
// which strands one data URL per edit under a key nothing can read again.
// Both are bounded here at the desktop's own numbers, with two extra rules
// the desktop does not need because the phone's templates index the map
// instead of asking through a getter. Then the instrument: artTally() on the
// handle, seventeen probe fields, and seven mock steps that install a live
// listener/timer registry in the page, drive two warm laps, a baseline and
// nine more, assert the census did not grow, and take the registry back out
// again. No new UI, no CSS, no Rust, no Kotlin.
const fs = require('fs');
const root = process.env.SS_ROOT || 'F:/Coding/SoulStream/';
const scratch = 'C:/Users/Kyle/AppData/Local/Temp/claude/F--Coding-SoulStream/5488514a-11e8-482a-b0a0-20cb511ee110/scratchpad/';
const ins = (name) => fs.readFileSync(scratch + name, 'utf8').replace(/\s+$/, '');
let p, s;
const load = (file) => { p = file; s = fs.readFileSync(root + p, 'utf8'); };
const save = () => fs.writeFileSync(root + p, s);
const once = (a) => {
const i = s.indexOf(a);
if (i < 0) throw new Error('missing in ' + p + ': ' + a.slice(0, 90));
if (s.indexOf(a, i + 1) >= 0) throw new Error('ambiguous in ' + p + ': ' + a.slice(0, 90));
return i;
};
const rep = (a, b) => { once(a); s = s.replace(a, () => b); };
const NL = String.fromCharCode(10);
/* The tally is APPENDED to art.svelte.js with no anchor of its own, and
several inserts below re-emit their own anchor, so a second run would
write twice where `once` cannot see it coming and would only throw
further down, leaving half a tree. Refuse one here, before a byte is
written: `artTally` appears nowhere in the repo today and is the first
thing this round writes. */
if (fs.readFileSync(root + 'src/mobile/art.svelte.js', 'utf8').includes('artTally')) {
throw new Error('round 41 is already applied in ' + root + ' — reset the tree rather than re-running');
}
/* PROBE is ONE template literal, so a raw backtick anywhere inside it — a
comment included — ends it, and what follows becomes a tagged template:
the file still parses, `node --check` says nothing at all, and smoke.mjs
throws the moment it is imported. `${` is the same trap the other way.
Round thirty-nine's check, kept. */
for (const bad of ['`', '${']) {
if (ins('ins-r41-probe.txt').includes(bad)) {
throw new Error('ins-r41-probe.txt carries a ' + bad + ' — the probe block lives inside a template literal');
}
}
// ── the thumbnail map: bounded where it grows, and countable ──────────
load('src/mobile/art.svelte.js');
rep(`const pending = new Map(); // path -> { promise, resolve }`,
ins('ins-r41-art-trim.txt') + NL + NL + `const pending = new Map(); // path -> { promise, resolve }`);
/* the one call site: the end of pump()'s own .then, AFTER the waiter has
been handed the url it asked for, and told which key just landed so that
the entry a consumer is about to read can never be the one thrown away */
rep(` art[p] = u;
const w = pending.get(p);
pending.delete(p);
if (w) w.resolve(u);`,
` art[p] = u;
const w = pending.get(p);
pending.delete(p);
if (w) w.resolve(u);
/* bounded here, where it grows, and nowhere else */
trimArt(p);`);
/* the accents: the same budget, in insertion order, with none of the
painting question — nothing renders a promise */
rep(` );
}
return accents.get(path);`,
` );
while (accents.size > ART_MAX) accents.delete(accents.keys().next().value);
}
return accents.get(path);`);
/* appended, so the guard above is what refuses a second run */
s = s.replace(/\s*$/, NL) + NL + ins('ins-r41-art-tally.txt') + NL;
save();
// ── the playlist covers a revision left behind ────────────────────────
load('src/mobile/playlists.svelte.js');
rep(` .then((u) => { pls.covers[k] = u || null; }, () => { pls.covers[k] = null; })`,
` .then((u) => { pls.covers[k] = u || null; }, () => { pls.covers[k] = null; })
/* the key is "id:revision" and an edit bumps the revision, so
every earlier revision of this playlist is unreachable by
construction — nothing looks a playlist's cover up by
anything but its current one. Dropped as the new one lands:
no cap, no policy, just the keys that are already dead. */
.then(() => { const mine = p.id + ":"; for (const old of Object.keys(pls.covers)) if (old !== k && old.startsWith(mine)) delete pls.covers[old]; })`);
save();
// ── the seam: one name on the handle, and nothing else ────────────────
load('src/mobile/Mobile.svelte');
rep(` import { art, wantArt, accentFor, applyAccent } from "./art.svelte.js";`,
` import { art, artTally, wantArt, accentFor, applyAccent } from "./art.svelte.js";`);
/* deliberately a THIRD fragment of this literal: round thirty-nine took the
head (`art, catalog, slsk, boot,`) and round thirty-four the tail
(`wifi: { downloadsAllowed, cellular }, next, prev, toggle`), so a sibling
round appending to the handle reaches for one of those, not for this */
rep(`xfn: xfernote, pls, notice, importer,`, `xfn: xfernote, pls, notice, importer, artTally,`);
save();
// ── the walk ──────────────────────────────────────────────────────────
load('tests/mobile/smoke.mjs');
/* the second-to-last line of the probe, NOT the `contactsLoaded:` line
(rounds thirty-one and thirty-four) nor the `lyrTools:` line (round
thirty-nine), and not the probe's last line either, so a sibling
appending at the end of the object does not land on top of this */
rep(` miniLeft: Math.round((box('.mini') || { left: -1 }).left),`,
` miniLeft: Math.round((box('.mini') || { left: -1 }).left),` + NL + ins('ins-r41-probe.txt'));
/* the walk's current last step, NOT `];\nconst MOCK_EXPECT = {`, which round
thirty-four used and a sibling is likely to reuse: anchoring on the step
means a round appending at the array end lands after my block rather than
inside it */
rep(` { do: \`document.querySelectorAll('.nav button')[0].click()\`, name: "export-clear", wait: 900 },`,
` { do: \`document.querySelectorAll('.nav button')[0].click()\`, name: "export-clear", wait: 900 },`
+ NL + ins('ins-r41-steps.txt'));
rep(` "export-clear": (i) => /^(Good |Still)/.test(i.title) && !i.toast && !i.notice && !i.half.includes("up"),`,
` "export-clear": (i) => /^(Good |Still)/.test(i.title) && !i.toast && !i.notice && !i.half.includes("up"),`
+ NL + ins('ins-r41-expect.txt'));
save();
// ── the doc ───────────────────────────────────────────────────────────
/* the last paragraph of ## Performance and the heading after it — away from
"Not done yet" and from the walk's own step-list sentence, both of which
rounds thirty-four and thirty-nine already rewrote and which siblings in
this wave are likely to reach for again */
load('ANDROID.md');
rep(`The audio side had its own lag: \`dumpsys media.audio_flinger\` showed the
track on the fast mixer with runs of ~55 ms underruns. The 8192-frame buffer
above moved it to the normal mixer; the phone walk samples the underrun
counters before and after and fails if they rose.
## Prerequisites (home machine, done 2026-09-12)`, ins('ins-r41-doc.txt'));
save();
console.log('round 41 wired');