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

156 lines
9.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 forty-two: turned over, for real. Round twenty-seven gave the phone
// a landscape layout and proved it in headless Chrome behind a viewport
// override; no real device has ever been rotated, and the phone walk refuses
// a `{ view: [...] }` step on purpose — a device's orientation is not the
// step list's to set. That refusal is right as a default and wrong as a
// permanent state, because the one thing the mock cannot tell you is what
// Android itself does: the angle (an override without `screenOrientation`
// leaves screen.orientation.angle at 0, so 90 is a fact the mock physically
// cannot fake), the real safe-area insets around the cutout and the gesture
// pill, whether the activity survived the configuration change instead of
// being recreated, and whether the audio kept going through it. So: a
// rotation block in the phone walk's TAIL rather than in its step list —
// because the two hard requirements, "restore even if an assertion fails"
// and "skip cleanly rather than fail", are exactly what the tail's phases
// already do and what a step array cannot — reading both rotation settings
// before writing either, and putting them back from four places. Nothing in
// it downloads, joins, sends, signs in, or writes to the config or the
// library. No src/, no CSS, no Rust, no Kotlin: the layout was already
// written, this round only proves it on the device.
//
// It also makes the `{ adb }` step keep its stdout, which it has always
// discarded: `am start` prints "Error: Activity not started, unable to
// resolve Intent" on STDOUT and exits 0, so `share-import` — the one step
// that exists to prove an intent resolves — could not fail for its own
// reason. Those three edits (4/5/6 below) are separable from the rotation
// block and can be dropped whole if a sibling round gets there first.
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);
/* Two of the inserts below re-emit their own anchor, so a second run would
double-write where `once` cannot see it coming and only throw further
down, leaving half a tree. Refuse one here, before anything is touched:
`rotationRestore` appears nowhere in the repo today and is the first name
this round writes. */
if (fs.readFileSync(root + 'tests/mobile/smoke.mjs', 'utf8').includes('rotationRestore')) {
throw new Error('round 42 is already applied in ' + root + ' — reset the tree rather than re-running');
}
/* The phase carries its own reading as a template literal (PLAYER, STREAM,
HERO, CAT, NET and BRW are all local literals — PROBE is deliberately left
alone, because sibling rounds append their fields to it). A raw backtick
inside that literal would end it and what follows becomes a tagged
template: the file still parses, `node --check` says nothing at all, and
smoke.mjs throws the moment it is imported. `${` is the same trap the
other way. Both checked here, across the ROT body only — the rest of the
phase is ordinary code and uses both legitimately. */
{
const phase = ins('ins-r42-phase.txt');
const TICK = String.fromCharCode(96);
const open = phase.indexOf('const ROT = ' + TICK);
if (open < 0) throw new Error('ins-r42-phase.txt no longer opens the ROT literal the guard knows about');
const close = phase.indexOf(TICK, open + 13);
if (close < 0) throw new Error('ins-r42-phase.txt never closes the ROT literal');
const body = phase.slice(open + 13, close);
if (body.includes('${')) throw new Error('the ROT body carries a ${ — it lives inside a template literal');
if (!/\)\(\)$/.test(body.trim())) throw new Error('the ROT body does not end in an invoked function — the literal closed early');
}
// ── the walk ──────────────────────────────────────────────────────────
load('tests/mobile/smoke.mjs');
/* EDIT 1 — the helpers, beside shadeNotice / mediaSessionState /
audioUnderruns, which are the precedent for shelling out and parsing.
They are at module scope because the restore latch has to be reachable
from main's finally and from a process handler, neither of which can see
inside main. */
rep(`/* The audio mixer's underrun count for the app's fast track: a dropout the`,
ins('ins-r42-helpers.txt') + NL + NL + `/* The audio mixer's underrun count for the app's fast track: a dropout the`);
/* EDIT 2 — the block itself, in the phone-only tail, between the
`background` check (which has just proved playback is live with the app
off screen) and the network block (which is the skip idiom this one
copies). Not a step: a step's adb array runs unconditionally and an
expectation cannot skip itself, and this round needs both. */
rep(` /* ── the network: a search, then a peer's copy played as it`,
ins('ins-r42-phase.txt') + NL + NL + ` /* ── the network: a search, then a peer's copy played as it`);
/* EDIT 3 — main's own finally. A throw out of the step loop or out of the
tail lands here, and today it restores nothing; this is the path that
would otherwise leave the phone sideways. The latch clears itself before
it writes, so the block's own finally having already restored makes this
a no-op. */
rep(` if (cdp) cdp.close();`,
` const rotLeft = rotationRestore();
if (rotLeft !== true && rotLeft !== null) problems.push("ROTATION could not put the phone's rotation settings back: " + rotLeft);
if (cdp) cdp.close();`);
/* EDIT 4 — the `{ adb }` step keeps its stdout. `adb()` already returns it
and throws only on a non-zero status, and `am start` answers "Error:
Activity not started, unable to resolve Intent" on stdout with status 0 —
so a discarded stdout is exactly why the share-sheet step cannot fail for
the one reason it exists. */
rep(` if (s.adb) {
try { adb(...s.adb); } catch (e) { problems.push("ADB " + (s.name || s.adb.join(" ")) + ": " + e.message); }
}`,
` let adbOut = "";
if (s.adb) {
/* kept, not discarded: \`am start\` prints "Error: Activity not
started, unable to resolve Intent" on STDOUT and still exits
0, so a step's own expectation is the only thing that can
tell a resolved intent from an unresolved one. It reaches
the expectation as \`adbOut\` below. */
try { adbOut = adb(...s.adb).toString().trim(); } catch (e) { problems.push("ADB " + (s.name || s.adb.join(" ")) + ": " + e.message); }
}`);
/* EDIT 5 — and reaches the expectation, before the `probe` merge so a probe
could still override it */
rep(` const info = await cdp.evaluate(PROBE);`,
` const info = await cdp.evaluate(PROBE);
if (adbOut) info.adbOut = adbOut;`);
/* EDIT 6 — the one place it bites today. This is PHONE_EXPECT's
share-import (the mock's own, five lines up the file, is a different
sentence and is left alone): the intent has to have RESOLVED, not merely
have been sent to an activity that is not there. */
rep(` "share-import": (i) => i.title.startsWith("Import") && (i.imp > 0 || i.impErr),`,
` "share-import": (i) => i.title.startsWith("Import") && (i.imp > 0 || i.impErr) && !/^Error/m.test(i.adbOut || ""),`);
save();
// ── the doc ───────────────────────────────────────────────────────────
load('ANDROID.md');
/* the refusal sentence is WRAPPED across two lines — a one-line grep for it
finds nothing, which is the trap a careless patch falls into. Kept
verbatim; the phone's own block is described after it. */
rep(`the phone walk refuses one, because a real device's orientation is not the
harness's to set.`, ins('ins-r42-doc-walk.txt'));
/* the paragraph at the top of the doc that has asserted, in prose, exactly
what no one had tested. The anchor runs to the end of the sheet sentence
and the insert re-emits all of it: a shorter anchor leaves the paragraph
wrapped around a 60-character orphan where the new sentence ends. */
rep(`layout is the only thing that has to answer. Two media queries do that. The
player sheet is the one surface whose portrait shape cannot survive a wide
viewport: its artwork is a square sized by WIDTH, so at 915×411 the cover came
out 879px tall and the title, the lane, the transport and the face buttons sat`,
ins('ins-r42-doc-layout.txt'));
/* "Not done yet" is left exactly as it is: it has no rotation entry to
close, and two rounds have already collided on the step COUNTS in the
sentences above it ("Fourteen mock steps", "Fifteen more mock steps",
"Nine mock steps"). This round changes none of them — it adds no step to
either list. */
save();
console.log('round 42 wired');