The eeschema symbol-chooser footprint-selector test was marked
test.fail() on CI for a software-GL wasm "index out of bounds" trap.
That trap no longer fires (SwiftShader retired + serial-inline footprint
load fix), so the test passes on both engines and the expected-fail
marker was flipping the run red ("expected to fail, but passed").
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The three page.waitForTimeout calls in eeschema-assign-footprints.spec.ts
are legitimate interaction dwells (two poll intervals that pump the wx
event loop inside asserting loops, one first-paint settle before a
screenshot), but were unannotated, so the determinism guard failed CI.
Mark each with a `dwell:` note per tests/TESTING.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
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
Quit used to mimic the Back button (history.back() whenever a referrer
existed), but every in-app entry and every tool switch is a hard
location.assign that pushes a history entry — so after schematic ⇄ pcb
switches, one step back is another editor, not the page the user left from.
Quit now navigates to the project overview explicitly (projectPath; "/" for
lib editors), which also covers deep links uniformly.
New VITE_QUIT_ORIGIN ("" ⇒ same-origin) lets the backed editor deployment
exit to the management app's project page instead of the standalone's own;
build-editor.mjs sets it (override with --app-origin).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Tools → "Switch to PCB Editor" is a hard location.assign that stacks a second
editor history entry, so a Back-style quit lands in the previous editor
instead of leaving for the project overview. Red until the quit hook stops
unwinding history.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- 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
One circular FAB (peer-count badge) replaces the top-right overlay row:
draggable anywhere (pointer capture, 4px click-vs-drag threshold — the
comment-pin pattern), position persisted in localStorage, panel opens
toward screen center and stacks composable sections — roster, source chip,
view-only pill, follow row, comments, chrome toggle. Renders at z-50 above
everything (decided: covers wx dialogs; trivially dismissed via click-away/
Esc/drag). CommentLayer portals its bar + list panel into the menu's
comments slot; pins/popovers/composer/click-catcher stay canvas-anchored.
The FAB is the chrome-hidden survivor. e2e specs updated with an
openOverlayMenu helper (panel is closed by default, click-away closes it).
NOTE: Playwright suites updated but not executed here (no local WASM
artifacts) — verify on CI / next session.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A42xfPFNdfsUt9eowkC9eM
createPresence accepts a lazy seedColors map (the doc's comment authors):
claims prefer your own comment slot, lowestFreeColor counts authors' slots
as used, colorOf answers offline authors from the seed before the hash.
Comments' own fallback chain gains the seed for presence-less binds.
WasmTool wires the bound doc into both presence paths (pcbnew + eeschema
per-sheet rebinds). Bumps pcbjam-shared (commentAuthorColors). +4 tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A42xfPFNdfsUt9eowkC9eM
presenceUser()/userSlug() now resolve the authenticated user: a plain
/api/me fetch (GPL no-link rule — no closed contract import) races the WASM
boot and is awaited before presence/comments bind. `?user=`/`?libowner=`
overrides are gated behind VITE_ALLOW_USER_OVERRIDE=1 (dev script + e2e
harnesses set it; prod builds never do). Anonymous/example-backend sessions
keep the pre-auth slug fallback. Fixes prod's empty presence (everyone was
"local-user", deduped as own tabs) and comment authorship in one move.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A42xfPFNdfsUt9eowkC9eM
VITE_APP_URL (build-editor --app-base, set to app.pcbjam.com in release CI)
turns on a redirect gate in App: home, project overview and mgmt-only paths
bounce to the mgmt app preserving path+search; tool boots, file deep-links,
lib editors and the @local pseudo-scope stay local. Unset (dev/demo) keeps
today's behavior. Policy is a pure helper (lib/redirect.ts) + vitest table.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A42xfPFNdfsUt9eowkC9eM
Tools → "Switch to PCB Editor" in a project with no .kicad_pcb (e.g. created
from a lone schematic) did nothing: the nav hook found no file for the target
tool and returned false. Now a session that can persist (ToolPage passes the
new createFile prop) writes the templated counterpart via
createProjectFileIfMissing — no download fallback, and it re-checks existence
so a collaborator's file is never clobbered — then navigates to it, matching
native KiCad (pcbnew opens a new board at the derived path). Sessions that
can't persist (read-only viewers, scratch/local-folder) keep the logged no-op.
Also latch the quit dispatcher off before every deliberate tool-switch
navigation (markDeliberateNavigation): the wx port's UnloadCallback runs on
BEFOREUNLOAD and closes the top frame the moment the navigation starts, so
the quit hook history.back()'d over the in-flight navigation — the pagehide
latch is too late (it only fires at commit time).
e2e: new tests/web/tool-switch-missing-file.spec.ts reproduces the flow via a
browser-local (IDB) project created from the home page; playwright-web config
gains VITE_LOCAL_PROJECTS=idb and derives STANDALONE_PORT/CORS_ORIGIN from
WEB_APP_URL (runs the suite past a squatted :3048); both vars declared in
web/turbo.json globalEnv (turbo strict-env strips undeclared vars). Includes
the previously-uncommitted tool-switch spec repairs (URL grammar + z-30
boot-overlay wait).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The wasm-opt bench VM cloud-init seed carried a committed console/SSH password
and enabled SSH password auth. setup-vm.sh already requires and injects an SSH
pubkey, so lock the password (lock_passwd: true) and disable SSH password auth
(ssh_pwauth: false).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- gerber-demo/boot.js honors the base/cdn/tag script-source overrides only on a
loopback hostname, so a shipped page ignores them.
- /api/waitlist gains a best-effort per-IP/per-address rate limit and masks the
submitter address in the no-key log line.
Adds vitest to the site: test/boot-overrides.test.ts (happy-dom) and
test/waitlist.test.ts.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The @pcbjam/backend-example server had a 1 GiB body limit, no per-owner/per-lib
quotas, bound to 0.0.0.0, and reflected any origin with credentials when
CORS_ORIGIN is *. Bound the write surface (5 MiB body cap, per-owner lib and
per-lib item quotas), bind 127.0.0.1 by default (opt in via HOST), and force
credentials off for a wildcard CORS origin. Refactor main() into an exported
buildApp() and add web/backend/test/security.test.ts (inject-based).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
All pcbjam properties now load the same new-format Plausible script
(https://plausible.io/js/pa-KjNS9YmidydULZTstsjRg.js — one dashboard,
segment by hostname) instead of per-domain data-domain sites:
- site: replace Vercel Analytics with the pa- snippet in BaseLayout
(is:inline, prod-only, crossorigin="anonymous" for the COEP
require-corp routes in vercel.json); drop @vercel/analytics dep.
Legal pages (cookies/privacy) rewritten Vercel -> Plausible.
- standalone: initAnalytics() rewritten for the new script format
(queue shim + plausible.init(), async, no data-domain). The gate is
now VITE_PLAUSIBLE_SRC (script URL, deploy-time opt-in) — unset means
no tracking, so dev checkouts and third-party GPL builds stay clean.
VITE_PLAUSIBLE_DOMAIN is gone.
- deploy: build-demo/build-editor --plausible now takes the script URL;
release.yml/deploy-demo.yml hardcode the (public) pa- URL, so the
repo Actions variable PLAUSIBLE_DOMAIN is no longer used.
Verified: standalone tsc + vite build (pa- URL present with env, absent
without, crossOrigin kept); astro build has the snippet on all layout
pages and no vercel/insights references; astro dev stays untracked.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
docker/build.sh "all" now includes kicad_tools (finalizes in-container, so
it never contends with the editor's wasm-opt critical path); .ci-cache-epoch
bumped — the cached FINAL output set changes. wasm-build.yml gains the
corpus-lint + CLI-contract gate step (run_tests leg): with the artifact now
built in CI, the skip-when-unbuilt scripts bite. Release runs
(upload_output) start shipping kicad_tools in the wasm-output artifact —
the closed repo's runner-image workflow consumes it from there.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HLua64PCVwkQ1hpWdaf1Gm
T4: kicad-binding.test.ts — revertNonce → DOC_REVERTED_EVENT dispatch via a
window stub (once per nonce; stale marker on open silent; observer gone
after destroy). T6: tests/tools/cli-contract.ts (npm run tools:contract,
skip-when-unbuilt): resave version bump + relint clean for board/schematic/
hierarchy/footprint (the CTL_FOR_LIBRARY version-header regression), exit
codes 2/4/1 — the exact contract run-tools-job.ts keys off. 15 checks
green. Also bumps pcbjam-shared (T3 unit tests).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HLua64PCVwkQ1hpWdaf1Gm
New --resave <file> <outdir> subcommand (root docs/features/kicad-validity/
0001): .kicad_sch one file per sheet mirroring the hierarchy layout,
.kicad_pcb via pcbToolsResaveBoard callback (mirror of the lint callback),
.kicad_sym/.lib via ConvertLibrary. Exit 0 ok / 2 usage / 4 input invalid /
5 write failed — only 4 marks the input invalid for the upload gate.
The s-expr schematic save stub is now #ifndef KICAD_TOOLS_COMBINED: the
merged image links the real writer (+65 KB), standalone sym_convert keeps
its diet. Board save needed no un-stubbing (pcb_io/ survives the CMake
prune).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HLua64PCVwkQ1hpWdaf1Gm
The quit hook only history.back()ed on a SAME-ORIGIN referrer, so the
primary closed-deploy entry — app.pcbjam.com deep-linking into
editor.pcbjam.com (cross-origin) — fell through to the editor's own
project view instead of returning to the app. Quit is Back-button
semantics (3a2255c): any non-empty referrer with in-tab history now
goes back; deep links / fresh tabs (no referrer) keep the project-page
fallback. Verified live against the closed stack: :3047 → editor →
quit lands back on :3047; a direct editor URL still falls back to the
editor's project overview.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The closed backend's storage rework scopes collab rooms/keys by team:
room ids are now <scopeId>:<projectId>:<docPath> (@pcbjam/shared bump).
The editor threads a scopeId prop parallel to projectId — ToolPage takes
it from the project DTO (falling back to "local" for scope-less
backends; Home/LibTool pages pass "local" explicitly) — through WasmTool
into the doc session, sheet-manager warm rooms, and cross-app presence.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
--gerbers <pcb> [outdir]: one .gbr per enabled layer (stackup plot order,
kicad-cli defaults via default JOB_EXPORT_PCB_GERBERS + PlotJobToPlotOpts)
plus the .gbrjob file — mirrors JobExportGerbers minus the zone re-check.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADzCSeN3Q9DJL3YW2FZTXB
720cff5 added occ-export-models.spec.ts (boots the ~190MB merged pcbnew
module, prefetches 17 model bodies, runs a 13MB OCC STEP export in the
occ_service worker — twice, serial) but never listed it in BIG_MODULE_SPECS,
so on CI it ran on the firefox project: headed under Xvfb with the
baseline-only WASM JIT and llvmpipe. That CPU storm ran concurrently with
the chromium-ci pool and starved the SwiftShader 3D-viewer opens past their
180s cap — 3d-viewer.spec:299 (edge-resize) timed out at openThreeDViewer in
runs 29076702564 (main) and 29090433643 (PR), and 29076702564 also dropped
occ-export.spec:105's menu click. In both red runs the last console line
before the 180s silence is the misrouted spec's export.
Add it to BIG_MODULE_SPECS like every other pcbnew.html-booting spec: it now
runs on chromium-ci (V8, headless SwiftShader) and firefox ignores it
(verified with CI=1 playwright --list).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VYxyBRJnKKExFcA5CRPXjM
Commenter role in the viewers/commenters FAQ (reader = view, commenter =
+add comments, editor = +edit); real-time collab listed on Free only, Pro/
Enterprise inherit via 'Everything from ...'; Team states seats are Pro
seats scoped to that team's projects only, plus a matching FAQ entry.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018RQcUBzy8mpYXkup9eNY1z
Free/Pro live, Team/Enterprise as coming-soon cards; monthly/yearly price
toggle; CTAs open a coming-soon dialog pointing at the waitlist; Pricing
link in header nav + footer; header chip nowrap + earlier hamburger
breakpoint; CheckItem gains an optional dimmed note suffix.
Copy source of truth: docs/features/pricing/copy.md (root repo).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018RQcUBzy8mpYXkup9eNY1z
Refuse to bind docs written by an unsupported s-expr encoding (update
required); surfaced via WasmTool's fatal status path. Guard unit tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0142yAmMGZ3Ejrq3wwVr2Yq2
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
synced-source subscribes to its SyncStack: remote changes (self-save echoes
consumed via a selfPushed flag) debounce per kind into kicadLibsReload — a
new embind export (pcbjam_libs_reload.h, all three TUs) that drops the lib's
plugin cache (LIBRARY_MANAGER::ReloadLibraryEntry), reloads it, and mails
MAIL_RELOAD_LIB with the nickname so the symbol tree force-refreshes (the
plugin's modify hash is a pinned constant, so a plain sync would skip it).
After the reload, kicadLibsSymbolUsage (new eeschema embind: placed
SCH_SYMBOL count across unique screens) gates LIB_ITEM_UPDATED_EVENT, and
WasmTool shows an amber toast when a PLACED symbol changed — placed copies
keep the previous version until updated from the library.
syncedScopeLibsSource gives PROJECT sessions the synced source under
VITE_LIBS_SOURCE=synced (remote contract for lib listing/createLib, lazy
per-lib SyncStacks for item ops/presync) so realtime reaches open
schematics; previously project sessions silently fell back to the per-item
remote source. Unit tests cover reload debounce, self-echo skip, per-kind
routing, usage-gated event, and the no-Module no-op.
Bumps kicad (MAIL_RELOAD_LIB force-refresh payload).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013QRWoXiM9uuo1enXGhAYku