=== Performance Gate Report ===
Generated: 2026-09-08
Ticket: #3273 — Forgot Password Feature Implementation
Gate: W14b — Performance (CSS / Render-Hydration / Animation)
Audited files:
  app/(auth)/forgot-password/page.tsx
  app/(auth)/forgot-password/verify/page.tsx
  app/(auth)/forgot-password/reset/page.tsx
  components/auth/ForgotPasswordForm.tsx
  components/auth/VerifyOtpForm.tsx
  components/auth/ResetPasswordForm.tsx
  components/auth/SignInForm.tsx

Skills invoked: /nextjs-tailwind-css-performance-audit, /nextjs-tailwind-performance-audit,
  /nextjs-tailwind-animation-performance-audit (skills unavailable in this environment;
  manual static audit performed against the real source files below)

Bundle baseline (from bundle-analyze-W14a.txt, produced by bundle-analyze-gate):
  /forgot-password         1.26 kB route,  109 kB First Load JS
  /forgot-password/verify  1.97 kB route,  110 kB First Load JS
  /forgot-password/reset   2.37 kB route,  101 kB First Load JS
  /login (SignInForm)      3.36 kB route,  108 kB First Load JS
  Build status: PASS (Next.js 14.2.35)

════════════════════════════════════════════════════════
AUDIT 1: CSS PERFORMANCE
════════════════════════════════════════════════════════

MEASURED:
1. All three auth form components use `fixed inset-0 z-[100] flex overflow-hidden bg-surface`
   on their outer div. The `z-[100]` is an arbitrary Tailwind value that compiles to a unique
   atomic class not shared elsewhere. Each auth route creates a full-viewport fixed stacking
   context. This is intentional (full-page form overlay) and correct for the design, but
   produces three near-identical CSS rule sets (one per component). No layout thrash risk —
   `fixed` elements are out of normal flow and do not affect sibling layout.

2. VerifyOtpForm applies two long arbitrary shadow values to each of 6 OTP inputs:
     shadow-[inset_0_0_0_1px_theme(colors.border.field)]       (default state)
     shadow-[inset_0_0_0_2px_theme(colors.accent.DEFAULT)]     (focus state)
     shadow-[inset_0_0_0_1px_theme(colors.status.danger)]      (error state)
     shadow-[inset_0_0_0_2px_theme(colors.status.danger)]      (error+focus)
   These four arbitrary shadow classes are unique to this component and will each generate
   a distinct CSS rule. Box-shadow changes trigger GPU-composited repaint only (no layout);
   acceptable for 6 small inputs. Total CSS bloat: 4 unique atomic rules, ~400 bytes.

3. ResetPasswordForm uses `bg-[rgb(230,230,230)]` as the "empty" bar colour on the strength
   meter — a hardcoded RGB value not mapped to any design token in tailwind.config.ts.
   Not a runtime performance concern, but this colour will not respond to theme changes.

4. BrandPanel (components/auth/BrandPanel.tsx) uses `Array.from({ length: 7 })` to render
   decorative dots. Since BrandPanel has no "use client" directive and is purely static
   markup, the array is evaluated server-side at render time and emitted as 7 static <div>
   elements. Zero client-side cost.

5. .field-input utility class (globals.css) is applied to the username input in SignInForm.
   It is a Tailwind @layer components rule that composes ~8 atomic utilities. No performance
   concern — it's already in the shared CSS chunk.

INFERRED (not measured):
6. BrandPanel is imported by all three Client Component forms. Because the (auth) layout
   (app/(auth)/layout.tsx) renders only `<>{children}</>` with no shared markup, BrandPanel
   is bundled into all three Client Component trees. Webpack's chunk-splitting may deduplicate
   it into the shared fd9d1056 chunk (168.79 kB "Shared UI components" in bundle report) but
   this cannot be confirmed without per-component bundle analysis. If BrandPanel is NOT
   deduplicated, its ~50-line SVG markup is bundled 3× client-side. Moving BrandPanel to the
   auth layout and extracting the form-column as the only Client Component would guarantee
   deduplication and reduce First Load JS. (Inferred from code structure — not measured.)

════════════════════════════════════════════════════════
AUDIT 2: RENDER / HYDRATION PERFORMANCE
════════════════════════════════════════════════════════

MEASURED:
1. VerifyOtpForm mounts a 30-second countdown timer via useEffect + setTimeout. Each tick
   calls `setCountdown((c) => c - 1)`, which schedules a state update that causes the
   entire VerifyOtpForm component to re-render. Over the full countdown, this is
   approximately 30 re-renders triggered by the timer alone (one per second). Each re-render
   recomputes:
     - `otp = digits.join("")` (6-element join)
     - `otpError`, `sessionError` (two ternary reads)
     - clsx calls on 6 OTP input elements (12 class-string evaluations)
   BrandPanel is stable (no props, no hooks) so React's bailout prevents re-rendering it.
   The cost is real but minor — all operations are O(1) or O(6). No layout thrash.
   RECOMMENDATION: Extract the countdown display + resend button into a `<ResendSection>`
   sub-component with its own state. This confines the 30 timer re-renders to that small
   sub-tree, shielding the OTP inputs from being re-evaluated each tick.

2. All three page shells (page.tsx files) are Server Components. They read cookies via
   next/headers and either redirect() or return the Client Component. Hydration boundary
   is at the Client Component level — server renders the full HTML including BrandPanel,
   client hydrates only the interactive form column. This is architecturally correct.

3. useFormState (react-dom) is used in all three form components. This is the correct API
   for Next.js 14.2 (useActionState was added in React 19 / Next.js 15). No deprecation
   concern for this version.

4. SignInForm is a lean Client Component: one useFormState hook, two inputs, one
   PasswordField, one SubmitButton. No timers, no computed state, no memoization needed.
   Re-renders occur only on form submission and server action response — correct behaviour.

5. ResetPasswordForm uses useMemo for `rulesMet` (RULES.map over newPassword). The 5
   regex tests are cheap, but memoization is still appropriate as it prevents redundant
   regex evaluation on unrelated state changes (e.g. confirmPassword changes). Derived
   values (strengthBars, strengthLabel, canSubmit) are inline computations on top of
   rulesMet — no additional memoization needed given their simplicity.

6. SubmitButton (components/ui/SubmitButton.tsx) uses useFormStatus, which is scoped to the
   nearest parent form. It only re-renders when the form's pending state changes. Correct
   pattern — no performance concern.

INFERRED (not measured):
7. If React's chunk splitting does NOT deduplicate BrandPanel across the three Client
   Component bundles (see CSS finding #6), each route hydrates BrandPanel separately,
   increasing hydration work. Playwright/profiler data is unavailable in this environment
   to confirm or refute. (Inferred from bundle structure — not measured.)

════════════════════════════════════════════════════════
AUDIT 3: ANIMATION PERFORMANCE
════════════════════════════════════════════════════════

MEASURED:
1. No Tailwind animation classes (animate-*, transition-*, duration-*, ease-*) found in any
   of the 7 audited files.

2. No Framer Motion, GSAP, or any JavaScript-driven animation library imports found.

3. No CSS @keyframes, will-change, or transform-based animation properties found.

4. The strength meter bars (ResetPasswordForm) switch between bg-status-success and
   bg-[rgb(230,230,230)] via clsx on each keystroke. These are instant class-swaps
   (a repaint, not an animation). No transition is applied. Acceptable — the switch is
   perceptually instant and imposes no compositor cost.

5. OTP digit boxes (VerifyOtpForm) switch between two box-shadow values on focus/error.
   No CSS transition is applied to the shadow change — it switches instantly. No animation
   performance concern.

6. The resend section in VerifyOtpForm conditionally renders either a countdown <span> or
   a <button> when countdown reaches 0. This is a conditional DOM node swap that may cause
   a brief repaint at the transition point. It is not animated and occurs only once per
   session. No concern.

════════════════════════════════════════════════════════
GATE VERDICT
════════════════════════════════════════════════════════

Gate Status: WARN

Reason for WARN:
  - VerifyOtpForm's 30-second countdown causes ~30 full-component re-renders (once per
    second), evaluated against 6 OTP inputs and their clsx logic on each tick. This is a
    real but minor measured inefficiency. Extracting countdown+resend into its own sub-
    component (ResendSection) would isolate the timer re-renders to a small leaf tree.
  - The `bg-[rgb(230,230,230)]` hardcoded colour in ResetPasswordForm's strength meter
    is not a design token and will not respond to theme changes (minor CSS maintainability
    risk, not a runtime performance regression).

No animation performance issues found (none exist).
No blocking CSS or hydration performance issues found.
Bundle sizes are within expected range per bundle-analyze-gate's PASS report.
