Files
SoulStream/handoff/rounds/patch-round21.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

188 lines
12 KiB
JavaScript

// Round twenty-one: a name of your own. The desktop's local contact
// metadata on the phone — a petname, a group, a private note and the two
// marks (Favorite, Trusted), kept in the app's own config and published
// nowhere. The petname stands in for the username wherever Community shows
// one, with the real name on the line beneath it.
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 NL = String.fromCharCode(10);
const ins = (name) => fs.readFileSync(scratch + name, 'utf8').replace(/\r\n/g, NL).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); };
// ── the store ──
fs.writeFileSync(root + 'src/mobile/contacts.svelte.js', ins('ins-r21-store.txt') + NL);
// ── the shell: the list read at boot, the petname on a thread's bar ──
load('src/mobile/Mobile.svelte');
rep(` import { downloadsAllowed, cellular } from "./network.svelte.js";`,
` import { downloadsAllowed, cellular } from "./network.svelte.js";
import { contacts, loadContacts, display } from "./contacts.svelte.js";`);
rep(` case "thread": return { title: e.c.username, kicker: "Chat", flush: true };`,
` case "thread": return { title: display(e.c.username), kicker: "Chat", flush: true };`);
rep(` bindImporter();
bindShare();`,
` bindImporter();
bindShare();
loadContacts();`);
rep(`, patch };`, `, patch, contacts, loadContacts };`);
save();
// ── the sheets: the peer's head and its new door, the editor, the roster ──
load('src/mobile/HalfSheet.svelte');
rep(` import { art, wantArt } from "./art.svelte.js";`,
` import { art, wantArt } from "./art.svelte.js";
import { metaOf, display, realName, setMeta, clearMeta } from "./contacts.svelte.js";`);
rep(` {@const buddy = slsk.buddies.includes(u)}`,
` {@const buddy = slsk.buddies.includes(u)}
{@const m = metaOf(u)}`);
rep(` <span class="b"><span class="t">{u}</span><span class="m">{g ? (g.slot ? "slot free" : "queue " + g.queue) + " · " + speedLabel(g.speed) + " · " + g.folderName : "on Soulseek"}</span></span>`,
` <span class="b"><span class="t">{display(u)}{#if m.favorite}<span class="nmark fav"><Icon name="star" size={12} /></span>{/if}{#if m.trusted}<span class="nmark tr"><Icon name="shield" size={12} /></span>{/if}</span><span class="m">{[realName(u), m.group, g ? (g.slot ? "slot free" : "queue " + g.queue) + " · " + speedLabel(g.speed) + " · " + g.folderName : "on Soulseek"].filter(Boolean).join(" · ")}</span></span>`);
{
const a = ` {@render row(buddy ? "x" : "heart", buddy ? "Remove buddy" : "Add buddy", buddy ? "" : "buddies stay on the Community screen", act(() => (buddy ? removeBuddy : addBuddy)(u).catch(oops)))}`;
rep(a, a + NL
+ ` <div class="sheet-hair"></div>` + NL
+ ` {@render row("pencil", "Name and notes", "a petname, a group, a note — this phone only", () => openHalf("contact", { username: u }))}`);
}
{
const a = ` /* the desktop's per-record cover refresh; the thumbnail is asked for again */`;
rep(a, ins('ins-r21-prompt.txt') + NL + a);
}
{
const a = ` {:else if (kind === "catalogAlbum-more" || kind === "catalogAlbum") && catPage}`;
rep(a, ins('ins-r21-sheet.txt') + NL + a);
}
rep(` <span class="b"><span class="t">{u.username}{#if buddy} <span class="tag hi">BUDDY</span>{/if}</span><span class="m">{u.file_count ? u.file_count.toLocaleString() + " files" : ""}</span></span>`,
` <span class="b"><span class="t">{display(u.username)}{#if buddy} <span class="tag hi">BUDDY</span>{/if}</span><span class="m">{[realName(u.username), u.file_count ? u.file_count.toLocaleString() + " files" : ""].filter(Boolean).join(" · ")}</span></span>`);
save();
// ── Community: favourites first, the name you gave them on the row ──
load('src/mobile/screens/Community.svelte');
rep(` import { push, patch, openHalf } from "../nav.svelte.js";`,
` import { push, patch, openHalf } from "../nav.svelte.js";
import { metaOf, display, realName } from "../contacts.svelte.js";`);
rep(` const buddies = $derived(slsk.buddies.map((u) => ({ u, st: slsk.buddyState[u] || {} })));`,
` /* favourites at the top, then by the name you gave them */
const buddies = $derived(
slsk.buddies
.map((u) => ({ u, st: slsk.buddyState[u] || {}, m: metaOf(u) }))
.sort((a, b) => (b.m.favorite ? 1 : 0) - (a.m.favorite ? 1 : 0) || display(a.u).localeCompare(display(b.u))),
);`);
rep(` <button class="row" onclick={() => thread(b.u)} oncontextmenu={(e) => { e.preventDefault(); openHalf("peer", { username: b.u }); }}>
<span class="art round idc"><Identicon seed={b.u} size={46} /></span>
<span class="b">
<span class="t">{b.u}</span>
<span class="m">{status(b.st)}{b.st.file_count ? \` · \${b.st.file_count.toLocaleString()} files\` : ""}</span>
</span>`, ins('ins-r21-buddyrow.txt'));
rep(` <span class="t">{t.u}{#if t.c.unread} <span class="unread">{t.c.unread}</span>{/if}</span>`,
` <span class="t">{display(t.u)}{#if t.c.unread} <span class="unread">{t.c.unread}</span>{/if}</span>`);
rep(` <span class="m">{t.last ? (t.last.me ? "you: " : "") + t.last.text : ""}</span>`,
` <span class="m">{realName(t.u) ? realName(t.u) + " · " : ""}{t.last ? (t.last.me ? "you: " : "") + t.last.text : ""}</span>`);
save();
// ── the thread: the head line and the composer ──
load('src/mobile/screens/Thread.svelte');
rep(` import { slsk, sendDm, openThread } from "../slsk.svelte.js";`,
` import { slsk, sendDm, openThread } from "../slsk.svelte.js";
import { display, realName } from "../contacts.svelte.js";`);
rep(` <span class="b"><span class="t-sub">{st.status === 2 || st.status === "online" ? "online" : st.status === 1 || st.status === "away" ? "away" : "on Soulseek"}{st.file_count ? \` · \${st.file_count.toLocaleString()} files\` : ""}</span></span>`,
` <span class="b"><span class="t-sub">{[realName(username), st.status === 2 || st.status === "online" ? "online" : st.status === 1 || st.status === "away" ? "away" : "on Soulseek", st.file_count ? \`\${st.file_count.toLocaleString()} files\` : ""].filter(Boolean).join(" · ")}</span></span>`);
rep(` <div class="sec-note" style="padding: 20px 0">Say something to {username}.</div>`,
` <div class="sec-note" style="padding: 20px 0">Say something to {display(username)}.</div>`);
rep(` <input class="field" bind:this={inputEl} bind:value={text} placeholder="Message {username}…"`,
` <input class="field" bind:this={inputEl} bind:value={text} placeholder="Message {display(username)}…"`);
save();
// ── a room line: the petname, their own name beside it ──
load('src/mobile/screens/Room.svelte');
rep(` import { slsk, sayRoom, leaveRoom, openThread, addBuddy } from "../slsk.svelte.js";`,
` import { slsk, sayRoom, leaveRoom, openThread, addBuddy } from "../slsk.svelte.js";
import { display, realName } from "../contacts.svelte.js";`);
rep(` {#if !me}<button class="who" onclick={() => person(l.username)}>{l.username}</button>{/if}`,
` {#if !me}<button class="who" onclick={() => person(l.username)}>{display(l.username)}{#if realName(l.username)}<span class="real">{realName(l.username)}</span>{/if}</button>{/if}`);
save();
// ── the marks' style ──
load('src/mobile/shell.css');
{
const a = `/* the nav's badges: transfers in flight on You, unread chats on Community */`;
rep(a, ins('ins-r21-css.txt') + NL + NL + a);
}
save();
// ── the mock: one person already named, and the backend's drop rule ──
load('src/lib/mockLibrary.js');
rep(`const LINES = ["anyone got the 2024 remaster?", "the vinyl rip is warmer imo", "that folder is complete btw", "brb"];`,
`const LINES = ["anyone got the 2024 remaster?", "the vinyl rip is warmer imo", "that folder is complete btw", "brb"];
/* one person already named, so a cold read of the list has something in it */
const CONTACTS = [{ username: "dusty_crates", petname: "Dusty the digger", group: "Traders", note: "always has the vinyl rips", trusted: true, favorite: false }];`);
rep(` if (cmd === "buddy_remove") return Promise.resolve(["ada", "cy"].filter((u) => u !== args?.username));`,
` if (cmd === "buddy_remove") return Promise.resolve(["ada", "cy"].filter((u) => u !== args?.username));
if (cmd === "contacts_meta_list") return Promise.resolve(CONTACTS.slice());
if (cmd === "contact_meta_set") {
/* the backend's own rule: an entry with nothing set is dropped */
const meta = args?.meta || {};
const txt = (v) => (typeof v === "string" && v.trim() ? v.trim() : null);
const i = CONTACTS.findIndex((c) => c.username === meta.username);
if (i >= 0) CONTACTS.splice(i, 1);
const kept = { username: meta.username, petname: txt(meta.petname), group: txt(meta.group), note: txt(meta.note), trusted: !!meta.trusted, favorite: !!meta.favorite };
if (kept.petname || kept.group || kept.note || kept.trusted || kept.favorite) CONTACTS.push(kept);
return Promise.resolve(CONTACTS.slice());
}`);
save();
// ── the walks ──
load('tests/mobile/smoke.mjs');
{
const a = ` cardPhs: document.querySelectorAll('.rail .acard svg').length,`;
rep(a, a + NL + ins('ins-r21-probe.txt'));
}
{
const a = ` { do: \`document.querySelectorAll('.screen button.row')[1].dispatchEvent(new MouseEvent('contextmenu', { bubbles: true, cancelable: true }))\`, name: "buddy-sheet", wait: 600 },`;
rep(a, a + NL + ins('ins-r21-steps-b.txt'));
}
{
const a = ` { do: \`__click('.screen button.row', 'Lounge')\`, name: "room", wait: 2400 },`;
rep(a, a + NL + ins('ins-r21-steps-c.txt'));
}
{
const a = ` /* a search answered by five peers; the best one's first file streams,`;
rep(a, ins('ins-r21-steps-d.txt') + NL + a);
}
rep(` "buddy-sheet": (i) => i.half.includes("up") && /Browse their files/.test(i.halfText) && /Remove buddy/.test(i.halfText),`,
` "buddy-sheet": (i) => i.half.includes("up") && /Browse their files/.test(i.halfText) && /Remove buddy/.test(i.halfText) && /Name and notes/.test(i.halfText),`);
rep(` "user-lookup": (i) => i.half.includes("up") && /dusty_crates/.test(i.halfText) && /Browse their files/.test(i.halfText) && /Add buddy/.test(i.halfText),`,
ins('ins-r21-expect.txt'));
{
const a = ` { do: \`document.querySelectorAll('.nav button')[3].click()\`, name: "community", wait: 900 },`;
rep(a, a + NL
+ ` /* the real config's contact list, read back over the real IPC */` + NL
+ ` { do: \`__ss.loadContacts()\`, name: "contacts-read", wait: 800 },`);
}
{
const a = ` "artist-cold": (i) => i.screen.includes("has-hero"),`;
rep(a, ` "contacts-read": (i) => i.contactsLoaded === "yes" && !i.toast,` + NL + a);
}
save();
// ── the doc ──
load('ANDROID.md');
rep(`| \`src/mobile/slsk.svelte.js\` | the Soulseek hub: the connection, the rooms, the chats, the buddies — the desktop's \`slsk\` store and events |`,
`| \`src/mobile/slsk.svelte.js\` | the Soulseek hub: the connection, the rooms, the chats, the buddies — the desktop's \`slsk\` store and events |
| \`src/mobile/contacts.svelte.js\` | the local view of a person on the network: a petname, a group, a note and the two marks (\`contacts_meta_list\` / \`contact_meta_set\`) — this phone only, published nowhere |`);
{
const a = `More long presses: a catalog song in the search results opens the catalog`;
rep(a, ins('ins-r21-doc.txt') + NL + NL + a);
}
save();
console.log('round twenty-one wired');