Commit graph

202 commits

Author SHA1 Message Date
Viktor Vaczi
7629ab978d test(e2e): fix wx suite — header deps, port pinning, drop legacy-GL tests, wx+pthread worker fix
Suite went 64 failed → 291 passed / 0 failed:
- Makefile.wasm: compile with -MMD -MP and include .d files — stale objects
  relinked against a newer wx lib crashed apps at startup with "function
  signature mismatch" after any header/vtable change
- playwright(.coroutine).config.ts: pin the server port for the whole run
  (resolvePort from playwright-kicad.config.ts); the 60s freshness window made
  workers restarted after a failure rotate to a dead port (ERR_CONNECTION_REFUSED
  cascade across ~50 tests)
- drop legacy-GL testing: KiCad renders via WebGL GAL (gal-webgl.spec.ts);
  remove minimal_test's OpenGL tab/GLTestCanvas, opengl.spec.ts, GL z-order
  describes, -sLEGACY_GL_EMULATION + gl_immediate_shim.js from the test build,
  and the orphaned GL baselines
- coroutine-pthread repro apps join the `all` target (make clean used to delete
  them while all never rebuilt them); clean no longer eats checked-in JS like
  worker_dom_stub.js
- bump wxwidgets: guard module-eval document access so wx+pthread apps
  (threadpool tests) survive Web Worker eval

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 13:40:00 +02:00
Viktor Vaczi
107fc7513f feat(site): publish Terms of Service at /terms
- replace the placeholder terms page with the real ToS (EU/Hungarian
  consumer law, Paddle Merchant-of-Record billing, GPLv3 corresponding-
  source offer), dated 10 June 2026
- serve legal pages from a markdown content collection rendered by
  src/pages/[legal].astro; drop the per-page placeholder .astro files
  (privacy/cookies keep placeholder content, now as markdown)
- add prose/table styles for legal pages
- footer link text: "Terms & Conditions" -> "Terms of Service"

Canonical working copy + clause-by-clause legal sourcing live in
features/ (untracked research).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 12:52:05 +02:00
Viktor Vaczi
ba3ba37c4d fix: use HTTPS submodule URLs so the repo clones anonymously
The Terms of Service's GPLv3 corresponding-source offer points at this
repo; with SSH submodule URLs, `git clone --recursive` fails for anyone
without a GitHub account. HTTPS URLs work for everyone (pushes still go
over SSH via insteadOf, if configured).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 12:51:55 +02:00
Gergő Törcsvári
eababd7ef9
chore: bump pcbjam-shared pointer (gitignore housekeeping)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 16:52:15 +02:00
Gergő Törcsvári
b6a77ee93c
refactor(web): split into GPL standalone editor + thin reference backend
Reduce web/ to a single generic editor app plus a minimal example backend,
moving all project-specific/app code out to the closed root repo:

- apps/frontend -> standalone (@pcbjam/standalone): the GPL editor. Strips the
  project-management UI (ProjectsPage/ProjectDetailPage/UploadDropzone and the
  write half of lib/api.ts). WasmTool now takes fetchBytes + assetBaseUrl as
  injected props (decoupled from the API client) so it can be driven by either
  a backend or a local folder. New HomePage (local-folder loader + backend
  project list) and read-only ProjectView. BroadcastChannel collab unchanged.
- backend (@pcbjam/backend-example): thin Fastify+ts-rest reference impl of the
  @pcbjam/shared contract — serves a single project off the local filesystem
  (PROJECT_DIR), no DB/auth/uploads.
- packages/contract -> web/pcbjam-shared (git submodule, MIT @pcbjam/shared).
- Remove apps/server, packages/storage, packages/contract, docker-compose
  (server + storage move to the closed root repo; preserved via subtree
  branches). Rewrite pnpm-workspace/turbo/.env for the frontend-only layout.
- Add tests/fixtures/demo so the editor + example backend run out of the box.

Standalone typechecks + builds; backend serves the contract end-to-end.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 12:35:46 +02:00
Gergő Törcsvári
b672c54189
fix(pcbnew): collab syncs real item layer — devirtualize Get/SetLayer; collab on by default 2026-06-09 06:04:00 +02:00
Gergő Törcsvári
1bfa562a61
test(pcbnew): assert collab add preserves each item's layer
Adds a layer-preservation check to the footprint/via/zone/text round-trip add tests,
documenting + verifying that an item's own serialized layer is applied by makeItem and
the peer editor's active layer is never consulted. 4 add tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 06:03:59 +02:00
Gergő Törcsvári
2ee87889fa
feat(pcbnew): collab adds board text natively (PCB_TEXT) incl. justification
A board-level graphic text (Place→Text) lands in Drawings() and would otherwise hit the
same asyncify-fragile (kicad_pcb …) envelope-blob wall as via/zone on add. So PCB_TEXT
reconstructs NATIVELY: itemToJson emits size/thickness/angle plus horizontal & vertical
justification, mirror, and bold/italic (the text string was already emitted for any
EDA_TEXT); makeItem builds a fresh PCB_TEXT and restores all of them. flushDiff skips the
blob for PCB_TEXT_T. Footprint child text is unaffected — it syncs by move, and its add
is carried by the footprint blob.

Justification matters: it anchors the glyphs relative to the text POSITION, so without it
a left-justified text reconstructed centered on the peer and rendered visibly offset even
though GetPosition() matched (which is why the anchor-only headless check missed it).

Verified two-tab in the real app: a left/bottom-justified bold text added in tab A
reconstructs in tab B at the exact position with matching justification + string. Headless:
a board-text round-trip add test (sample text is left/bottom-justified) asserts the
position AND that hjust/vjust/text round-trip. 7 passed, 1 skipped, 0 aborts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 06:03:59 +02:00
Gergő Törcsvári
9dbfddc527
feat(pcbnew): collab adds footprints (s-expr blob) + vias/zones (native)
Footprint add reconstructs from the bare `(footprint …)` s-expr clipboard blob via
CLIPBOARD_IO (SetWriter/SetReader redirect it to a string for headless/wasm). The
non-footprint `(kicad_pcb …)` envelope parse (parseBOARD) is asyncify-fragile in wasm
— commit.Add of an envelope-parsed item silently stages nothing and subsequent virtual
dispatch traps ("index out of bounds"), the same wall that deferred the eeschema symbol
blob — so vias and zones reconstruct NATIVELY instead: itemToJson emits drill/layer-pair
for a PCB_VIA and the outline polygon for a ZONE, and makeItem builds a fresh PCB_VIA /
ZONE. flushDiff attaches the blob only to types that need it (footprints/board graphics);
tracks/vias/zones skip it. Includes the dangling-parent fix for the envelope path
(SetParent before delete) which footprints don't hit.

Build: kicad_clipboard.h pulls in the generated pcb_lexer.h (emitted into the common
build subdir by make_lexer) — added -I${KICAD_BUILD}/common to the embind include path.

Tests: sample board gains a via + zone; snapshot asserts their native fields are emitted,
and three round-trip add tests (footprint via blob, via/zone native) delete then re-add
each by uuid and confirm it returns at the same position. 6 passed, 1 skipped, 0 aborts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 06:03:59 +02:00
Gergő Törcsvári
f60abaa349
fix(pcbnew): collab syncs footprint silkscreen text; un-no-op test-move helper
Bug 1 — footprint child text now syncs. forEachTopItem also visits each footprint's
TEXT children by their own uuid (GetFields() PCB_FIELDs + GraphicalItems() PCB_TEXT),
so moving a silkscreen reference/value/user text — which leaves the footprint origin
unchanged — produces a diff. Apply needed only a guard: the removed loop skips items
with a parent footprint (a footprint delete cascades to its children, so don't
double-remove). ResolveItem already resolves child uuids and BOARD_COMMIT::undoLevelItem
rolls a child Modify up to the footprint, so Modify+SetPosition (in the existing
COROUTINE) just works. itemToJson now carries text for EDA_TEXT items.

Bug 2 — kicadCollabTestMoveFirst no longer no-ops. Its CallAfter now runs the move
inside a COROUTINE fiber (like doApply) so the virtual BOARD_ITEM::Move dispatches
instead of asyncify-no-opping.

Tests: sample board gains a footprint with a Reference field + user fp_text; snapshot
test asserts the footprint and both text children are emitted by uuid; a new test
moves each child by uuid and asserts the footprint origin stays put. 3 pass, 1 skipped.
Verified two-tab in the real app: C1's reference text move syncs A->B, no footprint moves.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 06:03:59 +02:00
Gergő Törcsvári
2dc55c6138
feat(pcbnew): Yjs collab bridge — snapshot/diff emit + BOARD_COMMIT apply (move/delete/track-add), verified two-tab in real app
pcbnew's half of the unified Yjs collab bridge (4th tool; yjs-bridge commit 4),
a near-verbatim port of the eeschema design. Root-repo only — kicad/wxwidgets
submodules untouched.

- wasm/bindings/pcbnew_embind.cpp: BOARD_LISTENER trigger + post-settle snapshot
  diff emit; BOARD_COMMIT apply inside a CallAfter + COROUTINE fiber (so a new
  item's GAL view->Add dispatches correctly). Move/delete sync for any top-level
  item by uuid; native PCB_TRACK add. Footprint/via/zone add deferred.
- WasmTool.tsx: add pcbnew to COLLAB_TOOLS.
- tests/apps/kicad/pcbnew-collab.html: seeded (wizard-free) harness, leaving
  pcbnew.html untouched for its wizard test.
- tests/kicad/pcbnew-collab.spec.ts: snapshot + apply(move/remove/add) — 2 pass,
  two-tab skipped headless.

Verified two-tab in the real web app: footprint move applies + syncs A->B.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 06:03:59 +02:00
Viktor Vaczi
bb6dbfc76e feat(site): add Vercel Analytics
Install @vercel/analytics and render <Analytics /> from BaseLayout so it loads on every page. Auto-activates in production on Vercel (no-op locally).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 14:39:01 +02:00
Viktor Vaczi
c8c09cc3d7 fix(site): close mobile menu when a nav item is tapped
The CSS-only hamburger (checkbox toggle) only reset on astro:page-load, which fires for real navigations (Blog) but not for same-page anchor links (How it works / Open & yours / Join the waitlist) that scroll without navigating — so the menu stayed open after tapping them. Add a one-time delegated click handler on the nav that unchecks the toggle on any link tap. Items stay identical to desktop; scrolling unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 14:32:57 +02:00
Viktor Vaczi
37a1df2315 fix(site): wire Resend waitlist via astro:env, fix TS errors + deprecations
- Move secrets to Astro's typed astro:env/server (schema in astro.config.mjs);
  removes process.env usage and the 3 TS2580 "Cannot find name 'process'" errors
  without adding @types/node.
- Replace deprecated contacts.create({ audienceId }) with segments: [{ id }]
  (env var RESEND_AUDIENCE_ID -> RESEND_SEGMENT_ID).
- Check the { error } return on both Resend calls: contact add is best-effort
  (non-fatal); a failed confirmation send now returns 502 instead of a false 200.
- Type the waitlist fetch result in WaitlistForm.astro.
- Add @astrojs/check + typescript devDeps and an `npm run check` script.

Verified: npm run check (0 errors), npm run build, secret not inlined into the
build output, and a runtime smoke test of the validation/honeypot/GET paths.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 14:26:54 +02:00
Balint Ipkovich
98155496ba fix(view): use zoom-to-cursor in WASM canvas (browser can't warp pointer for center-on-zoom)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 10:37:08 +02:00
Viktor Vaczi
4951174487 fix(site): waitlist sender → hello@pcbjam.com
Use PCBJam's actual address (hello@pcbjam.com) as the waitlist confirmation
sender, in the API route default and .env.example.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 18:29:59 +02:00
Viktor Vaczi
34ae85b2b5 fix(site): mobile waitlist input height, "Demo coming soon" panel, blog cleanup
- WaitlistForm: reset the input's flex when the form stacks — the 16rem
  flex-basis was applying to the height in column layout (256px-tall input).
- DemoVideo: add a comingSoon mode that shows a big "Demo coming soon" panel
  instead of the internal PRODUCE placeholder; enable it in the hero and the
  multiplayer section.
- Blog: retitle the post to "Porting KiCad's Graphics to WebGL with Claude"
  (clean slug), set body/description to "Coming soon.", and drop the
  placeholder text from the blog index.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 18:15:02 +02:00
Viktor Vaczi
80042a0672 fix(site): remove AI icon from the AI section
Drop the ai.svg glyph (and its import/flex styling) from the "AI when you want
it" section per founder review.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 17:44:33 +02:00
Viktor Vaczi
735d8a48ee feat(site): 🫙 PCBJam landing revisions — branding, accuracy, positioning graphic
Founder review pass on the landing page, plus commit the landing-page
research/spec docs under features/lp.

Branding & assets:
- New PCBJam mark: convert the jam-jar image to favicon.svg (trimmed, rounded
  tile) and use it in the browser tab, header, and footer.
- Footer "Built by": flower mark + "Emergence Engineering" in white Oswald (the
  blog's title font, self-hosted via @fontsource) so it's legible on dark.
- Produce the positioning quadrant graphic (public/graphics) and drop the placeholder.

Copy / accuracy:
- Remove header GitHub link; point "Built by EE" + footer "About" to
  emergence-engineering.com; Built-by CTA -> mailto:contact@emergence-engineering.com.
- Replace the wrong testimonial with Greg Detre's real quote from EE's site.
- Drop blog icons from Multiplayer/Pillars/How-it-works; add "Coming soon" to
  live-cursors & pin-comment; drop the hard-coded Yjs mention.
- Rework monetization to free+local / paid cloud-sync; remove "no metered AI",
  "no per-seat toll", and the Flux price claim; soften the AI section.
- Reframe waitlist copy (incl. confirmation email) to newsletter + early access.

Fixes:
- Offset only #waitlist for the sticky header (scroll-margin-top); section
  anchors keep their original behavior.

Docs:
- Commit features/lp (copy spec, brand guide, competitive research) via force-add
  over the /features/ gitignore.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 17:14:19 +02:00
Viktor Vaczi
7f9dbdd068 feat(site): 🛬 PCBJam landing page — waitlist, sections, Tailwind
Replace the placeholder homepage with the PCBJam waitlist conversion page
(13 sections from the copy spec), shared chrome, and a real email-capture
backend, keeping the site zero-runtime-JS / zero-web-font.

- Tailwind v4 via @tailwindcss/postcss (the Vite plugin breaks on Astro 6
  rolldown-vite); Preflight skipped; brand tokens in :root + @theme inline.
- Waitlist: /api/waitlist serverless route (prerender=false) + Resend, with
  dual JSON/303 responses; WaitlistForm progressive enhancement bound on
  astro:page-load (survives ClientRouter swaps); honeypot + validation.
- New components (Icon, Placeholder, DemoVideo, SectionBand, Chip, EECredit,
  StatBar, Pillar, Step, CheckItem, WaitlistForm) + 13 section components.
- Redesigned Header (sticky, mobile menu, CTA) + 4-col Footer (KiCad
  attribution); BaseLayout gains full-bleed mode + OG/canonical meta.
- Assets scaffolded with marked placeholders; EE/generic/client SVGs copied.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 14:59:13 +02:00
Viktor Vaczi
d55a8a0afa feat(site): 🚀 add Astro marketing/content site
Standalone Astro 6 site in /site (decoupled from the /web app monorepo):
landing page, /blog with one post (Content Layer), and terms/privacy/cookie
legal pages. Pure Astro, server-rendered to static HTML.

- Static by default via @astrojs/vercel adapter; any route can opt into SSR
  with `export const prerender = false` (deploys as a Vercel Function).
- SPA-style navigation with <ClientRouter />, transition:persist on the header
  (no icon flash), and viewport prefetch — ~6 KB gzip JS, no React.
- Deploy on Vercel by setting Root Directory to 'site' (no vercel.json needed).

See site/README.md for dev and deploy instructions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 08:57:37 +02:00
Istvan Matejcsok
d677ffca51 ci: 🎡 add ci 2026-06-05 14:12:09 +02:00
Gergő Törcsvári
9f8628cfb2
fix(eeschema): collab converges on big drags — emit a post-settle snapshot diff
The batched-emit fix still lost segments on a large connected drag (peer
dropped the P3-C1 wire). Deeper cause: the SCHEMATIC_LISTENER fires in
pushSchEdit BEFORE RecalculateConnections (sch_commit.cpp ~402 vs ~430), so
every emit was pre-cleanup RAW geometry; the cleanup that follows (merge
collinear wires, drop/split junctions) was never broadcast. The peer rebuilt
the raw edit and ran its own cleanup over a different dirty scope, so the two
peers cleaned up differently and the peer lost segments.

Replace the listener-list emit with a post-settle full-model snapshot DIFF
(snapshotByUuid), flushed via CallAfter once Push (cleanup included) returns —
capturing tab A's final, already-clean geometry. The peer applies that and
re-cleaning already-clean geometry is idempotent, so they converge. The native
listener is now just a change trigger. g_baseline holds the last-broadcast
state; doApply and kicadCollabSnapshot rebaseline so applied/seed items aren't
re-broadcast (echo). Mirrors pl_editor's snapshot-differ; no kicad-fork change.

Verified two-tab, rigorously (real edit: tabA state changed AND tabA===tabB
byte-for-byte): a wire reroute plus U1A/U1B/C2 symbol drags all converge
exactly. eeschema-collab + eeschema-ui suites green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:59:21 +02:00
Gergő Törcsvári
f2a6deaf7b
fix(eeschema): collab drag no longer drops segments — batch one edit into one delta
A single SCH_COMMIT::Push fires OnItemsAdded/Removed/Changed separately and
synchronously, then RecalculateConnections once. COLLAB_LISTENER emitted each
category as its own delta, so the peer applied one atomic edit as three separate
commits, each with its own connectivity recompute. On a large connected drag the
junction at the wires' new crossing (added) was applied before the wires moved
(changed) -> dangling junction -> the peer's cleanup deleted it (lost segments).

COLLAB_LISTENER now buffers added/changed/removed (serialized in each synchronous
callback) and flushes one combined delta after Push returns, coalesced via
CallAfter. doApply applies it atomically removed->changed->added in a single
commit with one recompute, so the junction survives. Verified two-tab: a G-drag
of U1A that previously left the peer at 74 items / 7 junctions now emits one
delta {a:1,c:4,r:1} and both tabs converge at 75 / 8.

Root-repo only; kicad and wxwidgets untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:56:43 +02:00
Gergő Törcsvári
b577c05fae
fix(eeschema): wasm dialog positioning (web .window CSS) + collab SCH_SHAPE add via coroutine fiber stack
Thrust A — dialogs render top-left with OK clipped in the WEB app (not the
test harness): root cause was the React shell missing the .window /
.window-canvas CSS that wx.js relies on (it positions each dialog div via
inline left/top, which need position:absolute). Added the rules to
web/apps/frontend/src/index.css. Native draw-text now works end-to-end;
symbol/power choosers render (placing still blocked by absent libraries).

Thrust B — collab apply of a newly-added SCH_SHAPE trapped in KiCad core
(SCH_COMMIT::Push CHT_ADD -> GAL view->Add, an asyncify invoke_* mis-dispatch)
because doApply ran off a fiber stack. doApply now runs inside a COROUTINE so
it executes on a libcontext fiber, the same context native draws use; the add
dispatches correctly. Re-enabled the SCH_SHAPE converter (rect/circle). Added
thirdparty/libcontext to the embind include path (tool/coroutine.h needs it).
Verified two-tab: rectangle + circle drawn in tab A sync + render in tab B.
Extended eeschema-collab.spec.ts apply test with a SCH_SHAPE add.

All changes root-repo only; kicad and wxwidgets forks untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:56:43 +02:00
Gergő Törcsvári
6bd17aeb26
fix(eeschema): collab move carries the symbol/label text fields along
After the body-move devirtualization, moving a symbol synced the body to the peer but
left its reference/value text behind: SCH_SYMBOL::Move()/SCH_LABEL_BASE::Move() move the
child fields via an inner virtual field.Move() that also mis-dispatches in the apply
context. Move the fields explicitly with a devirtualized SCH_FIELD::Move (moveFields).
Verified: a moved symbol's text label now follows the body on the peer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:56:43 +02:00
Gergő Törcsvári
2d1d7c4681
fix(eeschema): collab sync of symbol/junction/label moves; bump wxwidgets (toolbar focus)
The changed-path used the virtual SCH_ITEM::Move(), which silently no-ops from the
apply/CallAfter context (asyncify call_indirect mis-dispatch) for every non-wire item —
so moving a symbol synced on the sender but not the peer. Devirtualize Move() with an
explicit class-qualified call (moveItemTo), which is statically bound (a plain call, not
call_indirect) and executes. Verified: a symbol move now propagates. Also bumps wxwidgets
to ea599f7 (toolbar clicks no longer steal canvas keyboard focus).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:56:42 +02:00
Gergő Törcsvári
16bbc52240
fix(eeschema-wasm): quasi-modal dialogs no longer freeze; Backspace deletes selection
Bump kicad + wxwidgets submodules with two wasm eeschema UI fixes, and add the
regression test tests/kicad/eeschema-ui.spec.ts (Delete + Backspace delete; the
text-tool properties dialog opens and closes without freezing).

- wxwidgets c27fe8b: nested (quasi-modal) event loops pump via Asyncify instead of
  re-entering emscripten_set_main_loop (which threw an un-resumable 'unwind').
- kicad 4132395: bind Backspace to delete under emscripten + apply default alt hotkeys.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:56:42 +02:00
Gergő Törcsvári
04b011d032
feat(eeschema): collab SCH_SHAPE emit + defensive text; defer shape/symbol add (asyncify trap); un-skip apply e2e
- itemToJson hand-maps SCH_SHAPE geometry (SHAPE_T, start/end, arc center,
  bezier ctrl pts, stroke width, fill) for emit + the changed/move path.
- makeItem: parent + default-size SCH_TEXT (mirrors createNewText).
- SCH_SHAPE/SCH_SYMBOL `added` reconstruction deferred: committing a new
  shape/symbol traps in SCH_COMMIT::Push from the programmatic apply context
  (asyncify invoke_* mis-dispatch, not the boundary 'signature mismatch' the
  dyncall shim catches). makeItem returns nullptr -> graceful 'no converter'.
- Un-skipped + extended the headless apply e2e (move via sx/sy line form,
  delete, text-add); apply works headless (old 'Push no-ops' belief was stale).
- features/yjs-bridge/0006: full findings (bugs 1+2 don't reproduce, etc).

No kicad-fork change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:56:42 +02:00
Gergő Törcsvári
5764d7ec24
docs(yjs-bridge): next-session init prompt for eeschema apply bugfixes
Captures milestone-3 state + the known in-app bugs (text-add freeze, deletes not
applying, SCH_SHAPE no sync, partial wire-move convergence) + the build/test/verify
workflow, so a fresh session can pick up the eeschema collab apply work.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:56:42 +02:00
Gergő Törcsvári
890e7b317c
feat(eeschema): collab apply converters for text, labels, no-connect
Extend doApply added-item construction beyond wires: SCH_TEXT, SCH_LABEL /
SCH_GLOBALLABEL / SCH_HIERLABEL (position + text + label shape), and SCH_NO_CONNECT.
Serialize text (any EDA_TEXT) and label shape in itemToJson. Moving/deleting existing
items of any type already worked (generic changed->Move and removed->Remove); this adds
their reconstruction on add.

Still uncovered: SCH_SYMBOL (needs lib-symbol + fields/orientation) and graphic shapes
(SCH_SHAPE). Known issues to fix next: adding text freezes the app; circles (SCH_SHAPE)
don't sync; deletes don't apply; wire moves only partially converge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:56:42 +02:00
Gergő Törcsvári
b5b1a0ac05
fix(wasm): dynCall signature-mismatch fallback + eeschema collab wire converters
Two things, both verified in the real web app (two-tab eeschema collab).

1. dynCall crash fix (all apps) — scripts/common/shims/dyncall-binding.js.tmpl.
   Programmatic editor edits trapped with 'indirect call signature mismatch': the
   asyncify-instrumented wasmExports[dynCall_<sig>] trampoline does call_indirect with a
   stale type for some table indices (post-asyncify+O2) even though the table entry is
   valid. Proven by patching the built js: at the trap getWasmTableEntry(index) SUCCEEDS
   where the trampoline fails. Fix: the shim now catches the 'signature mismatch'
   RuntimeError and falls back to getWasmTableEntry; the Asyncify unwind sentinel and real
   exceptions re-throw, so instrumentation/unwind is untouched for normal calls. This
   unblocks ALL programmatic edits, not just collab (e.g. eeschema SCH_ITEM::Move).

2. eeschema collab apply converters (wasm/bindings/eeschema_embind.cpp).
   doApply now handles added-item construction (build the SCH_ITEM with the delta's uuid
   via const_cast — as the s-expr parser does — + commit.Add) and richer SCH_LINE
   serialization (start/end/layer) so wire edits reconstruct on the peer. Implemented for
   SCH_LINE (wires) + SCH_JUNCTION; other types log 'no converter for added type' and are
   skipped (next batch). eeschema re-enabled in the web app collab gate.

Tests: eeschema-collab.spec snapshot (green); apply/two-tab skipped — they no-op headless
because the e2e harness's kicadOpenFile returns false (OpenProjectFiles bails before
building the connectivity graph), so SCH_COMMIT::Push doesn't persist. Verified in-app.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:56:42 +02:00
Gergő Törcsvári
4f8c2d1f56
feat(eeschema): collab bridge read/emit + build fixes; apply WIP (yjs-bridge commit 3)
eeschema's half of the Yjs collaborative bridge, reusing the generic reconciler /
BroadcastChannel transport unchanged. Zero kicad-fork change: native SCH_ITEM uuid +
native SCHEMATIC_LISTENER. All in the wasm layer (wasm/bindings/eeschema_embind.cpp).

Working (verified in the web app):
- kicadCollabSnapshot(): enumerate sch.Hierarchy() -> LastScreen()->Items() as
  {id,type,x,y}; registers the listener on first call
- emit: SCHEMATIC_LISTENER subclass -> per-item delta via window.kicadCollab.onDelta;
  fires on real SCH_COMMIT::Push (a real wire move broadcasts added/removed/changed)

Apply is a documented follow-up (gated off so a peer tab can't crash): SCH_ITEM::Move
traps with 'indirect call signature mismatch' when invoked outside a KiCad tool
coroutine (Asyncify+fiber+exception-trampoline). Modify/Clone/GetPosition all work;
only the virtual Move write traps. Fix direction: route apply through TOOL_MANAGER.

Also: build-kicad-target.sh now force-relinks when only <app>_embind.cpp changed (the
embind .o isn't a make dep, so new bindings silently vanished), and adds the
expected/rtree/fmt thirdparty includes the eeschema bindings need.

Tests: eeschema-collab.spec.ts covers snapshot (green); apply/two-tab skipped with the
blocker noted. WasmTool gates collab to pl_editor only until eeschema apply works.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:56:41 +02:00
Gergő Törcsvári
2d93e51329
docs(yjs-bridge): record commit-2 as built (bridge in wasm layer)
Update 0002 to note the bridge lives in wasm/bindings/pl_editor_embind.cpp (not the
kicad fork) per CLAUDE.md, keeping fork divergence to the single OnModify hook; CRDT
is a uuid-keyed Y.Map; verification is the two-tab BroadcastChannel e2e.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:56:41 +02:00
Gergő Törcsvári
83b3418778
feat(pl_editor): Yjs collaborative bridge — differ/apply + generic reconciler (yjs-bridge commit 2)
Bidirectional bridge between pl_editor's DS_DATA_MODEL and a Yjs doc, two same-origin
tabs syncing over BroadcastChannel. Full architecture in features/yjs-bridge/0001-0002.

C++ (wasm layer, wasm/bindings/pl_editor_embind.cpp — public DS_DATA_MODEL API only,
zero added fork divergence beyond the OnModify hook):
- snapshot-differ ChangeSource: diff model vs last-emitted snapshot on OnModify,
  emit per-item delta JSON via EM_ASM window.kicadCollab.onDelta
- kicadCollabApply(json): apply remote delta by uuid — scalars (text/segment/rect)
  by field, polygon/bitmap via SetPageLayout-append blob; reseed snapshot + HardRedraw
- kicadCollabSnapshot() (seed/baseline), s_applyingRemote echo guard, and a
  kicadCollabTestAddText() PoC local-edit hook
- wire format: {added:[item],changed:[item],removed:[uuid]}, item = {id,type,...fields}

JS (web/apps/frontend/src/wasm/collab/, generic + schema-agnostic):
- reconciler: uuid-keyed Y.Map of per-item Y.Map; down = onDelta→Y, up = observe→apply,
  origin-tagged echo suppression; seed-once join adopts the doc authoritatively
- broadcast-transport: minimal BroadcastChannel Yjs provider (query/state catch-up)
- WasmTool wiring behind ?collab=1 (pl_editor only); gated debug logging

Tests: tests/kicad/pl_editor-collab.spec.ts — single-page C++ contract (snapshot/apply
changed+removed+added/echo-suppression) + two-tab BroadcastChannel A<->B propagation.
Reconciler+yjs bundled via esbuild (tests/collab/build.mjs, npm run build:collab).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:56:41 +02:00
Gergő Törcsvári
f94114efb8
docs(yjs-bridge): add collaborative Yjs bridge design
Approved design for porting KiCad-wasm editors to collaborative editing via Yjs:
- 0001 general (tool-agnostic) bridge contract: structured CRDT, unified
  ChangeSource + apply, the 3 serialization mechanisms, 4-commit phasing
- 0002 pl_editor (first PoC): identity, snapshot-differ adapter, field mapping
- 0003 eeschema, 0004 pcbnew: native-listener/COMMIT adapters

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:56:41 +02:00
Gergő Törcsvári
7d49007473
feat(pl_editor): add KIID m_Uuid + (uuid) to .kicad_wks format (yjs-bridge commit 1)
Bump kicad submodule to the per-item uuid identity change, plus the wasm/test
infra to verify it:

- wasm/bindings/pl_editor_embind.cpp: test-only kicadSaveDrawingSheet(path) hook
  that serializes the singleton DS_DATA_MODEL to MEMFS (also a building block for
  the bridge's later materialize-to-file path)
- tests/kicad/pl_editor-uuid.spec.ts: open->save->read-back e2e proving (uuid …)
  backfill (4 distinct uuids) and load->save round-trip preservation

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:56:38 +02:00
Istvan Matejcsok
2bd39c3794 config: 🔧 move submodules to ee 2026-06-05 13:36:44 +02:00
Istvan Matejcsok
c148443b7b refactor: 💡 remove orphaned files 2026-06-05 13:18:24 +02:00
Istvan Matejcsok
8e413f89ec docs: ✏️ cleanup and organize docs 2026-06-05 12:16:13 +02:00
Istvan Matejcsok
8db6cfadc3 test: 💍 fix test cases 2026-06-05 11:42:01 +02:00
Balint Ipkovich
03fbd73e99 feat(web): wire gerbview tool (file-less launch, wizard-skip, UI)
Register the newly WASM-ported Gerber Viewer in the web app the same way as
the other tools — registry entries only, no UI edits (ProjectDetailPage renders
launch links generically from FILELESS_TOOLS).

GERBVIEW_FRAME opens gerber/drill files through its own File→Open UI and projects
carry no gerber files to auto-open, so it's treated as file-less (boot standalone),
mirroring symbol_editor. It boots through single_top.cpp's STARTWIZARD, so it seeds
config to skip the first-run wizard (TOOL_NEEDS_CONFIG_SEED) and gets a
/usr/bin/gerbview argv0.

- contract: add "gerbview" to TOOLS, TOOL_LABELS ("Gerber Viewer"), FILELESS_TOOLS
- frontend: add gerbview to TOOL_ARGV0 and TOOL_NEEDS_CONFIG_SEED
- e2e: add file-less gerbview case to tools-open.spec.ts (title "Gerber Viewer",
  canvas painted, wizard-free, no WASM abort)

Verified in-browser via npm run test:web: gerbview boots wizard-free with the
viewer chrome (toolbars + layers manager), 0 console errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 16:47:23 +02:00
Balint Ipkovich
4467bcd2c4 feat: gerbview WASM port 2026-06-03 14:23:57 +02:00
Gergő Törcsvári
3ef461a0f6
test(e2e): web-app tool open-path suite + pl_editor open/wizard-skip
Add an e2e suite that drives the real React web app (not the standalone
harness): tests/playwright-web.config.ts + tests/web/tools-open.spec.ts navigate
/p/demo/<tool>/<file> for all five tools and assert each boots, opens its demo
file (title drops "untitled"), shows no first-run wizard, and emits no WASM abort
or URL-regex modal. global-setup-web.ts re-seeds the demo project through the API
if missing, so the suite is self-sufficient against a running dev stack. Wired as
`npm run test:web`.

Also at the harness level:
- pl_editor-load.spec.ts: prove the pl_editor kicadOpenFile embind hook opens a
  .kicad_wks (mirrors eeschema-load.spec.ts).
- seed KiCad config in pl_editor.html / symbol_editor.html (matching eeschema.html
  and the web app's boot.ts) so the harness boots wizard-free; repurpose
  pl_editor.spec.ts's stale "wizard completes" test into a wizard-skip regression.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 08:52:34 +02:00
Gergő Törcsvári
0157741660
feat(web/server): seed a ready-to-open demo project on migrate
A freshly cloned + migrated install had no projects, so there was nothing to
click on. Add seedDemoProject() (run from db:migrate after seedDefaultOwner,
idempotent) that creates a "demo" project and loads three committed fixtures
from web/apps/server/seed-data/ — covering one openable file per editor:

  demo.kicad_sch  -> eeschema (Schematic Editor)
  demo.kicad_pcb  -> pcbnew (PCB Editor)
  demo.kicad_wks  -> pl_editor (Drawing Sheet Editor)

The sch/pcb are the self-contained ecc83 push-pull demo (version-compatible
with this build); the wks is a minimal hand-written drawing sheet. Bytes are
committed so seeding needs no submodule checkout at runtime.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 08:52:18 +02:00
Gergő Törcsvári
8341573c5c
feat(web): wire pl_editor + symbol_editor tools (open, wizard-skip, UI)
Add the two newly WASM-ported editors to the web app the same way as the
existing tools:

- pl_editor (drawing-sheet, .kicad_wks): PL_EDITOR_FRAME overrides
  OpenProjectFiles, so it gets the generic kicadOpenFile embind hook
  (wasm/bindings/pl_editor_embind.cpp) for deterministic open. Mapped
  .kicad_wks -> pl_editor in EXTENSION_TOOL.
- symbol_editor (symbol library): SYMBOL_EDIT_FRAME does NOT override
  OpenProjectFiles, so it's treated as file-less (boot standalone, opens
  libraries via its own UI). Added to FILELESS_TOOLS.

Both boot through single_top.cpp's STARTWIZARD, so both seed config to skip the
first-run wizard (TOOL_NEEDS_CONFIG_SEED) and get a /usr/bin/<binary> argv0.

contract: add to TOOLS, plus a TOOL_LABELS map for friendly names. The project
UI now renders file-less launch links generically from FILELESS_TOOLS and
per-file "Open in <label>" links from EXTENSION_TOOL (auto file-type detection),
so adding a tool needs no UI edits.

Verified in-browser: pl_editor opens a .kicad_wks (renders the sheet),
symbol_editor boots wizard-free; both with 0 console errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 21:49:04 +02:00
Gergő Törcsvári
c77fed2ef1
fix(web): seed KiCad config for all tools to skip the first-run wizard
Every standalone tool (eeschema, pcbnew, calculator) boots through
common/single_top.cpp, which runs STARTWIZARD::CheckAndRun() — the first-run
"KiCad Setup" wizard. It shows whenever any provider (SETTINGS / LIBRARIES /
PRIVACY) reports NeedsUserInput(), which is always true on our ephemeral MEMFS
with no config, and its modal loop crashes Asyncify. Only eeschema was seeding
config, so pcbnew and the calculator hit the wizard.

Flip TOOL_NEEDS_CONFIG_SEED to true for pcbnew and calculator so seedKicadConfig
runs in preRun for all three (it writes the kicad_common.json privacy flags and
the sym/fp/design-block lib-tables the providers check), making NeedsUserInput()
false and skipping the wizard. Verified in-browser: pcbnew renders a board at
/p/mytest/pcbnew/bottom.kicad_pcb and the calculator loads, both wizard-free.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 20:33:02 +02:00
Gergő Törcsvári
6681e0dd82
test(eeschema): guard URL-detection wxRegEx UTF-8 fix + bump wxwidgets
Add tests/kicad/eeschema-url-regex.spec.ts: opens a text_box-bearing
schematic with a real URL via Module.kicadOpenFile and asserts the
URL-detection wxRegEx compile no longer fails — no "Invalid regular
expression" in the console and no error dialog. A pre-fix build renders the
same text_box, hits IsURL() -> the failing static regex, and fires both
signals.

Bumps the wxwidgets submodule to the wxConvLibc->UTF-8 emscripten fix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 20:32:25 +02:00
Gergő Törcsvári
83462f7090
fix(web): boot KiCad WASM in-document (no iframe) and fix eeschema frame sizing
Replace the same-origin iframe in WasmTool with a direct in-document boot
(src/wasm/boot.ts): build the global Emscripten Module + preRun steps and
inject wx.js + <tool>.js into the page, the same artifacts the e2e harness
uses. The build is non-modularized (global Module/FS) and pthread-based, so
locateFile/mainScriptUrlOrBlob are set so the wasm + worker load regardless
of the SPA route, and only one tool runs per page load.

Two bugs found during in-browser verification:
- This build does not export Module.FS (touching it aborts); use the global
  window.FS like the harness does.
- The wasm reads top-level frame geometry from a global `mainWindow`
  (offsetWidth/offsetHeight/offsetTop), falling back to a hardcoded 1280x720
  when undefined. The harness sets it via `var mainWindow = ...`; we must too,
  or the frame mismatches the viewport and the whole AUI layout breaks
  (missing toolbars, transparent/ghosted panels). Expose the #main-window
  element as window.mainWindow.

Verified: eeschema renders the full UI (menus, toolbars, panels, schematic)
matching the e2e baseline. pcbnew remains pre-existing-broken at the build
level (raw pcbnew.html harness is equally broken: empty registry, dynCall
"ii signature" errors), independent of this change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 20:32:25 +02:00
Gergő Törcsvári
18a9de0449
fix(wasm): self-heal fiber trampoline so schematic load doesn't hang
The emscripten fiber glue gates Fibers.trampoline() on Fibers.trampolineRunning
and resets it at the end of its loop. At startup emscripten_set_main_loop(...,1)
throws "unwind" to establish the main loop, and KiCad does so from inside a tool
coroutine, so the throw propagates THROUGH the trampoline and skips the reset —
leaving the flag stuck true. Every fiber swap after startup then becomes a silent
no-op, so opening a schematic (SetScreen -> RunAction(selectionClear) -> fiber
swap) hangs forever with the editor stuck on "untitled".

Wrap the trampoline loop in try/finally (inject-dyncall-shims.sh section "3c") so
the flag is always reset. Add tests/kicad/eeschema-load.spec.ts, which opens a
small wires/junctions schematic via Module.kicadOpenFile and asserts the editor
title switches away from "untitled": it times out (RED) without the shim and
passes (GREEN) with it. Also add features/web-init/0002-url-regex-modal-followup.md
capturing the unrelated URL-detection wxRegEx modal surfaced once loading works.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 20:32:25 +02:00