Skip to main content

ALAI Frontend Engineering Spec v1 — Build, Auth, Cache, i18n, CSP, CI Gate (MC #106089)

ALAI Frontend Engineering Spec v1

Document ID: ALAI-FE-ENG-001
Version: 1.0
Date: 2026-07-20
Status: Active draft for cross-product PR/CI adoption
Owner: Vizu — Brad Frost + Lea Verou route, with Securion for security review
MC: #106089
Scope: Every ALAI product frontend using Next.js/App Router or React-based web UI. Product-level docs may be stricter, but may not weaken this spec without an explicit architecture decision.

0. Source Evidence and Existing Standards Read

This spec closes a cross-product standards gap surfaced by the 2026-07-14 to 2026-07-20 frontend incident chain:

MC Incident class Standard added here
#105793 Bilko stage Entra CIAM refresh-cookie regression after hard navigation Auth/session invariants, cookie-domain checks, CI auth replay
#106006 Next build failed on invalid page export; prior CI had passed with effectively same web code next build is a required gate; dependency/lockfile drift controls
#106020 Custom-domain E2E residual refresh-cookie failure: CI-vs-local delta custom-domain E2E, test cookie isolation, env-delta evidence
#106087 Long-lived tab after deploy kept stale bundle; button did nothing until reload deploymentId/version-skew guard and chunk-load recovery
#106088 Turnstile config used invalid size value vendor-config validation and browser console gate

Existing documents read and incorporated:

  • ~/system/specs/FRONTEND-BLUEPRINT.md — Next.js 15, React 19, strict TypeScript, next-intl, state/testing baseline.
  • ~/system/specs/ALAI-UNIVERSAL-BLUEPRINT.md — cross-product invariants and security headers.
  • ~/system/specs/ungameable-testing-methodology.md — tests must interact, assert, and leave evidence.
  • ~/ALAI/products/Bilko/docs/frontend/FRONTEND-ARCHITECTURE.md — product-level i18n, error boundary, performance, environment baseline.
  • ~/ALAI/products/Bilko/docs/frontend/DESIGN-SYSTEM.md — component/visual system context.
  • ~/ALAI/products/Bilko/docs/frontend/STATE-MANAGEMENT.md and FORMS.md — current frontend state/form gaps.
  • ~/ALAI/products/Bilko/docs/frontend/ACCESSIBILITY-AUDIT.md — WCAG and interaction constraints.

1. Non-Negotiable Frontend Invariants

  1. tsc is not a build. A PR is not frontend-build-clean until the framework production build passes (next build for Next.js).
  2. HTML shell is not cacheable. Serve route HTML/app shell with Cache-Control: no-store or a product-approved equivalent that always revalidates before use.
  3. Hashed chunks are immutable. Static hashed JS/CSS/image chunks may be public, max-age=31536000, immutable only when their filename contains content hash or framework build hash.
  4. Every deployed frontend exposes a build identity. UI and API must agree on a deploy/build identifier so long-lived tabs can detect skew.
  5. Every user action has loading, success, empty, error, and retry behavior where relevant. Dead clicks are bugs, even if the API/backend is healthy.
  6. Auth is validated on the public/custom domain, not only localhost or raw cloud host. Cookies, redirects, SameSite, Secure, Domain, Path, and CORS differ by domain.
  7. No production mock data. If real data cannot be fetched, render an explicit empty/error state, not fake records.
  8. No frontend claim is verified by HTTP 200 alone. Playwright/browser evidence is required for user-facing flows.

2. Build Identity and Version-Skew Guard

2.1 Required build metadata

Each frontend build must emit a stable metadata endpoint or static asset:

{
  "product": "bilko",
  "environment": "stage",
  "gitSha": "full-or-short-sha-from-ci",
  "deploymentId": "next-build-id-or-ci-run-id",
  "builtAt": "2026-07-20T18:00:00Z"
}
  • Next.js: /build-meta.json in public/, generated in CI before next build.
  • API-backed apps: API /api/v1/health also returns compatible gitSha/deploymentId.

2.2 Client-side skew detection

Every authenticated or long-lived shell must:

  1. Read current build identity on boot.
  2. Poll or revalidate on window focus and after route changes.
  3. Detect changed deploymentId/gitSha.
  4. Show a visible toast/banner: “New version available — refresh to continue.”
  5. Provide a button that calls window.location.reload().
  6. On dynamic import or chunk load failure (ChunkLoadError, script 404), show the same refresh UX and log to Sentry.

Do not silently reload during form entry unless the product has explicit unsaved-change protection.

2.3 Acceptance tests

CI must include at least one version-skew test per product:

  • Simulate current /build-meta.json changing after page load.
  • Verify toast/banner appears.
  • Click refresh CTA and verify location.reload path is invoked or page reloads.
  • Simulate a chunk-load error where possible and verify the same recovery UX.

3. Error, Retry, Loading, Empty, and Disabled-State UX

3.1 Standard state model

Every async view/mutation implements these states explicitly:

State UI requirement
Loading skeleton or spinner with accessible label; action controls disabled when duplicate submission would be unsafe
Success visible state change, toast, navigation, or updated data row
Empty human-readable empty state with next action where applicable
Error human-readable message; no raw stack traces; includes request correlation ID when available
Retry visible retry control for transient network/server errors

3.2 Retry rules

  • GET/query failures: allow user retry and optionally one automatic retry with backoff.
  • POST/PUT/PATCH/DELETE: no blind automatic retry unless the operation is idempotent by key. Use client-generated idempotency keys for financial or document mutations.
  • Auth 401: attempt exactly one refresh/session repair before redirecting to login.
  • Validation 400/422: field-level errors; do not show generic “Something went wrong” only.
  • Rate limit 429: show wait/backoff message.

3.3 Dead-click prevention

  • URL change, modal open/close, toast, disabled/loading state, data update, or field-level error.
  • Playwright must click core CTAs and assert the outcome. Checking that a button exists is not enough.

4. MSAL / Entra / Auth Pattern

This section applies to Entra External ID / MSAL products and all products with browser-auth cookies.

4.1 Configuration invariants

  • MSAL authority, client ID, redirect URI, post-logout redirect URI, and known authorities are environment-specific and documented in .env.example.
  • Public NEXT_PUBLIC_* auth variables are passed at build time for Next.js Docker builds, matching FRONTEND-BLUEPRINT.md §3.
  • Redirect URIs must use the product public/custom domain for stage/prod E2E, not raw cloud host unless the product explicitly supports both.

4.2 Cookie/session invariants

Refresh/session cookies must be verified on the live domain:

  • Secure on HTTPS.
  • HttpOnly for refresh/session tokens not read by JS.
  • SameSite=None only when cross-site flow requires it; otherwise Lax preferred.
  • Domain must match the browser origin strategy. Custom-domain E2E must not inject cookies for only the raw cloud host.
  • Path is explicit and broad enough for session refresh routes.

4.3 Client auth flow rules

  • MSAL event handling must be single-flight: no competing login redirect/session refresh races.
  • Silent token/session repair may run once per failing navigation; after that show a real error or redirect to login.
  • Logout clears product app state, MSAL cache, in-memory access token, and server session cookie where applicable.
  • E2E auth fixtures must be isolated per test worker and per domain; no cookie poisoning from earlier tests.

4.4 Required auth E2E

For every auth-enabled product stage gate:

  1. Login on public/custom domain.
  2. Verify dashboard or protected landing renders authenticated state.
  3. Hard-navigate to at least one protected deep link.
  4. Verify user remains authenticated and is not redirected to login.
  5. Capture Set-Cookie and request cookie-domain evidence for session/refresh endpoints when debugging auth failures.
  6. Logout and verify protected route no longer renders authenticated data.

5. Cache Policy Norm

5.1 Required headers

Asset class Required cache behavior
HTML/app shell/routes Cache-Control: no-store or revalidate-equivalent approved by deploy owner
/_next/static/* hashed chunks Cache-Control: public, max-age=31536000, immutable
Public hashed assets immutable only when filename is content-hashed
build-meta.json Cache-Control: no-store
Service worker avoid by default; if used, must have a documented update strategy and tests

5.2 CI header check

Deploy verification must assert headers on the public URL:

  • HTML route has no-store/revalidate behavior.
  • At least one loaded JS chunk has immutable caching.
  • build-meta.json is not cached.
  • No localhost/LAN URL appears in user-facing output or docs for CEO/client handoff.

6. i18n Standard — next-intl

Baseline remains FRONTEND-BLUEPRINT.md §9.

Additional engineering conventions:

  1. Translation keys use namespaces by product domain: navigation.dashboard, invoices.createButton, errors.networkError.
  2. User-visible strings in JSX are banned once a product is declared multi-language.
  3. Backend returns locale-independent values: ISO dates, enum codes, numeric amounts, currency codes.
  4. Frontend formats with next-intl/Intl.NumberFormat/Intl.DateTimeFormat.
  5. Error responses use stable error codes; frontend maps codes to localized copy.
  6. CI runs a missing-key check for every supported locale.
  7. E2E covers at least default locale plus one non-English locale for navigation labels and date/amount formatting on market-critical pages.

7. CSP and Browser Security Baseline

7.1 Required security headers

At minimum on stage/prod public domains:

Content-Security-Policy: default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; script-src 'self' 'nonce-{per-request-nonce}' 'strict-dynamic'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; connect-src 'self' https:; form-action 'self'; upgrade-insecure-requests
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()

Notes:

  • Prefer nonces for inline scripts. Avoid unsafe-eval in stage/prod.
  • style-src 'unsafe-inline' may be temporarily tolerated for framework/style tooling, but products should move toward nonce/hash-based styles where feasible.
  • Third-party vendors such as Turnstile, analytics, Sentry, and fonts must be explicitly listed per product. No wildcard vendor domains without a security review.
  • CSP report-only may be used during rollout, but enforcement must be the target for beta/prod.

7.2 Vendor config validation

Any third-party widget must have a typed/config-validated wrapper. Example classes:

  • Turnstile size allowed values must be normal, compact, or flexible; invalid values fail lint/unit test.
  • Analytics/Sentry DSNs and environment names must be validated at boot and surfaced as warnings/errors in stage.

8. Test Pyramid and CI Gate

8.1 Required frontend gates

Gate Required command/class Why
Typecheck tsc --noEmit or framework equivalent Catches static TS errors only
Lint ESLint + product rules Catches exports, hooks, a11y, i18n/mocks patterns
Unit/component Vitest/React Testing Library Validates state machines and wrappers
Production build next build Required because Next validates App Router exports/build-only behavior
Browser smoke Playwright on built/deployed app Verifies user-visible runtime
Critical E2E Playwright flows by feature class Auth, forms, navigation, mutations, deep links
Console/network gate Playwright listeners Fails on app console errors and unexpected 4xx/5xx
Header/cache/CSP gate curl/Playwright response inspection Prevents cache/security regressions
Bundle/perf/a11y Lighthouse or equivalent Prevents slow/inaccessible regressions

8.2 tsc is not enough

tsc can pass while next build fails because Next.js validates route module exports, server/client boundaries, metadata rules, and framework build semantics. Therefore:

  • PR cannot be green with tsc only.
  • Main/promote cannot run unless the exact commit passed next build using the lockfile used in deploy.
  • CI must install dependencies from lockfile (npm ci, pnpm install --frozen-lockfile, or equivalent). No floating install for production build.

8.3 Browser evidence rule

Every user-facing fix must leave machine evidence:

  • screenshot or trace for the interacted flow,
  • console error log,
  • network failure summary,
  • exact public URL and commit/build ID.

HTTP 200 without DOM/action assertions is not evidence.

9. PR Review Checklist

Use this checklist in every frontend PR review. A reviewer may mark non-applicable items as N/A only with a one-line reason.

Build and dependency discipline

  • Lockfile changed only when dependency change is intentional.
  • Dependency install in CI uses frozen lockfile / npm ci.
  • tsc passes.
  • Lint passes.
  • Production framework build passes (next build).
  • No invalid App Router page/layout exports.

Runtime UX

  • Loading, empty, error, and retry states are implemented for every async area touched.
  • Buttons/links clicked in tests have observable outcomes.
  • Mutations prevent duplicate unsafe submission.
  • Error copy is human-readable and does not expose stack traces.

Auth/session

  • Auth redirect/session flow tested on public/custom domain.
  • Hard navigation to protected deep link remains authenticated after login.
  • Logout clears client and server session state.
  • Cookie attributes are correct for the deployed domain strategy.

Cache/versioning

  • Build metadata is present and no-store.
  • Version-skew detection exists for long-lived shells or is tracked as an explicit product debt.
  • HTML no-store/revalidate and chunks immutable headers verified on deployed/public URL.

i18n/a11y/security

  • No new hardcoded user-visible strings in multi-language products.
  • Locale-aware date/amount formatting used.
  • Keyboard navigation and focus states remain valid.
  • CSP/security headers are not weakened.
  • Third-party widget config uses allowed values and is tested.

Evidence

  • PR includes command output for typecheck/lint/build/tests.
  • PR includes browser evidence for user-facing changes.
  • Known browser console warnings are named; unexpected console errors fail.

10. CI Gate Definition

Minimum required gate for every ALAI product frontend PR:

# dependency discipline
npm ci || pnpm install --frozen-lockfile

# static gates
npm run typecheck
npm run lint
npm run test:unit

# framework build gate — mandatory
npm run build

# browser smoke on built artifact or deployed preview
npm run test:e2e:smoke

Additional required stage/promote gate:

# public URL verification
curl -sI "$PUBLIC_WEB_URL"
curl -s "$PUBLIC_WEB_URL/build-meta.json"
npm run test:e2e:auth-critical
npm run test:e2e:core-flows
npm run test:e2e:headers

A product may use different script names, but must map to the classes above in docs/frontend/ci-gate.md.

11. Product Adoption Requirements

Each product must add or update:

  1. docs/frontend/engineering.md — product deviations and ownership.
  2. docs/frontend/ci-gate.md — exact CI commands and public URL checks.
  3. docs/frontend/auth.md — if auth-enabled, cookie/redirect/session domain map.
  4. public/build-meta.json generation or equivalent.
  5. Playwright smoke covering navigation, one critical form/action, auth if relevant, and console/network gate.

12. Definition of Done for Frontend User-Facing Work

A frontend task is not done until all are true:

  1. Relevant code/docs changed in the correct product worktree.
  2. BUILD-BLUEPRINT.md or product blueprint was read before code edits.
  3. Typecheck, lint, tests, and production framework build pass or failures are explicitly scoped as unrelated with evidence.
  4. Browser test interacted with the affected UI and asserted outcome.
  5. Public/deployed URL verified when the task is deploy/user-facing.
  6. Evidence files exist before MC ready/done.
  7. Independent reviewer/validator checked the exact diff for M/H or risky work.

Appendix A — Reviewer Short Form

Reviewer verdict format:

Frontend Spec v1 review: PASS | PARTIAL | BLOCKED
Build gate: pass/fail + command
Browser gate: pass/fail + URL/evidence
Auth/cache/CSP impacted: yes/no
Required follow-ups before merge/promote: ...

Appendix B — Known Anti-Patterns

  • Treating tsc as a production build.
  • Header-only deploy verification (curl 200) for user-facing changes.
  • E2E tests that only check body length or URL existence.
  • Cookie injection for raw host while tests run on custom domain.
  • App Router page files exporting arbitrary constants/functions.
  • Floating package installs that let Next/framework behavior drift between CI runs.
  • Immutable caching on un-hashed HTML or build metadata.
  • Silent chunk-load failure with no refresh UX.
  • Invalid third-party widget enum values caught only by browser console.