// Round twenty-seven: the phone turned sideways. The UI was drawn
// portrait-first and had never been looked at in landscape; the player sheet
// was the casualty (a cover sized by WIDTH inside a sheet that does not
// scroll, so at 915x411 the transport sat 687px below the fold). Two media
// queries answer it, the shell reads the side insets the cutout and the
// gesture pill take in landscape, and the walk turns the phone over itself:
// `{ view: [w, h] }` is a step kind now.
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);
/* The shell.css block is an APPEND with no anchor, so `once` cannot see a
second run coming: it would simply be written twice. Refuse one here,
before anything is touched. That string exists nowhere but in the block
this round adds. */
if (fs.readFileSync(root + 'src/mobile/shell.css', 'utf8').includes('a phone turned sideways')) {
throw new Error('round 27 is already applied in ' + root + ' — reset the tree rather than re-running');
}
// ── two more insets: sideways, the cutout and the pill are at the sides ──
load('src/mobile/mobile.css');
rep(` --inset-bottom: env(safe-area-inset-bottom, 0px);`,
` --inset-bottom: env(safe-area-inset-bottom, 0px);
/* landscape moves the cutout and the gesture pill to the SIDES, and
viewport-fit=cover puts our content under both */
--inset-left: env(safe-area-inset-left, 0px);
--inset-right: env(safe-area-inset-right, 0px);`);
save();
// ── the look. Appended, and the order is load-bearing: the side-inset
// longhands must come after .gbar's padding shorthand, .mini's left/right
// and .toast's left/right to win at equal specificity.
load('src/mobile/shell.css');
s = s.replace(/\s*$/, NL) + NL + ins('ins-r27-css.txt') + NL;
save();
// ── the sheet says which shape it is in: `small` is already the derived the
// artwork uses, and the landscape grid needs it on .np as a plain class
// rather than a :has()
load('src/mobile/Sheet.svelte');
rep(`
`, `
`);
save();
// ── the walk ──
load('tests/mobile/smoke.mjs');
// the geometry the orientation steps read back
rep(`const PROBE = \`(function () {
var e = document.querySelector('.screen');
return {`,
`const PROBE = \`(function () {
var e = document.querySelector('.screen');
/* geometry, for the orientation steps: box() is the rect or null, and
inside() asks whether every pixel of it is within the viewport —
which, sideways, is the whole question */
function box(sel) { var x = document.querySelector(sel); return x ? x.getBoundingClientRect() : null; }
function inside(b) { return !!b && b.height > 0 && b.top >= -1 && b.bottom <= innerHeight + 1 && b.left >= -1 && b.right <= innerWidth + 1; }
return {`);
rep(` contactsLoaded: (function () { try { return __ss.contacts.loaded ? 'yes' : (__ss.contacts.error ? 'err' : 'no'); } catch (e) { return ''; } })(),`,
` contactsLoaded: (function () { try { return __ss.contacts.loaded ? 'yes' : (__ss.contacts.error ? 'err' : 'no'); } catch (e) { return ''; } })(),` + NL + ins('ins-r27-probe.txt'));
// the new step kind, beside the adb one
rep(` /* a phone step can be an adb command — an intent, a key */
if (s.adb) {
try { adb(...s.adb); } catch (e) { problems.push("ADB " + (s.name || s.adb.join(" ")) + ": " + e.message); }
}`,
` /* a phone step can be an adb command — an intent, a key */
if (s.adb) {
try { adb(...s.adb); } catch (e) { problems.push("ADB " + (s.name || s.adb.join(" ")) + ": " + e.message); }
}
/* a mock step can turn the phone over: the viewport, through the
same override the walk boots with. A real device's orientation
is not the harness's to set, so a view step on the phone walk
is a failure rather than a no-op. send() resolves with the
whole reply and does not reject on a protocol error, so a
refused override has to be read out of it. */
if (s.view) {
if (PHONE) problems.push("VIEW " + (s.name || s.view.join("x")) + ": the phone walk must not set the viewport");
else {
try {
const r = await cdp.send("Emulation.setDeviceMetricsOverride", { width: s.view[0], height: s.view[1], deviceScaleFactor: 2, mobile: true });
if (r.error) throw new Error(r.error.message || JSON.stringify(r.error));
} catch (e) { problems.push("VIEW " + (s.name || s.view.join("x")) + ": " + e.message); }
}
}`);
rep(` { do: \`__click('.halfsheet .chip', '30 min')\`, name: "sleep-set", wait: 700 },`,
` { do: \`__click('.halfsheet .chip', '30 min')\`, name: "sleep-set", wait: 700 },` + NL + ins('ins-r27-steps.txt'));
rep(` "sleep-set": (i) => !i.half.includes("up"),`,
` "sleep-set": (i) => !i.half.includes("up"),` + NL + ins('ins-r27-expect.txt'));
save();
// ── the doc ──
load('ANDROID.md');
rep("`linear()`. Reduce motion (You → Appearance, or the OS setting) turns it off." + NL + NL + `## Search and the resolver`,
"`linear()`. Reduce motion (You → Appearance, or the OS setting) turns it off." + NL + NL + ins('ins-r27-doc.txt') + NL + NL + `## Search and the resolver`);
rep(`the sleep timer, asserts what each step should show,`,
`the sleep timer → the phone turned sideways (the player's three faces, a
record with a long sheet over it, an artist page, and the side insets faked,
all at 915×411) and back, asserts what each step should show,`);
rep(`page after a confirmed delete was found).`,
`page after a confirmed delete was found). A mock step can also set the
viewport — \`{ view: [915, 411] }\` sends \`Emulation.setDeviceMetricsOverride\`
through the same client, which is how the walk turns the phone over and back;
the phone walk refuses one, because a real device's orientation is not the
harness's to set.`);
save();
console.log('round 27 wired');