Commit graph pcbjam/web
Author SHA1 Message Date
Gergő Törcsvári
b0b7f69846
feat: standalone home rework — tool launcher + backend libraries listing
Rework HomePage into KiCad-launcher-style zones: local file-load, a Tools grid,
backend projects, and a backend libraries listing (useLibs over the shared
listLibs). The Tools grid shows all seven tools in KiCad's standalone order
(Schematic/Symbol/PCB/Footprint editors, Gerber Viewer, Calculator, Drawing
Sheet); clicking a tool or a library launches the editor with no project
(launchedTool → WasmTool slug=local) — file-less editors browse backend libs,
document editors open blank. Harden the web e2e global-setup to assert the
self-provisioned origin libs (Device, Resistor_SMD) are present, failing fast.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 16:28:21 +02:00
Gergő Törcsvári
c2e1214fd1
feat: GPL backend self-provisions example libs + standalone port override
Port the KiCad symbol/footprint brace-scanner parsers + a combined extractor
into web/backend/src/extract/; ensure-example-libs blobless/sparse-clones a
curated KiCad 10.0.3 slice and extracts to a gitignored .libs/ on dev/start, so
a bare GPL clone has libraries with no closed repo present. libsConfig() defaults
LIBS_DIR to ./.libs. vite honors STANDALONE_PORT (strictPort) so a second editor
can run on :3049 alongside the closed stack.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 16:28:21 +02:00
Gergő Törcsvári
2c52637b1a
feat: libs 0009-B — footprint serve + read wiring (GPL backend + standalone): extForKind .kicad_mod read/write, kind-filtered listLibs, LibsSource.listLibs(kind) threaded + boot per-tool kind; footprint-browse-remote e2e green (opens real capped 10.0.3 fp); bump pcbjam-shared 2026-06-16 16:28:21 +02:00
Gergő Törcsvári
19fce866c1
feat: libs 0009-S — footprint write-path spike DONE + verified (footprint editor as project-scoped WASM tool + PCB_IO_PCBJAM_FP plugin + bridge kind 4th-arg; New Footprint→FootprintSave fires through bridge on main thread, fork-native body v20251028, no wedge — Firefox e2e green); bump kicad + pcbjam-shared 2026-06-16 16:28:21 +02:00
Gergő Törcsvári
f04915ee1d
feat: libs 0005 — standalone sends the project header on lib requests (project-scoped server-side resolution); bump pcbjam-shared
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 16:28:20 +02:00
Gergő Törcsvári
fa7984b192
feat: libs 0004-C — standalone real remote writes (createLib/saveItemBody + OWNER_HEADER), single-mount collapse, boot ensures user lib; remote-write e2e; bump kicad
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 16:28:20 +02:00
Gergő Törcsvári
beb4ce0f3f
feat: libs 0004-B — example backend user-lib write (createLib + item PUT, owner-namespaced files, merged reads); bump pcbjam-shared
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 16:28:20 +02:00
Gergő Törcsvári
df4de65ba2
feat: libs 0004-A — symbol write-path spike (provider save op, writable lib + MEMFS placeholder files, rw mount); fix symbol_editor embind link; e2e write-bridge test; bump kicad
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 16:28:20 +02:00
Gergő Törcsvári
d2d2c19a8c
feat: libs 0003 — remote provider + example backend serving; bump kicad, shared
standalone: LibsSource abstraction (remote over the contract + static fallback),
generic window.kicadLibs provider, sym-lib-table generated from the backend's
lib list at boot (replaces the spike). backend: serve pre-built self-contained
symbol bodies from LIBS_DIR (listLibs/listLibItems + raw item-body route).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-16 16:28:20 +02:00
Gergő Törcsvári
29a7dcc233
feat: libs 0002 — /mnt/pcbjam lib URIs + mount point seed; bump kicad
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-16 16:28:20 +02:00
Gergő Törcsvári
7e454104aa
feat: libs 0002 spike — kicadLibs provider stub + lib-table seeding; bump kicad
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-16 16:28:19 +02:00
Istvan Matejcsok
aa9d613f50 fix: 🐛 press m & move w/ arrows 2026-06-16 15:46:43 +02:00
Gergő Törcsvári
5830f18393
feat: local-folder project view + suppress browser save dialog on Cmd/Ctrl+S
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 14:05:40 +02:00
Gergő Törcsvári
8131bf9bac
feat: standalone save/load routing + VITE_DOC_SOURCE ydoc mode
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 14:05:40 +02:00
Gergő Törcsvári
07863da416
feat: ysync 0008 Stage D — wire the v2 items collab into the app + verify
- WasmTool: hard cutover to startKicadCollab (gates on
  kicadCollabSnapshotItems; scalar reconciler no longer runs in the app)
- file-seed (ysync 0005 tie-in): empty rooms are seeded from the opened
  file via fileToDoc/docToY (meta+layout+items — file recoverable from the
  Y.Doc alone); populated rooms still adopt (seed-once unchanged)
- binding: gate UP applies until seed() — the provider's initial state
  sync otherwise streams the full doc into an editor that already holds
  the file (trapped eeschema's paste path in the real app)
- pcbnew blobForItem: footprints Format a uuid-corrected copy directly —
  SaveSelection's copy regenerated mandatory-field uuids (FOOTPRINT copy
  ctor assigns into fresh fields), breaking wire identity (rebuild)
- roundtrip.spec on the v2 items wire; new passing pcbnew
  footprint-containment round trip (the 0004 containment win); full
  fixture stays fixme on the tracked envelope-parse limit
- real-app two-tab emit verified for eeschema + pcbnew (see 0008 doc)

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

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 14:05:40 +02:00
Gergő Törcsvári
3a06a267b0
feat: v2 per-item s-expr collab bridge in all three tools (ysync 0008 Stage C)
Add kicadCollabSnapshotItems / kicadCollabApplyItems / window.kicadCollab.onItems
to pl_editor, eeschema, pcbnew — per-item native-blob payloads
({added/changed:[{sexpr,parent}], removed:[uuid]}) alongside the untouched scalar
wire (legacy collab specs stay green).

- pl_editor: itemBlob per item; apply = SetPageLayout(append) + replace-by-uuid
  (pointer-snapshot safe); bare payloads get the kicad_wks envelope. Snapshot +
  apply + emit verified headless.
- eeschema: clipboard Format per item (symbols carry their lib_symbols); apply
  mirrors the native paste — LoadContent into a throwaway sheet → detach →
  replace-by-uuid → symbol lib relink (blob's lib_symbols first, live screen's
  second) → SCH_COMMIT, in the CallAfter+COROUTINE context. Snapshot + apply
  verified headless (a "lost" lone junction turned out to be correct connection
  cleanup — test uses text).
- pcbnew: blobForItem per ROOT item with child→footprint lifting in flushDiff;
  apply = makeFromBlob + commit replace-by-uuid on the fiber; bare non-footprint
  payloads get wrapInBoardEnvelope (live board layer table). Snapshot + footprint
  replace/add WITH children (the 0004 containment gap, closed) + removal verified
  headless. Track/via/zone/text blob-apply hits the documented asyncify-fragile
  envelope parse (reconfirmed empirically — a verbatim SaveSelection segment
  envelope dies silently in the commit) and stays on the legacy scalar apply;
  tracked in ysync 0008 status.
- eeschema/pcbnew scheduleFlush now runs flushDiff inside a COROUTINE: the
  per-item Format in the v2 emit needs the fiber stack (0007 lesson). Their emit
  remains unverifiable headless (both legacy two-tab tests are test.skip:
  "open=false → SCH_COMMIT no-ops" / "harness can't PAINT") — verify in the real
  app at Stage D; pl_editor's emit IS verified.
- tests/kicad/items-bridge.spec.ts: per-tool suite (snapshot uuids → local-edit
  emit (where drivable) → apply changed/added/removed via save-readback → no
  apply echo). 3/3 pass; roundtrip + collab suites unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 14:05:40 +02:00
Gergő Törcsvári
8bbab863da
feat(standalone): Slot-model collab binding runtime (ysync 0008 Stage B)
The thin runtime over @pcbjam/shared's transport-unaware blocks — this module
owns exactly what shared must not: the observeDeep subscription, local-origin
echo policy, and seed-once authority.

- wasm/collab/kicad-binding.ts: KicadItemsBridge (the v2 per-item s-expr bridge
  contract: snapshotItems/applyItems/onItems), bindKicadCollab (DOWN: onItems →
  itemsWireToDelta → applyDeltaToY origin-tagged; UP: observeDeep → skip own
  origin → deltaFromYEvents → deltaToItemsWire → applyItems; seed-once with doc
  authority on join, mirroring the scalar reconciler §2), moduleItemsBridge
  adapter over the future Stage C Module exports.
- index.ts: startKicadCollab — the Slot-model counterpart of startCollab (same
  provider + whenSynced + seed flow); legacy scalar path untouched.
- debug.ts: clog/cwarn quiet + crash-free outside a DOM context (vitest node).
- kicad-binding.test.ts: fake editor bridge + two relayed Y.Docs — seed → adopt
  → edits propagate both ways → footprint removal cascades; no self-echo;
  divergent local root dropped on adopt (doc authority); destroy() detaches.

Verified: 19 standalone unit tests, collab bundle rebuilds, pl_editor two-tab
spec still green (legacy path unchanged).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 14:05:40 +02:00
Gergő Törcsvári
b69ba09ebd
feat: adopt shared wasm wire schemas; resurrect collab e2e bundle (ysync 0008 A)
- standalone wasm/collab/types.ts: the CollabItem/CollabDelta data shapes now
  come from MIT @pcbjam/shared (collab-wire zod schemas) via re-export; the
  RUNTIME adapter interface (CollabBridge) stays here. emptyDelta/isEmptyDelta
  alias the shared helpers — reconciler untouched.
- tests/collab/browser-entry.ts: fix the import path stale since the repo
  restructure (web/apps/frontend → web/standalone) — the collab bundle had been
  silently unbuildable; its opts type now tracks startCollab's real signature.
- tests/kicad/*-collab.spec.ts: update KicadCollab.start calls from the
  pre-ysync-0004 { channel, settleMs } API to { provider: { kind:
  "broadcastchannel", settleMs }, room } — the drift the broken bundle hid.

Verified: pl_editor 2/2, eeschema 2 passed, pcbnew 7 passed (skips pre-existing);
shared 47 unit tests; both typechecks clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 14:05:40 +02:00
Gergő Törcsvári
c98e88c33b
feat: bump pcbjam-shared for typed slot content (ysync 0007)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 14:05:39 +02:00
Gergő Törcsvári
f9a2bf7283
feat: bump pcbjam-shared for nested-item flattening (ysync 0007)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 14:05:39 +02:00
Gergő Törcsvári
f1cd813722
feat: bump pcbjam-shared for KiCad file⇄Y.Doc converter (ysync 0007)
Pointer bump for the MIT sexpr/kicad-doc converter + schema + round-trip suite;
record the vitest devDep in the web workspace lockfile.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 14:05:39 +02:00
Gergő Törcsvári
f29ceb4964
feat(standalone): sexprDiff structural comparator + vitest (0003/0004 §B)
Add the shared, uuid-keyed structural s-expr comparator (README §B) used by both
the live drift check (0003) and the round-trip tests (0004), plus a vitest setup.

- wasm/collab/sexpr-diff.ts: zero-dependency module (importable from standalone
  app code and the Playwright specs). Tokenizes s-expr text into nested lists,
  indexes item nodes by their direct (uuid "X") child, compares item sets order-
  insensitively as multisets of normalized children, emitting
  { equal, added, removed, changed } with per-property a/b. ignoreTokens drops
  volatile tokens. False-positive-free only for same-serializer inputs.
- wasm/collab/sexpr-diff.test.ts: 15 cases (parse, equality, changes, ignoreTokens).
- vitest config + test scripts (node env, isolated from the app build).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 14:05:39 +02:00
Gergő Törcsvári
0773acefaa
feat(standalone): "open in a new tab" escape hatch on OOM dialog (0002)
The automatic recovery uses an in-place location.replace reload, which does NOT
reliably release the OOM'd wasm heap in every browser (notably Firefox), so the
respawn can land right back in an out-of-memory state.

Add a user-gesture escape hatch to the terminal MemoryExhaustedDialog: an "Open
in a new tab" button that opens the editor in a fresh top-level browsing context
(clean retry chain) and closes the current tab. Because it runs from a real
click, window.open is not popup-blocked; window.close() may still be refused on
the original user-opened tab, which is fine — the new tab is the survivor. Copy
tells the user this sometimes helps where the in-place reload didn't.

- oom-watch.ts: export respawnInNewTab(win) — fresh-tab respawn with ?oomRetry
  cleared.
- MemoryExhaustedDialog: "Open in a new tab" (primary) + "Reload this tab"
  (secondary), with honest copy about Firefox + unsaved edits.
- WasmTool: wire onOpenNewTab to respawnInNewTab.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 14:05:39 +02:00
Gergő Törcsvári
0d2f88b437
feat(standalone): OOM tab recovery (0002)
Automatically recover from an out-of-memory by respawning a fresh browsing
context (fresh wasm heap) and discarding the dead one — the manual "duplicate
tab, close the OOM'd one" fix, automated and capped at 2 retries so a too-small
machine stops instead of looping.

- recovery/oom-watch.ts: the recover() state machine with the retry counter in
  the URL (?oomRetry=N, cap 2). Mode A soft detection — Module.onAbort plus
  window error/unhandledrejection matched against an OOM signature, behind an
  idempotent latch. Mode B hard-kill detection — a per-tab localStorage
  heartbeat sentinel; a stale sentinel on the next load continues the retry
  chain (heartbeat keys avoid mistaking a second live tab for a crash). Healthy
  session (alive HEALTHY_RESET_MS) resets the chain. Default strategy is
  location.replace; window.open+close is behind ?oomStrategy=newtab. Platform
  access goes through injectable win/storage seams for testability.
- recovery/MemoryExhaustedDialog.tsx: terminal "out of memory" UI, reusing
  0001's BlockingDialog; copy is honest about lost unsaved edits.
- wasm/boot.ts: forward emscripten Module.onAbort to the watcher (optional
  BootOptions.onAbort).
- WasmTool: install the watcher, gate boot on its proceed flag (skip when the
  chain is already exhausted), and render the terminal dialog.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 14:05:39 +02:00
Gergő Törcsvári
ae20d49241
feat(standalone): device-capability preflight check (0001)
Add a pre-boot "potato check" for the standalone editor: probe the device
against what the KiCad WASM tools actually require and warn (or block, with an
override) before the expensive WASM asset fetch.

- preflight/capabilities.ts: pure, unit-testable probe → CapabilityReport with
  stable codes. Fatal: no SharedArrayBuffer/cross-origin-isolation, no
  WebAssembly, no Workers+Atomics, no WebGL2. Warn: low deviceMemory, low JS
  heap limit (Chromium), few cores, mobile, small screen. Thresholds in one
  tunable const block.
- preflight/BlockingDialog.tsx: reusable modal blocking overlay (no dismiss
  except via explicit action) — also for feature 0002's terminal dialog.
- preflight/PreflightGate.tsx: runs the probe once on mount; fatal → blocking
  dialog with "Try anyway" (short-circuits the asset fetch); warnings →
  dismissible advisory banner with localStorage "don't show again" keyed to the
  exact warning codes.
- ToolPage: wrap WasmTool in PreflightGate so the gate runs before boot.

Optional micro-bench (phase 3) deferred per the spec.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 14:05:35 +02:00
Istvan Matejcsok
ee451c9de2 pcb-schema switch
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 12:26:45 +02:00
Gergő Törcsvári
285efe7a06
feat(standalone): env-selected Yjs provider abstraction (ysync 0004)
Generalize the collab transport into a provider registry (none |
broadcastchannel | partykit | hocuspocus), one active per env. startCollab
takes { provider, room } and awaits provider.whenSynced() before seed/adopt.
Network libs lazily imported; room id from @pcbjam/shared collabRoomId.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 14:37:30 +02:00
Gergő Törcsvári
cf18efcf66
chore: bump pcbjam-shared — collab schemas (ysync 0003) 2026-06-10 14:37:29 +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
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
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
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
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
Istvan Matejcsok
8e413f89ec docs: ✏️ cleanup and organize docs 2026-06-05 12:16:13 +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
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
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
ddd959fbc2
fix(web): eeschema schematic open path — Asyncify/dynCall fixes + bump submodules
- inject-dyncall-shims.sh: add a dynCallLegacy -> wasmExports fallback so embind's
  generic dynCall path finds the DYNCALLS=1 trampolines. Without it, an Asyncify
  unwind/rewind through an embind call (kicadOpenFile -> OpenProjectFiles) died
  with "f is not a function".
- open-flow.ts: kicadOpenFile runs OpenProjectFiles under Asyncify, so its sync
  return is a falsy placeholder. Invoke it and poll the frame title for the load
  instead of trusting the return value, and never fall back to UI automation
  while the hook is in flight (it would re-enter the suspended Asyncify call).
- .gitignore: ignore .playwright-mcp scratch.
- bump kicad + wxwidgets submodules to the wasm schematic-open fixes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 20:32:24 +02:00
Gergő Törcsvári
735e5aa8e9
feat(web): checkpoint web app init
- frontend (Vite/React) + server (Hono/Drizzle) scaffold under web/
- eeschema WASM embind kicadOpenFile hook + programmatic open-flow
- skip KiCad first-run setup wizard by seeding default config in preRun
- dev: auto-sync output/ WASM artifacts into tests/apps/kicad via link-wasm

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