Commit graph pcbjam/tests/kicad
Author SHA1 Message Date
Gergő Törcsvári
f92266fcee
fix(ysync): wire dialect == file dialect, uuid churn, drift noise
The Y.Doc is the source of truth for the FILE, but both live wires wrote
KiCad's CLIPBOARD dialect — a lossy, paste-oriented format. Every
difference was permanent, unfixable drift.

- pcbnew: serialize footprint blobs with CTL_FOR_BOARD, not CLIPBOARD_IO's
  CTL_FOR_CLIPBOARD, which emitted (version)(generator)(generator_version)
  inside every (footprint …). Keep (locked yes).
- eeschema: aForClipboard=false — clipboard mode collapsed every symbol's
  (instances … (path "/sheet")) to (path "").
- Re-supply (version) at PARSE time only (withFootprintVersion): the token
  is invalid file content but load-bearing on decode — without it the
  parser starts at m_requiredVersion=0 and stamps (hide yes) on every
  mandatory field.
- FOOTPRINT copy ctor: restore mandatory-field uuids (EDA_ITEM::operator=
  keeps the target's const m_Uuid, so Clone() rerolled all four).
- drift: classify order-only diffs as `reordered` — y-sexpr v2 reorders
  legitimately; excluded from counts, report-worthiness and dedupe hashes.
  Migration 0017.
- fpedit from eeschema: AsyncLoad()+BlockUntilLoaded() in initLibraryTree —
  FACE_PCB starts lazily there and never preloaded its libraries.

Guards: wire-vs-file round-trip tests (pcbnew + eeschema),
fpedit-from-eeschema (verified red without the fix), symedit-from-eeschema.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016p9kjdGBdcpwUSjJ3q5xg2
2026-07-20 18:32:04 +02:00
Viktor Vaczi
703cb010b7 eeschema simulator: lazy ngspice_service worker — static sharedspice (XSPICE registry + CIDER), init_dll ifdef, e2e both engines
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 15:59:21 +02:00
Istvan Matejcsok
be00e69e33 test(pcbnew): e2e repro for stacked-modal click routing + wxwidgets barrier
Board Setup -> "Import Settings from Another Board..." -> browse opens three
nested wx modals; a click inside the top file dialog leaked to the Import
Settings dialog beneath it (its checkbox toggled). New Playwright e2e repro
asserts the click no longer reaches the shadowed modal, with a sanity phase
proving it does toggle when Import Settings is the topmost modal. Lives in
tests/kicad/ so the kicad-firefox/kicad-chromium projects run it on both
engines automatically (no routing list; lint-ci-coverage satisfied).

Bumps wxwidgets to the JS-layer input barrier fix (build/wasm/wx.js) that
stops the click leaking through the shadowed dialog's live DOM controls.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 16:53:34 +02:00
Viktor Vaczi
63ed1f3c1f e2e/CI: dual-engine suites, per-engine screenshots, SwiftShader retired, prod web suite, CI-coverage gate
Squash of experiment/ff-big-modules vs main.

Big-module routing removed: native-EH shrank kicad_editor below
SpiderMonkey's x86-64 code budget (runs 29355049705/29356152413 green on
stock Firefox), so BIG_MODULE_SPECS routing and the baseline-only-JIT
crutch are gone — kicad-firefox and kicad-chromium both run the full
suite, with the module compiled the way real users' browsers compile it.

Per-engine screenshots end to end: stableShot/shotPath write
test-results/<engine>/<name>.png; baselines move to
baseline-screenshots/{chromium,firefox}/ and the whole tools/screenshots
pipeline (compare/promote/manifest/spec-map/changelog/Discord) keys on
<engine>/<name>. Previously Firefox and Chromium renders of one spec
overwrote each other and Firefox renders were never actually gated.
Seeded from CI run 29421380806 (92 new firefox baselines, +24 chromium
web-suite shots); manifest generated from the baseline tree.

One merged playwright.config.ts (kicad/asyncify/coroutine/perf as
projects); ~25 dead npm scripts dropped. The web suite is gated in CI for
the first time ever (4 rotted specs fixed, 5 broken lib-bridge specs
triaged as fixme in docs/features/web-e2e-rot/); cheap lint step after
npm ci; last 26 blind-sleep violations fixed.

SwiftShader retired: CI Chromium renders WebGL on ANGLE → Mesa llvmpipe
(--use-gl=angle --use-angle=gl --ignore-gpu-blocklist; the blocklist flag
is mandatory — llvmpipe is blocklisted and WebGL is silently unavailable
without it) in BOTH configs. Under WORKERS=4 congestion SwiftShader
transiently failed the first post-board-load draw and the recovery
cascade ended in a silent permanent Cairo fallback — that engine flip was
the "~1.2% changedRatio both directions" occ-export baseline flake.
Validated 160/160 across two 80-repeat rigs; full analysis in
docs/features/wx-parity-bugs/occ-export-context-eviction.md. Chromium
baselines shift slightly on llvmpipe — promote once from the first green
run. Deflakes the new coverage exposed: presence baselines settle before
capture; presence fixtures declare current file formats; perf gets its
own outputDir so CI evidence survives; occ-export settles the board paint
before the export dialog; menu-item waits (waitForRenderedByLabel before
clickMenuItem) in 4 specs + the TESTING.md rule.

Web suite runs the PROD build, in parallel: webServer becomes backend
`start` + the standalone's e2e:preview (build-preview.mjs: link-wasm →
stash the public/wasm symlink aside during vite build, build-demo.mjs's
move — then vite preview as the persistent server). The wasm middleware
serves /wasm/* in preview and emits COOP/COEP/CORP itself (a pthread
worker script's own response must carry COEP or Chrome kills it with
ERR_BLOCKED_BY_RESPONSE). VITE_* flags bake at build time;
VITE_ALLOW_USER_OVERRIDE joins turbo globalEnv. fullyParallel + default
workers: 5.2m → 1.4m. Determinism fixes the parallel run exposed:
shared-page specs become serial groups; locks.spec grabs alice's exact
item via the new kicadCollabTestSelectByUuid hook (cross-tab "first
footprint" order is not a ysync invariant); quit specs poll page.url()
(quit supersedes its own navigation — NS_BINDING_ABORTED on Firefox).
Suite: 51 passed / 12 skipped / 0 failed in 1.6m.

CI-coverage gate (lint:ci-coverage): every tests/**/*.spec.ts must be
reachable from the npm scripts the workflows invoke — scraped from
.github/workflows/, resolved through package.json, coverage asked from
playwright --list itself. Rules: uncovered-spec + orphan-project (with a
documented LOCAL_ONLY_PROJECTS allowlist). Gating next to
lint:determinism; 138 spec files / 13 projects accounted for.

Product fixes kept from the investigations (reachable on real GPUs too):
wx 7799fd1be5 — paint flags clear before dispatch + Invalidate always
propagates; kicad 3dcfea5e45 — SwiftShader pass-boundary flush +
per-instance font texture + first-frame GL-error drain (GAL recovery
recovers instead of falling back to Cairo) + the user-facing eeschema
switch navigates again under __EMSCRIPTEN__ (project-sync's
FaceRegistered gate had rerouted it into the hidden sync player; caught
by the newly-gated web suite).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018eUxiPApHgGiu9NFyQfhAq
2026-07-17 12:21:54 +02:00
Gergő Törcsvári
2d89f41060
project-sync: boot .kicad_pro synthesis + creation-time companion + live sibling restage
- defaultKicadPro() template + synthesizeProjectFile() writes a minimal
  <stem>.kicad_pro into MEMFS when a board/schematic opens without one, so KiCad
  runs a real project (self-heals legacy fileless projects).
- NewFileDialog creates the .kicad_pro companion alongside a new board/schematic.
- sibling-restage.ts: pcbnew sessions subscribe to sibling .kicad_sch collab rooms
  as invisible observers and re-stage MEMFS on update (restageFile extracted from
  syncProjectToMemfs), so a sync reads live schematic data, not the boot snapshot.
- tests/kicad/project-sync.spec.ts (2 e2e) + sibling-restage.test.ts (5 unit).
- Bump kicad pointer (SCH-from-PCB sync gate + hidden player + .kicad_pro save hook).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdqhM7j71XQn96hcHkabDu
2026-07-16 19:05:23 +02:00
Gergő Törcsvári
720cff54ba
feat(occ): ship board 3D model bodies with STEP/GLB exports (3d-models 0007)
The occ_service export worker has its own MEMFS — the editor's lazily-fetched
lib models were invisible there, so every export was a bare board (54
"Could not add 3D model" warnings on pic_programmer, 2 STEP products).

- models-bridge: collectBoardModelFiles(boardText) — scan refs, ensure via
  the 0004 sparse source (IDB/R2, wrl->step fallback), read staged bytes
  back, dedupe by real staged path.
- occ-service.ts: attach the collected models to every export request
  (best-effort — prefetch failure still exports, misses reported by the
  exporter); transfer the body buffers.
- occ-worker.js (shared app/harness): pass req.models through to occExport.
- occ_service_main.cpp: occExport(board, params, models) stages each entry
  under PCBJAM_3D::MODELS_MEMFS_ROOT (path-sanitized) for the exporter's
  staged-model probe (kicad 83645275ac), removed again after the export.
- tests: harness occ stub mirrors the prefetch against the page kicadLibs
  provider + captures report/productCount; new occ-export-models.spec.ts
  guards the delivery (green companion pins preconditions; guard asserts 0
  missing lib models + component PRODUCTs). pic_programmer: 17/17 staged,
  87 products @ 13.3 MB (was 2 @ 402 KB). models-bridge unit tests 13/13.

Known remainder (0007 step 4): project-local ${KIPRJMOD} refs still drop.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017UjpnviP3ZDxTM1Ap63Sqv
2026-07-10 09:26:07 +02:00
Gergő Törcsvári
1f85e13acf
feat(collab): follow-user (collab-presence 0008) + chip depth-layer fix
Follow-user: click a peer's roster avatar to mirror their viewport until
local input breaks it.
- collab_presence_core.h: CORE::fitViewport(cx, cy, halfW, halfH) — fit the
  leader's world rect with CONTAIN semantics (zoom derived from the
  follower's own canvas via the ToScreen ratio; GetScale is the zoom, not
  px/IU). Exported as kicadCollabFitViewport from both editor TUs + the
  merged dispatcher.
- presence-kicad.ts: publish the visible world rect (viewportRect) into
  awareness, 100 ms trailing throttle; guarded for pre-0008 handles.
- follow-user.ts: createFollow — follows an awareness CLIENT (a tab, not a
  user); applies leader rect changes via FitViewport, dedupes unchanged
  republishes; break-on-interact compares local onViewport echoes against
  the last applied rect (2% rel tolerance, echo-grace before the first fit
  lands); unfollows on leader-left; pauses on eeschema sheet mismatch.
- PresenceRoster: avatars are follow toggles (ring on the followed peer);
  WasmTool renders the "Following <name> — move to stop" banner.
- tests: 7 controller units (85/85 collab), fitViewport round-trip e2e in
  both kicad presence specs (20/20), two-tab tests/web/follow.spec.ts
  (converge → track → wheel-zoom breaks → subsequent moves ignored).

Chip depth-layer fix (user-reported): name chips washed out inside
low-alpha selection fills — chip rects shared the shapes overlay's single
depth, and same-depth fragments drawn LATER lose the depth test, so an
earlier-painted fill rejected the chip's pixels. Now three layers via the
fork's VIEW_OVERLAY::SetDepthOffset: text (0) < chips + pin dots (1) <
selection shapes (2). drawLabel/drawCursor/drawSelectionBox take the chip
overlay explicitly; comment-pin dots move to the chip layer too (the 0005
"drawn last so pins sit above" comment had the rule backwards). Verified
with a chip-inside-30%-fill pixel repro + the full presence suite.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013u9h8fkQktH7KRECaHJmUG
2026-07-10 09:25:13 +02:00
Gergő Törcsvári
fdac450a05
test(presence): rate-based cursor-throttle bound — deflake slow CI runners
The cursor-emit tests asserted a fixed <25 emits for 30 awaited mouse
moves, assuming the sweep outpaces the 50 ms throttle. On a loaded
Ubicloud runner each Playwright->Firefox->WASM round-trip can straddle
a throttle window, so 28/30 moves emitted and the test failed (twice on
main, once from a docs-only commit).

Bound by measured sweep duration instead: one emit per 50 ms window,
+1 leading edge, +2 slack. Fast machines get a tighter bound (~9-13);
slow runners scale with actual elapsed time. A broken throttle still
fails.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lg5jwWuhFH5dL8hEcBDuP2
2026-07-08 12:19:25 +02:00
Gergő Törcsvári
574284c486
feat(collab): local-ops-only undo — remote applies skip the undo stack (ysync miss 09)
Ctrl+Z after a peer's edit no longer reverts (and re-broadcasts) the
peer's work, and the adopt undo-bomb is gone:

- doApply/doApplyItems (both editors) Push with SKIP_UNDO; the emit path
  is unaffected (suppression keys off s_applyingRemote, not undo).
- With SKIP_UNDO no picker owns removed items — the bindings free them
  after Push (explicit removals + upsert's remove-before-re-add; fields
  excluded: CHT_REMOVE hides them, parent keeps ownership). Freeing stays
  out of the fork commit classes so DRC's SKIP_UNDO callers can't
  double-free.
- Test hooks kicadCollabTestUndo/UndoDepth, registered per-editor AND in
  the kicad_editor dispatcher (merged image compiles out per-app
  registrations).
- kicad pointer: eeschema UUID undo guard + SKIP_UNDO connectivity split
  + quiet stale-entry drop (ca8877324c).
- tests/kicad/collab-undo.spec.ts: 5 scenarios (no undo entry from remote
  applies; selective undo; stranded replaced/deleted entries) — 5/5, plus
  collab/ysync regression 30 pass.
- docs: ysync-review 20 fix record; 09 marked FIXED; overview indexed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ejJEvS7ogef2o9gVTXjmp
2026-07-08 11:09:26 +02:00
Gergő Törcsvári
c30bcd4531
feat(collab): selection soft-locks — remote-selected items can't be dragged locally (collab-presence 0007)
While a peer has an item selected, local users can still select it for
inspection but move/drag/rotate/delete skip it with an infobar naming the
holder (native locked-item UX; enforced via the fork's PCBJAM_REMOTE_LOCK
query — kicad 81f9cd80fd, the epic's first fork-touching phase). Overlapping
holds (both grabbed inside the awareness propagation window) tie-break
deterministically: lowest (user.id, clientID) keeps the item, every losing
client auto-releases it.

- lock-tiebreak.ts: pure policy — beats(), remoteLocks() (union of ALL other
  clients' selections incl. own user's other tabs, minus own-held-and-winning
  uuids so the winner isn't blocked mid-release), contestedReleases()
- presence.ts: clients() (per-client view, no user dedupe) + self(); FIX for
  a pre-existing flaky stack overflow — resolveCollision re-entered itself
  synchronously via its own patch's awareness 'change' and could ping-pong on
  stale same-user states (~1-in-3 unit runs); re-entrancy guard defers
  re-resolution to the next genuine delivery
- presence-kicad.ts: locks ride the kicadCollabSetRemote snapshot
  (`locks:[{uuid,name}]`); losing overlaps call kicadCollabReleaseSelection
- wasm bindings (both TUs + merged dispatch): g_locks map + fork query
  install; kicadCollabReleaseSelection (cancelInteractive only when a tool
  stack is live — bare ESC would clear the whole selection — then selective
  RemoveItemFromSel + infobar + forced re-emit); kicadCollabTestGetLocked
- tests: lock-tiebreak unit suite; presence-locks e2e for both editors (real
  move veto — pcbnew click+M hotkey since its default left-drag is
  rubber-band select, eeschema real drag — each with an unlocked control);
  two-tab tests/web/locks.spec.ts (lock propagation + deterministic tiebreak
  release + unlock on clear, passing vs real partykit)

Spec: docs/features/collab-presence/0007 (closed repo).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lg5jwWuhFH5dL8hEcBDuP2
2026-07-07 21:09:24 +02:00
Gergő Törcsvári
3186986a0a
feat(collab): cross-app selection — eeschema symbol ⇄ pcbnew footprint ghost highlight (collab-presence 0006)
Selecting a symbol in eeschema ghost-highlights the linked footprint(s) in
every pcbnew tab of the project, and vice versa — across users AND one
user's own two tabs. Native KIWAY cross-probe is inert in WASM (one frame
per page); this rides the presence layer instead.

- cross-app.ts: project-wide awareness-only room (presenceRoomId), publishes
  full PresenceState at selection rate (cursor always null); peers() = other-
  TOOL clients incl. own user's other tabs; window.__pcbjamCrossApp test handle
- presence-kicad.ts: parseSelectionEmit (bare array | {uuids,fpPaths}),
  xselFromPeerState (pcbnew paths → symbol uuids; eeschema uuids verbatim),
  cross peers appended to the kicadCollabSetRemote snapshot as
  {id "<user>#x<client>", name "<user> · sch|pcb", xsel}
- C++ (zero fork changes): pcbnew emits {uuids, fpPaths} (FOOTPRINT::GetPath)
  and ghost-renders xsel via path-tail suffix scan; eeschema resolves xsel via
  ResolveItem gated to the CURRENT sheet (xsel arrives project-wide, unlike
  room-scoped selections); ghostStyle = alphas × xselAlphaScale (0.55, tuner-
  patchable); new exports kicadCollabGetSelectionFull / TestGetCrossMapped /
  TestSelectComponent (skips power symbols — PWR_FLAG has no footprint) +
  merged-image dispatch
- tests: presence suites extended (payload shape, ghost render pixel tests,
  13/13) + new two-tab tests/web/cross-probe.spec.ts (passing vs real
  partykit); eeschema pixel compares now target the #glcanvas-* GAL panel
  (the whole-window #canvas compare flaked on the auto-dismissing version
  infobar — also fixes the long-known presence-eeschema restore flake);
  cross-app + presence-kicad vitest suites

Spec: docs/features/collab-presence/0006 (closed repo).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lg5jwWuhFH5dL8hEcBDuP2
2026-07-07 21:09:23 +02:00
Gergő Törcsvári
6bc54de92b
feat(collab): presence P3 — eeschema port (collab-presence 0003)
- eeschema_embind.cpp: presence section (0002 pattern, zero fork changes) —
  wx canvas triggers + SCHEMATIC_LISTENER piggyback → post-settle
  SCH_SELECTION_TOOL emit; throttled cursor; remote VIEW_OVERLAY render
  (SCHEMATIC::ResolveItem, name tags, screen-constant via GAL matrix);
  schCollab{PresenceStart,SetRemote,GetViewport,GetSelection,TestSelectFirst,
  TestClearSelection}; kicad_editor_embind dispatches by active frame.
- sheet-manager: parked rooms carry SKELETON awareness states
  ({user,tool,sheetPath=bound sheet}) via publishSkeletons on switch + late
  warm-up — the bound room's awareness then holds every project peer, so no
  multi-room aggregation; presence.ts publishSkeleton helper.
- PresenceRoster: sheet-aware — peers on another sheet render dimmed with
  'on <sheet>' tooltip; WasmTool un-gates the kicad presence bridge for
  eeschema and threads activeSheetPath.
- tests: presence-eeschema.spec.ts (5 e2e, mirrors pcbnew incl. the px/IU
  band at eeschema's 1e4/mm IU); presence.test.ts +2 (skeleton visibility,
  no ghost cursor after rebind). eeschema-collab/subschema stay green.

Verified live: two tabs on demo.kicad_sch — peer cursor cross + label,
selection box + name tag, roster avatar.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvqUd4QsJSGHN28aunJRTq
2026-07-07 21:09:22 +02:00
Gergő Törcsvári
8ad1cc673a
feat(collab): presence P2 — pcbnew selection/cursor emit + remote VIEW_OVERLAY render (collab-presence 0002)
Zero kicad-fork changes — all in the embind layer:
- pcbnew_embind.cpp: presence section — canvas wx Bind() triggers (motion/
  leave/up/key/wheel) + COLLAB_LISTENER piggyback → post-settle selection
  check (dedupe) → onSelection; throttled cursor emit via VIEW::ToWorld;
  viewport push/pull (px-per-IU via the GAL matrix — GetScale() is the zoom
  and sized the first cut's overlay nm-small); kicadCollabSetRemote renders
  peers' cursors (cross + name) and selection bbox outlines into one
  per-user-colored VIEW_OVERLAY (CallAfter+COROUTINE), never touching local
  selection; PresenceStart/GetSelection/TestSelectFirst/TestClearSelection.
- kicad_editor_embind.cpp: merged-image dispatch (pcb-only until 0003).
- presence-kicad.ts: bindKicadPresence — routes emits into awareness (0001)
  and pushes trailing-throttled peer snapshots into the wasm; wired from
  WasmTool.startPresence (pcbnew-gated). +5 unit tests.
- tests/kicad/presence-pcbnew.spec.ts: 5 e2e — programmatic + real box-select
  emit, throttled cursor, remote render with no-leak + pixel restore,
  viewport unit band. Existing pcbnew-collab/items-bridge suites stay green.

Verified live: two tabs over partykit — peer cursor cross + label + selection
outline visible on the other tab's canvas.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvqUd4QsJSGHN28aunJRTq
2026-07-07 21:09:22 +02:00
Viktor Vaczi
c1eb08108b test(screenshots): restore screenshot-neutrality after the determinism refactor
The determinism conversion silently dropped/renamed screenshots vs the committed
baselines, so CI flagged ~35 baselines as "removed". Restore them:

- pl_editor-*: re-add the "pl_editor-" prefix the conversion dropped (5).
- Buffer screenshots that lost their path: (eeschema draw-wires/loaded-css,
  pcbnew draw-lines/loaded-css, pcbnew-move, dark-mode, eeschema-crosshair,
  aui-resize) re-add path:'test-results/<name>' so they SAVE and still feed the
  in-test pixel-diff analysis (17).
- Dropped animating/loading captures re-added via stableShot / raw screenshot:
  timer-03/05/06, dialogs-timer-running/started, wxgrid-02/03, 01-loading (7).

Delete 5 obsolete wizard baselines (eeschema/gerbview/pl_editor/symbol_editor/
generic -wizard-00-initial): every app is now seeded to bypass the first-run
setup wizard (that was the point of removing the wizard for/if loop), so those
states are no longer produced — pl_editor.spec asserts "no wizard appears"
instead.

Net: every committed baseline has a producer again (0 "removed"); determinism
guard green; affected specs pass (e2e 40, kicad 12) and emit the restored PNGs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVX1pHMvRPYHdp6ZfEawrk
2026-07-07 11:35:39 +02:00
Viktor Vaczi
4c3a4cacd4 test(determinism): deterministic waits + stableShot screenshots; drop blind sleeps/ifs/retries
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
2026-07-07 10:50:24 +02:00
Gergő Törcsvári
62ca571802
feat(ysync): lib_symbols travel + layout save-sync (miss 08), TS hot-path opts (12), diff-on-rebind adopt (13) — doc 18
Miss 08A: the binding stores wire-carried (lib_symbols …) definitions in
kdoc_libsymbols and prefixes them on apply wires — a joiner that never saw a
symbol adopts it WITH its definition (new e2e ysync-libsymbols.spec.ts).
Miss 08B: registerSaveHook gains onSavedText; WasmTool routes saved-file text
to syncLayoutToY (per sheet room via the manager's syncLayoutFromSave, or the
single-room doc) so title block / paper / setup edits converge instead of
drifting. Opt 12 (TS half): zod off the observer hot path (yToItemUnchecked),
children index built once per conversion. Opt 13: seed()'s adopt diffs the
editor snapshot against the doc view and applies only the doc-authoritative
difference — clean rebinds apply nothing, the adopt undo entry shrinks to the
real changed set. Opt 14 deliberately deferred (doc 18). All TS-side; no wasm
rebuild (the C++ blob/findLib sides already carried definitions).

Verified: shared 107, standalone 79 (+2 known pre-existing wasm-assets),
ysync e2e 21/21 chromium, collab regression 21/3-skip firefox.

Bumps: web/pcbjam-shared (lib_symbols channel + syncLayoutToY + opts).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JgThWXtdvrYLK47EDFoGdq
2026-07-06 08:57:58 +02:00
Gergő Törcsvári
ae5d41a3d7
fix(ysync): review bugs 01–07 — listener registration, pad nets, child removals, dirty-set emit, targeted rebaseline, seed arbitration, stale hook (doc 17)
All seven ysync-review bugs fixed and verified; every repro's expected-fail
marker removed (they now run as regression tests). Also fixes two bugs found
while verifying (doc 17 F5/F6): file-seeded Y bodies are re-upserted in the
editor's serialization (doc-16 F4 was an artifact of bug 01), and the
0008-era "asyncify-fragile envelope parse" was really wrapInBoardEnvelope
emitting display layer names — canonical LSET::Name() fixes track/via/zone
v2 applies; makeFromBlob now logs parse errors instead of swallowing them.

Verified: shared 98, standalone collab 37, ysync e2e 20/20 (chromium),
collab regression set 21 passed / 3 pre-existing skips (firefox).

Bumps: kicad (board_commit child-removal listener notification),
web/pcbjam-shared (slot prune + arbitrated seed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JgThWXtdvrYLK47EDFoGdq
2026-07-06 08:57:57 +02:00
Viktor Vaczi
4e92baf5d0
test(ysync): repro tests for review bugs 01-07 + v2 items-wire e2e port (miss 11)
The 2026-07-02 sync review (docs/features/ysync-review, ysync-review branch)
found 7 bugs and that the two-tab e2e only exercised the DEAD legacy scalar
wire. This lands plan doc 15 in full; results + empirical findings in doc 16.

- tests/collab/browser-entry-v2.ts (+build.mjs): the PRODUCTION v2 stack
  bundled for e2e (connectKicadDoc + attachKicadCollab, kdoc_* keys), with
  in-page renderActiveDoc/singleSeedRender/driftReport helpers and yjs forced
  to ONE copy (the two web pnpm workspaces otherwise bundle two
  instanceof-incompatible instances).
- tests/kicad/ysync-two-tab.spec.ts: pl_editor green baseline (A↔B edits,
  ITEM-level drift silence) + divergent-uuid adopt; bug-01 pcb/ee fresh-room
  repros (Chromium-only: two kicad_editor tabs exceed Firefox's per-process
  wasm budget); bug-06 concurrent-seed race; bug-03 Y-half.
- tests/kicad/ysync-repros-{pcbnew,eeschema}.spec.ts: bugs 02/03/05 + the
  bug-04 matrix (anchor-centred fp rotation, pad resize, endpoint drag,
  symbol rotation, Value-field edit), each with green landed-preconditions;
  the "local move emits" controls double as headless-emit probes — GREEN on
  both tools, so every emit-dependent repro is a live test.fail.
- wasm/bindings: 7 local-edit test hooks via real commits
  (CallAfter+COROUTINE) — TestRemoveItem/TestRotateItem (both tools,
  dispatched in the merged image), TestSetPadSize/TestMoveEndpoint (pcbnew),
  TestSetFieldText (eeschema).
- web/standalone ysync-repros.test.ts: bug-01 units (C++-faithful fake gating
  emit on ensureBridge) + bug-07a/b (stale DOWN hook, real sheet-manager gap).
- web/pcbjam-shared bump: bug-03/06 unit repros.

Convention: every repro asserts the CORRECT behavior and is expected-fail
(test.fail/it.fails) naming its bug doc; a fix flips it to "unexpected pass",
forcing marker removal — the repro becomes the regression test. Every
expected failure verified (JSON reporter) to fail at its documented assert.
Suite state: 39 passed / 0 failed / 0 flaky / 5 skipped (2 firefox guards,
2 pre-existing legacy two-tab skips, 1 pre-existing roundtrip fixme).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DPfrVhfYgPPgtawjSssZfn
2026-07-06 08:57:01 +02:00
Istvan Matejcsok
561d0500a6 fix(e2e): rescope the 3D fixes for the webgl-era viewer (rebased onto main)
The 3d-webgl merge (kicad eb13ff3bdc: the viewer now defaults to the real
OpenGL renderer via wasm/gl1, and occ-split moves STEP parsing into the
occ_service worker) made the raytracer-era orchestration on this branch moot —
main's chromium-ci phase is green at 15-way parallelism (28666407570 /
28698861536). Drop what no longer earns its complexity, keep the diagnostics,
fix main's live flake, and make the deadlock spec test what it was written for.

- REVERT the chromium-ci-3d serial project, the two-phase test:kicad:ci, the
  SwiftShader GPU-process flags, and the resize-drag/models skips: config and
  package.json are byte-for-byte back to main's shape. The raytracer contention
  they guarded is no longer on the CI path.

- FIX main's live flake: run 28698861536 is green only via retry
  (3d-viewer.spec:26 flaky) and 28666407570's deadlock red sampled an ALL-ZERO
  pixel signature — the viewer's first frame lags the canvas's creation on
  software WebGL under parallel load, and sampling too early reads an all-black
  backbuffer. New waitForThreeDRender() gates render assertions on actual
  pixels (1s-interval full-frame CPU reads) instead of fixed sleeps, used by
  3d-viewer.spec:26 and the models render tail.

- KEEP the storm-proofed samplers (one full-frame getImageData on a
  willReadFrequently canvas replacing 256 per-pixel GPU round-trips per sample
  — the "GPU stall due to ReadPixels" trigger) and the logThreeDDiag
  instrumentation: engine-independent, and they de-risk every remaining
  software-GL pixel read.

- models spec: bridge assertions stay front-loaded (the protocol regression
  signal is independent of the render); the occ_service parse verdict is now
  POLLED — it lands async relative to the bridge ensures, so asserting it
  immediately raced the worker; the render tail runs again everywhere. (The
  pre-webgl raytracer+models renderer-death documented in a17f3be does not
  affect the OpenGL default path — the raytracer-toggle+models combination
  remains untested product surface, tracked outside this branch.)

- deadlock spec: the deadlock it guards is raytracer-specific and the viewer
  now defaults to OpenGL — on the GL engine it either passes vacuously (fast
  renders make every liveness assertion trivial, 28698861536) or fails on the
  black first frame (28666407570). It now flips the engine via the "Use
  raytracing" toolbar toggle (loud assert if the toggle moved) and
  cross-checks engagement by requiring the canvas pixels to CHANGE after the
  flip with no input in between (the raytraced frame is lit differently; a GL
  re-render reproduces identical pixels; heap growth is unusable — mimalloc
  satisfies the raytracer from freed arena pages). That guard immediately
  caught a REAL defect: on the webgl-era wasm build the toggle is INERT (the
  click lands and "Reload time" updates, but the canvas never changes —
  suspects: DoRePaint's silent catch(runtime_error) freezing the canvas after
  a raytracer Redraw throw, or ToggleRaytracing writing m_boardAdapter.m_Cfg
  while RenderEngineChanged() reads GetAppSettings<…>(), possibly different
  instances in the merged bundle). The spec is therefore test.skip-annotated
  as a KNOWN ISSUE with the full engine-force machinery in place — unskipping
  it self-validates the product fix. The CI-skip also stays (raytracer
  liveness needs real-GPU pacing; the Worker-boot deadlock mechanism is
  covered on CI by the standalone wx harnesses).

- 180s viewer-open waits kept as pure CI headroom (never slow a passing run).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 12:21:04 +02:00
Istvan Matejcsok
8b45348f71 fix(ci): 3D specs back to headless SwiftShader + GPU hardening; skip real-GPU drags on CI and the models render tail everywhere (product bug)
The headed Mesa-llvmpipe switch (ce44636) was built on a misdiagnosis. With
--use-gl=angle --use-angle=gl under Xvfb, Chromium gets NO WebGL AT ALL on the
GPU-less CI VM: runs 28652367347 + 28664038296 both log
`glcanvas count before opening 3D viewer: 0` (even the pcbnew board's own GAL
canvas fails to create) and every failure is the FIRST viewer-open wait timing
out. "Timeouts instead of crashes" meant no GL context existed to crash, not
that llvmpipe was stable-but-slow. The Mesa/GLX path works for the Firefox
project, but not for Chromium on this image.

Headless SwiftShader is the environment where GL provably works — the
serialized run 28649537489 opened the viewer and rendered/sampled 56 colours
(viewer:26/130/209 all passed single-tab). Its remaining failures split into
two families, each addressed on its own evidence:

1) SwiftShader GPU-process kills (deadlock spec's lost canvas/black frame):
   - chromium-ci-3d back to headless + --enable-unsafe-swiftshader, plus
     --disable-gpu-watchdog and --disable-gpu-process-crash-limit: heavy churn
     stalls the software-GL GPU process until the watchdog shoots it
     (CONTEXT_LOST -> removed GL canvas -> "Target crashed"); with the watchdog
     off a slow op completes, and without the crash limit a lost GPU process
     keeps restarting for later tests. Same args locally (no-ops on a healthy
     real GPU).
   - Storm-proof every canvas sampler (3d-viewer, models, deadlock specs): one
     full-frame getImageData on a willReadFrequently (CPU-backed) 2D canvas +
     JS grid sampling, replacing 256 per-pixel getImageData GPU round-trips per
     sample — the "GPU stall due to ReadPixels" trigger, worst in the deadlock
     spec's 1.5s-interval settle polling. Sampled pixels and sig order are
     unchanged.
   - edge-resize spec: keep the open + 5-resize-handles assertions on CI, then
     test.skip the drag — each resize step re-raytraces synchronously on the
     wasm main thread and the 12-step drag blocked mouse.move past the 240s
     budget even single-tab. Real-GPU-paced; still runs locally (deadlock
     spec's CI-skip from d9900fe unchanged, same rationale).

2) KNOWN PRODUCT BUG uncovered in the models spec (NOT SwiftShader, NOT CI):
   raytracing a scene WITH component models kills the Chromium renderer
   process outright ~6s after scene build — silent process death with a FLAT
   wasm heap (531-637 MB watched at 2s intervals; not OOM), no console error,
   no wasm abort, no crash report. Deterministic on a real GPU (5/5),
   fixture-independent (700 KB USB-C and 61 KB USON-8 STEP crash identically);
   the same death hits CI SwiftShader at ~66s. Board-only raytraces complete
   and stay alive, so the bug is specific to the model path. The spec's one
   historical green ended at raytrace-age ~6s — inside the death window by
   luck; every CI run of it ever died. Fix here: front-load the bridge
   assertions (ensure requests fire during scene BUILD, before the raytrace),
   so protocol regressions still fail the suite everywhere, and test.skip the
   render tail unconditionally with the bug documented at the skip site. The
   raytrace->blit pipeline stays gated on CI by 3d-viewer.spec's board-only
   render. Bug tracked for a dedicated fix session.

Correct the now-wrong llvmpipe comments (config, open-wait rationale, deadlock
skip reason). Expected CI matrix: viewer open/render/stacking/titlebar +
models-bridge on SwiftShader serial; edge-resize drag + camera-move deadlock
on real GPUs locally; the Worker-boot deadlock mechanism stays covered on CI
by the standalone wx harnesses.

Locally validated on a real GPU (--project=chromium): 5 passed + 1 skipped
(the documented models render tail).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 12:21:04 +02:00
Istvan Matejcsok
d60bc1575b fix(ci): 3D specs — 180s viewer-open wait for llvmpipe; skip deadlock spec on CI
Follow-up to the headed-llvmpipe switch. That run (28652367347) stopped CRASHING —
every failure became a plain timeout, confirming llvmpipe gives a stable WebGL context
(no more CONTEXT_LOST / "Target crashed") but software raytracing is just slow:

- The first board raytrace takes ~60s on llvmpipe, right at openThreeDViewer's 60s
  wait → raise it to 180s (shared helper + the models spec's inline copy). Real GPU
  returns in ~2s, so it is only CI headroom.
- The deadlock spec asserts the wasm main thread stays responsive within ~15s DURING a
  raytrace — a premise only true on a fast (real) GPU. Under slow software rendering a
  legitimately-slow raytrace is indistinguishable from a deadlock, so skip it on CI
  (test.skip on process.env.CI). The Worker-boot deadlock MECHANISM stays covered on CI
  by the standalone coroutine-pthread-ondemand / raytrace-threads wx harnesses; the spec
  still runs locally on a real GPU.
- Gate the headed + llvmpipe launch flags on CI so chromium-ci-3d behaves like a normal
  bundled-Chromium project locally.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 12:21:04 +02:00
Istvan Matejcsok
add7c60975 fix(ci): serialize the heavy 3D-viewer e2e specs (dedicated chromium-ci-3d project)
The three heavy 3D-viewer specs (3d-viewer, 3d-viewer-deadlock, 3d-viewer-models)
crashed on CI (run 28604015154): "Target crashed", "browser has been closed", and a
black frozen canvas. Each boots the 3D-enabled pcbnew build, which pre-warms
~hardwareConcurrency*2+8 (~68 on the 30-core VM) Web Workers AND runs a multi-threaded
CPU raytracer over SwiftShader software-WebGL. Under fullyParallel the three spec FILES
run in separate browser processes concurrently, and the simultaneous raytrace tabs
exhaust the shared GPU process's ~16 live-WebGL-context limit / renderer memory. The
pool pre-warm (the deadlock fix, 7630c7e) is correct and present in the tested binary;
per-file isolation prevented in-process Worker accumulation but NOT this cross-process
concurrency — the gap these specs' first CI exposure hit.

Fix (test-orchestration only; no wasm/submodule changes):
- Route the three specs to a dedicated `chromium-ci-3d` Playwright project (own
  outputDir=pw-artifacts/kicad-3d so its failure traces survive the later perf run's
  wipe; the existing tests/pw-artifacts/** upload already captures it).
- test:kicad:ci runs it as a SECOND `playwright test` pass with --workers=1, so at most
  one raytrace tab is alive at a time (the environment in which a single 3D load is
  reliably green — docs/features/3d-raytracer/README.md §7). An exit-code-aggregating
  sequencer runs both phases and fails if either fails (no silent skip of the 3D pass).
- Cap phase-1 CI workers to 12 as headroom under the WebGL-context cap.
- Log a resource-diagnostic snapshot (hardwareConcurrency, pthread pool, GL-canvas
  count, wasm/JS heap) before each 3D interaction so any recurrence is attributable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 12:21:04 +02:00
Viktor Vaczi
db9d6ee04b feat(wasm): occ-split — lazy occ_service worker; kicad_editor drops OCC (−31%)
Move OpenCASCADE out of the merged editor image into occ_service: a separate
emscripten module (-sASYNCIFY=0, MODULARIZE, in-container -Oz finalize, 2N+8
pre-warmed pthread pool) booted lazily in a dedicated Web Worker on the first
STEP export or STEP/IGES model parse. kicad_editor.wasm ~190 MB -> 130 MB;
sessions that never touch OCC never fetch its 57 MB. STEP export works in the
browser for the first time: the unchanged desktop dialog runs EXPORTER_STEP,
whose wasm shadow suspends into globalThis.occService and the export bytes go
straight to a browser download (never entering the editor heap). STEP/IGES 3D
models parse in the worker via the oce shadow (S3D WriteCache/ReadCache wire).

- wasm/occ-service/: service CMake target (hooked from the kicad fork's
  top-level CMakeLists, wasm/editor pattern), embind entry
  (occExport/occLoadModel), wxConfig pre-js.
- wasm/stubs/{exporter_step,oce_plugin}_stub.cpp: EM_ASYNC_JS worker bridges
  (callee-shadowing; no caller #ifdefs).
- web/standalone: provider installed whenever the kicad_editor bundle boots
  (cross-face safe); ONE shared worker-boot source occ-worker.js (vite ?raw;
  the e2e stub reads the same file) — blob worker with locateFile absolutized
  against the glue URL; export download-name guard.
- deps: OCC builds with RapidJSON so its glTF/GLB writer exists — pinned to
  the vcpkg master snapshot 2025-02-26 (24b5e7a8b27f), the same code official
  KiCad consumes via vcpkg.json's opencascade[rapidjson]; rapidjson's latest
  tag (v1.1.0, 2016) is ill-formed under modern clang.
- tests: occ-export dialog e2e (lazy-fetch boundary + STEP download bytes),
  occ-probe incl. a 9-format matrix (step/stpz/brep/xao/ply/stl/glb/u3d/pdf),
  3d-viewer-models hard-asserts the worker parse; occ provider stub installed
  ambiently by the kicad fixtures.

Validated against desktop kicad-cli 10.0.4: geometric exact equality (bbox
delta 0 um, volume delta 0.0000%) for STEP/GLB/STL/BREP/STPZ across three
boards and option sweeps — with desktop OCC 7.9 vs wasm OCC 7.8; PLY/XAO/PDF
structurally equal; U3D same-size (quantizer float LSBs differ). Full kicad
e2e green on Firefox and Chromium; standalone verified end to end (lazy fetch
only on the Export click; export.step 60,628 B ISO-10303-21; loadModel 700 KB
STEP -> 569 KB scenegraph cache).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 12:39:58 +02:00
Istvan Matejcsok
c5b4561495 docs+test(wasm): Part 2 as-built record; cross-face probe spec
- docs/features/editor-unification/06-part2-implementation.md — the merged
  kicad_editor as-built record: symbol-collision surgery (incl. the 21-symbol ODR
  audit), embind dispatcher, wiring, and validation results (frame-runtime 4/4,
  full suite 67 passed / 1 known pre-existing 3d-viewer drag failure fixed on main
  by 7630c7e, collab 10/10, cross-face probe green). README status/index updated;
  dangling perf/bundle-size.md links → perf/README.md.
- tests/kicad/xface-probe.spec.ts — permanent cross-face guard: a --frame=sch
  session opens Preferences and asserts the PCB engine's pages ("PCB Editor",
  "Footprint Editor") exist — i.e. KiFACE(FACE_PCB) lazy-starts inside a schematic
  session (absent on single-kiface bundles). Routed to BIG_MODULE_SPECS.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 17:56:02 +02:00
Istvan Matejcsok
bbeef6d20e feat(wasm): kicad_editor — merge the pcbnew+eeschema kifaces into ONE bundle (Part 2)
All four editors (PCB / Footprint / Schematic / Symbol) are now runtime --frame
choices of a single kicad_editor.wasm (178 MB at -O1 vs 147+82 separate; shared
wx/common/boost linked once). One editor per page load, as before; frames pcb /
fpedit / sch / symedit.

- wasm/editor/: the merged executable target (single_top + both kiface library sets,
  whole-archive pcbcommon) + the safety-net focus-walk Kiface() dispatch TU. Gated by
  KICAD_WASM_MERGED_EDITOR (kicad submodule bump carries the fork side: per-engine
  Kiface/getter binding + ODR renames + dual-kiface launcher).
- wasm/bindings/: per-editor collab entries renamed pcbCollab*/schCollab* (JS names
  unchanged); duplicate kicadOpenFile/kicadCollabOnSave + shared-name registrations
  guarded behind KICAD_MERGED_EMBIND; new kicad_editor_embind.cpp registers each
  shared JS name once, dispatching on the live frame.
- Build: kicad_editor app (build wrapper, target case arms, 3-object embind compile
  with the ABI-critical flags, STUB_APP=pcbnew); docker/build.sh "all" =
  kicad_editor calculator pl_editor gerbview (pcbnew/eeschema stay as explicit debug
  apps); scripts/kicad/audit-merged-symbols.sh = repeatable ODR-collision audit (run
  on kicad bumps).
- Frontend: Bundle type (bundle ≠ tool); TOOL_BUNDLE maps all four editors to
  kicad_editor; explicit --frame tokens for pcbnew (pcb) and eeschema (sch); publish
  list = the 4 real bundles.
- Tests/CI: five harnesses load kicad_editor.js with explicit frame tokens;
  PCBNEW_FAMILY_SPECS renamed BIG_MODULE_SPECS + the 8 eeschema-family specs (they
  now boot the merged module — SpiderMonkey x86 CI OOM routing); frame-runtime spec
  covers all four frames from the one bundle.

Validated so far: frame-runtime 4/4 (each frame boots with the right title, no
aborts, no duplicate embind registration); 24-spec merged-module regression green;
3D raytracer renders. Known pre-existing failure: 3d-viewer title-bar drag deadlock,
fixed on main by 7630c7e (2N+8 pthread pre-warm) — picked up by the follow-up rebase.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 17:56:02 +02:00
Istvan Matejcsok
c6716e6b60 feat(wasm): unify editor builds — footprint/symbol editors via runtime --frame
The footprint and symbol editors are no longer separate WASM bundles: the frontend loads the parent pcbnew/eeschema bundle and passes --frame=fpedit / --frame=symedit (TOOL_BUNDLE + TOOL_FRAME -> Module.arguments in boot). Drops the two duplicate build+deploy targets and their wrapper scripts + vestigial embind; adds low-level harnesses (footprint_editor.html, symbol_editor.html) and a runtime-frame spec. Bumps the kicad submodule to the runtime --frame launcher.

The frame-runtime spec is listed in PCBNEW_FAMILY_SPECS so CI routes it to the chromium-ci (V8) project — its footprint case boots the pcbnew module, which OOMs SpiderMonkey on x86 CI. Includes the editor-unification dossier (research docs 01-04 + the as-built implementation record 05).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 17:56:02 +02:00
Istvan Matejcsok
7630c7e521 fix: 🐛 raytrace deadlock - pre-warm 2N+8 pthread Workers 2026-07-02 13:01:02 +02:00
Gergő Törcsvári
6f3f3bcf00
feat(3d): lazy 3D model delivery R2→IDB→WASM
- cdnModelsSource: sparse per-lib stacks over the models CDN layout
  (libs/kicad-models/<tag>/<lib>/manifest + content-addressed blobs)
- models-bridge: board prescan ((model …) scan → prefetch, 4/4 in 74ms
  on the demo board) + the kind=model3d 'ensure' op answering absolute
  MEMFS paths; only board-referenced bodies ever enter IDB
- boot/runner/config/WasmTool wiring (VITE_MODELS_MANIFEST_URL,
  KICAD*_3DMODEL_DIR seeding, prefetch badge)
- StorageUsageCard on HomePage: per-kind cached sizes + delete-3D-cache
- publish-models.ts (brotli content-addressed publish, 606→93MB for the
  pic_programmer lib set) + dev-demo --models-tag/--models-local
- e2e: 3d-viewer-models.spec.ts (bridge normalize/dedup + STEP/WRL
  render); submodule bumps: kicad (static 3D plugins + ensure hook),
  pcbjam-shared (sparse sync layer)

Spec + findings: docs/features/3d-models (private repo).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014AT7gVHRktDYoQ68S4x6A4
2026-07-02 11:35:36 +02:00
Viktor Vaczi
13551f4f22 feat(tests): screenshot regression + Discord review, perf-tracked
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>
2026-07-01 18:17:20 +02:00
Viktor Vaczi
3e5791774b test(perf): track-only runtime-perf E2E for eeschema + pcbnew
Measures the current build's cold load, open+render, and pan/zoom FPS
(1x/4x/6x CPU-throttle sweep) for both editors, writing
tests/test-results/perf-*.json. Track-only: asserts only that the app booted
and the document opened — no perf thresholds, so it never gates CI.

Runs on a new Chromium `perf` Playwright project (CDP throttling; pcbnew needs
V8 anyway) via `npm run test:perf`, and is wired into wasm-build.yml as a
continue-on-error step so the numbers are captured/uploaded without flaking the
gate. Reuses the existing fixtures, ready-signal, fs-inject, and board-ready
helpers; FPS counter is cancel-able so a throttle sweep on one page doesn't
accumulate rAF loops.

.gitignore: ignore /benchmark-builds/ (disposable prebuilt-WASM + standalone
harness bundle used to reproduce the native-vs-JS-EH comparison locally).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 14:43:54 +02:00
Istvan Matejcsok
3628a090dd feat: 🎸 wxWidgets dialog/frame header/drag/resize 2026-06-30 15:46:52 +02:00
Istvan Matejcsok
cf1fdf5c3d feat: 🎸 wxWidgets dialog/frame DOM header 2026-06-30 15:46:49 +02:00
Viktor Vaczi
c1ef489cfa feat(wasm-eh): migrate the WASM build to native wasm exceptions (+ 3D viewer default-on)
Replace the legacy Emscripten JS-exceptions model with native wasm-EH (legacy
encoding) across the whole build, keeping Asyncify coroutines working via a
from-source Binaryen --hoist-cpp-catches pre-pass. Net result: native-EH is the
only build mode, the 3D viewer is on by default, and pcbnew shrinks substantially.

Highlights:
- Binaryen submodule everywhere + --hoist-cpp-catches integration in apply-asyncify;
  post-link Asyncify covers every app wasm (not just standalone test wasm).
- Build deps (incl. OpenCASCADE without OCC_CONVERT_SIGNALS) and all KiCad apps
  with -fwasm-exceptions; emscripten_sleep added to the post-link asyncify-imports.
- libcontext fiber entry wired under native exceptions; while-loop main loop +
  currData shim injected into all wx apps.
- Native-EH collab apply fixed: DEBUG-define the embind TU + match all out-of-CMake
  C++ TUs' ABI flags to the core, fixing the vtable-layout skew / mis-dispatch.
- 3D viewer enabled by default (real raytracer linked, not the stub).
- Retire the EH-spike scaffolding; flip the asyncify-races ablation pins to
  shim-redundancy pins (native-EH stays clean with the legacy shims ablated).
- Fix the asyncify-races quiescence check to not require Asyncify.currData==0:
  under the native-EH per-frame-yield top loop the main stack is asyncify-suspended
  every frame, so currData legitimately churns (a freed-but-not-yet-nulled buffer,
  not a leak). Refresh the pcbnew toolbar screenshot baseline for the new kicad.
- CI: drop the obsolete binaryen_version input/env (the build uses the binaryen
  submodule fork's wasm-opt, not a version download); key the wasm-output cache on
  the binaryen submodule SHA instead.

Bumps the wxwidgets + binaryen submodules to their squashed feature commits.

Validated green: all 7 apps native-EH (real 3D in pcbnew); KiCad e2e 63/63
Firefox + Chromium (3D viewer renders); wx 336; coroutine 34/34 both engines;
asyncify 7/7 both engines.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 09:40:26 +02:00
Istvan Matejcsok
40629fe9b5 test(eeschema): drop obsolete test.fail on crosshair cycle
The crosshair toolbar group-cycle (ACTION_TOOLBAR::onToolEvent) was
expected-to-fail because the kicad submodule was a pre-10.0 (9.99.0)
snapshot that lacked it. The KiCad 10.0.4 rebase on this branch restores
the behavior, so the test now passes and Playwright flagged it as
"Expected to fail, but passed". Remove the test.fail annotation as the
test's own comment instructed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 10:31:22 +02:00
Istvan Matejcsok
cdaa96250d fix(wasm): KiCad 10 kiplatform/libgit2 stubs, e2e fixtures, asyncify -g hook
- wasm/kiplatform: add EnableDarkMode, IO::TimestampDir, UI::AllowNetworkFileSystems,
  UI::CancelPendingScroll, SECRETS::DeleteSecret (new KiCad 10 functions).
- wasm/stubs/libgit2_stub.c: ~156 no-op stubs for KiCad 10's restructured git
  backend + git-backed local history + text_eval VCS.
- tests/kicad: bump the demo project dir 9.99 -> 10.0 (KiCad 10 stores projects
  under /home/kicad/documents/kicad/10.0/) — fixes load-pcb + 3d-viewer e2e.
- scripts/common/apply-asyncify.sh: ASYNCIFY_EXTRA_OPTS hook (e.g. -g for
  symbolizable wasm traces).
- kicad submodule -> ef00c2d3 (KiCad 10 link + runtime adaptations).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 10:30:25 +02:00
Istvan Matejcsok
a4dd0eaf3b fix: 🐛 wxWdigets popup position 2026-06-23 14:32:38 +02:00
Istvan Matejcsok
5c6e426b00 fix: 🐛 hide print preview 2026-06-19 10:25:14 +02:00
Istvan Matejcsok
1b1e7884ee test: 💍 wxWidgets secondary canvas 2026-06-19 09:22:58 +02:00
Istvan Matejcsok
0195ff0941 feat: enable WASM 3D viewer build — FFP link stubs, drop glemu shim
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 09:21:37 +02:00
Istvan Matejcsok
68c711e633 feat: enable the 3D viewer in the WASM port (WIP)
Builds KiCad's 3D viewer for the browser (opt in with BUILD_3D_VIEWER=ON) and
wires up the legacy-GL emulation it needs.

- build-kicad-target.sh / docker/build.sh: BUILD_3D_VIEWER →
  -DKICAD_BUILD_3D_VIEWER_WASM=ON + -sLEGACY_GL_EMULATION + the GL js-library.
- wasm/shims/gl_immediate_shim.js: display-list emulation (record/replay board
  layers), fixed-function + GLU stubs, throw-guards for unsupported pnames, and
  per-context GLImmediate init for the viewer's SECOND WebGL context.
- tests/kicad/3d-viewer.spec.ts + utils/pcbnew-ready.ts: open View → 3D Viewer.
- bumps the kicad submodule (EMSCRIPTEN-guarded GL changes).

WIP: the viewer window + UI render and geometry draws (glError=0x0), but the
board is not yet visibly rendered — GLImmediate's FFP shader program isn't
linked on the viewer's 2nd WebGL context. Full status + next steps in
features/feat/add-3d-view/notes.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 09:13:41 +02:00
Gergő Törcsvári
b1320afab9
feat(eeschema): hierarchical subschema collaboration — per-sheet rooms, lazy seed, save flow, embind + e2e
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 17:55:30 +02:00
Istvan Matejcsok
17e92cb1a2 fix: 🐛 01-revert-dead-code
01-revert-dead-code.md
2026-06-17 08:16:09 +02:00
Istvan Matejcsok
aa9d613f50 fix: 🐛 press m & move w/ arrows 2026-06-16 15:46:43 +02:00
Istvan Matejcsok
58fb1c5db7 fix: 🐛 pcb layers tab 2026-06-16 09:33:41 +02:00
Viktor Vaczi
68430fa87c feat(wasm-dom): tests for context menus + scrollbars; fix clean wx build
- Standalone wx test apps (contextmenu, scrollbar) + e2e specs proving the
  wxScrollBar control, the wxScrolledWindow gutter, and DoPopupMenu.
- KiCad in-app specs: pl_editor + pcbnew canvas right-click context menus and
  scrolled-panel scrollbar gutters, with baseline screenshots.
- build-wx-wasm.sh: serial -j1 fallback when the parallel build trips the
  intermittent clean-build race (a generated header read mid-rewrite by
  concurrent compiles; never happens at -j1). Recovery resumes from built
  objects; a genuine error still fails the serial pass.
- Bump wxwidgets submodule (context menus + draggable scrollbars).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 23:15:49 +02:00
Viktor Vaczi
7dc1bf27e0 refactor: collapse dual-mode plumbing — the DOM port is the only WASM build
The canvas (wxUniversal) mode is gone (wxwidgets submodule); remove every
piece of side-by-side plumbing so there is exactly one build and one test
flow:

- scripts/build-wxuniversal-wasm.sh -> scripts/build-wx-wasm.sh; no
  --dom/--enable-universal; builds into build-wasm/wxwidgets
- build-wasm-test.sh: no DOM_BUILD / apps-dom rsync mirror / PORT=dom;
  apps build straight into tests/apps (Makefile.wasm PORT conditionals
  collapsed; wx.js + wx-dom.js always pre-js)
- docker/build.sh, build-kicad-target.sh, env.sh: WX_PORT / -dom /
  -universal suffixes removed; kicad builds to kicad-<app>, outputs to
  output/; wx.js/wx-dom.js copied from the real source path
  (/workspace/wxwidgets/build/wasm — the old build-wasm path never
  existed and silently failed)
- setup-kicad-wasm.sh: single target dir; the perl wx-dom.js injection is
  gone — the 7 checked-in kicad pages now reference wx-dom.js directly
- playwright configs serve apps/; fixtures drop the test-results/dom and
  logs/wxwidgets/dom namespacing; boot.spec asserts wxDomPort
  unconditionally; pcbnew.spec uses one reference image;
  appearance.spec assertions unconditional
- compare/update-baseline-screenshots.sh: --port removed
- tests/gal-regression/wasm/Makefile: links build-wasm/wxwidgets and
  carries wx-dom.js as a second pre-js — the gal-webgl suite (30 specs)
  now actually builds and runs here (it needed host-side boost+glm via
  scripts/deps; the bundle had been missing, timing the whole spec out)
- tests: clickCanvas() dispatches via page.mouse (DOM widgets
  legitimately cover the canvas; locator actionability refused the
  click); the comprehensive spec drives wxChoice through its native
  <select> (browser-owned popup cannot be coordinate-clicked)
- docs: README/CLAUDE.md/build.md script names and dirs;
  features/wx-dom-port README reframed (DOM is THE port), visual-notes
  bugs 26-28; FindwxWidgets.cmake config label drops 'wasmuniv'
- wxwidgets submodule -> 9dbacc9448 (DOM-only port, fork diff shrunk)

Gate: full wx e2e suite 292 passed / 1 skipped / 0 failed — first run
ever with the gal-webgl specs green (28 scenarios + load + sequential).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 20:19:48 +02:00
Viktor Vaczi
2c1b0d409b feat(wasm-dom): appearance-panel e2e spec + wheel propagation; visual notes
New tests/kicad/appearance.spec.ts: tab cycle through Layers/Objects/
Nets and back (with blank-rows regression assertion), wheel scrolling
in Layers (movement + exact scroll-back restoration) and Objects, with
screenshots per state. All 3 pass against the DOM pcbnew.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 20:18:48 +02:00
Viktor Vaczi
14ca16cbd3 test(asyncify): red-green race harness + ablation flags, unwind-catch shim, spec tightening, decisions docs
The asyncify single-slot work, executed red-green (full ledger:
docs/features/asyncify-arbiter/redgreen.md; decisions record:
docs/features/async/07-decisions-and-outcome.md):

- tests/apps/standalone/asyncify-races/ + tests/asyncify/ + dedicated
  playwright config: 8 scenarios reproducing the KiCad asyncify failure
  family with the kicad-faithful startup topology (pre-park fiber swap →
  park throw through the live trampoline). Built in 3 variants; the
  SHIM_DISABLE_TRAMPOLINE_HEAL / SHIM_DISABLE_HANDLESLEEP ablation builds
  keep the historical hang and index-out-of-bounds crash reproducible
  forever (mutation-style pins for the existing shims).
- scripts/common/shims/handlesleep.js: catch the "unwind" park sentinel
  in the wakeUp path — when main's last pre-park suspension was a sleep,
  the main-loop park throw escaped through that sleep's promise reaction
  as an uncaught rejection (the calculator/gerbview console errors).
- scripts/common/inject-dyncall-shims.sh: SHIM_DISABLE_* ablation knobs.
- Spec tightening (the acceptance bar): 'uncaught exception: unwind'
  tolerance DELETED from pcbnew/eeschema specs; load-pcb gained a hard
  clean-console gate over 5 asyncify corruption signatures.
- wxwidgets pointer bump: modal LIFO resolvers, pump resolve-on-error,
  sync clipboard IsSupported (014f67e6c1).

Final state: asyncify suite 7/7, wx e2e 291/292 (1 skip), KiCad e2e 40
passed / 2 skipped with ZERO corruption signatures in any log across all
six apps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 17:02:59 +02:00
Gergő Törcsvári
8131bf9bac
feat: standalone save/load routing + VITE_DOC_SOURCE ydoc mode
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 14:05:40 +02:00
Gergő Törcsvári
07863da416
feat: ysync 0008 Stage D — wire the v2 items collab into the app + verify
- WasmTool: hard cutover to startKicadCollab (gates on
  kicadCollabSnapshotItems; scalar reconciler no longer runs in the app)
- file-seed (ysync 0005 tie-in): empty rooms are seeded from the opened
  file via fileToDoc/docToY (meta+layout+items — file recoverable from the
  Y.Doc alone); populated rooms still adopt (seed-once unchanged)
- binding: gate UP applies until seed() — the provider's initial state
  sync otherwise streams the full doc into an editor that already holds
  the file (trapped eeschema's paste path in the real app)
- pcbnew blobForItem: footprints Format a uuid-corrected copy directly —
  SaveSelection's copy regenerated mandatory-field uuids (FOOTPRINT copy
  ctor assigns into fresh fields), breaking wire identity (rebuild)
- roundtrip.spec on the v2 items wire; new passing pcbnew
  footprint-containment round trip (the 0004 containment win); full
  fixture stays fixme on the tracked envelope-parse limit
- real-app two-tab emit verified for eeschema + pcbnew (see 0008 doc)

Full kicad suite: 45 passed, 3 skipped (known), 0 failed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 14:05:40 +02:00