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.
81 lines
2.9 KiB
Plaintext
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;
|
|
}
|
|
}
|