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.
83 lines
5.1 KiB
Plaintext
83 lines
5.1 KiB
Plaintext
## When things go wrong
|
|
|
|
Thirty rounds built the happy path. A phone is also in a lift, signed out, on
|
|
a dead cell, or holding a library folder that has gone away, and until this
|
|
round every one of those showed up as an empty box, a spinner with no end, or
|
|
a Rust string in a red toast.
|
|
|
|
**One translator**, `src/mobile/trouble.js`. `say(e)` takes what the backend
|
|
actually hands up — `ClientError`'s Display impls (`io: {0}`, `protocol: {0}`,
|
|
`login failed: INVALIDPASS`, `user {0} is offline or unknown`, `transfer
|
|
rejected: {0}`, `timed out`), the commands' own literals (`not connected to
|
|
Soulseek`, `no stored credentials`, `no cache dir`, `could not reach server:
|
|
{e}`), and rusqlite or `std::io` through `e.to_string()` — and answers with a
|
|
sentence a person can act on. The rule that matters: **the stores keep the raw
|
|
string and only the DRAWING is translated.** `player.error` still holds what
|
|
Rust said, Diagnostics and the walk still read it, and `say()` is called at the
|
|
one place the toast is written — which is why fifty-nine `String(e)` sinks were
|
|
fixed without touching fifty-nine call sites, and why the sixtieth is covered
|
|
too. A message the backend already wrote in English ("The playlist needs a
|
|
name") matches no rule and passes through untouched.
|
|
|
|
The worst one it retires was on the sign-in card: a phone with no DNS drew, in
|
|
red under the password box, `could not reach server: io: failed to lookup
|
|
address information: no address associated with hostname`. It now says
|
|
"Couldn't reach the Soulseek server. Check the connection and try again."
|
|
|
|
The red toast gained the notice's shape — the sentence, and one action beside
|
|
it. **Try again** when retrying is the answer (a resolve that found no sources,
|
|
a library read that failed at launch), **Sign in** when `signedOut()` says the
|
|
trouble is that nobody is. The action is tied to the exact message it belongs
|
|
to (`player.retry = { for, fn }`), so a later, different error can never
|
|
inherit the last one's button.
|
|
|
|
What each screen says now, where it used to say nothing or worse:
|
|
|
|
- **Tracks** — a rejected `library_track_index` drew "0 tracks · 0:00 · unable
|
|
to open database file" and then "No tracks match.", which is a failed read
|
|
dressed up as an empty library. Now: "Couldn't read the library index", the
|
|
translated reason, and a Try again. "No tracks match" only when the read
|
|
SUCCEEDED.
|
|
- **A playlist** — a rejected `playlist_items` said "This playlist is empty."
|
|
Empty now means empty; failed says so, with a Try again.
|
|
- **A catalog record** and **an artist page** — the catalog's own error is
|
|
translated, the record's stub offers a Try again, and the artist's stub says
|
|
"the catalog didn't answer" rather than "had nothing to add" when that is
|
|
what happened.
|
|
- **Gaps** (Library) — the lookup's `catch` was empty, so the line flipped back
|
|
to "See what's missing" and could be tapped for ever. It now says "Couldn't
|
|
reach the catalogue — tap to try again".
|
|
- **Home** — `home.svelte.js` sets `slot.error` and nothing read it: offline
|
|
with follows, Home drew empty rails and said nothing. One line under the lead
|
|
now does, and points at the library below.
|
|
- **Search** — the whole network section sat inside `{#if online}` with no
|
|
`{:else}`, so signed out with a query typed there was no row and no reason,
|
|
just a gap. Now there is a line and a door to Community.
|
|
- **The lyrics face** — a lookup that FAILED was drawn as "No lyrics found for
|
|
this track.", and that branch was also the only one of the four with no `⋯`
|
|
button, so Paste lyrics was unreachable exactly when you wanted it. Both are
|
|
fixed: a failure says so, and both branches carry the `⋯`.
|
|
- **Transfers** — `failed · <rust>` on a row is `failed · <sentence>`.
|
|
|
|
**Two spinners were given an end.** `request_room_list` is fire-and-forget over
|
|
the wire (crates/soulseek-client/src/lib.rs — the client sends `room_list` and
|
|
there is no reply channel and no timeout), so "Asking the server for its
|
|
rooms…" could sit there for ever. `slsk.roomsWaitMs` (12 s) arms a timer in
|
|
`refreshRooms()`, the `soulseek:room-list` handler clears it, and a server that
|
|
never answers gets "The server hasn't sent its room list." and a Try again.
|
|
Tracks' own "Reading the index…" has its failure branch above. Everything else
|
|
was measured and left alone because it is already bounded: `user_info` 30 s,
|
|
`user_interests` 20 s, a browse 45 s, a login 15 s, a net search 15 s.
|
|
|
|
**One tap could wipe the library.** `rescan_library` (src-tauri/src/pipeline.rs)
|
|
ran `prune_missing()` FIRST, and `prune_missing` deletes every track row whose
|
|
path does not `exists()`, then every album left with no tracks. With the root
|
|
unreachable — an unmounted card, a revoked SAF grant, the drive-letter class of
|
|
trouble — You → **Rescan library** emptied the whole index, silently, and
|
|
returned `Ok(())`. There is now a guard above the prune: make the root if it is
|
|
merely not there yet (a fresh install still works), and refuse with
|
|
`library-folder-missing: <path>` if it cannot be made or read. You catches that
|
|
into a row under Library folder with the path and what to do about it, and the
|
|
toast says it in English.
|
|
|