// Round thirty-three: the sweep. Thirty rounds by several hands left drift // in src/mobile — nine imports nothing in their file ever named again, two // helpers written twice word for word, two functions and two CSS rules // nothing has ever reached, and one raw NUL byte inside a string literal in // player.svelte.js that has made `grep` call that file binary since the day // it landed. Nothing here changes what the phone does: every deletion is // something with no reference anywhere in src/, tests/, crates/ or // src-tauri/, and every unification moves a body that was already identical. // One line is ADDED — a comment over the one rule that looks like a stray // and is not, so the next sweep does not take it. The walk is untouched on // purpose: it is the guard, not the subject. // // Rounds thirty-one and thirty-two land first and this round reads import // lines they may also touch, so every anchor into one is scoped to // the NAME being taken out or put in rather than to the whole line — a // round that adds a name beside it does not collide. Where a whole import // statement goes the line is the anchor on purpose: if something else has // joined it, the statement is no longer dead and `once` should say so. 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); const NUL = String.fromCharCode(0); /* A sweep is all deletions and moves, so a second run would hit missing anchors halfway through and leave the tree half-swept. `stamp` in util.js is what this round creates and nothing else has ever had — refuse before a byte is written. */ if (fs.readFileSync(root + 'src/mobile/util.js', 'utf8').includes('export function stamp(ms)')) { throw new Error('src/mobile/util.js already has stamp() — round 33 has already run against ' + root); } /* ── util.js: the two helpers that were written twice, once ───────────── Both bodies are moved here WORD FOR WORD from the screens below, so the diff on each screen is a deletion and an added name on an import line it already had. */ load('src/mobile/util.js'); rep(`export function longDur(secs) { secs = Math.max(0, Math.round(secs || 0)); const h = Math.floor(secs / 3600); const m = Math.round((secs % 3600) / 60); if (h) return h + " hr " + m + " min"; return m + " min"; }`, `export function longDur(secs) { secs = Math.max(0, Math.round(secs || 0)); const h = Math.floor(secs / 3600); const m = Math.round((secs % 3600) / 60); if (h) return h + " hr " + m + " min"; return m + " min"; } ` + ins('ins-r33-stamp.txt')); rep(`export function fmtBytes(n) { if (!n) return ""; if (n >= 1073741824) return (n / 1073741824).toFixed(1) + " GB"; if (n >= 1048576) return (n / 1048576).toFixed(1) + " MB"; return Math.round(n / 1024) + " kB"; }`, `export function fmtBytes(n) { if (!n) return ""; if (n >= 1073741824) return (n / 1073741824).toFixed(1) + " GB"; if (n >= 1048576) return (n / 1048576).toFixed(1) + " MB"; return Math.round(n / 1024) + " kB"; } ` + ins('ins-r33-fmtcount.txt')); /* isLossless: written for the source rules, never called once. The desktop has its own; this copy has no reference in src/, tests/, crates/ or src-tauri/. The LOSSLESS set beside it stays — fmtOf reads it. */ rep(`export function isLossless(fmt) { return /^(FLAC|WAV|AIFF?|APE|WV|ALAC|DSF|DFF)\\b/.test(fmt || ""); } `, ``); save(); /* ── the four screens the two helpers came from ─────────────────────── The name goes in at the closing brace of the util.js import, so a round that has added its own name to that list still takes this cleanly. */ load('src/mobile/screens/Import.svelte'); rep(` } from "../util.js";`, `, stamp } from "../util.js";`); rep(` function stamp(ms) { const s = Math.floor((ms || 0) / 1000); const h = Math.floor(s / 3600); const m = Math.floor((s % 3600) / 60); const sec = s % 60; return (h ? h + ":" + String(m).padStart(2, "0") : String(m)) + ":" + String(sec).padStart(2, "0"); } `, ``); save(); load('src/mobile/screens/Playlist.svelte'); rep(` } from "../util.js";`, `, stamp } from "../util.js";`); rep(` function stamp(ms) { const s = Math.floor((ms || 0) / 1000); const h = Math.floor(s / 3600); const m = Math.floor((s % 3600) / 60); const sec = s % 60; return (h ? h + ":" + String(m).padStart(2, "0") : String(m)) + ":" + String(sec).padStart(2, "0"); } `, NL); save(); load('src/mobile/screens/Artist.svelte'); rep(` } from "../util.js";`, `, fmtCount } from "../util.js";`); rep(` const fmtCount = (n) => (!n ? "" : n >= 1e6 ? (n / 1e6).toFixed(1).replace(/\\.0$/, "") + "M" : n >= 1e3 ? Math.round(n / 1e3) + "k" : String(n)); `, ``); save(); load('src/mobile/screens/CatalogAlbum.svelte'); rep(` } from "../util.js";`, `, fmtCount } from "../util.js";`); rep(` const fmtCount = (n) => (!n ? "" : n >= 1e6 ? (n / 1e6).toFixed(1).replace(/\\.0$/, "") + "M" : n >= 1e3 ? Math.round(n / 1e3) + "k" : String(n)); `, ``); save(); /* ── slsk: a sign-out that forgets the account, which nothing offers ──── The sheet's Sign out calls disconnect(); nothing in the tree has ever called this. The desktop's Settings keeps api.forgetCredentials (and so the mock's forget_credentials branch) alive, so only this copy goes. */ load('src/mobile/slsk.svelte.js'); rep(` export async function forgetAccount() { try { await api.forgetCredentials(); } catch { /* fine */ } slsk.savedUser = ""; slsk.hasPassword = false; } `, ``); save(); /* ── nine imports no file ever names again ───────────────────────────── Each of these appears exactly once in its own file: on the import line. Where a whole statement goes, the module is still loaded through another import in the same graph (lib.svelte.js comes in through Mobile.svelte and player.svelte.js; Library.svelte imports util.js four lines higher), and neither module has a top-level statement other than a declaration, so nothing moves but the order they are evaluated in. Every export named here keeps other callers — only these imports go. The tenth was Sheet.svelte's `import * as api`, and it is round thirty-two's: a namespace import is the one shape Rollup cannot shake, so its reason was the bundle rather than drift, and the round that weighs the launch takes it. */ load('src/mobile/HalfSheet.svelte'); rep(`, albumsBy`, ``); rep(`, refreshRecents`, ``); save(); load('src/mobile/Sheet.svelte'); rep(` import { albumsBy } from "./lib.svelte.js"; `, ``); save(); load('src/mobile/screens/Library.svelte'); /* the second import OF THE SAME MODULE, four lines below the first, and the name it brings is unused — the line goes whole */ rep(` import { longDur } from "../util.js"; `, ``); save(); load('src/mobile/screens/Room.svelte'); rep(`, leaveRoom`, ``); rep(`, addBuddy`, ``); /* `back` is a word as well as a name — anchor it between its neighbours so a line of prose cannot make this ambiguous */ rep(`push, back,`, `push,`); save(); load('src/mobile/screens/Search.svelte'); rep(`, folderMeta`, ``); save(); load('src/mobile/screens/Transfers.svelte'); rep(`import { transfers, `, `import { `); save(); /* ── two CSS rules no markup can reach ────────────────────────────────── src/mobile has no classList call at all and exactly three class names built from an expression — `lane {cls}`, `screen tr-{nav.tr}` and `tag {cls}` — none of which can spell either of these. Loading the stylesheet before and after and diffing the CSSOM says the same: two rules fewer, these two, and no other rule's text changed. */ load('src/mobile/shell.css'); rep(`.iconbtn.liked { color: var(--accent); } `, ``); rep(`.cardpad { padding: 14px; } `, ``); /* and the opposite case, in the same file: a one-property rule 588 lines from the one it exists to defeat, which reads exactly like the strays above. Measured before writing this down — it is load-bearing. A comment is the whole change; the declaration is untouched. */ rep(`.screen.has-hero { padding-top: 0; } .mini .mini-lane { width: auto; }`, `.screen.has-hero { padding-top: 0; } /* not a stray: \`.lane\` is width: 100%, and the mini bar's lane is ALSO positioned with both left: 16px and right: 16px — an over-constrained absolute box drops \`right\`, so without this it comes out 32px wider than the strip it was cut for (395 where 363 was asked for, at 411), the bars sit off-centre and the tail runs under the bar's own overflow: hidden. */ .mini .mini-lane { width: auto; }`); save(); /* ── one byte ─────────────────────────────────────────────────────────── The play-history key joins artist and title on a NUL, and the NUL was written into the source as the character itself. That makes the file non-text: every grep of player.svelte.js since has answered "Binary file matches" and shown nothing. The escape is the same string to the engine, and it is what catalog.svelte.js's infoKey already writes. */ load('src/mobile/player.svelte.js'); rep(` const key = artist + "` + NUL + `" + title;`, ` const key = artist + "\\u0000" + title;`); save(); /* ── the doc: where the shared helpers live ─────────────────────────── */ load('ANDROID.md'); rep(`| \`src/mobile/prefs.svelte.js\` | Appearance, kept in localStorage: ambience, accent from artwork, motion |`, `| \`src/mobile/prefs.svelte.js\` | Appearance, kept in localStorage: ambience, accent from artwork, motion | | \`src/mobile/util.js\` | what every screen shares and no screen should write twice: the formatters (\`mmss\` / \`longDur\` / \`stamp\` for a length in seconds, in words and in milliseconds; \`ago\` / \`clock\`; \`fmtBytes\` / \`fmtCount\`; \`fmtOf\` / \`albumFmt\`), the name and folder readers a peer's file needs, the \`drag\` action, and \`devBuild\` |`); save(); console.log('round 33 wired');