tools/harness.sh resume seeds IndexedDB with a station and one rendered track, resumes it with no user gesture and expects the TAP TO PLAY wait. IndexedDB needs http, so the page is served from a temp folder; headless Chrome's virtual time doesn't wait for IndexedDB, so this mode is driven in real time over the DevTools protocol (tools/harness-cdp.py, stdlib only). The loop harness now prints state.phase too. ARCHITECTURE.md describes the machine as implemented; HANDOFF.md lists what was found and left alone; STORAGE.md gains db.get and the tapes store (the code already had both); README points at the new files. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K6897PWcUEwHiqZMcVnNF6
41 lines
3.8 KiB
HTML
41 lines
3.8 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>
|
|
try {
|
|
let n = 0;
|
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
venice = 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>
|