// Round sixteen: the desktop Library's summary hours on the count line, its // "By format" order (the file stats), and the empty Search page's Jump back // in — the last records played, as the desktop's empty search shows them. const fs = require('fs'); const root = 'F:/Coding/SoulStream/'; 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); }; // ── Library: hours on the count line, By format ── load('src/mobile/screens/Library.svelte'); rep(` const SORTS = [["added", "Recently added"], ["az", "A–Z"], ["artist", "By artist"], ["year", "By year"]];`, ` const SORTS = [["added", "Recently added"], ["az", "A–Z"], ["artist", "By artist"], ["year", "By year"], ["format", "By format"]];`); rep(` if (sort === "year") return (y.year || 0) - (x.year || 0) || y.added_at - x.added_at; return y.added_at - x.added_at;`, ` if (sort === "year") return (y.year || 0) - (x.year || 0) || y.added_at - x.added_at; if (sort === "format") { /* lossless first, then the higher bitrate, then the name */ const fx = fmtRank(lib.stats[x.id]); const fy = fmtRank(lib.stats[y.id]); return fy - fx || x.title.localeCompare(y.title); } return y.added_at - x.added_at;`); rep(` const totalTracks = $derived(lib.albums.reduce((n, a) => n + (a.track_count || 0), 0));`, ` const totalTracks = $derived(lib.albums.reduce((n, a) => n + (a.track_count || 0), 0)); const totalSecs = $derived(Object.values(lib.stats).reduce((n, st) => n + (st.secs || 0), 0)); const hours = $derived(totalSecs >= 3600 ? Math.round(totalSecs / 3600) + " hours" : totalSecs ? Math.round(totalSecs / 60) + " min" : ""); /* By format: a number the sort can compare — lossless by depth and rate, lossy by bitrate */ function fmtRank(st) { if (!st || !st.formats?.length) return 0; if (st.bit_depth && st.sample_rate) return 1_000_000 + st.bit_depth * 1000 + Math.round(st.sample_rate / 1000); return st.avg_kbps || 1; }`); rep(` {list.length} album{list.length === 1 ? "" : "s"} · {totalTracks} tracks`, ` {list.length} album{list.length === 1 ? "" : "s"} · {totalTracks} tracks{hours ? " · " + hours : ""}`); rep(`

Recent

`, ` {#if lib.recents.length}

Jump back in · picks up where you left off

{#each lib.recents.slice(0, 8) as r (r.album_id)} push("album", { id: r.album_id }, screenEl)} /> {/each}
{/if} {#if recents.length}

Recent

`); save(); // ── the mock: a few recents, so Jump back in has something ── load('src/lib/mockLibrary.js'); rep(` if (cmd === "recent_albums") return Promise.resolve([]);`, ` if (cmd === "recent_albums") return Promise.resolve(albums.slice(0, 3).map((a, i) => ({ kind: "library", album_id: a.id, title: a.title, artist: a.artist, art_path: a.art_path, last_played_at: Math.floor(Date.now() / 1000) - 3600 * (i + 1) })));`); save(); // ── the walks ── load('tests/mobile/smoke.mjs'); rep(` rooms: document.querySelectorAll('.screen button.row .art.hash').length,`, ` rooms: document.querySelectorAll('.screen button.row .art.hash').length, jump: document.querySelectorAll('.screen .rail .release').length, countline: (document.querySelector('.countline .t-sub') || {}).textContent || '', firstBadge: (document.querySelector('.grid2 .release .ed-badge') || {}).textContent || '',`); rep(` "search-recent": (i) => i.title.startsWith("Search") && i.recents >= 1,`, ` "search-recent": (i) => i.title.startsWith("Search") && i.recents >= 1 && i.jump >= 1,`); rep(` library: (i) => i.title.startsWith("Your library") && i.fmtBadges >= 10,`, ` library: (i) => i.title.startsWith("Your library") && i.fmtBadges >= 10 && /hours/.test(i.countline),`); rep(` { do: \`document.querySelectorAll('.segtabs button')[1].click()\`, name: "artists", wait: 600 },`, ` /* By format: the hi-res lossless records come first */ { do: \`__click('.chips .chip', 'By format')\`, name: "library-by-format", wait: 500 }, { do: \`__click('.chips .chip', 'Recently added')\`, wait: 400 }, { do: \`document.querySelectorAll('.segtabs button')[1].click()\`, name: "artists", wait: 600 },`); rep(` artists: (i) => i.title.startsWith("Your library") && i.rowArt >= 3,`, ` "library-by-format": (i) => /^FLAC 24/.test(i.firstBadge), artists: (i) => i.title.startsWith("Your library") && i.rowArt >= 3,`); save(); // ── the doc ── load('ANDROID.md'); rep(`The Library's cards wear the desktop's file stats`, `The Library's count line carries the collection's hours and the sort offers By format (lossless by depth and rate first, then by bitrate); the empty Search page shows Jump back in, the desktop's. The Library's cards wear the desktop's file stats`); save(); console.log('round sixteen wired');