|
Gate 7 — Responsive Audit (Ticket #3273, Forgot-Password Flow)
|
|
================================================================
|
|
Date: 2026-09-08
|
|
Feature 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
|
|
Shared deps examined:
|
|
components/auth/BrandPanel.tsx
|
|
components/ui/SubmitButton.tsx
|
|
components/ui/fields/PasswordField.tsx
|
|
components/ui/button.tsx
|
|
app/(auth)/layout.tsx
|
|
app/(auth)/login/page.tsx
|
|
app/globals.css
|
|
tailwind.config.ts
|
|
screen_inventory: <missing — UX-consistency audit used login page as comparator>
|
|
|
|
Skill resolution:
|
|
[components] read_file(".claude/skills/nextjs-tailwind-responsive-components-audit/SKILL.md")
|
|
[desktop] read_file(".claude/skills/nextjs-tailwind-responsive-desktop-audit/SKILL.md")
|
|
[mobile] read_file(".claude/skills/nextjs-tailwind-responsive-mobile-audit/SKILL.md")
|
|
[tablet] read_file(".claude/skills/nextjs-tailwind-responsive-tablet-breakpoint-audit/SKILL.md")
|
|
[typography] read_file(".claude/skills/nextjs-tailwind-responsive-typography-audit/SKILL.md")
|
|
[ux-consistency] read_file(".claude/skills/nextjs-tailwind-responsive-ux-consistency-audit/SKILL.md")
|
|
|
|
Tailwind breakpoints (default, no overrides in tailwind.config.ts):
|
|
sm=640px md=768px lg=1024px xl=1280px 2xl=1536px
|
|
|
|
Key observation: ZERO responsive breakpoint prefixes (sm:/md:/lg:/xl:) found in
|
|
any of the seven feature files or BrandPanel. The entire auth flow has no
|
|
responsive adaptations.
|
|
|
|
================================================================================
|
|
MERGED FINDINGS
|
|
================================================================================
|
|
|
|
--- FINDING 1 (P1) ---
|
|
Tags: [mobile] [components] [tablet]
|
|
File: components/auth/BrandPanel.tsx (line 7)
|
|
Component: BrandPanel
|
|
Problem: BrandPanel uses `w-[45%] shrink-0` with no responsive hiding or
|
|
collapsing. It occupies 45% of viewport at ALL widths — including mobile.
|
|
At 320px → 144px for BrandPanel, 176px remaining for the form.
|
|
At 375px → ~169px for BrandPanel, ~206px remaining for the form.
|
|
At 640px (sm) → 288px for BrandPanel, 352px remaining.
|
|
The form containers need 360-480px, so they overflow or are crushed.
|
|
Evidence: className="relative h-full w-[45%] shrink-0 overflow-hidden bg-accent"
|
|
No sm:/md:/lg: prefixes. No hidden/md:block pattern.
|
|
Why it matters: The auth flow is completely unusable on any device below ~800px.
|
|
Forms overflow their container or are clipped. This is a blocking mobile/tablet
|
|
issue affecting all four auth screens (login, forgot-password, verify, reset).
|
|
Recommended fix: Add `hidden lg:block` to BrandPanel wrapper (hide below 1024px),
|
|
or `hidden md:block` (hide below 768px). The form-side container should become
|
|
the full-width layout on mobile. Pattern: `<BrandPanel className="hidden lg:block" />`
|
|
and ensure the right-side form container doesn't depend on BrandPanel being present.
|
|
Risk of change: Low — BrandPanel is purely decorative/branding, hides no
|
|
actionable content. The three-file shell (login/page.tsx, ForgotPasswordForm,
|
|
VerifyOtpForm, ResetPasswordForm) needs the same change.
|
|
Fixed: false
|
|
|
|
--- FINDING 2 (P1) ---
|
|
Tags: [mobile] [tablet]
|
|
File: components/auth/ForgotPasswordForm.tsx (line 27)
|
|
Component: ForgotPasswordForm — form container
|
|
Problem: Form uses `w-[360px]` — a fixed pixel width with no responsive fallback.
|
|
On mobile (even if BrandPanel were hidden), 360px exceeds 320px viewport,
|
|
and on a 375px viewport the form would touch both edges with no padding.
|
|
Evidence: className="flex w-[360px] flex-col gap-1.5"
|
|
Compare with SignInForm (line 18): className="flex w-full max-w-[360px] flex-col gap-1.5"
|
|
SignInForm uses `w-full max-w-[360px]` which is the correct mobile-safe pattern.
|
|
Why it matters: Horizontal overflow on mobile — the form extends past the viewport edge,
|
|
parts of the input and button are unreachable.
|
|
Recommended fix: Change `w-[360px]` to `w-full max-w-[360px] px-6` (same pattern as
|
|
ResetPasswordForm, or `w-full max-w-[360px]` like SignInForm).
|
|
Risk of change: None — at desktop widths max-w-[360px] renders identically to w-[360px].
|
|
Fixed: false
|
|
|
|
--- FINDING 3 (P1) ---
|
|
Tags: [mobile] [tablet]
|
|
File: components/auth/VerifyOtpForm.tsx (line 103)
|
|
Component: VerifyOtpForm — form container
|
|
Problem: Same fixed-width issue as Finding 2. Form uses `w-[360px]` instead of
|
|
`w-full max-w-[360px]`.
|
|
Evidence: className="flex w-[360px] flex-col gap-1.5"
|
|
Why it matters: Same overflow as Finding 2. Additionally, the OTP digit boxes
|
|
inside need 338px minimum (see Finding 4), so even a `max-w-[360px]` container
|
|
barely fits them.
|
|
Recommended fix: Change to `w-full max-w-[360px] px-4` or `w-full max-w-[400px] px-6`.
|
|
Risk of change: None at desktop.
|
|
Fixed: false
|
|
|
|
--- FINDING 4 (P1) ---
|
|
Tags: [mobile]
|
|
File: components/auth/VerifyOtpForm.tsx (lines 123-146)
|
|
Component: VerifyOtpForm — OTP digit boxes
|
|
Problem: 6 digit inputs × w-12 (48px) + 5 × gap-[10px] (50px) = 338px minimum width.
|
|
Even if BrandPanel is hidden and the form is full-width, 338px overflows a 320px
|
|
viewport. At 360-375px with any padding, the boxes are crushed or overflow.
|
|
Evidence: className="flex gap-[10px] mb-2" (container)
|
|
Each input: className="h-[52px] w-12 rounded-md ..."
|
|
6 × 48 + 5 × 10 = 338px hard minimum.
|
|
Why it matters: OTP entry — the core action of this screen — is physically
|
|
impossible on a small mobile device. Boxes overflow and the rightmost digits
|
|
are unreachable.
|
|
Recommended fix: At mobile widths, reduce box size to w-10 (40px) and gap to
|
|
gap-1.5 (6px): 6×40+5×6 = 270px, fits comfortably in 320px with padding.
|
|
Pattern: `className="flex gap-1.5 sm:gap-[10px] mb-2"` and per-box
|
|
`className="h-11 w-10 sm:h-[52px] sm:w-12 ..."`.
|
|
Risk of change: Low — smaller boxes are still easily tappable at 40×44px.
|
|
Fixed: false
|
|
|
|
--- FINDING 5 (P2) ---
|
|
Tags: [mobile] [components]
|
|
File: components/ui/fields/PasswordField.tsx (lines 66-72)
|
|
Component: PasswordField — show/hide toggle button
|
|
Problem: Toggle button has no explicit touch-target sizing. The rendered tappable
|
|
area is approximately 16×16px (the Eye/EyeOff icon is `h-4 w-4`). No padding
|
|
on the <button>, so the hit area is just the icon itself. Well below the 44×44px
|
|
accessible touch target minimum.
|
|
Evidence: <button ... className="absolute right-2 top-1/2 -translate-y-1/2 text-text-helper">
|
|
{show ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
|
No p-* or min-h-*/min-w-* on the button element.
|
|
Why it matters: On mobile, users will frequently mis-tap this control or find it
|
|
difficult to toggle password visibility — a common mobile usability complaint.
|
|
Recommended fix: Add padding to the button: `className="absolute right-2 top-1/2
|
|
-translate-y-1/2 p-2 text-text-helper"` — this makes the hit area ~32×32px
|
|
(icon + padding), or use `p-3` for full 40×40px. Ensure the parent input's
|
|
`pr-9` accounts for the larger hit area.
|
|
Risk of change: Low — minor visual adjustment to clickable area, no layout shift.
|
|
Fixed: false
|
|
|
|
--- FINDING 6 (P2) ---
|
|
Tags: [mobile]
|
|
File: components/auth/ForgotPasswordForm.tsx (lines 28-34),
|
|
components/auth/VerifyOtpForm.tsx (lines 107-113)
|
|
Component: "Back to Sign In" / "Back" navigation link
|
|
Problem: Link touch target is small — the ChevronLeft icon is h-4 w-4 (16px),
|
|
and the text is `text-[13px]`. The flex container has no min-height or padding,
|
|
so the tappable area is approximately 16-18px tall. Below the accessible
|
|
minimum for a navigation control.
|
|
Evidence: <Link ... className="mb-[14px] flex cursor-pointer items-center gap-1.5">
|
|
<ChevronLeft className="h-4 w-4" />
|
|
<span className="text-[13px] ...">Back to Sign In</span>
|
|
No py-* or min-h-* on the Link element.
|
|
Why it matters: Primary navigation control for going back in the flow. Hard to tap
|
|
accurately on mobile without mis-hitting the heading below.
|
|
Recommended fix: Add vertical padding: `className="mb-[14px] flex cursor-pointer
|
|
items-center gap-1.5 py-2"` for a ~32px touch target height.
|
|
Risk of change: None — adds invisible tappable area, doesn't change visual design.
|
|
Fixed: false
|
|
|
|
--- FINDING 7 (P2) ---
|
|
Tags: [mobile]
|
|
File: components/auth/VerifyOtpForm.tsx (lines 168-175)
|
|
Component: VerifyOtpForm — "Resend Code" button
|
|
Problem: Button has no padding — rendered size is approximately text height only
|
|
(~18px) at `text-[13px] leading-[18px]`. Touch target well below 44px minimum.
|
|
Evidence: <button ... className="text-[13px] leading-[18px] font-semibold text-accent
|
|
disabled:opacity-50">
|
|
No py-*/px-*/min-h-* on the button.
|
|
Why it matters: Users who need to resend a code on mobile will struggle to tap this
|
|
accurately, especially adjacent to the "Didn't get a code?" text.
|
|
Recommended fix: Add `py-2 px-1` or wrap in a larger touch area.
|
|
Risk of change: None visual if using only py-* padding.
|
|
Fixed: false
|
|
|
|
--- FINDING 8 (P2) ---
|
|
Tags: [ux-consistency]
|
|
File: components/auth/ForgotPasswordForm.tsx (line 27),
|
|
components/auth/VerifyOtpForm.tsx (line 103),
|
|
components/auth/SignInForm.tsx (line 18),
|
|
components/auth/ResetPasswordForm.tsx (line 65)
|
|
Component: Form width strategy across auth screens
|
|
Problem: Inconsistent width approach among closely related forms in the same flow:
|
|
- SignInForm: `w-full max-w-[360px]` ← mobile-safe
|
|
- ResetPasswordForm: `w-full max-w-[480px] px-6` ← mobile-safe
|
|
- ForgotPasswordForm: `w-[360px]` ← NOT mobile-safe
|
|
- VerifyOtpForm: `w-[360px]` ← NOT mobile-safe
|
|
Two of four forms in the same auth flow use a mobile-safe pattern, two don't.
|
|
Evidence: Direct className comparison above.
|
|
Why it matters: Inconsistency means two screens in the same user flow will break
|
|
on mobile while two won't — an uneven experience that suggests the mobile-safe
|
|
pattern was established (SignInForm/ResetPasswordForm) but not applied
|
|
consistently to the newer forgot-password forms.
|
|
Recommended fix: Standardize all four forms to `w-full max-w-[360px]` (or max-w-[400px]
|
|
for VerifyOtpForm due to OTP boxes needing 338px). Add `px-6` for mobile padding.
|
|
Risk of change: None — the max-w pattern renders identically at desktop widths.
|
|
Fixed: false
|
|
|
|
--- FINDING 9 (P2) ---
|
|
Tags: [tablet]
|
|
File: components/auth/BrandPanel.tsx (lines 15-22)
|
|
Component: BrandPanel — inner content area
|
|
Problem: At tablet-range widths where BrandPanel might be shown (768-1024px),
|
|
BrandPanel at 45% = 345-460px. Inner content uses `left-12 right-12` (48px each),
|
|
leaving 249-364px for the `text-3xl` (30px) heading which wraps to 3 lines
|
|
with explicit <br> tags. At ~345px panel width (768px viewport), the content
|
|
area is 249px — cramped for the heading but technically readable.
|
|
At smaller widths below 768px (if BrandPanel isn't hidden per Finding 1's fix),
|
|
the heading becomes truly illegible.
|
|
Evidence: className="absolute left-12 right-12 top-1/2 flex -translate-y-1/2 flex-col gap-[18px]"
|
|
Heading: className="text-3xl font-bold leading-[38px] text-accent-subtle"
|
|
Content: 3 lines with explicit <br> tags.
|
|
Why it matters: If the responsive fix for Finding 1 hides BrandPanel below lg
|
|
(1024px), this is moot. If it hides at md (768px), the 768-1024px range should
|
|
be checked to ensure heading doesn't overflow. At 1024px → panel=460px,
|
|
content=364px — fine.
|
|
Recommended fix: Dependent on Finding 1's breakpoint choice. If BrandPanel shows
|
|
at md+ (768px+), consider reducing heading to `text-2xl md:text-3xl` and
|
|
reducing horizontal padding to `left-8 right-8` at md.
|
|
Risk of change: Low — BrandPanel is decorative.
|
|
Fixed: false
|
|
|
|
--- FINDING 10 (P3) ---
|
|
Tags: [typography]
|
|
File: components/auth/BrandPanel.tsx (line 16-17)
|
|
Component: BrandPanel heading
|
|
Problem: `text-3xl` (30px) heading with explicit `<br>` line breaks — no responsive
|
|
variant. If BrandPanel is visible at intermediate widths (per Finding 9), the
|
|
hard-coded line breaks may produce suboptimal wrapping.
|
|
Evidence: <span className="text-3xl font-bold leading-[38px] text-accent-subtle">
|
|
Manage your orders,<br />expenses and suppliers<br />in one place.
|
|
Why it matters: Minor — hard <br> tags prevent natural text reflow. At widths
|
|
where the text would fit on fewer lines, the breaks force it to wrap early;
|
|
at widths where it's cramped, the breaks may cause awkward single-word lines.
|
|
Recommended fix: Remove <br> tags and let text wrap naturally, OR keep them and
|
|
add responsive hiding: `<br className="hidden md:inline" />`.
|
|
Risk of change: Very low — visual-only change to decorative panel.
|
|
Fixed: false
|
|
|
|
--- FINDING 11 (P3) ---
|
|
Tags: [desktop]
|
|
File: components/auth/ResetPasswordForm.tsx (line 62)
|
|
Component: ResetPasswordForm — right-side scroll container
|
|
Problem: The right-side container uses `overflow-auto py-10` which is good practice
|
|
(allows scroll for longer form content), but the form itself uses `max-w-[480px]`
|
|
while the other forms use 360px. At desktop widths this is fine — the form is
|
|
well-centered. At 1920px+ the auth shell is appropriately proportioned. No
|
|
real desktop issue found.
|
|
Evidence: className="flex flex-grow items-center justify-center overflow-auto py-10"
|
|
form: className="flex w-full max-w-[480px] flex-col px-6"
|
|
Why it matters: Informational only — ResetPasswordForm's wider max-w is justified
|
|
by its additional content (strength meter, policy card, two buttons). Desktop
|
|
layout is correct.
|
|
Recommended fix: None needed for desktop. Already well-handled.
|
|
Risk of change: N/A
|
|
Fixed: N/A (no issue)
|
|
|
|
--- FINDING 12 (P3) ---
|
|
Tags: [typography]
|
|
File: components/auth/VerifyOtpForm.tsx (lines 159-176),
|
|
components/auth/ForgotPasswordForm.tsx (lines 83-85)
|
|
Component: Footer helper text
|
|
Problem: Footer text uses `text-xs leading-[18px]` (12px). While technically readable,
|
|
this is at the lower bound of comfortable mobile reading size. Combined with
|
|
`text-text-faint` (#a3a3a3 on white — contrast ratio ~2.6:1), it may fail WCAG
|
|
AA contrast for small text (requires 4.5:1).
|
|
Evidence: <p className="mt-5 text-center text-xs leading-[18px] text-text-faint">
|
|
color #a3a3a3 on #ffffff background = ~2.6:1 contrast ratio
|
|
Why it matters: Accessibility concern (contrast), but the text is supplementary
|
|
("The code expires in 10 minutes...") not actionable. Still, WCAG violation.
|
|
Recommended fix: Darken text-faint to at least #767676 (4.5:1 on white) or use
|
|
text-text-helper (#757575, ~4.6:1 — passes AA). This is a design-token-level
|
|
fix, not per-component.
|
|
Risk of change: Design decision — affects all text-text-faint usage app-wide.
|
|
Fixed: false
|
|
|
|
================================================================================
|
|
SUMMARY
|
|
================================================================================
|
|
|
|
Total findings: 12 (11 real issues + 1 informational/no-issue desktop confirmation)
|
|
P1: 4 (Findings 1-4) — all unresolved, all relate to the fundamental problem
|
|
that the two-panel auth shell has no responsive behavior at all.
|
|
P2: 5 (Findings 5-9)
|
|
P3: 3 (Findings 10-12)
|
|
|
|
Breakpoints reviewed: sm (640px), md (768px), lg (1024px), xl (1280px),
|
|
plus mobile (320-430px) and large desktop (1920-2560px).
|
|
|
|
Root cause: The auth shell (BrandPanel + form column) was designed and
|
|
implemented exclusively for desktop. No responsive breakpoint classes exist
|
|
in any feature file. SignInForm and ResetPasswordForm accidentally use a
|
|
more mobile-tolerant width pattern (w-full max-w-*), but the shell itself
|
|
(BrandPanel at 45% fixed width) defeats any benefit.
|
|
|
|
gate_status: FAIL — 4 unresolved P1 findings.
|