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.
This commit is contained in:
1 parent
867fd42972
commit
9f0601027c
400 files changed
+26401
-3
No files matched your search
@@ -0,0 +1,100 @@
|
||||
// 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');
|
||||
Reference in new issue
Block a user