electron+tests: zoom plumbing (construction-only lights), hermeticity fence for the jobs suite

- ZOOM: the ⌘±/⌘0 ladder is owned, not stock roles — applyZoom sets the
  factor and broadcasts 'lh:zoom'; the renderer publishes --lh-zoom; the
  gutter lengths (.lh-lights-gutter / .lh-closed-toggle) divide by it so
  their RENDERED size is a physical constant at every zoom — the graph
  button stays glued next to the lights, nothing slides under them.
  before-input-event catches ⌘=/layout variants the menu's Plus may not
  claim; did-finish-load re-pushes on reload; getZoom covers first paint.
  The lights themselves stay at construction position — Electron 44 has
  NO setTrafficLightPosition (options-only; the first run crashed on
  it). HIG-acceptable: native lights hug the corner; the web chrome
  compensates around them.
- JOBS SUITE FLAKE, actually fixed this time: the seams are
  module-global and the sweep scans every record — a timed-out async
  test left a running orphan + late wake that poisoned its NEIGHBORS
  (respawned 3 !== 0). New afterEach fence: settle every tracked
  running job, unregister aborts, seams reset to inert no-ops — every
  test starts from identical state regardless of upstream flake.
  Verified: two consecutive full-suite runs 1326/1326.
This commit is contained in:
amosblomqvist
2026-10-05 05:42:27 +03:00
parent f4775ebc93
commit 6b58d87d9e
5 changed files with 147 additions and 6 deletions
+28 -1
View File
@@ -26,7 +26,7 @@
import assert from "node:assert/strict";
import * as fs from "node:fs";
import * as path from "node:path";
import { after, describe, it } from "node:test";
import { after, afterEach, describe, it } from "node:test";
import { createFauxCore, fauxAssistantMessage, fauxText, fauxToolCall, type AssistantMessage, type FauxResponseStep } from "@earendil-works/pi-ai";
import {
accumulateJobUsage,
@@ -123,6 +123,33 @@ function trackJob(id: string) {
createdJobs.push(id);
}
/* THE HERMETICITY FENCE (owner round 7, 2026-10-05): the jobs seams are
* MODULE-GLOBAL (setJobRespawn / setTurnRunner / the abort registry) and
* the sweep scans EVERY job in the store. An async test that times out
* under full-suite load leaves a "running" record and a late wake behind
* — and that stray state then poisons the NEXT test (a foreign respawn
* lands in its counter; a foreign orphan in its sweep). Under isolation
* nothing ever times out, which is why the file passes alone and flaked
* in the full run. The fence makes every test's start state identical:
* all tracked jobs settled, aborts unregistered, seams inert. */
afterEach(() => {
for (const id of createdJobs) {
try {
const job = jobStore.getJob(id);
if (job && job.status === "running") {
jobStore.updateJob(id, { status: "done", activity: "settled (fence)" });
}
unregisterJobAbort(id);
} catch {
/* already gone */
}
}
// Late wakes from a prior test must be inert: a no-op respawn can't
// increment anyone's counter, a no-op runner can't spawn real work.
setJobRespawn(() => {});
setTurnRunner(async () => 200);
});
after(() => {
for (const id of createdChats) {
try {