Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions BACKLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,8 @@ Two items jump the queue by owner decision. Full reasoning in `decisions.md`; th
- **2026-09-06, found by reading and fixed, not proven to be the cause.** Every one of those five setters wrote its answer unconditionally, so a command sent against the file that was open could still be in flight when the next file opened and then set the error state about a document nobody had asked about. `useVideoPlayer` now stamps each command with the open it belongs to and drops an answer carrying an older one. It is a real race and the fix is right on its own terms; whether it is what this check keeps catching is only knowable from CI, because it has never reproduced here. Two attempts to reproduce it by taking the audio output away, which is the one difference this machine and the runner are known to have, did not: mpv found an output both times and the spec passed.
- **A second check joined it on 2026-09-06:** `editor.spec.js`'s "scrolls a viewport at a time without falling behind" failed on the runner and has never failed here. Two timing-sensitive checks failing only on CI is a property of the runner as much as of either check, and it blocks every pull request, so it is the owner's to weigh: the suite cannot be trusted to be green there today.

- [ ] **N41 The first save of a new translation proposes nothing, because there is no language to propose (filed 2026-09-06).** M2.6's second criterion asks the first save to offer a name and a place, "episode plus language". The save already asks: a document that has never had a file raises the chooser, which is what `saveDocument` does when `summary.path` is null. What it cannot do yet is propose, and the obvious proposal is the wrong one: handing the chooser the source's own path puts the source one Return away from being overwritten, and §3 promises the file being read is never written. A real proposal needs a language the product does not have anywhere, so this waits on a language being a thing a project knows about, which is M4's ground rather than M2.6's.

- [ ] **N37 The scaled surface check misses the doubling by eight pixels, about one run in several (filed 2026-09-06).** `e2e/scripts/scaled-surface-check.js` asserts the video surface doubles when `GDK_SCALE` doubles, within three pixels. On CI job 101433553363 it read 346x166 at ratio 1 and 700x342 at ratio 2, which is eight and ten pixels over twice, and the same script printed `5/5 checks` on the run before it with nothing changed between them. The failure message reads as though the surface had not been resolved to native pixels, which is not what these numbers say: the scale is applied, and the layout under it settles a few pixels away from where it settles at ratio 1. The candidates are the ruler band, whose height is a rounded number of device pixels and therefore not exactly half at ratio 2, and the surface being measured before the last layout pass at a size where that pass takes longer. **Second sighting, 2026-09-06, and it says the difference is fixed rather than random.** Job 101441553639 on a build whose top block is taller read 346x230 at ratio 1 and 700x470 at ratio 2. The first sighting read 346x166 and 700x342. The heights differ between the two runs and **the shortfall does not**: eight pixels of width and ten of height over twice, both times. That is the shape of a fixed inset in device pixels that is applied once at each ratio instead of scaling with it, not of a layout that settles late, and it rules out the ruler band, whose height does change between those runs. The script still prints only the surface. The way to name it is to print the stage rectangle the page sends and the rectangle the backend applies, at both ratios, and see which of the two carries the eight and the ten.

- [ ] **N36 A media with no picture sometimes says "Open a video first." to the translator, on the runner and not here (filed 2026-09-06).** `video-aspect.spec.js`'s "says a media with no picture has none, and is as quiet about it as about no audio" failed on CI job 101422670879 with `["Open a video first."]` where it expects no alert at all, at `video-aspect.spec.js:281`. That string is `video.errors.notLoaded`, which the status bar draws from `useVideoPlayer`'s error code, and the backend answers `NotLoaded` for mpv's `PropertyUnavailable` as well as for a genuinely closed player (`src-tauri/src/video/error.rs:81`). So a property read that lands in the window around an open is shown to a person as an instruction to do the thing they just did. This is the twin of the audio fix of 2026-09-05: a media with no video track is not an error and must be as quiet as a machine with no sound. The fix is to find which call answers it and stop that answer reaching the status bar, and the way to prove it is to force the answer and watch the check go red.
Expand Down
16 changes: 15 additions & 1 deletion e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,10 +254,16 @@ Environment knobs:
Neither entry point builds anything. A missing binary or fixture fails immediately with the command
to run, because a silent four-minute rebuild inside a test hook is worse than a red line.

## Five ways to make a run tell you nothing
## Seven ways to make a run tell you nothing

Each of these produced a failure that meant nothing, and cost a re-run to find out.

**Do not start a second run while one is going.** Both take a display and both write the same
tally, and the two sets of results are then read as one: on 2026-09-06 a full run reported four
failed spec files, and three of them were the run that had been started on top of it. `pgrep` for
the running battery before starting one, and be careful what you grep for, because the grep's own
command line contains the word you are looking for and matches itself.

**Do not edit a spec file while a run is in progress.** Workers read each file as they reach it, so
a run started before an edit and finished after it mixes an old binary with new expectations. The
failure looks like a defect and is not one. Wait for the run, then edit.
Expand Down Expand Up @@ -285,6 +291,14 @@ red is the one where `Compiling sublore` appeared and the printed numbers change
a green mutation run, look for the compile in the log, and print a number the mutation moves. A
green whose build was never seen proves nothing at all, and it is much easier to produce than a red.

**The runner is not this machine, and one check per CI run fails there and nowhere here.** Five
runs on 2026-09-06 each failed exactly one check and a different one every time, all of them
timing-sensitive, none of them reproducible here across many full runs. `specFileRetries` is 1 when
`CI` is set and 0 here, so the runner re-runs a whole spec file once: a defect that fails
deterministically fails twice and stays red, the count guard still demands every test, and a flake
on this machine is still a flake you see. What it can hide is a defect that is genuinely
intermittent in the product, so read wdio's own line naming the retried file whenever it appears.

**`cargo test -p <crate>` stops at the first failing test binary.** A mutation that reddens
`tests/mutation.rs` leaves `tests/session.rs` unrun, so the report undercounts what the mutation
actually broke. Use `--no-fail-fast` whenever the point of the run is to see the full blast radius.
Expand Down
12 changes: 9 additions & 3 deletions e2e/lib/tally.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,15 @@ export function recordPassedTest(title) {

export function passedTests() {
try {
return readFileSync(tallyFile, "utf8")
.split("\n")
.filter((line) => line !== "");
// Unique, because a retried spec file appends its passing tests a second time and a count that
// grew with a retry would let a deleted spec hide behind one. A title names one test.
return [
...new Set(
readFileSync(tallyFile, "utf8")
.split("\n")
.filter((line) => line !== ""),
),
];
} catch {
return [];
}
Expand Down
7 changes: 6 additions & 1 deletion e2e/specs/chrome.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,19 +262,24 @@ describe("the menu bar and the toolbar", () => {
});

it("walks the items with the arrows and steps over the disabled ones", async () => {
// Nothing is open, so Save, Save a copy and Discard are the disabled run in the middle of File.
// Nothing is open, so File has two disabled runs to step over rather than one: the source's
// close and the translation it cannot make yet, then Save, Save a copy and Discard.
pressKey("alt");
await waitForCursor("file-open-subtitle");
expect(
await browser.execute(() => document.querySelector("#menuitem-file-save")?.disabled ?? null),
).toBe(true);

pressKey("Down");
await waitForCursor("file-open-source");
pressKey("Down");
await waitForCursor("video-open");
pressKey("Down");
await waitForCursor("app-quit");
pressKey("Up");
await waitForCursor("video-open");
pressKey("Up");
await waitForCursor("file-open-source");

pressKey("Escape");
await waitForNoMenu();
Expand Down
14 changes: 8 additions & 6 deletions e2e/specs/command-registry.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const DECLARED = [
"file-open-subtitle",
"file-open-source",
"file-close-source",
"file-new-translation",
"video-open",
"file-save",
"file-save-copy",
Expand Down Expand Up @@ -106,13 +107,15 @@ const TITLES = [
];

/**
* File with nothing open: Save, Save a copy and Discard are drawn, and all three are greyed, and
* so are the two that read a second document beside one that is not there (M2.6 S1).
* File with nothing open: Save, Save a copy and Discard are drawn and greyed, and so are the two
* that need a document to act on. Open source subtitle is not among them: a translation is begun
* from a source, so opening one is the first gesture of all (M2.6 S1 and S2).
*/
const FILE_ITEMS = [
{ id: "file-open-subtitle", disabled: false },
{ id: "file-open-source", disabled: true },
{ id: "file-open-source", disabled: false },
{ id: "file-close-source", disabled: true },
{ id: "file-new-translation", disabled: true },
{ id: "video-open", disabled: false },
{ id: "file-save", disabled: true },
{ id: "file-save-copy", disabled: true },
Expand Down Expand Up @@ -508,9 +511,8 @@ describe("the command registry", () => {
// seed the cursor onto row 0 (decision 5): insert, delete and merge all only need that, so they
// ungrey too. Split stays gated behind a caret nothing has placed yet.
expect(flips(empty, open)).toEqual([
// A source is read beside a target, so the item that opens one wakes with the target (S1).
// Close source is not beside it: no source is open, so there is still nothing to close.
{ route: "menu", id: "file-open-source", disabled: false },
// Neither source item moves with a target: opening one never needed a target, and closing
// and translating both wait for a source, which this open is not (S1, S2).
{ route: "menu", id: "file-save-copy", disabled: false },
// Find and Replace need a document and nothing else, so both ungrey with the file (F2, F3).
// Find next is absent from this list on purpose: it also needs a pattern, and nothing here
Expand Down
Loading