Two issues surfaced when the deployed site was loaded:
1. favicon.svg shipped from public/ with macOS mode 600 (~/owner-only).
nginx runs as the 'nginx' user inside the alpine image and returned
403 for the icon. Add a chmod sweep at the end of the build stage so
every shipped asset is 644 + dirs 755 regardless of host umask.
2. <link rel="stylesheet"> to fonts.googleapis.com is render-blocking.
When the user's network is slow to reach Google Fonts (or blocks it),
the page spins indefinitely on first paint. Switch to the canonical
rel=preload + onload swap pattern, with a <noscript> fallback for
JS-off browsers. Tailwind's font-family stack already cascades to
ui-serif / ui-monospace, so type still renders before the swap.
The docker-label router (apess-web@docker) was being silently dropped
by Traefik after subsequent reloads, even though all labels and the
container's network attach were correct. Falling back to the
ClawBooks landing's catch-all PathPrefix(`/`) meant apess.redclaw.dev
served the wrong content after the first ~5 minutes.
Switch to an explicit file-provider definition in apess.yml with
priority: 1000 (well above landing's priority: 1) so the router is
authoritative regardless of docker-discovery races. The compose file
now just runs the container; routing is owned by the dynamic config.
Closes the first two PRD screens with shadcn primitives (button, input,
card, badge) and the standing TDD-first discipline.
Landing (/) — PRD §5.1 Screen 0
- Sticky nav with workshop CTA and lecture link
- Hero with eyebrow badge, headline, sub, dual CTA + 4-stat strip
- Programme table covering all 7 day-of moves (13:00 → 19:00)
- Speaker card for Omar Sobh + RedClaw Systems
- 3-card tech stack (Rust on Cortex-M4 · Anthropic+Groq · Web Serial)
- Prerequisites + closing CTA
- 5 tests, all passing
Team Registration (/workshop) — PRD §5.1 Screen 1
- PhaseStrip across top, Phase 1 badge eyebrow
- Team name + member chips (Enter to add, x to remove, capped at 5)
- KitSelector 5x3 grid of KIT-01..KIT-15 with aria-pressed
- Mock device-connect button (real Web Serial deferred to next slice)
- ?kit= URL param pre-selects kit (QR sticker entrypoint)
- Proceed gated until name + 1+ member + device connected
- On Proceed: marks phases.reg = true, navigates to /workshop/setup
- 6 tests, all passing
MemberChips (5 tests) + KitSelector (3 tests) extracted as reusable
components with own test coverage.
Scoped react-refresh/only-export-components off for src/components/ui/**
(shadcn vendored primitives export their cva variants alongside the
component — standard project pattern).
23/23 tests green · build 286KB JS, 90KB gzip · lint clean.
Initial gw-03 build failed with pnpm 10 lockfile-policy rejection inside
the container (different policy config from host). Pinning packageManager
and falling back to --no-frozen-lockfile when the policy gate rejects
unblocks the multi-stage build without weakening lockfile integrity in
day-to-day pnpm install on the host.
Stages the production deploy bundle next to the existing ClawBooks stack
on zeroclaw-gw-03. Reuses ClawBooks' Traefik v3.6 via shared
clawbooks-net Docker network, no separate proxy.
- deploy/Dockerfile.web: multi-stage Node 22 build -> nginx 1.27 alpine
- deploy/nginx.conf: SPA history fallback, asset caching, /healthz probe
- deploy/docker-compose.yml: apess-web service with Traefik labels for
apess.redclaw.dev on the websecure entrypoint
- deploy/traefik/apess.yml: TLS dynamic config pointing at the
*.redclaw.dev Cloudflare Origin Certificate (to be installed at
/etc/ssl/redclaw/apess.{pem,key} on gw-03)
- deploy/README.md: prereqs (CF DNS + Origin Cert), deploy commands,
verification, rollback
No infra changes applied yet -- this is the artifact bundle. Deploy
pending three out-of-repo prereqs: CF DNS records, CF Origin Cert
issuance, and rsync+up on gw-03.