What changed, and why nothing changes at merge
Everwhen's hosting moves to Vercel. This PR is the app half, and merging it changes nothing a player can see: playeverwhen.com keeps serving from the cluster until the DNS flip in the companion infra PR (mseeks/zo#26), which also owns the cutover ordering and the rollback plan.
For what it does, the diff is tiny. The 57-line Dockerfile goes, along with a 41-line .dockerignore and 66 lines of CI publish machinery, because the Nuxt/Nitro Vercel preset needs no in-repo config at all and Hobby fluid compute's defaults already cover this app. What arrives: an 8-line README Hosting section, a one-line Sentry config addition (plus its rewritten comment), and a doc sweep of the two files that still described the image era. After cutover a push to main deploys, and any PR gets a working preview URL. The label-gated k8s preview system this replaces was retired earlier.
One scheduling fact belongs up front. The moment this merges, the cluster's :latest image freezes (its publisher job is gone), so nothing new can ship to players until the DNS flip. Merge and cutover belong in the same sitting; mseeks/zo#26 sequences them that way.
CI: the gate stays, the shipping leaves
Two jobs are gone. publish built the SSR image on green main builds and pushed ghcr.io/mseeks/everwhen as :latest plus a :<sha>. publish-preview built a :pr-<N> image whenever a PR carried the preview label. Vercel builds from source on its own runners. The images these jobs made fed the cluster Deployment's :latest pull, and that tag freezes the moment this merges, which is the freeze window named above. check survives byte-identical: the same lint, typecheck, unit suite, build, and mocked Playwright run, on every push and every PR.
The whole remaining jobs section — check, now the end of the file.
.github/workflows/ci.yml · 49 lines
⋯ 19 lines hidden (lines 1–19)
The Sentry seam: the one real code change
Server-side Sentry used to boot from the Dockerfile CMD: node --import ./.output/server/sentry.server.config.mjs .output/server/index.mjs. There is no CMD on Vercel. The platform invokes the bundled handler directly, so without a change the server half of Sentry would silently vanish while the client half kept reporting. The Sentry module has a mode for exactly this situation: autoInjectServerSentry: "top-level-import" compiles the init into the server bundle itself.
The rewritten sentry block: source-map upload still off, server init now injected.
nuxt.config.ts · 143 lines
⋯ 118 lines hidden (lines 1–118)
⋯ 11 lines hidden (lines 133–143)
Platform fit: checked, not assumed
The claims below were verified against current Vercel and repo state rather than carried over from the static-site migrations, because this app is a different animal — SSR, streaming turns, payments, a native binary:
| surface | verdict |
|---|---|
streaming turn endpoints (SSE via createEventStream) | supported; duration includes streamed response, and Hobby fluid compute allows 300s default and max — wide margin |
| function memory | 2 GB Hobby default vs the pod's 512 Mi limit |
OG card's native @resvg/resvg-js | bundles within the 250 MB function limit; the route already falls back to the committed static card on rasterizer failure |
| Stripe webhook | URL and endpoint secret are domain-bound; the domain doesn't change, so nothing moves |
| env inventory | unchanged — the same production vars, set in the Vercel dashboard, never in-repo |
vercel.json | none needed; the Nuxt preset auto-detects |
How this was verified
The repo forbids building from submodule-worktree paths, where Nitro auto-imports silently break, so the gate runs from fresh clones on clean paths. The first gate run proved less than intended. Adversarial review noticed it had cloned before the commit existed, meaning its green results established the baseline, and only that. So the gate ran again, against the actual PR tip this time, and the build produced its own discriminating evidence: line 1 of .output/server/index.mjs reads import './sentry.server.config.mjs';. The injection is in the bundle, byte-visible. The suite passed 1458 tests there too, and CI's full check (mocked Playwright included) is green on the PR.
Runtime verification is deliberately staged in mseeks/zo#26. The Vercel deployment gets exercised on its .vercel.app URL first, including /, /play, a real streamed turn, and the OG card. DNS moves only after that passes, and the k8s namespace keeps running afterward as the instant rollback.