From 94a5008f058093d88caa8c878a209f5948154970 Mon Sep 17 00:00:00 2001 From: Alcahest Date: Sat, 12 Sep 2026 10:10:10 +0200 Subject: [PATCH 1/2] test: the page counts the keys it is given, so a lost press names itself --- e2e/specs/layer-owns-the-keyboard.spec.js | 29 ++++++++++++++++++++--- src/App.tsx | 6 +++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/e2e/specs/layer-owns-the-keyboard.spec.js b/e2e/specs/layer-owns-the-keyboard.spec.js index 0025327..5a0d0fd 100644 --- a/e2e/specs/layer-owns-the-keyboard.spec.js +++ b/e2e/specs/layer-owns-the-keyboard.spec.js @@ -53,6 +53,29 @@ function workingCopy() { return copy; } +/** + * How many keydowns the page has been given, which `App.tsx` counts on the element. + * + * The difference this is here to name: a key the app dropped and a key that never arrived read + * exactly the same from outside, and on the runner this file has twice waited out its whole timeout + * for a press that produced no line at all. See BACKLOG.md N101. + */ +function keysSeen() { + return browser.execute(() => Number(document.documentElement.dataset.keysSeen) || 0); +} + +/** Press a key and say which half failed: the page never saw it, or nothing came of it. */ +async function pressAndConfirmArrival(key) { + const before = await keysSeen(); + pressKey(key); + await waitFor(async () => ((await keysSeen()) > before ? 1 : null), { + timeout: 10000, + message: + `the page to be given the ${key} keypress at all. It was sent through XTEST to a window ` + + `that holds the focus, and the page never saw a keydown`, + }); +} + function present(selector) { return browser.execute((css) => document.querySelector(css) !== null, selector); } @@ -120,7 +143,7 @@ describe("a layer owns the keyboard while it is open", () => { // which is exactly how the first version of this fooled itself. const before = ranges().length; focusWindow(toplevel.id); - pressKey("t"); + await pressAndConfirmArrival("t"); await waitFor(async () => (ranges().length > before ? 1 : null), { timeout: STALL_ROOM_MS, message: "T to play a range with no layer on screen", @@ -137,7 +160,7 @@ describe("a layer owns the keyboard while it is open", () => { const before = ranges().length; focusWindow(toplevel.id); - pressKey("t"); + await pressAndConfirmArrival("t"); await browser.pause(2500); expect(ranges().length).toBe(before); @@ -148,7 +171,7 @@ describe("a layer owns the keyboard while it is open", () => { message: "the details panel to close", }); focusWindow(toplevel.id); - pressKey("t"); + await pressAndConfirmArrival("t"); await waitFor(async () => (ranges().length > before ? 1 : null), { timeout: STALL_ROOM_MS, message: "T to play again once the panel has closed", diff --git a/src/App.tsx b/src/App.tsx index 796e886..707e063 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3357,6 +3357,12 @@ export default function App() { useEffect(() => { const handle = (event: KeyboardEvent) => { + // Every keydown the page is given, counted before any road out of here. A check that presses + // a key and sees nothing cannot otherwise tell a key the app dropped from one that never + // arrived, and that difference is what the silences of N101 are made of. Straight onto the + // element, so it costs no render and no IPC. + const root = document.documentElement; + root.dataset.keysSeen = String((Number(root.dataset.keysSeen) || 0) + 1); // A layer owns the keyboard while it is open. Without this, a bare accelerator ran its // command on the document behind the dialog: measured with the video details panel on screen // and T playing the line under it. The key is left alone rather than swallowed, so the menu's From b28b5170000be7de3995c8b6c9dd8b3e7ec31e1e Mon Sep 17 00:00:00 2001 From: Alcahest Date: Sat, 12 Sep 2026 10:27:05 +0200 Subject: [PATCH 2/2] test: wait for the player to be ready before pressing a timing key --- e2e/specs/layer-owns-the-keyboard.spec.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/e2e/specs/layer-owns-the-keyboard.spec.js b/e2e/specs/layer-owns-the-keyboard.spec.js index 5a0d0fd..edcccd1 100644 --- a/e2e/specs/layer-owns-the-keyboard.spec.js +++ b/e2e/specs/layer-owns-the-keyboard.spec.js @@ -136,6 +136,27 @@ describe("a layer owns the keyboard while it is open", () => { timeout: 40000, message: "the transport to appear, which is the video being open", }); + // The transport being drawn is not the player being ready, and T is greyed until it is. A press + // against a greyed command does nothing at all, so this file sat out its whole wait and read as + // a silence: green alone, red in the battery, three times. The length arriving says the media is + // open and the button coming alive says it can be played. Same lesson as `timing-play-keys`, + // paid for twice. See BACKLOG.md N173. + await waitFor( + async () => { + const ready = await browser.execute(() => { + const slider = document.querySelector(".controls__slider"); + const button = document.querySelector(".controls__button"); + return { + duration: slider === null ? null : Number(slider.getAttribute("max")), + greyed: button?.disabled ?? true, + }; + }); + return ready.duration !== null && ready.duration > 0 && ready.greyed === false + ? true + : null; + }, + { timeout: 40000, message: "the player to be ready, which is what wakes the timing keys" }, + ); }); it("first proves the key works with nothing over the document", async () => {