Files
SoulStream/handoff/rounds/patch-round31.cjs
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

537 lines
29 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Round thirty-one: what the phone says when things go wrong. Thirty rounds
// built the happy path; this one is the other. 59 sites in src/mobile/ put a
// raw Rust string into a field a person reads (53 of them into player.error,
// which the red toast drew verbatim — "could not reach server: io: failed to
// lookup address information" under the password box, in a lift). One
// translator, src/mobile/trouble.js, routed through the places that already
// DRAW an error, fixes all of them at once and keeps the raw string in the
// stores. Five screens that drew a failed read as an empty answer are told to
// stop, two spinners with no end are given one, and rescan_library gets a
// guard: it ran prune_missing() FIRST, so one tap with the library folder
// unreachable emptied the whole index and returned Ok(()). The mock grows a
// weather system (fail / answer / offline / clear) so the walk can drive every
// one of these for the first time.
const fs = require('fs');
const root = process.env.SS_ROOT || 'F:/Coding/SoulStream/';
const scratch = 'C:/Users/Kyle/AppData/Local/Temp/claude/F--Coding-SoulStream/5488514a-11e8-482a-b0a0-20cb511ee110/scratchpad/';
const ins = (name) => fs.readFileSync(scratch + name, 'utf8').replace(/\s+$/, '');
let p, s;
const load = (file) => { p = file; s = fs.readFileSync(root + p, 'utf8'); };
const save = () => fs.writeFileSync(root + p, s);
const once = (a) => {
const i = s.indexOf(a);
if (i < 0) throw new Error('missing in ' + p + ': ' + a.slice(0, 90));
if (s.indexOf(a, i + 1) >= 0) throw new Error('ambiguous in ' + p + ': ' + a.slice(0, 90));
return i;
};
const rep = (a, b) => { once(a); s = s.replace(a, () => b); };
const NL = String.fromCharCode(10);
/* The shell.css block is an APPEND with no anchor, and several reps below
re-emit their own anchors, so a second run would double-write where `once`
cannot see it and only throw further down, leaving half a tree. The file
this round CREATES is the one thing that cannot be half-there. */
if (fs.existsSync(root + 'src/mobile/trouble.js')) {
throw new Error('round 31 is already applied in ' + root + ' — reset the tree rather than re-running');
}
// ── the translator ────────────────────────────────────────────────────
fs.writeFileSync(root + 'src/mobile/trouble.js', ins('ins-r31-trouble.txt') + NL);
// ── the look: the error toast gets the notice's shape ─────────────────
load('src/mobile/shell.css');
s = s.replace(/\s*$/, NL) + NL + ins('ins-r31-css.txt') + NL;
save();
// ── the toast, once, for all 59 sinks ─────────────────────────────────
load('src/mobile/Mobile.svelte');
rep(`import { notice, clearNote } from "./notice.svelte.js";`,
`import { notice, clearNote } from "./notice.svelte.js";
import { say, signedOut } from "./trouble.js";`);
rep(`import { slsk, bindSlsk, connectStored, unreadTotal } from "./slsk.svelte.js";`,
`import { slsk, bindSlsk, connectStored, refreshRooms, unreadTotal } from "./slsk.svelte.js";`);
rep(` 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(". "));`);
/* NOT the boot block: round 34 rewrites those five lines wholesale (the
first-run tour reads the config there), and an effect is the better home
anyway — lib.error is set again by every later reload, You → Rescan
library included. */
rep(` /* ── the card gets the cover once the thumbnail has landed ───────── */`,
` /* ── 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 ───────── */`);
rep(` {#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>
{/if}`,
` {#if player.error || lib.error}
{@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}`);
rep(`patch, contacts, loadContacts };`, `patch, contacts, loadContacts, say, refreshRooms };`);
save();
// ── the store keeps the raw string; the retry knows which one it is for ──
load('src/mobile/player.svelte.js');
rep(` error: "",
});`,
` error: "",
/* the toast's one action, tied to the message it belongs to:
{ for: <the exact error>, fn } — so a later, different error can never
inherit the last one's Try again */
retry: null,
});`);
save();
// ── the room list is fire-and-forget over the wire: give it an end ────
load('src/mobile/slsk.svelte.js');
rep(` /** the room or the person whose screen is open, so its unread stays 0 */
openChat: null,
});`,
` /** the room or the person whose screen is open, so its unread stays 0 */
openChat: null,
/** The server's room list is FIRE-AND-FORGET over the wire: the client
* sends \`room_list\` and there is no reply channel and no timeout
* (crates/soulseek-client/src/lib.rs), so a server that never answers
* left "Asking the server for its rooms…" up for ever. The wait is ours.
* \`roomsWaitMs\` is a plain field so the walk can shorten it rather than
* the harness needing a back door — which does mean the mechanism is
* proved at 1.2 s and the shipped 12 s is a judgement, not a measurement. */
roomsWaitMs: 12000,
roomsLate: false,
});`);
rep(`export function refreshRooms() {
api.slskRoomList().catch(() => {});
}`,
`let roomsTimer = null;
export function refreshRooms() {
slsk.roomsLate = false;
clearTimeout(roomsTimer);
roomsTimer = setTimeout(() => { if (!slsk.rooms.length) slsk.roomsLate = true; }, slsk.roomsWaitMs);
/* a refused request is a late list at once — there is nothing to wait for */
api.slskRoomList().catch(() => { clearTimeout(roomsTimer); slsk.roomsLate = true; });
}`);
/* the handler's BODY, not its `await on("soulseek:room-list",` line: round 32
drops the await from every one of those subscriptions, and this line is the
same either way round */
rep(` slsk.rooms = (rooms || []).slice().sort((a, b) => (b.users || 0) - (a.users || 0));`,
` clearTimeout(roomsTimer);
slsk.roomsLate = false;
slsk.rooms = (rooms || []).slice().sort((a, b) => (b.users || 0) - (a.users || 0));`);
save();
// ── a lyrics lookup that FAILED is not a track with no words ──────────
load('src/mobile/lyrics.svelte.js');
rep(` reduced: null, // { from, to } once one has been made for this track
});`,
` reduced: null, // { from, to } once one has been made for this track
/* the lookup itself failed, as opposed to finding nothing: the face has
said the wrong one of those since it was written */
failed: "",
});`);
rep(`candidates: null, searching: false });`, `candidates: null, searching: false, failed: "" });`);
rep(` } catch {
if (g === gen) Object.assign(lyr, { status: "none", lines: [], plain: "" });
}`,
` } catch (e) {
if (g === gen) Object.assign(lyr, { status: "none", lines: [], plain: "", failed: String(e) });
}`);
save();
// ── a failed transfer says why in English ─────────────────────────────
load('src/mobile/transfers.svelte.js');
rep(`import { listen } from "../lib/events.js";
export const transfers = $state({`,
`import { listen } from "../lib/events.js";
import { say } from "./trouble.js";
export const transfers = $state({`);
rep(' case "failed": return d.error ? `failed · ${d.error}` : "failed";',
' case "failed": return d.error ? `failed · ${say(d.error)}` : "failed";');
save();
// ── the resolver's failures carry a Try again ─────────────────────────
load('src/mobile/resolve.svelte.js');
rep(` player.status = "buffering";
player.phase = "loading";
player.error = "";
}`,
` player.status = "buffering";
player.phase = "loading";
player.error = "";
player.retry = null;
}`);
rep(`function fail(msg) {
resolver.error = msg;
player.error = msg;`,
`function fail(msg, retry = null) {
resolver.error = msg;
player.error = msg;
/* a Try again on the toast, tied to THIS message: the ladder is worth
another run when the network simply had nothing this time. Signed out it
is not, and the toast offers Community there instead. */
player.retry = retry ? { for: msg, fn: () => { void retry().catch((e) => { player.error = String(e); }); } } : null;`);
rep(' if (!cands.length) { fail(`No streamable sources found for “${title}”`); return; }',
' if (!cands.length) { fail(`No streamable sources found for “${title}”`, () => streamSong(artist, title, o)); return; }');
rep(' if (!folders.length) { fail(`No streamable sources found for “${album.title}”`); return; }',
' if (!folders.length) { fail(`No streamable sources found for “${album.title}”`, () => streamCatalogAlbum(album, at)); return; }');
save();
// ── the lyrics face: a failure says so, and BOTH quiet branches get the ⋯
// (Paste lyrics was unreachable exactly when you wanted it) ─────────
load('src/mobile/Sheet.svelte');
rep(` {:else}
<div class="lyr-note">No lyrics found for this track.</div>
{/if}`,
` {:else if lyr.failed}
<div class="lyr-note">Couldn't look up the words — the lookup didn't answer.</div>
<button class="lyr-tools" onclick={() => openHalf("lyrics")}>Try again, or paste them ⋯</button>
{:else}
<div class="lyr-note">No lyrics found for this track.</div>
<button class="lyr-tools" onclick={() => openHalf("lyrics")}>Paste them, or search ⋯</button>
{/if}`);
save();
// ── Tracks: a failed read was drawn as an empty library ───────────────
load('src/mobile/screens/Tracks.svelte');
/* every import below anchors on the line ABOVE the util.js one, never on it:
round 33 rewrites `} from "../util.js";` in four of these files. */
rep(` import { openHalf, patch } from "../nav.svelte.js";`,
` import { openHalf, patch } from "../nav.svelte.js";
import { say } from "../trouble.js";`);
rep(` api.libraryTrackIndex().then(
(rows) => { index = rows || []; },
(e) => { index = []; failed = String(e); },
);
});`,
` api.libraryTrackIndex().then(
(rows) => { index = rows || []; failed = ""; },
(e) => { index = []; failed = String(e); },
);
});
/* a rejected read used to draw "0 tracks · 0:00 · unable to open database
file" and "No tracks match." under it — a failure dressed as an empty
library. Putting the index back to null re-runs the effect above. */
function reread() { failed = ""; index = null; }`);
rep(` {#if index === null}Reading the index…{:else}{index.length} tracks · {longDur(total)}{/if}
{#if list.length > CAP} · showing {CAP} of {list.length}{/if}
{#if failed} · {failed}{/if}`,
` {#if index === null}Reading the index…{:else if failed}The index could not be read.{:else}{index.length} tracks · {longDur(total)}{/if}
{#if !failed && list.length > CAP} · showing {CAP} of {list.length}{/if}`);
rep(`{#if index && !list.length}
<div class="stub"><b>No tracks match.</b></div>
{/if}`,
`{#if failed}
<div class="stub why"><b>Couldn't read the library index.</b>{say(failed)}</div>
<div class="imp-foot"><button class="btn btn-ghost wide" onclick={reread}>Try again</button></div>
{:else if index && !list.length}
<div class="stub"><b>No tracks match.</b></div>
{/if}`);
save();
// ── a playlist that could not be READ is not an empty playlist ────────
load('src/mobile/screens/Playlist.svelte');
rep(` import { pls, bulk, cancelBulk, dismissBulk, coverFor, isSetlist, queueable, unresolvedCount, missingCount, totalSecs, playFrom, shufflePlay, rowClick } from "../playlists.svelte.js";`,
` import { pls, bulk, cancelBulk, dismissBulk, coverFor, isSetlist, queueable, unresolvedCount, missingCount, totalSecs, playFrom, shufflePlay, rowClick } from "../playlists.svelte.js";
import { say } from "../trouble.js";`);
rep(` let items = $state([]);
let loaded = $state(false);
let gen = 0;
$effect(() => {
const p = pl;
if (!p) return;
void p.revision;
const g = ++gen;
api.playlistItems(p.id).then(
(r) => { if (g === gen) { items = r || []; loaded = true; } },
(e) => { if (g === gen) { loaded = true; player.error = String(e); } },
);
});`,
` let items = $state([]);
let loaded = $state(false);
/* a rejected read used to leave items empty and loaded true, so the page
said "This playlist is empty." about a playlist it had not read */
let loadErr = $state("");
let again = $state(0);
let gen = 0;
$effect(() => {
const p = pl;
if (!p) return;
void p.revision;
void again;
const g = ++gen;
api.playlistItems(p.id).then(
(r) => { if (g === gen) { items = r || []; loaded = true; loadErr = ""; } },
(e) => { if (g === gen) { loaded = true; loadErr = String(e); } },
);
});`);
rep(` {#if loaded && !items.length}
<div class="stub"><b>This playlist is empty.</b>`,
` {#if loaded && loadErr}
<div class="stub why"><b>Couldn't read this playlist.</b>{say(loadErr)}</div>
<div class="imp-foot"><button class="btn btn-ghost wide" onclick={() => { loadErr = ""; again++; }}>Try again</button></div>
{:else if loaded && !items.length}
<div class="stub"><b>This playlist is empty.</b>`);
save();
// ── the catalog record: the reason in English, and a way to ask again ──
load('src/mobile/screens/CatalogAlbum.svelte');
rep(` import { releaseFamilies, editionLabel, splitDescription } from "../../lib/editions.js";`,
` import { releaseFamilies, editionLabel, splitDescription } from "../../lib/editions.js";
import { say } from "../trouble.js";`);
rep(` <div class="stub"><b>The catalog did not answer.</b>{entry.error}</div>
{/if}`,
` <div class="stub why"><b>The catalog did not answer.</b>{say(entry.error)}</div>
<div class="imp-foot"><button class="btn btn-ghost wide" onclick={() => lookupAlbum(id)}>Try again</button></div>
{/if}`);
save();
// ── the artist page: "had nothing to add" was also said about a failure ──
load('src/mobile/screens/Artist.svelte');
rep(` <div class="stub"><b>Nothing here yet.</b>No records by {shownName} on this phone, and the catalog had nothing to add.</div>`,
` <div class="stub"><b>Nothing here yet.</b>No records by {shownName} on this phone, and the catalog {entry?.status === "error" ? "didn't answer" : "had nothing to add"}.</div>`);
save();
// ── Gaps: the empty catch that could be tapped for ever ───────────────
load('src/mobile/screens/Library.svelte');
rep(` let gapLoading = $state({});`,
` let gapLoading = $state({});
/* the lookup's catch was empty, so a failed reach put the line back to
"See what's missing" and it could be tapped for ever with no reason */
let gapErr = $state({});`);
rep(` gapLoading = { ...gapLoading, [key]: true };
try {`,
` gapLoading = { ...gapLoading, [key]: true };
gapErr = { ...gapErr, [key]: "" };
try {`);
rep(` } catch { /* the line stays collapsed; nothing is invented */ }`,
` } catch (e) { gapErr = { ...gapErr, [key]: String(e) }; }`);
rep(` {#if gapLoading[key]}Checking the catalogue…{:else if count != null}<b>{count}</b> more released{:else}See what's missing{/if}`,
` {#if gapLoading[key]}Checking the catalogue…{:else if gapErr[key]}Couldn't reach the catalogue — tap to try again{:else if count != null}<b>{count}</b> more released{:else}See what's missing{/if}`);
save();
// ── Home: slot.error has been set since it was written and never read ──
load('src/mobile/screens/Home.svelte');
rep(` const slots = $derived(home.order.map((id) => home.slots[id]).filter(Boolean));`,
` const slots = $derived(home.order.map((id) => home.slots[id]).filter(Boolean));
/* every follow's slot came back empty-handed. home.svelte.js has always
set slot.error on a failed catalog page and nothing has ever read it, so
offline with follows Home drew empty rails and said nothing at all. */
const catalogDown = $derived(lib.follows.length > 0 && slots.length > 0 && slots.every((s) => s.error));`);
/* the rail below the lead, not the empty-library stub above it: round 34
rewrites that stub's copy and puts its own note inside it */
rep(`{#if feedRest.length}
<div class="sec"><h2>New from your artists`,
`{#if catalogDown}
<div class="sec-note cat-down">Couldn't reach the catalog, so there's nothing new from your artists here. What the library holds is below.</div>
{/if}
{#if feedRest.length}
<div class="sec"><h2>New from your artists`);
save();
// ── You: the rescan can refuse now, and it says which folder ──────────
load('src/mobile/screens/You.svelte');
rep(` let scanning = $state(false);`,
` let scanning = $state(false);
/* the library root the backend could not reach, out of its refusal */
let folderGone = $state("");`);
rep(` async function rescan() {
if (scanning) return;
scanning = true;
try { await api.rescanLibrary(); await loadLibrary(); } catch (e) { player.error = String(e); }
scanning = false;
}`,
` async function rescan() {
if (scanning) return;
scanning = true;
folderGone = "";
try { await api.rescanLibrary(); await loadLibrary(); }
catch (e) {
/* the backend refuses a rescan whose root it cannot reach
(src-tauri/src/pipeline.rs): the prune inside it would otherwise
delete every row whose file it could not see, which is the whole
library when the card is out */
const msg = String(e);
const TAG = "library-folder-missing:";
const at = msg.indexOf(TAG);
if (at >= 0) folderGone = msg.slice(at + TAG.length).trim();
player.error = msg;
}
scanning = false;
}`);
rep(` <button class="row" onclick={rescan}>
<span class="lead"><Icon name="refresh" size={20} /></span>
<span class="b"><span class="t">{scanning ? "Scanning…" : "Rescan library"}</span><span class="m">looks for new files in the folder, forgets the ones that are gone</span></span>
</button>`,
` <button class="row" onclick={rescan}>
<span class="lead"><Icon name="refresh" size={20} /></span>
<span class="b"><span class="t">{scanning ? "Scanning…" : "Rescan library"}</span><span class="m">looks for new files in the folder, forgets the ones that are gone</span></span>
</button>
{#if folderGone}
<div class="row folder-gone">
<span class="lead"><Icon name="alert" size={20} /></span>
<span class="b"><span class="t">The library folder isn't there</span><span class="m mono">{folderGone}</span></span>
</div>
<div class="sec-note" style="padding: 0 14px 12px">Nothing was forgotten — the rescan refused rather than dropping every record it could not see. Reconnect the storage, or pick the folder again above.</div>
{/if}`);
save();
// ── Community: the most visible raw-Rust site on the phone ────────────
load('src/mobile/screens/Community.svelte');
rep(` import { taste, loadTaste, setInterest, askSimilar } from "../taste.svelte.js";`,
` import { taste, loadTaste, setInterest, askSimilar } from "../taste.svelte.js";
import { say } from "../trouble.js";`);
rep(`{#if slsk.loginError}<div class="signin-err">{slsk.loginError}</div>{/if}`,
`{#if slsk.loginError}<div class="signin-err">{say(slsk.loginError)}</div>{/if}`);
rep(` <div class="stub"><b>The server did not answer.</b>{taste.error}</div>`,
` <div class="stub"><b>The server did not answer.</b>{say(taste.error)}</div>`);
rep(` {#if !slsk.rooms.length}
<div class="sec-note">Asking the server for its rooms…</div>`,
` {#if !slsk.rooms.length}
{#if slsk.roomsLate}
<!-- request_room_list is fire-and-forget over the wire, so this
line used to sit here for ever against a server that
simply never answered -->
<div class="sec-note rooms-note">The server hasn't sent its room list.</div>
<div class="imp-foot"><button class="btn btn-ghost wide" onclick={refreshRooms}>Try again</button></div>
{:else}
<div class="sec-note">Asking the server for its rooms…</div>
{/if}`);
save();
// ── Search: signed out, the network section rendered NOTHING ──────────
load('src/mobile/screens/Search.svelte');
rep(` import { latestOf } from "../disco.js";`,
` import { latestOf } from "../disco.js";
import { say } from "../trouble.js";`);
rep(` import { push, openHalf, patch } from "../nav.svelte.js";`,
` import { push, go, openHalf, patch } from "../nav.svelte.js";`);
rep(` {#if catErr}<div class="sec-note">{catErr}</div>{/if}`,
` {#if catErr}<div class="sec-note">{say(catErr)}</div>{/if}`);
rep(` {#if net.error}<div class="sec-note">{net.error}</div>{/if}`,
` {#if net.error}<div class="sec-note">{say(net.error)}</div>{/if}`);
rep(` {/each}
{/if}
{/if}
{/if}
<div class="endpad"></div>`,
` {/each}
{/if}
{:else}
<!-- signed out, the whole network section rendered nothing at all: no
row where "Search the network" lives, and no reason for its
absence. The catalog and the library above still answer. -->
<div class="sec-note offline-note">Not signed in, so nothing here can play from the network yet — sign in on Community.</div>
<div style="padding: 4px var(--pad) 8px"><button class="btn btn-ghost wide" onclick={() => go("community")}>Go to Community</button></div>
{/if}
{/if}
<div class="endpad"></div>`);
save();
// ── the harness: the weather doors ────────────────────────────────────
load('src/lib/mockLibrary.js');
rep(`export function mockInvoke(cmd, args) {
if (!enabled) return undefined;`,
`export function mockInvoke(cmd, args) {
if (!enabled) return undefined;
/* the weather, BEFORE any dispatch, so a plugin: command and a catalog
command arm exactly the same way (see the doors below) */
const forecast = weather(cmd);
if (forecast) return forecast;`);
rep(`if (enabled && typeof window !== "undefined") window.__ssMock = { emit: mockEmit };`, ins('ins-r31-mock.txt'));
save();
// ── the walks ─────────────────────────────────────────────────────────
load('tests/mobile/smoke.mjs');
rep(` contactsLoaded: (function () { try { return __ss.contacts.loaded ? 'yes' : (__ss.contacts.error ? 'err' : 'no'); } catch (e) { return ''; } })(),`,
` contactsLoaded: (function () { try { return __ss.contacts.loaded ? 'yes' : (__ss.contacts.error ? 'err' : 'no'); } catch (e) { return ''; } })(),
` + ins('ins-r31-probe.txt'));
/* the array's end, which is where round 34 appends too — both re-emit it, so
either order works and whichever lands second simply runs last */
rep(`];
const MOCK_EXPECT = {`, ins('ins-r31-steps.txt') + NL + `];
const MOCK_EXPECT = {`);
rep(` "sleep-set": (i) => !i.half.includes("up"),`,
` "sleep-set": (i) => !i.half.includes("up"),
` + ins('ins-r31-expect.txt'));
rep(` { do: \`document.querySelectorAll('.nav button')[0].click()\`, name: "end", wait: 800 },`,
ins('ins-r31-phone-steps.txt') + NL + ` { do: \`document.querySelectorAll('.nav button')[0].click()\`, name: "end", wait: 800 },`);
rep(` end: (i) => /^(Good |Still)/.test(i.title),`,
ins('ins-r31-phone-expect.txt') + NL + ` end: (i) => /^(Good |Still)/.test(i.title),`);
save();
// ── the one-tap library wipe ──────────────────────────────────────────
load('src-tauri/src/pipeline.rs');
rep(`pub async fn rescan_library(app: tauri::AppHandle) -> Result<(), String> {
let state = app.state::<LibraryState>();`,
`pub async fn rescan_library(app: tauri::AppHandle) -> Result<(), String> {
// The prune below deletes every track row whose file does not exist, and
// then every album left with none — so a root that has gone away (an
// unmounted card, a revoked SAF grant, a drive letter that moved) used to
// empty the WHOLE index on one tap and return Ok(()). Make the folder if
// it is merely not there yet, so a fresh install still works, and refuse
// if it cannot be made or read. Same idiom as connect_and_login's.
let root = downloads_dir(&app);
if std::fs::create_dir_all(&root).is_err() || !root.is_dir() {
return Err(format!("library-folder-missing: {}", root.display()));
}
let state = app.state::<LibraryState>();`);
rep(` let root = downloads_dir(&app);
sweep_stale_partials(&root);`,
` sweep_stale_partials(&root);`);
save();
// ── the doc ───────────────────────────────────────────────────────────
load('ANDROID.md');
rep(`## The downloads in the shade`, ins('ins-r31-doc.txt') + NL + NL + `## The downloads in the shade`);
/* its own sentence, not a clause inside the walk's chain: round 34 rewrites
that chain's tail ("all at 915×411) and back, asserts …") wholesale */
rep(`any exception or console error. The mock answers every command the phone`,
`any exception or console error. The block at the end of the mock walk is the
weather: a library index that will not read and its Try again, the client taken
offline, a refused password and a server that cannot be reached, Search with
nowhere to search, a room list that never arrives, and a rescan that refuses
because the folder is gone — then all of it cleared and the app left as it was
found. The mock answers every command the phone`);
rep(`page after a confirmed delete was found). A mock step can also set the`,
`page after a confirmed delete was found). The mock is also the weather:
\`__ssMock.fail(cmd, message)\` arms a command to reject with that exact string,
\`answer(cmd, value)\` arms one to resolve with a value the walk names (an empty
list, a null page), \`offline(on)\` takes the client down — everything the
backend funnels through \`with_client()\` answers "not connected to Soulseek",
the two connects answer the server string, and \`soulseek:state\` goes out — and
\`clear()\` puts all of it back. They reject with a plain string because that is
what Tauri rejects with. Nine mock steps drive them, and the phone walk carries
one safe step of its own (\`trouble-words\`: eleven real backend strings through
\`__ss.say\` on the device, every output asserted free of Rust). A mock step can
also set the`);
save();
console.log('round 31 wired');