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

128 lines
6.8 KiB
JavaScript

// Round twenty-two: your own Soulseek interests, and who the server says is
// like you. Community grows a fourth tab, Taste — the likes and hates this
// account publishes (slsk_interests_mine, slsk_interest_set) and the people
// the server ranks on them (slsk_similar_users), each a row into the peer
// sheet. Every declaration asks the server again: the ranking only moves
// when its input does.
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 = (f) => fs.readFileSync(SCRATCH + f, 'utf8');
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); };
// ── the store: the lists, the match, and one rule ──
fs.writeFileSync(root + 'src/mobile/taste.svelte.js', ins('ins-r22-taste-store.txt'));
// ── Community: the fourth tab ──
load('src/mobile/screens/Community.svelte');
rep(` /* Community — the Soulseek side: rooms, chat, buddies. Signed out it is
the sign-in card; signed in it is the hub, the desktop's seats drawn
with the phone's components. The private layer (Spaces, holdings, the
sealed inbox) waits for the Freenet story on the phone. */`,
` /* Community — the Soulseek side: rooms, chat, buddies, taste. Signed
out it is the sign-in card; signed in it is the hub, the desktop's
seats drawn with the phone's components. The private layer (Spaces,
holdings, the sealed inbox) waits for the Freenet story on the
phone. */`);
rep(` import { ago } from "../util.js";`, ` import { ago } from "../util.js";
import { taste, loadTaste, setInterest, askSimilar } from "../taste.svelte.js";`);
rep(`["buddies", "Buddies"]`, `["buddies", "Buddies"], ["taste", "Taste"]`);
rep(` async function signIn() {`, ` /* your own declared interests and the people the server ranks close to
them — read once per account, when the tab is opened */
$effect(() => {
if (online && tab === "taste") void loadTaste();
});
function declare(like) {
openHalf("prompt", {
title: like ? "Declare a like" : "Declare a hate",
placeholder: like ? "an artist, a label, a genre" : "an artist or a genre you would rather not hear",
ok: "Declare",
onsubmit: (v) => { void setInterest(v, like, true); },
});
}
async function signIn() {`);
{
const a = ` {/if}
<div style="padding: 18px var(--pad) 0"><button class="btn btn-ghost wide" onclick={disconnect}>Sign out</button></div>`;
rep(a, ins('ins-r22-community.txt') + a);
}
save();
// ── the chips wrap: a declared list is read, not swiped ──
load('src/mobile/shell.css');
s = s.replace(/\s*$/, NL) + `
/* Community → Taste: the interests you publish, wrapped rather than
scrolled — a declared list is read, not swiped */
.tastechips { flex-wrap: wrap; overflow: visible; }
.tastechips .tchip svg { opacity: 0.62; margin-right: -3px; }
.tastechips .chip:disabled { opacity: 0.5; }
`;
save();
// ── mocks: a fake account's declarations, and a match that moves with them ──
load('src/lib/mockLibrary.js');
rep(`function mockNet(cmd, args) {`, `/* the interests this fake account has declared, and the server's match on
them — a declaration moves both, the way the real server does */
const MY_INTERESTS = { likes: ["Daft Punk", "french house", "Warp Records"], hates: ["loudness war"] };
function mockNet(cmd, args) {`);
rep(` if (cmd === "stop_search") return Promise.resolve();`, ` if (cmd === "stop_search") return Promise.resolve();
if (cmd === "slsk_interests_mine") return Promise.resolve({ likes: MY_INTERESTS.likes.slice(), hates: MY_INTERESTS.hates.slice() });
if (cmd === "slsk_interest_set") {
const thing = String(args?.thing || "").trim();
if (!thing) return Promise.reject(new Error("nothing to declare"));
const list = args?.like ? MY_INTERESTS.likes : MY_INTERESTS.hates;
const at = list.findIndex((t) => t.toLowerCase() === thing.toLowerCase());
if (args?.on && at < 0) list.push(thing);
if (!args?.on && at >= 0) list.splice(at, 1);
return new Promise((r) => setTimeout(() => r({ likes: MY_INTERESTS.likes.slice(), hates: MY_INTERESTS.hates.slice() }), 200));
}
if (cmd === "slsk_similar_users") {
/* the server ranks on the likes we declared: one more name per like */
const pool = ["hifi_hank", "dusty_crates", "mp3_marge", "ada", "cy", "bo"];
const n = MY_INTERESTS.likes.length;
return new Promise((r) => setTimeout(() => r(pool.slice(0, Math.min(pool.length, n + 1)).map((username, i) => ({ username, rating: Math.max(1, n - i) }))), 300));
}`);
save();
// ── the walk: declared, taken back, and the match moving with it ──
load('tests/mobile/smoke.mjs');
rep(` cardPhs: document.querySelectorAll('.rail .acard svg').length,`,
` cardPhs: document.querySelectorAll('.rail .acard svg').length,
likes: [].map.call(document.querySelectorAll('.tastechips.likes .tchip'), function (c) { return c.textContent.trim(); }).join('|'),
hates: [].map.call(document.querySelectorAll('.tastechips.hates .tchip'), function (c) { return c.textContent.trim(); }).join('|'),
similar: document.querySelectorAll('.screen .row.similar').length,`);
{
const a = ` { do: \`__click('.segtabs button', 'Rooms')\`, wait: 500 },`;
rep(a, ins('ins-r22-smoke-steps.txt') + a);
}
rep(` "home-more": (i) => i.half.includes("up"),`,
` taste: (i) => i.likes === "Daft Punk|french house|Warp Records" && i.hates === "loudness war" && i.similar === 4,
"taste-declared": (i) => /Boards of Canada/.test(i.likes) && i.similar === 5,
"taste-hated": (i) => i.hates === "loudness war|loudness war 2",
"taste-dropped": (i) => !/Boards of Canada/.test(i.likes) && i.similar === 4 && !i.toast,
"taste-peer": (i) => i.half.includes("up") && /hifi_hank/.test(i.halfText),
"home-more": (i) => i.half.includes("up"),`);
save();
// ── the doc ──
load('ANDROID.md');
rep(`Community (sign in; rooms, chat, buddies`, `Community (sign in; rooms, chat, buddies, taste`);
{
const a = `The server's room list on Community is filtered by name and shows 80 at a
time with a way to the rest (Soulseek lists hundreds).`;
rep(a, a + NL + NL + ins('ins-r22-doc.txt').replace(/\s*$/, ''));
}
save();
console.log('round twenty-two wired');