// 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');