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
- gerber-demo/boot.js honors the base/cdn/tag script-source overrides only on a
loopback hostname, so a shipped page ignores them.
- /api/waitlist gains a best-effort per-IP/per-address rate limit and masks the
submitter address in the no-key log line.
Adds vitest to the site: test/boot-overrides.test.ts (happy-dom) and
test/waitlist.test.ts.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Now that demo.pcbjam.com is live, the landing page leads with it instead
of "coming soon":
- Hero: "Open the live demo" is the primary CTA; the coming-soon panel is
replaced by a DemoLaunchCard (screenshot + play overlay, new tab — the
editor needs COOP/COEP so it can never be iframed here); waitlist demoted
to a ghost button under a "Want early access to multiplayer?" lead-in.
- Removed the Gerber demo and positioning-map sections, then restored the
positioning map per review — recolored for light theme and without the
multiplayer badge/legend ("Everyone picks one. We do both.").
- Light theme: flipped the :root ink tokens, darkened accent/signal for
contrast on white; fixed two dark-assumption bugs it exposed (header CTA
text losing to .nav>a specificity, DemoVideo play scrim derived from --bg).
- Copy: positioned around what's live (real KiCad in a tab) with
multiplayer explicitly "coming — join the waitlist"; cut untrue claims
(GitHub import, self-host, offline PWA -> roadmap); cut the jam metaphor
outside the product name; TrustBar fake-stat grid -> plain claims strip;
footer tagline + fallback meta description aligned.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01REwWZGkAz1AZUJw8yqxkCN
Demo (web/standalone): retitle "KiCad Web" -> "PCBJam - KiCad demo", add
description + OG/Twitter tags (absolute URLs pinned to demo.pcbjam.com),
ship favicons (page had none), add the og-demo.png share card, and show an
"Early-access alpha" badge next to the home-page heading.
Site: add the missing og/og-home.png (BaseLayout referenced it but the
asset never existed -> og:image 404ed in prod) and set astro `site` so
canonical/OG URLs resolve absolutely (prerendered pages shipped
canonical=http://localhost:4321/ to production).
Cards are 1200x630, generated from the favicon.svg logo mark on a pure
white background.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Refer to demo.pcbjam.com from the landing: an accent "Live demo" link in the
header nav, an "Open the live editor" hero link, a note in the gerber section,
and a "Try the live demo now" line in the final CTA.
Rework the embedded gerber viewer (public/gerber-demo/boot.js) to source the
WASM from the deploy pipeline's versioned CDN instead of the hand-synced
assets.pcbjam.com bucket: resolve gerbview's content-addressed folder at runtime
from the release manifest (manifest-latest -> tag -> manifest-<tag> -> gerbview),
load all assets (glue + wasm + images.tar.gz) from cdn.pcbjam.com, and load the
cross-origin pthread worker via a same-origin blob importScripts shim (mirrors
web/standalone/src/wasm/boot.ts). Bump the config-seed KICAD_VERSION_DIR
9.99 -> 10.0 to match the deployed build. Drop the now-obsolete committed glue
mirror and the old assets.pcbjam.com sync/r2-deploy scripts.
Collapse the header nav to the hamburger below 1025px — the added demo link no
longer fits the 1024px-capped bar on a single row.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Embed KiCad's gerbview WASM in the graphics-to-WebGL blog post as a
click-to-load demo that opens the tiny_tapeout board entirely in-browser.
- GerberDemo.astro: lazy iframe embed + cross-origin-isolation reload guard
+ WebGL2/SharedArrayBuffer feature-detect with a poster fallback
- public/gerber-demo/{index.html,boot.js}: self-contained gerbview boot
harness (config seed, MEMFS board preload, argv auto-open)
- board/ tiny_tapeout layers + poster.png committed; wasm/ glue JS committed
(served same-origin — pthread worker can't be cross-origin), while
gerbview.wasm + kicad-resources.bin are git-ignored and served from
Cloudflare R2 (assets.pcbjam.com)
- COOP/COEP require-corp scoped to the post + /gerber-demo routes
(dev: astro.config.mjs + middleware.ts; prod: vercel.json)
- post converted to MDX (@astrojs/mdx) so it can embed the component
- scripts/: sync-demo-wasm.sh, r2-deploy.sh, r2-cors.json
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Chrome also fires an implicit request for /favicon.ico; with only an SVG
link declared and no .ico at the site root, that 404 left the tab without
an icon. Add a square multi-res .ico (48/32/16) and a 180x180
apple-touch-icon — both derived from the jar art already embedded in
favicon.svg — and declare the full icon set (ico + svg + apple-touch) in
BaseLayout.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Founder review pass on the landing page, plus commit the landing-page
research/spec docs under features/lp.
Branding & assets:
- New PCBJam mark: convert the jam-jar image to favicon.svg (trimmed, rounded
tile) and use it in the browser tab, header, and footer.
- Footer "Built by": flower mark + "Emergence Engineering" in white Oswald (the
blog's title font, self-hosted via @fontsource) so it's legible on dark.
- Produce the positioning quadrant graphic (public/graphics) and drop the placeholder.
Copy / accuracy:
- Remove header GitHub link; point "Built by EE" + footer "About" to
emergence-engineering.com; Built-by CTA -> mailto:contact@emergence-engineering.com.
- Replace the wrong testimonial with Greg Detre's real quote from EE's site.
- Drop blog icons from Multiplayer/Pillars/How-it-works; add "Coming soon" to
live-cursors & pin-comment; drop the hard-coded Yjs mention.
- Rework monetization to free+local / paid cloud-sync; remove "no metered AI",
"no per-seat toll", and the Flux price claim; soften the AI section.
- Reframe waitlist copy (incl. confirmation email) to newsletter + early access.
Fixes:
- Offset only #waitlist for the sticky header (scroll-margin-top); section
anchors keep their original behavior.
Docs:
- Commit features/lp (copy spec, brand guide, competitive research) via force-add
over the /features/ gitignore.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Standalone Astro 6 site in /site (decoupled from the /web app monorepo):
landing page, /blog with one post (Content Layer), and terms/privacy/cookie
legal pages. Pure Astro, server-rendered to static HTML.
- Static by default via @astrojs/vercel adapter; any route can opt into SSR
with `export const prerender = false` (deploys as a Vercel Function).
- SPA-style navigation with <ClientRouter />, transition:persist on the header
(no icon flash), and viewport prefetch — ~6 KB gzip JS, no React.
- Deploy on Vercel by setting Root Directory to 'site' (no vercel.json needed).
See site/README.md for dev and deploy instructions.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>