WEB-AG-5 QA Findings — Analysis

Executive Summary

Session 7 closed the WEB-AG-5 port of the Pride website to Next.js 16 App Router. Runtime acceptance passed: Lighthouse mobile Performance, LCP, CLS, and A11y targets met on /, /events, /cocktail-lounge; axe-core returned zero critical violations on every audited public page; keyboard traversal and focus rings pass on desktop + mobile viewports. One above-fold SSR-flash issue surfaced mid-session and was fixed by converting the first Reveal on five pages to the immediate variant. Build and TypeScript checks pass clean; repo-wide ESLint is not clean but carries only pre-existing errors outside the S7 change scope (tracked as WEB-META-2).

Five items are formally deferred to follow-up WEB-META tickets (lint, CSP img-src, brand-colour contrast, Safari browser verification, Footer TikTok). With those parked, WEB-AG-5 is closed.


Lighthouse (mobile, pre-lock run)

PagePerformanceA11yBest PracticesSEOLCPCLS
/≥ 8510092100464 ms0.00
/events≥ 859892100260 ms0.00
/cocktail-lounge≥ 8510092100267 ms0.00

All three pages cleared the spec’s LCP < 2.5 s, CLS < 0.1, and A11y ≥ 90 gates comfortably. Best Practices landed at 92 across the board due to a single deferred violation (CSP img-src — see WEB-META-3 below).


axe-core

Zero critical violations on every audited public page. Zero violations on /events and /functions. Remaining serious count across the site: brand-colour contrast only — deferred as a WEB-META candidate (see Known Deferrals). No serious issues attributable to the App Router port itself.


Keyboard & Focus

  • 37 focusable elements on /; all rendered :focus-visible on Tab with the project’s amber focus ring
  • Skip-link (#main-content) surfaces on first Tab and scrolls correctly
  • Navigation dropdown + mobile drawer ARIA roles verified (aria-haspopup, aria-expanded, role="menu", trap working)
  • Contact topic chips traversable by keyboard
  • No focus traps or invisible focus on any route

SSR Flash Fix — Reveal immediate Prop

Above-fold Reveal wrappers were waiting on IntersectionObserver before animating in, producing a visible 100–300 ms opacity: 0 flash on initial render of below-hero sections. The fix: pass immediate to the first Reveal in each page so it animates on mount rather than on viewport intersection.

Pages updated in S7:

RouteFileLine
/cocktail-loungesrc/app/cocktail-lounge/page.tsx48
/functionssrc/app/functions/page.tsx57
/artistssrc/app/artists/page.tsx56
/aboutsrc/app/about/page.tsx66
/find-ussrc/app/find-us/page.tsx54

Home (/) hero already ships with immediate. Employment (/employment) doesn’t use Reveal (inlined Framer Motion variants).

Viewport-triggered Reveal on below-fold sections retained unchanged — they’re the correct pattern for scroll-in reveals.


Reduced Motion

The Reveal component (src/components/motion/Reveal.tsx:43) short-circuits to a plain tag when useReducedMotion() returns true, skipping Framer Motion entirely. Other motion components audited in S6–S7 follow the same pattern. The S7 immediate conversions don’t change reduced-motion behaviour — the useReducedMotion gate runs before the immediate branch.


Safari

Formally deferred. No webkit MCP available in the S7 session environment; visual verification of backdrop-filter, per-page blob border-radii (S6), and the Syne font on Safari was not performed this session. Chrome-based Lighthouse/axe runs are clean. Tracked as a WEB-META candidate.


Viewport Sweep (375 / 768 / 1440)

Sampled post-edit via the preview server on /cocktail-lounge: no horizontal scroll at any of the three widths. Prior sessions verified the full page set at these breakpoints.


Unsplash Scrub

grep -r "unsplash" src/ returns zero hits. All 7 Unsplash fallbacks replaced with repo-owned imagery per Step 8. Other spec grep gates (prideoffootscray, hello@prideof, Tailwind dark: prefix) also return zero true hits. The four dark: grep results in src/app/globals.css are CSS variable names (--color-primary-dark, --color-surface-dark) and a cautionary comment — not Tailwind prefix usage.


MotionWrapper Audit

MotionWrapper.tsx, AnimatedGrid.tsx, and SectionDivider.tsx deleted in S7. Grep for MotionWrapper|FadeInSection|StaggerSection|HoverCard across src/ returns zero hits — orphaned and removed cleanly.


Build / TypeScript / Lint

  • npx tsc --noEmit → exit 0 ✓
  • npm run build (Next 16, Turbopack) → exit 0 ✓
  • npm run lint → exit 1 (175 errors, 5 warnings) — accepted deferral

Lint Deferral Detail

All five S7-touched files lint-clean on scoped check. Repo-wide lint fails, but all 175 errors are pre-existing, in files S7 did not touch:

  • src/components/shared/PortableText.tsx
  • src/lib/sanity/image.ts
  • src/lib/sanity/types.ts
  • src/lib/events/index.ts
  • src/lib/merch/index.ts
  • Plus several others (rule mix: @typescript-eslint/no-explicit-any, react/no-unescaped-entities, react/jsx-no-undef)

Errors predate S1 of this migration. Count dropped from 179 at the start of S2 to 175 at end of S7 — the S7 a11y fixes incidentally cleared a few. Not a regression from the port.

The S7 spec acceptance criterion “npm run lint passes” was written against an assumed-clean baseline that didn’t exist — the same defect was retroactively corrected in S2 for a similar criterion. Resolution deferred to WEB-META-2.


Known Deferrals

TicketAreaNotes
WEB-META-2Repo-wide ESLint cleanup175 pre-existing errors in non-S7 files; dedicated post-AG-5 resolution
WEB-META-3CSP img-srcSingle Best-Practices point deduction on Lighthouse; needs CSP directive tuning for Sanity CDN + Instagram oEmbed
WEB-META-4 (candidate)Brand-colour contrastRemaining axe serious violations; decide whether to adjust tokens or formally accept the brand trade-off
WEB-META-5 (candidate)Safari browser verificationFull visual pass on backdrop-filter, per-page blob radii, Syne font; requires webkit environment
Post-AG-5 choreFooter TikTok linkOnce the venue’s TikTok handle is confirmed, add it alongside Instagram/Facebook

Session 7 Change Summary

  • Unsplash fallbacks removed (7 files → repo-owned imagery)
  • Reveal primitive migration completed (replaced legacy MotionWrapper everywhere)
  • AnimatedGrid.tsx, MotionWrapper.tsx, SectionDivider.tsx deleted (no remaining callers)
  • Contact page phone placeholder swap
  • layout.tsx main-content wrapper changed from <main><div id="main-content"> now that every route’s page.tsx provides its own <main> landmark (verified: 7/7 routes)
  • Input/textarea/select a11y — focus rings, ARIA labelling, describedby wiring
  • Footer a11y — heading hierarchy, link labels
  • Five above-fold Revealimmediate (SSR-flash fix, this session)
  • public/axe.min.js deleted (551 KB test artefact)

Closure

WEB-AG-5 acceptance criteria are met or formally deferred with ticket pointers. The Next.js 16 App Router port is the live state. Any follow-up work lands under the WEB-META line.