Tools → "Switch to PCB Editor" in a project with no .kicad_pcb (e.g. created
from a lone schematic) did nothing: the nav hook found no file for the target
tool and returned false. Now a session that can persist (ToolPage passes the
new createFile prop) writes the templated counterpart via
createProjectFileIfMissing — no download fallback, and it re-checks existence
so a collaborator's file is never clobbered — then navigates to it, matching
native KiCad (pcbnew opens a new board at the derived path). Sessions that
can't persist (read-only viewers, scratch/local-folder) keep the logged no-op.
Also latch the quit dispatcher off before every deliberate tool-switch
navigation (markDeliberateNavigation): the wx port's UnloadCallback runs on
BEFOREUNLOAD and closes the top frame the moment the navigation starts, so
the quit hook history.back()'d over the in-flight navigation — the pagehide
latch is too late (it only fires at commit time).
e2e: new tests/web/tool-switch-missing-file.spec.ts reproduces the flow via a
browser-local (IDB) project created from the home page; playwright-web config
gains VITE_LOCAL_PROJECTS=idb and derives STANDALONE_PORT/CORS_ORIGIN from
WEB_APP_URL (runs the suite past a squatted :3048); both vars declared in
web/turbo.json globalEnv (turbo strict-env strips undeclared vars). Includes
the previously-uncommitted tool-switch spec repairs (URL grammar + z-30
boot-overlay wait).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
On a mobile device (or ?mobile=1) the editors run canvas-only with touch
gestures driving the view:
- touch-gestures.ts: pure recognizer (unit-tested) + DOM shim installed in
boot preRun — one-finger drag → synthetic middle-drag (pan), pinch →
synthetic wheel at the centroid (zoom-to-cursor), tap → left click.
preRun registration order is what lets stopImmediatePropagation suppress
the wx layer's single-finger→LEFT-drag touch mapping.
- kicadSetChrome(bool) embind: hides all AUI panes except DrawFrame + the
menubar/status bar via generic wx APIs (kicad fork untouched); boot polls
it after runtime init. Pairs with the wxwidgets IsShown layout fix.
- mobile-mode.ts: ?mobile=1/0 override or UA-CH/coarse-pointer autodetect;
shell hides its overlays and the inherent-to-mobile preflight warnings.
- e2e: mobile-chromium project (Pixel 7) + 4 specs (chrome-less, tap,
pinch, pan) with screenshot-invertibility assertions; also fixes
tool-switch.spec's stale pre-scope-refactor URLs (was broken on main).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Make the Playwright e2e + kicad suites deterministic so screenshot flake stops
tracing to timing races.
- Blind page.waitForTimeout -> condition waits (expect.poll, web-first
assertions, waitUntil) + readiness helpers (waitForWxApp, waitForCanvasApp).
Remaining sleeps are documented interaction dwells (annotated).
- Defensive "if element exists" branches -> loud asserts; label-fallback chains
-> normalized clickMenuItemByText. First-run wizard for/if loops removed by
seeding calculator/gerbview/pcbnew HTMLs.
- Screenshots: new stableShot(page, name) settles the render in-page (canvas
hash over rAF) then writes a raw PNG to test-results/ for the existing offline
gate (tools/screenshots vs baseline-screenshots). Replaces toHaveScreenshot,
which did inline compare + its own baselines and had decoupled the specs from
the real gate. scale:'css' pinned.
- retries: 0 in both configs.
- Guard: tests/tools/lint-determinism.ts (npm run lint:determinism) bans blind
sleeps / toHaveScreenshot / inline retries / swallowed catches in specs;
documented exceptions carry a marker. Rules in tests/TESTING.md.
Assertions, coverage, and renders unchanged (semantic-equivalence reviewed;
captures pixel-identical modulo inherent timer/timestamp/3d-raytrace variance).
Both suites green at retries:0 (e2e 340, kicad 92); ~35-61% faster.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVX1pHMvRPYHdp6ZfEawrk
New tooling in tests/tools/screenshots/ (TypeScript via tsx):
- compare.ts: one pixelmatch engine (AA-excluded), connected-component
"where to look" boxes, old|new+boxes|heatmap triptych, per-engine floors.
- promote.ts: churn-free updater — overwrite a baseline only when decoded
pixels differ beyond the floor, copying CI bytes verbatim (no re-encode
churn); pulls a CI run via `gh run download` or a local --from dir.
- post-discord.ts: always-on CI-on-main report (SHA + e2e status + the
track-only runtime-perf table), then screenshot triptychs, batched +
size-capped + flood-collapsed + 429-aware.
- perf-report.ts: perf table with Δ vs the previous main run (via gh).
- changelog.ts: no-build git-history baseline differ (Discord trigger B).
- noise.ts / gen-manifest.ts: calibration + manifest generation.
CI wiring:
- wasm-build.yml: post-test step runs the gate + report on the already-
produced test-results (no extra build); report-only (continue-on-error),
posts only on push to main, inert without DISCORD_WEBHOOK_URL.
- ci-ubicloud.yml: secrets: inherit (pass the webhook through).
- screenshot-changelog.yml: ~30s no-build changelog on baseline changes.
screenshot-manifest.json: canonical 354-name set + best-effort engine tags
(313 chromium-swiftshader / 41 firefox-llvmpipe).
Normalize scale:'device'->'css' across 18 spec files (no-op at CI DSF=1)
so committed baselines are uniformly css-scaled.
Design: CI's Linux render is the single source of truth; no pinned
container (accept rare env drift -> re-promote); dev commits via promote.
Replaces the byte-cmp compare-screenshots.sh + file-size-proxy
update-baseline-screenshots.sh (kept for now until the first re-baseline).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>