diff --git a/frontend/components/account/AccountCenter.tsx b/frontend/components/account/AccountCenter.tsx index dbf82d8a..913eb8e6 100644 --- a/frontend/components/account/AccountCenter.tsx +++ b/frontend/components/account/AccountCenter.tsx @@ -2,7 +2,6 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import Link from "next/link"; -import dynamic from "next/dynamic"; import { useRouter } from "next/navigation"; import type { User } from "@supabase/supabase-js"; import { @@ -48,17 +47,7 @@ import { } from "@/lib/payment-host"; import { trackAppEvent } from "@/lib/app-analytics"; import { useI18n } from "@/hooks/useI18n"; - -const UnlockProOverlay = dynamic( - () => - import("@/components/subscription/UnlockProOverlay").then( - (module) => module.UnlockProOverlay, - ), - { - ssr: false, - loading: () => null, - }, -); +import { UnlockProOverlay } from "@/components/subscription/UnlockProOverlay"; // --- Types --- diff --git a/frontend/components/account/__tests__/paymentShell.test.ts b/frontend/components/account/__tests__/paymentShell.test.ts new file mode 100644 index 00000000..a525f687 --- /dev/null +++ b/frontend/components/account/__tests__/paymentShell.test.ts @@ -0,0 +1,35 @@ +import fs from "node:fs"; +import path from "node:path"; + +function assert(condition: unknown, message: string) { + if (!condition) throw new Error(message); +} + +export function runTests() { + const projectRoot = process.cwd(); + const accountCenterPath = path.join( + projectRoot, + "components", + "account", + "AccountCenter.tsx", + ); + const serviceWorkerPath = path.join(projectRoot, "public", "sw.js"); + + const accountCenterSource = fs.readFileSync(accountCenterPath, "utf8"); + const serviceWorkerSource = fs.readFileSync(serviceWorkerPath, "utf8"); + + assert( + accountCenterSource.includes( + 'import { UnlockProOverlay } from "@/components/subscription/UnlockProOverlay";', + ), + "checkout overlay must be in the account bundle, not lazy-loaded after the user clicks pay", + ); + assert( + !/const\s+UnlockProOverlay\s*=\s*dynamic\s*\(/.test(accountCenterSource), + "checkout overlay must not be dynamically imported; stale deployments can make the lazy chunk fail at pay time", + ); + assert( + !/STATIC_ASSETS\s*=\s*\[[^\]]*["']\/_next\//s.test(serviceWorkerSource), + "service worker must not cache-first the whole /_next/ tree; stale chunks break checkout after deploys", + ); +} diff --git a/frontend/public/sw.js b/frontend/public/sw.js index f0be9bf1..394a47e8 100644 --- a/frontend/public/sw.js +++ b/frontend/public/sw.js @@ -1,5 +1,14 @@ -const CACHE_NAME = "polyweather-v1"; -const STATIC_ASSETS = ["/_next/", "/favicon"]; +const CACHE_NAME = "polyweather-v2"; +const CACHEABLE_PUBLIC_ASSETS = [ + "/favicon.ico", + "/favicon-16x16.png", + "/favicon-32x32.png", + "/apple-touch-icon.png", + "/android-chrome-192x192.png", + "/android-chrome-512x512.png", + "/manifest.webmanifest", + "/site.webmanifest", +]; self.addEventListener("install", (event) => { event.waitUntil(self.skipWaiting()); @@ -7,18 +16,27 @@ self.addEventListener("install", (event) => { self.addEventListener("activate", (event) => { event.waitUntil( - caches.keys().then((keys) => - Promise.all( - keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key)), - ), - ), + caches + .keys() + .then((keys) => + Promise.all( + keys + .filter((key) => key !== CACHE_NAME) + .map((key) => caches.delete(key)), + ), + ) + .then(() => self.clients.claim()), ); }); self.addEventListener("fetch", (event) => { const url = new URL(event.request.url); - if (STATIC_ASSETS.some((prefix) => url.pathname.startsWith(prefix))) { + // Do not cache-first Next.js build chunks. A user can keep an old app shell + // open across deployments; if checkout UI is loaded later, stale/missing + // chunks surface as a generic page fault. Let the browser/Vercel handle + // immutable _next/static asset caching instead. + if (CACHEABLE_PUBLIC_ASSETS.includes(url.pathname)) { event.respondWith( caches.open(CACHE_NAME).then((cache) => cache.match(event.request).then( diff --git a/frontend/scripts/run-business-state-tests.mjs b/frontend/scripts/run-business-state-tests.mjs index 74e1ec3d..3c498a88 100644 --- a/frontend/scripts/run-business-state-tests.mjs +++ b/frontend/scripts/run-business-state-tests.mjs @@ -84,7 +84,7 @@ function collectTests(dir) { }); } -const testsRoot = path.join(projectRoot, "components", "dashboard", "scan-terminal", "__tests__"); +const testsRoot = path.join(projectRoot, "components"); const testFiles = fs.existsSync(testsRoot) ? collectTests(testsRoot).sort() : []; if (!testFiles.length) {