// 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');