Files
site/tools/harness-resume.html
T
teahubxyzandClaude Fable 5.1 f855ee15b7 split tooling: line-map splitter, post edits, node checks, harness fragments as modules over window.g, a workflow that performs a step
The harness fragments now run as inline modules (after app.js) and stub the renderer through render.setFetcher; harness.sh's loop mode goes over http + DevTools like resume (file:// cannot load modules; --dump-dom also hangs on current Chrome). No page change yet.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dhu3iHseRewQ2WpBSVLokG
2026-10-21 22:17:50 +00:00

43 lines
4.0 KiB
HTML

<!doctype html>
<meta charset="utf-8">
<title>harness: resume</title>
<p>This file is a fragment: <code>tools/harness.sh resume</code> appends everything from the marker below to index.html, serves
the folder over http (IndexedDB does not work from file:// in headless Chrome), drives it in real time over the
DevTools protocol (tools/harness-cdp.py) and runs it WITHOUT autoplay allowed. It seeds the library with one station
and one rendered track and resumes the station with no user gesture, the way an installed app comes back to a station
link. Expected: the track is loaded but autoplay is refused, so the player waits with the cover saying TAP TO PLAY,
and the next track cooks behind it. The other half — the tap starting it — needs a real user activation, which
headless Chrome cannot fake; it is documented in ARCHITECTURE.md, not asserted here.</p>
<!-- harness -->
<script type="module">
/* a module so it runs after app.js (module scripts are deferred, in order); the page is reached through window.g */
const { state, openStation, cookNext, crackle, db, wavBlob, render } = window.g, hash32 = window.hash32;
try {
let n = 0;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
render.setFetcher(async function (path, body, { raw = false } = {}) {
if (path.includes("chat")) return { choices: [{ message: { content: JSON.stringify({ title: "T" + (++n), tags: "lo-fi, piano, instrumental", lyrics: "[verse]\nla\n[chorus]\nlo", bpm: 80, key: "C major", energy: 0.4, seconds: 30, meter: "4/4", station: "Resume FM", line: "hi" }) } }] };
if (path === "/render") { await sleep(50); const b = wavBlob(new Int16Array(8000 * 2), 8000); return new Response(b, { headers: { "content-type": "audio/wav", "x-cost": "0.001", "x-renderer": "test" } }); }
throw new Error("unexpected " + path);
});
const log = []; const dd = document.createElement("pre"); dd.id = "diag"; document.body.append(dd);
const L = (m) => { log.push(m); dd.textContent = log.join("\n"); };
const S = (tag) => L(tag + "\n phase=" + state.phase + " needTap=" + !!state.needTap + " tap=" + document.querySelector("#cover").classList.contains("tap") + " veil=" + JSON.stringify(document.querySelector("#cover .veil").textContent) + " paused=" + state.audio.paused + " playing=" + state.playing + " holding=" + !!state.holding + " cur=" + (state.current && state.current.spec.title) + " curblob=" + !!(state.current && state.current.blob) + " next=" + (state.nextJob && state.nextJob.spec.title) + " nextblob=" + !!(state.nextJob && state.nextJob.blob) + " crackle=" + crackle.on);
window.addEventListener("error", (e) => L("ERR " + e.message + " @" + e.lineno));
window.addEventListener("unhandledrejection", (e) => L("REJ " + (e.reason && (e.reason.stack || e.reason)).toString().slice(0, 200)));
(async () => {
const sid = "s-1700000000000-1", made = 1700000000000, seed = "resume seed";
const spec = { title: "R1", tags: "lo-fi, piano, instrumental", lyrics: "", bpm: 80, key: "C major", energy: 0.4, seconds: 30, meter: "4/4", line: "hi", seed, stationId: sid, made, id: made + "-" + hash32("R1") };
await db.put({ id: spec.id, spec, blob: wavBlob(new Int16Array(8000 * 2), 8000), made, kept: false, plays: 0 });
await db.put({ id: sid, seed, name: "Resume FM", taste: { like: [], avoid: [], notes: [] }, history: [spec], made, last: made, played: 1 }, "stations");
await sleep(600); // past the 400 ms room-tone attempt, which autoplay refuses too
S("before resume expect phase=idle cur=null paused=true needTap=false");
await openStation(seed, { id: sid, name: "Resume FM", history: [spec], played: 1 }); // no gesture: a station link opening
await sleep(1500);
S("resume, no gesture expect phase=playing needTap=true tap=true veil=\"TAP TO PLAY\" paused=true cur=R1 curblob=true next=T1 nextblob=true");
L("(the tap that starts it needs a real user activation; not asserted here)");
L("END");
})();
} catch (e) { const p = document.createElement("pre"); p.id = "diag"; p.textContent = "TOPERR " + e.message; document.body.append(p); }
</script>