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.
71 lines
4.4 KiB
Plaintext
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));
|