next/script rendered the theme script with a nonce, and browsers hide a
nonce attribute from the DOM once they have parsed it, so the server sent
nonce="" and the client read undefined. Every page load logged a
hydration mismatch.
The cookie is the fix rather than a workaround for it. The server reads
it and stamps data-theme on the html element, so the first frame is
already the right colour and there is no inline script at all.
Chasing that turned up worse. /offline sat outside [locale], which made
it a sibling of the root layout, so Next gave it a generated one: no
stylesheet, no font, no theme, and a second html element. The phase 7
check asserted the text and a button and passed while the page was
plainly broken.
It is a normal screen now. The service worker keeps one offline copy per
language the reader actually visits, learned from their own successful
navigations, so no list of locales lives in the worker and a third
language stays a catalog file.
The e2e warm-up also asks for the dynamic routes, which cost a compile of
their own and were being paid for by whichever test reached one first.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
GSAP carries the counter roll-ups, the bandeja card physics, the dialog
transitions and the three success moments FLOWS.md allows. Every one of
them checks prefers-reduced-motion first and does nothing when it is set.
boneyard and canvas-ui are not what SPEC.md's stack table says they are:
on npm the names belong to two abandoned projects that do neither job.
The skeletons were already ours; the two canvas spots are now sixty lines
each with no dependency. DECISIONS.md records the substitution.
The app installs, keeps a scan taken with no network in IndexedDB and
sends it when there is one, falls back to a page that explains itself,
and can push a deadline notice. Reading the log of what is queued is the
source of truth, so the notice clears when the capture actually lands.
The CSP now allows scripts by per-request nonce rather than by
'unsafe-inline'. That forced /offline to render per request: a
prerendered page carries a build-time nonce no live policy matches, so
its scripts were blocked and it never hydrated.
Two crashes fixed on the way. web-push throws on a VAPID subject that is
not https: or mailto:, and the code handed it APP_PUBLIC_URL, so any
machine with push keys died at boot; a misconfigured optional channel now
switches itself off and says why. And a subscription the push service
answers 410 for is deleted rather than retried forever.
Lighthouse on the production build: accessibility 100, best practices 96,
SEO 100, performance 73. The performance number is not trustworthy on
this machine and DECISIONS.md says why; total blocking time did fall from
17.6s to 1.7s once the hero canvas stopped drawing at full resolution
every frame and the landing page stopped importing GSAP.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Monorepo (pnpm workspaces) with two deployable apps and three pure packages.
apps/api (Hono on Node): Zod validated env that fails fast and names the problem,
Kysely factories for SQLite and Postgres chosen by DATABASE_URL scheme, portable
migrations covering the whole SPEC section 5 schema, Better Auth with the four
roles and seeded demo accounts, localized error envelope, /healthz and /readyz,
graceful SIGTERM drain. Dialect specific SQL is confined to the two factories.
apps/web (Next.js App Router): locale routed shell in es and en with a language
switcher, sign in screen, and a runtime /api proxy so the browser only ever sees
one origin and cookies stay first party.
packages/i18n ships both catalogs complete; es is generated from COPY.md and a
test re-derives it from the document on every run so it cannot drift.
packages/contracts holds the Zod schemas and the typed client the web app uses.
Verified: 43 vitest tests, 14 Playwright tests on mobile and desktop, typecheck
and lint clean, migrate and seed from a clean database, sign in through the proxy
with CSRF rejection of foreign origins.
Not verified here: docker compose. This user has no access to the docker socket.
RULES.md is absent from docs/, so packages/rules exports only RULES_VERSION and
no tax rule, check digit or deadline was invented. See DECISIONS.md.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>