Project

General

Profile

Feature #3269 » responsive-audit-3269.txt

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

 
Gate 7 — Responsive Audit — Ticket #3269 (Password Reset Flow)
================================================================
Date: 2026-09-04
Feature files audited:
- middleware.ts (no UI — excluded from responsive checks)
- 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

Related files inspected (not in feature_files but consumed by them):
- components/auth/BrandPanel.tsx (shared left-hand panel, w-[45%] shrink-0)
- components/ui/SubmitButton.tsx
- components/ui/fields/PhoneInput.tsx (country dropdown w-72)
- components/ui/fields/PasswordField.tsx (eye toggle touch target)
- components/ui/button.tsx (buttonVariants CVA)
- components/ui/Toast.tsx (fixed bottom-4 right-4)
- app/(auth)/layout.tsx (session guard only, no styling)
- app/(auth)/login/page.tsx + components/auth/SignInForm.tsx (UX-consistency baseline)
- app/globals.css (field-input/field-control/field-error layer components)
- tailwind.config.ts (breakpoints: default Tailwind sm:640 md:768 lg:1024 xl:1280)

screen_inventory: <missing> — UX-consistency audit limited to login page comparison.

Skills resolved (all via read_file, path 3 — no slash-command layer available):
1. nextjs-tailwind-responsive-components-audit — SKILL.md read ✓
2. nextjs-tailwind-responsive-desktop-audit — SKILL.md read ✓
3. nextjs-tailwind-responsive-mobile-audit — SKILL.md read ✓
4. nextjs-tailwind-responsive-tablet-breakpoint-audit — SKILL.md read ✓
5. nextjs-tailwind-responsive-typography-audit — SKILL.md read ✓
6. nextjs-tailwind-responsive-ux-consistency-audit — SKILL.md read ✓

========================================================================
MERGED FINDINGS (deduplicated, tagged by source audit)
========================================================================

--- FINDING 1 [P1] ---
Tags: [mobile][components][tablet]
File: app/(auth)/forgot-password/page.tsx, app/(auth)/verify-otp/page.tsx,
app/(auth)/reset-password/page.tsx (all three pages)
Root cause in: components/auth/BrandPanel.tsx (w-[45%] shrink-0, no responsive hide)
Component: Auth page layout (BrandPanel + form side-by-side)
Viewport(s) affected: 320-767px (all mobile and small-tablet widths)
Problem: BrandPanel renders at w-[45%] with shrink-0 at EVERY viewport — no
responsive hide/collapse. At 320px mobile, BrandPanel takes 144px and the form
area gets only 176px. At 375px, BrandPanel=169px / form=206px. At 430px,
BrandPanel=194px / form=236px. All three widths are far below the forms'
designed min content width (~360px). The forms are effectively unusable:
inputs are impossibly narrow, PhoneInput's country selector consumes most of
the available width, and the BrandPanel's own content (with left-12 + right-12
= 96px horizontal padding) has only 48px of usable text width at 320px — its
text-3xl heading is unreadable.
Evidence:
BrandPanel.tsx line 7: className="relative h-full w-[45%] shrink-0 overflow-hidden bg-accent"
forgot-password/page.tsx line 14: <div className="fixed inset-0 z-[100] flex overflow-hidden bg-surface">
No breakpoint-conditional rendering of BrandPanel. Same pattern on all three pages.
Login page (app/(auth)/login/page.tsx) uses the identical pattern — this is an
inherited layout, but it is still a P1 on these pages because they ship the
same broken mobile experience.
Why it matters: All three password-reset pages are completely unusable on any
phone. A user who forgot their password and is on mobile cannot complete the
reset flow.
Recommended fix: Add responsive hide to BrandPanel's root div:
className="relative hidden h-full w-[45%] shrink-0 overflow-hidden bg-accent md:block"
And ensure the form wrapper gets padding on mobile:
className="flex flex-grow items-center justify-center p-6 md:p-0"
(This also fixes the login page since BrandPanel is shared.)
Risk of change: Low — BrandPanel is purely decorative branding, hiding it on
mobile loses no functionality. The login page gains the same fix.
Fixed: false

--- FINDING 2 [P2] ---
Tags: [mobile]
File: app/(auth)/forgot-password/page.tsx, app/(auth)/verify-otp/page.tsx
Component: Form wrapper (flex-grow container around ForgotPasswordForm / VerifyOtpForm)
Viewport(s) affected: 320-767px
Problem: The form wrapper div has no horizontal padding:
<div className="flex flex-grow items-center justify-center">
If/when BrandPanel is hidden on mobile, the form (max-w-[360px] w-full)
would stretch to full viewport width with zero breathing room — inputs
would touch screen edges. The reset-password page correctly has p-6 on its
wrapper; these two pages do not.
Evidence:
forgot-password/page.tsx line 16: className="flex flex-grow items-center justify-center"
verify-otp/page.tsx line 37: className="flex flex-grow flex-col items-center justify-center gap-0"
reset-password/page.tsx line 37: className="flex flex-grow items-center justify-center overflow-y-auto p-6"
(reset-password has p-6, the other two do not)
Why it matters: Once the BrandPanel-hide fix is applied, these pages still lack
the safe-area padding mobile forms need.
Recommended fix: Add p-6 (or at minimum px-4 sm:px-6) to both page wrappers,
matching reset-password's existing pattern.
Risk of change: None — adding padding only narrows the max available width,
well within the form's own max-w-[360px].
Fixed: false

--- FINDING 3 [P2] ---
Tags: [mobile][components]
File: components/auth/VerifyOtpForm.tsx (line 120-126)
Component: Resend code button
Viewport(s) affected: 320-430px (all mobile)
Category: touch-targets
Problem: The "Resend code" / "Resend code in Xs" button is a bare text button
with no explicit height or padding:
className="text-sm font-medium text-accent hover:underline ..."
At text-sm (14px font, ~20px line-height), the rendered touch target is
approximately 20px tall — well below the 44px accessible minimum for touch
targets on mobile.
Evidence:
VerifyOtpForm.tsx lines 120-131: <button type="button" ... className="text-sm font-medium ...">
No h-*, py-*, min-h-*, or p-* classes.
Why it matters: On mobile, users will mis-tap or struggle to hit this button,
especially after a failed OTP attempt when they need to resend urgently.
Recommended fix: Add min-h-[44px] and py-2 to ensure the touch target meets
accessible minimums: className="min-h-[44px] py-2 text-sm font-medium ..."
Risk of change: Very low — only adds padding/height around existing text.
Fixed: false

--- FINDING 4 [P2] ---
Tags: [mobile][components]
File: components/ui/fields/PhoneInput.tsx (line 267-270)
Component: PhoneInput country dropdown
Viewport(s) affected: 320-430px
Category: layout
Problem: The country selector dropdown has a fixed width of w-72 (288px):
className="absolute left-0 z-40 mt-1 w-72 rounded-md ..."
On a 320px mobile viewport (even with BrandPanel hidden), after page padding
(24px each side), the available width is ~272px. The 288px dropdown overflows
its container by ~16px, getting clipped or causing horizontal scroll.
With BrandPanel present (Finding 1), only ~176px is available — massive overflow.
Evidence:
PhoneInput.tsx line 270: w-72 (18rem = 288px fixed)
Why it matters: Users on small phones selecting their country code will see a
clipped or partially off-screen dropdown, unable to read country names or tap
options near the right edge.
Recommended fix: Change w-72 to w-full or max-w-[min(18rem,calc(100vw-3rem))]
so the dropdown adapts to available width.
Risk of change: Low — dropdown content already wraps via truncate on country names.
Fixed: false

--- FINDING 5 [P2] ---
Tags: [tablet]
File: components/auth/CompleteResetForm.tsx (line 69)
Component: CompleteResetForm two-column grid
Exact width or range affected: 1024-1279px (lg: active, xl: not yet)
Category: grid
Problem: The password form grid transitions from single-column to two-column
at lg: (1024px viewport) via lg:grid-cols-[1fr_280px]. However, with BrandPanel
taking 45% of viewport width and the outer max-w-[600px] wrapper, the actual
available width for the 1fr column at 1024px is only ~195px:
1024px viewport * 55% = 563px right panel
- 48px padding (p-6 = 24px * 2) = 515px
- 280px policy card - 40px gap (gap-10) = 195px for password fields
At 195px, the password labels ("Re-enter New Password") wrap and the input
fields (minus 36px for the eye toggle, pr-9) have only ~159px of typing area.
Evidence:
CompleteResetForm.tsx line 69: className="grid grid-cols-1 gap-10 lg:grid-cols-[1fr_280px]"
reset-password/page.tsx line 38: className="w-full max-w-[600px]"
BrandPanel.tsx line 7: w-[45%] shrink-0
At 1280px viewport: 1280*0.55=704, -48=656, capped at 600, -280-40 = 280px — workable.
Why it matters: The two-column layout is cramped to the point of unusability in
the 1024-1279px range. Password fields at ~195px with the eye toggle are too
narrow for comfortable input.
Recommended fix: Change lg:grid-cols-[1fr_280px] to xl:grid-cols-[1fr_280px]
so the two-column layout only activates at 1280px+ where the form column
gets ~280px (acceptable). Single-column at lg: is perfectly fine — the
password policy card stacks below the form as it does on mobile.
Risk of change: Low — the single-column layout at lg: is the same as the
already-working mobile/tablet layout. Only changes when the side-by-side
view appears, not whether it appears at all.
Fixed: false

--- FINDING 6 [P3] ---
Tags: [typography]
File: components/auth/BrandPanel.tsx (lines 16-22)
Component: BrandPanel tagline heading
Viewport(s) affected: 640-1024px (tablet range where BrandPanel is visible)
Content type: heading
Problem: The tagline "Manage your orders, expenses and suppliers in one place."
uses hardcoded <br /> tags that produce correct line breaks only at one
specific width. At tablet widths (768px: BrandPanel=345px minus 96px padding
= 249px content width), these forced breaks produce awkward orphans or
imbalanced lines because the text wraps at different natural points than the
forced breaks assume.
Evidence:
BrandPanel.tsx lines 17-20:
<span className="text-3xl font-bold leading-[38px] text-accent-subtle">
Manage your orders,<br />expenses and suppliers<br />in one place.
</span>
Three hard <br /> breaks designed for ~300px+ content width.
Why it matters: At tablet-portrait width the heading wraps oddly — "in one
place." ends up on a very short line after a break that was meant to balance
three roughly-equal lines.
Recommended fix: Remove <br /> tags and let the text wrap naturally, or use
CSS (max-w-[Xch]) to constrain line width. If specific breaks are desired,
use <br className="hidden lg:inline" /> to make them conditional.
Risk of change: Very low — only affects visual line breaking of decorative text.
Fixed: false

--- FINDING 7 [P3] ---
Tags: [mobile]
File: components/ui/fields/PasswordField.tsx (lines 66-71)
Component: Password eye toggle button (used in CompleteResetForm + SignInForm)
Viewport(s) affected: 320-430px
Category: touch-targets
Problem: The show/hide password toggle button contains only a 16x16px icon
(h-4 w-4) with no explicit padding or minimum size on the button element:
className="absolute right-2 top-1/2 -translate-y-1/2 text-text-helper"
The rendered touch target is approximately 16x16px — far below the 44px
accessible minimum. (PasswordField.tsx is not in the feature_files list but is
consumed by CompleteResetForm and the login flow.)
Evidence:
PasswordField.tsx line 66-70: <button type="button" ...> containing
<EyeOff className="h-4 w-4" />
No min-h, min-w, p-*, or size classes on the button.
Why it matters: Users on mobile will struggle to toggle password visibility —
the target is ~3x smaller than the accessible minimum, risking mis-taps into
the password input itself.
Recommended fix: Add min-w-[44px] min-h-[44px] flex items-center justify-center
to the button, or add p-3 to give sufficient padding around the icon.
Risk of change: Low — only enlarges the invisible tap area. The icon position
may need right-offset adjustment to stay visually aligned.
Fixed: false

--- FINDING 8 [P3] ---
Tags: [ux-consistency]
File: app/(auth)/reset-password/page.tsx vs forgot-password/page.tsx, verify-otp/page.tsx
Component: Page heading placement
Viewports compared: all
Problem: ForgotPasswordForm and VerifyOtpForm render their own <h1> inside the
form component (at max-w-[360px]), while reset-password/page.tsx renders its
<h1> OUTSIDE CompleteResetForm in a wider max-w-[600px] wrapper. This means
the heading on the reset-password page is horizontally offset from the heading
position on the other two pages — the left edge of the heading aligns with
different points in the viewport across the three steps of the same flow.
Evidence:
forgot-password/page.tsx: <ForgotPasswordForm /> contains <h1> inside
className="flex w-full max-w-[360px] flex-col ..."
reset-password/page.tsx lines 38-42: <div className="w-full max-w-[600px]">
<h1 ...>Set New Password</h1> then <CompleteResetForm /> (form itself has
max-w-[1080px])
Why it matters: A user progressing through the 3-step flow sees the heading
"jump" horizontally when they reach step 3. The layout change is deliberate
(step 3 needs more width for the password policy card), but the heading
alignment inconsistency makes the transition feel like a different page rather
than the next step in a flow.
Recommended fix: Consider keeping the <h1> inside CompleteResetForm (matching
the other two forms) and applying the wider max-width only to the form body
below the heading, so all three headings start at the same horizontal position.
Alternatively, accept this as intentional given the wider form layout.
Risk of change: Low — heading placement only, no functional change.
Fixed: false

--- FINDING 9 [P3] ---
Tags: [desktop]
File: app/(auth)/reset-password/page.tsx, components/auth/CompleteResetForm.tsx
Component: CompleteResetForm submit button alignment
Viewport(s) affected: 1280px+ (desktop, where xl:grid-cols-[1fr_280px] would activate)
Category: content-width
Problem: The "Set New Password" SubmitButton sits in <div className="mt-8">
outside the grid, inheriting the form's full width. On desktop, the button
renders at its intrinsic content width while ForgotPasswordForm and
VerifyOtpForm both use className="w-full" on their submit buttons (constrained
by parent's max-w-[360px]). This produces a narrower button relative to the
form width on the reset page compared to the other two pages.
Evidence:
CompleteResetForm.tsx lines 167-170: <div className="mt-8"><SubmitButton ...>
No w-full on SubmitButton, no max-w constraint on its container.
ForgotPasswordForm.tsx line 55: <SubmitButton className="mt-6 w-full">
VerifyOtpForm.tsx line 114: <SubmitButton className="mt-6 w-full">
Why it matters: Minor visual inconsistency — the button occupies less of the
form's visual width than on the other two pages. Not a usability problem.
Recommended fix: No action strictly needed. Optionally constrain the button
wrapper with max-w-[460px] to align with the field column.
Risk of change: Negligible.
Fixed: false

========================================================================
SUMMARY
========================================================================
P1 findings: 1 (BrandPanel not hidden on mobile — all three pages unusable on phones)
P2 findings: 4 (missing form padding, touch target on resend button, dropdown overflow,
CompleteResetForm grid too early at lg:)
P3 findings: 4 (hardcoded br tags, password toggle touch target, heading placement,
button width)
Total: 9 findings

Breakpoints reviewed: base (mobile-first), sm (640px), md (768px), lg (1024px), xl (1280px)

GATE STATUS: FAIL
Reason: 1 unresolved P1 finding — the auth layout renders BrandPanel at all
viewports, making all three password-reset pages unusable on mobile (320-767px).

Auto-fix: Read-only access — no auto-fixes applied. All findings reported as-is.

Note: middleware.ts contains no UI/responsive code (it is Edge middleware for
auth routing) and was correctly excluded from all six audits.

Note: BrandPanel.tsx and PhoneInput.tsx are NOT in the feature_files list but
are directly consumed by the feature files. Findings on those components are
reported because they determine the responsive behavior of the audited pages.
The login page (app/(auth)/login/page.tsx) shares the same BrandPanel pattern
and would benefit from the same fix — but it is outside this ticket's scope.

Note: screen_inventory was <missing>, so the UX-consistency audit was limited to
comparing the three password-reset pages against each other and against the
login page (the only other auth-group page).
(9-9/10)