Commit graph

537 commits

Author SHA1 Message Date
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
Istvan Matejcsok
d9264549bf test: 💍 regression test for the notebook layer-list collapse 2026-07-06 09:17:10 +02:00
Gergő Törcsvári
09cd9566ab
sync: bump kicad pointer after rebase onto wasm-port
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JgThWXtdvrYLK47EDFoGdq
2026-07-06 09:01:51 +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
Viktor Vaczi
80135a99ac
docs(ysync-review): repro suite results + empirical findings (doc 16); plan 15 executed
New doc 16: the full repro-suite map (per-bug unit/e2e paths with verified
failure sites), the phase-C probe outcome, and four findings only the RUNNING
system revealed:
- F1: bug 03's sending half emits NOTHING — a child-only delete commit never
  triggers a flush at all (worse than the doc's predicted bare removed-wire);
  the GetWidth-assert tracer evidence and the fix implication.
- F2: Firefox cannot host two kicad_editor tabs in one context (per-process
  wasm budget) — bug-01 two-tab repros are Chromium-only.
- F3: headless emit WORKS on both pcbnew and eeschema — the legacy two-tab
  skip rationale and items-bridge localEdit omissions are stale.
- F4: drift-detect is strictly ITEM-silent on the green path (no writer-
  formatting false positives).

Cross-updates: 00 index + verdict note; 01/04/05/06/07 Verification sections
gain their repro paths; 02 upgraded to runtime-CONFIRMED; 03 gains the F1
empirical correction; 11 (no v2 e2e coverage) CLOSED with a point-by-point
status update — only the legacy retirement remains.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DPfrVhfYgPPgtawjSssZfn
2026-07-06 08:56:11 +02:00
Viktor Vaczi
f9696ac503 site(landing): demo-first CTAs, light theme, honest copy; restore positioning map
Now that demo.pcbjam.com is live, the landing page leads with it instead
of "coming soon":

- Hero: "Open the live demo" is the primary CTA; the coming-soon panel is
  replaced by a DemoLaunchCard (screenshot + play overlay, new tab — the
  editor needs COOP/COEP so it can never be iframed here); waitlist demoted
  to a ghost button under a "Want early access to multiplayer?" lead-in.
- Removed the Gerber demo and positioning-map sections, then restored the
  positioning map per review — recolored for light theme and without the
  multiplayer badge/legend ("Everyone picks one. We do both.").
- Light theme: flipped the :root ink tokens, darkened accent/signal for
  contrast on white; fixed two dark-assumption bugs it exposed (header CTA
  text losing to .nav>a specificity, DemoVideo play scrim derived from --bg).
- Copy: positioned around what's live (real KiCad in a tab) with
  multiplayer explicitly "coming — join the waitlist"; cut untrue claims
  (GitHub import, self-host, offline PWA -> roadmap); cut the jam metaphor
  outside the product name; TrustBar fake-stat grid -> plain claims strip;
  footer tagline + fallback meta description aligned.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01REwWZGkAz1AZUJw8yqxkCN
2026-07-04 21:04:37 +02:00
Viktor Vaczi
bd00bef96d copy(waitlist): drop "no spam, unsubscribe anytime" from demo + marketing
Trim the waitlist microcopy on the standalone demo and the marketing site's
CTA/hero forms to just the value prop, removing the "no spam, unsubscribe
anytime" reassurance line.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01REwWZGkAz1AZUJw8yqxkCN
2026-07-04 12:37:33 +02:00
Istvan Matejcsok
c8580329e1 chore(screenshots): promote baselines from main run 28703177748
The three 3D-viewer shots changed because the specs now screenshot the
COMPLETED render (pixel-gated waitForThreeDRender) instead of a fixed-sleep
mid-render frame. The rest is the accumulated unpromoted backlog: eeschema/
symbol-editor drift, and first-ever baselines for the models-spec renders
(tail re-enabled in 561d050), frame-runtime, occ-export/probe and
xface-preferences from earlier merges.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 12:32:00 +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
4c33ff6f66 fix(ci): run heavy 3D-viewer specs headed on Mesa llvmpipe (not headless SwiftShader)
Serializing the 3D specs (prior commit) proved the crashes are NOT concurrency: with
--workers=1 (one tab at a time) the same three heavy-interaction specs still failed
(run 28649537489). The [DIAG] instrumentation + trace console pinned the real cause —
under HEADLESS SwiftShader on the GPU-less CI VM, the CPU raytracer's WebGL blit plus
the tests' drawImage(glCanvas) pixel reads trigger "GPU stall due to ReadPixels", and
Chromium's GPU watchdog then resets the context (CONTEXT_LOST_WEBGL) or crashes the
renderer ("Target crashed") — even single-tab.

Run chromium-ci-3d HEADED under the CI's Xvfb with Mesa llvmpipe (--use-gl=angle
--use-angle=gl) — the same software-WebGL stack the Firefox CI project already relies
on — and disable the GPU watchdog / crash-limit so a slow ReadPixels completes instead
of being killed. --enable-unsafe-swiftshader kept only as a last-ditch fallback. The
serial --workers=1 pass + phase-1 worker cap stay (cheap, and they removed concurrency
as a variable).

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
34d2602132 fix(demo): POST waitlist to canonical www host to avoid preflight redirect
The demo (demo.pcbjam.com) cross-posts the waitlist to the marketing site's
serverless endpoint. It targeted the apex pcbjam.com, which 308-redirects to
www on Vercel; a CORS preflight can't follow redirects, so the OPTIONS failed
("Redirect is not allowed for a preflight request") and the POST never landed.

Point the waitlist target at the canonical www host. www.pcbjam.com already
returns 204 with access-control-allow-origin: https://demo.pcbjam.com, so the
preflight passes. Landing/version-badge link stays on the apex.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01REwWZGkAz1AZUJw8yqxkCN
2026-07-04 11:39:38 +02:00
Viktor Vaczi
e86be160a6 feat(site,demo): OG share cards + demo page meta ahead of the HN post
Demo (web/standalone): retitle "KiCad Web" -> "PCBJam - KiCad demo", add
description + OG/Twitter tags (absolute URLs pinned to demo.pcbjam.com),
ship favicons (page had none), add the og-demo.png share card, and show an
"Early-access alpha" badge next to the home-page heading.

Site: add the missing og/og-home.png (BaseLayout referenced it but the
asset never existed -> og:image 404ed in prod) and set astro `site` so
canonical/OG URLs resolve absolutely (prerendered pages shipped
canonical=http://localhost:4321/ to production).

Cards are 1200x630, generated from the favicon.svg logo mark on a pure
white background.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 09:14:44 +02:00
Istvan Matejcsok
aa78c9c5b1 fix(submodule): repoint kicad at the rebased 3d-webgl-port head
The rebase-landing of 3d-webgl-port rewrote the kicad submodule branch
(979e4d1ef7 -> eb13ff3b, same content on the occ-split base) and
force-pushed it, but the rebased root commits kept the gitlink at the
now-orphaned pre-rebase SHA — every checkout of main (CI actions/checkout
included) failed with 'upload-pack: not our ref 979e4d1ef7'.

Point the gitlink at eb13ff3b, which both kicad wasm-port and
3d-webgl-port already reference. Lesson: when a landing rebases a
submodule too, bump the root gitlinks to the rewritten SHAs and push the
submodule first (scripts/git-workflow/repo-status.sh catches this).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 16:20:08 +02:00
Istvan Matejcsok
121ab28ab6 ci(3d): 3D-renderer parity status line in the Discord screenshot report
Reads the compare-dirs report.json of the parity + webgl-self levels (when
present — inert for builds without the 3D harness) and appends one line:
' 3D renderer: parity 47/47 · webgl-self 47/47', or on drift the changed
count + worst offender with a pointer to the run-artifact triptychs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 15:46:23 +02:00
Istvan Matejcsok
4fd41a31d5 ci(3d): build the 3D WebGL harness + parity report in wasm-build
- build 3d-webgl test app after gal-webgl (same recipe: wx build + docker
  sysroot headers); the capture spec already lives in the default e2e suite
  and self-skips when the app is missing, so it now runs
- report-only parity + webgl-self step after the e2e suites
  (continue-on-error: the committed baseline-webgl set is Mac-Chromium-
  sourced — promote CI renders and flip gating if SwiftShader drifts past
  the 0.005 floor)
- exclude tests/apps/3d-webgl from the test-apps cache (rebuilt every run,
  like gal-webgl) and upload tests/3d-regression/output/** (renders, diffs,
  the 3d:review triptych gallery) with the e2e artifacts

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 15:46:23 +02:00
Istvan Matejcsok
2aa7657c64 test(3d): npm run 3d:review — triptychs/heatmaps for ALL pairs, not just changed
compare-dirs gains --artifacts changed|always: 'always' also writes the
side-by-side triptych + heatmap for pairs that PASS the floor (blue-grey
UNCHANGED caption), so a human can eyeball old-vs-new without a failing
gate. The gating scripts keep the default (empty diff dir = green);
3d:review writes the full 47-pair set to output/diff/parity-review/.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 15:46:23 +02:00
Istvan Matejcsok
d11854328a feat(3d): switch the WASM 3D viewer to the real OpenGL renderer (wasm/gl1 emulation layer)
kicad pointer bump: the __EMSCRIPTEN__ raytracer force-pins are removed, so
the viewer defaults to RENDER_3D_OPENGL running on the wasm/gl1 GL1->WebGL2
emulation layer (the raytracer stays available as the RAYTRACING engine).
Verified: tests/kicad 3d-viewer + 3d-viewer-models e2e green against the
kicad_editor build; 3d-regression parity 47/47.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 15:46:20 +02:00
Istvan Matejcsok
85656dd717 test(3d): populate baseline-webgl (47 browser goldens) + clean stale diff artifacts per run
- baseline-webgl/: first population from the passing local Chromium renders
  (47/47 under the parity floor) — activates the webgl-self regression gate
  (floor 0.005). CI may re-promote its own renders if the Linux/swiftshader
  rasterization differs beyond the floor (the README's CI-promote flow).
- compare-dirs.ts: recreate the output dir on every run — triptychs/heatmaps
  are only written for CHANGED pairs, so artifacts from earlier (redder) runs
  lingered and misreported scenarios that had since gone green (a stale
  red-state gen-cylinder triptych survived three green runs).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 15:45:58 +02:00
Istvan Matejcsok
27992f4f18 feat(3d): gl1 shim M4+M6+M7 — GLU quadrics, production link, stub retired; 47/47 parity
- gl1_glu: gluCylinder/gluDisk/gluSphere per the SGI GLU reference
  tessellation (sin/cos phase, strip emission order, pole fans — Apple's
  GLU derives from the same source, so vertices match the goldens);
  emission goes through the public glBegin/glVertex entry points so
  quadrics record into display lists
- glLineWidth>1 one-time warning (browsers clamp to 1; affected coverage
  stays under the parity floor — zero floors.json overrides needed)
- production link site: build-kicad-target.sh now compiles wasm/gl1 from
  sources.txt and applies wrapped_symbols.txt --wrap flags (same manifests
  as the test harness); kicad_editor's RENDER_3D_OPENGL links against the
  real emulation layer instead of no-ops
- wasm/stubs/gl_ffp_stub.c deleted (role fully superseded; history in git)
- docs: 3d-regression README port-status section (floor-blind small
  geometry + line-width caveats), harness comments de-red-stated

Parity: 47/47 under the 0.02 floor, zero overrides. Native golden path
byte-identical to bb0e238 (whose gate ran green) — scenarios/, native/,
baseline/, manifest, floors, spec all untouched by the port.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 15:45:58 +02:00
Istvan Matejcsok
3fe4f6fb00 feat(3d): gl1 shim M3+M5 — display-list recorder, client arrays, VBO routing; 44/47 under parity floor
- display lists: literal command replay through the shim state machine
  (state leaks + between-list material changes get GL semantics for free);
  recorded glDrawArrays EAGERLY snapshots enabled client-memory arrays at
  compile time (the renderer frees them right after glEndList) and records
  buffer+offset for VBO-backed pointers
- client-array draws: shared AttribSource path packs client-memory arrays
  into one scratch-VBO upload and binds user VBOs with original offsets
  (GL_BYTE normals / GL_UNSIGNED_BYTE colors normalized per GL1)
- glDrawElements over user VBO/IBO passes indices through untouched
  (ELEMENT_ARRAY_BUFFER never touched by the shim)
- state mutators (enable/bindTexture/blendFunc/lineWidth/alphaFunc) shared
  between the __wrap interceptors and list replay

Parity: 44/47 under the 0.02 floor (was 20). Verified visually: mini-board
Tier-3 composite (mask translucency + stencil-punched TH holes), stencil
hole subtraction, alpha-tested seg-end discs, COMBINE const-alpha model
transparency, and post-machining reproducing the upstream countersink bug
(walls absent, matching the native golden). Remaining 3: camera-* trio
needing the M4 GLU quadrics (+5 small-geometry false-greens they share).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 15:45:58 +02:00
Istvan Matejcsok
071eef5454 feat(3d): gl1 shim M0-M2 — FFP-on-WebGL2 substrate + lighting; 12 scenarios truly green
wasm/gl1: the GL1.x fixed-function emulation layer replacing the
gl_ffp_stub.c no-ops in the 3d-regression harness link. This batch:

- symbol split: 52 FFP-only entry points implemented; 10 Emscripten-owned
  names intercepted via wasm-ld --wrap (sources.txt/wrapped_symbols.txt are
  the shared manifests for both link sites; production hookup lands in M7)
- matrix stacks (MODELVIEW/PROJECTION, glGetFloatv readback), immediate
  mode with all 8 GL1 primitive conversions, GL1-default state mirror
- full GL 1.5 Gouraud lighting uber-shader (eye-space light capture at
  glLightfv time, color-material, two-side, COMBINE evaluator + alpha test
  wired but inert until M3/M5)
- draw routing: FFP traffic identified by GL_VERTEX_ARRAY client state;
  blit/2D-GAL draws pass through untouched
- display lists: correct glGenLists/glIsList existing-empty semantics;
  recorder itself is M3 (recorded commands drop with a one-time warning)

Parity: 20/47 under the 0.02 floor, of which 12 verified genuinely
rendering (bg-gradient x2, bounding-box, half-open-cylinder, segment x2,
material x3, light x3 — eyeballed against baselines); the other 8 are
small-geometry scenarios whose missing GLU/display-list content sits under
the floor (become real in M3/M4).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 15:45:58 +02:00
Istvan Matejcsok
19e850d098 fix(tests): 5s timeout on the playwright port-picker execSync
A corrupted homebrew python3 (self-referencing exec wrapper) made the
config-load-time free-port helper hang forever, stalling every playwright
run on the machine. With a timeout the existing catch falls back to the
random-port path instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 15:45:58 +02:00
Istvan Matejcsok
ce1473c9ab test(3d): screenshot-baseline TDD suite for the 3D viewer OpenGL->WebGL port — 47 native goldens + red-state WebGL harness
tests/3d-regression mirrors the gal-regression pattern at renderer scale:
shared C++ scenarios call real KiCad 3D-viewer code (opengl_utils, display
lists + DrawCulled stencil subtraction, MODEL_3D VBOs, private generators via
a rob-template accessor, and full reload()+Redraw() composites over a
synthetic BOARD_ADAPTER). A native macOS harness renders them on real OpenGL
into 47 committed goldens (bit-deterministic, FBO capture); the wasm harness
compiles the same TUs against wasm/stubs/gl_ffp_stub.c no-ops so every
scenario renders blank — the TDD red state (parity meter: 47/47 changed).
Comparisons use the CI pixelmatch engine via the new generic compare-dirs.ts
(floors.json levels; manifest.json cmp-guards registry drift).

Documents an upstream bug: appendPostMachiningGeometry's countersink path
adds middle quads without normals, silently erasing the walls of any
display list it is batched into (3d-post-machining.png keeps the lists
separate to record it).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 15:45:58 +02:00
Viktor Vaczi
1a24c2894e fix(build): stop the builder container when the build exits
An idle builder keeps the Docker Desktop VM ballooned: each per-worktree
compose project carries its own ${KICAD_DOCKER_MEM:-32G} cap, build.sh left
the container running afterwards ("interactive use"), and two forgotten
builders plus the VM's page-cache/high-water behavior read as ~56 GB of host
RAM during a ~20 GB build.

stop_builder() now runs from the EXIT trap on every path (success, failure,
Ctrl-C — both the sequential and the pipeline-mode trap). The container is
pure scaffolding — all caches live in the named volumes and `up -d` restarts
it in seconds — so stopping costs nothing. KICAD_KEEP_CONTAINER=1 restores
the old keep-running behavior for interactive debugging; docker/shell.sh is
unaffected (it does its own `up -d`).

Verified live: the full `all --build-deps` run on main left zero containers
running at exit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 14:42:38 +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
96abe5d4a5 fix(3d): return the substituted .step path from the model memo, not the ref's .wrl
ensureModelInMemfs memoized materialized refs in a Set<string> and, on a cache
hit, returned `${MODELS_3D_ROOT}/${ref}` — the ref's OWN path. But the wrl→step
fallback writes the body under the .step filename and returns that. So the first
ensure (prescan) wrote M.step and returned it, while the second ensure (the C++
viewer's PCBJAM_3D::EnsureModelFile lazy fallback) hit the early-return and got
back M.wrl — a file never written. KiCad then stat'd the missing .wrl
(S3D_CACHE::checkCache → GetModificationTime), so every component model on a
KiCad-6-vintage (all-.wrl) board failed with "Failed to retrieve file times for
'…​.wrl' (error 44)" and nothing rendered.

Fix: memoize ref → the ABSOLUTE path actually written (Set<string> →
Map<string,string>) and return that; doEnsure reuses an on-disk body via
analyzePath and records the real target. Regression test: a second ensure of a
.wrl ref returns the .step path, not the .wrl.

JS-only, no pcbnew rebuild. Only bit boards that reference .wrl (the CDN is
STEP-only from 10.x) AND double-ensure via prescan + lazy fallback — which is
why .step-ref boards worked.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 18:01:32 +02:00
Istvan Matejcsok
7020d68040 docs(wasm): Part 2 record — rebase onto main done, 3d-viewer drag deadlock resolved (7630c7e pool pre-warm), rerun green
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 17:56:02 +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
Viktor Vaczi
c005e1ba73 feat(ci): e2e failure no longer suppresses the screenshot report
An e2e failure used to skip everything downstream: a wx failure skipped the
asyncify suite (&&-chain) and the kicad suite, and any failure skipped the
screenshot check + Discord report entirely — leaving only the bare text notice
even though the rendered screenshots were already on disk.

Now the three suites run as separate steps gated on !cancelled() + "previous
stage wasn't skipped" (build failures still skip all tests), and the screenshot
report runs whenever the suites ran, posting with an --e2e pass/fail badge
computed from the step outcomes — so a wrong or MISSING screenshot (spec died
before page.screenshot() => classified "removed") is visible on Discord on red
builds too. The text-only failure notice becomes a fallback for when the rich
report didn't post (build broke before tests, or the report errored) — no
duplicate ping. A failing suite still fails the job; gating is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTxTrY9on6A8gLvogFbGiy
2026-07-02 17:30:15 +02:00
Viktor Vaczi
e01256b54d fix(ci): binaryen tools cache also needs lib/ — bin-only restore broke every post-process
The binopt cache from b4cb10b saved only binaryen-hoist-build/bin, but the
tools dynamically link lib/libbinaryen.so, so the first cache HIT (run
28585074335) failed all 7 tools' finalize with a loader error. Cache bin/ +
lib/, bump the key to binopt-v2 (Ubicloud never re-saves an exact-key hit, so
the poisoned v1 entry can't be repaired in place), and make the
BINARYEN_TRUST_PREBUILT guard exec both tools with --version instead of -x
existence checks so an incomplete restore falls through to a source build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTxTrY9on6A8gLvogFbGiy
2026-07-02 14:29:20 +02:00
Istvan Matejcsok
7630c7e521 fix: 🐛 raytrace deadlock - pre-warm 2N+8 pthread Workers 2026-07-02 13:01:02 +02:00
Viktor Vaczi
b4cb10b1c6 perf(ci): cache the per-run toolchain + test-app work (binaryen, emsdk, apps, browsers)
Four new caches in wasm-build.yml, keyed on their real inputs:

- Binaryen post-process tools (bin/ only, keyed on the submodule SHA): on a
  hit BINARYEN_TRUST_PREBUILT=1 tells build-wasm-opt.sh to skip cmake+ninja
  and trust the restored binaries (~46s/run). The guard is env-gated so local
  pass iteration (uncommitted sources, same SHA) is unaffected.
- Built wx test apps (tests/apps minus kicad/ staging + gal-webgl/): skips
  the whole build step on a hit (~3 min — the post-link hoist+asyncify over
  ~74 apps dominates). Key covers wx SHA + kicad SHA (some apps compile real
  KiCad sources: thread_pool.cpp, libcontext), binaryen SHA, tracked app
  sources, and the build/post-link scripts + shims. Gated on a wx cache HIT:
  the app build creates the libwx_*.a symlinks the GAL link needs, so on a
  wx rebuild the apps must rebuild too.
- Host emsdk (tools/emsdk, keyed on the pinned EMSCRIPTEN_VERSION): mostly
  availability insurance — a fresh install is only ~23s but pulls ~340 MB
  from github.com + storage.googleapis.com on every run. downloads/ tarballs
  are pruned before the post-job save.
- Playwright browsers (~/.cache/ms-playwright keyed on tests/package-lock).

Warm-cache runs should drop from ~12 min to ~8-8.5 min (e2e suites are the
floor). Measured baselines from runs 28577824366 / 28520845242.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XeiSRRScdaox5jBueJNcyG
2026-07-02 12:40:22 +02:00
Viktor Vaczi
0702192fed fix(ci): screenshot report is report-only again — never fail the build on drift
Per request: a screenshot difference shouldn't fail CI; the Discord post is the
signal. Revert the enforcing gate (3efcdcf): drop `--fail-on-change` + the
exit-status gating and restore `continue-on-error` on the on-success report step
(compare.ts exits 0 without the flag). Remove the now-moot fail-notice guard.
Build/e2e failures still post the "CI failed" notice; changed/added/removed still
post the captioned Discord report + perf.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 12:22:19 +02:00
Viktor Vaczi
9787efc2c9 feat(tests): caption posted screenshots (name + spec); raise drift floor to 0.5%
Bake a caption strip onto every posted screenshot composite — status + name +
the spec that produces it — for changed, added, and removed; removed now shows
the old baseline image (was a text-only line). Zero native-dep: an embedded
public-domain 8x8 bitmap font (font8x8.ts) rendered by image-ops `withBottomLabel`;
the name→spec attribution is factored out of gen-manifest into a shared
`spec-map.ts` resolver. Bottom strip, colour per status (green/red/orange).
Applies in both compare (drift gate) and changelog (git-history diff), and
post-discord now attaches the captioned removed images.

Also raise the per-engine drift floor 0.2% → 0.5% (changedRatio) to absorb the
sub-1% inter-run flakiness seen after the re-baseline, while still catching real
localized changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 12:07:41 +02:00
Gergő Törcsvári
4330c9344e
feat(3d): live models CDN — 10.0.3 publish wiring + wrl→step fallback
- kicad-packages3D is STEP-only from the 10.x tags: ensureModelInMemfs
  falls back from a missing .wrl to the same-stem .step, written and
  answered under the .step path (the returned path's extension picks
  the parsing plugin — no C++ awareness). Verified against the live
  CDN with the gallery demo board (4/4 wrl refs served as step).
- upload-models-r2.sh: rclone/S3 bulk upload of a local publish layout
  (wrangler-per-object can't move ~14k blobs); published 10.0.3 —
  105 libs / 7,238 models, 3.4GB raw → 500MB brotli — to pcbjam-cdn.
- deploy-demo.yml MODELS_TAG=10.0.3 → build-demo --models-tag →
  VITE_MODELS_MANIFEST_URL (matches LIB_TAG: model refs come from the
  footprints at that release).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014AT7gVHRktDYoQ68S4x6A4
2026-07-02 11:35:37 +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
3efcdcf91c fix(tests): enforce screenshot gate; drop shadow dir, flaky retinascale, 12 stale
Finalize the screenshot review system after the first Linux re-baseline
(that run showed 355/356 stable — the placeholder floor is fine as-is):

- ENFORCE the gate: `screenshots:check --fail-on-change` now fails the build on any
  changed/added/removed vs baselines (it still posts the drift report first, so the
  failure is actionable; a real render change → re-promote). The generic "CI failed"
  notice is suppressed for gate failures so we don't double-post on drift.
- Exclude retinascale-01-loaded: a fullPage HiDPI test whose captured height + DPR
  scaling vary run-to-run (~60% inter-run diff) — a flaky test, not render noise.
  IGNORE_SCREENSHOTS in config.ts; compare/promote/gen-manifest skip it; baseline removed.
- Kill the baseline-dir shadowing: drop e2e/baseline-screenshots/ from BASELINE_DIRS +
  delete its 3 files (grid-tab-final, wxgrid-controls, wxgrid-dedicated-page) that
  duplicated names in baseline-screenshots/ with different bytes.
- Prune 12 stale baselines (renamed/removed specs: wizard-01..04, gerbview-wizard-01..04,
  zoom-pl_editor-*, popup-03-palette).
- Delete the dead compare-screenshots.sh / update-baseline-screenshots.sh.
- Regenerate screenshot-manifest.json (355 entries).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 11:30:33 +02:00
Viktor Vaczi
348596c515 perf(build): use all cores in host builds — wx lib, test apps, gal-webgl
env.sh exports a docker-safe JOBS=1 default, and every host build silently
inherited it (the docker kicad path dodges it by passing -j explicitly):

- build-wx-wasm.sh: the intended "${JOBS:-nproc}" fallback sat BELOW the
  env.sh source, so it was dead code — full wx builds ran make -j1 on the
  Mac and on CI. Compute the all-cores default before env.sh instead
  (explicit JOBS/PARALLEL_JOBS still wins); also -j the PCRE pre-build.
- build-wasm-test.sh: same default fix, plus fan the post-link
  hoist+asyncify loop out across JOBS with xargs -P. Per-app wasm-opt
  can't feed many cores (small modules), so serial stays ~4min even with
  BINARYEN_CORES=16; fanning across the 74 independent apps is what
  scales. Safe: apply-asyncify is in-place per wasm, injector tmp is
  per-js, HOIST_WASMOPT resolved once up front.
- build-gal-webgl-test.sh: make had no -j at all.

Measured on a 16-core M4 Max, clean test-app build: 15m36s (-j1) → 2m08s,
peak RAM 10.7 GB summed across all build processes (64 GB machine; CI
runners have 120 GB). Core counts are always derived from nproc at
runtime — nothing hardcoded, CI workers differ.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XeiSRRScdaox5jBueJNcyG
2026-07-02 10:54:14 +02:00
Viktor Vaczi
616bbbf67c test(screenshots): adopt Linux CI baselines (first re-baseline)
Promote the first green CI run built with the outputDir fix (b270507, run
28572705121) — the run that keeps test-results across the sequential suites.
Overwrites the Mac-rendered baselines with CI's Linux render (churn-free,
verbatim bytes):

- 335 updated  — Mac->Linux font/AA rendering (expected, not regressions)
- 14 added     — new specs (modal, notebook, pcbnew-move, 3d-viewer, dark-mode, ...)
- 7 unchanged  — blank/loading screens identical across platforms

Renders eyeballed (pcbnew / eeschema / wx controls) — all correct UI. From here
the baselines are the Linux source of truth; the next run compares Linux-vs-Linux.

12 stale baselines (renamed specs, e.g. wizard-01 -> wizard-01-loaded) are left in
place pending confirmation — prune separately.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 10:44:40 +02:00
Viktor Vaczi
f49d536f95 fix(build): wx 'zlib.h not found' CI flake — self-heal zlib port + deterministic cache touch
Two fixes for the intermittent wx-build failure on CI (zipstrm.cpp: fatal
error: 'zlib.h' file not found, surviving the serial retry):

- build-wx-wasm.sh: --with-zlib=sys resolves to the Emscripten zlib PORT,
  which only `embuilder build zlib` installs — and that ran only in the
  configure branch. A pre-configured build dir (CI cache) + fresh emsdk
  therefore died on any recompile of a zlib-using TU. Ensure the port
  before every make; --force because embuilder stamps the port on libz.a
  alone, so a half-populated cache would no-op.

- wasm-build.yml: the cache-restore `touch {} +` stamped files ns-apart in
  readdir order; GNU make 4.x compares ns mtimes, so objects touched before
  a generated header they depend on (wx/setup.h, pcre2.h via .deps/*.d)
  recompiled — a random subset per run, which is what made the failure
  flaky. Use one shared timestamp instead: equal mtimes = up to date.

Reproduced + validated locally (remove sysroot zlib.h + one .o → exact CI
failure; with fix, self-heals in 0.5s; Linux make ns/equal-mtime behavior
verified in a container).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XeiSRRScdaox5jBueJNcyG
2026-07-02 09:59:32 +02:00
Viktor Vaczi
b270507505 fix(tests): keep test-results across CI suites; loud pipeline alert
The screenshot report went silent on green CI runs. CI runs four sequential
`playwright test` invocations (wx, asyncify, kicad, perf) that all default their
outputDir to `test-results`; Playwright clears the whole outputDir at the start
of every run, so each suite wiped the previous suite's screenshots. By the time
`compare` ran, only perf-*.json remained -> all 354 baselines read as "removed"
-> the no-render guard stayed silent.

- Gate Playwright's outputDir off test-results/ in CI only
  (process.env.CI ? 'pw-artifacts/<suite>' : 'test-results') in the wx / asyncify
  / kicad configs, so the committed-baseline screenshots (page.screenshot to
  'test-results/...') accumulate in test-results/ across the suites for compare.
  Local single-suite behavior is unchanged.
- post-discord: replace the silent no-render guard with a loud, image-less
  pipeline alert ("No screenshots produced this run") when 0 render; genuine
  partial removals still post as the normal capped REMOVED list.
- gitignore pw-artifacts/; upload tests/pw-artifacts/** as a CI artifact.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 09:20:35 +02:00
Gergő Törcsvári
9b41d7321f feat(site): link the live demo + serve the gerber demo from the versioned CDN
Refer to demo.pcbjam.com from the landing: an accent "Live demo" link in the
header nav, an "Open the live editor" hero link, a note in the gerber section,
and a "Try the live demo now" line in the final CTA.

Rework the embedded gerber viewer (public/gerber-demo/boot.js) to source the
WASM from the deploy pipeline's versioned CDN instead of the hand-synced
assets.pcbjam.com bucket: resolve gerbview's content-addressed folder at runtime
from the release manifest (manifest-latest -> tag -> manifest-<tag> -> gerbview),
load all assets (glue + wasm + images.tar.gz) from cdn.pcbjam.com, and load the
cross-origin pthread worker via a same-origin blob importScripts shim (mirrors
web/standalone/src/wasm/boot.ts). Bump the config-seed KICAD_VERSION_DIR
9.99 -> 10.0 to match the deployed build. Drop the now-obsolete committed glue
mirror and the old assets.pcbjam.com sync/r2-deploy scripts.

Collapse the header nav to the hamburger below 1025px — the added demo link no
longer fits the 1024px-capped bar on a single row.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 07:40:30 +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