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,228 @@
|
||||
// Round thirty-three: the sweep. Thirty rounds by several hands left drift
|
||||
// in src/mobile — nine imports nothing in their file ever named again, two
|
||||
// helpers written twice word for word, two functions and two CSS rules
|
||||
// nothing has ever reached, and one raw NUL byte inside a string literal in
|
||||
// player.svelte.js that has made `grep` call that file binary since the day
|
||||
// it landed. Nothing here changes what the phone does: every deletion is
|
||||
// something with no reference anywhere in src/, tests/, crates/ or
|
||||
// src-tauri/, and every unification moves a body that was already identical.
|
||||
// One line is ADDED — a comment over the one rule that looks like a stray
|
||||
// and is not, so the next sweep does not take it. The walk is untouched on
|
||||
// purpose: it is the guard, not the subject.
|
||||
//
|
||||
// Rounds thirty-one and thirty-two land first and this round reads import
|
||||
// lines they may also touch, so every anchor into one is scoped to
|
||||
// the NAME being taken out or put in rather than to the whole line — a
|
||||
// round that adds a name beside it does not collide. Where a whole import
|
||||
// statement goes the line is the anchor on purpose: if something else has
|
||||
// joined it, the statement is no longer dead and `once` should say so.
|
||||
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);
|
||||
const NUL = String.fromCharCode(0);
|
||||
|
||||
/* A sweep is all deletions and moves, so a second run would hit missing
|
||||
anchors halfway through and leave the tree half-swept. `stamp` in util.js
|
||||
is what this round creates and nothing else has ever had — refuse before
|
||||
a byte is written. */
|
||||
if (fs.readFileSync(root + 'src/mobile/util.js', 'utf8').includes('export function stamp(ms)')) {
|
||||
throw new Error('src/mobile/util.js already has stamp() — round 33 has already run against ' + root);
|
||||
}
|
||||
|
||||
/* ── util.js: the two helpers that were written twice, once ─────────────
|
||||
Both bodies are moved here WORD FOR WORD from the screens below, so the
|
||||
diff on each screen is a deletion and an added name on an import line it
|
||||
already had. */
|
||||
load('src/mobile/util.js');
|
||||
rep(`export function longDur(secs) {
|
||||
secs = Math.max(0, Math.round(secs || 0));
|
||||
const h = Math.floor(secs / 3600);
|
||||
const m = Math.round((secs % 3600) / 60);
|
||||
if (h) return h + " hr " + m + " min";
|
||||
return m + " min";
|
||||
}`,
|
||||
`export function longDur(secs) {
|
||||
secs = Math.max(0, Math.round(secs || 0));
|
||||
const h = Math.floor(secs / 3600);
|
||||
const m = Math.round((secs % 3600) / 60);
|
||||
if (h) return h + " hr " + m + " min";
|
||||
return m + " min";
|
||||
}
|
||||
` + ins('ins-r33-stamp.txt'));
|
||||
rep(`export function fmtBytes(n) {
|
||||
if (!n) return "";
|
||||
if (n >= 1073741824) return (n / 1073741824).toFixed(1) + " GB";
|
||||
if (n >= 1048576) return (n / 1048576).toFixed(1) + " MB";
|
||||
return Math.round(n / 1024) + " kB";
|
||||
}`,
|
||||
`export function fmtBytes(n) {
|
||||
if (!n) return "";
|
||||
if (n >= 1073741824) return (n / 1073741824).toFixed(1) + " GB";
|
||||
if (n >= 1048576) return (n / 1048576).toFixed(1) + " MB";
|
||||
return Math.round(n / 1024) + " kB";
|
||||
}
|
||||
` + ins('ins-r33-fmtcount.txt'));
|
||||
/* isLossless: written for the source rules, never called once. The desktop
|
||||
has its own; this copy has no reference in src/, tests/, crates/ or
|
||||
src-tauri/. The LOSSLESS set beside it stays — fmtOf reads it. */
|
||||
rep(`export function isLossless(fmt) {
|
||||
return /^(FLAC|WAV|AIFF?|APE|WV|ALAC|DSF|DFF)\\b/.test(fmt || "");
|
||||
}
|
||||
`, ``);
|
||||
save();
|
||||
|
||||
/* ── the four screens the two helpers came from ───────────────────────
|
||||
The name goes in at the closing brace of the util.js import, so a round
|
||||
that has added its own name to that list still takes this cleanly. */
|
||||
load('src/mobile/screens/Import.svelte');
|
||||
rep(` } from "../util.js";`, `, stamp } from "../util.js";`);
|
||||
rep(` function stamp(ms) {
|
||||
const s = Math.floor((ms || 0) / 1000);
|
||||
const h = Math.floor(s / 3600);
|
||||
const m = Math.floor((s % 3600) / 60);
|
||||
const sec = s % 60;
|
||||
return (h ? h + ":" + String(m).padStart(2, "0") : String(m)) + ":" + String(sec).padStart(2, "0");
|
||||
}
|
||||
`, ``);
|
||||
save();
|
||||
|
||||
load('src/mobile/screens/Playlist.svelte');
|
||||
rep(` } from "../util.js";`, `, stamp } from "../util.js";`);
|
||||
rep(`
|
||||
function stamp(ms) {
|
||||
const s = Math.floor((ms || 0) / 1000);
|
||||
const h = Math.floor(s / 3600);
|
||||
const m = Math.floor((s % 3600) / 60);
|
||||
const sec = s % 60;
|
||||
return (h ? h + ":" + String(m).padStart(2, "0") : String(m)) + ":" + String(sec).padStart(2, "0");
|
||||
}
|
||||
`, NL);
|
||||
save();
|
||||
|
||||
load('src/mobile/screens/Artist.svelte');
|
||||
rep(` } from "../util.js";`, `, fmtCount } from "../util.js";`);
|
||||
rep(` const fmtCount = (n) => (!n ? "" : n >= 1e6 ? (n / 1e6).toFixed(1).replace(/\\.0$/, "") + "M" : n >= 1e3 ? Math.round(n / 1e3) + "k" : String(n));
|
||||
`, ``);
|
||||
save();
|
||||
|
||||
load('src/mobile/screens/CatalogAlbum.svelte');
|
||||
rep(` } from "../util.js";`, `, fmtCount } from "../util.js";`);
|
||||
rep(` const fmtCount = (n) => (!n ? "" : n >= 1e6 ? (n / 1e6).toFixed(1).replace(/\\.0$/, "") + "M" : n >= 1e3 ? Math.round(n / 1e3) + "k" : String(n));
|
||||
`, ``);
|
||||
save();
|
||||
|
||||
/* ── slsk: a sign-out that forgets the account, which nothing offers ────
|
||||
The sheet's Sign out calls disconnect(); nothing in the tree has ever
|
||||
called this. The desktop's Settings keeps api.forgetCredentials (and so
|
||||
the mock's forget_credentials branch) alive, so only this copy goes. */
|
||||
load('src/mobile/slsk.svelte.js');
|
||||
rep(`
|
||||
export async function forgetAccount() {
|
||||
try { await api.forgetCredentials(); } catch { /* fine */ }
|
||||
slsk.savedUser = "";
|
||||
slsk.hasPassword = false;
|
||||
}
|
||||
`, ``);
|
||||
save();
|
||||
|
||||
/* ── nine imports no file ever names again ─────────────────────────────
|
||||
Each of these appears exactly once in its own file: on the import line.
|
||||
Where a whole statement goes, the module is still loaded through another
|
||||
import in the same graph (lib.svelte.js comes in through Mobile.svelte
|
||||
and player.svelte.js; Library.svelte imports util.js four lines higher),
|
||||
and neither module has a top-level statement other than a declaration, so
|
||||
nothing moves but the order they are evaluated in. Every export named
|
||||
here keeps other callers — only these imports go.
|
||||
The tenth was Sheet.svelte's `import * as api`, and it is round
|
||||
thirty-two's: a namespace import is the one shape Rollup cannot shake,
|
||||
so its reason was the bundle rather than drift, and the round that weighs
|
||||
the launch takes it. */
|
||||
load('src/mobile/HalfSheet.svelte');
|
||||
rep(`, albumsBy`, ``);
|
||||
rep(`, refreshRecents`, ``);
|
||||
save();
|
||||
|
||||
load('src/mobile/Sheet.svelte');
|
||||
rep(` import { albumsBy } from "./lib.svelte.js";
|
||||
`, ``);
|
||||
save();
|
||||
|
||||
load('src/mobile/screens/Library.svelte');
|
||||
/* the second import OF THE SAME MODULE, four lines below the first, and
|
||||
the name it brings is unused — the line goes whole */
|
||||
rep(` import { longDur } from "../util.js";
|
||||
`, ``);
|
||||
save();
|
||||
|
||||
load('src/mobile/screens/Room.svelte');
|
||||
rep(`, leaveRoom`, ``);
|
||||
rep(`, addBuddy`, ``);
|
||||
/* `back` is a word as well as a name — anchor it between its neighbours
|
||||
so a line of prose cannot make this ambiguous */
|
||||
rep(`push, back,`, `push,`);
|
||||
save();
|
||||
|
||||
load('src/mobile/screens/Search.svelte');
|
||||
rep(`, folderMeta`, ``);
|
||||
save();
|
||||
|
||||
load('src/mobile/screens/Transfers.svelte');
|
||||
rep(`import { transfers, `, `import { `);
|
||||
save();
|
||||
|
||||
/* ── two CSS rules no markup can reach ──────────────────────────────────
|
||||
src/mobile has no classList call at all and exactly three class names
|
||||
built from an expression — `lane {cls}`, `screen tr-{nav.tr}` and
|
||||
`tag {cls}` — none of which can spell either of these. Loading the
|
||||
stylesheet before and after and diffing the CSSOM says the same: two
|
||||
rules fewer, these two, and no other rule's text changed. */
|
||||
load('src/mobile/shell.css');
|
||||
rep(`.iconbtn.liked { color: var(--accent); }
|
||||
`, ``);
|
||||
rep(`.cardpad { padding: 14px; }
|
||||
`, ``);
|
||||
/* and the opposite case, in the same file: a one-property rule 588 lines
|
||||
from the one it exists to defeat, which reads exactly like the strays
|
||||
above. Measured before writing this down — it is load-bearing. A comment
|
||||
is the whole change; the declaration is untouched. */
|
||||
rep(`.screen.has-hero { padding-top: 0; }
|
||||
.mini .mini-lane { width: auto; }`,
|
||||
`.screen.has-hero { padding-top: 0; }
|
||||
/* not a stray: \`.lane\` is width: 100%, and the mini bar's lane is ALSO
|
||||
positioned with both left: 16px and right: 16px — an over-constrained
|
||||
absolute box drops \`right\`, so without this it comes out 32px wider than
|
||||
the strip it was cut for (395 where 363 was asked for, at 411), the bars
|
||||
sit off-centre and the tail runs under the bar's own overflow: hidden. */
|
||||
.mini .mini-lane { width: auto; }`);
|
||||
save();
|
||||
|
||||
/* ── one byte ───────────────────────────────────────────────────────────
|
||||
The play-history key joins artist and title on a NUL, and the NUL was
|
||||
written into the source as the character itself. That makes the file
|
||||
non-text: every grep of player.svelte.js since has answered "Binary file
|
||||
matches" and shown nothing. The escape is the same string to the engine,
|
||||
and it is what catalog.svelte.js's infoKey already writes. */
|
||||
load('src/mobile/player.svelte.js');
|
||||
rep(` const key = artist + "` + NUL + `" + title;`,
|
||||
` const key = artist + "\\u0000" + title;`);
|
||||
save();
|
||||
|
||||
/* ── the doc: where the shared helpers live ─────────────────────────── */
|
||||
load('ANDROID.md');
|
||||
rep(`| \`src/mobile/prefs.svelte.js\` | Appearance, kept in localStorage: ambience, accent from artwork, motion |`,
|
||||
`| \`src/mobile/prefs.svelte.js\` | Appearance, kept in localStorage: ambience, accent from artwork, motion |
|
||||
| \`src/mobile/util.js\` | what every screen shares and no screen should write twice: the formatters (\`mmss\` / \`longDur\` / \`stamp\` for a length in seconds, in words and in milliseconds; \`ago\` / \`clock\`; \`fmtBytes\` / \`fmtCount\`; \`fmtOf\` / \`albumFmt\`), the name and folder readers a peer's file needs, the \`drag\` action, and \`devBuild\` |`);
|
||||
save();
|
||||
console.log('round 33 wired');
|
||||
Reference in new issue
Block a user