diff --git a/CHANGELOG.md b/CHANGELOG.md index 564529c9..e2cf935a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,7 @@ latest tag; security fixes land on the latest release (see `SECURITY.md`). - TypeScript 7: the typecheck gate runs on the stable native (Go) compiler — `typescript@^7.0.2` installed under the `typescript7` npm alias, invoked by path (`bun node_modules/typescript7/bin/tsc --noEmit`) from `check`, `typecheck`, `typecheck:watch`, and the lefthook pre-commit hook. The classic `typescript@^6` package keeps the `typescript` name because Next 16.2's build-time type checking and dev server require the classic JS compiler API, which the TS7 native package does not ship — when Next supports TS7 directly, the alias flips to owning the name. The preview-era machinery is gone: the `@typescript/native-preview` (`tsgo`) dependency, the `typecheck:tsc`/`typecheck:compare` cross-check scripts, and the obsolete `typescript.experimental.useTsgo` VS Code flag. Operational wins from the native compiler: the pre-commit typecheck drops `--incremental false` (the TS7 alias is the sole `.tsbuildinfo` writer, so the ~4x-faster warm cache is safe) and `typecheck:watch` uses TS7's rebuilt native file watcher. - Whole-codebase maintainability pass (net −1.2k lines): removed a dead Zustand WebGL-tunnel store; deduped the Shopify integration install path through `installBundle` and the fluid/flowmap pointer handling through a shared `usePointerInput`; split the 1k-line `ast-transforms.ts` into focused `helpers`/`remove-ops`/`add-ops`/`index` modules (behind a barrel re-export) and trimmed `setup-project.ts` under 1k; made bundle-key typing honest (`BundleId` + a `getBundle` accessor for the wider `RemovableId` space) and removed dishonestly-optional types (`CartLineItem.id`, `CartMeta`, a discriminated `CanvasContextValue`); and routed server-side integration env reads through the typed `@/lib/env` accessor. No consumer-facing API change. +- Next.js 16.3 preview: bumped `next` to the `preview` dist-tag, enabled top-level `partialPrefetching: true` (the 16.3 successor to `experimental.prefetchInlining`), removed the now-superseded `experimental.prefetchInlining` flag from the `experimental` block, and replaced the placeholder `app/loading.tsx` with an accessible skeleton shell (`role="status"`, `aria-busy="true"`, `sr-only` label, `animate-pulse` bars) demonstrating the instant-navigation loading pattern. Added throwaway, branch-only demo routes under `app/instant-nav-demo/` (hub + `fast`/`slow`/`[item]` routes exercising shell prefetch, partial streaming via ``/`connection()`, and a reused dynamic-route `loading.tsx` shell) so the preview deploy can actually test instant navigations; reachable at `/instant-nav-demo` and never merged to main. Also enabled the 16.3 Turbopack experimental flags `turbopackFileSystemCacheForBuild` (persistent build cache) and `turbopackRustReactCompiler` (native React Compiler, pairs with `reactCompiler: true`) to validate build-perf on the same preview. - Dependencies: bumped every package to its latest version. The only breaking change was `tempus` dev.18, whose `TempusCallback` moved from positional `(time, deltaTime)` arguments to a single `TempusState` object (`{ time, deltaTime, frame, budget }`) and deprecated the `priority` option in favor of `order` — migrated all seven call sites (the GSAP, Lenis, marquee, and WebGL RAF runtimes, the `lib/utils/raf` write queue, and the dev `Stats` overlay). Also bumped `@base-ui/react` 1.6, `next-sanity` 13.1, `@types/node` 26, `deslop-cli` 0.5.8, `@biomejs/biome` 2.5.1, Storybook 10.4.6, Tailwind 4.3.1, and `@typescript/native-preview`; `actions/checkout` 6 → 7 in CI. Supersedes #251–254. (#255) - Dependencies: bumped the Sanity toolchain to v6 (`sanity`, `@sanity/vision`, `groq` → `^6.1.0`) — Studio v6's breaking changes (Node 20 dropped, `auth.providers` replace semantics, `enableLegacySearch` removed) don't touch the integration's API surface, so forks on Node ≥ 22 update without code changes. Also bumped `@biomejs/biome` 2.5.0, `deslop-cli` 0.0.25, and `@typescript/native-preview`. Biome 2.5's newly-enabled rules required a `` on `darkroom.svg` (`noSvgWithoutTitle`) and an import-spacing fix in generated `sanity.types.ts` (`organizeImports`); the inline version `<Script>` carries a documented `useInlineScriptId` ignore (the `id` is present — biome mis-detects JSX-children content). Supersedes #221–226. (#228) - Integration registry is single-source: `INTEGRATION_BUNDLES` keys are typed against `RemovableId` from `lib/integrations/registry`, `prepare-handoff` matches integrations by id, and `next.config.ts` cleanup runs through typed ts-morph AST ops — the regex `updateNextConfig` is deleted. diff --git a/app/instant-nav-demo/README.md b/app/instant-nav-demo/README.md new file mode 100644 index 00000000..38051084 --- /dev/null +++ b/app/instant-nav-demo/README.md @@ -0,0 +1,17 @@ +# instant-nav-demo — throwaway + +These routes exist ONLY on the `next/16.3-preview` branch to exercise Next.js +16.3 Instant Navigations (`partialPrefetching`) on a real deploy. They are NOT +starter content and must never merge to `main`. + +- `page.tsx` — hub linking to the demo routes (prefetch fires on hover/viewport) +- `fast/` — synchronous route, the prefetched shell is the whole page +- `slow/` — shell paints immediately; an inner `<Suspense>` panel streams in + after a delay (`connection()` + artificial wait) — partial prerendering +- `[item]/` — dynamic route that suspends at the top level, so its `loading.tsx` + shell is shown on the way in and reused identically for every param value + (`/instant-nav-demo/alpha`, `/instant-nav-demo/bravo`, + `/instant-nav-demo/charlie`) + +Reach it on the preview at `/instant-nav-demo`. Delete this whole folder before +adopting anything from this branch. diff --git a/app/instant-nav-demo/[item]/loading.tsx b/app/instant-nav-demo/[item]/loading.tsx new file mode 100644 index 00000000..e258a3b3 --- /dev/null +++ b/app/instant-nav-demo/[item]/loading.tsx @@ -0,0 +1,17 @@ +export default function Loading() { + return ( + // <output> has an implicit role="status"; `flex` overrides its default + // inline display, so the layout is unchanged. + <output + aria-busy="true" + className="gap-3 flex min-h-dvh flex-col items-center justify-center font-mono" + > + <span className="sr-only">Loading item</span> + <div className="w-40 animate-pulse space-y-2"> + <div className="h-2 rounded bg-current opacity-20" /> + <div className="h-2 rounded w-3/4 bg-current opacity-20" /> + <div className="h-2 rounded w-1/2 bg-current opacity-10" /> + </div> + </output> + ) +} diff --git a/app/instant-nav-demo/[item]/page.tsx b/app/instant-nav-demo/[item]/page.tsx new file mode 100644 index 00000000..d43670f1 --- /dev/null +++ b/app/instant-nav-demo/[item]/page.tsx @@ -0,0 +1,41 @@ +import { connection } from 'next/server' + +import { Wrapper } from '@/components/layout/wrapper' +import { Link } from '@/components/ui/link' + +export const metadata = { + title: 'Dynamic route — Instant Navigations demo', +} + +export default async function ItemPage({ + params, +}: { + params: Promise<{ item: string }> +}) { + // `params` and `connection()` are independent, so they resolve together. + const [{ item }] = await Promise.all([params, connection()]) + // The delay stays sequential on purpose — a slow route is what this demo is + // demonstrating. + await new Promise((resolve) => setTimeout(resolve, 1200)) + + return ( + <Wrapper theme="dark"> + <section className="py-24 dr-layout-grid min-h-dvh content-center font-mono"> + <div className="gap-6 col-span-full flex flex-col dt:col-start-2 dt:col-end-11"> + <h1 className="text-2xl uppercase">Dynamic route: {item}</h1> + <p className="max-w-prose opacity-70"> + One reusable shell is prefetched for this route and shown on the way + in (see the skeleton). It is identical for every param value —{' '} + <code>alpha</code>, <code>bravo</code>, and <code>charlie</code>. + </p> + <Link + className="underline underline-offset-4" + href="/instant-nav-demo" + > + ← Back to demo hub + </Link> + </div> + </section> + </Wrapper> + ) +} diff --git a/app/instant-nav-demo/fast/page.tsx b/app/instant-nav-demo/fast/page.tsx new file mode 100644 index 00000000..98ed1bcb --- /dev/null +++ b/app/instant-nav-demo/fast/page.tsx @@ -0,0 +1,28 @@ +import { Wrapper } from '@/components/layout/wrapper' +import { Link } from '@/components/ui/link' + +export const metadata = { + title: 'Fast route — Instant Navigations demo', +} + +export default function FastPage() { + return ( + <Wrapper theme="dark"> + <section className="py-24 dr-layout-grid min-h-dvh content-center font-mono"> + <div className="gap-6 col-span-full flex flex-col dt:col-start-2 dt:col-end-11"> + <h1 className="text-2xl uppercase">Fast route</h1> + <p className="max-w-prose opacity-70"> + Fully static — no async data. The prefetched shell IS the page, so + navigation here is immediate with nothing to stream. + </p> + <Link + className="underline underline-offset-4" + href="/instant-nav-demo" + > + ← Back to demo hub + </Link> + </div> + </section> + </Wrapper> + ) +} diff --git a/app/instant-nav-demo/page.tsx b/app/instant-nav-demo/page.tsx new file mode 100644 index 00000000..b0fa0ba4 --- /dev/null +++ b/app/instant-nav-demo/page.tsx @@ -0,0 +1,53 @@ +import { Wrapper } from '@/components/layout/wrapper' +import { Link } from '@/components/ui/link' + +export const metadata = { + title: 'Instant Navigations demo', +} + +const ITEMS = ['alpha', 'bravo', 'charlie'] + +export default function InstantNavDemoPage() { + return ( + <Wrapper theme="dark"> + <section className="py-24 dr-layout-grid min-h-dvh content-center font-mono"> + <div className="gap-6 col-span-full flex flex-col dt:col-start-2 dt:col-end-11"> + <h1 className="text-2xl uppercase">Instant Navigations demo</h1> + <p className="max-w-prose opacity-70"> + Throwaway routes for exercising Next.js 16.3 shell-based + prefetching. Hover a link to prefetch its shell, then click to see + the shell paint instantly while data streams in. + </p> + + <nav className="gap-3 flex flex-col"> + <Link + className="underline underline-offset-4" + href="/instant-nav-demo/fast" + > + → Fast route (synchronous, paints immediately) + </Link> + <Link + className="underline underline-offset-4" + href="/instant-nav-demo/slow" + > + → Slow route (shell first, panel streams in) + </Link> + {ITEMS.map((item) => ( + <Link + key={item} + className="underline underline-offset-4" + href={`/instant-nav-demo/${item}`} + > + → Dynamic route: {item} (one shell, reused per param) + </Link> + ))} + </nav> + + <Link className="underline underline-offset-4 opacity-60" href="/"> + ← Home + </Link> + </div> + </section> + </Wrapper> + ) +} diff --git a/app/instant-nav-demo/slow/page.tsx b/app/instant-nav-demo/slow/page.tsx new file mode 100644 index 00000000..da7067e5 --- /dev/null +++ b/app/instant-nav-demo/slow/page.tsx @@ -0,0 +1,65 @@ +import { connection } from 'next/server' +import { Suspense } from 'react' + +import { Wrapper } from '@/components/layout/wrapper' +import { Link } from '@/components/ui/link' + +export const metadata = { + title: 'Slow route — Instant Navigations demo', +} + +// connection() marks this subtree runtime-dynamic; the artificial delay makes +// the stream visible. Lives inside <Suspense> so the page shell paints +// immediately and only this panel streams in (partial prerendering under +// cacheComponents). +async function StreamedPanel() { + await connection() + await new Promise((resolve) => setTimeout(resolve, 1500)) + return ( + <p className="max-w-prose opacity-90"> + Streamed in after ~1.5s. The heading and back-link above were already on + screen — only this panel waited on the server. + </p> + ) +} + +function PanelSkeleton() { + return ( + // `block` is required: <output> is inline by default, and `w-40` would do + // nothing on an inline element. The implicit role="status" replaces the + // explicit one. + <output aria-busy="true" className="w-40 block"> + <span className="sr-only">Loading panel</span> + <div aria-hidden className="animate-pulse space-y-2"> + <div className="h-2 rounded bg-current opacity-20" /> + <div className="h-2 rounded w-3/4 bg-current opacity-20" /> + <div className="h-2 rounded w-1/2 bg-current opacity-10" /> + </div> + </output> + ) +} + +export default function SlowPage() { + return ( + <Wrapper theme="dark"> + <section className="py-24 dr-layout-grid min-h-dvh content-center font-mono"> + <div className="gap-6 col-span-full flex flex-col dt:col-start-2 dt:col-end-11"> + <h1 className="text-2xl uppercase">Slow route</h1> + <p className="max-w-prose opacity-70"> + The shell (this heading and the link below) paints instantly. The + panel streams in once the server resolves. + </p> + <Suspense fallback={<PanelSkeleton />}> + <StreamedPanel /> + </Suspense> + <Link + className="underline underline-offset-4" + href="/instant-nav-demo" + > + ← Back to demo hub + </Link> + </div> + </section> + </Wrapper> + ) +} diff --git a/app/loading.tsx b/app/loading.tsx index fc97e1a2..f3053682 100644 --- a/app/loading.tsx +++ b/app/loading.tsx @@ -3,8 +3,19 @@ // and fails prerendering (e.g. /studio). Keep this fallback dependency-free. export default function Loading() { return ( - <div className="flex min-h-dvh flex-col items-center justify-center font-mono uppercase"> - <p>Cooking...</p> - </div> + // <output> carries an implicit role="status", so the role is redundant. + // `flex` overrides the element's default inline display, so the layout is + // unchanged. + <output + aria-busy="true" + className="gap-3 flex min-h-dvh flex-col items-center justify-center font-mono" + > + <span className="sr-only">Loading</span> + <div className="w-40 animate-pulse space-y-2"> + <div className="h-2 rounded bg-current opacity-20" /> + <div className="h-2 rounded w-3/4 bg-current opacity-20" /> + <div className="h-2 rounded w-1/2 bg-current opacity-10" /> + </div> + </output> ) } diff --git a/bun.lock b/bun.lock index b95d6dbb..8cd9fed6 100644 --- a/bun.lock +++ b/bun.lock @@ -18,7 +18,7 @@ "gsap": "^3.15.0", "hamo": "^1.0.1", "lenis": "^1.3.25", - "next": "16.2.12", + "next": "16.3.0-preview.10", "next-sanity": "^13.2.3", "postprocessing": "^6.39.4", "react": "19.2.8", @@ -34,7 +34,7 @@ "@clack/prompts": "^1.7.0", "@csstools/postcss-global-data": "^4.0.0", "@happy-dom/global-registrator": "^20.11.1", - "@next/bundle-analyzer": "16.2.12", + "@next/bundle-analyzer": "16.3.0-preview.10", "@playwright/test": "1.62.0", "@sanity/vision": "^6.7.0", "@storybook/addon-mcp": "^0.7.0", @@ -599,53 +599,57 @@ "@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="], - "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="], + "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.3.2" }, "os": "darwin", "cpu": "arm64" }, "sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg=="], - "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="], + "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.3.2" }, "os": "darwin", "cpu": "x64" }, "sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w=="], - "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="], + "@img/sharp-freebsd-wasm32": ["@img/sharp-freebsd-wasm32@0.35.3", "", { "dependencies": { "@img/sharp-wasm32": "0.35.3" }, "os": "freebsd" }, "sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg=="], - "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="], + "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.3.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg=="], - "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="], + "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.3.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw=="], - "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="], + "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.3.2", "", { "os": "linux", "cpu": "arm" }, "sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ=="], - "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="], + "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA=="], - "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="], + "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.3.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw=="], - "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="], + "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.3.2", "", { "os": "linux", "cpu": "none" }, "sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w=="], - "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="], + "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.3.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ=="], - "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="], + "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w=="], - "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="], + "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw=="], - "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="], + "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ=="], - "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="], + "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.3.2" }, "os": "linux", "cpu": "arm" }, "sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA=="], - "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="], + "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.3.2" }, "os": "linux", "cpu": "arm64" }, "sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ=="], - "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="], + "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.3.2" }, "os": "linux", "cpu": "ppc64" }, "sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA=="], - "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="], + "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.3.2" }, "os": "linux", "cpu": "none" }, "sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ=="], - "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="], + "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.3.2" }, "os": "linux", "cpu": "s390x" }, "sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw=="], - "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="], + "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.3.2" }, "os": "linux", "cpu": "x64" }, "sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA=="], - "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="], + "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.3.2" }, "os": "linux", "cpu": "arm64" }, "sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w=="], - "@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="], + "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.3.2" }, "os": "linux", "cpu": "x64" }, "sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg=="], - "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="], + "@img/sharp-wasm32": ["@img/sharp-wasm32@0.35.3", "", { "dependencies": { "@emnapi/runtime": "^1.11.1" } }, "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w=="], - "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="], + "@img/sharp-webcontainers-wasm32": ["@img/sharp-webcontainers-wasm32@0.35.3", "", { "dependencies": { "@img/sharp-wasm32": "0.35.3" }, "cpu": "none" }, "sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q=="], - "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="], + "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.35.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w=="], + + "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.35.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw=="], + + "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.35.3", "", { "os": "win32", "cpu": "x64" }, "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA=="], "@inquirer/ansi": ["@inquirer/ansi@1.0.2", "", {}, "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ=="], @@ -741,25 +745,25 @@ "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.6", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg=="], - "@next/bundle-analyzer": ["@next/bundle-analyzer@16.2.12", "", { "dependencies": { "webpack-bundle-analyzer": "4.10.1" } }, "sha512-0dYhmCYsTMFYUFaoEUx+VKw/oYP6b3XiGgq47EujXTE2UGgwVWAaur2tbko2pxfUka3WRZ9YWxa3PlSv3luWNQ=="], + "@next/bundle-analyzer": ["@next/bundle-analyzer@16.3.0-preview.10", "", { "dependencies": { "webpack-bundle-analyzer": "4.10.1" } }, "sha512-9x7sNN+MLcz5ETF7UDfE3BV658wzNNMer/ZRshfI7xMBkbGYewiFG/0zaDzZQRWFJ7/lrr08tFm/rK39TqKGjA=="], - "@next/env": ["@next/env@16.2.12", "", {}, "sha512-d0Z5Bc13Fa4nR8pFAKx2jay2yhJM16vlfHbTzYnUQAxlNb6B6lmn4hjt69lYNt4kRtyYP6gEM49lPRHNbIyneg=="], + "@next/env": ["@next/env@16.3.0-preview.10", "", {}, "sha512-R4uP3RswLWJnbsGqbtoHnE5cZY+2VR7KDMyp/UMvBX/SFRNF/YxTs0Dikr+fL4zhyrFKoejn5Iv+24vzZrNn6g=="], - "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.2.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-0W1R0teHWJrqKX0FH20IzzIWAOuGtBxPGuObrxy1lE8hQvCFj49KE8a3WUg0D7sq6rn6zkM4c7YGUnhudBS6oA=="], + "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.3.0-preview.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Jslk3Uf38yPaN7HOw89ewSHmeWb58uRxlqtguXoq4hWrAQl6mtGIeRNvZpOMOQg7/KCf8VOO+LYj9Elulnui5g=="], - "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.2.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-Hy5Ls099+aFUmOLmIgPfLqNi6iCwhL3uQCssz5rWk+5Nkc6TUKCE83DY5BbNylfm3+mfwcSFnLRfrZDJhVxdtw=="], + "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.3.0-preview.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-Dfb4xQSTqJLDmMTJ+HFt1i9p5UwHs0nmYbWkAt3BNwIbn4+oLgTMy9b6JxQkDyU0jGUgH2JrE+pfgiD1jXNBoA=="], - "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.2.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-+YqU2h1cQkHsGfvjAsrSmst8UIFBibBGm5x3Xgel8NLMiDQtNOM4sM2GOEMvG5YiOBNeN/Ykk8cQC2S0Xrqljg=="], + "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.3.0-preview.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-JVhEe2YonY9GaXi8XOT2uwQlH9WfzEDg3+1x+j2lhmpfsl0qFVguwYwzc5o+XK1BdXDI2WLrvRTjmG29SXz7WA=="], - "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.2.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-0qjhiYBaKAqF63LA1ZWAAnKTzFUguAaZiRa5etMLGGPj/B6uEVjtIZldIzFEp3wHlB0koK6aTzqPtSdplTCjoA=="], + "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.3.0-preview.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-ftdSy3vYHmrGPBPSVIpKstmjZeI7kd83/5Q6cgcwPLyzOQzQEhcGqQM0u+HLq+lIcephTh6oTtwvOvx6QxWcyQ=="], - "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.2.12", "", { "os": "linux", "cpu": "x64" }, "sha512-7A3q26W+h7gnA15uqBToNuDqBEFZZcqh0mW2mn4AJh/G5pdg2RVE3n4slzLEliASZFG3NmsbEzng/x2Sh09mBg=="], + "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.3.0-preview.10", "", { "os": "linux", "cpu": "x64" }, "sha512-iDMWCIq9S1nYTDRs50sKcp05nzYJkBE8YG869tieA9W+fAKBkL005xBJuqL6R6iBGYav80vXhO58ias6i0ApBw=="], - "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.2.12", "", { "os": "linux", "cpu": "x64" }, "sha512-qSjL/uppm+cbh21s72Ss8gkiOhQ4dExWHNGOWy6eZV7STj5WsKehgxT61beSsOj+YYQuTplL376lOCdMQU5T8w=="], + "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.3.0-preview.10", "", { "os": "linux", "cpu": "x64" }, "sha512-p8AVApwWPAmVS0gzk119ZxqZUB6Jc6uQ/USbWzJNOBSmG9PWSg+SS+rQPG+zHi8lWyFO++VwiLghG7jI/VjIbw=="], - "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.2.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-X6hzsOUJac/e7AWSbn9gQ9nzHld1xWP5iyjHpYWvud8pufB679O1xg4JDyKr8Xd69Jvd+kM2Der6uftiZCmjYA=="], + "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.3.0-preview.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-khUIO/lttNa7qyuQVDic2rP4IF3Wx9tOsoQlXzcpAfmHmlRZxtbtNfG6RbUcWV1GngbffTXMTvwTMAHmuqkY5w=="], - "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.2.12", "", { "os": "win32", "cpu": "x64" }, "sha512-F6fakeHuFTLOPt0bslQJdf+xtT+WIP9DVn/m4y1w1mRnVPyh3D/cNvzlRkxM444xfm+IvvYNSOrKiA2CDJ0Uxw=="], + "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.3.0-preview.10", "", { "os": "win32", "cpu": "x64" }, "sha512-oYKDXWAhMii9inJEUfPUm6v23My8HEaYqY8YQfOArqvTLz4gzt0w3yqKcrzxiWu7tHvBMIqZduD9j/hVdLfqkw=="], "@noble/ed25519": ["@noble/ed25519@3.1.0", "", {}, "sha512-pfcObRY3CtvwfaG9Mt5XqZdKmAQppl37tHUeuBhDUbiwJBCVY4/A4lbMvb1xKhMDx96AqAqZpMWuBX1HulhX4g=="], @@ -2331,7 +2335,7 @@ "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], - "next": ["next@16.2.12", "", { "dependencies": { "@next/env": "16.2.12", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.2.12", "@next/swc-darwin-x64": "16.2.12", "@next/swc-linux-arm64-gnu": "16.2.12", "@next/swc-linux-arm64-musl": "16.2.12", "@next/swc-linux-x64-gnu": "16.2.12", "@next/swc-linux-x64-musl": "16.2.12", "@next/swc-win32-arm64-msvc": "16.2.12", "@next/swc-win32-x64-msvc": "16.2.12", "sharp": "^0.34.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-iD59eYQWmbFcEbX7v/acG5DRym9iw1DdaPoD0WTA920naWsE25wShzJW4+UvAs8MK9EC2kBfIH6vtto1H1PHGw=="], + "next": ["next@16.3.0-preview.10", "", { "dependencies": { "@next/env": "16.3.0-preview.10", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.5.10", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.3.0-preview.10", "@next/swc-darwin-x64": "16.3.0-preview.10", "@next/swc-linux-arm64-gnu": "16.3.0-preview.10", "@next/swc-linux-arm64-musl": "16.3.0-preview.10", "@next/swc-linux-x64-gnu": "16.3.0-preview.10", "@next/swc-linux-x64-musl": "16.3.0-preview.10", "@next/swc-win32-arm64-msvc": "16.3.0-preview.10", "@next/swc-win32-x64-msvc": "16.3.0-preview.10", "sharp": "^0.35.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-eObeUsXGUpcUgIjK3rCKnD+3xF8kbR13bznbzyqJLlWGdbqQuLYt6DYks3n5pdYYrNFA2qgjoisItDzaRHKJQQ=="], "next-sanity": ["next-sanity@13.2.3", "", { "dependencies": { "@portabletext/react": "^7.0.1", "@sanity/client": "^7.25.0", "@sanity/generate-help-url": "^4.0.0", "@sanity/preview-url-secret": "^4.1.2", "@sanity/visual-editing": "^5.7.3", "@sanity/webhook": "^4.0.4", "groq": "^6.7.0", "history": "^5.3.0" }, "peerDependencies": { "next": "^16.0.0-0", "react": "^19.2.3", "react-dom": "^19.2.3", "sanity": "^5.29.0 || ^6.0.0", "styled-components": "^6.1" } }, "sha512-tvZ4sdCGvq6HVbJ1eqdmVamZ2RmkxZCUImFlE7j4f17hvg7OdBY5PWf5b26BUynvoR6RGyY9oAD9MMei6hAyLA=="], @@ -2673,7 +2677,7 @@ "shallow-equals": ["shallow-equals@1.0.0", "", {}, "sha512-xd/FKcdmfmMbyYCca3QTVEJtqUOGuajNzvAX6nt8dXILwjAIEkfHc4hI8/JMGApAmb7VeULO0Q30NTxnbH/15g=="], - "sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="], + "sharp": ["sharp@0.35.3", "", { "dependencies": { "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", "semver": "^7.8.5" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.35.3", "@img/sharp-darwin-x64": "0.35.3", "@img/sharp-freebsd-wasm32": "0.35.3", "@img/sharp-libvips-darwin-arm64": "1.3.2", "@img/sharp-libvips-darwin-x64": "1.3.2", "@img/sharp-libvips-linux-arm": "1.3.2", "@img/sharp-libvips-linux-arm64": "1.3.2", "@img/sharp-libvips-linux-ppc64": "1.3.2", "@img/sharp-libvips-linux-riscv64": "1.3.2", "@img/sharp-libvips-linux-s390x": "1.3.2", "@img/sharp-libvips-linux-x64": "1.3.2", "@img/sharp-libvips-linuxmusl-arm64": "1.3.2", "@img/sharp-libvips-linuxmusl-x64": "1.3.2", "@img/sharp-linux-arm": "0.35.3", "@img/sharp-linux-arm64": "0.35.3", "@img/sharp-linux-ppc64": "0.35.3", "@img/sharp-linux-riscv64": "0.35.3", "@img/sharp-linux-s390x": "0.35.3", "@img/sharp-linux-x64": "0.35.3", "@img/sharp-linuxmusl-arm64": "0.35.3", "@img/sharp-linuxmusl-x64": "0.35.3", "@img/sharp-webcontainers-wasm32": "0.35.3", "@img/sharp-win32-arm64": "0.35.3", "@img/sharp-win32-ia32": "0.35.3", "@img/sharp-win32-x64": "0.35.3" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q=="], "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], @@ -3225,7 +3229,7 @@ "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], - "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], + "next/postcss": ["postcss@8.5.10", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ=="], "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], diff --git a/lib/scripts/bench-navigation.ts b/lib/scripts/bench-navigation.ts new file mode 100644 index 00000000..63ff34ce --- /dev/null +++ b/lib/scripts/bench-navigation.ts @@ -0,0 +1,246 @@ +/** + * Client-side navigation benchmark. + * + * Lighthouse audits a single cold page load, which is the wrong instrument for + * Next's instant-navigation work: `partialPrefetching` and `cachedNavigations` + * change how fast an ALREADY-LOADED app swaps routes, and a load audit never + * navigates. This measures that instead. + * + * For each run it clicks a real <Link> and records: + * + * shell click -> the target route's heading is on screen. This is the one + * prefetching moves. + * content click -> the target's streamed content is on screen. Server-bound, + * so it should NOT improve — it is the control. Only reported for + * scenarios that actually have something streaming; see below. + * + * Reports the median plus the full sample, because TBT/TTI-style numbers on a + * dev machine are noisy enough that a small-sample median can be moved by one + * outlier — the sample is what tells you whether a delta is real. + * + * Usage: + * bun run build && PORT=3123 bun run start & + * bun run bench:nav # demo scenario + * bun run bench:nav --scenario notfound # works on any build + * bun run bench:nav --runs 10 --url http://localhost:3124 + * + * --------------------------------------------------------------------------- + * What it found (7 runs each, 100ms RTT, `demo` scenario, this branch on next + * 16.3.0-preview.10), toggling partialPrefetching + cachedNavigations + + * varyParams + optimisticRouting together: + * + * flags off flags on + * shell 110ms (spread 84-250) 90ms (spread 78-105) + * content 1420ms 1915ms + * + * The shell median moves a little, but the useful part is the spread: an + * on-demand shell fetch ranges 84-250ms, a prefetched one 78-105ms. Prefetching + * buys predictability more than raw speed. + * + * The content number is a genuine cost, not noise — it reproduced unthrottled + * (+537ms) and at 100ms RTT (+495ms). Splitting shell from data serialises + * them: the shell paints from cache, and only then does the dynamic request + * start, so this route's 1.5s server sleep begins ~500ms later than it would + * in a single streamed response. Whether that trade is right depends on + * whether an instant shell plus skeleton beats a shorter blank wait for the + * route in question. It is a trade, not a free win. + * + * The `notfound` scenario, same 7 runs and 100ms RTT, comparing `main` (next + * 16.2.12, no instant-nav flags) against this branch (16.3.0-preview.10, all + * four on): + * + * main shell 116ms (spread 83-223) + * branch shell 107ms (spread 81-327) + * + * No signal. 9ms apart with ~40ms of run-to-run variation and one large outlier + * on each side. That is the expected result, and it is the useful half of the + * comparison: `/` is statically prerendered, so there is no shell-versus-data + * split for `partialPrefetching` to make. A static route is equally + * prefetchable before and after. The flags target dynamic routes, which is + * precisely why `demo` shows a difference and this does not — so do not read + * this scenario as evidence the flags do nothing. + * --------------------------------------------------------------------------- + */ +import { chromium, type Page } from 'playwright-core' + +type Scenario = { + /** Path to load before clicking. */ + from: string + /** Heading that confirms the starting page is hydrated. */ + fromHeading: string | RegExp + /** Accessible name of the <Link> to click. */ + linkName: RegExp + /** Heading that confirms the target route's shell has painted. */ + shellHeading: string | RegExp + /** + * Text that confirms the target's server-bound content arrived. Omit when + * the target has nothing streaming — reporting a `content` number for a + * fully static route would just be a second, noisier reading of `shell`. + */ + contentText?: RegExp + describe: string +} + +const SCENARIOS: Record<string, Scenario> = { + /** + * The real measurement. Needs `app/instant-nav-demo/**`, which lives only on + * the Next 16.3 preview branch — those routes exist specifically to create a + * surface where instant navigation is observable. + */ + demo: { + from: '/instant-nav-demo', + fromHeading: 'Instant Navigations demo', + linkName: /slow route/i, + shellHeading: 'Slow route', + contentText: /Streamed in after/, + describe: 'demo hub -> /instant-nav-demo/slow (shell + streamed content)', + }, + + /** + * Fallback that runs against ANY build, including `main`. + * + * `main` has no client-side navigation surface on its happy path: the only + * internal <Link>s are in error.tsx and not-found.tsx, there is no header + * nav, and nothing links `/` to `/sanity`. Since prefetching is a <Link> + * behaviour, the 404's "Go Home" link is the one real navigation available. + * + * Read it narrowly. `/` is statically prerendered, so this times a prefetched + * STATIC shell and nothing else. It cannot show the shell-vs-data + * serialisation trade the `demo` scenario found, because there is no dynamic + * data to serialise — which is exactly why it reports no `content` number. + * Useful as a same-scenario A/B across two builds; not comparable to `demo`. + */ + notfound: { + from: '/__bench-nonexistent-path', + fromHeading: '404', + linkName: /go home/i, + shellHeading: 'Satūs', + describe: '404 -> / via "Go Home" (static shell only, no streamed content)', + }, +} + +type Sample = { shell: number; content?: number } + +const args = process.argv.slice(2) +function arg(name: string, fallback: string) { + const i = args.indexOf(`--${name}`) + return i !== -1 && args[i + 1] ? (args[i + 1] as string) : fallback +} + +const BASE = arg('url', 'http://localhost:3123').replace(/\/+$/, '') +const RUNS = Number(arg('runs', '7')) +const SCENARIO_NAME = arg('scenario', 'demo') + +/** + * Round-trip latency to emulate, in ms. This is not optional decoration: on + * localhost the RTT is ~0, and hiding round trips is the entire reason + * prefetching exists. Measured against an unthrottled local server the flags + * look useless — there is no latency for a prefetched shell to save, so the + * extra request the split introduces for dynamic content shows up as pure + * cost. Throttle, or the number you get is an artifact of the harness. + * + * `--latency 0` opts out, for when you specifically want the no-network case. + */ +const LATENCY = Number(arg('latency', '100')) +// Rough Fast-3G-ish pipe. Bandwidth matters far less than RTT here, but +// leaving it uncapped alongside a latency figure would be an odd mix. +const DOWNLOAD_MBPS = Number(arg('download', '10')) +const UPLOAD_MBPS = Number(arg('upload', '3')) + +const median = (xs: number[]) => + [...xs].sort((a, b) => a - b)[Math.floor(xs.length / 2)] ?? Number.NaN + +function heading(page: Page, name: string | RegExp) { + return page.getByRole('heading', { name }).first() +} + +async function main() { + const scenario = SCENARIOS[SCENARIO_NAME] + if (!scenario) { + console.error( + `Unknown scenario "${SCENARIO_NAME}". Available: ${Object.keys(SCENARIOS).join(', ')}` + ) + process.exit(1) + } + + const browser = await chromium.launch({ args: ['--no-sandbox'] }) + const page = await browser.newPage() + + if (LATENCY > 0) { + const cdp = await page.context().newCDPSession(page) + await cdp.send('Network.enable') + await cdp.send('Network.emulateNetworkConditions', { + offline: false, + latency: LATENCY, + downloadThroughput: (DOWNLOAD_MBPS * 1_000_000) / 8, + uploadThroughput: (UPLOAD_MBPS * 1_000_000) / 8, + }) + } + + const samples: Sample[] = [] + + for (let i = 0; i < RUNS; i++) { + // Full reload each iteration so every run starts from the same place: a + // freshly hydrated starting page. Without this, run 2 onward would measure + // a warm client cache and read far faster than reality. + await page.goto(`${BASE}${scenario.from}`, { waitUntil: 'load' }) + await heading(page, scenario.fromHeading).waitFor() + + // Let prefetching settle. This is the whole point of the flags — the shell + // should already be in the client cache before the click happens. + await page.waitForTimeout(1000) + + const started = Date.now() + await page.getByRole('link', { name: scenario.linkName }).first().click() + + await heading(page, scenario.shellHeading).waitFor() + const shell = Date.now() - started + + if (scenario.contentText) { + await page.getByText(scenario.contentText).waitFor({ timeout: 30_000 }) + samples.push({ shell, content: Date.now() - started }) + } else { + samples.push({ shell }) + } + + process.stdout.write('.') + } + + await browser.close() + + const shells = samples.map((s) => s.shell) + + console.log(`\n\nclient-side navigation: ${scenario.describe}`) + console.log( + `${BASE} · ${RUNS} runs · ${ + LATENCY > 0 + ? `${LATENCY}ms RTT, ${DOWNLOAD_MBPS}/${UPLOAD_MBPS} Mbps` + : 'unthrottled (no RTT — prefetching has nothing to hide, see header)' + }\n` + ) + console.log( + ` shell median ${median(shells).toFixed(0)}ms ${JSON.stringify(shells)}` + ) + + if (scenario.contentText) { + const contents = samples.map((s) => s.content ?? Number.NaN) + console.log( + ` content median ${median(contents).toFixed(0)}ms ${JSON.stringify(contents)}` + ) + console.log( + `\n shell is what prefetching moves. content is server-bound and is the` + + `\n control — it should not improve.\n` + ) + } else { + console.log( + `\n no content metric: this target has nothing streaming, so the only` + + `\n honest number is the shell. Compare against the same scenario on` + + `\n another build, not against the demo scenario.\n` + ) + } +} + +main().catch((error) => { + console.error(error) + process.exit(1) +}) diff --git a/lib/scripts/bench-rerender.ts b/lib/scripts/bench-rerender.ts index d57317c9..9876b8f1 100644 --- a/lib/scripts/bench-rerender.ts +++ b/lib/scripts/bench-rerender.ts @@ -5,11 +5,8 @@ * other two structurally cannot: * * bun run lighthouse one cold page load. Blind to re-renders entirely. - * (bench:nav) client-side route swaps. Lives on the Next 16.3 - * preview branch, because its useful scenario needs the - * instant-nav demo routes, which are branch-only. - * Measures navigation, not the work a mounted tree does - * when state changes. + * bun run bench:nav client-side route swaps. Measures navigation, not + * the work a mounted tree does when state changes. * bun run bench:rerender ← this. Main-thread cost of re-rendering an * already-mounted tree. * diff --git a/next.config.ts b/next.config.ts index 3fcfd04e..d1e5879d 100644 --- a/next.config.ts +++ b/next.config.ts @@ -93,6 +93,9 @@ const nextConfig: NextConfig = { : false, }, cacheComponents: true, + // 16.3: shell-based prefetching — the top-level successor to the + // experimental.prefetchInlining stepping stone; requires cacheComponents: true. + partialPrefetching: true, compress: true, logging: { fetches: { @@ -103,8 +106,28 @@ const nextConfig: NextConfig = { experimental: { taint: true, cachedNavigations: true, - prefetchInlining: true, + // The rest of the instant-navigation cluster that `partialPrefetching` and + // `cachedNavigations` belong to (they are passed to renderOpts together in + // next/dist/esm/server/base-server.js). + // + // `varyParams` lets Cache Components key a segment on the params it + // actually reads, instead of invalidating the whole segment whenever any + // param changes. It is the one with the most to gain here, since + // `cacheComponents` is already on. + varyParams: true, + // `optimisticRouting` is the current route-tree match for the client + // segment cache. It replaces a deprecated search-params-based path that + // Next still ships behind `deprecated_requestOptimisticRouteCacheEntry`. + optimisticRouting: true, + // 16.3 preview: persistent Turbopack FS cache for `next build` (faster + // cached CI/Vercel builds) + the native Rust port of the React Compiler + // (pairs with the top-level `reactCompiler: true`). Both experimental. + turbopackFileSystemCacheForBuild: true, + turbopackRustReactCompiler: true, sri: { algorithm: 'sha384' }, + // Not setting `cssChunking: 'graph'`. Measured on this branch it produced + // 11 stylesheets instead of the default's 9, for identical total bytes — + // more requests, no saving. The default 'loose' heuristic wins here. optimizePackageImports: [ '@react-three/drei', '@react-three/fiber', diff --git a/package.json b/package.json index b3c7ac9c..a41ca5ef 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "analyze:experimental": "next experimental-analyze", "dev:inspect": "bun ./lib/scripts/dev.ts --inspect", "lighthouse": "bunx @unlighthouse/cli --site http://localhost:3000", + "bench:nav": "bun ./lib/scripts/bench-navigation.ts", "bench:rerender": "bun ./lib/scripts/bench-rerender.ts", "setup:project": "bun ./lib/scripts/setup-project.ts", "test": "bun test", @@ -59,7 +60,7 @@ "gsap": "^3.15.0", "hamo": "^1.0.1", "lenis": "^1.3.25", - "next": "16.2.12", + "next": "16.3.0-preview.10", "next-sanity": "^13.2.3", "postprocessing": "^6.39.4", "react": "19.2.8", @@ -75,7 +76,7 @@ "@clack/prompts": "^1.7.0", "@csstools/postcss-global-data": "^4.0.0", "@happy-dom/global-registrator": "^20.11.1", - "@next/bundle-analyzer": "16.2.12", + "@next/bundle-analyzer": "16.3.0-preview.10", "@playwright/test": "1.62.0", "@sanity/vision": "^6.7.0", "@storybook/addon-mcp": "^0.7.0",