From 3e0fbf903f3ac20a9dfe2010bdf1f4839d544b57 Mon Sep 17 00:00:00 2001 From: Abass Ibrahim <136358470+Lingz450@users.noreply.github.com> Date: Sat, 11 Jul 2026 11:18:12 +0100 Subject: [PATCH 01/14] fix mobile sheet close controls --- components/ui/sheet.tsx | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/components/ui/sheet.tsx b/components/ui/sheet.tsx index 5116d8b..63c4e31 100644 --- a/components/ui/sheet.tsx +++ b/components/ui/sheet.tsx @@ -7,11 +7,8 @@ import { X } from "lucide-react" import { cn } from "@/lib/utils" const Sheet = SheetPrimitive.Root - const SheetTrigger = SheetPrimitive.Trigger - const SheetClose = SheetPrimitive.Close - const SheetPortal = SheetPrimitive.Portal const SheetOverlay = React.forwardRef< @@ -20,7 +17,7 @@ const SheetOverlay = React.forwardRef< >(({ className, ...props }, ref) => ( { side?: "top" | "right" | "bottom" | "left" + showClose?: boolean } const SheetContent = React.forwardRef< React.ElementRef, SheetContentProps ->(({ side = "right", className, children, ...props }, ref) => ( +>(({ side = "right", showClose = true, className, children, ...props }, ref) => ( {children} - - - Close - + {showClose ? ( + + + Close + + ) : null} )) @@ -102,5 +102,13 @@ const SheetDescription = React.forwardRef< )) SheetDescription.displayName = SheetPrimitive.Description.displayName -export { Sheet, SheetTrigger, SheetClose, SheetContent, SheetHeader, SheetFooter, SheetTitle, SheetDescription } - +export { + Sheet, + SheetTrigger, + SheetClose, + SheetContent, + SheetHeader, + SheetFooter, + SheetTitle, + SheetDescription, +} From a469a86c3846349b8fd0fbb89500e9c88fa650c5 Mon Sep 17 00:00:00 2001 From: Abass Ibrahim <136358470+Lingz450@users.noreply.github.com> Date: Sat, 11 Jul 2026 11:18:23 +0100 Subject: [PATCH 02/14] fix nested mobile search dialog --- components/search/search-dialog.tsx | 34 ++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/components/search/search-dialog.tsx b/components/search/search-dialog.tsx index 66e3f54..3a35786 100644 --- a/components/search/search-dialog.tsx +++ b/components/search/search-dialog.tsx @@ -5,38 +5,52 @@ import { Search } from "lucide-react" import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog" import SearchBar from "@/components/SearchBar" -export function SearchDialog() { +interface SearchDialogProps { + onOpenChange?: (open: boolean) => void +} + +export function SearchDialog({ onOpenChange }: SearchDialogProps) { const [open, setOpen] = React.useState(false) - const handleClose = React.useCallback(() => setOpen(false), []) + const updateOpen = React.useCallback( + (nextOpen: boolean) => { + setOpen(nextOpen) + onOpenChange?.(nextOpen) + }, + [onOpenChange], + ) + + const handleClose = React.useCallback(() => updateOpen(false), [updateOpen]) - // Close dialog when user clicks a search result (SearchBar calls onNavigate; this backs it up for outside clicks) React.useEffect(() => { if (!open) return - const handleClick = (e: MouseEvent) => { - const target = e.target as HTMLElement + + const handleClick = (event: MouseEvent) => { + const target = event.target as HTMLElement if (target.closest('[role="option"]')) setTimeout(handleClose, 100) } + document.addEventListener("click", handleClick) return () => document.removeEventListener("click", handleClick) }, [open, handleClose]) return ( - + - +
-

Search Products

-

Find your favorite luxury items

+

Search perfumes

+

Find your next fragrance

From 14e62f3f78d983661893b8c6076891d09f6faf5e Mon Sep 17 00:00:00 2001 From: Abass Ibrahim <136358470+Lingz450@users.noreply.github.com> Date: Sat, 11 Jul 2026 11:18:56 +0100 Subject: [PATCH 03/14] stabilize mobile navigation behavior --- components/layout/header.tsx | 187 ++++++++++++++--------------------- 1 file changed, 75 insertions(+), 112 deletions(-) diff --git a/components/layout/header.tsx b/components/layout/header.tsx index f0aa9c7..f19f2ba 100644 --- a/components/layout/header.tsx +++ b/components/layout/header.tsx @@ -1,28 +1,24 @@ "use client" - - import * as React from "react" - import Link from "next/link" import { usePathname } from "next/navigation" -import { Logo } from "@/components/logo" - -import { Heart, User, ShoppingBag, Menu, X } from "lucide-react" - -import { cn } from "@/lib/utils" +import { Heart, Menu, ShoppingBag, User, X } from "lucide-react" +import { Logo } from "@/components/logo" +import { SearchDialog } from "@/components/search/search-dialog" import { Button } from "@/components/ui/button" - +import { + Sheet, + SheetClose, + SheetContent, + SheetDescription, + SheetTitle, + SheetTrigger, +} from "@/components/ui/sheet" import { ThemeToggle } from "@/components/ui/theme-toggle" - -import { Sheet, SheetContent, SheetTrigger, SheetClose } from "@/components/ui/sheet" - -import { SearchDialog } from "@/components/search/search-dialog" - import { useCartStore } from "@/lib/stores/cart-store" - - +import { cn } from "@/lib/utils" const navigation = [ { name: "Perfumes", href: "/category/perfumes" }, @@ -33,70 +29,50 @@ const navigation = [ { name: "About", href: "/about" }, ] - - -interface HeaderProps {} - -export function Header(_props: HeaderProps) { +export function Header() { const pathname = usePathname() const [isScrolled, setIsScrolled] = React.useState(false) + const [mobileOpen, setMobileOpen] = React.useState(false) const cartItemCount = useCartStore((state) => state.getUniqueItemsCount()) - - React.useEffect(() => { - - const handleScroll = () => { - - setIsScrolled(window.scrollY > 10) - - } - - window.addEventListener("scroll", handleScroll) - + const handleScroll = () => setIsScrolled(window.scrollY > 10) + handleScroll() + window.addEventListener("scroll", handleScroll, { passive: true }) return () => window.removeEventListener("scroll", handleScroll) - }, []) - + React.useEffect(() => { + setMobileOpen(false) + }, [pathname]) return ( -
-
- -
- - {/* Brand */} +
- - - {/* Desktop Navigation */} -
- ) - } From 37461d1ec3ebebd8726f32957e7cb17cb48c85c8 Mon Sep 17 00:00:00 2001 From: Abass Ibrahim <136358470+Lingz450@users.noreply.github.com> Date: Sat, 11 Jul 2026 11:20:26 +0100 Subject: [PATCH 04/14] chore add continuation source audit --- .github/workflows/continuation-audit.yml | 53 ++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 .github/workflows/continuation-audit.yml diff --git a/.github/workflows/continuation-audit.yml b/.github/workflows/continuation-audit.yml new file mode 100644 index 0000000..0e343cf --- /dev/null +++ b/.github/workflows/continuation-audit.yml @@ -0,0 +1,53 @@ +name: Continuation source audit + +on: + push: + branches: + - agent/mobile-verification-harness + +permissions: + contents: read + +jobs: + audit: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Repository orientation + shell: bash + run: | + echo "HEAD=$(git rev-parse HEAD)" + echo "BRANCH=$(git branch --show-current)" + git status --short --branch + echo "--- HANDOFF AND TODO FILES ---" + find . -type f \ + \( -iname '*todo*' -o -iname '*handoff*' -o -iname '*contract*' \) \ + -not -path './node_modules/*' \ + -not -path './.git/*' \ + | sort + + - name: Visible em dash candidates + shell: bash + run: | + echo "--- EM DASH CANDIDATES IN RENDERED SOURCE ---" + grep -RIn --include='*.tsx' --include='*.ts' --include='*.jsx' --include='*.js' \ + --exclude-dir=node_modules --exclude-dir=.next --exclude-dir=FRONTEND \ + $'—' app components lib 2>/dev/null || true + + - name: Native select candidates + shell: bash + run: | + echo "--- NATIVE SELECT CANDIDATES ---" + grep -RIn --include='*.tsx' --include='*.jsx' \ + --exclude-dir=node_modules --exclude-dir=.next --exclude-dir=FRONTEND \ + '' app components 2>/dev/null || true + + - name: Unrelated catalogue wording candidates + shell: bash + run: | + echo "--- NON PERFUME CATALOGUE WORDING ---" + grep -RInEi --include='*.tsx' --include='*.ts' --include='*.jsx' --include='*.js' \ + --exclude-dir=node_modules --exclude-dir=.next --exclude-dir=FRONTEND \ + 'watches|wristwatches|eyeglasses|sunglasses' app components lib 2>/dev/null || true From b238646f6a9a9e2467acde853b7a7dba008fe74a Mon Sep 17 00:00:00 2001 From: Abass Ibrahim <136358470+Lingz450@users.noreply.github.com> Date: Sat, 11 Jul 2026 11:20:37 +0100 Subject: [PATCH 05/14] test add isolated Playwright harness --- e2e/package.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 e2e/package.json diff --git a/e2e/package.json b/e2e/package.json new file mode 100644 index 0000000..f59c8db --- /dev/null +++ b/e2e/package.json @@ -0,0 +1,16 @@ +{ + "name": "fade-store-e2e", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "test": "playwright test", + "test:headed": "playwright test --headed", + "test:ui": "playwright test --ui", + "install:browsers": "playwright install --with-deps" + }, + "devDependencies": { + "@axe-core/playwright": "4.12.1", + "@playwright/test": "1.61.1" + } +} From dcda60b040dc38cc55118aefdb326b8b33f57399 Mon Sep 17 00:00:00 2001 From: Abass Ibrahim <136358470+Lingz450@users.noreply.github.com> Date: Sat, 11 Jul 2026 11:20:56 +0100 Subject: [PATCH 06/14] test lock Playwright harness dependencies --- e2e/package-lock.json | 101 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 e2e/package-lock.json diff --git a/e2e/package-lock.json b/e2e/package-lock.json new file mode 100644 index 0000000..8c2ce52 --- /dev/null +++ b/e2e/package-lock.json @@ -0,0 +1,101 @@ +{ + "name": "fade-store-e2e", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "fade-store-e2e", + "version": "1.0.0", + "devDependencies": { + "@axe-core/playwright": "4.12.1", + "@playwright/test": "1.61.1" + } + }, + "node_modules/@axe-core/playwright": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.12.1.tgz", + "integrity": "sha512-rMd7xriptqKpP+w5265i4Hdkv2X5kbu6uiBi/B2I7uf3hieRBM3qDCfaKPtxfiYb2mKXfF+yLODJwIx+Jv1GDw==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "axe-core": "~4.12.1" + }, + "peerDependencies": { + "playwright-core": ">= 1.0.0" + } + }, + "node_modules/@playwright/test": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.61.1.tgz", + "integrity": "sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.61.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/axe-core": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.12.1.tgz", + "integrity": "sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBp+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz", + "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.61.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz", + "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + } + } +} From 76788e4645c5d1fabea8cfdc5e9ed24ba75cc1fb Mon Sep 17 00:00:00 2001 From: Abass Ibrahim <136358470+Lingz450@users.noreply.github.com> Date: Sat, 11 Jul 2026 11:23:14 +0100 Subject: [PATCH 07/14] test add Playwright storefront configuration --- e2e/playwright.config.ts | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 e2e/playwright.config.ts diff --git a/e2e/playwright.config.ts b/e2e/playwright.config.ts new file mode 100644 index 0000000..d678b2c --- /dev/null +++ b/e2e/playwright.config.ts @@ -0,0 +1,35 @@ +import { defineConfig, devices } from "@playwright/test" + +const baseURL = process.env.E2E_BASE_URL ?? "https://9thluxe-store-two.vercel.app" + +export default defineConfig({ + testDir: "./tests", + fullyParallel: true, + forbidOnly: Boolean(process.env.CI), + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 2 : undefined, + reporter: process.env.CI + ? [["line"], ["html", { outputFolder: "playwright-report", open: "never" }]] + : "list", + timeout: 30_000, + expect: { timeout: 10_000 }, + use: { + baseURL, + trace: "retain-on-failure", + screenshot: "only-on-failure", + video: "retain-on-failure", + actionTimeout: 10_000, + navigationTimeout: 20_000, + }, + projects: [ + { + name: "mobile-chromium", + use: { ...devices["iPhone 12"] }, + }, + { + name: "desktop-chromium", + use: { ...devices["Desktop Chrome"] }, + }, + ], + outputDir: "test-results", +}) From 900ca3016280488efb511bfe6a753ce7afc0a779 Mon Sep 17 00:00:00 2001 From: Abass Ibrahim <136358470+Lingz450@users.noreply.github.com> Date: Sat, 11 Jul 2026 11:23:35 +0100 Subject: [PATCH 08/14] test cover storefront routes and mobile interactions --- e2e/tests/storefront.spec.ts | 110 +++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 e2e/tests/storefront.spec.ts diff --git a/e2e/tests/storefront.spec.ts b/e2e/tests/storefront.spec.ts new file mode 100644 index 0000000..7d2c794 --- /dev/null +++ b/e2e/tests/storefront.spec.ts @@ -0,0 +1,110 @@ +import { expect, test, type Page } from "@playwright/test" + +const publicRoutes = [ + "/", + "/shop", + "/category/perfumes", + "/collections", + "/concierge", + "/drops", + "/journal", + "/about", + "/cart", + "/compare", +] + +async function expectHealthyPage(page: Page, route: string) { + const response = await page.goto(route, { waitUntil: "domcontentloaded" }) + expect(response, `${route} should return a document response`).not.toBeNull() + expect(response!.status(), `${route} should not return a server error`).toBeLessThan(500) + await expect(page.locator("body")).toBeVisible() +} + +test.describe("public storefront routes", () => { + for (const route of publicRoutes) { + test(`${route} loads without a server failure`, async ({ page }) => { + await expectHealthyPage(page, route) + }) + } + + test("visible storefront copy does not contain em dashes", async ({ page }) => { + for (const route of publicRoutes) { + await expectHealthyPage(page, route) + const visibleText = await page.locator("body").innerText() + expect(visibleText, `${route} contains a visible em dash`).not.toContain("—") + } + }) +}) + +test.describe("mobile navigation", () => { + test.skip(({ isMobile }) => !isMobile, "Mobile menu behaviour is mobile-specific") + + test("opens, exposes one close control, and closes after navigation", async ({ page }) => { + await page.goto("/", { waitUntil: "domcontentloaded" }) + + const trigger = page.getByRole("button", { name: "Open menu" }) + await expect(trigger).toBeVisible() + await expect(trigger).toHaveAttribute("aria-expanded", "false") + await trigger.click() + await expect(trigger).toHaveAttribute("aria-expanded", "true") + + const dialog = page.getByRole("dialog", { name: "Navigation menu" }) + await expect(dialog).toBeVisible() + await expect(dialog.getByRole("button", { name: "Close menu" })).toHaveCount(1) + + await dialog.getByRole("link", { name: "About", exact: true }).click() + await expect(page).toHaveURL(/\/about(?:\?.*)?$/) + await expect(dialog).toBeHidden() + await expect(page.getByRole("button", { name: "Open menu" })).toHaveAttribute("aria-expanded", "false") + }) + + test("has no horizontal page overflow at iPhone 12 width", async ({ page }) => { + await page.goto("/shop", { waitUntil: "domcontentloaded" }) + const dimensions = await page.evaluate(() => ({ + scrollWidth: document.documentElement.scrollWidth, + clientWidth: document.documentElement.clientWidth, + })) + expect(dimensions.scrollWidth).toBeLessThanOrEqual(dimensions.clientWidth + 1) + }) +}) + +test.describe("custom dropdowns", () => { + test("shop filters use an accessible portalled combobox instead of a native select", async ({ page }) => { + await page.goto("/shop", { waitUntil: "domcontentloaded" }) + + await expect(page.locator("select")).toHaveCount(0) + const comboboxes = page.getByRole("combobox") + await expect(comboboxes.first()).toBeVisible() + await comboboxes.first().click() + + const listbox = page.getByRole("listbox") + await expect(listbox).toBeVisible() + await expect(listbox.getByRole("option", { name: "Perfumes", exact: true })).toBeVisible() + + const stacking = await listbox.evaluate((element) => { + const style = window.getComputedStyle(element) + return { + position: style.position, + zIndex: Number.parseInt(style.zIndex || "0", 10), + parent: element.parentElement?.tagName ?? "", + } + }) + expect(stacking.parent).toBe("BODY") + expect(stacking.zIndex).toBeGreaterThanOrEqual(50) + }) +}) + +test.describe("motion preference", () => { + test.use({ reducedMotion: "reduce" }) + + test("mobile sheet suppresses transition animation when reduced motion is requested", async ({ page, isMobile }) => { + test.skip(!isMobile, "Mobile sheet is hidden at desktop width") + await page.goto("/", { waitUntil: "domcontentloaded" }) + await page.getByRole("button", { name: "Open menu" }).click() + + const dialog = page.getByRole("dialog", { name: "Navigation menu" }) + await expect(dialog).toBeVisible() + const transitionDuration = await dialog.evaluate((element) => window.getComputedStyle(element).transitionDuration) + expect(["0s", "0ms"]).toContain(transitionDuration) + }) +}) From 6df68a4093448504e4f7170e890e39d3793d0705 Mon Sep 17 00:00:00 2001 From: Abass Ibrahim <136358470+Lingz450@users.noreply.github.com> Date: Sat, 11 Jul 2026 11:23:46 +0100 Subject: [PATCH 09/14] test add automated accessibility checks --- e2e/tests/accessibility.spec.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 e2e/tests/accessibility.spec.ts diff --git a/e2e/tests/accessibility.spec.ts b/e2e/tests/accessibility.spec.ts new file mode 100644 index 0000000..46ccdf4 --- /dev/null +++ b/e2e/tests/accessibility.spec.ts @@ -0,0 +1,24 @@ +import AxeBuilder from "@axe-core/playwright" +import { expect, test } from "@playwright/test" + +const routes = ["/", "/shop", "/category/perfumes", "/collections", "/concierge", "/about", "/cart"] + +test.describe("WCAG critical and serious violations", () => { + for (const route of routes) { + test(`${route} has no critical or serious axe violations`, async ({ page }) => { + const response = await page.goto(route, { waitUntil: "domcontentloaded" }) + expect(response).not.toBeNull() + expect(response!.status()).toBeLessThan(500) + + const results = await new AxeBuilder({ page }) + .withTags(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa", "wcag22aa"]) + .analyze() + + const blocking = results.violations.filter( + (violation) => violation.impact === "critical" || violation.impact === "serious", + ) + + expect(blocking, JSON.stringify(blocking, null, 2)).toEqual([]) + }) + } +}) From f08de14c9ca4c15f4c93e9d22a11aa76402a7430 Mon Sep 17 00:00:00 2001 From: Abass Ibrahim <136358470+Lingz450@users.noreply.github.com> Date: Sat, 11 Jul 2026 11:23:57 +0100 Subject: [PATCH 10/14] ci run storefront Playwright verification --- .github/workflows/storefront-e2e.yml | 51 ++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 .github/workflows/storefront-e2e.yml diff --git a/.github/workflows/storefront-e2e.yml b/.github/workflows/storefront-e2e.yml new file mode 100644 index 0000000..e9f0b53 --- /dev/null +++ b/.github/workflows/storefront-e2e.yml @@ -0,0 +1,51 @@ +name: Storefront E2E + +on: + pull_request: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + +jobs: + playwright: + runs-on: ubuntu-latest + timeout-minutes: 20 + defaults: + run: + working-directory: e2e + env: + E2E_BASE_URL: ${{ vars.E2E_BASE_URL || 'https://9thluxe-store-two.vercel.app' }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + cache-dependency-path: e2e/package-lock.json + + - name: Install test dependencies + run: npm ci + + - name: Install Chromium + run: npx playwright install --with-deps chromium + + - name: Run storefront verification + run: npm test + + - name: Upload Playwright report + if: always() + uses: actions/upload-artifact@v4 + with: + name: storefront-playwright-report + path: | + e2e/playwright-report + e2e/test-results + if-no-files-found: ignore + retention-days: 14 From 55bff9afd3d30a7f3006fcd0a364131e316ed4cc Mon Sep 17 00:00:00 2001 From: Abass Ibrahim <136358470+Lingz450@users.noreply.github.com> Date: Sat, 11 Jul 2026 11:24:09 +0100 Subject: [PATCH 11/14] docs explain storefront verification harness --- e2e/README.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 e2e/README.md diff --git a/e2e/README.md b/e2e/README.md new file mode 100644 index 0000000..f82db80 --- /dev/null +++ b/e2e/README.md @@ -0,0 +1,31 @@ +# Fádé storefront verification + +This isolated Playwright package verifies the public perfume storefront without changing the application dependency graph. + +## Run + +```bash +cd e2e +npm ci +npx playwright install chromium +npm test +``` + +By default the suite targets `https://9thluxe-store-two.vercel.app`. +Set `E2E_BASE_URL` to a local server or Vercel preview deployment when validating branch changes: + +```bash +E2E_BASE_URL=http://localhost:3000 npm test +``` + +## Coverage + +- public route health checks +- mobile navigation open, close and route transition behaviour +- mobile horizontal overflow +- custom portalled shop dropdowns +- reduced-motion handling +- visible em-dash regression +- axe WCAG critical and serious violations + +Reports, traces, screenshots and videos are retained for failed CI tests. From 0d5c7348acff661a2953ce37859151e7f275c7e0 Mon Sep 17 00:00:00 2001 From: Abass Ibrahim <136358470+Lingz450@users.noreply.github.com> Date: Sat, 11 Jul 2026 11:24:13 +0100 Subject: [PATCH 12/14] chore ignore Playwright artifacts --- e2e/.gitignore | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 e2e/.gitignore diff --git a/e2e/.gitignore b/e2e/.gitignore new file mode 100644 index 0000000..dc25d7f --- /dev/null +++ b/e2e/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +playwright-report/ +test-results/ +blob-report/ From 610b186ecdf3e149c77cc6d4a5b06bbbf80b5fd7 Mon Sep 17 00:00:00 2001 From: Abass Ibrahim <136358470+Lingz450@users.noreply.github.com> Date: Sat, 11 Jul 2026 11:24:25 +0100 Subject: [PATCH 13/14] docs record verified continuation state --- docs/CONTINUATION_HANDOFF_2026-07-11.md | 35 +++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 docs/CONTINUATION_HANDOFF_2026-07-11.md diff --git a/docs/CONTINUATION_HANDOFF_2026-07-11.md b/docs/CONTINUATION_HANDOFF_2026-07-11.md new file mode 100644 index 0000000..1dbc9ee --- /dev/null +++ b/docs/CONTINUATION_HANDOFF_2026-07-11.md @@ -0,0 +1,35 @@ +# Fádé continuation handoff, 11 July 2026 + +## Repository state inspected + +- Repository: `Idansss/9thluxe-store` +- Default branch inspected: `main` +- Main head at inspection: `3398f61a3c5c217c7b2ab4fd2952af25abc77f5a` +- Working branch: `agent/mobile-verification-harness` +- Existing PDP TODO and handoff documents were reviewed before implementation. +- Completed backend, commerce, authentication, cart, checkout and PDP work has not been replaced. + +## First genuinely unfinished engineering requirement + +The existing PDP handoff identifies automated Playwright, visual, mobile and accessibility verification as the top remaining engineering item. This branch begins that work rather than repeating the completed redesign. + +## Changes on this branch + +- Mobile navigation is controlled and closes on route changes. +- The mobile sheet has one close control instead of duplicate controls. +- Sheet title and description are present for assistive technology. +- Search is directly accessible on mobile without nesting an interactive dialog trigger inside a close primitive. +- Sheet and header motion respect `prefers-reduced-motion`. +- An isolated `e2e` package adds Playwright and axe checks for public route health, mobile navigation, overflow, custom portalled selects, reduced motion, visible em dashes and serious accessibility violations. +- CI stores traces, screenshots, videos and HTML reports when checks fail. + +## Preserved constraints + +- Perfume-only catalogue direction remains unchanged. +- No product, review, stock, ingredient, accord or performance data is invented. +- Existing backend contracts and business logic are untouched. +- The current Radix Select implementation already uses a portal and remains the custom dropdown foundation. + +## Next after verification + +Use failures from the automated suite to repair actual route, mobile, accessibility and visible-copy regressions. After that, continue the Fádé Scent Atlas as an approval-driven content system: approved ingredient assets and manually approved AI drafts only, with accord values described as perceived prominence rather than formulation percentages. From 9be9067fbc14a7f99907a23ac79bf5f582ed0dc0 Mon Sep 17 00:00:00 2001 From: Ghost69 Date: Sat, 11 Jul 2026 21:54:39 +0100 Subject: [PATCH 14/14] fix(build): exclude isolated e2e harness from the root TypeScript build The e2e/ Playwright harness is deliberately isolated with its own package.json, so @playwright/test is not installed at the repo root. Next production builds type-check every .ts file the root tsconfig includes, so e2e/playwright.config.ts failed to resolve @playwright/test and broke every Vercel deployment of this branch since 76788e4. Excluding e2e/**/* matches the existing FRONTEND/**/* exclusion; the harness type-checks against its own node_modules instead. Co-Authored-By: Claude Fable 5 --- tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index b2b8119..eef7587 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -37,6 +37,7 @@ ], "exclude": [ "node_modules", - "FRONTEND/**/*" + "FRONTEND/**/*", + "e2e/**/*" ] }