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.
156 lines
9.5 KiB
JavaScript
156 lines
9.5 KiB
JavaScript
// 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');
|