Files
SoulStream/handoff/rounds/ins-r22-taste-store.txt
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

81 lines
2.9 KiB
Plaintext

/* Taste: what this device has told the Soulseek server it likes and what
it hates, and the people the server ranks as close to it. The desktop
keeps all three in `community.svelte.js` and follows one rule — the
ranking only moves when its input does, so every declaration asks the
server again and the chips stay quiet until it has answered. The lists
are THIS CLIENT's: the backend keeps them and re-declares every one of
them at login, because the server's own copy belongs to the account and
another client signed in elsewhere can change it under us. */
import * as api from "../lib/api.js";
import { slsk } from "./slsk.svelte.js";
import { player } from "./player.svelte.js";
export const taste = $state({
/** the things declared, as the backend last answered */
likes: [],
hates: [],
/** null until asked; { users: [{ username, rating }] } after */
similar: null,
/** a declaration in flight, so the chips go quiet */
busy: false,
asking: false,
/** the server's word when it would not answer */
error: "",
});
/* the account these lists were read for — a plain variable, never $state:
the screen's effect is what calls loadTaste, and reading this inside it
would arm that effect on our own write */
let readFor = null;
/** What this device has declared. Once per account, unless forced. */
export async function loadTaste(force = false) {
const who = slsk.username || slsk.savedUser || "";
if (readFor === who && !force) return;
readFor = who;
try {
const v = await api.slskInterestsMine();
taste.likes = v?.likes ?? [];
taste.hates = v?.hates ?? [];
} catch (e) {
player.error = String(e);
}
await askSimilar(true);
}
/** Declare a thing, or take it back. The backend answers with both lists,
* and the server's match is asked again before the chips come back — it
* ranks on exactly this, so a second declaration must not overtake it. */
export async function setInterest(thing, like, on) {
const t = String(thing || "").trim();
if (!t || taste.busy) return false;
taste.busy = true;
try {
const v = await api.slskInterestSet(t, like, on);
taste.likes = v?.likes ?? [];
taste.hates = v?.hates ?? [];
await askSimilar(true);
return true;
} catch (e) {
player.error = String(e);
return false;
} finally {
taste.busy = false;
}
}
/** The people the server puts near us, by the likes we declared. */
export async function askSimilar(force = false) {
if ((taste.similar && !force) || taste.asking) return;
taste.asking = true;
taste.error = "";
try {
taste.similar = { users: (await api.slskSimilarUsers()) || [] };
} catch (e) {
taste.similar = { users: [] };
taste.error = String(e);
} finally {
taste.asking = false;
}
}