pcbjam/web
Repository files (latest commit first)
Filename Latest commit message Latest commit date
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
..
apps feat(eeschema): collab bridge read/emit + build fixes; apply WIP (yjs-bridge commit 3) 2026-06-05 13:56:41 +02:00
packages feat(web): wire gerbview tool (file-less launch, wizard-skip, UI) 2026-06-03 16:47:23 +02:00
.env.example feat(web): checkpoint web app init 2026-06-02 20:32:19 +02:00
.gitignore feat(web): checkpoint web app init 2026-06-02 20:32:19 +02:00
docker-compose.yml feat(web): checkpoint web app init 2026-06-02 20:32:19 +02:00
package.json feat(web): checkpoint web app init 2026-06-02 20:32:19 +02:00
pnpm-lock.yaml feat(pl_editor): Yjs collaborative bridge — differ/apply + generic reconciler (yjs-bridge commit 2) 2026-06-05 13:56:41 +02:00
pnpm-workspace.yaml feat(web): checkpoint web app init 2026-06-02 20:32:19 +02:00
README.md docs: ✏️ cleanup and organize docs 2026-06-05 12:16:13 +02:00
tsconfig.base.json feat(web): checkpoint web app init 2026-06-02 20:32:19 +02:00
turbo.json feat(web): checkpoint web app init 2026-06-02 20:32:19 +02:00

KiCad Web

Single web app to create/open KiCad projects, upload files, and open them in the WASM tools (pcbnew / eeschema / calculator) by URL:

/p/<project>/<tool>/<file-path>      e.g. /p/project5/pcbnew/nyak.kicad_pcb

Design + decisions: ../docs/features/web-init/0001-web-app-spec.md.

Stack

  • Monorepo: pnpm + turbo
  • Frontend: Vite + React + TypeScript + shadcn/ui (apps/frontend)
  • Backend: Fastify + ts-rest + Zod (apps/server)
  • DB: Postgres + Drizzle (project/file metadata)
  • Storage: pluggable FileStorage (local disk now, S3 later) (packages/storage)
  • Shared types: ts-rest contract + Zod (packages/contract)
web/
├── apps/
│   ├── frontend/   # Vite React app
│   └── server/     # Fastify API + WASM static + Drizzle
└── packages/
    ├── contract/   # ts-rest contract + Zod schemas (FE + BE share this)
    └── storage/    # FileStorage interface + LocalDiskStorage

Quick start

cd web
cp .env.example .env          # Postgres host port defaults to 54329 (non-default)
pnpm install

pnpm db:up                    # start Postgres (docker compose)
pnpm db:migrate               # apply migrations + seed the default owner

pnpm dev                      # turbo: server :3050 + frontend :3048

Open http://localhost:3048 — create a project, upload files (multi / folder / .zip), then open a .kicad_pcb / .kicad_sch in its tool.

WASM artifacts

The runtime artifacts (<tool>.js/.wasm, wx.js, images.tar.gz, plus the <tool>.html harness pages) are build outputs, not committed here. The complete set is synced into tests/apps/kicad/ by tests/scripts/setup-kicad-wasm.sh from repo-root output/ (+ wx.js from wxwidgets/; output/ alone lacks wx.js). That script is a real sync — it skips files already byte-identical at the destination, so re-running it does not rewrite the multi-hundred-MB .wasm.

They must be served same-origin as the app. Under the document's COEP/ cross-origin-isolation (set by the Vite dev server), KiCad WASM refuses to load its glue/wasm from a different origin. So the app serves them from its own origin with no extra copy: pnpm dev runs scripts/link-wasm.mjs, which symlinks apps/frontend/public/wasm → tests/apps/kicad. Vite then serves them at /wasm (same origin). VITE_WASM_ASSET_BASE_URL defaults to /wasm.

  • Point the symlink elsewhere with WASM_SRC_DIR=/path pnpm --filter @kicad-web/frontend link-wasm.
  • If the tool won't load, the target dir is probably empty — run tests/scripts/setup-kicad-wasm.sh to populate tests/apps/kicad/.

The tool view (WasmTool.tsx + src/wasm/boot.ts) boots the tool directly in the React document — no iframe. It replicates the proven harness HTML (tests/apps/kicad/<tool>.html): builds the same global Emscripten Module config and preRun steps (create canvas, write images.tar.gz, seed config), then injects the same wx.js + <tool>.js artifacts into the page. It then syncs the project tree into MEMFS and drives File→Open. The build is non-modularized (global Module/FS) and pthread-based, so only one tool runs per page load; switching tools requires a full navigation. locateFile resolves the wasm and the pthread worker against <base> so they load regardless of the SPA route.

prod: point VITE_WASM_ASSET_BASE_URL at a CDN URL — but that origin must itself satisfy the same-origin / COEP constraints (e.g. served under the app's own origin/path).

Scripts

Command What
pnpm dev server + frontend (turbo)
pnpm db:up / pnpm db:down start/stop Postgres
pnpm db:generate generate Drizzle migration SQL from schema
pnpm db:migrate apply migrations + seed default owner
pnpm db:seed (re)seed the default owner
pnpm typecheck typecheck all packages
pnpm build build all packages

API (shared via packages/contract)

JSON (ts-rest): GET/POST /api/projects, GET/DELETE /api/projects/:project, GET /api/projects/:project/files.

Binary (raw Fastify, response shapes still shared via Zod): POST /api/projects/:project/files (multi-file + folder), POST /api/projects/:project/files/zip, GET /api/projects/:project/files/* (stream bytes).

Status / next iteration

Working end-to-end: create / open / upload (files, folder, zip) / file download / WASM static serving / project list & detail UI / URL routing.

Booting a tool syncs the whole project tree into MEMFS, then opens the target file. The open step (apps/frontend/src/wasm/open-flow.ts) prefers a programmatic hook (Module.kicadOpenFile) and falls back to EXPERIMENTAL UI automation ported from the e2e tests — this needs in-browser validation against built artifacts, and exposing a real embind open-entry-point is the intended follow-up (spec §11.2). Lazy/partial MEMFS loading and save-back land together in a later iteration (spec §§9, 12).