// Round twenty: a peer in full — the peer sheet's "About this user" opens a
// screen of its own (screens/Peer.svelte): the card their client serves
// (slsk_user_info), the interests they declared (slsk_user_interests), and
// the comparison this phone makes of their share against the library
// (overlap.rs), whose gap rows are the point of opening a stranger.
const fs = require('fs');
const root = process.env.SS_ROOT || 'F:/Coding/SoulStream/';
const INS = 'C:/Users/Kyle/AppData/Local/Temp/claude/F--Coding-SoulStream/5488514a-11e8-482a-b0a0-20cb511ee110/scratchpad/';
const NL = String.fromCharCode(10);
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); };
/* Two of the anchors below are re-emitted — round twenty-one wants the
sheet's Browse block byte-identical — so a second run would double-insert
where `once` cannot see it. Refuse one before anything is written. */
if (fs.existsSync(root + 'src/mobile/screens/Peer.svelte')) {
throw new Error('round twenty is already applied in ' + root + ' — reset the tree rather than re-running');
}
// ── the screen ──
fs.writeFileSync(root + 'src/mobile/screens/Peer.svelte', fs.readFileSync(INS + 'ins-r20-peer.txt', 'utf8'));
// ── the door: one row on the peer sheet, above Browse ──
load('src/mobile/HalfSheet.svelte');
{
const a = ` {#if !(top().n === "browse" && top().c.username === u)}
{@render row("folder-open", "Browse their files", "everything they share", act(() => push("browse", { username: u })))}
{/if}`;
rep(a, ` {#if !(top().n === "peer" && top().c.username === u)}
{@render row("users", "About this user", "their card, and what they have that you don't", act(() => push("peer", { username: u })))}
{/if}
` + a);
}
save();
// ── the route ──
load('src/mobile/Mobile.svelte');
rep(` import Browse from "./screens/Browse.svelte";`, ` import Browse from "./screens/Browse.svelte";
import Peer from "./screens/Peer.svelte";`);
rep(` case "browse": return { title: e.c.username, kicker: "Their files" };`,
` case "browse": return { title: e.c.username, kicker: "Their files" };
case "peer": return { title: e.c.username, kicker: "On Soulseek" };`);
rep(` {:else if entry.n === "browse"}
`,
` {:else if entry.n === "browse"}
{:else if entry.n === "peer"}
`);
save();
// ── the viewer takes a data: key, so their own picture opens like a cover ──
load('src/mobile/Viewer.svelte');
rep(` if (/^https?:/i.test(k)) { full = k; return; }`,
` if (/^https?:/i.test(k)) { full = k; return; }
if (/^data:/i.test(k)) { full = k; return; } /* a peer's own picture: the bytes are already here */`);
save();
// ── the look ──
load('src/mobile/shell.css');
s = s.replace(/\s*$/, NL) + `
/* A peer in full: the head wears their own picture, their own words sit in
a card, and the comparison's rows read as records they have and you don't */
.pr-head .pr-pic { width: 44px; height: 44px; flex: none; padding: 0; border: 0; border-radius: 13px; overflow: hidden; background: var(--surface); box-shadow: var(--ring); }
.pr-head .pr-pic img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pr-about { margin: 2px var(--pad) 10px; padding: 12px 14px; border-radius: var(--r-card); background: var(--surface); box-shadow: var(--ring); font-size: 13.5px; line-height: 1.55; color: var(--text-secondary); white-space: pre-wrap; overflow-wrap: anywhere; }
.chips.pr-tags { flex-wrap: wrap; overflow: visible; row-gap: 7px; }
.pr-gap .lead { color: var(--text-muted); }
`;
save();
// ── the mocks: a card, a picture, interests, and a ledger per username ──
load('src/lib/mockLibrary.js');
rep(`const TITLES = ["One More Time"`, `/* a peer's own card, and the comparison overlap.rs keeps per username */
const MOCK_PIC = "iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAEUlEQVR42mOQz+7HihiGlgQAV11GQceAU4sAAAAASUVORK5CYII=";
const MOCK_ABOUT = "Ripping my own vinyl since 2004. FLAC only, folders named Artist - Year - Album. Ask before you queue a whole discography.";
const MOCK_LIKES = ["french house", "vinyl rips", "detroit techno", "boards of canada"];
const MOCK_HATES = ["transcodes", "128 kbps"];
const OVERLAP_COMMON = [["Daft Punk", "Discovery"], ["Daft Punk", "Homework"], ["Caribou", "Swim"]];
const OVERLAP_GAPS = [
["Boards of Canada", "Geogaddi"],
["Aphex Twin", "Drukqs"],
["Daft Punk", "Alive 2007"],
["Burial", "Untrue"],
["Four Tet", "Rounds"],
["Autechre", "Amber"],
];
const overlaps = new Map();
const pairsOf = (list) => list.map(([artist, album]) => ({ artist, album }));
const TITLES = ["One More Time"`);
rep(` if (cmd === "start_search") {`, ` if (cmd === "slsk_user_info") {
const u = String(args?.username || "");
const peer = PEERS.find((x) => x.username === u);
return new Promise((r) => setTimeout(() => r({
username: u,
description: MOCK_ABOUT,
picture_png_base64: MOCK_PIC,
picture_note: null,
total_uploads: 18422,
queue_size: peer ? peer.queue : 0,
has_free_slot: peer ? peer.slot : true,
}), 450));
}
if (cmd === "slsk_user_interests") {
return new Promise((r) => setTimeout(() => r({ likes: MOCK_LIKES.slice(), hates: MOCK_HATES.slice() }), 400));
}
if (cmd === "overlap_list") return Promise.resolve([...overlaps.values()]);
if (cmd === "overlap_compute") {
/* the real one browses their whole share before it compares: the wait is the point */
const u = String(args?.username || "");
const o = {
username: u,
computed_at: Math.floor(Date.now() / 1000),
folders: 812,
audio_files: 9134,
their_albums: 74,
albums_in_common: pairsOf(OVERLAP_COMMON),
albums_in_common_count: OVERLAP_COMMON.length,
artists_in_common: ["Daft Punk", "Caribou"],
artists_in_common_count: 2,
gaps: pairsOf(OVERLAP_GAPS),
gaps_count: OVERLAP_GAPS.length,
score: OVERLAP_COMMON.length / 74,
};
return new Promise((r) => setTimeout(() => { overlaps.set(u.toLowerCase(), o); r(o); }, 1100));
}
if (cmd === "overlap_forget") { overlaps.delete(String(args?.username || "").toLowerCase()); return Promise.resolve(); }
if (cmd === "start_search") {`);
save();
// ── the walk ──
load('tests/mobile/smoke.mjs');
rep(` brwHead: (document.querySelector('.brw-head .m') || {}).textContent || '',`,
` brwHead: (document.querySelector('.brw-head .m') || {}).textContent || '',
peerLine: (document.querySelector('.pr-head .m') || {}).textContent || '',
peerDesc: (document.querySelector('.pr-about') || {}).textContent || '',
peerPic: !!document.querySelector('.pr-pic img'),
peerTags: document.querySelectorAll('.pr-tags .chip').length,
peerGaps: document.querySelectorAll('.pr-gap').length,
peerSum: (document.querySelector('.pr-sum') || {}).textContent || '',`);
{
const a = ` { do: \`document.querySelector('.peer-head .peer-menu').click()\`, name: "peer-sheet", wait: 700 },
{ do: \`__click('.halfsheet .row', 'Browse their files')\`, name: "browse", wait: 1400 },`;
rep(a, ` { do: \`document.querySelector('.peer-head .peer-menu').click()\`, name: "peer-sheet", wait: 700 },
/* the stranger in full: the card their client serves, their picture in
the viewer, what they declared, and the comparison of their share
against this library — a record they have and it doesn't, found */
{ do: \`__click('.halfsheet .row', 'About this user')\`, name: "peer-screen", wait: 1400 },
{ do: \`document.querySelector('.pr-pic').click()\`, name: "peer-picture", wait: 900 },
{ do: \`history.back()\`, wait: 700 },
{ do: \`__click('.screen .btn', 'Compare with my library')\`, name: "peer-compare", wait: 2200 },
{ do: \`__click('.pr-gap', 'Geogaddi')\`, name: "peer-gap-open", wait: 1300 },
{ do: \`history.back()\`, name: "peer-again", wait: 1200 },
{ do: \`__click('.screen .btn', 'Forget this comparison')\`, name: "peer-forgotten", wait: 1100 },
{ do: \`document.querySelector('.pr-head .iconbtn').click()\`, name: "peer-sheet-again", wait: 700 },
{ do: \`__click('.halfsheet .row', 'Browse their files')\`, name: "browse", wait: 1400 },`);
}
rep(` "peer-sheet": (i) => i.half.includes("up") && /Browse their files/.test(i.halfText) && /Play this folder/.test(i.halfText),`,
` "peer-sheet": (i) => i.half.includes("up") && /Browse their files/.test(i.halfText) && /Play this folder/.test(i.halfText),
"peer-screen": (i) => i.title.startsWith("hifi_hank") && /Ripping my own vinyl/.test(i.peerDesc) && i.peerPic && i.peerTags === 6 && /slot is free/.test(i.peerLine) && /served/.test(i.peerLine) && i.peerGaps === 0,
"peer-picture": (i) => i.viewer,
"peer-compare": (i) => i.peerGaps === 6 && /in your library/.test(i.peerSum),
"peer-gap-open": (i) => i.title.startsWith("Search") && i.sq === "Boards of Canada Geogaddi",
"peer-again": (i) => i.title.startsWith("hifi_hank") && i.peerGaps === 6,
"peer-forgotten": (i) => i.peerGaps === 0 && !i.peerSum && /forgotten/i.test(i.notice),
"peer-sheet-again": (i) => i.half.includes("up") && /Browse their files/.test(i.halfText) && !/About this user/.test(i.halfText),`);
save();
// ── the doc ──
load('ANDROID.md');
rep(`## Playlists`, `Tapping **About this user** on the peer sheet opens the stranger in full
(\`screens/Peer.svelte\`, \`push("peer", { username })\`): the card their own
client serves (\`slsk_user_info\` — what they wrote about themselves, their
picture, whether a slot is free, the queue, how much they have served),
the likes and hates they declared to the server (\`slsk_user_interests\`),
and the comparison this phone makes of their share against the library
(\`overlap.rs\`: \`overlap_compute\` browses them on purpose, \`overlap_list\`
reads what was compared before, \`overlap_forget\` drops it). Nothing is
uploaded to work it out — their folder names against your albums, here.
The comparison's rows are the reason to open a stranger: every record they
have by an artist in your library that you don't, each one opening the
catalog search that finds it. Their picture opens in the artwork viewer,
which takes a \`data:\` key now as well as a library path and an https URL.
## Playlists`);
save();
console.log('round twenty wired');