feat(web): SvelteKit PWA shell with login
Phase 0 scaffolding for the frontend: SvelteKit + adapter-static in SPA mode
(fallback index.html, ssr disabled in the root layout — no Node process in
production, Caddy serves build/ directly per docs/PLAN.md), a login page and
auth store backed by /api/v1/auth/{login,me,logout}, an installable-PWA shell
(hand-written manifest, iOS meta/safe-area handling, an install-onboarding
banner), and a Dockerfile whose only job is to produce a buildable /app/build
artifact for deploy/ to consume.
Service worker notes, since the wiring isn't obvious from the diff:
- injectManifest, not generateSW: the caching policy needs to say "never
cache /api/*", which generateSW's declarative config can't express as
precisely as hand-written Workbox routes can.
- Uses the base `vite-plugin-pwa` plugin, not `@vite-pwa/sveltekit`'s
SvelteKit-specific wrapper. That wrapper's injectManifest build expects
SvelteKit's own built-in src/service-worker.{js,ts} convention to have
already transpiled the file — but that native build only permits importing
SvelteKit's own three virtual modules and hard-rejects `workbox-*` imports,
which our SW needs. The base plugin bundles src/service-worker.ts directly
instead, which works. SvelteKit's native service-worker convention is
explicitly disabled (`kit.files.serviceWorker` pointed at a path that
doesn't exist) so the two builds can't collide and silently clobber each
other's output — they do, if both are left enabled, and the failure mode is
silent (the SW builds fine, just precaches nothing).
- workbox-core/precaching/routing/strategies had to be added as direct
devDependencies even though workbox-build depends on them — pnpm doesn't
hoist transitive deps into the top-level node_modules, so the SW bundle
step couldn't resolve them otherwise.
- The SPA fallback index.html doesn't exist yet at service-worker-build time
(adapter-static writes it after all Vite plugins finish), so it can't be
glob-hashed into the precache manifest normally. It gets a synthetic
manifest entry instead, revisioned by a per-build-invocation timestamp
(see the swIndexRevision comment in vite.config.ts) so the cached shell
still invalidates correctly on every deploy.
Full rationale for each choice is inline as comments in vite.config.ts and
apps/web/README.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
# This image's ONLY purpose is to produce /app/build — the static SPA output
|
||||
# (HTML/CSS/JS/manifest/service worker). There is no Node runtime in
|
||||
# production: per docs/PLAN.md's "Stack" and "The PWA decision", Caddy serves
|
||||
# these files directly and proxies /api/* to the FastAPI backend. This image
|
||||
# is never run as a long-lived container in production.
|
||||
#
|
||||
# Build context: this directory (apps/web), e.g. `docker build -f
|
||||
# apps/web/Dockerfile apps/web`.
|
||||
#
|
||||
# How deploy/ is expected to consume this: build this image, then copy its
|
||||
# /app/build contents out into a location the `caddy` service in
|
||||
# deploy/docker-compose.yml bind-mounts — either via a multi-stage
|
||||
# `COPY --from=bennybergle/velodrome-web:<tag> /app/build /srv/web` in the
|
||||
# Caddy image build, or with a one-shot `docker create` + `docker cp` /
|
||||
# `docker run --rm -v ...` step in the deploy pipeline that dumps /app/build
|
||||
# into a named volume or bind-mounted host directory before `caddy` starts.
|
||||
# Picked this over a scratch/busybox "artifact-holder" final stage because a
|
||||
# single builder stage is simpler to reason about and there's nothing here
|
||||
# that needs to run — the consumer only ever needs the files, not a container.
|
||||
# The deploy/ agent may adjust this to whatever's simplest for the compose
|
||||
# setup; this is just the contract (build → /app/build).
|
||||
|
||||
FROM node:22-slim AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies first, isolated from source changes, for layer caching.
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
RUN corepack enable && corepack prepare pnpm@9 --activate \
|
||||
&& pnpm install --frozen-lockfile
|
||||
|
||||
COPY . .
|
||||
RUN pnpm run build
|
||||
|
||||
# Nothing further: /app/build is the artifact. No CMD/ENTRYPOINT — this image
|
||||
# is not meant to be run.
|
||||
Reference in New Issue
Block a user