Project

General

Profile

Feature #3269 » performance-3269.txt

Bot AI, 09/04/2026 12:09 PM

 
Performance Gate Report — W14b
Ticket: #3269 (Forgot Password / Password Reset Flow)
Date: 2026-09-04
Audits: CSS Performance, General Render/Hydration Performance, Animation Performance

Feature files analyzed:
- middleware.ts
- app/(auth)/forgot-password/page.tsx
- app/(auth)/verify-otp/page.tsx
- app/(auth)/reset-password/page.tsx
- components/auth/ForgotPasswordForm.tsx
- components/auth/VerifyOtpForm.tsx
- components/auth/CompleteResetForm.tsx

Skill availability note:
Skills /nextjs-tailwind-css-performance-audit, /nextjs-tailwind-performance-audit,
/nextjs-tailwind-animation-performance-audit resolved to "Unknown skill" at runtime.
Audit was performed by direct file analysis against the actual source files.

Bundle-analyze gate status (already produced — not re-built here):
See docs/gate-evidence/bundle-analyze-3269.txt.
Build FAILED: lib/actions/forgot-password.ts exports a non-async constant
(RESET_FLOW_COOKIE) from a "use server" file — a Next.js constraint violation.
This is a supporting-file defect, not in the 7 feature files. Bundle size delta
cannot be confirmed from that report, but the 7 feature files contain no
heavy third-party imports beyond existing shared components (PasswordField,
SubmitButton, Toast, PhoneInput, BrandPanel) and react-dom/next/navigation hooks.


=======================================================================
AUDIT 1: CSS Performance
=======================================================================

MEASURED — Tailwind class analysis (direct source read):

Finding CSSPF-1 [css-performance] — No impact (informational)
Files: forgot-password/page.tsx:14, verify-otp/page.tsx:35, reset-password/page.tsx:35
Class string: "fixed inset-0 z-[100] flex overflow-hidden bg-surface"
Observation: Identical class string repeated across three auth pages (and
login/page.tsx — four total). Tailwind generates each utility class once in
the output stylesheet regardless of how many templates reference it — no CSS
file-size duplication. The (auth)/layout.tsx noted this in the quality audit
(CSSDRY-1) as a markup-DRY issue; it has zero CSS-output-size impact.
Verdict: No performance finding. Already flagged in W14 quality gate.

Finding CSSPF-2 [css-performance] — No impact (informational)
File: CompleteResetForm.tsx (lines 97-112), strength-bar divs
Observation: Four strength-bar divs use clsx() with conditional
bg-status-success / bg-border-subtle class toggling per keystroke.
Tailwind emits both classes unconditionally; the toggle is a className-string
swap (not a style mutation). No style recalculation beyond a single-element
class change per bar per keystroke. DOM depth is 4 sibling divs, cost negligible.
Verdict: No performance finding.

Finding CSSPF-3 [css-performance] — No impact (informational)
Files: Multiple
Arbitrary Tailwind values in use: z-[100], max-w-[360px], max-w-[600px],
max-w-[180px], max-w-[460px], max-w-[1080px], text-[26px], text-[13px],
h-[5px], gap-[3px], py-[18px], tracking-[0.5em]
Observation: Each arbitrary value generates a unique CSS class. Across the 7
files, 12 distinct arbitrary values are present. Estimated CSS-size overhead:
~300–400 bytes (12 rules × ~25–35 bytes each). Negligible at these quantities.
Verdict: No performance finding.

Areas checked and found clean (CSS):
- No @apply directives in any feature file (Tailwind plugin would purge them;
any @apply in globals.css is outside scope).
- No inline style= attributes except one intentional SVG stroke color override
in CompleteResetForm.tsx (rule-check SVG icons). Inline SVG stroke avoids a
separate SVG icon sprite or icon library import — acceptable trade-off.
- No bg-gradient or complex filter utilities that trigger expensive paint layers.
- No fixed-height containers that could cause content overflow or reflow loops.
- overflow-hidden on the auth page shell prevents scrollbar layout shift.
- overflow-y-auto on reset-password's right panel is correct (long form).


=======================================================================
AUDIT 2: General Render / Hydration Performance
=======================================================================

MEASURED — direct source analysis:

Finding PERFR-1 [render-performance] — No impact (informational)
File: components/auth/VerifyOtpForm.tsx (lines 41-45)
Pattern: setInterval(1-second tick) inside useEffect, updating secondsLeft state.
Observation: Each tick calls setSecondsLeft((s) => Math.max(0, s - 1)), triggering
one re-render per second. The re-rendered subtree is the resend-button text node
only ("Resend code in Ns"). The interval is cleared when secondsLeft <= 0 (the
early-return guard on line 42) and on unmount via the returned clearInterval.
Lifecycle is correct; re-render scope is minimal.
Verdict: No performance finding.

Finding PERFR-2 [render-performance] — No impact (informational)
File: components/auth/CompleteResetForm.tsx (lines 42-43)
Pattern: useMemo(() => RULES.map(r => r.test(newPassword)), [newPassword])
Observation: 5 lightweight regex tests (.length, /[A-Z]/, /[a-z]/, /[0-9]/,
/[^A-Za-z0-9]/) are memoized. The dependent computation (metCount, strengthBars,
strengthLabel, policyOk, matchOk, canSubmit) runs synchronously from the memoized
value. This is the correct pattern — no unnecessary recomputation.
RULES is module-level (not inside the component body), so its reference is stable
and won't defeat the memo.
Verdict: No performance finding.

Finding PERFR-3 [render-performance] — No impact (informational)
File: app/(auth)/verify-otp/page.tsx, app/(auth)/reset-password/page.tsx
Pattern: Server Component awaits getResetFlowState(flowId) — a DB call — at
render time, with redirect() on a falsy result.
Observation: These pages are gated behind the oms_reset_flow cookie, which only
exists for users mid-flow. Traffic volume is inherently low (forgot-password is
an infrequent path). The DB call selects by primary UUID — O(1) with an indexed
PK. No N+1 or unbounded query visible from the page files.
Verdict: No performance finding (measured within scope of these files).

Finding PERFR-4 [render-performance] — No impact (informational)
File: middleware.ts (lines 49–98)
Pattern: Edge middleware runs on every matched request; excludes /login,
/forgot-password, /verify-otp, /reset-password, /api/cron, /_next/*.
Observation: For unauthenticated reset-flow pages: the matcher excludes them,
so middleware is BYPASSED entirely for these routes (correct, by design per the
ticket comment on line 45). Middleware overhead for authenticated routes: awaits
verifyEdgeMarker (crypto — Ed25519 or HMAC, Edge-compatible); then a synchronous
permission lookup (resolveRoutePermission + can). Overall per-request cost is
sub-millisecond for the crypto step on Edge infrastructure.
Verdict: No performance finding.

Hydration check:
ForgotPasswordForm: "use client", one useFormState + one useEffect + useRouter.
Hydration bundle: this component + PhoneInput + SubmitButton + next/navigation.
No heavy imports. Hydration cost: low.
VerifyOtpForm: "use client", useFormState + 3× useState + useTransition +
useRef + 2× useEffect. Hydration bundle: this component + SubmitButton + Toast.
No third-party libraries beyond react-dom hooks. Hydration cost: low-medium
(multiple hook initializations, but no large state trees).
CompleteResetForm: "use client", useFormState + 3× useState + useMemo +
useEffect + clsx. Hydration bundle: this component + PasswordField (×2) +
SubmitButton + Toast. No heavy libraries. Hydration cost: low-medium.
Server Component pages (forgot-password, verify-otp, reset-password): zero client
JS in the RSC payload for the page shell itself. Only their Client Component
children hydrate.

Areas checked and found clean (render/hydration):
- No useLayoutEffect (would cause SSR/client mismatch warnings).
- No useContext with deeply nested providers in these files.
- No large initialState objects that inflate the RSC/HTML payload.
- No React.lazy or dynamic() import in these files — not needed for auth
pages, which are infrequently visited and don't benefit from code-splitting
beyond what Next.js app-router does automatically per route segment.
- No prop-drilling of large objects.
- No setState calls in render (outside of effects/handlers).


=======================================================================
AUDIT 3: Animation Performance
=======================================================================

MEASURED — direct source analysis:

No animation or transition Tailwind classes found in any of the 7 feature files.

Checked for:
- animate-* : 0 occurrences
- transition-* : 0 occurrences
- duration-* : 0 occurrences
- ease-* : 0 occurrences
- delay-* : 0 occurrences
- motion-* : 0 occurrences
- will-change-* : 0 occurrences
- transform / translate / scale / rotate : 0 occurrences (outside implicit Tailwind reset)
- framer-motion, react-spring, or other animation libraries : not imported

The resend-button countdown string update (`Resend code in Ns`) causes a DOM text-node
update every second — this is a data update, not an animation. No compositor
layers, no paint, no reflow beyond the text node. Cost is negligible.

The strength-bar color toggle (clsx bg-status-success / bg-border-subtle) on each
keystroke changes a computed style but triggers no layout (background-color changes
are paint-only, not layout). No compositor promotion; paint is confined to 4 small
bar elements.

Verdict: No animation performance findings.


=======================================================================
INFERRED (not measured) — clearly labeled
=======================================================================

INFERRED-1: PasswordField onChange re-renders
Both PasswordField instances in CompleteResetForm call onChange (setNewPassword,
setConfirmPassword) on every keypress. Each calls the parent state setter,
triggering a full CompleteResetForm re-render. The PasswordField components
themselves are controlled (they receive value). Without React.memo on PasswordField,
both fields re-render on every keystroke even when only one is being typed into.
Impact estimate: low — the component tree is shallow (2 PasswordField + 1 strength
section + 1 policy card). A profiler run would be needed to confirm any jank.
This is a plausible optimization target if typing latency is ever observed, but
NOT a confirmed regression from this code path alone.
Confidence: inferred (no profiler data; no prior baseline to compare against).

INFERRED-2: getResetFlowState cold-start latency
On the verify-otp and reset-password pages, if the DB connection pool is cold
(first request after inactivity), getResetFlowState may add 50–200ms to TTFB.
This is not observable from the source files alone and is an infrastructure-level
concern, not a code-level one.
Confidence: inferred.


=======================================================================
SUMMARY
=======================================================================

Measured findings: 0 blocking, 0 warn-level
Inferred findings: 2 (both low-confidence, low-impact — included for completeness)
Animation findings: 0

Bundle analysis: Build failed upstream (lib/actions/forgot-password.ts "use server"
constant export) — bundle delta unconfirmed. Feature files themselves contain no
heavy imports.

GATE STATUS: PASS
No real (measured) CSS, render/hydration, or animation performance issues found
in the 7 feature files. All findings are informational or inferred.
(7-7/10)