android: what the phone says when things go wrong
Fifty-nine places put a raw Rust string into a field a person reads, and the red toast drew it verbatim — a phone in a lift showed "could not reach server: io: failed to lookup address information" under the password box. One translator, src/mobile/trouble.js, called where an error is DRAWN rather than where it is caught: the stores keep what Rust said and only the sentence on screen is ours, so every sink is fixed at once. Five screens that drew a failed read as an empty answer stop lying — Tracks no longer reports a broken database as an empty library, a playlist that could not be read is not an empty playlist, the lyrics face says a lookup failed instead of "no lyrics" and finally carries the ⋯ that reaches Paste lyrics, Gaps says why it could not reach the catalogue, and Search signed out says so where it used to render nothing at all. The room list, which is fire-and-forget over the wire, gets a twelve-second end and a Try again. rescan_library ran prune_missing() first, which deletes every track row whose file does not exist — so one tap on You → Rescan library with the folder unreachable emptied the whole index and returned Ok(()). It now makes the root if it is merely not there yet and refuses if it cannot be read, and You draws the path with what to do about it. The mock grew a weather system so the walk can drive all of this: fail, answer, offline and clear, checked before dispatch so a plugin command arms like any other. Nine mock steps and one on the phone — the first steps in thirty rounds that ever made a command reject or saw the client signed out — and the last of them puts the weather away and leaves the app as it found it.
This commit is contained in:
1 parent
44eda8dff5
commit
052e15302b
22 files changed
+537
-35
No files matched your search
@@ -44,10 +44,11 @@
|
||||
import { loadPrefs } from "./prefs.svelte.js";
|
||||
import { catalog, artistKey } from "./catalog.svelte.js";
|
||||
import { bindMedia, syncMedia } from "./media.svelte.js";
|
||||
import { slsk, bindSlsk, connectStored, unreadTotal } from "./slsk.svelte.js";
|
||||
import { slsk, bindSlsk, connectStored, refreshRooms, unreadTotal } from "./slsk.svelte.js";
|
||||
import { net, bindNet } from "./net.svelte.js";
|
||||
import { resolver, streamSong, streamCatalogAlbum } from "./resolve.svelte.js";
|
||||
import { notice, clearNote } from "./notice.svelte.js";
|
||||
import { say, signedOut } from "./trouble.js";
|
||||
import * as pls from "./playlists.svelte.js";
|
||||
import { importer, bindImporter, startFolder } from "./importer.svelte.js";
|
||||
import { bindShare } from "./share.svelte.js";
|
||||
@@ -130,7 +131,7 @@
|
||||
The desktop does the same (FollowToast's always-mounted .layer,
|
||||
PreviewOverlay's .sr-announce). Never drawn — shell.css clips it to
|
||||
a pixel. */
|
||||
const announce = $derived([player.error || lib.error, notice.text, notice.action?.label].filter(Boolean).join(". "));
|
||||
const announce = $derived([say(player.error || lib.error), notice.text, notice.action?.label].filter(Boolean).join(". "));
|
||||
|
||||
/* ── the ground: two layers, the incoming one fades up over the old ── */
|
||||
let ambA = $state(null);
|
||||
@@ -157,6 +158,20 @@
|
||||
which is why that record is a plain object. */
|
||||
$effect(() => { syncShade(); });
|
||||
|
||||
/* ── a library read that failed gets a Try again ──────────────────
|
||||
The library is the one thing every screen stands on, and a rejected
|
||||
read left the app empty with a Rust string over it. The retry is wired
|
||||
HERE, where the toast is drawn, rather than in lib.svelte.js: that
|
||||
store reaching for the player store would close an import loop —
|
||||
player.svelte.js already reads lib. */
|
||||
$effect(() => {
|
||||
const e = lib.error;
|
||||
if (!e) return;
|
||||
untrack(() => {
|
||||
player.retry = { for: e, fn: () => { player.error = ""; lib.error = ""; player.retry = null; void loadLibrary(); } };
|
||||
});
|
||||
});
|
||||
|
||||
/* ── the card gets the cover once the thumbnail has landed ───────── */
|
||||
$effect(() => {
|
||||
const k = player.album?.art_path;
|
||||
@@ -233,7 +248,7 @@
|
||||
setTimeout(() => { api.libraryArtPrewarm([360]).catch(() => {}); }, 8000);
|
||||
/* the probe handle — the desktop has __soulstream for the same reason:
|
||||
tests/mobile/smoke.mjs drives the real app through it */
|
||||
window.__ss = { nav, top, go, push, back, openSheet, closeSheet, openHalf, closeHalf, player, lib, art, catalog, slsk, net, resolver, streamSong, streamCatalogAlbum, sources, api, transfers, xfn: xfernote, pls, notice, importer, imp: { startFolder }, wifi: { downloadsAllowed, cellular }, next, prev, toggle, seekSecs, playQueueAt, patch, contacts, loadContacts };
|
||||
window.__ss = { nav, top, go, push, back, openSheet, closeSheet, openHalf, closeHalf, player, lib, art, catalog, slsk, net, resolver, streamSong, streamCatalogAlbum, sources, api, transfers, xfn: xfernote, pls, notice, importer, imp: { startFolder }, wifi: { downloadsAllowed, cellular }, next, prev, toggle, seekSecs, playQueueAt, patch, contacts, loadContacts, say, refreshRooms };
|
||||
return () => { if (unlisten) unlisten(); };
|
||||
});
|
||||
</script>
|
||||
@@ -356,7 +371,19 @@
|
||||
say nothing on their own — two announcers say everything twice -->
|
||||
<div class="sr-live" data-live aria-live="polite" aria-atomic="true">{announce}</div>
|
||||
{#if player.error || lib.error}
|
||||
<div class="toast" onclick={() => { player.error = ""; lib.error = ""; }} role="button" tabindex="0" onkeydown={() => {}} aria-label="Dismiss this message">{player.error || lib.error}</div>
|
||||
{@const msg = player.error || lib.error}
|
||||
<!-- the notice's shape, in the red: the sentence trouble.js makes of
|
||||
whatever Rust said, and one action beside it where there is one to
|
||||
offer — Sign in when nobody is, else the Try again that was armed
|
||||
for THIS message and no other -->
|
||||
<div class="toast" onclick={() => { player.error = ""; lib.error = ""; player.retry = null; }} role="button" tabindex="0" onkeydown={() => {}} aria-label="Dismiss this message">
|
||||
<span class="nt">{say(msg)}</span>
|
||||
{#if signedOut(msg)}
|
||||
<button class="na" onclick={(e) => { e.stopPropagation(); player.error = ""; lib.error = ""; player.retry = null; go("community"); }}>Sign in</button>
|
||||
{:else if player.retry?.for === msg}
|
||||
<button class="na" onclick={(e) => { e.stopPropagation(); const r = player.retry; player.error = ""; lib.error = ""; player.retry = null; r.fn(); }}>Try again</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{#if notice.text}
|
||||
<div class="toast notice">
|
||||
|
||||
Reference in new issue
Block a user