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

101 lines
6.5 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 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(` <span class="t-sub">{list.length} album{list.length === 1 ? "" : "s"} · {totalTracks} tracks</span>`,
` <span class="t-sub">{list.length} album{list.length === 1 ? "" : "s"} · {totalTracks} tracks{hours ? " · " + hours : ""}</span>`);
rep(` <Release title={a.title} sub={sort === "year" ? String(a.year || "") : a.artist} path={a.art_path} badge={statLabel(lib.stats[a.id]) || null}`,
` <Release title={a.title} sub={sort === "year" ? String(a.year || "") : sort === "format" ? (statLabel(lib.stats[a.id]) || a.artist) : a.artist} path={a.art_path} badge={statLabel(lib.stats[a.id]) || null}`);
save();
load('src/mobile/HalfSheet.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"]];`);
save();
// ── Search: Jump back in on the empty page ──
load('src/mobile/screens/Search.svelte');
rep(` {#if recents.length}
<div class="sec"><h2>Recent</h2><button class="all" onclick={() => { recents = []; keepRecents(); }}>Clear</button></div>`,
` {#if lib.recents.length}
<div class="sec"><h2>Jump back in <em>· picks up where you left off</em></h2></div>
<div class="rail">
{#each lib.recents.slice(0, 8) as r (r.album_id)}
<Release title={r.title} sub={r.artist} path={r.art_path} onclick={() => push("album", { id: r.album_id }, screenEl)} />
{/each}
</div>
{/if}
{#if recents.length}
<div class="sec"><h2>Recent</h2><button class="all" onclick={() => { recents = []; keepRecents(); }}>Clear</button></div>`);
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');