Files
SoulStream/handoff/rounds/ins-r21-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

71 lines
2.9 KiB
Plaintext

/* The local view of a person on the network: a petname, a group, a private
note and the two marks. All of it lives in this app's own config and is
published nowhere — Soulseek never learns it, and neither does the person
it describes. The desktop keeps the same index by username
(src/lib/community.svelte.js), and the same rule: the backend drops an
entry with nothing set, so the list it answers with is the one we take. */
import * as api from "../lib/api.js";
import { player } from "./player.svelte.js";
export const contacts = $state({
/** username → { username, petname, group, note, trusted, favorite } */
by: {},
/** the list came back; false until it does, and after a failure */
loaded: false,
error: "",
});
const BLANK = { petname: null, group: null, note: null, trusted: false, favorite: false };
/** what the backend stores: a trimmed string, or nothing at all */
const trimmed = (v) => { const s = typeof v === "string" ? v.trim() : v; return s ? s : null; };
function index(list) {
contacts.by = Object.fromEntries((list || []).map((c) => [c.username, c]));
contacts.loaded = true;
contacts.error = "";
}
let inflight = null;
/** Read the whole list — once at boot, and again whenever anyone asks. A
* failure is quiet: a petname is an enhancement, not the screen. */
export function loadContacts() {
if (inflight) return inflight;
inflight = api
.contactsMetaList()
.then(index)
.catch((e) => { contacts.loaded = false; contacts.error = String(e); })
.finally(() => { inflight = null; });
return inflight;
}
export const metaOf = (u) => contacts.by[u] ?? { username: u, ...BLANK };
/** The name to show: the petname when there is one, the username otherwise. */
export const display = (u) => contacts.by[u]?.petname || u;
/** The username, but only while a petname stands in front of it — a stranger
* is never quietly turned into someone you know. */
export const realName = (u) => (contacts.by[u]?.petname ? u : "");
/** Upsert one person. The answer is the whole list after the write, so it is
* taken as it comes rather than patched into our own copy: that is how an
* entry with nothing left in it disappears. A failure goes to the bar. */
export async function setMeta(username, patch) {
const m = { ...metaOf(username), ...patch };
try {
index(
await api.contactMetaSet({
username,
petname: trimmed(m.petname),
group: trimmed(m.group),
note: trimmed(m.note),
trusted: !!m.trusted,
favorite: !!m.favorite,
}),
);
} catch (e) {
player.error = String(e);
}
}
/** Everything blank: the entry leaves the list rather than being stored empty. */
export const clearMeta = (u) => setMeta(u, { petname: "", group: "", note: "", trusted: false, favorite: false });