diff --git a/.github/workflows/wasm-build.yml b/.github/workflows/wasm-build.yml index aa3a916..a41b983 100644 --- a/.github/workflows/wasm-build.yml +++ b/.github/workflows/wasm-build.yml @@ -323,15 +323,16 @@ jobs: working-directory: tests run: xvfb-run -a npm run test:perf - # Screenshot drift gate + always-on Discord report (screenshots + perf). + # ON SUCCESS ONLY: screenshot drift gate + Discord report (perf + triptychs). + # Runs only when the build + e2e passed (so renders and test deps exist). # Report-only during rollout: compare.ts exits 0 without --fail-on-change and # the step is continue-on-error, so it never blocks the build — flip to gating # once the per-engine floors are calibrated (tests/tools/screenshots/config.ts, - # seeded by `npm run screenshots:noise`). post-discord posts ONLY on push to - # main and no-ops without DISCORD_WEBHOOK_URL (inert on PRs/forks). It reads the - # already-produced test-results (screenshots + perf-*.json) — no extra build. - - name: Screenshot compare + Discord report (screenshots + perf) - if: always() && inputs.run_tests + # seeded by `npm run screenshots:noise`). Posts ONLY on push to main and no-ops + # without DISCORD_WEBHOOK_URL (inert on PRs/forks). No extra build — reads the + # already-produced test-results (screenshots + perf-*.json). + - name: Screenshot + perf report (on success) + if: success() && inputs.run_tests continue-on-error: true working-directory: tests env: @@ -339,7 +340,25 @@ jobs: GH_TOKEN: ${{ github.token }} run: | npm run screenshots:check - npm run screenshots:report -- --e2e ${{ (steps.wx_e2e.outcome == 'success' && steps.kicad_e2e.outcome == 'success') && 'pass' || 'fail' }} + npm run screenshots:report -- --e2e pass + + # ON FAILURE (build or e2e): a minimal text-only "CI failed" notice, nothing + # else (no images / no comparison). Uses curl, NOT the TS reporter, because on a + # build failure the test deps (npm ci) never installed. Main-push only. + - name: Discord CI-failure notice + if: failure() && github.ref == 'refs/heads/main' && github.event_name == 'push' + continue-on-error: true + env: + DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} + run: | + [ -z "$DISCORD_WEBHOOK_URL" ] && { echo "no webhook — skipping"; exit 0; } + SHORT="$(echo "${{ github.sha }}" | cut -c1-7)" + URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + SUBJ="$(git log -1 --pretty=%s 2>/dev/null || true)" + CONTENT="❌ **CI failed** \`$SHORT\` — $SUBJ"$'\n'"$URL" + jq -n --arg c "$CONTENT" '{content:$c, allowed_mentions:{parse:[]}}' \ + | curl -sS -X POST "$DISCORD_WEBHOOK_URL" -H "Content-Type: application/json" -d @- >/dev/null \ + && echo "posted CI-failure notice" - name: Upload test logs & screenshots if: always() && inputs.run_tests diff --git a/site/.gitignore b/site/.gitignore index fcb4302..374d5cd 100644 --- a/site/.gitignore +++ b/site/.gitignore @@ -2,13 +2,6 @@ dist/ .vercel/ -# Gerber-viewer demo WASM (synced from /output by scripts/sync-demo-wasm.sh). -# The big binaries are served from Cloudflare R2 in production — never commit / -# ship them to Vercel. The small glue JS (wx.js, wx-dom.js, gerbview.js) MUST be -# committed: it's served same-origin (the pthread worker can't be cross-origin). -public/gerber-demo/wasm/gerbview.wasm -public/gerber-demo/wasm/kicad-resources.bin - # generated content collection types & cache .astro/ diff --git a/site/public/gerber-demo/README.md b/site/public/gerber-demo/README.md index a9ee3a0..5ffc0dd 100644 --- a/site/public/gerber-demo/README.md +++ b/site/public/gerber-demo/README.md @@ -1,38 +1,65 @@ -# Gerber viewer blog demo +# Gerber viewer demo -KiCad's `gerbview` compiled to WebAssembly, embedded (lazily) in the blog post. -Click → it streams the WASM and renders the bundled tiny_tapeout board in-browser. +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-.json -> tools.gerbview -> +base = https://cdn.pcbjam.com/wasm/gerbview/ +``` + +See `docs/features/demo-deploy/0001-wasm-cdn-versioning.md` (in `pcbjam-private`) +for the CDN layout, manifest shapes, and header matrix. ## Files here (`site/public/gerber-demo/`) | Path | What it does | |------|--------------| -| `index.html` | The iframe target — minimal page with the `#main-window` / `#window-container` the WASM needs. | -| `boot.js` | Boot harness: 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`. Holds `R2_BASE` + the dev/prod asset-base switch. | +| `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. | | `board/` | The tiny_tapeout Gerber layers (committed) the demo opens. | | `poster.png` | Static fallback shown to browsers that can't run the live viewer. | -| `wasm/` | `wx.js`/`wx-dom.js`/`gerbview.js` (committed, served same-origin) + `gerbview.wasm`/`kicad-resources.bin` (git-ignored; served from R2 in prod, local in dev). Synced from `/output`. | + +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()` is a SecurityError). This +mirrors the standalone editor's `web/standalone/src/wasm/boot.ts`. ## Related pieces (elsewhere in `site/`) | Path | What it does | |------|--------------| -| `src/components/GerberDemo.astro` | The embed: lazy click-to-load iframe, cross-origin-isolation reload guard, feature-detect + poster fallback. | -| `src/content/blog/porting-kicad-graphics-to-webgl-with-claude.mdx` | The post that renders ``. | +| `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. | | `astro.config.mjs` + `src/middleware.ts` | Dev cross-origin-isolation headers (COOP/COEP `require-corp`). | -| `vercel.json` | Prod COOP/COEP, scoped to the post + `/gerber-demo/` routes. | -| `scripts/sync-demo-wasm.sh` | Copy fresh WASM from `/output` into `wasm/` (run after a gerbview rebuild). | -| `scripts/r2-deploy.sh` + `scripts/r2-cors.json` | Upload the heavy binaries to Cloudflare R2 (`pcbjam-assets` → `assets.pcbjam.com`) and set CORS. | +| `vercel.json` | Prod COOP/COEP, scoped to the blog post + `/gerber-demo/` routes. | -## Run / update +## Dev overrides -```bash -# local dev (serves wasm/ from the local mirror) -scripts/sync-demo-wasm.sh && npm run dev # /blog/porting-kicad-graphics-to-webgl-with-claude +`boot.js` reads query params so you can point it elsewhere without a rebuild: -# after a new gerbview build: refresh local mirror, then push binaries to R2 -scripts/sync-demo-wasm.sh && scripts/r2-deploy.sh -``` +| Param | Effect | +|-------|--------| +| `?tag=` | Pin a specific release instead of following `manifest-latest.json`. | +| `?cdn=` | Swap the CDN root (e.g. a local mirror serving `manifest-*.json` + `gerbview//`). | +| `?base=` | Use a tool folder verbatim (e.g. a fresh local build) — skips manifest resolution. | The live viewer needs SharedArrayBuffer + WebGL2 (Chrome/Edge/Firefox, Safari 15.2+); -other browsers get `poster.png`. \ No newline at end of file +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`). diff --git a/site/public/gerber-demo/boot.js b/site/public/gerber-demo/boot.js index 0c96566..b907c0d 100644 --- a/site/public/gerber-demo/boot.js +++ b/site/public/gerber-demo/boot.js @@ -1,44 +1,51 @@ /* * Self-contained boot harness for the KiCad Gerber viewer (gerbview) WASM, - * embedded in the blog post via an