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>
This commit is contained in:
parent
b672c54189
commit
b6a77ee93c
80 changed files with 671 additions and 2992 deletions
127
web/README.md
127
web/README.md
|
|
@ -1,116 +1,83 @@
|
|||
# KiCad Web
|
||||
# PCBJam Web — standalone editor (GPL)
|
||||
|
||||
Single web app to create/open KiCad projects, upload files, and open them in the
|
||||
WASM tools (pcbnew / eeschema / calculator) by URL:
|
||||
A self-contained, GPL web app that opens KiCad projects in the WASM tools
|
||||
(pcbnew / eeschema / pl_editor / …) — from a local folder, or from any backend
|
||||
that implements the MIT [`@pcbjam/shared`](./pcbjam-shared) contract. It opens a
|
||||
tool by URL:
|
||||
|
||||
```
|
||||
/p/<project>/<tool>/<file-path> e.g. /p/project5/pcbnew/nyak.kicad_pcb
|
||||
/p/<project>/<tool>/<file-path> e.g. /p/demo/pcbnew/nyak.kicad_pcb
|
||||
```
|
||||
|
||||
Design + decisions: [`../docs/features/web-init/0001-web-app-spec.md`](../docs/features/web-init/0001-web-app-spec.md).
|
||||
This workspace contains **only** the generic editor and a thin reference
|
||||
backend. All project-specific concerns (accounts, project management, uploads,
|
||||
auth) live in the separate closed application, which reuses this editor by
|
||||
hosting it standalone and redirecting to it (it must not link the GPL editor).
|
||||
|
||||
## 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`)
|
||||
## Layout
|
||||
|
||||
```
|
||||
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
|
||||
├── standalone/ # @pcbjam/standalone — the GPL editor (Vite + React)
|
||||
├── backend/ # @pcbjam/backend-example — thin reference @pcbjam/shared impl
|
||||
└── pcbjam-shared/ # @pcbjam/shared — the FE↔BE contract (git submodule, MIT)
|
||||
```
|
||||
|
||||
- **Editor**: Vite + React + TypeScript. Boots a tool directly in the document
|
||||
(no iframe), syncs the project tree into MEMFS, drives File→Open, and runs
|
||||
same-tab collaboration over BroadcastChannel.
|
||||
- **Example backend**: Fastify + ts-rest serving a single project off the local
|
||||
filesystem (`PROJECT_DIR`). No DB, no auth, no uploads — the minimum the editor
|
||||
needs, and a worked example of the contract.
|
||||
|
||||
## Quick start
|
||||
|
||||
```bash
|
||||
cd web
|
||||
cp .env.example .env # Postgres host port defaults to 54329 (non-default)
|
||||
pnpm install
|
||||
git submodule update --init web/pcbjam-shared # if not already populated
|
||||
|
||||
pnpm db:up # start Postgres (docker compose)
|
||||
pnpm db:migrate # apply migrations + seed the default owner
|
||||
cp standalone/.env.example standalone/.env
|
||||
cp backend/.env.example backend/.env # PROJECT_DIR=../../tests/fixtures/demo
|
||||
|
||||
pnpm dev # turbo: server :3050 + frontend :3048
|
||||
pnpm dev # turbo: backend :3060 + editor :3048
|
||||
```
|
||||
|
||||
Open http://localhost:3048 — create a project, upload files (multi / folder /
|
||||
.zip), then open a `.kicad_pcb` / `.kicad_sch` in its tool.
|
||||
Open http://localhost:3048 — either **open a local folder** (no backend needed)
|
||||
or open the backend's project. The editor can point at any conforming backend
|
||||
via `VITE_API_BASE_URL`.
|
||||
|
||||
## 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`.
|
||||
The runtime artifacts (`<tool>.js/.wasm`, `wx.js`, `images.tar.gz`, `<tool>.html`)
|
||||
are build outputs, **not** committed. They are synced into `tests/apps/kicad/` by
|
||||
`tests/scripts/setup-kicad-wasm.sh` (from repo-root `output/`).
|
||||
|
||||
**They must be served same-origin as the app.** Under the document's COEP/
|
||||
**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`.
|
||||
its glue/wasm from a different origin. `pnpm dev` runs `scripts/link-wasm.mjs`,
|
||||
which **symlinks** `standalone/public/wasm → tests/apps/kicad`; Vite serves them
|
||||
at `/wasm`. `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
|
||||
`WASM_SRC_DIR=/path pnpm --filter @pcbjam/standalone link-wasm`.
|
||||
- If a 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).
|
||||
- **prod**: point `VITE_WASM_ASSET_BASE_URL` at a URL whose origin also satisfies
|
||||
the same-origin / COEP constraints.
|
||||
|
||||
## 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 dev` | editor + example backend (turbo) |
|
||||
| `pnpm build` | build all packages |
|
||||
| `pnpm typecheck` | typecheck all packages |
|
||||
|
||||
## API (shared via `packages/contract`)
|
||||
## Contract (`@pcbjam/shared`, MIT)
|
||||
|
||||
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).
|
||||
The editor reads from a backend over the shared contract:
|
||||
`GET /api/projects`, `GET /api/projects/:project`,
|
||||
`GET /api/projects/:project/files`, and the streamed
|
||||
`GET /api/projects/:project/files/*` (raw bytes). Management/write operations and
|
||||
ownership are **not** part of this contract — they belong to the closed app.
|
||||
|
|
|
|||
Loading…
Reference in a new issue