2026-07-01 20:06:51 +02:00
# Gerber viewer demo
2026-06-17 10:28:06 +02:00
2026-07-01 20:06:51 +02:00
KiCad's `gerbview` compiled to WebAssembly, embedded (lazily) in the landing page
and the blog post. Click → it streams the WASM and renders the bundled
tiny_tapeout board in-browser.
The WASM is **not ** kept here — `boot.js` loads it from the versioned CDN
(`cdn.pcbjam.com` ), the same artifacts the demo/app deploy publishes. It resolves
gerbview's immutable, content-addressed folder at runtime from the release
manifest, so this page always shows the **latest deployed ** gerbview with no
manual sync:
```
manifest-latest.json -> { tag }
manifest-<tag>.json -> tools.gerbview -> <ver>
base = https://cdn.pcbjam.com/wasm/gerbview/<ver>
```
See `docs/features/demo-deploy/0001-wasm-cdn-versioning.md` (in `pcbjam-private` )
for the CDN layout, manifest shapes, and header matrix.
2026-06-17 10:28:06 +02:00
## Files here (`site/public/gerber-demo/`)
| Path | What it does |
|------|--------------|
2026-07-01 20:06:51 +02:00
| `index.html` | The iframe/standalone target — minimal page with the `#main-window` / `#window-container` the WASM needs. |
| `boot.js` | Boot harness: resolves gerbview's CDN folder from the manifest, configures Emscripten `Module` , seeds KiCad config, preloads the board into MEMFS, auto-opens it via `Module.arguments` , and injects `wx.js → wx-dom.js → gerbview.js` from the CDN. |
2026-06-17 10:28:06 +02:00
| `board/` | The tiny_tapeout Gerber layers (committed) the demo opens. |
| `poster.png` | Static fallback shown to browsers that can't run the live viewer. |
2026-07-01 20:06:51 +02:00
The folder is cross-origin to this page (which is COEP `require-corp` ); the CDN
sends `Cross-Origin-Resource-Policy: cross-origin` + `Access-Control-Allow-Origin: *` ,
and the cross-origin pthread worker is loaded via a same-origin `blob:`
`importScripts` shim (`new Worker(<cross-origin URL>)` is a SecurityError). This
mirrors the standalone editor's `web/standalone/src/wasm/boot.ts` .
2026-06-17 10:28:06 +02:00
## Related pieces (elsewhere in `site/`)
| Path | What it does |
|------|--------------|
2026-07-01 20:06:51 +02:00
| `src/sections/GerberDemoSection.astro` | The landing-page showcase: a poster + launch button that opens `/gerber-demo/` in a new tab (the landing itself is not cross-origin isolated). |
| `src/components/GerberDemo.astro` | The blog embed: lazy click-to-load iframe, cross-origin-isolation reload guard, feature-detect + poster fallback. |
2026-06-17 10:28:06 +02:00
| `astro.config.mjs` + `src/middleware.ts` | Dev cross-origin-isolation headers (COOP/COEP `require-corp` ). |
feat(site): move the marketing site from Vercel to Cloudflare Pages
www.pcbjam.com was the last piece of the stack on Vercel. It is now a
Cloudflare Pages project (pcbjam-site) deployed by deploy-site.yml on
every push to main touching site/** — content must not wait for a
release tag.
The Astro adapter is gone entirely: the build is pure static and the one
dynamic route, /api/waitlist, is a Pages Function. Going adapter-free
(rather than swapping in @astrojs/cloudflare, which has dropped Pages
support and only targets Workers) removes three problems at once — no
Astro/adapter major-version coupling, Footer.astro's build-time execSync
keeps working because prerendering stays in Node, and image optimisation
stays plain build-time sharp with no Cloudflare Images binding.
Verified against a real Pages runtime (wrangler pages dev): 21/21 parity
probes pass, versus 19/21 on live Vercel. The scripted runbook is in
deploy/site/ — every mutating step is dry-run by default.
Four behaviour differences were found by measurement and are handled here:
- The blog post's COOP/COEP was already broken in production. vercel.json
scoped the headers to the bare URL, but the page's own canonical is the
trailing-slash form, which served 200 with no isolation headers — so
search arrivals lost SharedArrayBuffer and the embedded Gerber viewer
degraded. public/_headers covers both forms.
- Pages answers unknown URLs with the homepage at HTTP 200 when the
output has no 404.html — a soft-404 that invites indexing junk URLs as
the homepage. Hence src/pages/404.astro.
- Vercel's edge refused cross-site form POSTs ("Cross-site POST form
submissions are forbidden"); Pages does not, and a cross-site <form>
submit needs no CORS permission to be sent, so the allowlist cannot
stop it. The Function reproduces the guard; JSON posts stay exempt as
that is demo.pcbjam.com's allowlisted path.
- Cache-Control: immutable on /_astro/* came from the Vercel adapter's
generated route config, so it is now an explicit _headers rule.
Secrets move to `wrangler pages secret put --project-name pcbjam-site`
(RESEND_API_KEY, RESEND_SEGMENT_ID, WAITLIST_FROM_EMAIL);
WAITLIST_ALLOWED_ORIGINS stays unset so the allowlist stays in code.
Local dev reads .dev.vars, now gitignored — the root repo's **/.dev.vars
does not cover a nested git repo.
privacy.md and cookies.md named Vercel as a GDPR Art. 28 processor; those
mentions are removed and the existing Cloudflare entry widened to cover
website hosting.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LAmkjM7okPdScp9XLW1JVr
2026-07-27 13:34:12 +02:00
| `public/_headers` | Prod COOP/COEP on Cloudflare Pages, scoped to the blog post + `/gerber-demo/` routes (both URL forms of the post). |
2026-06-17 10:28:06 +02:00
2026-07-01 20:06:51 +02:00
## Dev overrides
2026-06-17 10:28:06 +02:00
2026-07-01 20:06:51 +02:00
`boot.js` reads query params so you can point it elsewhere without a rebuild:
2026-06-17 10:28:06 +02:00
2026-07-01 20:06:51 +02:00
| Param | Effect |
|-------|--------|
| `?tag=<tag>` | Pin a specific release instead of following `manifest-latest.json` . |
| `?cdn=<root>` | Swap the CDN root (e.g. a local mirror serving `manifest-*.json` + `gerbview/<ver>/` ). |
| `?base=<folder>` | Use a tool folder verbatim (e.g. a fresh local build) — skips manifest resolution. |
2026-06-17 10:28:06 +02:00
The live viewer needs SharedArrayBuffer + WebGL2 (Chrome/Edge/Firefox, Safari 15.2+);
2026-07-01 20:06:51 +02:00
other browsers get `poster.png` .
## Updating `KICAD_VERSION_DIR`
`boot.js` seeds KiCad config under a version dir (currently `"10.0"` ) to suppress
the first-run wizard. It **must ** match the deployed build's
`GetMajorMinorVersion()` . If a future deploy bumps KiCad's major.minor, update the
`KICAD_VERSION_DIR` constant in `boot.js` (same coupling as
`web/standalone/src/wasm/constants.ts` ).