mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-24 21:01:43 +02:00
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>
49 lines
2.7 KiB
JavaScript
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");
|