Files
SoulStream/handoff/rounds/ins-r31-trouble.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
4.4 KiB
Plaintext

/* What the phone says when things go wrong.
The backend answers in Rust. `ClientError`'s Display impls (crates/
soulseek-client/src/lib.rs) hand up `io: {0}`, `protocol: {0}`, `login
failed: INVALIDPASS`, `user {0} is offline or unknown`, `timed out`; the
commands add their own literals (`not connected to Soulseek`, `no stored
credentials`, `no cache dir`, `could not reach server: {e}`); rusqlite and
std::io come through `e.to_string()`. Fifty-nine places in src/mobile/ put
one of those straight into a field a person reads, and the red toast drew
it verbatim — a phone in a lift showed, under the password box, "could not
reach server: io: failed to lookup address information: no address
associated with hostname".
ONE translator, called where the words are DRAWN rather than where they
are caught. The stores keep the raw string — Diagnostics and the walk still
see exactly what Rust said — and only the sentence on screen is ours, so
every future `String(e)` is covered by the same rule as the fifty-nine
already here. A message the backend already wrote in English ("The playlist
needs a name") matches nothing and passes through untouched.
The order below is the order they are tried: the most specific shape
first, the two catch-alls (`io:` / `protocol:`) last but one, and anything
unrecognised kept as it is, capped. */
/** The message inside whatever was thrown: an Error, a plain string (which
* is what Tauri rejects with), or something with neither. */
const clean = (e) => String(e?.message ?? e ?? "").replace(/^Error:\s*/, "").trim();
const RULES = [
/* this round's own guard (src-tauri/src/pipeline.rs): the library root
could not be made or read, so the rescan refused rather than pruning
every row whose file it could not see */
[/^library-folder-missing:\s*(.*)$/i, (m) => `The library folder isn't there any more — ${m[1]}. Reconnect the storage, or pick the folder again in You.`],
/* with_client()'s funnel, and start_search's plainer version */
[/not connected( to (soulseek|server))?$/i, () => "Not signed in to Soulseek. Sign in on Community and try again."],
[/^no stored credentials$/i, () => "No Soulseek account saved on this phone yet. Sign in on Community."],
/* the server's own refusal words, which are not for reading */
[/^login failed:\s*(.*)$/i, (m) => (/invalidpass/i.test(m[1]) ? "Soulseek refused that password." : /invalidusername/i.test(m[1]) ? "Soulseek refused that username." : `Soulseek refused the sign-in (${m[1]}).`)],
[/^could not reach server/i, () => "Couldn't reach the Soulseek server. Check the connection and try again."],
[/^timed out$/i, () => "The network didn't answer in time."],
[/^user (.+?) is offline or unknown$/i, (m) => `${m[1]} isn't online right now.`],
[/^could not reach peer (.+)$/i, (m) => `${m[1]}'s client didn't answer — offline, or behind a firewall.`],
[/^transfer rejected:\s*(.*)$/i, (m) => `They refused the transfer${m[1] ? ` (${m[1].replace(/\.$/, "")})` : ""}.`],
[/^(io|protocol):\s*(.*)$/i, () => "The network didn't answer. Check the connection and try again."],
/* the library's own two: a database that will not open, and a folder the
phone has not been given */
[/unable to open database file|os error (2|3)\b|cannot find the (path|file)/i, () => "The library folder isn't readable right now."],
[/permission denied|os error 13\b/i, () => "The phone refused access to that folder — check the app's storage permission in You."],
[/^no cache dir$/i, () => "This phone has no cache folder to work in."],
/* a page with no Tauri under it — the browser walk, and only ever that */
[/reading 'invoke'|transformCallback/i, () => "The app's backend isn't here (this is the browser walk)."],
];
/** Longer than this and it was never a sentence anyone meant to write. */
const CAP = 180;
/** The backend's error → something a person can act on. */
export function say(e) {
const raw = clean(e);
if (!raw) return "";
for (const [re, fn] of RULES) {
const m = re.exec(raw);
if (m) return fn(m);
}
return raw.length > CAP ? raw.slice(0, CAP - 1) + "…" : raw;
}
/** Is this the kind of trouble Community fixes? The two places that offer a
* way to sign in rather than a Try again ask here. */
export const signedOut = (e) => /not connected|no stored credentials/i.test(clean(e));