android: the repair sheet, browse, every long-press sheet, You's last sections, the lyrics tools — and the walk's audio check made honest

The playlist repair (Find a local match: the library searched by the
row's name, a pick relinks it through playlist_relink_item; an unknown
row takes the track's identity). A peer's sheet on the raw results (play
or save the folder, browse their files, message, buddy) and the Browse
screen — the hub's Browse tab: a user's whole share with sizes and totals,
a folder played or saved whole; a share can hold thousands of folders, so
it filters and renders 80 at a time. "Look up a user" on Community →
Buddies. The sheets the phone lacked: a queue row's (Play now, Move to
next, Remove from queue), a catalog song's, a catalog card's and the
record page's own, a raw file's (in the results and in Browse), a room
name's, a buddy's, the now-playing Save (the desktop's Save menu for a
streamed track or record). The artwork viewer with pinch zoom, one layer
the back gesture peels. Search's recent searches and its spelling
suggestion from the names this phone knows. You → Streaming, Downloads,
Sharing, Lyrics, Album artwork, and Clear play history; a record's
Re-fetch the cover. The lyrics tools on the sheet's face: the offset
nudged and kept per track, another LRCLIB candidate chosen, the lookup
forgotten and done again (lyrics.svelte.js, shared by the face and the
lyrics sheet).

The harness: every CDP call times out and the reload step re-attaches
(a WebView that stopped answering hung a walk for ten minutes); the
audio check counts SoulStream's own tracks' underruns from the mixer dump
instead of the thread-wide counters that any app's notification moves —
the afternoon's "dropouts" were other apps' sounds, and the browse of a
49,544-file share measured at zero; __settle resets every tab to its root
through the nav; an adb step kind; the walks cover all of the above on
the mock and on the phone.
This commit is contained in:
KeelrLeek committed 2026-09-13 19:56:20 -05:00
1 parent 6e39dc43f3
commit 26928b706d
21 files changed
+1294 -92

No files matched your search

+62 -1
View File
@@ -124,6 +124,14 @@ function plTouch(p) {
setTimeout(() => mockEmit("soulseek:playlists-changed", {}), 30);
}
const plOf = (args) => PL.find((p) => p.id === Number(args?.id ?? args?.playlistId));
/* every track has three timed lines; the sheet's tools have something to act on */
function mockLyrics(title) {
return {
status: "synced", source: "lrclib",
synced: [{ time_ms: 0, text: "Here is " + (title || "a song") }, { time_ms: 4000, text: "Every word in time" }, { time_ms: 9000, text: "And the last one, late" }],
plain: null, offset_ms: 0, auto_offset_ms: 0, auto_sync_off: false,
};
}
function mockPlaylists(cmd, args) {
if (cmd === "list_playlists") return Promise.resolve(PL.map((p) => ({ ...p })));
if (cmd === "playlist_items") return new Promise((r) => setTimeout(() => r((PL_ITEMS[Number(args?.id)] || []).map((x) => ({ ...x }))), 80));
@@ -177,6 +185,19 @@ function mockPlaylists(cmd, args) {
plTouch(p);
return Promise.resolve(rows.map((r) => r.id));
}
if (cmd === "playlist_relink_item") {
const p = plOf(args);
const it = p ? (PL_ITEMS[p.id] || []).find((x) => x.id === Number(args?.itemId)) : null;
if (!it) return Promise.reject(new Error("playlist_relink_item: no such row"));
if (args.path != null) { it.path = args.path; it.kind = "local"; it.missing = false; }
if (args.artPath != null) it.art_path = args.artPath;
if (args.durationSecs != null) it.duration_secs = args.durationSecs;
if (args.title != null) it.title = args.title;
if (args.artist != null) it.artist = args.artist;
if (args.album != null) it.album = args.album;
plTouch(p);
return Promise.resolve();
}
return undefined;
}
@@ -462,6 +483,7 @@ function mockCatalog(cmd, args) {
}
if (cmd === "cancel_download") return Promise.resolve();
if (cmd === "rescan_library") return Promise.resolve();
if (cmd === "refresh_all_album_art") return new Promise((r) => setTimeout(() => r(140), 300));
if (cmd === "catalog_art") return Promise.resolve(svgArt(200, "D"));
if (cmd === "stream_cache_ready") return Promise.resolve(cacheReady.has(args?.path) || (xfer && xfer.path === args?.path && xfer.received >= xfer.total));
if (cmd === "preload_stream") {
@@ -495,6 +517,17 @@ let downloadSeq = 0;
const mockSettings = {
download_dir: "C:\\mock\\Library",
volume_normalization: true,
library_layout: "{artist}\\{album}",
shared_folders: [],
upload_slots: 2,
share_files: 1234,
word_timing: false,
replace_embedded_art: true,
embed_art_max_edge: 1500,
write_folder_cover: true,
stream_keep_forever: false,
stream_retention_days: 30,
stream_cache_max_mb: 2048,
stream_source_prefs: { max: "any", min: "any", priority: "quality" },
download_source_prefs: { max: "any", min: "any", priority: "quality" },
};
@@ -533,6 +566,25 @@ function mockNet(cmd, args) {
later(900, () => mockEmit("soulseek:private-message", { username: args?.username, message: "sure, sending", timestamp: Math.floor(Date.now() / 1000) }));
return Promise.resolve();
}
if (cmd === "slsk_browse_user") {
/* a share: two records and a folder of scans, in the browse DTO's shape */
const dir = (album, ext, bitrate) => {
const folder = "@@shared\\Music\\Daft Punk\\Daft Punk - 2001 - " + album + (ext === "flac" ? " [FLAC]" : " [MP3 " + bitrate + "]");
return {
name: folder,
files: TITLES.slice(0, album === "Discovery" ? 14 : 8).map((t, i) => {
const name = String(i + 1).padStart(2, "0") + " - " + t + "." + ext;
const duration = 200 + ((i * 37) % 180);
return { name, path: folder + "\\" + name, size: ext === "flac" ? duration * 110000 : duration * bitrate * 125, ext, bitrate: ext === "flac" ? null : bitrate, duration };
}),
};
};
return new Promise((r) => setTimeout(() => r([
dir("Discovery", "flac"),
dir("Homework", "mp3", 320),
{ name: "@@shared\\Music\\Scans", files: [{ name: "front.jpg", path: "@@shared\\Music\\Scans\\front.jpg", size: 1200000, ext: "jpg", bitrate: null, duration: null }] },
]), 600));
}
if (cmd === "start_search") {
const t = ++token;
const q = String(args?.query || "");
@@ -548,7 +600,16 @@ function mockNet(cmd, args) {
const n = args?.buckets || 900;
return new Promise((r) => setTimeout(() => r(Array.from({ length: n }, (_, i) => 0.2 + 0.75 * Math.abs(Math.sin(i * 0.23) * Math.cos(i * 0.017)))), 120));
}
if (cmd === "lyrics_get") return Promise.resolve(null);
if (cmd === "lyrics_get") return new Promise((r) => setTimeout(() => r(mockLyrics(args?.title)), 150));
if (cmd === "lyrics_search") return new Promise((r) => setTimeout(() => r([
{ id: 901, title: args?.title || "Track", artist: args?.artist || "?", album: "The Mock Sessions", synced: true, instrumental: false, durationSecs: 214 },
{ id: 902, title: (args?.title || "Track") + " (Live)", artist: args?.artist || "?", album: null, synced: false, instrumental: false, durationSecs: 233 },
]), 200));
if (cmd === "lyrics_select") return Promise.resolve({ ...mockLyrics(args?.title), source: "lrclib #" + args?.candidateId });
if (cmd === "lyrics_set_offset") return Promise.resolve();
if (cmd === "lyrics_clear") return Promise.resolve();
if (cmd === "clear_play_history") return Promise.resolve();
if (cmd === "refresh_album_art") return new Promise((r) => setTimeout(() => r("C:\\mock\\cover.jpg"), 200));
if (cmd === "play_local") {
/* a file: the engine reports playing after a beat, then the position
from where it was asked to start */