Files
SoulStream/handoff/rounds/patch-round32.cjs
T
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

194 lines
11 KiB
JavaScript

// Round thirty-two: the phone's own weight. Thirty rounds of onMount had
// never been read end to end, and read end to end it was a queue — two
// dozen commands with a critical path ten deep, because Tauri's `listen`
// is an invoke like any other: five independent SQLite reads one behind
// the other in loadLibrary, eight listeners one behind the other in
// bindSlsk, two more each in watchLibrary and the player's bindEvents.
// Nothing here depended on anything before it. They go out in one round
// now — the same commands, ten of which used to leave in the shell's own
// first turn where twenty-three do, and the critical path drops to three.
// Nothing else changes: the round is meant to be invisible except that the
// app starts sooner.
//
// So that it stays that way, the round also adds the project's first boot
// instrument — one counter every invoke and every listen passes, sealed at
// the first frame the shell paints, and a budget in the mock walk held
// against it, whose sharpest clause (bootSync) has no clock in it at all.
// perf.mjs measures frames; nothing measured the launch.
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);
/* Several of the replacements below re-emit their own anchors — the census
lines in api.js and events.js, the Promise.all bodies — so `once` cannot
see a second run coming and would throw only halfway through, leaving a
half-patched tree. Refuse one here, before anything is written.
src/lib/boot.js is created by this round and by nothing else. */
if (fs.existsSync(root + 'src/lib/boot.js')) {
throw new Error('round 32 has already run against ' + root + ' — src/lib/boot.js exists; reset the tree rather than re-running');
}
// ── the census itself ─────────────────────────────────────────────────
fs.writeFileSync(root + 'src/lib/boot.js', ins('ins-r32-boot.txt') + NL);
// ── one counter on the way past the bridge, both doors ────────────────
load('src/lib/api.js');
rep(`import { mockInvoke } from "./mockLibrary.js";`,
`import { mockInvoke } from "./mockLibrary.js";
import { countBoot } from "./boot.js";`);
rep(`const invoke = (cmd, args) => mockInvoke(cmd, args) ?? tauriInvoke(cmd, args);`,
`/* the boot census (src/lib/boot.js) counts what the app asks for before its
first frame and stops there, so this is one comparison for the rest of the
run. Inert on the desktop: nothing seals it there and nothing reads it. */
const invoke = (cmd, args) => { countBoot(cmd); return mockInvoke(cmd, args) ?? tauriInvoke(cmd, args); };`);
save();
load('src/lib/events.js');
rep(`import { mockListen } from "./mockLibrary.js";`,
`import { mockListen } from "./mockLibrary.js";
import { countBoot } from "./boot.js";`);
rep(`export const listen = (event, cb) => mockListen(event, cb) ?? tauriListen(event, cb);`,
`/* a listen is an invoke too — Tauri registers the subscription over the
same bridge — so the boot census counts it as one */
export const listen = (event, cb) => { countBoot("listen " + event); return mockListen(event, cb) ?? tauriListen(event, cb); };`);
save();
// ── loadLibrary: five reads in one round ──────────────────────────────
load('src/mobile/lib.svelte.js');
rep(` try {
const st = await api.appStatus();
lib.version = st?.version ?? "";
lib.username = st?.connected_as ?? null;
} catch (e) { lib.error = String(e); }
try { lib.albums = await api.libraryAlbums(); } catch (e) { lib.error = String(e); }
await refreshRecents();
try { lib.follows = (await api.followedArtists()) || []; } catch { lib.follows = []; }
try { lib.playlists = (await api.listPlaylists()) || []; } catch { lib.playlists = []; }
lib.loaded = true;
void refreshStats();`, ins('ins-r32-loadlibrary.txt'));
// ── watchLibrary: the two library subscriptions in one ────────────────
rep(` await listen("soulseek:library-changed", () => {
clearTimeout(timer);
timer = setTimeout(async () => {
try { lib.albums = await api.libraryAlbums(); } catch { /* keep */ }
await refreshRecents();
void refreshStats();
}, 600);
});
/* a playlist mutation (the desktop's commands emit it) re-lists them */
await listen("soulseek:playlists-changed", () => { void refreshPlaylists(); });`,
ins('ins-r32-watchlibrary.txt'));
save();
// ── bindSlsk: eight listeners in one round ────────────────────────────
// Every handler body is left exactly as it was; only the shape around
// them changes, and the eight `await`s come off the call sites (an
// `await` on the new `on` would put the queue straight back).
load('src/mobile/slsk.svelte.js');
rep(` const subs = [];
const on = async (name, fn) => { try { subs.push(await listen(name, (ev) => fn(ev.payload))); } catch { /* not a phone */ } };
await on("soulseek:state", (ev) => {`, ins('ins-r32-slsk-on.txt'));
for (const ev of ['room-list', 'room-joined', 'room-left', 'room-user', 'room-message', 'private-message', 'user-state']) {
rep(` await on("soulseek:` + ev + `",`, ` on("soulseek:` + ev + `",`);
}
rep(` return subs;`,
` /* the one ordering that matters: connectStored() runs off this promise,
so a fast connected state must not land with nobody listening */
await Promise.all(pending);
return subs;`);
save();
// ── the player's two, likewise ────────────────────────────────────────
load('src/mobile/player.svelte.js');
rep(` try {
await listen("soulseek:stream", (ev) => {
const p = ev.payload || {};
if (!current()?.t?.stream) return;
player.stream = { received: p.received || 0, total: p.total || 0, done: !!p.done, failed: p.failed || null };
if (p.failed) setErr("The stream from " + current().t.stream.username + " failed: " + p.failed);
});
} catch { /* not a phone */ }
return await listen("soulseek:player", (ev) => {`,
ins('ins-r32-player-stream.txt') + NL + ` const transportSub = listen("soulseek:player", (ev) => {`);
rep(` case "error":
player.error = p.message || "playback error";
player.status = "idle";
break;
}
});
}`,
` case "error":
player.error = p.message || "playback error";
player.status = "idle";
break;
}
` + ins('ins-r32-player-tail.txt'));
save();
// ── the shell: the marks, the seal, and the handle ────────────────────
load('src/mobile/Mobile.svelte');
rep(` import * as sources from "../lib/sources.js";`,
` import * as sources from "../lib/sources.js";
import { boot, sealBoot } from "../lib/boot.js";`);
rep(` onMount(() => {
loadPrefs();
bootNav();`, ins('ins-r32-mobile-onmount.txt'));
rep(` /* the probe handle — the desktop has __soulstream for the same reason:
tests/mobile/smoke.mjs drives the real app through it */
window.__ss = { nav, top, go, push, back, openSheet, closeSheet, openHalf, closeHalf, player, lib, art, catalog, slsk,`,
ins('ins-r32-mobile-seal.txt') + NL + ` /* the probe handle — the desktop has __soulstream for the same reason:
tests/mobile/smoke.mjs drives the real app through it */
window.__ss = { nav, top, go, push, back, openSheet, closeSheet, openHalf, closeHalf, player, lib, art, catalog, slsk, boot,`);
rep(` onMount(() => {`, ins('ins-r32-mobile-effect.txt') + NL + NL + ` onMount(() => {`);
save();
// ── the one dead import of the ten whose reason is the bundle. Rollup
// drops an unreferenced NAMED import by itself, so the other nine are
// hygiene rather than weight and go out with round thirty-three's sweep,
// which is the round for code nothing references. A NAMESPACE import is
// the one shape tree-shaking cannot see through, and this one is dead.
// It frees nothing by itself — twenty-nine other files in the phone's
// graph hold api.js the same way — but it is the only one of the ten
// that belongs to a round about weight, so it goes here.
load('src/mobile/Sheet.svelte');
rep(` import * as api from "../lib/api.js";` + NL, ``);
save();
// ── the walk: the census, and a budget on both legs ───────────────────
load('tests/mobile/smoke.mjs');
rep(` contactsLoaded: (function () { try { return __ss.contacts.loaded ? 'yes' : (__ss.contacts.error ? 'err' : 'no'); } catch (e) { return ''; } })(),`,
` contactsLoaded: (function () { try { return __ss.contacts.loaded ? 'yes' : (__ss.contacts.error ? 'err' : 'no'); } catch (e) { return ''; } })(),
` + ins('ins-r32-probe.txt'));
// Home is the first named step and waits 1600 ms, so the census has been
// sealed for well over a second by the time this probe lands.
rep(`)?$/.test(i.navTabs) && !i.a11yAnon && !i.a11yGhost && !i.a11yBad && !i.a11yState,`,
`)?$/.test(i.navTabs) && !i.a11yAnon && !i.a11yGhost && !i.a11yBad && !i.a11yState
` + ins('ins-r32-mock-expect.txt'));
rep(`const PHONE_EXPECT = {`, `const PHONE_EXPECT = {` + NL + ins('ins-r32-phone-expect.txt'));
save();
// ── the doc ───────────────────────────────────────────────────────────
load('ANDROID.md');
rep(`The audio side had its own lag:`, ins('ins-r32-doc.txt') + NL + NL + `The audio side had its own lag:`);
rep(`asserts what each step should show, screenshots every step into \`tests/mobile/out/\``,
`asserts what each step should show — the Home
step holds the launch to a budget as well, reading off \`__ss.boot\` how many
commands the app issued before its first frame, how many of those left in the
shell's own turn, how long it took, and requiring round 12's art prewarm to
still be absent from them — screenshots every step into \`tests/mobile/out/\``);
save();
console.log('round 32 wired');