Files
saphid--frame-control/tests/page/pad_queue.mjs
T
saphidandClaude Opus 5.5 fed2249776 Apps / Games / Tools / Settings, one Add / Install flow, and the whole-app review fixes
Primary tabs become Apps (Installed / Browse / Linux apps), Games (Installed /
Owned / Store), Tools and Settings, with shortcuts 1-4 and one Add / Install
chooser for catalogs, Steam, Linux apps, files and install links. Installed
rows show identity, one state and Open/Stop; the rest moves to app details.

Review fixes (#106): input, touch, approvals, artwork repair, keyboard helper
delivery and catalogue jobs stay bound to the headset they were meant for;
report preview follows the latest consent; failed Store reads are not an empty
library; keyboard access to device details and the Store sheet; the iOS
bundle ships the artwork renderer and OpenXR resources; typed addresses and
custom SSH ports survive setup; WebKit crashes recover; native preferences
survive relaunch; APK update rollback and repair stay retryable.

Refs #101, #104, #106

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 15:08:09 +11:00

49 lines
2.7 KiB
JavaScript

// The page's input queue, run in node against the real functions from ui/index.html.
import { readFileSync } from "fs";
const src = readFileSync(new URL("../../ui/index.html", import.meta.url), "utf8");
const grab = name => src.match(new RegExp(`(?:const ${name} = [^\\n]*\\n)|((?:async )?function ${name}\\([\\s\\S]*?\\n}\\n)`))[0];
const code = ["isMove", "padSend", "padKeep", "padFlush"].map(grab).join("");
const fail = msg => { console.log("FAIL " + msg); process.exit(1); };
function page(api) {
const pad = { state: "ready", queue: [], sending: false };
const shown = [];
const fns = new Function("pad", "api", "padShow", "devGen", code + "; return { padSend, padFlush };")(pad, api, s => { shown.push(s); pad.state = s.state; }, 0);
return { pad, shown, ...fns };
}
// While a request is in flight, events queue by these rules.
{
const { pad, padSend } = page(() => new Promise(() => {})); // a request that never returns
const send = (state, e) => { pad.state = state; padSend(e); };
send("ready", { singlehold: true }); // goes out, and stays in flight
send("error", { dx: 3 }); // dropped: a stale move while broken
send("error", { singlerelease: true }); // kept
send("off", { singlerelease: true }); // off: nothing was held
send("pairing", { key: "a" }); // kept while reconnecting
send("pairing", { dx: 1 }); // dropped
for (let i = 0; i < 250; i++) send("pairing", { key: "x" });
send("pairing", { singlerelease: true }); // kept past the 200 cap
const q = pad.queue;
if (!(q[0].singlerelease && q[1].key === "a" && q.at(-1).singlerelease && !q.some(e => "dx" in e)
&& q.filter(e => e.singlerelease).length === 2)) fail("queueing " + JSON.stringify(q.slice(0, 3)) + " len " + q.length);
}
// A request that fails, or reaches a link that had dropped, keeps its keys and releases.
for (const [label, api] of [["request failed", async () => { throw new Error("offline"); }],
["not sent", async () => ({ state: "starting", sent: false })]]) {
const { pad, padSend, shown } = page(api);
padSend({ dx: 5 }); // sent and lost: a move, fine to drop
await new Promise(r => setTimeout(r, 0));
pad.state = "ready";
padSend({ singlerelease: true });
padSend({ dx: 7 }); // queued while that request is in flight: stale once it fails
padSend({ key: "b" });
await new Promise(r => setTimeout(r, 0));
const q = pad.queue;
if (!(q.some(e => e.singlerelease) && q.some(e => e.key === "b") && !q.some(e => "dx" in e)))
fail(label + ": " + JSON.stringify(q));
if (!shown.length || shown.at(-1).state === "ready") fail(label + ": state not shown");
}
console.log("queue rules ok");