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.mdandFORMS.md— current frontend state/form gaps.~/ALAI/products/Bilko/docs/frontend/ACCESSIBILITY-AUDIT.md— WCAG and interaction constraints.
1. Non-Negotiable Frontend Invariants
tscis not a build. A PR is not frontend-build-clean until the framework production build passes (next buildfor Next.js).- HTML shell is not cacheable. Serve route HTML/app shell with
Cache-Control: no-storeor a product-approved equivalent that always revalidates before use. - Hashed chunks are immutable. Static hashed JS/CSS/image chunks may be
public, max-age=31536000, immutableonly when their filename contains content hash or framework build hash. - Every deployed frontend exposes a build identity. UI and API must agree on a deploy/build identifier so long-lived tabs can detect skew.
- Every user action has loading, success, empty, error, and retry behavior where relevant. Dead clicks are bugs, even if the API/backend is healthy.
- 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.
- No production mock data. If real data cannot be fetched, render an explicit empty/error state, not fake records.
- 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"
}
Recommended paths:
- Next.js:
/build-meta.jsoninpublic/, generated in CI beforenext build. - API-backed apps: API
/api/v1/healthalso returns compatiblegitSha/deploymentId.
2.2 Client-side skew detection
Every authenticated or long-lived shell must:
- Read current build identity on boot.
- Poll or revalidate on window focus and after route changes.
- Detect changed
deploymentId/gitSha. - Show a visible toast/banner: “New version available — refresh to continue.”
- Provide a button that calls
window.location.reload(). - 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.jsonchanging after page load. - Verify toast/banner appears.
- Click refresh CTA and verify
location.reloadpath 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
4.2 Cookie/session invariants
Refresh/session cookies must be verified on the live domain:
Secureon HTTPS.HttpOnlyfor refresh/session tokens not read by JS.SameSite=Noneonly when cross-site flow requires it; otherwiseLaxpreferred.Domainmust match the browser origin strategy. Custom-domain E2E must not inject cookies for only the raw cloud host.Pathis 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:
- Login on public/custom domain.
- Verify dashboard or protected landing renders authenticated state.
- Hard-navigate to at least one protected deep link.
- Verify user remains authenticated and is not redirected to login.
- Capture
Set-Cookieand request cookie-domain evidence for session/refresh endpoints when debugging auth failures. - 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.jsonis 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:
- Translation keys use namespaces by product domain:
navigation.dashboard,invoices.createButton,errors.networkError. - User-visible strings in JSX are banned once a product is declared multi-language.
- Backend returns locale-independent values: ISO dates, enum codes, numeric amounts, currency codes.
- Frontend formats with
next-intl/Intl.NumberFormat/Intl.DateTimeFormat. - Error responses use stable error codes; frontend maps codes to localized copy.
- CI runs a missing-key check for every supported locale.
- 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-evalin 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
sizeallowed values must benormal,compact, orflexible; 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
tsconly. - Main/promote cannot run unless the exact commit passed
next buildusing 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. -
tscpasses. - 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:
docs/frontend/engineering.md— product deviations and ownership.docs/frontend/ci-gate.md— exact CI commands and public URL checks.docs/frontend/auth.md— if auth-enabled, cookie/redirect/session domain map.public/build-meta.jsongeneration or equivalent.- 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:
- Relevant code/docs changed in the correct product worktree.
BUILD-BLUEPRINT.mdor product blueprint was read before code edits.- Typecheck, lint, tests, and production framework build pass or failures are explicitly scoped as unrelated with evidence.
- Browser test interacted with the affected UI and asserted outcome.
- Public/deployed URL verified when the task is deploy/user-facing.
- Evidence files exist before MC ready/done.
- 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
tscas 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.