// Round twenty-six: the odds and ends. A cached stream copied out into a // folder picked on the phone (the desktop has the command and no UI for it), // the Soulseek listening port in You → Sharing (the desktop never exposed it // at all), and the desktop's debug overlay as a screen behind a new dev-build // gate. community_pick_file is deliberately not here: it is the avatar / // recovery-artifact dialog, and the backend has no chat file-send at all. 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 dev gate: Vite's own DEV flag, plus the mock walk saying so ── load('src/mobile/util.js'); // an append has no anchor to be ambiguous, so it guards itself: a re-run // after a throw further down must not export devBuild twice if (s.includes('export const devBuild')) throw new Error('src/mobile/util.js already exports devBuild — round 26 has already run against this tree'); s = s.replace(/\s*$/, NL) + ` /** The dev flavour, and nothing else. That build is the one that loads from * the Vite dev server over the cable (ANDROID.md, "Iterating without * cargo"), so Vite's own DEV flag IS the flavour; the embedded build the * phone keeps is compiled and reads false. The mock walk builds for * production too, so it says who it is through the key the harness sets. */ export const devBuild = (() => { if (import.meta.env.DEV) return true; try { return localStorage.getItem("mockLibrary") === "1"; } catch { return false; } })(); `; save(); // ── the sheet: a cached stream copied into a folder on this phone ── load('src/mobile/HalfSheet.svelte'); rep(` /* a peer's sheet (a raw result's folder, or the browse screen's user) */`, ins('ins-r26-halfsheet-copy.txt') + NL + ` /* a peer's sheet (a raw result's folder, or the browse screen's user) */`); rep(` {#if streamMates(track, album).length > 1} {@render row("download", "Save this record", streamMates(track, album).length + " tracks streaming from the same record", act(() => saveStreams(streamMates(track, album))))} {/if}`, ` {#if streamMates(track, album).length > 1} {@render row("download", "Save this record", streamMates(track, album).length + " tracks streaming from the same record", act(() => saveStreams(streamMates(track, album))))} {/if} {@render row("copy", "Copy to a folder…", "the streamed file as it is, into a folder on this phone", act(() => copyToFolder([{ t: track, album }])))}`); // a prompt that asks for a number gets the numeric keypad; Svelte drops an // attribute whose value is undefined, so every other prompt is unchanged rep(` { if (e.key === "Enter") submitPrompt(); }} use:focusLater />`, ` { if (e.key === "Enter") submitPrompt(); }} use:focusLater />`); save(); // ── You: the listening port, and the dev flavour's door to Diagnostics ── load('src/mobile/screens/You.svelte'); rep(` import { refreshSourcePrefs } from "../../lib/sources.js";`, ` import { refreshSourcePrefs } from "../../lib/sources.js"; import { devBuild } from "../util.js";`); rep(` async function refetchCovers() {`, ins('ins-r26-you-port.txt') + NL + ` async function refetchCovers() {`); rep(`
Sharing
`, `
Sharing
What other people can take from you, and how they reach you. Your library folder is always shared; the listening port is what peers dial when they download from you — the app asks the router to forward it over UPnP and falls back to the server relaying the connection when the router will not. On mobile data the carrier's NAT blocks incoming connections whatever the port is, and a change applies on the next connect.
`); rep(` {#each cfg?.shared_folders ?? [] as folder (folder)}`, ` {#each cfg?.shared_folders ?? [] as folder (folder)}`); rep(`
Soulseek{lib.username || "not signed in"}
`, `
Soulseek{lib.username || "not signed in"}
{#if devBuild} {/if}`); save(); // ── the screen itself ── fs.writeFileSync(root + 'src/mobile/screens/Diagnostics.svelte', ins('ins-r26-diag.txt') + NL); // ── the route ── load('src/mobile/Mobile.svelte'); rep(` import Transfers from "./screens/Transfers.svelte";`, ` import Transfers from "./screens/Transfers.svelte"; import Diagnostics from "./screens/Diagnostics.svelte";`); rep(` case "transfers": return { title: "Transfers", kicker: "Downloads" };`, ` case "transfers": return { title: "Transfers", kicker: "Downloads" }; case "diag": return { title: "Diagnostics", kicker: "Dev build" };`); rep(` `, ` {:else if entry.n === "diag"} `); save(); // ── the mocks ── load('src/lib/mockLibrary.js'); rep(` if (cmd === "excluded_search_phrases") return Promise.resolve([]);`, ` if (cmd === "excluded_search_phrases") return Promise.resolve([]);` + NL + ins('ins-r26-mock.txt')); rep(` upload_slots: 2,`, ` upload_slots: 2, listen_port: null,`); save(); // ── the walks ── load('tests/mobile/smoke.mjs'); rep(` cardPhs: document.querySelectorAll('.rail .acard svg').length,`, ` cardPhs: document.querySelectorAll('.rail .acard svg').length,` + NL + ins('ins-r26-probe.txt')); rep(` { do: \`__click('.halfsheet .row', 'Save to library')\`, name: "np-saved", wait: 900 },`, ins('ins-r26-np-steps.txt')); rep(` "np-saved": (i) => !i.half.includes("up") && /Saved to the library/.test(i.notice),`, ` "np-copied": (i) => !i.half.includes("up") && /1 file copied/.test(i.notice), "np-saved": (i) => !i.half.includes("up") && /Saved to the library/.test(i.notice),`); rep(` { do: \`__click('.screen .row', 'Wi-Fi only')\`, name: "wifi-off", wait: 400 },`, ` { do: \`__click('.screen .row', 'Wi-Fi only')\`, name: "wifi-off", wait: 400 },` + NL + ins('ins-r26-mock-steps.txt')); rep(` "wifi-off": (i) => i.wifiOnly === "off" && !i.toast,`, ` "wifi-off": (i) => i.wifiOnly === "off" && !i.toast,` + NL + ins('ins-r26-mock-expect.txt')); rep(` { do: \`__click('.prefs[data-mode="albumart"] .chip', '1500 px')\`, name: "you-artedge-back", wait: 900 },`, ` { do: \`__click('.prefs[data-mode="albumart"] .chip', '1500 px')\`, name: "you-artedge-back", wait: 900 },` + NL + ins('ins-r26-phone-steps.txt')); rep(` "you-artedge-back": (i) => i.artEdge === "1500 px" && !i.toast,`, ` "you-artedge-back": (i) => i.artEdge === "1500 px" && !i.toast,` + NL + ins('ins-r26-phone-expect.txt')); save(); // ── the doc ── load('ANDROID.md'); rep(`record the queue holds. A moment after boot the phone warms the library's thumbnails the`, ins('ins-r26-doc-copy.txt')); rep(`eight: Enter, or the screen left with a query in it), kept on the device.`, `eight: Enter, or the screen left with a query in it), kept on the device.` + NL + NL + ins('ins-r26-doc.txt')); save(); console.log('round 26 wired');