t-convex-nextjs-saas/.sisyphus/plans/app-shell-and-route-fallbacks.md
nxtkofi d41d4687ee feat(legal): add GDPR-compliant cookie consent banner
Add CookieBanner component with useCookieConsent hook, translations in EN/PL, and integration into root layout

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-02 16:02:13 +02:00

373 lines
27 KiB
Markdown

# App Shell and Route Fallbacks
## TL;DR
> **Summary**: Add a minimal locale-aware application shell with auth-aware navigation, then add deterministic not-found, loading, and route-error UX for the App Router without expanding into broader product UI.
> **Deliverables**:
> - Shared shell in `src/app/[locale]/layout.tsx`
> - Header-safe theme switcher and auth-aware nav actions
> - Localized 404 handling plus root 404 fallback
> - Locale-scoped `loading.tsx` and `error.tsx`
> - Stable selectors and deterministic QA hooks for nav/fallback states
> - EN/PL translations for new shell and fallback copy
> **Effort**: Medium
> **Parallel**: YES - 2 waves
> **Critical Path**: 1 → 2 → 3 → 4/5/6
## Context
### Original Request
Plan implementation of items 1 and 2 from the previously proposed quick wins: error boundaries + loading states, and not-found page + global layout navigation.
### Interview Summary
- Scope is intentionally limited to shared shell/navigation and App Router fallbacks.
- Stripe, uploads, admin, and other template upgrades are explicitly out of scope for this slice.
- Locale routing already exists and must be preserved.
### Metis Review (gaps addressed)
- Avoid root-layout auth checks in the shared layout because they would force unnecessary dynamic rendering and conflict with existing page-level protection.
- Do not rely on `[locale]/not-found.tsx` alone for unknown localized URLs; add an explicit catch-all route.
- Do not assume `error.tsx` handles root-layout/provider failures; keep this slice scoped to locale-route runtime errors only.
- Make loading and error QA deterministic with stable selectors and an internal non-linked route error trigger.
## Work Objectives
### Core Objective
Provide a minimal, auth-aware app shell across localized routes and add deterministic localized UX for loading, 404, and route-segment runtime errors.
### Deliverables
- Shared shell wrapper in `src/app/[locale]/layout.tsx`
- New shell components under `src/components/core/`
- Refactored `ThemeChanger` suitable for header usage
- Localized route copy in `messages/en.json` and `messages/pl.json`
- `src/app/[locale]/not-found.tsx`
- `src/app/[locale]/[...rest]/page.tsx`
- `src/app/not-found.tsx`
- `src/app/[locale]/loading.tsx`
- `src/app/[locale]/error.tsx`
- Internal non-linked QA trigger routes for loading and route-error validation
### Definition of Done (verifiable conditions with commands)
- `pnpm lint` completes successfully
- `pnpm build` completes successfully
- Anonymous users see shell navigation with home/sign-in/sign-up actions and do not see dashboard/settings/sign-out controls
- Authenticated users see shell navigation with dashboard/settings/sign-out controls and do not see sign-in/sign-up controls
- Navigating to `/en/does-not-exist` renders the localized 404 UI
- Navigating to a non-localized unmatched URL renders the root 404 UI
- Navigating between locale routes can surface a deterministic loading fallback with `[data-testid="route-loading"]`
- Triggering the internal QA error route renders the locale error boundary with a retry control
### Must Have
- Shell lives at `[locale]` scope, not root scope
- Auth-awareness in nav is client-hydrated via Better Auth session hook
- Existing page-level redirect guards remain untouched as the only access-control source of truth
- Theme switcher remains available from the shared header
- All shell/fallback copy is translated in both locales
- Stable selectors/test IDs exist for nav, auth action groups, loading, 404, and retry controls
### Must NOT Have (guardrails, AI slop patterns, scope boundaries)
- No avatar dropdown, profile menu, locale switcher, breadcrumbs, sidebar, or mobile drawer in this slice
- No root-layout auth gating with `isAuthenticated()` in `src/app/[locale]/layout.tsx`
- No `app/global-error.tsx` or provider-failure handling in this slice
- No page-specific skeleton systems for dashboard/settings/auth pages
- No new test framework setup
- No hard-coded paths when route constants already exist
## Verification Strategy
> ZERO HUMAN INTERVENTION - all verification is agent-executed.
- Test decision: tests-after using agent-executed browser QA plus lint/build
- QA policy: every task includes explicit selectors, routes, and expected visibility states
- Evidence: `.sisyphus/evidence/task-{N}-{slug}.{ext}`
## Execution Strategy
### Parallel Execution Waves
> Target: 5-8 tasks per wave. <3 per wave (except final) = under-splitting.
> Extract shared dependencies as Wave-1 tasks for max parallelism.
Wave 1: 1) translation contract and selectors, 2) shell primitives + theme control, 3) auth-aware shell integration
Wave 2: 4) localized/root 404 handling, 5) route loading fallback, 6) locale error boundary + QA trigger
### Dependency Matrix (full, all tasks)
- 1 blocks 2, 3, 4, 5, 6
- 2 blocks 3, 4, 5, 6
- 3 blocks 4, 5, 6 because shell selectors and layout structure become QA baseline
- 4, 5, and 6 can proceed in parallel after 1-3
- 6 blocks final verification wave because retry/error QA depends on its internal trigger route
### Agent Dispatch Summary (wave → task count → categories)
- Wave 1 → 3 tasks → quick, visual-engineering, unspecified-high
- Wave 2 → 3 tasks → unspecified-high, quick, unspecified-high
## TODOs
> Implementation + Test = ONE task. Never separate.
> EVERY task MUST have: Agent Profile + Parallelization + QA Scenarios.
- [x] 1. Establish shell and fallback translation contract
**What to do**: Add new translation namespaces for shell navigation and fallback UX in both locale files before touching UI. Define exact labels for brand/home, dashboard, settings, sign-in, sign-up, sign-out, loading title/description, localized 404 title/description/actions, and route-error title/description/retry. Add a small selector contract in the plan implementation itself by reserving stable `data-testid` names that later tasks must use verbatim: `app-shell`, `app-nav`, `nav-public-links`, `nav-private-links`, `nav-auth-actions`, `theme-switcher`, `route-loading`, `localized-not-found`, `root-not-found`, `route-error`, `route-error-retry`, `sign-out-button`.
**Must NOT do**: Do not create a third locale. Do not move existing namespaces. Do not hardcode copy directly into components.
**Recommended Agent Profile**:
- Category: `quick` - Reason: bounded i18n and selector contract work across two JSON files
- Skills: `[]` - Existing repo patterns are sufficient
- Omitted: `writing` - This is product-copy extension, not long-form documentation
**Parallelization**: Can Parallel: YES | Wave 1 | Blocks: 2, 3, 4, 5, 6 | Blocked By: none
**References** (executor has NO interview context - be exhaustive):
- Translation pattern: `messages/en.json:1-58` - existing namespace structure to extend instead of flattening
- Translation mirror: `messages/pl.json` - keep key parity with `messages/en.json`
- Existing auth copy: `src/components/auth/AuthForm.tsx:62-225` - current `AuthPage` key usage pattern
- Existing route labels: `src/app/[locale]/dashboard/page.tsx:26-40` - server-side translation usage for page copy
**Acceptance Criteria** (agent-executable only):
- [ ] `messages/en.json` and `messages/pl.json` contain matching shell and fallback namespaces/keys
- [ ] Reserved `data-testid` names are documented in code comments-free implementation choices and used consistently in later tasks
- [ ] `pnpm build` succeeds after translation additions
**QA Scenarios** (MANDATORY - task incomplete without these):
```
Scenario: Translation key parity check
Tool: Bash
Steps: Run a Node script that loads `messages/en.json` and `messages/pl.json`, extracts the new shell/fallback namespaces, and compares key sets recursively.
Expected: Script exits 0 and prints no missing-key differences.
Evidence: .sisyphus/evidence/task-1-translation-parity.txt
Scenario: Existing auth copy not regressed
Tool: Playwright
Steps: Start app; open `/en/sign-in`; assert the sign-in title and forgot-password link still render.
Expected: Existing auth form renders with non-empty text and no raw i18n keys.
Evidence: .sisyphus/evidence/task-1-auth-copy-regression.png
```
**Commit**: YES | Message: `feat(i18n): add shell and fallback copy` | Files: `messages/en.json`, `messages/pl.json`
- [x] 2. Build shared shell primitives and refactor ThemeChanger for header use
**What to do**: Introduce the minimal shell primitives under `src/components/core/` needed for the shared layout: a server-friendly shell wrapper (`AppShell`), a presentational nav/header component (`AppNav`), and a compact theme control by refactoring `ThemeChanger` into a header-sized control that still uses `@wrksz/themes`. Keep the shell visual language aligned with existing card/button primitives: simple top border/header, constrained content width, no dropdowns, no drawer, no avatar. Ensure every interactive control exposes the reserved `data-testid` values from Task 1.
**Must NOT do**: Do not fetch auth state here. Do not add mobile menu logic, locale switcher, breadcrumbs, or profile UI. Do not leave the old verbose “The current theme is...” wording in the header.
**Recommended Agent Profile**:
- Category: `visual-engineering` - Reason: shared shell UX and header ergonomics must feel intentional, not placeholder
- Skills: `[]` - Existing UI primitives cover the work
- Omitted: `frontend-ui-ux` - The UI is deliberately minimal and tightly anchored to repo primitives
**Parallelization**: Can Parallel: NO | Wave 1 | Blocks: 3, 4, 5, 6 | Blocked By: 1
**References** (executor has NO interview context - be exhaustive):
- Root provider context: `src/app/layout.tsx:26-38` - shell components will render under these providers
- Locale insertion point: `src/app/[locale]/layout.tsx:10-25` - current wrapper that later task will extend
- Theme control baseline: `src/components/core/ThemeChanger.tsx:5-28` - refactor this component instead of creating duplicate theme logic
- Button styling: `src/components/ui/button.tsx:7-67` - use existing variants/sizes
- Card styling baseline: `src/components/ui/card.tsx:5-103` - follow existing spacing, radius, and muted sections if cardized sub-sections are needed
**Acceptance Criteria** (agent-executable only):
- [ ] New shell primitives exist under `src/components/core/` and compile without auth coupling
- [ ] `ThemeChanger` becomes header-compatible and no longer renders debug-like explanatory text
- [ ] Shell primitives expose stable selectors: `app-shell`, `app-nav`, and `theme-switcher`
- [ ] `pnpm lint` passes after component creation/refactor
**QA Scenarios** (MANDATORY - task incomplete without these):
```
Scenario: Header primitives render on a locale route
Tool: Playwright
Steps: Open `/en`; inspect the DOM for `[data-testid="app-shell"]`, `[data-testid="app-nav"]`, and `[data-testid="theme-switcher"]`.
Expected: All three selectors exist exactly once and are visible in the viewport.
Evidence: .sisyphus/evidence/task-2-shell-primitives.png
Scenario: Theme control is compact and interactive
Tool: Playwright
Steps: On `/en`, interact with the header theme control and toggle from the default theme to dark.
Expected: The control remains in the header, toggles theme successfully, and does not render the old debug sentence.
Evidence: .sisyphus/evidence/task-2-theme-toggle.png
```
**Commit**: YES | Message: `feat(shell): add shared app shell primitives` | Files: `src/components/core/AppShell.tsx`, `src/components/core/AppNav.tsx`, `src/components/core/ThemeChanger.tsx`, any minimal supporting files
- [x] 3. Integrate an auth-aware shell into the locale layout without changing route protection semantics
**What to do**: Extend `src/app/[locale]/layout.tsx` to render the shared shell around all locale routes. Keep locale validation and `setRequestLocale(locale)` exactly as the first logic in the layout. Implement auth-aware nav actions in a client component (for example `AuthNavActions`) that uses `authClient.useSession()` and `authClient.signOut(...)` with a success redirect to `routes.public.home`. Show home plus auth links (`sign-in`, `sign-up`) when unauthenticated; show home plus private links (`dashboard`, `settings`) and a sign-out button when authenticated. Keep access control in the pages themselves; the shell only changes visibility of actions.
**Must NOT do**: Do not call `isAuthenticated()` in `[locale]/layout.tsx`. Do not redirect from the shell. Do not hide the shell from public auth pages. Do not hardcode `/sign-in`, `/dashboard`, or `/settings`.
**Recommended Agent Profile**:
- Category: `unspecified-high` - Reason: mixed server/client composition with auth session hydration and layout integration
- Skills: `[]` - Existing auth client and layout patterns are sufficient
- Omitted: `playwright` - QA uses Playwright, implementation should stay focused on code
**Parallelization**: Can Parallel: NO | Wave 1 | Blocks: 4, 5, 6 | Blocked By: 1, 2
**References** (executor has NO interview context - be exhaustive):
- Locale layout anchor: `src/app/[locale]/layout.tsx:10-25` - preserve locale guard and wrap children with shell
- Route contract: `src/lib/routes.ts:1-14` - use these constants for every nav link and sign-out redirect target
- Client auth client: `src/lib/auth-client.ts:1-6` - Better Auth client instance for `useSession()` and `signOut()`
- Better Auth sign-out pattern: `https://github.com/better-auth/better-auth/blob/main/docs/content/docs/authentication/email-password.mdx` - `authClient.signOut({ fetchOptions: { onSuccess: ... } })`
- Existing protected-route semantics: `src/app/[locale]/dashboard/page.tsx:16-24` - keep these page-level guards intact
- Existing auth link styling: `src/components/auth/AuthForm.tsx:109-220` - link/button patterns already used in auth UI
**Acceptance Criteria** (agent-executable only):
- [ ] `[locale]/layout.tsx` renders the shell around all locale routes while preserving locale validation and `setRequestLocale(locale)`
- [ ] Anonymous state shows `[data-testid="nav-public-links"]` and hides `[data-testid="nav-private-links"]` and `[data-testid="sign-out-button"]`
- [ ] Authenticated state shows `[data-testid="nav-private-links"]` and `[data-testid="sign-out-button"]` and hides public auth links
- [ ] Clicking sign-out returns the user to `routes.public.home` and removes private nav actions
**QA Scenarios** (MANDATORY - task incomplete without these):
```
Scenario: Anonymous shell state on public route
Tool: Playwright
Steps: Open a fresh browser context; visit `/en/sign-in`; assert `[data-testid="app-nav"]` is visible; inspect public/private action groups.
Expected: `[data-testid="nav-public-links"]` is visible with sign-in/sign-up links; `[data-testid="nav-private-links"]` and `[data-testid="sign-out-button"]` are absent.
Evidence: .sisyphus/evidence/task-3-anonymous-shell.png
Scenario: Authenticated shell state and sign-out
Tool: Playwright
Steps: Create a fresh QA user via `/en/sign-up` (for example `shell-nav@example.com` / `TemplatePass123!`); after auto-sign-in lands on the authenticated flow, visit `/en/dashboard`; assert dashboard/settings/sign-out controls; click `[data-testid="sign-out-button"]`.
Expected: User lands on `/en` or `/`; private controls disappear; sign-in/sign-up controls are visible again.
Evidence: .sisyphus/evidence/task-3-authenticated-shell.png
```
**Commit**: YES | Message: `feat(shell): integrate auth-aware nav into locale layout` | Files: `src/app/[locale]/layout.tsx`, `src/components/core/AuthNavActions.tsx`, related shell imports
- [x] 4. Add localized and root not-found handling with explicit catch-all coverage
**What to do**: Add three coordinated pieces: `src/app/[locale]/not-found.tsx` for localized `notFound()` rendering inside the locale segment, `src/app/[locale]/[...rest]/page.tsx` that immediately calls `notFound()` to catch unknown localized URLs such as `/en/unknown`, and `src/app/not-found.tsx` as a root fallback for unmatched non-localized requests that bypass locale routing. The localized 404 must render inside the shared shell and use localized copy. The root 404 must be intentionally minimal, must not depend on locale context, and must not attempt to render the locale shell.
**Must NOT do**: Do not enable experimental `global-not-found`. Do not rely on `[locale]/not-found.tsx` alone for unmatched localized URLs. Do not duplicate the full shell in `src/app/not-found.tsx`.
**Recommended Agent Profile**:
- Category: `unspecified-high` - Reason: Next.js file-convention work across locale and root routing layers
- Skills: `[]` - Existing routing patterns plus docs references are enough
- Omitted: `ultrabrain` - this is nuanced but not research-heavy anymore
**Parallelization**: Can Parallel: YES | Wave 2 | Blocks: Final verification wave | Blocked By: 1, 2, 3
**References** (executor has NO interview context - be exhaustive):
- Locale layout invalid-locale behavior: `src/app/[locale]/layout.tsx:17-23` - invalid locales already call `notFound()`
- Route constants: `src/lib/routes.ts:1-14` - use `routes.public.home` for return-home actions where locale context exists
- Next.js not-found docs: `https://nextjs.org/docs/app/api-reference/file-conventions/not-found` - root `app/not-found.tsx` handles global unmatched URLs
- next-intl error-files guide: `https://next-intl.dev/docs/environments/error-files` - localized 404 requires `[locale]/not-found.tsx` plus `[locale]/[...rest]/page.tsx`
- Existing card style: `src/components/ui/card.tsx:23-103` - use for localized 404 presentation
**Acceptance Criteria** (agent-executable only):
- [ ] `/en/does-not-exist` resolves via `[locale]/[...rest]/page.tsx` into the localized 404 UI
- [ ] Invalid locale handling from `[locale]/layout.tsx` still resolves to a 404 outcome instead of crashing
- [ ] A non-localized unmatched request resolves to `src/app/not-found.tsx`
- [ ] Localized 404 shows shell/nav; root 404 does not depend on locale shell
**QA Scenarios** (MANDATORY - task incomplete without these):
```
Scenario: Unknown localized route shows localized 404 inside shell
Tool: Playwright
Steps: Visit `/en/does-not-exist`; inspect `[data-testid="localized-not-found"]` and `[data-testid="app-nav"]`.
Expected: Localized 404 card is visible; shell navigation is still present; return-home action links to localized home.
Evidence: .sisyphus/evidence/task-4-localized-not-found.png
Scenario: Non-localized unmatched route shows root 404 fallback
Tool: Playwright
Steps: Visit `/totally-missing-route`; inspect `[data-testid="root-not-found"]`.
Expected: Root 404 UI is visible; localized shell selector `[data-testid="app-shell"]` is absent.
Evidence: .sisyphus/evidence/task-4-root-not-found.png
```
**Commit**: YES | Message: `feat(routing): add localized and root not-found handling` | Files: `src/app/[locale]/not-found.tsx`, `src/app/[locale]/[...rest]/page.tsx`, `src/app/not-found.tsx`
- [x] 5. Add a deterministic locale route loading fallback
**What to do**: Add `src/app/[locale]/loading.tsx` as the single shared loading fallback for localized routes. The loading UI must be lightweight, shell-compatible, and selector-stable via `[data-testid="route-loading"]`. Use the existing `Spinner` and current visual primitives. Do not fetch runtime data in the loading file. Ensure the loading UI is designed to render while the shell remains interactive, which means the shell itself must not perform blocking auth work. To make QA deterministic, also add one internal non-linked slow route such as `src/app/[locale]/__qa/slow/page.tsx` that intentionally awaits ~1500ms before rendering success content.
**Must NOT do**: Do not add per-page skeleton files in this slice. Do not put auth/session fetching into `[locale]/layout.tsx`. Do not use vague text-only fallback without the stable selector. Do not expose the slow QA route in navigation or docs user flows.
**Recommended Agent Profile**:
- Category: `quick` - Reason: single-file route fallback plus selector discipline
- Skills: `[]` - Existing spinner/card primitives are enough
- Omitted: `visual-engineering` - this should stay intentionally simple
**Parallelization**: Can Parallel: YES | Wave 2 | Blocks: Final verification wave | Blocked By: 1, 2, 3
**References** (executor has NO interview context - be exhaustive):
- Locale layout caveat: `https://nextjs.org/docs/app/api-reference/file-conventions/loading` - loading fallback will not cover blocking runtime data in the same layout
- Spinner primitive: `src/components/ui/spinner.tsx:5-12` - reuse existing spinner icon and status semantics
- Existing shell insertion: `src/app/[locale]/layout.tsx:10-25` - loading will render beneath this layout
- Existing page spacing: `src/app/[locale]/dashboard/page.tsx:28-41` - use similar content width and spacing rhythm
**Acceptance Criteria** (agent-executable only):
- [ ] `src/app/[locale]/loading.tsx` exists and renders a visible fallback with `[data-testid="route-loading"]`
- [ ] The fallback can render while the shell remains mounted
- [ ] Internal slow QA route deterministically triggers the loading fallback
- [ ] `pnpm build` succeeds with the new loading file in place
**QA Scenarios** (MANDATORY - task incomplete without these):
```
Scenario: Loading fallback appears during deterministic slow route
Tool: Playwright
Steps: Visit `/en/__qa/slow`; immediately inspect the page before the delayed route resolves.
Expected: `[data-testid="route-loading"]` becomes visible before the success content appears, while `[data-testid="app-nav"]` remains visible.
Evidence: .sisyphus/evidence/task-5-route-loading.png
Scenario: Loading fallback does not replace the shell chrome
Tool: Playwright
Steps: Repeat `/en/__qa/slow` navigation and capture the header region plus loading region simultaneously.
Expected: Header navigation remains mounted; only the route content area swaps to the loading fallback.
Evidence: .sisyphus/evidence/task-5-shell-persistence.png
```
**Commit**: YES | Message: `feat(ux): add locale route loading fallback` | Files: `src/app/[locale]/loading.tsx`, `src/app/[locale]/__qa/slow/page.tsx`
- [x] 6. Add a localized route error boundary with deterministic retry QA
**What to do**: Add `src/app/[locale]/error.tsx` as a client component that uses localized copy, surfaces a stable `[data-testid="route-error"]` wrapper, and exposes a retry control `[data-testid="route-error-retry"]` wired to `unstable_retry()`. To make QA deterministic without adding a full test framework, add one internal non-linked route such as `src/app/[locale]/__qa/route-error/page.tsx` that throws a controlled runtime error for boundary validation. The QA route should continue throwing on retry so the expected post-click state is deterministic. Keep it excluded from navigation and docs user flows; it exists only so agents can validate the boundary reliably.
**Must NOT do**: Do not add `app/global-error.tsx`. Do not leak raw server error details into user-facing copy. Do not put the QA route in nav or route constants.
**Recommended Agent Profile**:
- Category: `unspecified-high` - Reason: file-convention error boundary plus deterministic QA mechanism
- Skills: `[]` - Existing i18n and UI patterns are enough
- Omitted: `writing` - localized copy already comes from Task 1
**Parallelization**: Can Parallel: YES | Wave 2 | Blocks: Final verification wave | Blocked By: 1, 2, 3
**References** (executor has NO interview context - be exhaustive):
- Next.js error docs: `https://nextjs.org/docs/app/api-reference/file-conventions/error` - `error.tsx` must be a client component and should use `unstable_retry()` in v16.2+
- next-intl error-files guide: `https://next-intl.dev/docs/environments/error-files` - error boundary can use translated messages under existing provider context
- Root provider context: `src/app/layout.tsx:32-38` - translated client error boundary can rely on intl provider because scope is `[locale]`, not root
- Existing toast pattern: `src/components/auth/AuthForm.tsx:78-106` - follow repo style for non-blocking error handling if logging/toasts are added
- Button primitive: `src/components/ui/button.tsx:44-64` - use for retry CTA
**Acceptance Criteria** (agent-executable only):
- [ ] `src/app/[locale]/error.tsx` exists, is a client component, and calls `unstable_retry()` from its retry control
- [ ] Error UI is localized and does not expose raw server stack details
- [ ] Internal QA route throws and renders the route error boundary reliably
- [ ] Retry control is selector-stable via `[data-testid="route-error-retry"]`
**QA Scenarios** (MANDATORY - task incomplete without these):
```
Scenario: Controlled route error renders localized boundary
Tool: Playwright
Steps: Visit `/en/__qa/route-error`; inspect `[data-testid="route-error"]` and retry control.
Expected: Localized route error UI is visible; the page does not hard-crash into a blank screen; retry control is present.
Evidence: .sisyphus/evidence/task-6-route-error.png
Scenario: Retry re-renders the failing boundary deterministically
Tool: Playwright
Steps: On `/en/__qa/route-error`, click `[data-testid="route-error-retry"]` once.
Expected: Retry executes without a white-screen crash and the same localized error fallback re-renders cleanly because the QA route is intentionally still failing.
Evidence: .sisyphus/evidence/task-6-route-error-retry.png
```
**Commit**: YES | Message: `feat(ux): add locale error boundary and retry flow` | Files: `src/app/[locale]/error.tsx`, `src/app/[locale]/__qa/route-error/page.tsx`, any minimal supporting component
## Final Verification Wave (MANDATORY — after ALL implementation tasks)
> 4 review agents run in PARALLEL. ALL must APPROVE. Present consolidated results to user and get explicit "okay" before completing.
> **Do NOT auto-proceed after verification. Wait for user's explicit approval before marking work complete.**
> **Never mark F1-F4 as checked before getting user's okay.** Rejection or user feedback -> fix -> re-run -> present again -> wait for okay.
- [x] F1. Plan Compliance Audit — oracle
- [x] F2. Code Quality Review — unspecified-high
- [x] F3. Real Manual QA — unspecified-high (+ playwright if UI)
- [x] F4. Scope Fidelity Check — deep
## Commit Strategy
- Commit 1: `feat(i18n): add shell and fallback copy`
- Commit 2: `feat(shell): add shared app shell primitives`
- Commit 3: `feat(shell): integrate auth-aware nav into locale layout`
- Commit 4: `feat(routing): add localized and root not-found handling`
- Commit 5: `feat(ux): add locale route loading fallback`
- Commit 6: `feat(ux): add locale error boundary and retry flow`
## Success Criteria
- The template gains one consistent shell across localized routes without changing access-control semantics
- Anonymous and authenticated navigation states are deterministic and testable
- Unknown localized URLs and non-localized unmatched URLs both resolve to intentional 404 experiences
- Route loading and runtime error states are intentionally designed, translated, and recoverable