From 24ed66abe930c005adc40b10f4b1871ae252642a Mon Sep 17 00:00:00 2001 From: Aurora Scharff <66901228+aurorascharff@users.noreply.github.com> Date: Thu, 27 Aug 2026 21:46:19 +0200 Subject: [PATCH 1/9] docs(skills): preserve prefetched UI during Partial Prefetching adoption (#97712) ## What - Documents how legacy `prefetch={true}` behavior changes with Partial Prefetching and how to choose the UI worth preserving. - Updates the adoption skill to capture a passing flag-off `instant()` baseline, use the same assertions to drive the migration, and retain them as regression coverage. - Adds a reusable production test rig, keeps additional per-link optimization separate from adoption, and surfaces `prefetch={false}` links that may no longer be needed. - Applies the shared test-rig fixes discovered during dogfooding to the Cache Components optimizer. ## Why Legacy `prefetch={true}` could include uncached dynamic content. With Partial Prefetching, it includes the shared App Shell and cached URL-specific content instead. A passing build or clean insight sweep does not prove that the intended prefetched UI survived the migration, so the skill now records that contract before adoption and verifies it unchanged afterward. The guide presents the same test workflow as an optional programmatic check for manual adopters. ## Testing - Prettier, Alex, and skill frontmatter validation - Dogfooded on v0 in https://github.com/vercel/v0/pull/28842 - Partial Prefetching adoption eval: 6/6 in https://github.com/vercel/next.js/pull/97759 --- .../adopting-partial-prefetching.mdx | 57 +++--- docs/01-app/02-guides/ai-agents.mdx | 8 +- .../rig-template.md | 10 +- .../SKILL.md | 128 ++++++++----- .../rig-template.md | 172 ++++++++++++++++++ 5 files changed, 301 insertions(+), 74 deletions(-) create mode 100644 skills/next-partial-prefetching-adoption/rig-template.md diff --git a/docs/01-app/02-guides/adopting-partial-prefetching.mdx b/docs/01-app/02-guides/adopting-partial-prefetching.mdx index 1aef7ca25da0..2de25df65d27 100644 --- a/docs/01-app/02-guides/adopting-partial-prefetching.mdx +++ b/docs/01-app/02-guides/adopting-partial-prefetching.mdx @@ -14,7 +14,7 @@ related: [Partial Prefetching](/docs/app/glossary#partial-prefetching) changes what a `` downloads for a Cache Components route. With Partial Prefetching enabled, a `` prefetches the route's [App Shell](/docs/app/glossary#app-shell): its static content and the cached content that doesn't depend on the URL. Next.js builds one App Shell per route and reuses it for every link to that route, rather than prefetching each link separately as it did before. -To prefetch more than the App Shell, a link can opt into [per-link prefetching](/docs/app/guides/optimizing-prefetching) with [``](/docs/app/api-reference/components/link#prefetch). The prefetch then also resolves URL-specific content that depends on `params`, `searchParams`, or the full URL. +To prefetch more than the App Shell, a link can opt into [per-link prefetching](/docs/app/guides/optimizing-prefetching) with [``](/docs/app/api-reference/components/link#prefetch). The prefetch can then resolve cached URL-specific content that depends on `params`, `searchParams`, or the full URL. Along the way, Next.js surfaces [instant navigation](/docs/app/guides/instant-navigation) insights in development, naming the link or route to change. @@ -22,7 +22,7 @@ Along the way, Next.js surfaces [instant navigation](/docs/app/guides/instant-na ## Use the adoption skill (recommended) -The [`next-partial-prefetching-adoption`](https://github.com/vercel/next.js/tree/canary/skills/next-partial-prefetching-adoption) skill drives this adoption with a coding agent. It audits your `` calls with you, enables the flag, and sweeps every route for the insights this guide covers. +The [`next-partial-prefetching-adoption`](https://github.com/vercel/next.js/tree/canary/skills/next-partial-prefetching-adoption) skill drives this adoption with a coding agent. It reviews your `` calls, captures the prefetched UI to preserve with [`instant()` tests](#verify-prefetched-ui-with-tests) when a production test rig is available, then enables the flag and checks each route for the insights this guide covers. Install the skill: @@ -38,16 +38,19 @@ Adopt Partial Prefetching in this project using the next-partial-prefetching-ado ## Or adopt by hand -Adopting an existing app by hand follows the two [instant navigation](/docs/app/guides/instant-navigation) insights Next.js surfaces in `next dev`: +To adopt an existing app by hand: -1. [Enable `partialPrefetching`](#enable-partial-prefetching), then [audit every ``](#auditing-link-prefetchtrue-calls) and decide per destination how to preserve what its prefetch delivered. When the adoption is large enough that reviewers need smaller diffs, or adopted routes should reach users before the rest, [adopt incrementally](#adopting-incrementally) with the flag off, guided by the [dynamic data during prefetching](/docs/messages/instant-link-prefetch-partial) insight. -2. [Audit routes for URL data](#auditing-routes-for-url-data), resolving the [URL data outside of Suspense](/docs/messages/instant-shell-url-data) insight. -3. Optionally, [prefetch URL data](#prefetching-url-data) on the routes where streaming in after navigation isn't enough. +1. [Review existing full prefetches](#migrate-existing-full-prefetches) and choose which part of each destination should continue to be prefetched. You can use [`instant()` tests](#verify-prefetched-ui-with-tests) to add regression coverage. +2. [Enable `partialPrefetching`](#enable-partial-prefetching). For larger migrations, you can [adopt incrementally](#adopting-incrementally) with the global flag off. The [dynamic data during prefetching](/docs/messages/instant-link-prefetch-partial) insight identifies each destination that still uses the legacy full prefetch. +3. [Move URL data behind Suspense](#move-url-data-behind-suspense), resolving the [URL data outside of Suspense](/docs/messages/instant-shell-url-data) insight. +4. Optionally, [prefetch URL data](#prefetching-url-data) on the routes where streaming in after navigation isn't enough. Both insights are development-only and never block the build. They appear in the dev overlay with fix cards that link the docs page for each fix. If a route isn't ready to adopt, export [`instant = false`](/docs/app/api-reference/file-conventions/route-segment-config/instant) from its page or layout to opt the route out of instant-navigation validation, and return to it later. ## Enable Partial Prefetching +> **Good to know**: In an existing app, review the UI delivered by [``](#migrate-existing-full-prefetches) before enabling Partial Prefetching. These links use the legacy full prefetch, and enabling Partial Prefetching changes their behavior. + Enable [`partialPrefetching`](/docs/app/api-reference/config/next-config-js/partialPrefetching) in `next.config.ts`: ```ts filename="next.config.ts" highlight={5} @@ -61,31 +64,29 @@ const nextConfig: NextConfig = { export default nextConfig ``` -After enabling the flag, every `` prefetches its destination's App Shell, and `` no longer includes the route's dynamic content. Audit those links next to preserve what they delivered. A new project has no legacy links to audit and is done here. +After enabling the flag, links using the default behavior, `prefetch="auto"`, or `prefetch={true}` prefetch the destination's App Shell. A link with `prefetch={true}` can also resolve cached URL-specific content, but it no longer includes uncached dynamic content from the legacy full prefetch. New projects can enable the flag without this audit because they have no legacy full prefetches to migrate. ## What changes for `` -| `` prop | Before (Cache Components default) | After Partial Prefetching | -| ----------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | -| `` | Prefetched the cached page render. | Loads the shared App Shell for `/x`. | -| `` | Prefetched the cached page render **and** any dynamic content. | Loads the App Shell, plus URL-specific content through [per-link prefetching](/docs/app/guides/optimizing-prefetching) when `/x` reads it. | -| `` | Disabled prefetching for this link. | Unchanged. Still disabled. | +| `` prop | Before (Cache Components default) | After Partial Prefetching | +| ----------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `` | Prefetched the cached page render. | Prefetches the shared App Shell for `/x`. | +| `` | Prefetched the cached page render **and** any dynamic content. | Prefetches the App Shell, plus cached URL-specific content through [per-link prefetching](/docs/app/guides/optimizing-prefetching) when `/x` reads it. | +| `` | Disabled prefetching for this link. | Unchanged. Still disabled. | The App Shell is shared across every link to a given route, regardless of dynamic params, so rendering many ``s to the same destination doesn't multiply the work. -## Auditing `` calls +## Migrate existing full prefetches + +Before Partial Prefetching, a `` whose effective `prefetch` value is `true` prefetches the complete destination, including uncached dynamic content. This includes `prefetch={true}`, the bare `prefetch` prop, and wrapper or conditional props that resolve to `true`. The default value, `prefetch="auto"`, and `prefetch={false}` don't use the legacy full prefetch. -Each `` used to prefetch its destination's dynamic content along with the page. With the flag on it loads the App Shell like every other link, which can be thinner than the old full prefetch. Go through each one and decide what the destination should still prefetch: +A legacy full prefetch can include more UI than the navigation needs. In most cases, the primary content at the top of the destination should be available before navigation, while secondary or frequently changing content can stream afterward. > **Good to know**: `cookies()` and `headers()` don't tie a prefetch to a URL. They vary per session, not per link, so the App Shell still carries session content. Only `params` and `searchParams` are [URL data](/docs/app/glossary#url-data), which varies per link and can't be included in the shared App Shell. -| Destination | Recommendation | -| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | -| [Fully static, or content already cached](#static-or-cached-content) | Remove the now-redundant `prefetch={true}`. | -| [Delivered uncached content you want kept ahead of the click](#uncached-content) | Cache it with `use cache`, then remove `prefetch={true}`. | -| [Delivered content that depends on `cookies()` or `headers()`](#session-content) | Cache the lookup behind the session value, then remove `prefetch={true}`. | -| [Reads URL data, or has cached content that depends on it](#url-data) | Keep `prefetch={true}` to resolve the content ahead of the click. | -| [Delivers real-time content that must stay fresh per request](#real-time-content) | Remove `prefetch={true}` and let the content stream in. | +### Verify prefetched UI with tests + +To verify the migration programmatically, an [`instant()`](/docs/app/guides/instant-navigation#prevent-regressions-with-e2e-tests) test can capture the UI that should stay available from an existing full prefetch. Run this baseline against a production build with `partialPrefetching` disabled because automatic prefetching doesn't run in `next dev`. Then rerun the same assertions after adopting the destination. A failure identifies UI the App Shell no longer carries, so you can cache the data or subtree that restores it. The passing test provides regression coverage. ### Static or cached content @@ -246,11 +247,19 @@ export default function Page() { A prefetch of real-time content would be stale by the click, so there is nothing to preserve. Remove `prefetch={true}` and let the content stream in from behind its `` boundary. +| Destination | Recommendation | +| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | +| [Fully static, or content already cached](#static-or-cached-content) | Remove the now-redundant `prefetch={true}`. | +| [Delivered uncached content you want kept ahead of the click](#uncached-content) | Cache it with `use cache`, then remove `prefetch={true}`. | +| [Delivered content that depends on `cookies()` or `headers()`](#session-content) | Cache the lookup behind the session value, then remove `prefetch={true}`. | +| [Reads URL data, or has cached content that depends on it](#url-data) | Keep `prefetch={true}` to resolve the content ahead of the click. | +| [Delivers real-time content that must stay fresh per request](#real-time-content) | Remove `prefetch={true}` and let the content stream in. | + ## Adopting incrementally Adoption doesn't have to happen in one change. [`prefetch = 'partial'`](/docs/app/api-reference/file-conventions/route-segment-config/prefetch) is the global flag scoped to one route, so with the global flag still off, you can adopt and deploy each destination on its own. -While the flag is off, `prefetch={true}` still performs the legacy full prefetch. Navigating through one of these links in development surfaces the [dynamic data during prefetching](/docs/messages/instant-link-prefetch-partial) insight, naming the destination and pointing at its fixes: +Before a destination opts into Partial Prefetching, `prefetch={true}` performs the legacy full prefetch. Navigating through one of these links in development surfaces the [dynamic data during prefetching](/docs/messages/instant-link-prefetch-partial) insight, naming the destination and pointing at its fixes: `](https://react.dev/reference/react/Suspense) boundary ties the shell to the link's URL and surfaces the [URL data outside of Suspense](/docs/messages/instant-shell-url-data) insight, naming the route and pointing at its fixes: diff --git a/docs/01-app/02-guides/ai-agents.mdx b/docs/01-app/02-guides/ai-agents.mdx index c4c780f15110..d2e32031ef96 100644 --- a/docs/01-app/02-guides/ai-agents.mdx +++ b/docs/01-app/02-guides/ai-agents.mdx @@ -222,11 +222,11 @@ Make the navigation from /settings to /dashboard instant using the next-cache-co The [`next-partial-prefetching-adoption`](https://www.skills.sh/vercel/next.js/next-partial-prefetching-adoption) Skill moves an app onto [Partial Prefetching](/docs/app/guides/adopting-partial-prefetching), where links share one App Shell: -1. Audits the existing `` calls with you. -2. Turns the flag on and resolves the insights it surfaces. -3. Marks the routes whose URL data might be worth prefetching later. +1. Audits existing `` navigations and identifies the prefetched UI to preserve. +2. Captures that UI in passing [`instant()`](/docs/app/guides/instant-navigation#prevent-regressions-with-e2e-tests) tests before enabling Partial Prefetching, then migrates each destination until the same tests pass unchanged. +3. Enables the flag, resolves the URL-data insights it surfaces, and marks optional per-link prefetching candidates for later. -It needs [Cache Components](/docs/app/getting-started/caching) already adopted. +It needs [Cache Components](/docs/app/getting-started/caching) already adopted and a production-like build it can run for prefetch verification. ```bash filename="Terminal" npx skills add vercel/next.js --skill next-partial-prefetching-adoption diff --git a/skills/next-cache-components-optimizer/rig-template.md b/skills/next-cache-components-optimizer/rig-template.md index d0f2923004f7..c19c0ba520cf 100644 --- a/skills/next-cache-components-optimizer/rig-template.md +++ b/skills/next-cache-components-optimizer/rig-template.md @@ -39,7 +39,10 @@ build/run obstacles, accumulated as you first hit them). and never for real production? Spellings: an explicit `EXPOSE_TESTING_API=1` for local production builds; `process.env.DEPLOY_ENV === 'staging'` for a generic CI/staging env var; `process.env.VERCEL_ENV === -'preview'` on Vercel. +'preview'` on Vercel. Set the condition during `next build`, not only + `next start`. Otherwise `instant()` may not acquire the testing cookie + before the test times out; rebuild the artifact before debugging the + assertion. 3. **RUN**: how is the Playwright suite invoked, and against which `BASE_URL`? 4. **TEST USER**: which account does the suite run as, and how does login @@ -64,7 +67,10 @@ build/run obstacles, accumulated as you first hit them). chosen mechanism. For a local `build && start` rig the artifact is the one freshly built, so no SHA probe is needed. Record the port, stop the previous server before starting, fail the loop on `EADDRINUSE`, and verify the newly - started process owns the port before running the test. + started process owns the port before running the test. `next start` can fork + a `next-server` child, so the launcher process ID may not own the port. Start + the server in a process group that the rig can stop as a unit, or discover + and stop the process listening on the recorded port before the next build. ## The file: copy, fill, commit as `instant-nav.rig.md` diff --git a/skills/next-partial-prefetching-adoption/SKILL.md b/skills/next-partial-prefetching-adoption/SKILL.md index f09895b653d9..aa11cd07d3a5 100644 --- a/skills/next-partial-prefetching-adoption/SKILL.md +++ b/skills/next-partial-prefetching-adoption/SKILL.md @@ -5,7 +5,8 @@ description: > insights it surfaces. Use when the user wants to enable or adopt Partial Prefetching, flip the `partialPrefetching` flag, opt routes in with `export const prefetch = 'partial'`, audit - `` calls, or resolve the + `Link prefetch={true}` behavior, preserve existing prefetched UI + with `instant()` tests, or resolve the instant-link-prefetch-partial and instant-shell-url-data insights. --- @@ -13,19 +14,23 @@ description: > Enable Partial Prefetching and walk the app until every link reuses a shared App Shell. This skill sequences the work; per-insight recipes live in the dev overlay fix cards and their docs pages. The [Adopting Partial Prefetching guide](https://nextjs.org/docs/app/guides/adopting-partial-prefetching) is the canonical reference for the concepts this skill applies. -The one thing that shapes everything below: **these insights surface only in `next dev`, in the dev overlay's Insights tab.** Nothing fails the build. There is no build-only fallback loop — confirming an insight is _cleared_ means driving the running app in a browser. But a missing browser gates that verification, not the whole skill: the adoption work is static and runs from the guide, so do the static pass anyway and hand off the live shell check. +The development insights and the preservation tests are two different paths. Insights surface only in `next dev`, in the dev overlay's Insights tab. Test-backed preservation runs against a production-like build with `instant()` and does not need a development server. After the flag is enabled, the separate URL-data insight sweep still uses `next dev`. -Talk to the user in terms of what they'll see — PRs, features, and how the app behaves after — never the insight slugs or step labels. Before you start, tell them briefly what Partial Prefetching changes: a `` loads a shared App Shell, and `prefetch={true}` no longer prefetches everything the old full prefetch did. +## preservation gate + +When using test-backed preservation, the first implementation milestone is a passing flag-off `instant()` suite. Set up the production test rig, write the selected assertions, run them with `partialPrefetching` disabled, and record the command and exit status. Test-only configuration required by the rig is allowed, but until that baseline passes, do not enable `partialPrefetching` or edit the destination, cache boundaries, or Link props. Installing missing test dependencies is part of reaching the baseline, not a reason to adopt first. Use the manual path only when `rig-template.md` identifies a concrete blocker the repository cannot resolve, and record the blocker and deferred test coverage. + +Talk to the user in terms of what they'll see — PRs, features, and how the app behaves after — never the insight slugs or step labels. Before you start, tell them briefly what Partial Prefetching changes: links to a route prefetch one shared App Shell, and `prefetch={true}` can also resolve cached URL-specific content. The audit determines which UI from the legacy full prefetch to preserve. ## requires -- **Cache Components on (`cacheComponents: true`).** This is the only hard requirement; `partialPrefetching` depends on it. Full Cache Components adoption is the ideal starting point but not a gate. Nothing in this skill blocks the build, and neither do the prerender insights an unadopted route surfaces, like a leftover `unstable_noStore` or a `cookies()` read outside ``: they are non-blocking dev signals, expected on any fresh branch off `main`, not a reason to stop. They replace the URL-data insight only on their own route in the [step 3](#step-3-sweep-for-url-data-insights-after-enabling) sweep; the flag-off step 1 audit and its static adoption run regardless. The only thing that actually stops this skill is a build-blocking failure, and anything build-blocking would have been resolved before you reached here. Otherwise fix the prerender insights you hit as inline [`next-cache-components-adoption`](https://github.com/vercel/next.js/tree/canary/skills/next-cache-components-adoption) work, or hand them off, and keep going. +- **Cache Components adopted (`cacheComponents: true`) with a passing build.** Both `partialPrefetching` and the route-level `prefetch` export require Cache Components. If it is off, use [`next-cache-components-adoption`](https://github.com/vercel/next.js/tree/canary/skills/next-cache-components-adoption) first and return after its build-blocking prerender errors are resolved. Those errors can fail `next build`; only the Partial Prefetching insights handled by this skill are non-blocking development signals. - **Next.js 16.3 or later.** `partialPrefetching`, the `prefetch` route segment config, and the prefetch insights all land there. -- **A browser you can drive.** Install [`next-dev-loop`](https://github.com/vercel/next.js/tree/canary/skills/next-dev-loop) before starting, unless it is already available — it ships alongside this skill (`npx skills add https://github.com/vercel/next.js/tree/canary/skills/next-dev-loop`). Install it without asking — it's a tool, not a product change — and don't assume it's blocked: verify a real blocker (no network, no npm, read-only filesystem) before falling back, and name it in your report. Link prefetches fire when a link renders and enters the viewport, and shell validation fires on navigation — neither is reachable from `curl` or the build. If the app is webpack-pinned, drive a browser directly (`agent-browser`, Playwright) — you lose the framework cross-checks, not the insights; they're still in the overlay and the dev log. +- **A browser you can drive.** Test-backed preservation uses an existing or minimal production-mode Playwright suite; manual preservation and the final demonstration use the running production app. The development insight path and the post-flag URL-data sweep use [`next-dev-loop`](https://github.com/vercel/next.js/tree/canary/skills/next-dev-loop); install it before either development pass unless it is already available (`npx skills add https://github.com/vercel/next.js/tree/canary/skills/next-dev-loop`). If the app is webpack-pinned, drive a browser directly (`agent-browser`, Playwright) — you lose the framework cross-checks, not the insights; they're still in the overlay and the dev log. -- **A runnable app.** Verification runs against `next dev` for the insight sweep and a production `next build`/`next start` for prefetching (prefetching is prod-only), so the app has to boot in both. If it reads a database or required env at import (e.g. an `env.ts` that throws on a missing `DATABASE_URL`), confirm it starts — with the real environment, or local data you stand up — before step 1. An app that won't run can't be swept or verified. +- **A runnable app.** Preservation and the final demonstration need a production-like build because automatic prefetching runs only in production. The development server is required only when using the insight path or running the post-flag URL-data sweep; do not start it merely to confirm a test-backed preservation case. If the app reads a database or required environment at import, confirm the environment used by the chosen path can start before step 1. ### notes @@ -35,75 +40,104 @@ Talk to the user in terms of what they'll see — PRs, features, and how the app ## background -Adopting Partial Prefetching means every route still delivers what its links prefetched before, now split between the shared App Shell and any extra per-link data a link explicitly asks for. The [guide](https://nextjs.org/docs/app/guides/adopting-partial-prefetching) is the canonical reference for what a prefetch contains and how to decide each case; this skill sequences that work against a running app. +Adopting Partial Prefetching means every route preserves the prefetched UI that matters, now split between the shared App Shell and any extra per-link data a link explicitly asks for. The [guide](https://nextjs.org/docs/app/guides/adopting-partial-prefetching) is the canonical reference for what a prefetch contains and how to decide each case; this skill sequences that work against a running app. The catch that decides most of the sweep: a default link warms only the shared App Shell. A route keyed by `params` or `searchParams` can prefetch more only after it has adopted Partial Prefetching and a specific link uses [``](https://nextjs.org/docs/app/api-reference/components/link#prefetch); then Next.js resolves the URL data and any cached content behind it before the click (the guide's [URL data](https://nextjs.org/docs/app/guides/adopting-partial-prefetching#url-data) section). ## working surfaces -- **The dev server terminal — your primary record.** Each validated route's insights are logged as `Error: Route "...": Next.js encountered ...` lines with the `https://nextjs.org/docs/messages/` link. Tail the dev log during the sweep; it's the greppable record of what fired where, and it works the same on Turbopack and webpack. +- **The production-mode `instant()` suite — the primary record for test-backed preservation.** Reuse the app's production build, test context, and Playwright setup. Read an existing `instant-nav.rig.md` first; if the project has no rig, create it from **`rig-template.md`**. The same tests define the legacy target before adoption and become the work queue after each destination opts into Partial Prefetching. Development can help investigate a failure, but only this suite decides whether the prefetched UI was preserved. +- **The dev server terminal — the primary record for the insight path.** Each validated route's insights are logged as `Error: Route "...": Next.js encountered ...` lines with the `https://nextjs.org/docs/messages/` link. Tail the dev log during the sweep; it's the greppable record of what fired where, and it works the same on Turbopack and webpack. - **The dev overlay Insights tab.** Insights are the amber, non-blocking tab. It appears only once an insight has fired, so a route that surfaces nothing shows no tab at all — that's the clean state, not a missing feature. Don't hunt for the tab on a quiet route; confirm clean from the dev log above, which is the reliable signal. The precondition is no blocking-prerender errors — those replace the insight on their route (see requires). An unrelated Issue (a hydration error, a console error) doesn't block the sweep; don't stall on it. When the tab is present, the overlay pill shows the count and each insight has fix cards linking its docs page. The overlay renders inside a shadow root (`nextjs-portal`), so accessibility-tree snapshots don't see it — evaluate into `shadowRoot` when you need to read or click it programmatically. - **`next-dev-loop`** to drive navigations and read the overlay. Prefer it over hand-rolled browser automation for the same reasons as in the Cache Components skill (webpack apps: see requires). When browsing its `/_next/mcp` tools, the prefetch insights surface through `get_errors` and the overlay, not the similarly-named `get_request_insights`. That one is the span and performance recorder (gated behind `experimental.requestInsights`) and reports nothing about prefetching. Every insight has a docs page — open it. Fetch the linked page for every distinct insight you encounter; the inline message is a summary, the page is the recipe. -## step 1: audit `` (before enabling) +## step 1: audit `` navigations (before enabling) -If `partialPrefetching: true` is already set in `next.config.ts`, the app is adopted — skip to [step 3](#step-3-sweep-for-url-data-insights-after-enabling). Otherwise work the audit with the global flag **off**, adopting each destination with `export const prefetch = 'partial'` — enabling the flag first would mark every route adopted and silence the [`instant-link-prefetch-partial`](https://nextjs.org/docs/messages/instant-link-prefetch-partial) insight this audit runs on. Ask the user how to ship it, in the language of PRs: +Keep the global flag **off** through this audit and the legacy baseline in step 2. Enabling it earlier would remove the legacy behavior the migration needs to measure. If the flag is already on in unshipped work, use the pre-flag commit for the audit and baseline. When the user is available, ask how to ship it in the language of PRs: -- **One branch** — the whole audit in one change, with the flag enabled and the codemod run at the end (step 2). -- **Route by route** — each adopted destination ships as its own PR. The insight still fires for the destinations you haven't reached, a live worklist, and step 2 comes after the last one. +- **One branch** — the whole audit in one change, with the flag enabled and the codemod run at the end (step 4). +- **Route by route** — each adopted destination ships as its own PR. The insight still fires for the destinations you haven't reached, a live worklist, and step 4 comes after the last one. -The work is identical either way — only the commit boundaries differ. Default by app size: one branch for a handful of links, route by route when the audit is big enough that reviewers need smaller diffs. Note the choice in your report. +The work and its order are identical either way — only the commit boundaries differ. When no user is available, default by app size: one branch for a handful of links, route by route when the audit is big enough that reviewers need smaller diffs. Note the choice in your report. -Enumerate the prefetch sites across the whole source tree, not only `app/` — they often live in `src/components` or shared UI packages: `rg -n '\bprefetch\b|router\.prefetch' -g '*.tsx' -g '*.jsx' .`. Keep the `` and bare-prop matches (a bare prop is `true`) as the over-prefetching links this audit adopts destinations for, and drop `prefetch={false}` and other values. Also audit existing imperative [`router.prefetch()`](https://nextjs.org/docs/app/api-reference/functions/use-router#userouter) call sites with the same table, because they can be preserving the same "fetch before navigation" behavior and have no dev insight. For new navigation prefetching, prefer [``](https://nextjs.org/docs/app/api-reference/components/link), which the docs call the primary navigation API; use [`router.prefetch()`](https://nextjs.org/docs/app/guides/prefetching#manual-prefetch) only for manual prefetching. If the app already passes an internal `kind` option, treat that as existing implementation detail, not a pattern to spread. Always inspect custom Link wrappers and trace their consumers: a wrapper can call `router.prefetch()` on hover or touch while a consumer omits `prefetch` or passes `prefetch={false}`. Record the declarative and imperative behavior separately, and use keyboard activation when verifying the declarative path so hover prefetching does not mask it. If nothing matches, say so in your report and move on to [step 2](#step-2-enable-the-flag). +Enumerate explicit prefetch and manual prefetch sites across the whole source tree, not only `app/` — they often live in `src/components` or shared UI packages. Start from `next/link` imports and re-exports, then follow custom wrappers to their consumers. Use `rg -n '\bprefetch\b|router\.prefetch' -g '*.tsx' -g '*.jsx' .` as a candidate list, not as the complete audit; inspect conditional props and forwarded `LinkProps` to determine the effective production value. Include every audited navigation whose effective production Link value is `prefetch={true}`: explicit `true`, a bare `prefetch` prop, and expressions that resolve to `true`. Exclude the default value, `prefetch="auto"`, and `prefetch={false}` from the preservation suite because they do not request the legacy full prefetch. Audit existing [`router.prefetch()`](https://nextjs.org/docs/app/api-reference/functions/use-router#userouter) calls separately because they have no Link insight. For new manual prefetching, follow the [Prefetching guide](https://nextjs.org/docs/app/guides/prefetching#manual-prefetch). If no Link resolves to `prefetch={true}`, say so and move on to [step 4](#step-4-enable-the-flag). -Then, for each one: +### Choose what to preserve and how to verify it -1. **Click each `` in `next dev`.** The insight fires at navigation time, not when the link prefetches, so a link sitting in the viewport won't trip it — you have to navigate through it. This click is _verification_: it confirms the insight fires before you adopt and clears after. Imperative `router.prefetch()` sites have no equivalent insight, so audit them from source and verify them in production ([step 4](#step-4-verify)). Without a browser, skip the click and adopt from [`instant-link-prefetch-partial`](https://nextjs.org/docs/messages/instant-link-prefetch-partial) and the audit table below — the destination's structure tells you the row, and type-check gates the edit — then leave the live confirmation for the hand-off. -2. **Adopt the destination.** Add the temporary route config with a link to the migration guide. That clears the insight for every link pointing at it: +Before writing tests or editing destinations, follow the guide's [migration guidance](https://nextjs.org/docs/app/guides/adopting-partial-prefetching#migrate-existing-full-prefetches) to propose the UI worth preserving. Present the result in one concise table: - ```tsx - // See: https://nextjs.org/docs/app/guides/adopting-partial-prefetching - export const prefetch = 'partial' - ``` +| Navigation | Proposed result | +| ---------- | --------------- | - If the route reads URL data (`params`, `searchParams`), the default link still warms only its skeleton (the guide's [URL data](https://nextjs.org/docs/app/guides/adopting-partial-prefetching#url-data) section), so it's a per-link-prefetch candidate for step 5, not a finished adoption. Keep `prefetch={true}` on its links and mark the route: +Group equivalent navigations. Summarize what will be ready immediately and what will stream. When a proposal is ambiguous, show the navigation in the running app and ask the user to confirm it. If they are unavailable, follow the guide and record the assumption. - ```tsx - // TODO(per-link-prefetch): assess with the user whether URL data should resolve before click. - // See: https://nextjs.org/docs/app/guides/optimizing-prefetching - export const prefetch = 'partial' - ``` +After the target UI is settled, inspect the existing test setup. The `instant()` helper comes from the separate [`@next/playwright`](https://nextjs.org/docs/app/guides/instant-navigation#prevent-regressions-with-e2e-tests) package, not `next/experimental/testmode/playwright`. + +- **Applicable production-mode suite:** use test-backed preservation by default. Reuse the project's `@next/playwright` tests, production scripts, authentication, and existing `instant-nav.rig.md`. Follow the guide's [prefetched UI test workflow](https://nextjs.org/docs/app/guides/adopting-partial-prefetching#verify-prefetched-ui-with-tests) and make the complete flag-off suite green before adoption. The unchanged assertions drive the migration and stay as regression coverage. +- **No applicable production-mode suite:** set up the production-mode rig in **`rig-template.md`** using the project's package manager and test conventions. This is part of test-backed adoption and does not require a user to be present. +- **Rig cannot run reliably:** work through **`rig-template.md`** setup and liveness checks. Fall back to manual preservation only for a concrete blocker the repository cannot resolve, such as unavailable credentials or an inaccessible production environment. Record the blocker and the deferred test coverage; do not claim test-backed verification. + +No user input is required to reuse an existing suite or create the rig. Ask only when the repository cannot answer an environment question or when the target UI itself is a product decision. If no user is available, use the guide's safe product default and reserve manual verification for a concrete rig blocker. Treat new prefetched UI as step 7 work; verify any deliberate removal separately after adoption. + +This workflow is specific to a clicked ``. A direct call such as `router.prefetch('/dashboard')` is a manual prefetch, not a Link prefetch; keep it in the source audit and verify it separately in step 6. + +## step 2: capture the legacy baseline + +Do not enable `partialPrefetching` or edit route behavior, Link props, or cache boundaries during this step. Test-only configuration required to run `instant()` is allowed. + +For test-backed preservation, complete the [preservation gate](#preservation-gate): write the complete `instant()` suite and **run it** against the production-like rig with Partial Prefetching disabled. A test file, build, completed navigation, or command printed for the user is not a baseline. Do not continue to step 3 until the suite has actually passed. - Use that exact prefix so step 5 can grep them back. Don't cache or decide anything for these routes now. +For manual preservation, finish the before/target inventory before editing any destination. Fall back to this path only for a concrete rig blocker identified through `rig-template.md`, and record the blocker and deferred tests. -3. **Preserve what that prefetch delivered.** The guide's [audit table](https://nextjs.org/docs/app/guides/adopting-partial-prefetching#auditing-link-prefetchtrue-calls) is the canonical decision — fetch it and apply the matching row. Caching uncached content is the judgment call in that table: trace where the data comes from and what freshness and revalidation it needs, per the [`use cache`](https://nextjs.org/docs/app/api-reference/directives/use-cache) docs, and ask the user when the answer isn't clear-cut. The URL-data routes you marked in the previous item wait for step 5. +## step 3: adopt destinations and restore the target - If the repository already has `instant()` e2e coverage for a destination, run it before editing and preserve its assertion as the contract. A successful build or completed navigation does not prove that the same UI was prefetched. If caching the primary data loader still leaves only a fallback inside `instant()`, inspect rendered descendants and providers for dynamic work, then expand the cache only to the smallest coherent rendered subtree that restores the contract. +Adopt every audited destination with the temporary route config. The route export is enough for the unchanged tests to exercise Partial Prefetching on that destination while the global flag remains off: + +```tsx +// See: https://nextjs.org/docs/app/guides/adopting-partial-prefetching +export const prefetch = 'partial' +``` + +If other URL-specific UI might be worth prefetching but was not part of the legacy contract, keep `prefetch={true}` on its links and mark the route for step 7: + +```tsx +// TODO(per-link-prefetch): assess with the user whether URL data should resolve before click. +// See: https://nextjs.org/docs/app/guides/optimizing-prefetching +export const prefetch = 'partial' +``` + +Use that exact prefix so step 7 can grep them back. Do not select new target UI now; restore only the target chosen from the legacy behavior. + +For test-backed preservation, rerun the affected **unchanged** tests after each destination changes and treat failures as the work queue. Run the complete suite and record its passing exit status before enabling the global flag. For manual preservation, compare the adopted production navigation with the selected target and document anything not yet restored. Apply the guide's matching preservation pattern for caching and Link-prop changes, and ask the user before making an unclear freshness or caching decision. New URL-data candidates marked above wait for step 7. + +When restoring the target changes caching or invalidation, follow the project's existing verification approach. Reuse or extend an applicable suite for the affected lifecycle, such as freshness after mutations, cache scope, or generated values. If the project doesn't test this type of behavior, do not introduce new test infrastructure during adoption; verify it manually in production and record the expected and observed results. A green `instant()` test proves readiness, not cache correctness. Ask the user only when the intended behavior is unclear. > **If you add `use cache`, verify under `next start`, not only the build.** A `cookies()`/`headers()`/session read anywhere in the cached call tree throws at request time while `next build` passes clean. See [`use cache`](https://nextjs.org/docs/app/api-reference/directives/use-cache). -## step 2: enable the flag +## step 4: enable the flag Once every audited destination has `prefetch = 'partial'`, finish in two moves. 1. **Enable the flag globally.** Set `partialPrefetching: true` in `next.config.ts` (alongside `cacheComponents: true`). Every route is adopted now, so every link is good. -2. **Strip the redundant `prefetch = 'partial'` exports.** Run the first-party `remove-partial-prefetch` codemod rather than a text find-and-replace. It removes only `export const prefetch = 'partial'` and its generated Partial Prefetching guide comment. It leaves other values such as `prefetch = 'force-disabled'` in place, along with your `TODO(per-link-prefetch)` markers and their Optimizing prefetching guide links, which wait for step 5. +2. **Strip the redundant `prefetch = 'partial'` exports.** Run the first-party `remove-partial-prefetch` codemod rather than a text find-and-replace. It removes every `export const prefetch = 'partial'`, including exports below a `TODO(per-link-prefetch)` marker, and removes its generated Partial Prefetching guide comment. The TODO marker and its Optimizing prefetching guide link stay for step 7. Other values such as `prefetch = 'force-disabled'` stay in place. ```bash - npx @next/codemod@latest remove-partial-prefetch ./app + npx @next/codemod@canary remove-partial-prefetch ./app ``` - The codemod refuses to run on a dirty working tree. Commit or stash unrelated work first, or pass `--force` to let its edits land alongside your WIP. If the codemod isn't available (older `@next/codemod`, sandboxed environment, offline run), reproduce it by hand by removing `export const prefetch = 'partial'` and its generated Partial Prefetching guide comment from every `app/**/{page,layout}.{js,jsx,ts,tsx}` — leave other `prefetch` values in place, and leave the `TODO(per-link-prefetch)` markers and Optimizing prefetching guide links where they are. Don't hand-edit when the codemod can run. + Use `./src/app` in a `src/` project and check the reported file count. The codemod refuses to run on a dirty working tree. Commit or stash unrelated work first, or pass `--force` to let its edits land alongside your WIP. If the codemod isn't available (older `@next/codemod`, sandboxed environment, offline run), reproduce it by hand by removing `export const prefetch = 'partial'` and its generated Partial Prefetching guide comment from every `app/**/{page,layout}.{js,jsx,ts,tsx}` — leave other `prefetch` values in place, and leave the `TODO(per-link-prefetch)` markers and Optimizing prefetching guide links where they are. Don't hand-edit when the codemod can run. + +After the flag and codemod land together, rerun the locked preservation suite when using the test-backed path. Otherwise repeat the documented production comparisons under the final global configuration. -## step 3: sweep for URL-data insights (after enabling) +## step 5: sweep for URL-data insights (after enabling) -This is a dev-only second pass. The shell check runs only with the flag on, fires at navigation time, and never blocks the build, so it can happen any time after step 2. Build the route queue from a concrete source (the last `next build` route table, or the `app/` tree) and keep it as a todo list. +This is a dev-only second pass. The shell check runs only with the flag on, fires at navigation time, and never blocks the build, so it can happen any time after step 4. Build the route queue from a concrete source (the last `next build` route table, or the `app/` tree) and keep it as a todo list. Sweep feature by feature. A feature is a single product surface — `app/settings/**`, `app/posts/[slug]/**` — not a whole top-level area. Finish one end-to-end before starting the next: load its routes in `next dev` and resolve their insights. The insight never blocks the build and each route is independent, so a partial sweep leaves a working app, and each feature is a self-contained change the user can review or ship on its own. -If the environment can't finish the whole sweep (slow first compiles, a dev server that falls over under load, no browser at all), take the browser-free work as far as it goes before handing off. Adopt every route you can statically: apply the fix from [`URL data`](https://nextjs.org/docs/messages/instant-shell-url-data) (up to a new `` boundary) and opt the route into `prefetch = 'partial'`, gating on type-check. Work the whole queue in one pass — a larger refactor isn't a reason to defer, and asking whether to continue to the next route or tier isn't a checkpoint; keep going. Stop only for a genuine judgment call, and batch those into the single hand-off report: the routes you statically adopted, the ones still needing a live shell check, and the queue. +If the environment can't finish the whole sweep (slow first compiles, a dev server that falls over under load, no browser at all), take the browser-free work as far as it goes before handing off. Adopt every route you can statically: apply the fix from [`URL data`](https://nextjs.org/docs/messages/instant-shell-url-data) up to a new `` boundary, gating on type-check. Work the whole queue in one pass — a larger refactor isn't a reason to defer, and asking whether to continue to the next route or tier isn't a checkpoint; keep going. Stop only for a genuine judgment call, and batch those into the single hand-off report: the routes you statically adopted, the ones still needing a live shell check, and the queue. Watch the Insights tab and the dev log for `Next.js encountered … data` lines. The signal this step adds is [`URL data`](https://nextjs.org/docs/messages/instant-shell-url-data): a `params` or `searchParams` read too high in the suspended subtree ties the shared shell to one URL. This insight is narrow; it most reliably appears on a `generateStaticParams` route where `params` is already under ``, but still awaited before the URL-specific leaf boundary. If a `blocking-prerender-*` error fires instead, apply the same structural fix. @@ -111,13 +145,15 @@ Loading a route with the flag on prerenders its App Shell, which validates more These fixes rarely involve the user — each insight names the offending read and its docs page has the fix, so apply it and keep sweeping. Collect the rare exceptions for one batched question at the end: a page that is entirely one URL-dependent region (wrapping it all leaves an empty shell), or a route that should arguably stay opted out. Don't narrate the refactor with comments — the `` boundaries speak for themselves. -## step 4: verify +## step 6: verify Checklist before checking in with the user: - **An empty sweep is expected when Cache Components adoption finished cleanly.** A quiet log is success, not a missing signal. If you deliberately probe the validation path, use a `generateStaticParams` route with `params` read inside `` but before the URL-specific leaf boundary; other shapes may surface `blocking-prerender-*` instead. - The App Shells are real: for each route you changed, confirm the first paint after a navigation shows the intended shared content, not an empty shell or a stuck fallback. A `` around the whole page body passes validation with an empty shell, which defeats the point. -- The insights validate shell _structure_, not that a prefetch actually happened. Confirm on the production run (prefetching is prod-only) that navigating a changed link lands on the shared shell instantly. +- The insights validate shell _structure_, not that a prefetch actually happened. Confirm on the production run (automatic prefetching runs only in production) that navigating a changed link lands on the shared shell instantly. +- For test-backed preservation, every locked `instant()` test for an audited `` passes against the production run. For manual preservation, the before/after inventory and any deferred test follow-ups are recorded. +- Any caching or invalidation changed to preserve the target is verified through an applicable existing test suite or a recorded manual check when the project has no such coverage. - **If the app prefetches imperatively**, the insight sweep does not cover it, so an empty sweep is not proof the prefetch survived the flag. Verify the call under `next start`: compare the `_rsc` prefetch response or resource timing before/after, and make sure any intentionally preserved full prefetch still carries the data the old call was warming. If it now returns only the App Shell, migrate that call site using the same decision as the nearest `` destination — cache the data, or move per-link-prefetch behavior to a docs-supported ``. - **Before blaming a broken route on the flag, reproduce it with `partialPrefetching` off** (or on the pre-flag branch). The flag surfaces existing issues — a fragile request-time auth gate, a rewrite, deployment skew — earlier and more visibly, but rarely causes them. If it breaks flag-off too, it isn't a Partial Prefetching problem; fix it there, not here. - `next build` still passes. @@ -126,19 +162,23 @@ Then check in with the user. Speak their language — no insight slugs or step l - What you did: which links you audited, which destinations you adopted, and what each link now prefetches. - What changed: dropped props, `use cache` boundaries added, and which routes carry a `TODO(per-link-prefetch)` marker for later. -- Demo against a production run. Prefetching is limited in development, so `next dev` won't show the result — run `next build` and `next start`, and hand the user that URL. That run needs the app's real environment (database, auth, secrets), and a partial or stale install or leftover generated artifacts can fail the build for reasons unrelated to the adoption. Set the expectation up front that verification is a complete, credentialed production run, not a quick check. +- Demo against a production run. Automatic prefetching runs only in production, so `next dev` won't show the result — run `next build` and `next start`, and hand the user that URL. That run needs the app's real environment (database, auth, secrets), and a partial or stale install or leftover generated artifacts can fail the build for reasons unrelated to the adoption. Set the expectation up front that verification is a complete, credentialed production run, not a quick check. - Show, don't tell: drive one link live in the headed browser against the production server, so they see the shared App Shell paint instantly and the URL-specific region stream in. Attach before/after screenshots only when a live browser isn't possible. - Give them the click-through: a table of each changed route — the link to click, and what to expect after the click (what paints instantly, what streams in) — so they can verify each result themselves. - The question: "Want to commit this (or open the PR) before we look at which routes should also prefetch their URL-specific content?" Wait for the answer — adoption and per-link prefetching read best as their own changes. -## step 5: per-link prefetching (optional) +## step 7: per-link prefetching (optional) + +The audit marked candidates beyond the already-preserved legacy contract instead of deciding them. Grep for `TODO(per-link-prefetch)` and walk the list with the user in one conversation. The question per route is whether they want the additional URL-dependent content prefetched ahead of the click, or streaming in after navigation is fine. A per-link prefetch costs a server invocation per prefetchable link — the guide's [trade-offs](https://nextjs.org/docs/app/guides/optimizing-prefetching#trade-offs) section is the checklist. Don't make these calls alone. + +Where the answer is no, delete the marker and leave the route on the App Shell default. Where the answer is yes, follow the [Optimizing prefetching guide](https://nextjs.org/docs/app/guides/optimizing-prefetching), confirm the opted-in link against a production run, and delete the marker when the selected result is verified. -The audit marked the candidates instead of deciding them. Grep for `TODO(per-link-prefetch)` and walk the list with the user in one conversation. The question per route is whether they want the URL-dependent content prefetched ahead of the click, or streaming in after navigation is fine. A per-link prefetch costs a server invocation per prefetchable link — the guide's [trade-offs](https://nextjs.org/docs/app/guides/optimizing-prefetching#trade-offs) section is the checklist. Don't make these calls alone. +No `TODO(per-link-prefetch)` marker survives the finished step. Per-link optimization remains a separate commit or PR from adoption. -Where the answer is yes, follow the [Optimizing prefetching guide](https://nextjs.org/docs/app/guides/optimizing-prefetching): keep [``](https://nextjs.org/docs/app/api-reference/components/link#prefetch) on the links that should resolve more than the App Shell, and cache the content behind the URL-data read using the guide's patterns (`use cache` with the runtime value passed in, or `use cache: private` for per-user data). Each per-link prefetch is a server render when the destination needs non-static data, so use the guide's [per-link trade-offs](https://nextjs.org/docs/app/guides/optimizing-prefetching#trade-offs) to decide when viewport prefetching is worth it and when [hover-triggered prefetch](https://nextjs.org/docs/app/guides/prefetching#hover-triggered-prefetch) is a better fit. Where it's no, delete the marker and leave the route on the App Shell default. Either way no `TODO(per-link-prefetch)` marker survives this step. Confirm the opted-in links against a production run (`next build` and `next start` — the per-link prefetch runs there, not in `next dev`), give the user the same click-through for them, and keep this as its own commit or PR. +Finally, show any effective `prefetch={false}` links in a concise `Navigation | Why it may no longer be needed` table. Explain that `false` disables all prefetching, while Partial Prefetching's default `auto` behavior prefetches only the shared App Shell, so opt-outs added to avoid legacy full-route prefetching may now be unnecessary. Invite the user to revisit them separately. ## further reading - [Instant navigation](https://nextjs.org/docs/app/guides/instant-navigation) — the broader validation model and loading-state tooling. -- [Prevent regressions with e2e tests](https://nextjs.org/docs/app/guides/instant-navigation#prevent-regressions-with-e2e-tests) — the `@next/playwright` `instant()` helper locks in what a navigation shows immediately; recommend it once the sweep is clean, since nothing else guards these in CI. +- [Prevent regressions with e2e tests](https://nextjs.org/docs/app/guides/instant-navigation#prevent-regressions-with-e2e-tests) — use the `@next/playwright` `instant()` helper to build the flag-off baseline suite, then keep it as the CI regression guard. - [`next-cache-components-optimizer`](https://github.com/vercel/next.js/tree/canary/skills/next-cache-components-optimizer) — grows each route's static shell so the App Shell carries more. diff --git a/skills/next-partial-prefetching-adoption/rig-template.md b/skills/next-partial-prefetching-adoption/rig-template.md new file mode 100644 index 000000000000..bbfa4c7e84c8 --- /dev/null +++ b/skills/next-partial-prefetching-adoption/rig-template.md @@ -0,0 +1,172 @@ +# Production `instant()` rig + +The preservation suite needs a production build that exposes the Next.js +testing API, a stable URL for that build, and a Playwright command that can +drive the audited Links. Discover this setup once, record it in +`instant-nav.rig.md`, and reuse it throughout adoption. + +Read an existing `instant-nav.rig.md` before creating one. Inspect the +repository before asking the user: + +- `package.json` scripts for build, start, and end-to-end tests +- `playwright.config.*` for `baseURL`, `webServer`, projects, and authentication +- `next.config.*` for existing `experimental` options +- CI, preview deployment, container, and hosting configuration +- test helpers for login, `storageState`, fixtures, flags, and seeded data + +Ask only for details the repository cannot answer, such as unavailable +credentials or which remote environment may expose the testing API. + +## What the rig must define + +### Production build and server + +Use `next build` followed by `next start`, or a remote artifact produced by the +same production build. Automatic prefetching does not run in `next dev`, so a +development server cannot verify preservation. + +Record separate build and start commands. For a local rig, record the port, +stop any previous server before starting, fail on `EADDRINUSE`, and confirm the +new process owns the port before running Playwright. `next start` can fork a +`next-server` child, so the launcher process ID may not own the port. Start the +server in a process group that the rig can stop as a unit, or discover and stop +the process listening on the recorded port before the next build. + +### Testing API + +An `instant()` test against a production build requires +`experimental.exposeTestingApiInProductionBuild`. Gate it so real production +builds do not expose the API: + +```ts filename="next.config.ts" highlight={3,8-10} +import type { NextConfig } from 'next' + +const exposeTestingApi = process.env.EXPOSE_TESTING_API === '1' + +const nextConfig: NextConfig = { + cacheComponents: true, + experimental: { + exposeTestingApiInProductionBuild: exposeTestingApi, + }, +} + +export default nextConfig +``` + +Merge the option into an existing `experimental` object instead of replacing +the project's other experimental options. + +Set the condition while running `next build`. Setting it only for `next start` +is too late because the testing API is compiled into the production artifact. +When the artifact was built without it, Next.js does not activate the +navigation lock, so the test cannot distinguish prefetched UI from streamed +dynamic content. Rebuild with the condition enabled before interpreting the +results. Use the project's existing environment naming when it already +distinguishes test, staging, preview, and production builds. + +### Test command and base URL + +Record the exact Playwright command and how it receives the measured build's +URL. Reuse the project's package manager, Playwright configuration, projects, +and reporters. The suite must import `instant()` from `@next/playwright`. If +the dependencies are absent, install `@next/playwright` on the same release +line as the project's `next`, alongside `@playwright/test`. + +For a local rig, a typical sequence is: + +```bash filename="Terminal" +EXPOSE_TESTING_API=1 pnpm build +pnpm start --port 3000 +BASE_URL=http://localhost:3000 pnpm playwright test tests/prefetch-preservation.spec.ts +``` + +Adapt the script names and port to the project. Keep the production server +running while the test command executes. Follow the public +[client-navigation test](https://nextjs.org/docs/app/guides/instant-navigation#prevent-regressions-with-e2e-tests): load the source route, confirm the real +Link is visible, then enter `instant()`, click, wait for the destination URL, +and assert the prefetched UI. + +### Test context + +Record the state required to reach the audited Links and destination UI: + +- Use `public; no authentication` when the navigation is public. +- Otherwise record the test account and login mechanism, including a fixture, + `storageState`, API login, or seeded session. +- Record flags, plan, role, locale, seeded data, and other state that can change + which UI the test sees. + +A test user is not required. The field exists to make authenticated and +state-dependent tests reproducible when the app needs one. + +### Drift + +List differences between the state used to choose the preservation target and +the state used by Playwright. Feature flags, permissions, empty test data, and +locale differences can make an assertion fail because the target is +unreachable, not because Partial Prefetching removed it. Write `none known` +only after checking the test context. + +### Iteration loop + +Record the complete loop the agent can repeat without rediscovering commands: + +- Local: build with the testing API, start the new artifact, run the focused + suite, stop the server, edit, and repeat. +- Remote: push, wait for the measured artifact, verify it matches `HEAD`, run + the focused suite against its URL, edit, and repeat. + +Note any step the agent cannot perform without the user, including deployment +approval, protected branches, secrets, or multi-factor authentication. + +### Artifact liveness + +For a remote rig, record how the test proves the deployment matches `HEAD`. +Prefer an endpoint or response header that exposes the deployed commit SHA. If +the app has neither, use the deployment provider's API to select the artifact +whose commit SHA matches `HEAD`. + +A freshly completed local `build` followed by `start` does not need a SHA +probe. Record `n/a; local build and start`. + +### Walls + +Record build and run obstacles with their working resolution, such as required +environment variables, server-only imports that fail during prerendering, +unavailable credentials, or a process that keeps reclaiming the test port. +Reuse these notes on the next iteration. + +## Write `instant-nav.rig.md` + +Place this file at the repository root or next to the end-to-end configuration: + +```md +# instant-nav rig: + +- BUILD: +- EXPOSE: +- RUN: +- TEST USER: ; state: +- DRIFT: +- LOOP: ; agent limits: <...> +- LIVENESS: +- WALLS: +``` + +Every field needs a concrete value. `n/a` is valid only with a reason, such as +`TEST USER: public; no authentication` or `LIVENESS: n/a; local build and +start`. + +## Check the rig before writing the baseline + +Before recording the legacy prefetched UI: + +1. Build with the testing API condition enabled. +2. Start or locate that exact artifact and confirm the base URL responds. +3. Run one focused `instant()` smoke test through a real `` navigation. +4. Confirm the test can reach its source Link and eventual destination UI in + the recorded test context. + +Fix the rig before interpreting a preservation failure. A missing testing API, +stale deployment, unreachable target, or wrong test state is an environment +failure rather than evidence that the migration changed the prefetch. From 3243c970fc4dd6add9e64d0dd1e51ae899b675b3 Mon Sep 17 00:00:00 2001 From: "next-js-bot[bot]" <279046576+next-js-bot[bot]@users.noreply.github.com> Date: Thu, 27 Aug 2026 20:13:20 +0000 Subject: [PATCH 2/9] Upgrade React from `f789f203-20260825` to `29d9d318-20260826` (#97995) --- package.json | 30 +- .../cjs/react-dom-client.development.js | 15 +- .../cjs/react-dom-client.production.js | 15 +- .../cjs/react-dom-profiling.development.js | 15 +- .../cjs/react-dom-profiling.profiling.js | 15 +- ...t-dom-server-legacy.browser.development.js | 2 +- ...ct-dom-server-legacy.browser.production.js | 2 +- ...eact-dom-server-legacy.node.development.js | 2 +- ...react-dom-server-legacy.node.production.js | 2 +- .../react-dom-server.browser.development.js | 6 +- .../react-dom-server.browser.production.js | 6 +- .../cjs/react-dom-server.bun.production.js | 6 +- .../cjs/react-dom-server.edge.development.js | 6 +- .../cjs/react-dom-server.edge.production.js | 6 +- .../cjs/react-dom-server.node.development.js | 6 +- .../cjs/react-dom-server.node.production.js | 6 +- .../react-dom-unstable_testing.development.js | 15 +- .../react-dom-unstable_testing.production.js | 15 +- .../cjs/react-dom.development.js | 2 +- .../cjs/react-dom.production.js | 2 +- .../cjs/react-dom.react-server.development.js | 2 +- .../cjs/react-dom.react-server.production.js | 2 +- .../react-dom-experimental/package.json | 4 +- .../cjs/react-dom-client.development.js | 15 +- .../cjs/react-dom-client.production.js | 15 +- .../cjs/react-dom-profiling.development.js | 15 +- .../cjs/react-dom-profiling.profiling.js | 15 +- ...t-dom-server-legacy.browser.development.js | 2 +- ...ct-dom-server-legacy.browser.production.js | 2 +- ...eact-dom-server-legacy.node.development.js | 2 +- ...react-dom-server-legacy.node.production.js | 2 +- .../react-dom-server.browser.development.js | 6 +- .../react-dom-server.browser.production.js | 6 +- .../cjs/react-dom-server.bun.production.js | 6 +- .../cjs/react-dom-server.edge.development.js | 6 +- .../cjs/react-dom-server.edge.production.js | 6 +- .../cjs/react-dom-server.node.development.js | 6 +- .../cjs/react-dom-server.node.production.js | 6 +- .../react-dom/cjs/react-dom.development.js | 2 +- .../react-dom/cjs/react-dom.production.js | 2 +- .../cjs/react-dom.react-server.development.js | 2 +- .../cjs/react-dom.react-server.production.js | 2 +- .../next/src/compiled/react-dom/package.json | 4 +- .../cjs/react.development.js | 2 +- .../cjs/react.production.js | 2 +- .../cjs/react.react-server.development.js | 2 +- .../cjs/react.react-server.production.js | 2 +- .../next/src/compiled/react-is/package.json | 2 +- ...om-turbopack-client.browser.development.js | 4 +- .../package.json | 4 +- ...om-turbopack-client.browser.development.js | 4 +- .../react-server-dom-turbopack/package.json | 4 +- ...-dom-webpack-client.browser.development.js | 4 +- .../package.json | 4 +- ...-dom-webpack-client.browser.development.js | 4 +- .../react-server-dom-webpack/package.json | 4 +- .../compiled/react/cjs/react.development.js | 2 +- .../compiled/react/cjs/react.production.js | 2 +- .../cjs/react.react-server.development.js | 2 +- .../cjs/react.react-server.production.js | 2 +- .../next/src/compiled/unistore/unistore.js | 2 +- pnpm-lock.yaml | 1192 ++++++++--------- 62 files changed, 779 insertions(+), 769 deletions(-) diff --git a/package.json b/package.json index 29ea9d3fa523..dedde3d180ec 100644 --- a/package.json +++ b/package.json @@ -259,24 +259,24 @@ "pretty-ms": "7.0.0", "random-seed": "0.3.0", "react": "19.0.0", - "react-builtin": "npm:react@19.3.0-canary-f789f203-20260825", + "react-builtin": "npm:react@19.3.0-canary-29d9d318-20260826", "react-dom": "19.0.0", - "react-dom-builtin": "npm:react-dom@19.3.0-canary-f789f203-20260825", - "react-dom-experimental-builtin": "npm:react-dom@0.0.0-experimental-f789f203-20260825", - "react-experimental-builtin": "npm:react@0.0.0-experimental-f789f203-20260825", - "react-is-builtin": "npm:react-is@19.3.0-canary-f789f203-20260825", - "react-server-dom-turbopack": "npm:react-server-dom-turbopack@19.3.0-canary-f789f203-20260825", - "react-server-dom-turbopack-experimental": "npm:react-server-dom-turbopack@0.0.0-experimental-f789f203-20260825", - "react-server-dom-webpack": "npm:react-server-dom-webpack@19.3.0-canary-f789f203-20260825", - "react-server-dom-webpack-experimental": "npm:react-server-dom-webpack@0.0.0-experimental-f789f203-20260825", + "react-dom-builtin": "npm:react-dom@19.3.0-canary-29d9d318-20260826", + "react-dom-experimental-builtin": "npm:react-dom@0.0.0-experimental-29d9d318-20260826", + "react-experimental-builtin": "npm:react@0.0.0-experimental-29d9d318-20260826", + "react-is-builtin": "npm:react-is@19.3.0-canary-29d9d318-20260826", + "react-server-dom-turbopack": "npm:react-server-dom-turbopack@19.3.0-canary-29d9d318-20260826", + "react-server-dom-turbopack-experimental": "npm:react-server-dom-turbopack@0.0.0-experimental-29d9d318-20260826", + "react-server-dom-webpack": "npm:react-server-dom-webpack@19.3.0-canary-29d9d318-20260826", + "react-server-dom-webpack-experimental": "npm:react-server-dom-webpack@0.0.0-experimental-29d9d318-20260826", "react-ssr-prepass": "1.0.8", "react-virtualized": "9.22.3", "request-promise-core": "1.1.2", "resolve-from": "5.0.0", "sass": "1.54.0", "satori": "0.29.0", - "scheduler-builtin": "npm:scheduler@0.28.0-canary-f789f203-20260825", - "scheduler-experimental-builtin": "npm:scheduler@0.0.0-experimental-f789f203-20260825", + "scheduler-builtin": "npm:scheduler@0.28.0-canary-29d9d318-20260826", + "scheduler-experimental-builtin": "npm:scheduler@0.0.0-experimental-29d9d318-20260826", "seedrandom": "3.0.5", "semver": "7.3.7", "serve-handler": "6.1.6", @@ -321,10 +321,10 @@ "@types/react-dom": "19.2.4", "@types/retry": "0.12.0", "jest-snapshot": "30.0.0-alpha.6", - "react": "npm:react@19.3.0-canary-f789f203-20260825", - "react-dom": "npm:react-dom@19.3.0-canary-f789f203-20260825", - "react-is": "npm:react-is@19.3.0-canary-f789f203-20260825", - "scheduler": "npm:scheduler@0.28.0-canary-f789f203-20260825" + "react": "npm:react@19.3.0-canary-29d9d318-20260826", + "react-dom": "npm:react-dom@19.3.0-canary-29d9d318-20260826", + "react-is": "npm:react-is@19.3.0-canary-29d9d318-20260826", + "scheduler": "npm:scheduler@0.28.0-canary-29d9d318-20260826" }, "packageExtensions": { "eslint-plugin-react-hooks@0.0.0-experimental-6de32a5a-20250822": { diff --git a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-client.development.js b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-client.development.js index 59a141fc069c..a960699aed48 100644 --- a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-client.development.js +++ b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-client.development.js @@ -26890,7 +26890,7 @@ } function normalizeListenerOptions(opts) { return null == opts - ? "0" + ? "c=0" : "boolean" === typeof opts ? "c=" + (opts ? "1" : "0") : "c=" + (opts.capture ? "1" : "0"); @@ -30307,7 +30307,8 @@ SyntheticWheelEvent = createSyntheticEvent(WheelEventInterface), ToggleEventInterface = assign({}, EventInterface, { newState: 0, - oldState: 0 + oldState: 0, + source: 0 }), SyntheticToggleEvent = createSyntheticEvent(ToggleEventInterface), END_KEYCODES = [9, 13, 27, 32], @@ -33369,11 +33370,11 @@ }; (function () { var isomorphicReactPackageVersion = React.version; - if ("19.3.0-experimental-f789f203-20260825" !== isomorphicReactPackageVersion) + if ("19.3.0-experimental-29d9d318-20260826" !== isomorphicReactPackageVersion) throw Error( 'Incompatible React versions: The "react" and "react-dom" packages must have the exact same version. Instead got:\n - react: ' + (isomorphicReactPackageVersion + - "\n - react-dom: 19.3.0-experimental-f789f203-20260825\nLearn more: https://react.dev/warnings/version-mismatch") + "\n - react-dom: 19.3.0-experimental-29d9d318-20260826\nLearn more: https://react.dev/warnings/version-mismatch") ); })(); ("function" === typeof Map && @@ -33410,10 +33411,10 @@ !(function () { var internals = { bundleType: 1, - version: "19.3.0-experimental-f789f203-20260825", + version: "19.3.0-experimental-29d9d318-20260826", rendererPackageName: "react-dom", currentDispatcherRef: ReactSharedInternals, - reconcilerVersion: "19.3.0-experimental-f789f203-20260825" + reconcilerVersion: "19.3.0-experimental-29d9d318-20260826" }; internals.overrideHookState = overrideHookState; internals.overrideHookStateDeletePath = overrideHookStateDeletePath; @@ -33561,7 +33562,7 @@ listenToAllSupportedEvents(container); return new ReactDOMHydrationRoot(initialChildren); }; - exports.version = "19.3.0-experimental-f789f203-20260825"; + exports.version = "19.3.0-experimental-29d9d318-20260826"; "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop && diff --git a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-client.production.js b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-client.production.js index 62c663b08968..10539270ae37 100644 --- a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-client.production.js +++ b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-client.production.js @@ -2040,7 +2040,8 @@ var KeyboardEventInterface = assign({}, UIEventInterface, { SyntheticWheelEvent = createSyntheticEvent(WheelEventInterface), ToggleEventInterface = assign({}, EventInterface, { newState: 0, - oldState: 0 + oldState: 0, + source: 0 }), SyntheticToggleEvent = createSyntheticEvent(ToggleEventInterface), END_KEYCODES = [9, 13, 27, 32], @@ -17996,7 +17997,7 @@ function getAttachOptions(opts) { } function normalizeListenerOptions(opts) { return null == opts - ? "0" + ? "c=0" : "boolean" === typeof opts ? "c=" + (opts ? "1" : "0") : "c=" + (opts.capture ? "1" : "0"); @@ -20362,14 +20363,14 @@ ReactDOMHydrationRoot.prototype.unstable_scheduleHydration = function (target) { }; var isomorphicReactPackageVersion$jscomp$inline_2233 = React.version; if ( - "19.3.0-experimental-f789f203-20260825" !== + "19.3.0-experimental-29d9d318-20260826" !== isomorphicReactPackageVersion$jscomp$inline_2233 ) throw Error( formatProdErrorMessage( 527, isomorphicReactPackageVersion$jscomp$inline_2233, - "19.3.0-experimental-f789f203-20260825" + "19.3.0-experimental-29d9d318-20260826" ) ); ReactDOMSharedInternals.findDOMNode = function (componentOrElement) { @@ -20391,10 +20392,10 @@ ReactDOMSharedInternals.findDOMNode = function (componentOrElement) { }; var internals$jscomp$inline_2889 = { bundleType: 0, - version: "19.3.0-experimental-f789f203-20260825", + version: "19.3.0-experimental-29d9d318-20260826", rendererPackageName: "react-dom", currentDispatcherRef: ReactSharedInternals, - reconcilerVersion: "19.3.0-experimental-f789f203-20260825" + reconcilerVersion: "19.3.0-experimental-29d9d318-20260826" }; if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) { var hook$jscomp$inline_2890 = __REACT_DEVTOOLS_GLOBAL_HOOK__; @@ -20501,4 +20502,4 @@ exports.hydrateRoot = function (container, initialChildren, options) { listenToAllSupportedEvents(container); return new ReactDOMHydrationRoot(initialChildren); }; -exports.version = "19.3.0-experimental-f789f203-20260825"; +exports.version = "19.3.0-experimental-29d9d318-20260826"; diff --git a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-profiling.development.js b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-profiling.development.js index 8e65e156d890..7821f27f9881 100644 --- a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-profiling.development.js +++ b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-profiling.development.js @@ -26898,7 +26898,7 @@ } function normalizeListenerOptions(opts) { return null == opts - ? "0" + ? "c=0" : "boolean" === typeof opts ? "c=" + (opts ? "1" : "0") : "c=" + (opts.capture ? "1" : "0"); @@ -30364,7 +30364,8 @@ SyntheticWheelEvent = createSyntheticEvent(WheelEventInterface), ToggleEventInterface = assign({}, EventInterface, { newState: 0, - oldState: 0 + oldState: 0, + source: 0 }), SyntheticToggleEvent = createSyntheticEvent(ToggleEventInterface), END_KEYCODES = [9, 13, 27, 32], @@ -33426,11 +33427,11 @@ }; (function () { var isomorphicReactPackageVersion = React.version; - if ("19.3.0-experimental-f789f203-20260825" !== isomorphicReactPackageVersion) + if ("19.3.0-experimental-29d9d318-20260826" !== isomorphicReactPackageVersion) throw Error( 'Incompatible React versions: The "react" and "react-dom" packages must have the exact same version. Instead got:\n - react: ' + (isomorphicReactPackageVersion + - "\n - react-dom: 19.3.0-experimental-f789f203-20260825\nLearn more: https://react.dev/warnings/version-mismatch") + "\n - react-dom: 19.3.0-experimental-29d9d318-20260826\nLearn more: https://react.dev/warnings/version-mismatch") ); })(); ("function" === typeof Map && @@ -33467,10 +33468,10 @@ !(function () { var internals = { bundleType: 1, - version: "19.3.0-experimental-f789f203-20260825", + version: "19.3.0-experimental-29d9d318-20260826", rendererPackageName: "react-dom", currentDispatcherRef: ReactSharedInternals, - reconcilerVersion: "19.3.0-experimental-f789f203-20260825" + reconcilerVersion: "19.3.0-experimental-29d9d318-20260826" }; internals.overrideHookState = overrideHookState; internals.overrideHookStateDeletePath = overrideHookStateDeletePath; @@ -33960,7 +33961,7 @@ exports.useFormStatus = function () { return resolveDispatcher().useHostTransitionStatus(); }; - exports.version = "19.3.0-experimental-f789f203-20260825"; + exports.version = "19.3.0-experimental-29d9d318-20260826"; "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop && diff --git a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-profiling.profiling.js b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-profiling.profiling.js index 13e4f4c97cc7..f00a5c11c87b 100644 --- a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-profiling.profiling.js +++ b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-profiling.profiling.js @@ -2136,7 +2136,8 @@ var KeyboardEventInterface = assign({}, UIEventInterface, { SyntheticWheelEvent = createSyntheticEvent(WheelEventInterface), ToggleEventInterface = assign({}, EventInterface, { newState: 0, - oldState: 0 + oldState: 0, + source: 0 }), SyntheticToggleEvent = createSyntheticEvent(ToggleEventInterface), END_KEYCODES = [9, 13, 27, 32], @@ -20061,7 +20062,7 @@ function getAttachOptions(opts) { } function normalizeListenerOptions(opts) { return null == opts - ? "0" + ? "c=0" : "boolean" === typeof opts ? "c=" + (opts ? "1" : "0") : "c=" + (opts.capture ? "1" : "0"); @@ -22449,14 +22450,14 @@ ReactDOMHydrationRoot.prototype.unstable_scheduleHydration = function (target) { }; var isomorphicReactPackageVersion$jscomp$inline_2537 = React.version; if ( - "19.3.0-experimental-f789f203-20260825" !== + "19.3.0-experimental-29d9d318-20260826" !== isomorphicReactPackageVersion$jscomp$inline_2537 ) throw Error( formatProdErrorMessage( 527, isomorphicReactPackageVersion$jscomp$inline_2537, - "19.3.0-experimental-f789f203-20260825" + "19.3.0-experimental-29d9d318-20260826" ) ); ReactDOMSharedInternals.findDOMNode = function (componentOrElement) { @@ -22478,10 +22479,10 @@ ReactDOMSharedInternals.findDOMNode = function (componentOrElement) { }; var internals$jscomp$inline_3210 = { bundleType: 0, - version: "19.3.0-experimental-f789f203-20260825", + version: "19.3.0-experimental-29d9d318-20260826", rendererPackageName: "react-dom", currentDispatcherRef: ReactSharedInternals, - reconcilerVersion: "19.3.0-experimental-f789f203-20260825" + reconcilerVersion: "19.3.0-experimental-29d9d318-20260826" }; if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) { var hook$jscomp$inline_3211 = __REACT_DEVTOOLS_GLOBAL_HOOK__; @@ -22761,7 +22762,7 @@ exports.useFormState = function (action, initialState, permalink) { exports.useFormStatus = function () { return ReactSharedInternals.H.useHostTransitionStatus(); }; -exports.version = "19.3.0-experimental-f789f203-20260825"; +exports.version = "19.3.0-experimental-29d9d318-20260826"; "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop && diff --git a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server-legacy.browser.development.js b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server-legacy.browser.development.js index a799884350e8..979bae2a841c 100644 --- a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server-legacy.browser.development.js +++ b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server-legacy.browser.development.js @@ -10825,5 +10825,5 @@ 'The server used "renderToString" which does not support Suspense. If you intended for this Suspense boundary to render the fallback content on the server consider throwing an Error somewhere within the Suspense boundary. If you intended to have the server wait for the suspended component please switch to "renderToReadableStream" which supports Suspense on the server' ); }; - exports.version = "19.3.0-experimental-f789f203-20260825"; + exports.version = "19.3.0-experimental-29d9d318-20260826"; })(); diff --git a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server-legacy.browser.production.js b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server-legacy.browser.production.js index 006d98b76713..5ca2403da3dc 100644 --- a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server-legacy.browser.production.js +++ b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server-legacy.browser.production.js @@ -7253,4 +7253,4 @@ exports.renderToString = function (children, options) { 'The server used "renderToString" which does not support Suspense. If you intended for this Suspense boundary to render the fallback content on the server consider throwing an Error somewhere within the Suspense boundary. If you intended to have the server wait for the suspended component please switch to "renderToReadableStream" which supports Suspense on the server' ); }; -exports.version = "19.3.0-experimental-f789f203-20260825"; +exports.version = "19.3.0-experimental-29d9d318-20260826"; diff --git a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server-legacy.node.development.js b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server-legacy.node.development.js index 234b5517fc5f..1aacab42b352 100644 --- a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server-legacy.node.development.js +++ b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server-legacy.node.development.js @@ -10825,5 +10825,5 @@ 'The server used "renderToString" which does not support Suspense. If you intended for this Suspense boundary to render the fallback content on the server consider throwing an Error somewhere within the Suspense boundary. If you intended to have the server wait for the suspended component please switch to "renderToPipeableStream" which supports Suspense on the server' ); }; - exports.version = "19.3.0-experimental-f789f203-20260825"; + exports.version = "19.3.0-experimental-29d9d318-20260826"; })(); diff --git a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server-legacy.node.production.js b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server-legacy.node.production.js index c69a5a2f4781..bc3f2593dc92 100644 --- a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server-legacy.node.production.js +++ b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server-legacy.node.production.js @@ -7358,4 +7358,4 @@ exports.renderToString = function (children, options) { 'The server used "renderToString" which does not support Suspense. If you intended for this Suspense boundary to render the fallback content on the server consider throwing an Error somewhere within the Suspense boundary. If you intended to have the server wait for the suspended component please switch to "renderToPipeableStream" which supports Suspense on the server' ); }; -exports.version = "19.3.0-experimental-f789f203-20260825"; +exports.version = "19.3.0-experimental-29d9d318-20260826"; diff --git a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server.browser.development.js b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server.browser.development.js index d517eff162a3..628547df0380 100644 --- a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server.browser.development.js +++ b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server.browser.development.js @@ -9849,11 +9849,11 @@ } function ensureCorrectIsomorphicReactVersion() { var isomorphicReactPackageVersion = React.version; - if ("19.3.0-experimental-f789f203-20260825" !== isomorphicReactPackageVersion) + if ("19.3.0-experimental-29d9d318-20260826" !== isomorphicReactPackageVersion) throw Error( 'Incompatible React versions: The "react" and "react-dom" packages must have the exact same version. Instead got:\n - react: ' + (isomorphicReactPackageVersion + - "\n - react-dom: 19.3.0-experimental-f789f203-20260825\nLearn more: https://react.dev/warnings/version-mismatch") + "\n - react-dom: 19.3.0-experimental-29d9d318-20260826\nLearn more: https://react.dev/warnings/version-mismatch") ); } var React = require("next/dist/compiled/react-experimental"), @@ -11650,5 +11650,5 @@ startWork(request); }); }; - exports.version = "19.3.0-experimental-f789f203-20260825"; + exports.version = "19.3.0-experimental-29d9d318-20260826"; })(); diff --git a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server.browser.production.js b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server.browser.production.js index d1933f669301..d9f1a5c590be 100644 --- a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server.browser.production.js +++ b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server.browser.production.js @@ -7972,12 +7972,12 @@ function getPostponedState(request) { } function ensureCorrectIsomorphicReactVersion() { var isomorphicReactPackageVersion = React.version; - if ("19.3.0-experimental-f789f203-20260825" !== isomorphicReactPackageVersion) + if ("19.3.0-experimental-29d9d318-20260826" !== isomorphicReactPackageVersion) throw Error( formatProdErrorMessage( 527, isomorphicReactPackageVersion, - "19.3.0-experimental-f789f203-20260825" + "19.3.0-experimental-29d9d318-20260826" ) ); } @@ -8192,4 +8192,4 @@ exports.resumeAndPrerender = function (children, postponedState, options) { startWork(request); }); }; -exports.version = "19.3.0-experimental-f789f203-20260825"; +exports.version = "19.3.0-experimental-29d9d318-20260826"; diff --git a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server.bun.production.js b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server.bun.production.js index 53c6cc838988..8a4f2c02f900 100644 --- a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server.bun.production.js +++ b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server.bun.production.js @@ -7668,11 +7668,11 @@ function getPostponedState(request) { } function ensureCorrectIsomorphicReactVersion() { var isomorphicReactPackageVersion = React.version; - if ("19.3.0-experimental-f789f203-20260825" !== isomorphicReactPackageVersion) + if ("19.3.0-experimental-29d9d318-20260826" !== isomorphicReactPackageVersion) throw Error( 'Incompatible React versions: The "react" and "react-dom" packages must have the exact same version. Instead got:\n - react: ' + (isomorphicReactPackageVersion + - "\n - react-dom: 19.3.0-experimental-f789f203-20260825\nLearn more: https://react.dev/warnings/version-mismatch") + "\n - react-dom: 19.3.0-experimental-29d9d318-20260826\nLearn more: https://react.dev/warnings/version-mismatch") ); } ensureCorrectIsomorphicReactVersion(); @@ -8165,4 +8165,4 @@ exports.resumeToPipeableStream = function (children, postponedState, options) { } }; }; -exports.version = "19.3.0-experimental-f789f203-20260825"; +exports.version = "19.3.0-experimental-29d9d318-20260826"; diff --git a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server.edge.development.js b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server.edge.development.js index c531030c39c6..f34e543b3127 100644 --- a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server.edge.development.js +++ b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server.edge.development.js @@ -9878,11 +9878,11 @@ } function ensureCorrectIsomorphicReactVersion() { var isomorphicReactPackageVersion = React.version; - if ("19.3.0-experimental-f789f203-20260825" !== isomorphicReactPackageVersion) + if ("19.3.0-experimental-29d9d318-20260826" !== isomorphicReactPackageVersion) throw Error( 'Incompatible React versions: The "react" and "react-dom" packages must have the exact same version. Instead got:\n - react: ' + (isomorphicReactPackageVersion + - "\n - react-dom: 19.3.0-experimental-f789f203-20260825\nLearn more: https://react.dev/warnings/version-mismatch") + "\n - react-dom: 19.3.0-experimental-29d9d318-20260826\nLearn more: https://react.dev/warnings/version-mismatch") ); } var React = require("next/dist/compiled/react-experimental"), @@ -11675,5 +11675,5 @@ startWork(request); }); }; - exports.version = "19.3.0-experimental-f789f203-20260825"; + exports.version = "19.3.0-experimental-29d9d318-20260826"; })(); diff --git a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server.edge.production.js b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server.edge.production.js index d6c22e206055..127ba4936fe9 100644 --- a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server.edge.production.js +++ b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server.edge.production.js @@ -8092,11 +8092,11 @@ function getPostponedState(request) { } function ensureCorrectIsomorphicReactVersion() { var isomorphicReactPackageVersion = React.version; - if ("19.3.0-experimental-f789f203-20260825" !== isomorphicReactPackageVersion) + if ("19.3.0-experimental-29d9d318-20260826" !== isomorphicReactPackageVersion) throw Error( 'Incompatible React versions: The "react" and "react-dom" packages must have the exact same version. Instead got:\n - react: ' + (isomorphicReactPackageVersion + - "\n - react-dom: 19.3.0-experimental-f789f203-20260825\nLearn more: https://react.dev/warnings/version-mismatch") + "\n - react-dom: 19.3.0-experimental-29d9d318-20260826\nLearn more: https://react.dev/warnings/version-mismatch") ); } ensureCorrectIsomorphicReactVersion(); @@ -8310,4 +8310,4 @@ exports.resumeAndPrerender = function (children, postponedState, options) { startWork(request); }); }; -exports.version = "19.3.0-experimental-f789f203-20260825"; +exports.version = "19.3.0-experimental-29d9d318-20260826"; diff --git a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server.node.development.js b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server.node.development.js index a867dee67855..74b58fe619b1 100644 --- a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server.node.development.js +++ b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server.node.development.js @@ -9739,11 +9739,11 @@ } function ensureCorrectIsomorphicReactVersion() { var isomorphicReactPackageVersion = React.version; - if ("19.3.0-experimental-f789f203-20260825" !== isomorphicReactPackageVersion) + if ("19.3.0-experimental-29d9d318-20260826" !== isomorphicReactPackageVersion) throw Error( 'Incompatible React versions: The "react" and "react-dom" packages must have the exact same version. Instead got:\n - react: ' + (isomorphicReactPackageVersion + - "\n - react-dom: 19.3.0-experimental-f789f203-20260825\nLearn more: https://react.dev/warnings/version-mismatch") + "\n - react-dom: 19.3.0-experimental-29d9d318-20260826\nLearn more: https://react.dev/warnings/version-mismatch") ); } function createDrainHandler(destination, request) { @@ -11826,5 +11826,5 @@ } }; }; - exports.version = "19.3.0-experimental-f789f203-20260825"; + exports.version = "19.3.0-experimental-29d9d318-20260826"; })(); diff --git a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server.node.production.js b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server.node.production.js index bf4410fcb6a7..4717890240ea 100644 --- a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server.node.production.js +++ b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server.node.production.js @@ -7971,11 +7971,11 @@ function getPostponedState(request) { } function ensureCorrectIsomorphicReactVersion() { var isomorphicReactPackageVersion = React.version; - if ("19.3.0-experimental-f789f203-20260825" !== isomorphicReactPackageVersion) + if ("19.3.0-experimental-29d9d318-20260826" !== isomorphicReactPackageVersion) throw Error( 'Incompatible React versions: The "react" and "react-dom" packages must have the exact same version. Instead got:\n - react: ' + (isomorphicReactPackageVersion + - "\n - react-dom: 19.3.0-experimental-f789f203-20260825\nLearn more: https://react.dev/warnings/version-mismatch") + "\n - react-dom: 19.3.0-experimental-29d9d318-20260826\nLearn more: https://react.dev/warnings/version-mismatch") ); } ensureCorrectIsomorphicReactVersion(); @@ -8473,4 +8473,4 @@ exports.resumeToPipeableStream = function (children, postponedState, options) { } }; }; -exports.version = "19.3.0-experimental-f789f203-20260825"; +exports.version = "19.3.0-experimental-29d9d318-20260826"; diff --git a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-unstable_testing.development.js b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-unstable_testing.development.js index fdda0ab0e207..e206b53aa999 100644 --- a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-unstable_testing.development.js +++ b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-unstable_testing.development.js @@ -27113,7 +27113,7 @@ } function normalizeListenerOptions(opts) { return null == opts - ? "0" + ? "c=0" : "boolean" === typeof opts ? "c=" + (opts ? "1" : "0") : "c=" + (opts.capture ? "1" : "0"); @@ -30623,7 +30623,8 @@ SyntheticWheelEvent = createSyntheticEvent(WheelEventInterface), ToggleEventInterface = assign({}, EventInterface, { newState: 0, - oldState: 0 + oldState: 0, + source: 0 }), SyntheticToggleEvent = createSyntheticEvent(ToggleEventInterface), END_KEYCODES = [9, 13, 27, 32], @@ -33690,11 +33691,11 @@ }; (function () { var isomorphicReactPackageVersion = React.version; - if ("19.3.0-experimental-f789f203-20260825" !== isomorphicReactPackageVersion) + if ("19.3.0-experimental-29d9d318-20260826" !== isomorphicReactPackageVersion) throw Error( 'Incompatible React versions: The "react" and "react-dom" packages must have the exact same version. Instead got:\n - react: ' + (isomorphicReactPackageVersion + - "\n - react-dom: 19.3.0-experimental-f789f203-20260825\nLearn more: https://react.dev/warnings/version-mismatch") + "\n - react-dom: 19.3.0-experimental-29d9d318-20260826\nLearn more: https://react.dev/warnings/version-mismatch") ); })(); ("function" === typeof Map && @@ -33731,10 +33732,10 @@ !(function () { var internals = { bundleType: 1, - version: "19.3.0-experimental-f789f203-20260825", + version: "19.3.0-experimental-29d9d318-20260826", rendererPackageName: "react-dom", currentDispatcherRef: ReactSharedInternals, - reconcilerVersion: "19.3.0-experimental-f789f203-20260825" + reconcilerVersion: "19.3.0-experimental-29d9d318-20260826" }; internals.overrideHookState = overrideHookState; internals.overrideHookStateDeletePath = overrideHookStateDeletePath; @@ -34048,5 +34049,5 @@ } }; }; - exports.version = "19.3.0-experimental-f789f203-20260825"; + exports.version = "19.3.0-experimental-29d9d318-20260826"; })(); diff --git a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-unstable_testing.production.js b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-unstable_testing.production.js index 39fe2cab9618..2d0a47c66328 100644 --- a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-unstable_testing.production.js +++ b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-unstable_testing.production.js @@ -2126,7 +2126,8 @@ var KeyboardEventInterface = assign({}, UIEventInterface, { SyntheticWheelEvent = createSyntheticEvent(WheelEventInterface), ToggleEventInterface = assign({}, EventInterface, { newState: 0, - oldState: 0 + oldState: 0, + source: 0 }), SyntheticToggleEvent = createSyntheticEvent(ToggleEventInterface), END_KEYCODES = [9, 13, 27, 32], @@ -18268,7 +18269,7 @@ function getAttachOptions(opts) { } function normalizeListenerOptions(opts) { return null == opts - ? "0" + ? "c=0" : "boolean" === typeof opts ? "c=" + (opts ? "1" : "0") : "c=" + (opts.capture ? "1" : "0"); @@ -20678,14 +20679,14 @@ ReactDOMHydrationRoot.prototype.unstable_scheduleHydration = function (target) { }; var isomorphicReactPackageVersion$jscomp$inline_2262 = React.version; if ( - "19.3.0-experimental-f789f203-20260825" !== + "19.3.0-experimental-29d9d318-20260826" !== isomorphicReactPackageVersion$jscomp$inline_2262 ) throw Error( formatProdErrorMessage( 527, isomorphicReactPackageVersion$jscomp$inline_2262, - "19.3.0-experimental-f789f203-20260825" + "19.3.0-experimental-29d9d318-20260826" ) ); ReactDOMSharedInternals.findDOMNode = function (componentOrElement) { @@ -20707,10 +20708,10 @@ ReactDOMSharedInternals.findDOMNode = function (componentOrElement) { }; var internals$jscomp$inline_2923 = { bundleType: 0, - version: "19.3.0-experimental-f789f203-20260825", + version: "19.3.0-experimental-29d9d318-20260826", rendererPackageName: "react-dom", currentDispatcherRef: ReactSharedInternals, - reconcilerVersion: "19.3.0-experimental-f789f203-20260825" + reconcilerVersion: "19.3.0-experimental-29d9d318-20260826" }; if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) { var hook$jscomp$inline_2924 = __REACT_DEVTOOLS_GLOBAL_HOOK__; @@ -20968,4 +20969,4 @@ exports.observeVisibleRects = function ( } }; }; -exports.version = "19.3.0-experimental-f789f203-20260825"; +exports.version = "19.3.0-experimental-29d9d318-20260826"; diff --git a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom.development.js b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom.development.js index 3590e012d839..e19f16f50011 100644 --- a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom.development.js +++ b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom.development.js @@ -435,7 +435,7 @@ exports.useFormStatus = function () { return resolveDispatcher().useHostTransitionStatus(); }; - exports.version = "19.3.0-experimental-f789f203-20260825"; + exports.version = "19.3.0-experimental-29d9d318-20260826"; "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop && diff --git a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom.production.js b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom.production.js index 89569197df97..0152e6562161 100644 --- a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom.production.js +++ b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom.production.js @@ -226,4 +226,4 @@ exports.useFormState = function (action, initialState, permalink) { exports.useFormStatus = function () { return ReactSharedInternals.H.useHostTransitionStatus(); }; -exports.version = "19.3.0-experimental-f789f203-20260825"; +exports.version = "19.3.0-experimental-29d9d318-20260826"; diff --git a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom.react-server.development.js b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom.react-server.development.js index fdb1995c714e..7e5f9c0a2907 100644 --- a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom.react-server.development.js +++ b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom.react-server.development.js @@ -345,5 +345,5 @@ })) : Internals.d.m(href)); }; - exports.version = "19.3.0-experimental-f789f203-20260825"; + exports.version = "19.3.0-experimental-29d9d318-20260826"; })(); diff --git a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom.react-server.production.js b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom.react-server.production.js index 461aba1ce8e7..8834656f1529 100644 --- a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom.react-server.production.js +++ b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom.react-server.production.js @@ -158,4 +158,4 @@ exports.preloadModule = function (href, options) { }); } else Internals.d.m(href); }; -exports.version = "19.3.0-experimental-f789f203-20260825"; +exports.version = "19.3.0-experimental-29d9d318-20260826"; diff --git a/packages/next/src/compiled/react-dom-experimental/package.json b/packages/next/src/compiled/react-dom-experimental/package.json index b57114b5b6ce..b807474ce367 100644 --- a/packages/next/src/compiled/react-dom-experimental/package.json +++ b/packages/next/src/compiled/react-dom-experimental/package.json @@ -72,10 +72,10 @@ "./package.json": "./package.json" }, "dependencies": { - "scheduler": "0.0.0-experimental-f789f203-20260825" + "scheduler": "0.0.0-experimental-29d9d318-20260826" }, "peerDependencies": { - "react": "0.0.0-experimental-f789f203-20260825" + "react": "0.0.0-experimental-29d9d318-20260826" }, "browser": { "./server.js": "./server.browser.js", diff --git a/packages/next/src/compiled/react-dom/cjs/react-dom-client.development.js b/packages/next/src/compiled/react-dom/cjs/react-dom-client.development.js index d48f34c4b019..f1d15bc94104 100644 --- a/packages/next/src/compiled/react-dom/cjs/react-dom-client.development.js +++ b/packages/next/src/compiled/react-dom/cjs/react-dom-client.development.js @@ -24511,7 +24511,7 @@ } function normalizeListenerOptions(opts) { return null == opts - ? "0" + ? "c=0" : "boolean" === typeof opts ? "c=" + (opts ? "1" : "0") : "c=" + (opts.capture ? "1" : "0"); @@ -27937,7 +27937,8 @@ SyntheticWheelEvent = createSyntheticEvent(WheelEventInterface), ToggleEventInterface = assign({}, EventInterface, { newState: 0, - oldState: 0 + oldState: 0, + source: 0 }), SyntheticToggleEvent = createSyntheticEvent(ToggleEventInterface), END_KEYCODES = [9, 13, 27, 32], @@ -30938,11 +30939,11 @@ }; (function () { var isomorphicReactPackageVersion = React.version; - if ("19.3.0-canary-f789f203-20260825" !== isomorphicReactPackageVersion) + if ("19.3.0-canary-29d9d318-20260826" !== isomorphicReactPackageVersion) throw Error( 'Incompatible React versions: The "react" and "react-dom" packages must have the exact same version. Instead got:\n - react: ' + (isomorphicReactPackageVersion + - "\n - react-dom: 19.3.0-canary-f789f203-20260825\nLearn more: https://react.dev/warnings/version-mismatch") + "\n - react-dom: 19.3.0-canary-29d9d318-20260826\nLearn more: https://react.dev/warnings/version-mismatch") ); })(); ("function" === typeof Map && @@ -30979,10 +30980,10 @@ !(function () { var internals = { bundleType: 1, - version: "19.3.0-canary-f789f203-20260825", + version: "19.3.0-canary-29d9d318-20260826", rendererPackageName: "react-dom", currentDispatcherRef: ReactSharedInternals, - reconcilerVersion: "19.3.0-canary-f789f203-20260825" + reconcilerVersion: "19.3.0-canary-29d9d318-20260826" }; internals.overrideHookState = overrideHookState; internals.overrideHookStateDeletePath = overrideHookStateDeletePath; @@ -31120,7 +31121,7 @@ listenToAllSupportedEvents(container); return new ReactDOMHydrationRoot(initialChildren); }; - exports.version = "19.3.0-canary-f789f203-20260825"; + exports.version = "19.3.0-canary-29d9d318-20260826"; "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop && diff --git a/packages/next/src/compiled/react-dom/cjs/react-dom-client.production.js b/packages/next/src/compiled/react-dom/cjs/react-dom-client.production.js index c2979ad709f9..215608077328 100644 --- a/packages/next/src/compiled/react-dom/cjs/react-dom-client.production.js +++ b/packages/next/src/compiled/react-dom/cjs/react-dom-client.production.js @@ -2069,7 +2069,8 @@ var KeyboardEventInterface = assign({}, UIEventInterface, { SyntheticWheelEvent = createSyntheticEvent(WheelEventInterface), ToggleEventInterface = assign({}, EventInterface, { newState: 0, - oldState: 0 + oldState: 0, + source: 0 }), SyntheticToggleEvent = createSyntheticEvent(ToggleEventInterface), END_KEYCODES = [9, 13, 27, 32], @@ -16053,7 +16054,7 @@ function getAttachOptions(opts) { } function normalizeListenerOptions(opts) { return null == opts - ? "0" + ? "c=0" : "boolean" === typeof opts ? "c=" + (opts ? "1" : "0") : "c=" + (opts.capture ? "1" : "0"); @@ -18431,14 +18432,14 @@ ReactDOMHydrationRoot.prototype.unstable_scheduleHydration = function (target) { }; var isomorphicReactPackageVersion$jscomp$inline_2040 = React.version; if ( - "19.3.0-canary-f789f203-20260825" !== + "19.3.0-canary-29d9d318-20260826" !== isomorphicReactPackageVersion$jscomp$inline_2040 ) throw Error( formatProdErrorMessage( 527, isomorphicReactPackageVersion$jscomp$inline_2040, - "19.3.0-canary-f789f203-20260825" + "19.3.0-canary-29d9d318-20260826" ) ); ReactDOMSharedInternals.findDOMNode = function (componentOrElement) { @@ -18460,10 +18461,10 @@ ReactDOMSharedInternals.findDOMNode = function (componentOrElement) { }; var internals$jscomp$inline_2583 = { bundleType: 0, - version: "19.3.0-canary-f789f203-20260825", + version: "19.3.0-canary-29d9d318-20260826", rendererPackageName: "react-dom", currentDispatcherRef: ReactSharedInternals, - reconcilerVersion: "19.3.0-canary-f789f203-20260825" + reconcilerVersion: "19.3.0-canary-29d9d318-20260826" }; if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) { var hook$jscomp$inline_2584 = __REACT_DEVTOOLS_GLOBAL_HOOK__; @@ -18561,4 +18562,4 @@ exports.hydrateRoot = function (container, initialChildren, options) { listenToAllSupportedEvents(container); return new ReactDOMHydrationRoot(initialChildren); }; -exports.version = "19.3.0-canary-f789f203-20260825"; +exports.version = "19.3.0-canary-29d9d318-20260826"; diff --git a/packages/next/src/compiled/react-dom/cjs/react-dom-profiling.development.js b/packages/next/src/compiled/react-dom/cjs/react-dom-profiling.development.js index 0839978b3958..559e6a69663f 100644 --- a/packages/next/src/compiled/react-dom/cjs/react-dom-profiling.development.js +++ b/packages/next/src/compiled/react-dom/cjs/react-dom-profiling.development.js @@ -24519,7 +24519,7 @@ } function normalizeListenerOptions(opts) { return null == opts - ? "0" + ? "c=0" : "boolean" === typeof opts ? "c=" + (opts ? "1" : "0") : "c=" + (opts.capture ? "1" : "0"); @@ -27995,7 +27995,8 @@ SyntheticWheelEvent = createSyntheticEvent(WheelEventInterface), ToggleEventInterface = assign({}, EventInterface, { newState: 0, - oldState: 0 + oldState: 0, + source: 0 }), SyntheticToggleEvent = createSyntheticEvent(ToggleEventInterface), END_KEYCODES = [9, 13, 27, 32], @@ -30996,11 +30997,11 @@ }; (function () { var isomorphicReactPackageVersion = React.version; - if ("19.3.0-canary-f789f203-20260825" !== isomorphicReactPackageVersion) + if ("19.3.0-canary-29d9d318-20260826" !== isomorphicReactPackageVersion) throw Error( 'Incompatible React versions: The "react" and "react-dom" packages must have the exact same version. Instead got:\n - react: ' + (isomorphicReactPackageVersion + - "\n - react-dom: 19.3.0-canary-f789f203-20260825\nLearn more: https://react.dev/warnings/version-mismatch") + "\n - react-dom: 19.3.0-canary-29d9d318-20260826\nLearn more: https://react.dev/warnings/version-mismatch") ); })(); ("function" === typeof Map && @@ -31037,10 +31038,10 @@ !(function () { var internals = { bundleType: 1, - version: "19.3.0-canary-f789f203-20260825", + version: "19.3.0-canary-29d9d318-20260826", rendererPackageName: "react-dom", currentDispatcherRef: ReactSharedInternals, - reconcilerVersion: "19.3.0-canary-f789f203-20260825" + reconcilerVersion: "19.3.0-canary-29d9d318-20260826" }; internals.overrideHookState = overrideHookState; internals.overrideHookStateDeletePath = overrideHookStateDeletePath; @@ -31520,7 +31521,7 @@ exports.useFormStatus = function () { return resolveDispatcher().useHostTransitionStatus(); }; - exports.version = "19.3.0-canary-f789f203-20260825"; + exports.version = "19.3.0-canary-29d9d318-20260826"; "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop && diff --git a/packages/next/src/compiled/react-dom/cjs/react-dom-profiling.profiling.js b/packages/next/src/compiled/react-dom/cjs/react-dom-profiling.profiling.js index adbf48c783ff..cb8f69749917 100644 --- a/packages/next/src/compiled/react-dom/cjs/react-dom-profiling.profiling.js +++ b/packages/next/src/compiled/react-dom/cjs/react-dom-profiling.profiling.js @@ -2166,7 +2166,8 @@ var KeyboardEventInterface = assign({}, UIEventInterface, { SyntheticWheelEvent = createSyntheticEvent(WheelEventInterface), ToggleEventInterface = assign({}, EventInterface, { newState: 0, - oldState: 0 + oldState: 0, + source: 0 }), SyntheticToggleEvent = createSyntheticEvent(ToggleEventInterface), END_KEYCODES = [9, 13, 27, 32], @@ -17965,7 +17966,7 @@ function getAttachOptions(opts) { } function normalizeListenerOptions(opts) { return null == opts - ? "0" + ? "c=0" : "boolean" === typeof opts ? "c=" + (opts ? "1" : "0") : "c=" + (opts.capture ? "1" : "0"); @@ -20365,14 +20366,14 @@ ReactDOMHydrationRoot.prototype.unstable_scheduleHydration = function (target) { }; var isomorphicReactPackageVersion$jscomp$inline_2344 = React.version; if ( - "19.3.0-canary-f789f203-20260825" !== + "19.3.0-canary-29d9d318-20260826" !== isomorphicReactPackageVersion$jscomp$inline_2344 ) throw Error( formatProdErrorMessage( 527, isomorphicReactPackageVersion$jscomp$inline_2344, - "19.3.0-canary-f789f203-20260825" + "19.3.0-canary-29d9d318-20260826" ) ); ReactDOMSharedInternals.findDOMNode = function (componentOrElement) { @@ -20394,10 +20395,10 @@ ReactDOMSharedInternals.findDOMNode = function (componentOrElement) { }; var internals$jscomp$inline_2900 = { bundleType: 0, - version: "19.3.0-canary-f789f203-20260825", + version: "19.3.0-canary-29d9d318-20260826", rendererPackageName: "react-dom", currentDispatcherRef: ReactSharedInternals, - reconcilerVersion: "19.3.0-canary-f789f203-20260825" + reconcilerVersion: "19.3.0-canary-29d9d318-20260826" }; if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) { var hook$jscomp$inline_2901 = __REACT_DEVTOOLS_GLOBAL_HOOK__; @@ -20668,7 +20669,7 @@ exports.useFormState = function (action, initialState, permalink) { exports.useFormStatus = function () { return ReactSharedInternals.H.useHostTransitionStatus(); }; -exports.version = "19.3.0-canary-f789f203-20260825"; +exports.version = "19.3.0-canary-29d9d318-20260826"; "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop && diff --git a/packages/next/src/compiled/react-dom/cjs/react-dom-server-legacy.browser.development.js b/packages/next/src/compiled/react-dom/cjs/react-dom-server-legacy.browser.development.js index c6d47be0f36e..dccad9e3bf07 100644 --- a/packages/next/src/compiled/react-dom/cjs/react-dom-server-legacy.browser.development.js +++ b/packages/next/src/compiled/react-dom/cjs/react-dom-server-legacy.browser.development.js @@ -10438,5 +10438,5 @@ 'The server used "renderToString" which does not support Suspense. If you intended for this Suspense boundary to render the fallback content on the server consider throwing an Error somewhere within the Suspense boundary. If you intended to have the server wait for the suspended component please switch to "renderToReadableStream" which supports Suspense on the server' ); }; - exports.version = "19.3.0-canary-f789f203-20260825"; + exports.version = "19.3.0-canary-29d9d318-20260826"; })(); diff --git a/packages/next/src/compiled/react-dom/cjs/react-dom-server-legacy.browser.production.js b/packages/next/src/compiled/react-dom/cjs/react-dom-server-legacy.browser.production.js index 9182f77ba462..a512c3f3c9ea 100644 --- a/packages/next/src/compiled/react-dom/cjs/react-dom-server-legacy.browser.production.js +++ b/packages/next/src/compiled/react-dom/cjs/react-dom-server-legacy.browser.production.js @@ -6975,4 +6975,4 @@ exports.renderToString = function (children, options) { 'The server used "renderToString" which does not support Suspense. If you intended for this Suspense boundary to render the fallback content on the server consider throwing an Error somewhere within the Suspense boundary. If you intended to have the server wait for the suspended component please switch to "renderToReadableStream" which supports Suspense on the server' ); }; -exports.version = "19.3.0-canary-f789f203-20260825"; +exports.version = "19.3.0-canary-29d9d318-20260826"; diff --git a/packages/next/src/compiled/react-dom/cjs/react-dom-server-legacy.node.development.js b/packages/next/src/compiled/react-dom/cjs/react-dom-server-legacy.node.development.js index 61ec17a94e43..836ff9bde22a 100644 --- a/packages/next/src/compiled/react-dom/cjs/react-dom-server-legacy.node.development.js +++ b/packages/next/src/compiled/react-dom/cjs/react-dom-server-legacy.node.development.js @@ -10438,5 +10438,5 @@ 'The server used "renderToString" which does not support Suspense. If you intended for this Suspense boundary to render the fallback content on the server consider throwing an Error somewhere within the Suspense boundary. If you intended to have the server wait for the suspended component please switch to "renderToPipeableStream" which supports Suspense on the server' ); }; - exports.version = "19.3.0-canary-f789f203-20260825"; + exports.version = "19.3.0-canary-29d9d318-20260826"; })(); diff --git a/packages/next/src/compiled/react-dom/cjs/react-dom-server-legacy.node.production.js b/packages/next/src/compiled/react-dom/cjs/react-dom-server-legacy.node.production.js index 97128d6aba88..2afa2044aef8 100644 --- a/packages/next/src/compiled/react-dom/cjs/react-dom-server-legacy.node.production.js +++ b/packages/next/src/compiled/react-dom/cjs/react-dom-server-legacy.node.production.js @@ -7069,4 +7069,4 @@ exports.renderToString = function (children, options) { 'The server used "renderToString" which does not support Suspense. If you intended for this Suspense boundary to render the fallback content on the server consider throwing an Error somewhere within the Suspense boundary. If you intended to have the server wait for the suspended component please switch to "renderToPipeableStream" which supports Suspense on the server' ); }; -exports.version = "19.3.0-canary-f789f203-20260825"; +exports.version = "19.3.0-canary-29d9d318-20260826"; diff --git a/packages/next/src/compiled/react-dom/cjs/react-dom-server.browser.development.js b/packages/next/src/compiled/react-dom/cjs/react-dom-server.browser.development.js index cea316d60764..07087e9d103c 100644 --- a/packages/next/src/compiled/react-dom/cjs/react-dom-server.browser.development.js +++ b/packages/next/src/compiled/react-dom/cjs/react-dom-server.browser.development.js @@ -9417,11 +9417,11 @@ } function ensureCorrectIsomorphicReactVersion() { var isomorphicReactPackageVersion = React.version; - if ("19.3.0-canary-f789f203-20260825" !== isomorphicReactPackageVersion) + if ("19.3.0-canary-29d9d318-20260826" !== isomorphicReactPackageVersion) throw Error( 'Incompatible React versions: The "react" and "react-dom" packages must have the exact same version. Instead got:\n - react: ' + (isomorphicReactPackageVersion + - "\n - react-dom: 19.3.0-canary-f789f203-20260825\nLearn more: https://react.dev/warnings/version-mismatch") + "\n - react-dom: 19.3.0-canary-29d9d318-20260826\nLearn more: https://react.dev/warnings/version-mismatch") ); } var React = require("next/dist/compiled/react"), @@ -11201,5 +11201,5 @@ startWork(request); }); }; - exports.version = "19.3.0-canary-f789f203-20260825"; + exports.version = "19.3.0-canary-29d9d318-20260826"; })(); diff --git a/packages/next/src/compiled/react-dom/cjs/react-dom-server.browser.production.js b/packages/next/src/compiled/react-dom/cjs/react-dom-server.browser.production.js index afe35988e66f..46255aaa6104 100644 --- a/packages/next/src/compiled/react-dom/cjs/react-dom-server.browser.production.js +++ b/packages/next/src/compiled/react-dom/cjs/react-dom-server.browser.production.js @@ -7611,12 +7611,12 @@ function getPostponedState(request) { } function ensureCorrectIsomorphicReactVersion() { var isomorphicReactPackageVersion = React.version; - if ("19.3.0-canary-f789f203-20260825" !== isomorphicReactPackageVersion) + if ("19.3.0-canary-29d9d318-20260826" !== isomorphicReactPackageVersion) throw Error( formatProdErrorMessage( 527, isomorphicReactPackageVersion, - "19.3.0-canary-f789f203-20260825" + "19.3.0-canary-29d9d318-20260826" ) ); } @@ -7831,4 +7831,4 @@ exports.resumeAndPrerender = function (children, postponedState, options) { startWork(request); }); }; -exports.version = "19.3.0-canary-f789f203-20260825"; +exports.version = "19.3.0-canary-29d9d318-20260826"; diff --git a/packages/next/src/compiled/react-dom/cjs/react-dom-server.bun.production.js b/packages/next/src/compiled/react-dom/cjs/react-dom-server.bun.production.js index 4585433fc2a2..992c52895e57 100644 --- a/packages/next/src/compiled/react-dom/cjs/react-dom-server.bun.production.js +++ b/packages/next/src/compiled/react-dom/cjs/react-dom-server.bun.production.js @@ -7322,11 +7322,11 @@ function getPostponedState(request) { } function ensureCorrectIsomorphicReactVersion() { var isomorphicReactPackageVersion = React.version; - if ("19.3.0-canary-f789f203-20260825" !== isomorphicReactPackageVersion) + if ("19.3.0-canary-29d9d318-20260826" !== isomorphicReactPackageVersion) throw Error( 'Incompatible React versions: The "react" and "react-dom" packages must have the exact same version. Instead got:\n - react: ' + (isomorphicReactPackageVersion + - "\n - react-dom: 19.3.0-canary-f789f203-20260825\nLearn more: https://react.dev/warnings/version-mismatch") + "\n - react-dom: 19.3.0-canary-29d9d318-20260826\nLearn more: https://react.dev/warnings/version-mismatch") ); } ensureCorrectIsomorphicReactVersion(); @@ -7819,4 +7819,4 @@ exports.resumeToPipeableStream = function (children, postponedState, options) { } }; }; -exports.version = "19.3.0-canary-f789f203-20260825"; +exports.version = "19.3.0-canary-29d9d318-20260826"; diff --git a/packages/next/src/compiled/react-dom/cjs/react-dom-server.edge.development.js b/packages/next/src/compiled/react-dom/cjs/react-dom-server.edge.development.js index 0292932024d9..8ca91d8c35dd 100644 --- a/packages/next/src/compiled/react-dom/cjs/react-dom-server.edge.development.js +++ b/packages/next/src/compiled/react-dom/cjs/react-dom-server.edge.development.js @@ -9440,11 +9440,11 @@ } function ensureCorrectIsomorphicReactVersion() { var isomorphicReactPackageVersion = React.version; - if ("19.3.0-canary-f789f203-20260825" !== isomorphicReactPackageVersion) + if ("19.3.0-canary-29d9d318-20260826" !== isomorphicReactPackageVersion) throw Error( 'Incompatible React versions: The "react" and "react-dom" packages must have the exact same version. Instead got:\n - react: ' + (isomorphicReactPackageVersion + - "\n - react-dom: 19.3.0-canary-f789f203-20260825\nLearn more: https://react.dev/warnings/version-mismatch") + "\n - react-dom: 19.3.0-canary-29d9d318-20260826\nLearn more: https://react.dev/warnings/version-mismatch") ); } var React = require("next/dist/compiled/react"), @@ -11220,5 +11220,5 @@ startWork(request); }); }; - exports.version = "19.3.0-canary-f789f203-20260825"; + exports.version = "19.3.0-canary-29d9d318-20260826"; })(); diff --git a/packages/next/src/compiled/react-dom/cjs/react-dom-server.edge.production.js b/packages/next/src/compiled/react-dom/cjs/react-dom-server.edge.production.js index a9b11504b2ba..048a8309ee0b 100644 --- a/packages/next/src/compiled/react-dom/cjs/react-dom-server.edge.production.js +++ b/packages/next/src/compiled/react-dom/cjs/react-dom-server.edge.production.js @@ -7720,11 +7720,11 @@ function getPostponedState(request) { } function ensureCorrectIsomorphicReactVersion() { var isomorphicReactPackageVersion = React.version; - if ("19.3.0-canary-f789f203-20260825" !== isomorphicReactPackageVersion) + if ("19.3.0-canary-29d9d318-20260826" !== isomorphicReactPackageVersion) throw Error( 'Incompatible React versions: The "react" and "react-dom" packages must have the exact same version. Instead got:\n - react: ' + (isomorphicReactPackageVersion + - "\n - react-dom: 19.3.0-canary-f789f203-20260825\nLearn more: https://react.dev/warnings/version-mismatch") + "\n - react-dom: 19.3.0-canary-29d9d318-20260826\nLearn more: https://react.dev/warnings/version-mismatch") ); } ensureCorrectIsomorphicReactVersion(); @@ -7938,4 +7938,4 @@ exports.resumeAndPrerender = function (children, postponedState, options) { startWork(request); }); }; -exports.version = "19.3.0-canary-f789f203-20260825"; +exports.version = "19.3.0-canary-29d9d318-20260826"; diff --git a/packages/next/src/compiled/react-dom/cjs/react-dom-server.node.development.js b/packages/next/src/compiled/react-dom/cjs/react-dom-server.node.development.js index 376921d4db5c..ec8066e523dd 100644 --- a/packages/next/src/compiled/react-dom/cjs/react-dom-server.node.development.js +++ b/packages/next/src/compiled/react-dom/cjs/react-dom-server.node.development.js @@ -9315,11 +9315,11 @@ } function ensureCorrectIsomorphicReactVersion() { var isomorphicReactPackageVersion = React.version; - if ("19.3.0-canary-f789f203-20260825" !== isomorphicReactPackageVersion) + if ("19.3.0-canary-29d9d318-20260826" !== isomorphicReactPackageVersion) throw Error( 'Incompatible React versions: The "react" and "react-dom" packages must have the exact same version. Instead got:\n - react: ' + (isomorphicReactPackageVersion + - "\n - react-dom: 19.3.0-canary-f789f203-20260825\nLearn more: https://react.dev/warnings/version-mismatch") + "\n - react-dom: 19.3.0-canary-29d9d318-20260826\nLearn more: https://react.dev/warnings/version-mismatch") ); } function createDrainHandler(destination, request) { @@ -11385,5 +11385,5 @@ } }; }; - exports.version = "19.3.0-canary-f789f203-20260825"; + exports.version = "19.3.0-canary-29d9d318-20260826"; })(); diff --git a/packages/next/src/compiled/react-dom/cjs/react-dom-server.node.production.js b/packages/next/src/compiled/react-dom/cjs/react-dom-server.node.production.js index 209463757406..b140ad38d627 100644 --- a/packages/next/src/compiled/react-dom/cjs/react-dom-server.node.production.js +++ b/packages/next/src/compiled/react-dom/cjs/react-dom-server.node.production.js @@ -7613,11 +7613,11 @@ function getPostponedState(request) { } function ensureCorrectIsomorphicReactVersion() { var isomorphicReactPackageVersion = React.version; - if ("19.3.0-canary-f789f203-20260825" !== isomorphicReactPackageVersion) + if ("19.3.0-canary-29d9d318-20260826" !== isomorphicReactPackageVersion) throw Error( 'Incompatible React versions: The "react" and "react-dom" packages must have the exact same version. Instead got:\n - react: ' + (isomorphicReactPackageVersion + - "\n - react-dom: 19.3.0-canary-f789f203-20260825\nLearn more: https://react.dev/warnings/version-mismatch") + "\n - react-dom: 19.3.0-canary-29d9d318-20260826\nLearn more: https://react.dev/warnings/version-mismatch") ); } ensureCorrectIsomorphicReactVersion(); @@ -8115,4 +8115,4 @@ exports.resumeToPipeableStream = function (children, postponedState, options) { } }; }; -exports.version = "19.3.0-canary-f789f203-20260825"; +exports.version = "19.3.0-canary-29d9d318-20260826"; diff --git a/packages/next/src/compiled/react-dom/cjs/react-dom.development.js b/packages/next/src/compiled/react-dom/cjs/react-dom.development.js index 33da6f2c561f..f1f630e2afa8 100644 --- a/packages/next/src/compiled/react-dom/cjs/react-dom.development.js +++ b/packages/next/src/compiled/react-dom/cjs/react-dom.development.js @@ -435,7 +435,7 @@ exports.useFormStatus = function () { return resolveDispatcher().useHostTransitionStatus(); }; - exports.version = "19.3.0-canary-f789f203-20260825"; + exports.version = "19.3.0-canary-29d9d318-20260826"; "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop && diff --git a/packages/next/src/compiled/react-dom/cjs/react-dom.production.js b/packages/next/src/compiled/react-dom/cjs/react-dom.production.js index cb4119b0fb6a..eb6e840dec56 100644 --- a/packages/next/src/compiled/react-dom/cjs/react-dom.production.js +++ b/packages/next/src/compiled/react-dom/cjs/react-dom.production.js @@ -226,4 +226,4 @@ exports.useFormState = function (action, initialState, permalink) { exports.useFormStatus = function () { return ReactSharedInternals.H.useHostTransitionStatus(); }; -exports.version = "19.3.0-canary-f789f203-20260825"; +exports.version = "19.3.0-canary-29d9d318-20260826"; diff --git a/packages/next/src/compiled/react-dom/cjs/react-dom.react-server.development.js b/packages/next/src/compiled/react-dom/cjs/react-dom.react-server.development.js index 09c7396dc237..ff6ce8a33951 100644 --- a/packages/next/src/compiled/react-dom/cjs/react-dom.react-server.development.js +++ b/packages/next/src/compiled/react-dom/cjs/react-dom.react-server.development.js @@ -345,5 +345,5 @@ })) : Internals.d.m(href)); }; - exports.version = "19.3.0-canary-f789f203-20260825"; + exports.version = "19.3.0-canary-29d9d318-20260826"; })(); diff --git a/packages/next/src/compiled/react-dom/cjs/react-dom.react-server.production.js b/packages/next/src/compiled/react-dom/cjs/react-dom.react-server.production.js index 1a443c18df61..e6cebf093477 100644 --- a/packages/next/src/compiled/react-dom/cjs/react-dom.react-server.production.js +++ b/packages/next/src/compiled/react-dom/cjs/react-dom.react-server.production.js @@ -158,4 +158,4 @@ exports.preloadModule = function (href, options) { }); } else Internals.d.m(href); }; -exports.version = "19.3.0-canary-f789f203-20260825"; +exports.version = "19.3.0-canary-29d9d318-20260826"; diff --git a/packages/next/src/compiled/react-dom/package.json b/packages/next/src/compiled/react-dom/package.json index a44988ce6813..24f324d5c77e 100644 --- a/packages/next/src/compiled/react-dom/package.json +++ b/packages/next/src/compiled/react-dom/package.json @@ -67,10 +67,10 @@ "./package.json": "./package.json" }, "dependencies": { - "scheduler": "0.28.0-canary-f789f203-20260825" + "scheduler": "0.28.0-canary-29d9d318-20260826" }, "peerDependencies": { - "react": "19.3.0-canary-f789f203-20260825" + "react": "19.3.0-canary-29d9d318-20260826" }, "browser": { "./server.js": "./server.browser.js", diff --git a/packages/next/src/compiled/react-experimental/cjs/react.development.js b/packages/next/src/compiled/react-experimental/cjs/react.development.js index da523b204702..56f070203fcd 100644 --- a/packages/next/src/compiled/react-experimental/cjs/react.development.js +++ b/packages/next/src/compiled/react-experimental/cjs/react.development.js @@ -1391,7 +1391,7 @@ exports.useTransition = function () { return resolveDispatcher().useTransition(); }; - exports.version = "19.3.0-experimental-f789f203-20260825"; + exports.version = "19.3.0-experimental-29d9d318-20260826"; "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop && diff --git a/packages/next/src/compiled/react-experimental/cjs/react.production.js b/packages/next/src/compiled/react-experimental/cjs/react.production.js index 0ec66ca95d98..e9fdb3704571 100644 --- a/packages/next/src/compiled/react-experimental/cjs/react.production.js +++ b/packages/next/src/compiled/react-experimental/cjs/react.production.js @@ -613,4 +613,4 @@ exports.useSyncExternalStore = function ( exports.useTransition = function () { return ReactSharedInternals.H.useTransition(); }; -exports.version = "19.3.0-experimental-f789f203-20260825"; +exports.version = "19.3.0-experimental-29d9d318-20260826"; diff --git a/packages/next/src/compiled/react-experimental/cjs/react.react-server.development.js b/packages/next/src/compiled/react-experimental/cjs/react.react-server.development.js index 05da3175e170..b440c378777d 100644 --- a/packages/next/src/compiled/react-experimental/cjs/react.react-server.development.js +++ b/packages/next/src/compiled/react-experimental/cjs/react.react-server.development.js @@ -1061,5 +1061,5 @@ exports.useMemo = function (create, deps) { return resolveDispatcher().useMemo(create, deps); }; - exports.version = "19.3.0-experimental-f789f203-20260825"; + exports.version = "19.3.0-experimental-29d9d318-20260826"; })(); diff --git a/packages/next/src/compiled/react-experimental/cjs/react.react-server.production.js b/packages/next/src/compiled/react-experimental/cjs/react.react-server.production.js index 80d97888b0bf..876475660107 100644 --- a/packages/next/src/compiled/react-experimental/cjs/react.react-server.production.js +++ b/packages/next/src/compiled/react-experimental/cjs/react.react-server.production.js @@ -579,4 +579,4 @@ exports.useId = function () { exports.useMemo = function (create, deps) { return ReactSharedInternals.H.useMemo(create, deps); }; -exports.version = "19.3.0-experimental-f789f203-20260825"; +exports.version = "19.3.0-experimental-29d9d318-20260826"; diff --git a/packages/next/src/compiled/react-is/package.json b/packages/next/src/compiled/react-is/package.json index 812df45351a5..86d00200ddc3 100644 --- a/packages/next/src/compiled/react-is/package.json +++ b/packages/next/src/compiled/react-is/package.json @@ -1,6 +1,6 @@ { "name": "react-is", - "version": "19.3.0-canary-f789f203-20260825", + "version": "19.3.0-canary-29d9d318-20260826", "description": "Brand checking of React Elements.", "main": "index.js", "sideEffects": false, diff --git a/packages/next/src/compiled/react-server-dom-turbopack-experimental/cjs/react-server-dom-turbopack-client.browser.development.js b/packages/next/src/compiled/react-server-dom-turbopack-experimental/cjs/react-server-dom-turbopack-client.browser.development.js index dd1e1a3d8d62..018d21476164 100644 --- a/packages/next/src/compiled/react-server-dom-turbopack-experimental/cjs/react-server-dom-turbopack-client.browser.development.js +++ b/packages/next/src/compiled/react-server-dom-turbopack-experimental/cjs/react-server-dom-turbopack-client.browser.development.js @@ -5296,10 +5296,10 @@ return hook.checkDCE ? !0 : !1; })({ bundleType: 1, - version: "19.3.0-experimental-f789f203-20260825", + version: "19.3.0-experimental-29d9d318-20260826", rendererPackageName: "react-server-dom-turbopack", currentDispatcherRef: ReactSharedInternals, - reconcilerVersion: "19.3.0-experimental-f789f203-20260825", + reconcilerVersion: "19.3.0-experimental-29d9d318-20260826", getCurrentComponentInfo: function () { return currentOwnerInDEV; } diff --git a/packages/next/src/compiled/react-server-dom-turbopack-experimental/package.json b/packages/next/src/compiled/react-server-dom-turbopack-experimental/package.json index fb99ddf61d6f..d2575def23ac 100644 --- a/packages/next/src/compiled/react-server-dom-turbopack-experimental/package.json +++ b/packages/next/src/compiled/react-server-dom-turbopack-experimental/package.json @@ -48,7 +48,7 @@ "neo-async": "^2.6.1" }, "peerDependencies": { - "react": "0.0.0-experimental-f789f203-20260825", - "react-dom": "0.0.0-experimental-f789f203-20260825" + "react": "0.0.0-experimental-29d9d318-20260826", + "react-dom": "0.0.0-experimental-29d9d318-20260826" } } \ No newline at end of file diff --git a/packages/next/src/compiled/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.browser.development.js b/packages/next/src/compiled/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.browser.development.js index cbf28ad1bc6b..0918b99c2e07 100644 --- a/packages/next/src/compiled/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.browser.development.js +++ b/packages/next/src/compiled/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.browser.development.js @@ -5277,10 +5277,10 @@ return hook.checkDCE ? !0 : !1; })({ bundleType: 1, - version: "19.3.0-canary-f789f203-20260825", + version: "19.3.0-canary-29d9d318-20260826", rendererPackageName: "react-server-dom-turbopack", currentDispatcherRef: ReactSharedInternals, - reconcilerVersion: "19.3.0-canary-f789f203-20260825", + reconcilerVersion: "19.3.0-canary-29d9d318-20260826", getCurrentComponentInfo: function () { return currentOwnerInDEV; } diff --git a/packages/next/src/compiled/react-server-dom-turbopack/package.json b/packages/next/src/compiled/react-server-dom-turbopack/package.json index 639365cd3e43..7ae717096989 100644 --- a/packages/next/src/compiled/react-server-dom-turbopack/package.json +++ b/packages/next/src/compiled/react-server-dom-turbopack/package.json @@ -48,7 +48,7 @@ "neo-async": "^2.6.1" }, "peerDependencies": { - "react": "19.3.0-canary-f789f203-20260825", - "react-dom": "19.3.0-canary-f789f203-20260825" + "react": "19.3.0-canary-29d9d318-20260826", + "react-dom": "19.3.0-canary-29d9d318-20260826" } } \ No newline at end of file diff --git a/packages/next/src/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-client.browser.development.js b/packages/next/src/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-client.browser.development.js index 3bfe31510e2f..428a7fdb8582 100644 --- a/packages/next/src/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-client.browser.development.js +++ b/packages/next/src/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-client.browser.development.js @@ -5308,10 +5308,10 @@ return hook.checkDCE ? !0 : !1; })({ bundleType: 1, - version: "19.3.0-experimental-f789f203-20260825", + version: "19.3.0-experimental-29d9d318-20260826", rendererPackageName: "react-server-dom-webpack", currentDispatcherRef: ReactSharedInternals, - reconcilerVersion: "19.3.0-experimental-f789f203-20260825", + reconcilerVersion: "19.3.0-experimental-29d9d318-20260826", getCurrentComponentInfo: function () { return currentOwnerInDEV; } diff --git a/packages/next/src/compiled/react-server-dom-webpack-experimental/package.json b/packages/next/src/compiled/react-server-dom-webpack-experimental/package.json index c4d2d665a3a8..68056c332e65 100644 --- a/packages/next/src/compiled/react-server-dom-webpack-experimental/package.json +++ b/packages/next/src/compiled/react-server-dom-webpack-experimental/package.json @@ -52,8 +52,8 @@ "webpack-sources": "^3.2.0" }, "peerDependencies": { - "react": "0.0.0-experimental-f789f203-20260825", - "react-dom": "0.0.0-experimental-f789f203-20260825", + "react": "0.0.0-experimental-29d9d318-20260826", + "react-dom": "0.0.0-experimental-29d9d318-20260826", "webpack": "^5.59.0" } } \ No newline at end of file diff --git a/packages/next/src/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js b/packages/next/src/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js index 19f1f422df92..e973b14288b9 100644 --- a/packages/next/src/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js +++ b/packages/next/src/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js @@ -5289,10 +5289,10 @@ return hook.checkDCE ? !0 : !1; })({ bundleType: 1, - version: "19.3.0-canary-f789f203-20260825", + version: "19.3.0-canary-29d9d318-20260826", rendererPackageName: "react-server-dom-webpack", currentDispatcherRef: ReactSharedInternals, - reconcilerVersion: "19.3.0-canary-f789f203-20260825", + reconcilerVersion: "19.3.0-canary-29d9d318-20260826", getCurrentComponentInfo: function () { return currentOwnerInDEV; } diff --git a/packages/next/src/compiled/react-server-dom-webpack/package.json b/packages/next/src/compiled/react-server-dom-webpack/package.json index 3f4b1b2ed5bf..47d582be99b8 100644 --- a/packages/next/src/compiled/react-server-dom-webpack/package.json +++ b/packages/next/src/compiled/react-server-dom-webpack/package.json @@ -52,8 +52,8 @@ "webpack-sources": "^3.2.0" }, "peerDependencies": { - "react": "19.3.0-canary-f789f203-20260825", - "react-dom": "19.3.0-canary-f789f203-20260825", + "react": "19.3.0-canary-29d9d318-20260826", + "react-dom": "19.3.0-canary-29d9d318-20260826", "webpack": "^5.59.0" } } \ No newline at end of file diff --git a/packages/next/src/compiled/react/cjs/react.development.js b/packages/next/src/compiled/react/cjs/react.development.js index 168ec0b49b27..93c19a768381 100644 --- a/packages/next/src/compiled/react/cjs/react.development.js +++ b/packages/next/src/compiled/react/cjs/react.development.js @@ -1322,7 +1322,7 @@ exports.useTransition = function () { return resolveDispatcher().useTransition(); }; - exports.version = "19.3.0-canary-f789f203-20260825"; + exports.version = "19.3.0-canary-29d9d318-20260826"; "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop && diff --git a/packages/next/src/compiled/react/cjs/react.production.js b/packages/next/src/compiled/react/cjs/react.production.js index 8f0a1acad217..88424bf38e71 100644 --- a/packages/next/src/compiled/react/cjs/react.production.js +++ b/packages/next/src/compiled/react/cjs/react.production.js @@ -562,4 +562,4 @@ exports.useSyncExternalStore = function ( exports.useTransition = function () { return ReactSharedInternals.H.useTransition(); }; -exports.version = "19.3.0-canary-f789f203-20260825"; +exports.version = "19.3.0-canary-29d9d318-20260826"; diff --git a/packages/next/src/compiled/react/cjs/react.react-server.development.js b/packages/next/src/compiled/react/cjs/react.react-server.development.js index 3822f1981832..c0ed749da514 100644 --- a/packages/next/src/compiled/react/cjs/react.react-server.development.js +++ b/packages/next/src/compiled/react/cjs/react.react-server.development.js @@ -874,5 +874,5 @@ exports.useMemo = function (create, deps) { return resolveDispatcher().useMemo(create, deps); }; - exports.version = "19.3.0-canary-f789f203-20260825"; + exports.version = "19.3.0-canary-29d9d318-20260826"; })(); diff --git a/packages/next/src/compiled/react/cjs/react.react-server.production.js b/packages/next/src/compiled/react/cjs/react.react-server.production.js index ff0b35967188..dfc9079cf2d3 100644 --- a/packages/next/src/compiled/react/cjs/react.react-server.production.js +++ b/packages/next/src/compiled/react/cjs/react.react-server.production.js @@ -433,4 +433,4 @@ exports.useId = function () { exports.useMemo = function (create, deps) { return ReactSharedInternals.H.useMemo(create, deps); }; -exports.version = "19.3.0-canary-f789f203-20260825"; +exports.version = "19.3.0-canary-29d9d318-20260826"; diff --git a/packages/next/src/compiled/unistore/unistore.js b/packages/next/src/compiled/unistore/unistore.js index f9dadceb3f25..bf75f7faa2ac 100644 --- a/packages/next/src/compiled/unistore/unistore.js +++ b/packages/next/src/compiled/unistore/unistore.js @@ -1 +1 @@ -(()=>{var t={755:t=>{function n(t,i){for(var _ in i)t[_]=i[_];return t}t.exports=function(t){var i=[];function u(t){for(var _=[],a=0;a{var t={607:t=>{function n(t,i){for(var _ in i)t[_]=i[_];return t}t.exports=function(t){var i=[];function u(t){for(var _=[],a=0;a=0.15.0' - version: 2.2.1(react@19.3.0-canary-f789f203-20260825) + version: 2.2.1(react@19.3.0-canary-29d9d318-20260826) source-map: specifier: ^0.7.0 version: 0.7.3 @@ -1956,7 +1956,7 @@ importers: devDependencies: '@napi-rs/cli': specifier: 3.7.2 - version: 3.7.2(@emnapi/runtime@1.11.2)(@types/node@20.17.7(patch_hash=fe6957869a9b616ec526ededb72bbe846b863bd2349204218772df5c1e62d6ab))(node-addon-api@6.1.0) + version: 3.7.2(@emnapi/runtime@1.9.2)(@types/node@20.17.7(patch_hash=fe6957869a9b616ec526ededb72bbe846b863bd2349204218772df5c1e62d6ab))(node-addon-api@6.1.0) cross-env: specifier: 6.0.3 version: 6.0.3 @@ -1976,8 +1976,8 @@ importers: packages/third-parties: dependencies: react: - specifier: npm:react@19.3.0-canary-f789f203-20260825 - version: 19.3.0-canary-f789f203-20260825 + specifier: npm:react@19.3.0-canary-29d9d318-20260826 + version: 19.3.0-canary-29d9d318-20260826 third-party-capital: specifier: 1.0.20 version: 1.0.20 @@ -2040,14 +2040,14 @@ importers: specifier: 29.5.0 version: 29.5.0 react: - specifier: npm:react@19.3.0-canary-f789f203-20260825 - version: 19.3.0-canary-f789f203-20260825 + specifier: npm:react@19.3.0-canary-29d9d318-20260826 + version: 19.3.0-canary-29d9d318-20260826 react-test-renderer: specifier: 18.2.0 - version: 18.2.0(react@19.3.0-canary-f789f203-20260825) + version: 18.2.0(react@19.3.0-canary-29d9d318-20260826) styled-jsx: specifier: ^5.1.2 - version: 5.1.6(@babel/core@7.26.10)(babel-plugin-macros@3.1.0)(react@19.3.0-canary-f789f203-20260825) + version: 5.1.6(@babel/core@7.26.10)(babel-plugin-macros@3.1.0)(react@19.3.0-canary-29d9d318-20260826) packages: @@ -16026,18 +16026,18 @@ packages: resolution: {integrity: sha512-APPU8HB2uZnpl6Vt/+0AFoVYgSRtfiP6FLrZgPPTDmqSb2R4qZRbgd0A3VzIFxDt5e+Fozjx79WjLWnF69DK8g==} engines: {node: '>=16.14.0'} - react-dom@0.0.0-experimental-f789f203-20260825: - resolution: {integrity: sha512-5LD0y0cPHRWB6UB5G5yYwytQSmZbASPSAGheATBeax57QIiygY/6SkiofnsTdUfp3F+iaDtsfOqxKjcSExoq7g==} + react-dom@0.0.0-experimental-29d9d318-20260826: + resolution: {integrity: sha512-Bf/wSOf7EgGoBZbIatdvjOaRZkJLOvd6aA2Z6C99PID9RfjJcvbc58zSw8MPDmR3laeaBcz6G0hks0UY5fgphg==} peerDependencies: - react: 0.0.0-experimental-f789f203-20260825 + react: 0.0.0-experimental-29d9d318-20260826 - react-dom@19.3.0-canary-f789f203-20260825: - resolution: {integrity: sha512-JtisJchWIlp7UooeN9Ltf3gkvnBr7wcRfe8IqfJMRFDJ6D3aJotxfmYIqmKO84vBP9Xzk5zaQOlgncvQGex7/w==} + react-dom@19.3.0-canary-29d9d318-20260826: + resolution: {integrity: sha512-VpVza0fV7h5m/9PvH/pKBSCy6tc3rhC9gJ//zCqC6KuIurH9ZVjZcpPRZLWj9AlL46zU7KSZZbclG6jj+YF2jA==} peerDependencies: - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 - react-is@19.3.0-canary-f789f203-20260825: - resolution: {integrity: sha512-4cCfM6jUm8rnNS0ont+tY8mx0oF7XuVl5Rj6Sdz8dBMPgiNIlDPaiMiuilYKpIyKukZUU0OWXrxsXgNwA4ZGTg==} + react-is@19.3.0-canary-29d9d318-20260826: + resolution: {integrity: sha512-+7T79hubjHd0QmsGef2OKIit0nDOYCBOtv9OzGohIgXtR6ttpqPrCsOsoPVPmequqjL5bfIMwd+VFcEWX8sckA==} react-lifecycles-compat@3.0.4: resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==} @@ -16092,34 +16092,34 @@ packages: '@types/react': optional: true - react-server-dom-turbopack@0.0.0-experimental-f789f203-20260825: - resolution: {integrity: sha512-OTamdULfmh5fvYy9m3iCn6/vJFGyXSDbj/xzfC5USooTgU0HX5H89ShByE6Glo/ck87tgdWuJDS0NNWgNIV2dg==} + react-server-dom-turbopack@0.0.0-experimental-29d9d318-20260826: + resolution: {integrity: sha512-MEDCFSFaRKbpgNNy6MCLLUyYcyyD0LD6Q/diW2AIHnwgc9UMvEVuHvMgNisHLQOGz4/yivcFV6/lkYqJhrSKbg==} engines: {node: '>=0.10.0'} peerDependencies: - react: 0.0.0-experimental-f789f203-20260825 - react-dom: 0.0.0-experimental-f789f203-20260825 + react: 0.0.0-experimental-29d9d318-20260826 + react-dom: 0.0.0-experimental-29d9d318-20260826 - react-server-dom-turbopack@19.3.0-canary-f789f203-20260825: - resolution: {integrity: sha512-8+oNV4La5x5xygbXC8p4//yPOwq9n7gS9DRxB/cul6ctdjTLSHoayQjSo6dPiOTEd8/Q2sCZQ/JVlW647dZ/VA==} + react-server-dom-turbopack@19.3.0-canary-29d9d318-20260826: + resolution: {integrity: sha512-DWOkoNKV9yJJCTCJQ19zlBp5Yu4QuP4q7YqECgPDs6A/1mixyK6r7Et2jw+tTeorNDwAKwCY6vR0KkmgiVgUxg==} engines: {node: '>=0.10.0'} peerDependencies: - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826 - react-server-dom-webpack@0.0.0-experimental-f789f203-20260825: - resolution: {integrity: sha512-fUnKzl9d07JujxFGjCLeL+K6xtYM2JwApLFMum83dTaPBhXegnUy76Q7IYGSF1QBm0LLM08p/1YQFmcnsAvuVg==} + react-server-dom-webpack@0.0.0-experimental-29d9d318-20260826: + resolution: {integrity: sha512-fj7EWDApUHcFSmMOTeQAE5oNHAGog4ki8QZACj+sK5SJJm/all1EVKVkGNihL4ZLTdrRHXcUKQe+DHIhPmchYw==} engines: {node: '>=0.10.0'} peerDependencies: - react: 0.0.0-experimental-f789f203-20260825 - react-dom: 0.0.0-experimental-f789f203-20260825 + react: 0.0.0-experimental-29d9d318-20260826 + react-dom: 0.0.0-experimental-29d9d318-20260826 webpack: 5.98.0 - react-server-dom-webpack@19.3.0-canary-f789f203-20260825: - resolution: {integrity: sha512-xtd+0Vx47NElbiwRXKVHCuSY4MngplTAY4749smwTYcTXsPwSHF266ZvcwE3DdrguaE5CjfRs4wOKTNPV+PLIQ==} + react-server-dom-webpack@19.3.0-canary-29d9d318-20260826: + resolution: {integrity: sha512-aS1oYsgtHfEG9kR9Kcgeeu7zOPsBMhX9pcs4CebHyx1kZtFGM+Afb5cjJ7Uvg/+cLQTj2dWrRnb5TjpYyD2Tag==} engines: {node: '>=0.10.0'} peerDependencies: - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826 webpack: 5.98.0 react-shallow-renderer@16.15.0: @@ -16170,12 +16170,12 @@ packages: react: ^15.3.0 || ^16.0.0-alpha react-dom: ^15.3.0 || ^16.0.0-alpha - react@0.0.0-experimental-f789f203-20260825: - resolution: {integrity: sha512-s2FNlz9xYsWnXKTN4qwHjgT/eau/Mw6iseMCnZxxHepGmH6pD16OAxa2TGWUDv5/N6PU7uON6o6Alfeo7L5fqg==} + react@0.0.0-experimental-29d9d318-20260826: + resolution: {integrity: sha512-9XFx39foeYhOA3sYuTlcjN4Pv+pq3qgLlyGDaBGHqnro4PdeXRgMEoLR9Os8U8PGqH5zgUMlVvpNMb0ol0Iztw==} engines: {node: '>=0.10.0'} - react@19.3.0-canary-f789f203-20260825: - resolution: {integrity: sha512-b8dZ2V6iz/09DP9Ugpb2ESD+BUZOy4Nta6KN3tvH5tQTdJwphhTnkXyzWbZUJN36zcxVAXnqFmPGo1QLXPcYMQ==} + react@19.3.0-canary-29d9d318-20260826: + resolution: {integrity: sha512-ug9L441Vb9qtOlasxbhIAY/YXVpPG0esOQVbOaxRuHL8AK5PLmvpF7e1nKL8498VWnifeoyt47fQk2k7GnDKVQ==} engines: {node: '>=0.10.0'} read-all-stream@3.1.0: @@ -16707,11 +16707,11 @@ packages: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} - scheduler@0.0.0-experimental-f789f203-20260825: - resolution: {integrity: sha512-KLrwM5DfquWJXeRLE+wgeGvuv0QF9t4lwYtcAAw1Z/ijur7HSWVeuZrjijBBP+T5VC+THVe8IsY711KenQBQNg==} + scheduler@0.0.0-experimental-29d9d318-20260826: + resolution: {integrity: sha512-LkCn+aT5qCdGZ5oeq46EH4ZiYma8gGcZaVHddfdLx5v2Vy9IWIdQIHXypH/gWREJZE6UCGa9y39ebQ7tnwN6wA==} - scheduler@0.28.0-canary-f789f203-20260825: - resolution: {integrity: sha512-4otCFOc9yhVL3mBhHwvg2n2i1p2lWCWNdoyqI+BvzWBof0rDJ9mO7WE0iWw8+AscEd5JYnogVPDZ7MyT87LAFw==} + scheduler@0.28.0-canary-29d9d318-20260826: + resolution: {integrity: sha512-wszPzDIYcYKBuqaQvhLrUBCNao0BS7W4kGpbJX2pZdKhYU3Fgrf7OOzZ0A81FeGTOapeSvSZ6oFik+yNogxT6Q==} schema-utils@2.7.1: resolution: {integrity: sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==} @@ -20032,28 +20032,28 @@ snapshots: '@balena/dockerignore@1.0.2': {} - '@base-ui-components/react@1.0.0-beta.2(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': + '@base-ui-components/react@1.0.0-beta.2(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': dependencies: '@babel/runtime': 7.27.6 - '@base-ui-components/utils': 0.1.0(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@floating-ui/react-dom': 2.1.5(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) + '@base-ui-components/utils': 0.1.0(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@floating-ui/react-dom': 2.1.5(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) '@floating-ui/utils': 0.2.10 - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) reselect: 5.1.1 tabbable: 6.2.0 - use-sync-external-store: 1.5.0(react@19.3.0-canary-f789f203-20260825) + use-sync-external-store: 1.5.0(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 - '@base-ui-components/utils@0.1.0(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': + '@base-ui-components/utils@0.1.0(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': dependencies: '@babel/runtime': 7.27.6 '@floating-ui/utils': 0.2.10 - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) reselect: 5.1.1 - use-sync-external-store: 1.5.0(react@19.3.0-canary-f789f203-20260825) + use-sync-external-store: 1.5.0(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 @@ -20253,17 +20253,17 @@ snapshots: '@emotion/memoize@0.8.1': {} - '@emotion/react@11.11.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825)': + '@emotion/react@11.11.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826)': dependencies: '@babel/runtime': 7.27.0 '@emotion/babel-plugin': 11.11.0 '@emotion/cache': 11.11.0 '@emotion/serialize': 1.1.2 - '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@19.3.0-canary-f789f203-20260825) + '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@19.3.0-canary-29d9d318-20260826) '@emotion/utils': 1.2.1 '@emotion/weak-memoize': 0.3.1 hoist-non-react-statics: 3.3.2 - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 optionalDependencies: '@types/react': 19.2.18 transitivePeerDependencies: @@ -20281,9 +20281,9 @@ snapshots: '@emotion/unitless@0.8.1': {} - '@emotion/use-insertion-effect-with-fallbacks@1.0.1(react@19.3.0-canary-f789f203-20260825)': + '@emotion/use-insertion-effect-with-fallbacks@1.0.1(react@19.3.0-canary-29d9d318-20260826)': dependencies: - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 '@emotion/utils@1.2.1': {} @@ -20705,24 +20705,24 @@ snapshots: '@floating-ui/core': 1.7.3 '@floating-ui/utils': 0.2.10 - '@floating-ui/react-dom@2.1.0(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': + '@floating-ui/react-dom@2.1.0(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': dependencies: '@floating-ui/dom': 1.7.3 - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) - '@floating-ui/react-dom@2.1.5(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': + '@floating-ui/react-dom@2.1.5(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': dependencies: '@floating-ui/dom': 1.7.3 - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) - '@floating-ui/react@0.26.16(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': + '@floating-ui/react@0.26.16(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': dependencies: - '@floating-ui/react-dom': 2.1.0(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) + '@floating-ui/react-dom': 2.1.0(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) '@floating-ui/utils': 0.2.2 - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) tabbable: 6.2.0 '@floating-ui/utils@0.2.10': {} @@ -21733,23 +21733,23 @@ snapshots: dependencies: call-bind: 1.0.8 - '@mantine/core@7.10.1(@mantine/hooks@7.10.1(react@19.3.0-canary-f789f203-20260825))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': + '@mantine/core@7.10.1(@mantine/hooks@7.10.1(react@19.3.0-canary-29d9d318-20260826))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': dependencies: - '@floating-ui/react': 0.26.16(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@mantine/hooks': 7.10.1(react@19.3.0-canary-f789f203-20260825) + '@floating-ui/react': 0.26.16(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@mantine/hooks': 7.10.1(react@19.3.0-canary-29d9d318-20260826) clsx: 2.1.1 - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) - react-number-format: 5.4.0(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - react-remove-scroll: 2.5.10(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - react-textarea-autosize: 8.5.3(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) + react-number-format: 5.4.0(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + react-remove-scroll: 2.5.10(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + react-textarea-autosize: 8.5.3(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) type-fest: 4.18.3 transitivePeerDependencies: - '@types/react' - '@mantine/hooks@7.10.1(react@19.3.0-canary-f789f203-20260825)': + '@mantine/hooks@7.10.1(react@19.3.0-canary-29d9d318-20260826)': dependencies: - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 '@mapbox/node-pre-gyp@2.0.3(encoding@0.1.13)': dependencies: @@ -21794,17 +21794,17 @@ snapshots: transitivePeerDependencies: - supports-color - '@mdx-js/react@2.2.1(react@19.3.0-canary-f789f203-20260825)': + '@mdx-js/react@2.2.1(react@19.3.0-canary-29d9d318-20260826)': dependencies: '@types/mdx': 2.0.13 '@types/react': 19.2.18 - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 - '@mdx-js/react@3.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825)': + '@mdx-js/react@3.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826)': dependencies: '@types/mdx': 2.0.13 '@types/react': 19.2.18 - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 '@modelcontextprotocol/sdk@1.18.1(patch_hash=680fe4edb7abd1de29d08cdf217a22506e815a8cc1c2282201d5d47aa3e5da12)': dependencies: @@ -21870,7 +21870,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@napi-rs/cli@3.7.2(@emnapi/runtime@1.11.2)(@types/node@20.17.7(patch_hash=fe6957869a9b616ec526ededb72bbe846b863bd2349204218772df5c1e62d6ab))(node-addon-api@6.1.0)': + '@napi-rs/cli@3.7.2(@emnapi/runtime@1.9.2)(@types/node@20.17.7(patch_hash=fe6957869a9b616ec526ededb72bbe846b863bd2349204218772df5c1e62d6ab))(node-addon-api@6.1.0)': dependencies: '@inquirer/prompts': 8.5.2(@types/node@20.17.7(patch_hash=fe6957869a9b616ec526ededb72bbe846b863bd2349204218772df5c1e62d6ab)) '@napi-rs/cross-toolchain': 1.0.3 @@ -21885,7 +21885,7 @@ snapshots: semver: 7.8.5 typanion: 3.14.0 optionalDependencies: - '@emnapi/runtime': 1.11.2 + '@emnapi/runtime': 1.9.2 transitivePeerDependencies: - '@napi-rs/cross-toolchain-arm64-target-aarch64' - '@napi-rs/cross-toolchain-arm64-target-armv7' @@ -22869,589 +22869,589 @@ snapshots: '@radix-ui/primitive@1.1.3': {} - '@radix-ui/react-arrow@1.1.0(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-arrow@1.1.0(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': dependencies: - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-arrow@1.1.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-arrow@1.1.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': dependencies: - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-collection@1.1.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-collection@1.1.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': dependencies: - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-context': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-slot': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-context': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-slot': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-compose-refs@1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-compose-refs@1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826)': dependencies: - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 optionalDependencies: '@types/react': 19.2.18 - '@radix-ui/react-compose-refs@1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-compose-refs@1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826)': dependencies: - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 optionalDependencies: '@types/react': 19.2.18 - '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826)': dependencies: - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 optionalDependencies: '@types/react': 19.2.18 - '@radix-ui/react-context@1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-context@1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826)': dependencies: - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 optionalDependencies: '@types/react': 19.2.18 - '@radix-ui/react-context@1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-context@1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826)': dependencies: - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 optionalDependencies: '@types/react': 19.2.18 - '@radix-ui/react-context@1.1.2(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-context@1.1.2(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826)': dependencies: - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 optionalDependencies: '@types/react': 19.2.18 - '@radix-ui/react-dialog@1.1.4(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-dialog@1.1.4(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': dependencies: '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-context': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-dismissable-layer': 1.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-focus-guards': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-focus-scope': 1.1.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-id': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-portal': 1.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-presence': 1.1.2(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-slot': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-context': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-dismissable-layer': 1.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-focus-guards': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-focus-scope': 1.1.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-id': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-portal': 1.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-presence': 1.1.2(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-slot': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) aria-hidden: 1.2.6 - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) - react-remove-scroll: 2.7.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) + react-remove-scroll: 2.7.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-direction@1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-direction@1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826)': dependencies: - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 optionalDependencies: '@types/react': 19.2.18 - '@radix-ui/react-direction@1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-direction@1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826)': dependencies: - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 optionalDependencies: '@types/react': 19.2.18 - '@radix-ui/react-dismissable-layer@1.1.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-dismissable-layer@1.1.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': dependencies: '@radix-ui/primitive': 1.1.0 - '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-dismissable-layer@1.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-dismissable-layer@1.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': dependencies: '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-focus-guards@1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-focus-guards@1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826)': dependencies: - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 optionalDependencies: '@types/react': 19.2.18 - '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826)': dependencies: - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 optionalDependencies: '@types/react': 19.2.18 - '@radix-ui/react-focus-scope@1.1.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-focus-scope@1.1.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': dependencies: - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-id@1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-id@1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 optionalDependencies: '@types/react': 19.2.18 - '@radix-ui/react-id@1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-id@1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 optionalDependencies: '@types/react': 19.2.18 - '@radix-ui/react-popover@1.1.4(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-popover@1.1.4(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': dependencies: '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-context': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-dismissable-layer': 1.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-focus-guards': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-focus-scope': 1.1.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-id': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-popper': 1.2.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-portal': 1.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-presence': 1.1.2(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-slot': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-context': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-dismissable-layer': 1.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-focus-guards': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-focus-scope': 1.1.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-id': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-popper': 1.2.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-portal': 1.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-presence': 1.1.2(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-slot': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) aria-hidden: 1.2.6 - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) - react-remove-scroll: 2.7.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) + react-remove-scroll: 2.7.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-popper@1.2.0(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': - dependencies: - '@floating-ui/react-dom': 2.1.5(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-arrow': 1.1.0(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-context': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-rect': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-size': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) + '@radix-ui/react-popper@1.2.0(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': + dependencies: + '@floating-ui/react-dom': 2.1.5(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-arrow': 1.1.0(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-context': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-rect': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-size': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) '@radix-ui/rect': 1.1.0 - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-popper@1.2.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': - dependencies: - '@floating-ui/react-dom': 2.1.5(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-arrow': 1.1.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-context': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-rect': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-size': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) + '@radix-ui/react-popper@1.2.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': + dependencies: + '@floating-ui/react-dom': 2.1.5(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-arrow': 1.1.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-context': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-rect': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-size': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) '@radix-ui/rect': 1.1.0 - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': - dependencies: - '@floating-ui/react-dom': 2.1.5(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) + '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': + dependencies: + '@floating-ui/react-dom': 2.1.5(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) '@radix-ui/rect': 1.1.1 - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-portal@1.1.2(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-portal@1.1.2(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': dependencies: - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-portal@1.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-portal@1.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': dependencies: - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-presence@1.1.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-presence@1.1.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': dependencies: - '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-presence@1.1.2(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-presence@1.1.2(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': dependencies: - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-primitive@2.0.0(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-primitive@2.0.0(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': dependencies: - '@radix-ui/react-slot': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + '@radix-ui/react-slot': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-primitive@2.0.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-primitive@2.0.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': dependencies: - '@radix-ui/react-slot': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + '@radix-ui/react-slot': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': dependencies: - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-roving-focus@1.1.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-roving-focus@1.1.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': dependencies: '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-collection': 1.1.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-context': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-direction': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-id': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + '@radix-ui/react-collection': 1.1.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-context': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-direction': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-id': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-select@2.2.6(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-select@2.2.6(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': dependencies: '@radix-ui/number': 1.1.1 '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) aria-hidden: 1.2.6 - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) - react-remove-scroll: 2.7.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) + react-remove-scroll: 2.7.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-slot@1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-slot@1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826)': dependencies: - '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 optionalDependencies: '@types/react': 19.2.18 - '@radix-ui/react-slot@1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-slot@1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826)': dependencies: - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 optionalDependencies: '@types/react': 19.2.18 - '@radix-ui/react-slot@1.2.3(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-slot@1.2.3(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 optionalDependencies: '@types/react': 19.2.18 - '@radix-ui/react-toggle-group@1.1.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-toggle-group@1.1.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': dependencies: '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-context': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-direction': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-roving-focus': 1.1.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-toggle': 1.1.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + '@radix-ui/react-context': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-direction': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-roving-focus': 1.1.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-toggle': 1.1.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-toggle@1.1.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-toggle@1.1.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': dependencies: '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-tooltip@1.1.4(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-tooltip@1.1.4(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': dependencies: '@radix-ui/primitive': 1.1.0 - '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-context': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-id': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-popper': 1.2.0(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-portal': 1.1.2(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-presence': 1.1.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-slot': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-visually-hidden': 1.1.0(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-context': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-id': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-popper': 1.2.0(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-portal': 1.1.2(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-presence': 1.1.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-slot': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-visually-hidden': 1.1.0(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-use-callback-ref@1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-use-callback-ref@1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826)': dependencies: - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 optionalDependencies: '@types/react': 19.2.18 - '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826)': dependencies: - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 optionalDependencies: '@types/react': 19.2.18 - '@radix-ui/react-use-controllable-state@1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-use-controllable-state@1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826)': dependencies: - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 optionalDependencies: '@types/react': 19.2.18 - '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826)': dependencies: - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 optionalDependencies: '@types/react': 19.2.18 - '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 optionalDependencies: '@types/react': 19.2.18 - '@radix-ui/react-use-escape-keydown@1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-use-escape-keydown@1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826)': dependencies: - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 optionalDependencies: '@types/react': 19.2.18 - '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826)': dependencies: - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 optionalDependencies: '@types/react': 19.2.18 - '@radix-ui/react-use-layout-effect@1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-use-layout-effect@1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826)': dependencies: - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 optionalDependencies: '@types/react': 19.2.18 - '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826)': dependencies: - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 optionalDependencies: '@types/react': 19.2.18 - '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826)': dependencies: - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 optionalDependencies: '@types/react': 19.2.18 - '@radix-ui/react-use-rect@1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-use-rect@1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826)': dependencies: '@radix-ui/rect': 1.1.0 - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 optionalDependencies: '@types/react': 19.2.18 - '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826)': dependencies: '@radix-ui/rect': 1.1.1 - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 optionalDependencies: '@types/react': 19.2.18 - '@radix-ui/react-use-size@1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-use-size@1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 optionalDependencies: '@types/react': 19.2.18 - '@radix-ui/react-use-size@1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-use-size@1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 optionalDependencies: '@types/react': 19.2.18 - '@radix-ui/react-visually-hidden@1.1.0(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-visually-hidden@1.1.0(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': dependencies: - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 '@types/react-dom': 19.2.4(@types/react@19.2.18) @@ -23758,12 +23758,12 @@ snapshots: '@storybook/addon-docs@8.6.0(@types/react@19.2.18)(storybook@8.6.0(prettier@3.6.2))': dependencies: - '@mdx-js/react': 3.1.0(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@storybook/blocks': 8.6.0(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)(storybook@8.6.0(prettier@3.6.2)) + '@mdx-js/react': 3.1.0(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@storybook/blocks': 8.6.0(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)(storybook@8.6.0(prettier@3.6.2)) '@storybook/csf-plugin': 8.6.0(storybook@8.6.0(prettier@3.6.2)) - '@storybook/react-dom-shim': 8.6.0(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)(storybook@8.6.0(prettier@3.6.2)) - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + '@storybook/react-dom-shim': 8.6.0(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)(storybook@8.6.0(prettier@3.6.2)) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) storybook: 8.6.0(prettier@3.6.2) ts-dedent: 2.2.0 transitivePeerDependencies: @@ -23828,14 +23828,14 @@ snapshots: - '@swc/helpers' - webpack - '@storybook/blocks@8.6.0(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)(storybook@8.6.0(prettier@3.6.2))': + '@storybook/blocks@8.6.0(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)(storybook@8.6.0(prettier@3.6.2))': dependencies: - '@storybook/icons': 1.3.0(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) + '@storybook/icons': 1.3.0(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) storybook: 8.6.0(prettier@3.6.2) ts-dedent: 2.2.0 optionalDependencies: - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) '@storybook/builder-webpack5@8.6.0(@rspack/core@1.6.7(patch_hash=4cf28ea116b0e27c7c80b09035905f9d16a7b18d1f2b7d312fc80d42cd57068d)(@swc/helpers@0.5.23))(@swc/core@1.11.24(@swc/helpers@0.5.23))(esbuild@0.25.9)(storybook@8.6.0(prettier@3.6.2))(typescript@6.0.2)': dependencies: @@ -23914,10 +23914,10 @@ snapshots: '@storybook/global@5.0.0': {} - '@storybook/icons@1.3.0(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': + '@storybook/icons@1.3.0(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': dependencies: - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) '@storybook/instrumenter@8.6.0(storybook@8.6.0(prettier@3.6.2))': dependencies: @@ -23929,17 +23929,17 @@ snapshots: dependencies: storybook: 8.6.0(prettier@3.6.2) - '@storybook/preset-react-webpack@8.6.0(@storybook/test@8.6.0(storybook@8.6.0(prettier@3.6.2)))(@swc/core@1.11.24(@swc/helpers@0.5.23))(esbuild@0.25.9)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)(storybook@8.6.0(prettier@3.6.2))(typescript@6.0.2)': + '@storybook/preset-react-webpack@8.6.0(@storybook/test@8.6.0(storybook@8.6.0(prettier@3.6.2)))(@swc/core@1.11.24(@swc/helpers@0.5.23))(esbuild@0.25.9)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)(storybook@8.6.0(prettier@3.6.2))(typescript@6.0.2)': dependencies: '@storybook/core-webpack': 8.6.0(storybook@8.6.0(prettier@3.6.2)) - '@storybook/react': 8.6.0(@storybook/test@8.6.0(storybook@8.6.0(prettier@3.6.2)))(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)(storybook@8.6.0(prettier@3.6.2))(typescript@6.0.2) + '@storybook/react': 8.6.0(@storybook/test@8.6.0(storybook@8.6.0(prettier@3.6.2)))(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)(storybook@8.6.0(prettier@3.6.2))(typescript@6.0.2) '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@6.0.2)(webpack@5.98.0(@swc/core@1.11.24(@swc/helpers@0.5.23))(esbuild@0.25.9)) '@types/semver': 7.5.6 find-up: 5.0.0 magic-string: 0.30.19 - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 react-docgen: 7.1.0 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) resolve: 1.22.10 semver: 7.6.3 storybook: 8.6.0(prettier@3.6.2) @@ -23973,19 +23973,19 @@ snapshots: transitivePeerDependencies: - supports-color - '@storybook/react-dom-shim@8.6.0(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)(storybook@8.6.0(prettier@3.6.2))': + '@storybook/react-dom-shim@8.6.0(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)(storybook@8.6.0(prettier@3.6.2))': dependencies: - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) storybook: 8.6.0(prettier@3.6.2) - '@storybook/react-webpack5@8.6.0(@rspack/core@1.6.7(patch_hash=4cf28ea116b0e27c7c80b09035905f9d16a7b18d1f2b7d312fc80d42cd57068d)(@swc/helpers@0.5.23))(@storybook/test@8.6.0(storybook@8.6.0(prettier@3.6.2)))(@swc/core@1.11.24(@swc/helpers@0.5.23))(esbuild@0.25.9)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)(storybook@8.6.0(prettier@3.6.2))(typescript@6.0.2)': + '@storybook/react-webpack5@8.6.0(@rspack/core@1.6.7(patch_hash=4cf28ea116b0e27c7c80b09035905f9d16a7b18d1f2b7d312fc80d42cd57068d)(@swc/helpers@0.5.23))(@storybook/test@8.6.0(storybook@8.6.0(prettier@3.6.2)))(@swc/core@1.11.24(@swc/helpers@0.5.23))(esbuild@0.25.9)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)(storybook@8.6.0(prettier@3.6.2))(typescript@6.0.2)': dependencies: '@storybook/builder-webpack5': 8.6.0(@rspack/core@1.6.7(patch_hash=4cf28ea116b0e27c7c80b09035905f9d16a7b18d1f2b7d312fc80d42cd57068d)(@swc/helpers@0.5.23))(@swc/core@1.11.24(@swc/helpers@0.5.23))(esbuild@0.25.9)(storybook@8.6.0(prettier@3.6.2))(typescript@6.0.2) - '@storybook/preset-react-webpack': 8.6.0(@storybook/test@8.6.0(storybook@8.6.0(prettier@3.6.2)))(@swc/core@1.11.24(@swc/helpers@0.5.23))(esbuild@0.25.9)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)(storybook@8.6.0(prettier@3.6.2))(typescript@6.0.2) - '@storybook/react': 8.6.0(@storybook/test@8.6.0(storybook@8.6.0(prettier@3.6.2)))(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)(storybook@8.6.0(prettier@3.6.2))(typescript@6.0.2) - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + '@storybook/preset-react-webpack': 8.6.0(@storybook/test@8.6.0(storybook@8.6.0(prettier@3.6.2)))(@swc/core@1.11.24(@swc/helpers@0.5.23))(esbuild@0.25.9)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)(storybook@8.6.0(prettier@3.6.2))(typescript@6.0.2) + '@storybook/react': 8.6.0(@storybook/test@8.6.0(storybook@8.6.0(prettier@3.6.2)))(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)(storybook@8.6.0(prettier@3.6.2))(typescript@6.0.2) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) storybook: 8.6.0(prettier@3.6.2) optionalDependencies: typescript: 6.0.2 @@ -23998,16 +23998,16 @@ snapshots: - uglify-js - webpack-cli - '@storybook/react@8.6.0(@storybook/test@8.6.0(storybook@8.6.0(prettier@3.6.2)))(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)(storybook@8.6.0(prettier@3.6.2))(typescript@6.0.2)': + '@storybook/react@8.6.0(@storybook/test@8.6.0(storybook@8.6.0(prettier@3.6.2)))(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)(storybook@8.6.0(prettier@3.6.2))(typescript@6.0.2)': dependencies: '@storybook/components': 8.6.0(storybook@8.6.0(prettier@3.6.2)) '@storybook/global': 5.0.0 '@storybook/manager-api': 8.6.0(storybook@8.6.0(prettier@3.6.2)) '@storybook/preview-api': 8.6.0(storybook@8.6.0(prettier@3.6.2)) - '@storybook/react-dom-shim': 8.6.0(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)(storybook@8.6.0(prettier@3.6.2)) + '@storybook/react-dom-shim': 8.6.0(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)(storybook@8.6.0(prettier@3.6.2)) '@storybook/theming': 8.6.0(storybook@8.6.0(prettier@3.6.2)) - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) storybook: 8.6.0(prettier@3.6.2) optionalDependencies: '@storybook/test': 8.6.0(storybook@8.6.0(prettier@3.6.2)) @@ -24289,13 +24289,13 @@ snapshots: lodash: 4.18.1 redent: 3.0.0 - '@testing-library/react@15.0.7(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)': + '@testing-library/react@15.0.7(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)': dependencies: '@babel/runtime': 7.27.0 '@testing-library/dom': 10.1.0 '@types/react-dom': 19.2.4(@types/react@19.2.18) - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 @@ -26586,14 +26586,14 @@ snapshots: cmd-shim@7.0.0: {} - cmdk@1.0.4(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825): + cmdk@1.0.4(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826): dependencies: - '@radix-ui/react-dialog': 1.1.4(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825) - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) - use-sync-external-store: 1.5.0(react@19.3.0-canary-f789f203-20260825) + '@radix-ui/react-dialog': 1.1.4(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) + use-sync-external-store: 1.5.0(react@19.3.0-canary-29d9d318-20260826) transitivePeerDependencies: - '@types/react' - '@types/react-dom' @@ -30030,7 +30030,7 @@ snapshots: hoist-non-react-statics@3.3.2: dependencies: - react-is: 19.3.0-canary-f789f203-20260825 + react-is: 19.3.0-canary-29d9d318-20260826 homedir-polyfill@1.0.3: dependencies: @@ -32248,13 +32248,13 @@ snapshots: lru-cache@7.18.3: {} - lucide-react@0.383.0(react@19.3.0-canary-f789f203-20260825): + lucide-react@0.383.0(react@19.3.0-canary-29d9d318-20260826): dependencies: - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 - lucide-react@0.554.0(react@19.3.0-canary-f789f203-20260825): + lucide-react@0.554.0(react@19.3.0-canary-29d9d318-20260826): dependencies: - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 lz-string@1.5.0: {} @@ -33462,23 +33462,23 @@ snapshots: dependencies: inherits: 2.0.4 - next-themes@0.4.6(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825): + next-themes@0.4.6(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826): dependencies: - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) next-tick@1.0.0: {} - next@16.2.3(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@0.0.0-experimental-1371fcb-20260227)(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)(sass@1.77.8): + next@16.2.3(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(@playwright/test@1.61.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@0.0.0-experimental-1371fcb-20260227)(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)(sass@1.77.8): dependencies: '@next/env': 16.2.3 '@swc/helpers': 0.5.15 baseline-browser-mapping: 2.9.19 caniuse-lite: 1.0.30001746 postcss: 8.5.23 - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) - styled-jsx: 5.1.6(@babel/core@7.26.10)(babel-plugin-macros@3.1.0)(react@19.3.0-canary-f789f203-20260825) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) + styled-jsx: 5.1.6(@babel/core@7.26.10)(babel-plugin-macros@3.1.0)(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@next/swc-darwin-arm64': 16.2.3 '@next/swc-darwin-x64': 16.2.3 @@ -35181,31 +35181,31 @@ snapshots: dependencies: ansi-regex: 5.0.1 ansi-styles: 5.2.0 - react-is: 19.3.0-canary-f789f203-20260825 + react-is: 19.3.0-canary-29d9d318-20260826 pretty-format@29.5.0: dependencies: '@jest/schemas': 29.4.3 ansi-styles: 5.2.0 - react-is: 19.3.0-canary-f789f203-20260825 + react-is: 19.3.0-canary-29d9d318-20260826 pretty-format@29.7.0: dependencies: '@jest/schemas': 29.6.3 ansi-styles: 5.2.0 - react-is: 19.3.0-canary-f789f203-20260825 + react-is: 19.3.0-canary-29d9d318-20260826 pretty-format@30.0.0-alpha.6: dependencies: '@jest/schemas': 30.0.0-alpha.6 ansi-styles: 5.2.0 - react-is: 19.3.0-canary-f789f203-20260825 + react-is: 19.3.0-canary-29d9d318-20260826 pretty-format@30.2.0: dependencies: '@jest/schemas': 30.0.5 ansi-styles: 5.2.0 - react-is: 19.3.0-canary-f789f203-20260825 + react-is: 19.3.0-canary-29d9d318-20260826 pretty-ms@7.0.0: dependencies: @@ -35267,7 +35267,7 @@ snapshots: dependencies: loose-envify: 1.4.0 object-assign: 4.1.1 - react-is: 19.3.0-canary-f789f203-20260825 + react-is: 19.3.0-canary-29d9d318-20260826 property-information@5.6.0: dependencies: @@ -35490,157 +35490,157 @@ snapshots: transitivePeerDependencies: - supports-color - react-dom@0.0.0-experimental-f789f203-20260825(react@19.3.0-canary-f789f203-20260825): + react-dom@0.0.0-experimental-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826): dependencies: - react: 19.3.0-canary-f789f203-20260825 - scheduler: 0.28.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 + scheduler: 0.28.0-canary-29d9d318-20260826 - react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825): + react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826): dependencies: - react: 19.3.0-canary-f789f203-20260825 - scheduler: 0.28.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 + scheduler: 0.28.0-canary-29d9d318-20260826 - react-is@19.3.0-canary-f789f203-20260825: {} + react-is@19.3.0-canary-29d9d318-20260826: {} react-lifecycles-compat@3.0.4: {} - react-number-format@5.4.0(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825): + react-number-format@5.4.0(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826): dependencies: prop-types: 15.8.1 - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) react-refresh@0.12.0: {} - react-remove-scroll-bar@2.3.6(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825): + react-remove-scroll-bar@2.3.6(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826): dependencies: - react: 19.3.0-canary-f789f203-20260825 - react-style-singleton: 2.2.3(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) + react: 19.3.0-canary-29d9d318-20260826 + react-style-singleton: 2.2.3(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) tslib: 2.8.1 optionalDependencies: '@types/react': 19.2.18 - react-remove-scroll-bar@2.3.8(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825): + react-remove-scroll-bar@2.3.8(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826): dependencies: - react: 19.3.0-canary-f789f203-20260825 - react-style-singleton: 2.2.3(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) + react: 19.3.0-canary-29d9d318-20260826 + react-style-singleton: 2.2.3(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) tslib: 2.8.1 optionalDependencies: '@types/react': 19.2.18 - react-remove-scroll@2.5.10(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825): + react-remove-scroll@2.5.10(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826): dependencies: - react: 19.3.0-canary-f789f203-20260825 - react-remove-scroll-bar: 2.3.6(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - react-style-singleton: 2.2.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) + react: 19.3.0-canary-29d9d318-20260826 + react-remove-scroll-bar: 2.3.6(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + react-style-singleton: 2.2.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) tslib: 2.8.1 - use-callback-ref: 1.3.2(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - use-sidecar: 1.1.2(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) + use-callback-ref: 1.3.2(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + use-sidecar: 1.1.2(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 - react-remove-scroll@2.7.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825): + react-remove-scroll@2.7.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826): dependencies: - react: 19.3.0-canary-f789f203-20260825 - react-remove-scroll-bar: 2.3.8(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - react-style-singleton: 2.2.3(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) + react: 19.3.0-canary-29d9d318-20260826 + react-remove-scroll-bar: 2.3.8(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + react-style-singleton: 2.2.3(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) tslib: 2.8.1 - use-callback-ref: 1.3.3(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) - use-sidecar: 1.1.3(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) + use-callback-ref: 1.3.3(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) + use-sidecar: 1.1.3(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 - react-server-dom-turbopack@0.0.0-experimental-f789f203-20260825(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825): + react-server-dom-turbopack@0.0.0-experimental-29d9d318-20260826(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826): dependencies: acorn-loose: 8.3.0 neo-async: 2.6.1 - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) - react-server-dom-turbopack@19.3.0-canary-f789f203-20260825(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825): + react-server-dom-turbopack@19.3.0-canary-29d9d318-20260826(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826): dependencies: acorn-loose: 8.3.0 neo-async: 2.6.1 - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) - react-server-dom-webpack@0.0.0-experimental-f789f203-20260825(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)(webpack@5.98.0(@swc/core@1.11.24(@swc/helpers@0.5.23))): + react-server-dom-webpack@0.0.0-experimental-29d9d318-20260826(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)(webpack@5.98.0(@swc/core@1.11.24(@swc/helpers@0.5.23))): dependencies: acorn-loose: 8.3.0 neo-async: 2.6.1 - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) webpack: 5.98.0(@swc/core@1.11.24(@swc/helpers@0.5.23)) webpack-sources: 3.2.3(patch_hash=26afc15966a3fc37a3d1d366312a95ce66723513f6a3a720e4166a37147da8bd) - react-server-dom-webpack@19.3.0-canary-f789f203-20260825(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825)(webpack@5.98.0(@swc/core@1.11.24(@swc/helpers@0.5.23))): + react-server-dom-webpack@19.3.0-canary-29d9d318-20260826(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826)(webpack@5.98.0(@swc/core@1.11.24(@swc/helpers@0.5.23))): dependencies: acorn-loose: 8.3.0 neo-async: 2.6.1 - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) webpack: 5.98.0(@swc/core@1.11.24(@swc/helpers@0.5.23)) webpack-sources: 3.2.3(patch_hash=26afc15966a3fc37a3d1d366312a95ce66723513f6a3a720e4166a37147da8bd) - react-shallow-renderer@16.15.0(react@19.3.0-canary-f789f203-20260825): + react-shallow-renderer@16.15.0(react@19.3.0-canary-29d9d318-20260826): dependencies: object-assign: 4.1.1 - react: 19.3.0-canary-f789f203-20260825 - react-is: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 + react-is: 19.3.0-canary-29d9d318-20260826 - react-ssr-prepass@1.0.8(react-is@19.3.0-canary-f789f203-20260825)(react@19.3.0-canary-f789f203-20260825): + react-ssr-prepass@1.0.8(react-is@19.3.0-canary-29d9d318-20260826)(react@19.3.0-canary-29d9d318-20260826): dependencies: object-is: 1.0.2 - react: 19.3.0-canary-f789f203-20260825 - react-is: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 + react-is: 19.3.0-canary-29d9d318-20260826 - react-style-singleton@2.2.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825): + react-style-singleton@2.2.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826): dependencies: get-nonce: 1.0.1 invariant: 2.2.4 - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 tslib: 2.8.1 optionalDependencies: '@types/react': 19.2.18 - react-style-singleton@2.2.3(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825): + react-style-singleton@2.2.3(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826): dependencies: get-nonce: 1.0.1 - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 tslib: 2.8.1 optionalDependencies: '@types/react': 19.2.18 - react-test-renderer@18.2.0(react@19.3.0-canary-f789f203-20260825): + react-test-renderer@18.2.0(react@19.3.0-canary-29d9d318-20260826): dependencies: - react: 19.3.0-canary-f789f203-20260825 - react-is: 19.3.0-canary-f789f203-20260825 - react-shallow-renderer: 16.15.0(react@19.3.0-canary-f789f203-20260825) - scheduler: 0.28.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 + react-is: 19.3.0-canary-29d9d318-20260826 + react-shallow-renderer: 16.15.0(react@19.3.0-canary-29d9d318-20260826) + scheduler: 0.28.0-canary-29d9d318-20260826 - react-textarea-autosize@8.5.3(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825): + react-textarea-autosize@8.5.3(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826): dependencies: '@babel/runtime': 7.27.0 - react: 19.3.0-canary-f789f203-20260825 - use-composed-ref: 1.3.0(react@19.3.0-canary-f789f203-20260825) - use-latest: 1.2.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) + react: 19.3.0-canary-29d9d318-20260826 + use-composed-ref: 1.3.0(react@19.3.0-canary-29d9d318-20260826) + use-latest: 1.2.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) transitivePeerDependencies: - '@types/react' - react-virtualized@9.22.3(react-dom@19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825))(react@19.3.0-canary-f789f203-20260825): + react-virtualized@9.22.3(react-dom@19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826))(react@19.3.0-canary-29d9d318-20260826): dependencies: '@babel/runtime': 7.27.0 clsx: 1.1.1 dom-helpers: 5.2.1 loose-envify: 1.4.0 prop-types: 15.8.1 - react: 19.3.0-canary-f789f203-20260825 - react-dom: 19.3.0-canary-f789f203-20260825(react@19.3.0-canary-f789f203-20260825) + react: 19.3.0-canary-29d9d318-20260826 + react-dom: 19.3.0-canary-29d9d318-20260826(react@19.3.0-canary-29d9d318-20260826) react-lifecycles-compat: 3.0.4 - react@0.0.0-experimental-f789f203-20260825: {} + react@0.0.0-experimental-29d9d318-20260826: {} - react@19.3.0-canary-f789f203-20260825: {} + react@19.3.0-canary-29d9d318-20260826: {} read-all-stream@3.1.0: dependencies: @@ -36331,9 +36331,9 @@ snapshots: dependencies: xmlchars: 2.2.0 - scheduler@0.0.0-experimental-f789f203-20260825: {} + scheduler@0.0.0-experimental-29d9d318-20260826: {} - scheduler@0.28.0-canary-f789f203-20260825: {} + scheduler@0.28.0-canary-29d9d318-20260826: {} schema-utils@2.7.1: dependencies: @@ -37213,10 +37213,10 @@ snapshots: postcss: 8.5.23 postcss-load-plugins: 2.3.0 - styled-jsx@5.1.6(@babel/core@7.26.10)(babel-plugin-macros@3.1.0)(react@19.3.0-canary-f789f203-20260825): + styled-jsx@5.1.6(@babel/core@7.26.10)(babel-plugin-macros@3.1.0)(react@19.3.0-canary-29d9d318-20260826): dependencies: client-only: 0.0.1 - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 optionalDependencies: '@babel/core': 7.26.10 babel-plugin-macros: 3.1.0 @@ -37316,11 +37316,11 @@ snapshots: '@swc/counter': 0.1.3 webpack: 5.98.0(@swc/core@1.11.24(@swc/helpers@0.5.23))(esbuild@0.25.9) - swr@2.2.4(react@19.3.0-canary-f789f203-20260825): + swr@2.2.4(react@19.3.0-canary-29d9d318-20260826): dependencies: client-only: 0.0.1 - react: 19.3.0-canary-f789f203-20260825 - use-sync-external-store: 1.5.0(react@19.3.0-canary-f789f203-20260825) + react: 19.3.0-canary-29d9d318-20260826 + use-sync-external-store: 1.5.0(react@19.3.0-canary-29d9d318-20260826) symbol-tree@3.2.4: {} @@ -38153,9 +38153,9 @@ snapshots: unist-util-is: 6.0.0 unist-util-visit-parents: 6.0.1 - unistore@3.4.1(react@19.3.0-canary-f789f203-20260825): + unistore@3.4.1(react@19.3.0-canary-29d9d318-20260826): optionalDependencies: - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 universal-github-app-jwt@1.1.1: dependencies: @@ -38272,56 +38272,56 @@ snapshots: punycode: 1.4.1 qs: 6.14.0 - use-callback-ref@1.3.2(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825): + use-callback-ref@1.3.2(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826): dependencies: - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 tslib: 2.8.1 optionalDependencies: '@types/react': 19.2.18 - use-callback-ref@1.3.3(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825): + use-callback-ref@1.3.3(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826): dependencies: - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 tslib: 2.8.1 optionalDependencies: '@types/react': 19.2.18 - use-composed-ref@1.3.0(react@19.3.0-canary-f789f203-20260825): + use-composed-ref@1.3.0(react@19.3.0-canary-29d9d318-20260826): dependencies: - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 - use-isomorphic-layout-effect@1.1.2(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825): + use-isomorphic-layout-effect@1.1.2(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826): dependencies: - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 optionalDependencies: '@types/react': 19.2.18 - use-latest@1.2.1(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825): + use-latest@1.2.1(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826): dependencies: - react: 19.3.0-canary-f789f203-20260825 - use-isomorphic-layout-effect: 1.1.2(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825) + react: 19.3.0-canary-29d9d318-20260826 + use-isomorphic-layout-effect: 1.1.2(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826) optionalDependencies: '@types/react': 19.2.18 - use-sidecar@1.1.2(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825): + use-sidecar@1.1.2(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826): dependencies: detect-node-es: 1.1.0 - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 tslib: 2.8.1 optionalDependencies: '@types/react': 19.2.18 - use-sidecar@1.1.3(@types/react@19.2.18)(react@19.3.0-canary-f789f203-20260825): + use-sidecar@1.1.3(@types/react@19.2.18)(react@19.3.0-canary-29d9d318-20260826): dependencies: detect-node-es: 1.1.0 - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 tslib: 2.8.1 optionalDependencies: '@types/react': 19.2.18 - use-sync-external-store@1.5.0(react@19.3.0-canary-f789f203-20260825): + use-sync-external-store@1.5.0(react@19.3.0-canary-29d9d318-20260826): dependencies: - react: 19.3.0-canary-f789f203-20260825 + react: 19.3.0-canary-29d9d318-20260826 util-deprecate@1.0.2: {} From 6d6228c0c6513718a517965c55d2d444f14ac842 Mon Sep 17 00:00:00 2001 From: Josh Story Date: Thu, 27 Aug 2026 13:23:43 -0700 Subject: [PATCH 3/9] Prune incomplete parallel route matchers (#97108) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary This adds an experimental `strictRouteMatching` flag that leaves a matcher out when its finalized loader tree contains a synthesized default that will always call `notFound()` for a slot actually declared by the owning layout. Adding a `default.tsx` keeps the matcher, and structural router-state branches that are not real slots of that layout do not make it incomplete. The flag defaults to `false` so matcher pruning can roll out independently from the preceding loader-tree correction. The main goal is to make `children` and named slots behave the same way. If a URL cannot construct a complete route tree on its own, it should be treated as an unmatched URL instead of sometimes showing a slot 404 and sometimes producing a missing page or default error. ## Semantics For example: ```text app/split-matcher/ ├── layout.tsx ├── foo/page.tsx ├── bar/page.tsx └── @slot/[...parts]/page.tsx ``` With strict route matching disabled, Next.js emits matchers for `/split-matcher/foo`, `/split-matcher/bar`, and the broad `/split-matcher/[...parts]`. The broad matcher can match any URL, but its `children` branch has neither a matching page nor a default, so it can only construct a permanent 404 tree. With strict route matching enabled, that broad matcher is omitted while `/foo` and `/bar` remain because each combines a real `children` page with the named-slot catchall. Adding `app/split-matcher/default.tsx` deliberately makes the broad matcher complete and keeps it. The same rule applies without a catchall: ```text app/disagreeing-slots/ ├── layout.tsx ├── @first/foo/page.tsx └── @second/bar/page.tsx ``` `/foo` is incomplete because `@second` has no matching page or default, and `/bar` is incomplete for the corresponding reason in `@first`, so neither matcher is emitted. A later PR in this stack reports the now-unreachable page files as a project misconfiguration. A declared `children` route is treated like any named slot: ```text app/declared-children/ ├── layout.tsx ├── page.tsx └── @panel/ ├── default.tsx └── details/page.tsx ``` `/declared-children` is complete because `children` uses `page.tsx` and `@panel` uses its default. `/declared-children/details` is incomplete because `@panel` matches its page while the declared `children` slot has no matching page or default at `/details`, so that matcher is pruned. A route composed entirely from declared named slots is also complete: ```text app/named-only/ ├── layout.tsx ├── @left/[...slug]/page.tsx └── @right/[...slug]/page.tsx ``` The preceding PR's default-on `explicitParallelRouteChildren` behavior means this loader tree contains only `left` and `right`. Strict matching keeps `/named-only/[...slug]` because both declared slots match; it does not invent `children` and then prune the route for failing to satisfy that nonexistent slot. We got here incrementally. [#47872](https://github.com/vercel/next.js/pull/47872) introduced the 404 fallback for unmatched parallel slots, [#60186](https://github.com/vercel/next.js/pull/60186) added a development warning because this was confusing in practice and linked [#51805](https://github.com/vercel/next.js/issues/51805) and [#49569](https://github.com/vercel/next.js/issues/49569), and [#84702](https://github.com/vercel/next.js/pull/84702) later made a missing default a build error for named slots while leaving `children` on the old fallback for backwards compatibility. Strict matching takes the next step and treats a matcher that can only construct a permanent 404 tree as unmatched. This changes soft navigations that only worked by preserving a previously active slot even though the URL could not be loaded directly, which is why matcher pruning remains behind its own experimental flag. The interception retention markers backed by `default-null`, including named host slots from the first PR in this stack, are complete route patches and are not pruned. This change however is well motivated because if you did client nav to a route that only matches a named slot and then hard refresh you will end up getting a 404. This is a sign our current semantics are actually broken. If you want to preserve the perma 404 behavior of the slot-without-default you can just add a default and make it call `notFound()` unconditionally. ## Verification - `pnpm build-all` - Focused `normalize-catchall-routes` unit coverage - Production and development e2e coverage on Turbopack and Webpack - The pruning e2e matrix with Cache Components enabled --- crates/next-api/src/app.rs | 1 + crates/next-core/src/app_structure.rs | 221 ++++++++++++-- crates/next-core/src/next_config.rs | 8 + packages/next/src/build/build-context.ts | 1 + packages/next/src/build/entries.ts | 39 ++- packages/next/src/build/index.ts | 13 + .../build/normalize-catchall-routes.test.ts | 288 ++++++++++++++++++ .../src/build/normalize-catchall-routes.ts | 12 +- packages/next/src/build/route-discovery.ts | 18 +- packages/next/src/build/webpack-build/impl.ts | 2 + .../webpack/loaders/next-app-loader/index.ts | 44 ++- packages/next/src/server/config-schema.ts | 1 + packages/next/src/server/config-shared.ts | 9 + packages/next/src/server/config.test.ts | 30 ++ packages/next/src/server/config.ts | 7 + .../src/server/dev/hot-reloader-webpack.ts | 19 ++ .../router-utils/normalize-catchall-routes.ts | 195 +++++++++++- .../lib/router-utils/setup-dev-bundler.ts | 11 +- .../named-catchall-target/[...slug]/page.tsx | 3 + .../app/named-host/@canonical/page.tsx | 3 + .../app/named-host/@content/page.tsx | 1 + .../[...slug]/page.tsx | 5 + .../app/named-host/layout.tsx | 3 + .../app/regular-route/default.tsx | 8 + .../interception-dynamic-segment.test.ts | 54 ++++ .../next.config.js | 4 + .../@content/dashboard/page.tsx | 3 + .../app/interception-host/@content/page.tsx | 3 + .../@modal/(.)dashboard/page.tsx | 3 + .../app/interception-host/@modal/default.tsx | 3 + .../app/interception-host/dashboard/page.tsx | 3 + .../app/interception-host/layout.tsx | 17 ++ .../app/interception-host/page.tsx | 3 + .../fixtures/no-build-error/next.config.js | 11 + ...outes-leaf-segments.no-build-error.test.ts | 13 + .../grouped/@slot/specific/page.tsx | 3 + .../grouped/[...slug]/page.tsx | 3 + .../app/(pruning-group)/grouped/layout.tsx | 21 ++ .../(pruning-group)/grouped/specific/page.tsx | 3 + .../app/children-catchall/@first/foo/page.tsx | 3 + .../children-catchall/@second/bar/page.tsx | 3 + .../app/children-catchall/[...slug]/page.tsx | 3 + .../app/children-catchall/layout.tsx | 25 ++ .../children-default/@slot/[...slug]/page.tsx | 3 + .../app/children-default/default.tsx | 3 + .../app/children-default/layout.tsx | 20 ++ .../app/client-navigation.tsx | 17 ++ .../@slot/[...slug]/page.tsx | 3 + .../app/complete-catchalls/[...slug]/page.tsx | 3 + .../app/complete-catchalls/layout.tsx | 20 ++ .../app/default-not-found/@slot/default.tsx | 5 + .../app/default-not-found/[...slug]/page.tsx | 3 + .../app/default-not-found/layout.tsx | 21 ++ .../app/layout.tsx | 17 ++ .../@catchall/[...slug]/page.tsx | 3 + .../app/named-catchall/@specific/foo/page.tsx | 3 + .../app/named-catchall/layout.tsx | 22 ++ .../@left/[...slug]/page.tsx | 3 + .../@right/[...slug]/page.tsx | 3 + .../app/named-only-catchalls/layout.tsx | 21 ++ .../@outer/@inner/specific/page.tsx | 3 + .../nested-parallel/@outer/[...slug]/page.tsx | 3 + .../app/nested-parallel/@outer/layout.tsx | 20 ++ .../nested-parallel/@outer/specific/page.tsx | 3 + .../app/nested-parallel/[...slug]/page.tsx | 3 + .../app/nested-parallel/layout.tsx | 21 ++ .../app/nested-parallel/specific/page.tsx | 3 + .../app/not-found.tsx | 3 + .../@slot/specific/page.tsx | 3 + .../[[...slug]]/page.tsx | 3 + .../app/optional-children-catchall/layout.tsx | 19 ++ .../specific/page.tsx | 3 + .../app/page.tsx | 22 ++ .../split-matcher/@slot/[...parts]/page.tsx | 3 + .../app/split-matcher/bar/page.tsx | 3 + .../app/split-matcher/foo/page.tsx | 3 + .../app/split-matcher/layout.tsx | 21 ++ .../app/valid/@slot/default.tsx | 3 + .../app/valid/@slot/special/page.tsx | 3 + .../app/valid/[...slug]/page.tsx | 3 + .../app/valid/layout.tsx | 21 ++ .../next.config.js | 10 + .../parallel-routes-pruned-matchers.test.ts | 225 ++++++++++++++ 83 files changed, 1654 insertions(+), 43 deletions(-) create mode 100644 test/e2e/app-dir/interception-dynamic-segment/app/named-host/@canonical/named-catchall-target/[...slug]/page.tsx create mode 100644 test/e2e/app-dir/interception-dynamic-segment/app/named-host/@canonical/page.tsx create mode 100644 test/e2e/app-dir/interception-dynamic-segment/app/named-host/@modal/(.)named-catchall-target/[...slug]/page.tsx create mode 100644 test/e2e/app-dir/interception-dynamic-segment/app/regular-route/default.tsx create mode 100644 test/e2e/app-dir/parallel-routes-leaf-segments/fixtures/no-build-error/app/interception-host/@content/dashboard/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-leaf-segments/fixtures/no-build-error/app/interception-host/@content/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-leaf-segments/fixtures/no-build-error/app/interception-host/@modal/(.)dashboard/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-leaf-segments/fixtures/no-build-error/app/interception-host/@modal/default.tsx create mode 100644 test/e2e/app-dir/parallel-routes-leaf-segments/fixtures/no-build-error/app/interception-host/dashboard/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-leaf-segments/fixtures/no-build-error/app/interception-host/layout.tsx create mode 100644 test/e2e/app-dir/parallel-routes-leaf-segments/fixtures/no-build-error/app/interception-host/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-leaf-segments/fixtures/no-build-error/next.config.js create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/(pruning-group)/grouped/@slot/specific/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/(pruning-group)/grouped/[...slug]/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/(pruning-group)/grouped/layout.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/(pruning-group)/grouped/specific/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/children-catchall/@first/foo/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/children-catchall/@second/bar/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/children-catchall/[...slug]/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/children-catchall/layout.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/children-default/@slot/[...slug]/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/children-default/default.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/children-default/layout.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/client-navigation.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/complete-catchalls/@slot/[...slug]/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/complete-catchalls/[...slug]/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/complete-catchalls/layout.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/default-not-found/@slot/default.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/default-not-found/[...slug]/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/default-not-found/layout.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/layout.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/named-catchall/@catchall/[...slug]/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/named-catchall/@specific/foo/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/named-catchall/layout.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/named-only-catchalls/@left/[...slug]/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/named-only-catchalls/@right/[...slug]/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/named-only-catchalls/layout.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/nested-parallel/@outer/@inner/specific/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/nested-parallel/@outer/[...slug]/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/nested-parallel/@outer/layout.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/nested-parallel/@outer/specific/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/nested-parallel/[...slug]/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/nested-parallel/layout.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/nested-parallel/specific/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/not-found.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/optional-children-catchall/@slot/specific/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/optional-children-catchall/[[...slug]]/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/optional-children-catchall/layout.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/optional-children-catchall/specific/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/split-matcher/@slot/[...parts]/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/split-matcher/bar/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/split-matcher/foo/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/split-matcher/layout.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/valid/@slot/default.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/valid/@slot/special/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/valid/[...slug]/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/app/valid/layout.tsx create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/next.config.js create mode 100644 test/e2e/app-dir/parallel-routes-pruned-matchers/parallel-routes-pruned-matchers.test.ts diff --git a/crates/next-api/src/app.rs b/crates/next-api/src/app.rs index daffc0aa0eee..d9be7f73c1cd 100644 --- a/crates/next-api/src/app.rs +++ b/crates/next-api/src/app.rs @@ -171,6 +171,7 @@ impl AppProject { conf.page_extensions(), conf.is_global_not_found_enabled(), conf.explicit_parallel_route_children(), + conf.strict_route_matching(), self.project.next_mode(), ) } diff --git a/crates/next-core/src/app_structure.rs b/crates/next-core/src/app_structure.rs index e675114a430d..115d8155228d 100644 --- a/crates/next-core/src/app_structure.rs +++ b/crates/next-core/src/app_structure.rs @@ -517,6 +517,41 @@ impl AppPageLoaderTree { true } + fn is_builtin_not_found_default(&self, builtin_default: &FileSystemPath) -> bool { + &*self.segment == "__DEFAULT__" + && self.modules.default.as_ref().is_some_and(|default| { + default.fs == builtin_default.fs && default.path == builtin_default.path + }) + } + + /// Returns true when a slot declared by its owning layout can only use Next.js' built-in + /// not-found default. Structural router-state branches can contain the same default without + /// being renderable slots, so they do not make the matcher incomplete. + fn contains_declared_builtin_not_found_default( + &self, + builtin_default: &FileSystemPath, + declared_slots: &FxIndexMap>, + parent_layout: Option<&FileSystemPath>, + ) -> bool { + let owner_layout = self.modules.layout.as_ref().or(parent_layout); + + self.parallel_routes.iter().any(|(slot, tree)| { + let is_declared_default = tree.is_builtin_not_found_default(builtin_default) + && owner_layout.is_some_and(|layout| { + declared_slots + .get(layout) + .is_some_and(|slots| slots.contains(slot)) + }); + + is_declared_default + || tree.contains_declared_builtin_not_found_default( + builtin_default, + declared_slots, + owner_layout, + ) + }) + } + /// Returns true if this loader tree contains an intercepting route match. pub fn is_intercepting(&self) -> bool { if self.page.is_intercepting() && self.has_page() { @@ -825,6 +860,7 @@ pub fn get_entrypoints( page_extensions: Vc>, is_global_not_found_enabled: Vc, explicit_parallel_route_children: Vc, + strict_route_matching: Vc, next_mode: Vc, ) -> Vc { directory_tree_to_entrypoints( @@ -833,6 +869,7 @@ pub fn get_entrypoints( get_global_metadata(app_dir, page_extensions), is_global_not_found_enabled, explicit_parallel_route_children, + strict_route_matching, next_mode, Default::default(), Default::default(), @@ -856,28 +893,86 @@ pub async fn collect_root_params( } #[turbo_tasks::function] -fn directory_tree_to_entrypoints( +async fn directory_tree_to_entrypoints( app_dir: FileSystemPath, directory_tree: Vc, global_metadata: Vc, is_global_not_found_enabled: Vc, explicit_parallel_route_children: Vc, + strict_route_matching: Vc, next_mode: Vc, root_layouts: Vc, root_params: Vc, -) -> Vc { - directory_tree_to_entrypoints_internal( - app_dir, +) -> Result> { + let entrypoints = directory_tree_to_entrypoints_internal( + app_dir.clone(), global_metadata, is_global_not_found_enabled, explicit_parallel_route_children, + strict_route_matching, next_mode, rcstr!(""), directory_tree, AppPage::new(), root_layouts, root_params, - ) + ); + + if !*strict_route_matching.await? { + return Ok(entrypoints); + } + + let builtin_default = get_next_package(app_dir.clone()) + .await? + .join("dist/client/components/builtin/default.js")?; + let entrypoints_ref = entrypoints.await?; + let plain_tree = directory_tree.into_plain().await?; + let mut declared_slots = FxIndexMap::default(); + collect_declared_parallel_route_slots(&plain_tree, &mut declared_slots); + let mut retained_entrypoints = FxIndexMap::default(); + + // Loader trees built while walking a subtree may still contain temporary synthesized + // defaults that disappear when sibling pages are combined. Prune only the finalized root + // entrypoints so complete routes are never discarded based on an intermediate tree. + for (app_path, entrypoint) in entrypoints_ref.iter() { + let is_incomplete = match entrypoint { + Entrypoint::AppPage { loader_tree, .. } => loader_tree + .await? + .contains_declared_builtin_not_found_default( + &builtin_default, + &declared_slots, + None, + ), + _ => false, + }; + + if !is_incomplete { + retained_entrypoints.insert(app_path.clone(), entrypoint.clone()); + } + } + + // This assertion is intentionally separate from the filtering condition above. It guards + // future changes to entrypoint construction or pruning that might retain an incomplete tree. + for (app_path, entrypoint) in &retained_entrypoints { + let Entrypoint::AppPage { loader_tree, .. } = entrypoint else { + continue; + }; + if !app_path.contains_interception() + && loader_tree + .await? + .contains_declared_builtin_not_found_default( + &builtin_default, + &declared_slots, + None, + ) + { + bail!( + "Invariant: strict route matching retained the incomplete route matcher \ + `{app_path}`" + ); + } + } + Ok(Vc::cell(retained_entrypoints)) } #[turbo_tasks::value] @@ -1083,6 +1178,44 @@ fn has_declared_children_slot(directory_tree: &PlainDirectoryTree) -> bool { .any(|(_, subdirectory)| subdirectory.contains_page_or_default) } +/// Collects named slots at the current URL level. Route groups are transparent, while ordinary +/// segments and parallel routes establish nested levels with their own layout ownership. +fn collect_named_slots_at_level( + directory_tree: &PlainDirectoryTree, + slots: &mut FxIndexSet, +) { + for (name, subdirectory) in &directory_tree.subdirectories { + if let Some(slot) = match_parallel_route(name) { + if subdirectory.contains_page_or_default { + slots.insert(slot.into()); + } + } else if is_group_route(name) { + collect_named_slots_at_level(subdirectory, slots); + } + } +} + +/// Records the filesystem slots owned by each layout. Loader trees can also contain structural +/// branches used to carry parallel-route state; those branches must not participate in matcher +/// completeness unless the owning layout actually declares the slot. +fn collect_declared_parallel_route_slots( + directory_tree: &PlainDirectoryTree, + slots_by_layout: &mut FxIndexMap>, +) { + if let Some(layout) = &directory_tree.modules.layout { + let mut slots = FxIndexSet::default(); + if has_declared_children_slot(directory_tree) { + slots.insert(rcstr!("children")); + } + collect_named_slots_at_level(directory_tree, &mut slots); + slots_by_layout.insert(layout.clone(), slots); + } + + for subdirectory in directory_tree.subdirectories.values() { + collect_declared_parallel_route_slots(subdirectory, slots_by_layout); + } +} + async fn check_duplicate( duplicate: &mut FxHashMap, loader_tree: &AppPageLoaderTree, @@ -1109,7 +1242,7 @@ async fn check_duplicate( #[turbo_tasks::value(transparent)] struct AppPageLoaderTreeOption(Option>); -/// creates the loader tree for a specific route (pathname / [AppPath]) +/// Creates the loader tree for a specific route (pathname / [AppPath]). #[turbo_tasks::function] async fn directory_tree_to_loader_tree( app_dir: FileSystemPath, @@ -1120,23 +1253,38 @@ async fn directory_tree_to_loader_tree( // the page this loader tree is constructed for for_app_path: AppPath, explicit_parallel_route_children: Vc, + strict_route_matching: Vc, ) -> Result> { let plain_tree_vc = directory_tree.into_plain(); let plain_tree = &*plain_tree_vc.await?; + let strict_route_matching = *strict_route_matching.await?; + let mut missing_defaults = Vec::new(); let tree = directory_tree_to_loader_tree_internal( - app_dir, + app_dir.clone(), global_metadata, directory_name, plain_tree, - app_page, + app_page.clone(), for_app_path, *explicit_parallel_route_children.await?, AppDirModules::default(), Some(&plain_tree.url_tree), + &mut missing_defaults, ) .await?; + // Strict matching handles incomplete routes after the finalized entrypoint is assembled. + // Preserve the legacy per-tree missing-default diagnostics when strict matching is disabled. + if !strict_route_matching { + for (page, slot) in missing_defaults { + missing_default_parallel_route_issue(app_dir.clone(), page, slot) + .to_resolved() + .await? + .emit(); + } + } + Ok(Vc::cell(tree.map(AppPageLoaderTree::resolved_cell))) } @@ -1215,6 +1363,7 @@ async fn directory_tree_to_loader_tree_internal( explicit_parallel_route_children: bool, mut parent_modules: AppDirModules, url_tree: Option<&UrlSegmentTree>, + missing_defaults: &mut Vec<(AppPage, RcStr)>, ) -> Result> { let app_path = AppPath::from(app_page.clone()); @@ -1360,6 +1509,7 @@ async fn directory_tree_to_loader_tree_internal( explicit_parallel_route_children, parent_modules.clone(), child_url_tree, + missing_defaults, )) .await?; @@ -1408,14 +1558,7 @@ async fn directory_tree_to_loader_tree_internal( && !is_leaf_segment && !slot_has_children { - missing_default_parallel_route_issue( - app_dir.clone(), - app_page.clone(), - key.into(), - ) - .to_resolved() - .await? - .emit(); + missing_defaults.push((app_page.clone(), key.into())); } tree.parallel_routes.insert(key.into(), subtree); @@ -1461,15 +1604,43 @@ async fn directory_tree_to_loader_tree_internal( .any(|(_, parallel_tree)| parallel_tree.is_intercepting()); if is_interception_host { - let mut keys_to_replace = Vec::new(); + let keys_to_replace = tree + .parallel_routes + .iter() + .filter(|(_, parallel_tree)| !parallel_tree.is_intercepting()) + .map(|(key, _)| key.clone()) + .collect::>(); + let is_inside_catchall = app_page.is_catchall(); + let is_leaf_segment = !has_child_routes(directory_tree); + + for key in keys_to_replace { + let subdir_name: RcStr = format!("@{key}").into(); - for (key, parallel_tree) in &tree.parallel_routes { - if !parallel_tree.is_intercepting() { - keys_to_replace.push(key.clone()); + let default = if key == "children" { + modules.default.clone() + } else if let Some(subdirectory) = directory_tree.subdirectories.get(&subdir_name) { + subdirectory.modules.default.clone() + } else { + None + }; + let slot_has_children = directory_tree + .subdirectories + .get(&subdir_name) + .is_some_and(has_child_routes); + + // Only emit the issue if this is not the children slot and there's no default + // component. The ordinary children route does not require a default.js file. + // Also skip validation if the slot is UNDER a catch-all route or if this is a + // leaf segment (no child routes), or if the slot has matching child routes. + if default.is_none() + && key != "children" + && !is_inside_catchall + && !is_leaf_segment + && !slot_has_children + { + missing_defaults.push((app_page.clone(), key.clone())); } - } - for key in keys_to_replace { tree.parallel_routes.insert( key.clone(), retained_route_tree(app_dir.clone(), global_metadata, app_page.clone()).await?, @@ -1580,6 +1751,7 @@ async fn directory_tree_to_entrypoints_internal( global_metadata: ResolvedVc, is_global_not_found_enabled: Vc, explicit_parallel_route_children: Vc, + strict_route_matching: Vc, next_mode: Vc, directory_name: RcStr, directory_tree: Vc, @@ -1593,6 +1765,7 @@ async fn directory_tree_to_entrypoints_internal( global_metadata, is_global_not_found_enabled, explicit_parallel_route_children, + strict_route_matching, next_mode, directory_name, directory_tree, @@ -1609,6 +1782,7 @@ async fn directory_tree_to_entrypoints_internal_untraced( global_metadata: ResolvedVc, is_global_not_found_enabled: Vc, explicit_parallel_route_children: Vc, + strict_route_matching: Vc, next_mode: Vc, directory_name: RcStr, directory_tree: Vc, @@ -1674,6 +1848,7 @@ async fn directory_tree_to_entrypoints_internal_untraced( app_page.clone(), app_path, explicit_parallel_route_children, + strict_route_matching, ) .await?; @@ -1953,6 +2128,7 @@ async fn directory_tree_to_entrypoints_internal_untraced( *global_metadata, is_global_not_found_enabled, explicit_parallel_route_children, + strict_route_matching, next_mode, subdir_name.clone(), *subdirectory, @@ -1983,6 +2159,7 @@ async fn directory_tree_to_entrypoints_internal_untraced( app_page.clone(), app_path, explicit_parallel_route_children, + strict_route_matching, ); loader_trees.push(loader_tree); } diff --git a/crates/next-core/src/next_config.rs b/crates/next-core/src/next_config.rs index c6936ce85174..48bc92785f46 100644 --- a/crates/next-core/src/next_config.rs +++ b/crates/next-core/src/next_config.rs @@ -1433,6 +1433,9 @@ pub struct ExperimentalConfig { global_not_found: Option, /// Only include children in a parallel route layout when ordinary route content declares it. explicit_parallel_route_children: Option, + /// Omit catch-all-derived route matchers whose loader trees contain an unmatched parallel + /// route. + strict_route_matching: Option, /// Experimental Rust React compiler (Turbopack only); requires `reactCompiler`. turbopack_rust_react_compiler: Option, /// Defaults to false in development mode, true in production mode. @@ -2028,6 +2031,11 @@ impl NextConfig { ) } + #[turbo_tasks::function] + pub fn strict_route_matching(&self) -> Vc { + Vc::cell(self.experimental.strict_route_matching.unwrap_or_default()) + } + #[turbo_tasks::function] pub fn transpile_packages(&self) -> Vc> { Vc::cell(self.transpile_packages.clone().unwrap_or_default()) diff --git a/packages/next/src/build/build-context.ts b/packages/next/src/build/build-context.ts index 0eff06e1194c..838ccbc976bc 100644 --- a/packages/next/src/build/build-context.ts +++ b/packages/next/src/build/build-context.ts @@ -75,6 +75,7 @@ export const NextBuildContext: Partial<{ previewProps: __ApiPreviewProps mappedPages: MappedPages | undefined mappedAppPages: MappedPages | undefined + mappedAppDefaults: MappedPages | undefined mappedRootPaths: MappedPages hasInstrumentationHook: boolean diff --git a/packages/next/src/build/entries.ts b/packages/next/src/build/entries.ts index 9f2c9b376496..fcfaa9de7985 100644 --- a/packages/next/src/build/entries.ts +++ b/packages/next/src/build/entries.ts @@ -42,6 +42,7 @@ import type { ServerRuntime } from '../types' import { normalizeAppPath, compareAppPaths, + selectAppPageEntry, } from '../shared/lib/router/utils/app-paths' import { encodeMatchers } from './webpack/loaders/next-middleware-loader' import type { EdgeFunctionLoaderOptions } from './webpack/loaders/next-edge-function-loader' @@ -100,6 +101,7 @@ export interface CreateEntrypointsParams { rootPaths?: MappedPages appDir?: string appPaths?: MappedPages + appDefaultPaths?: MappedPages pageExtensions: PageExtensions hasInstrumentationHook?: boolean /** @@ -393,6 +395,7 @@ export async function createEntrypoints( rootPaths, appDir, appPaths, + appDefaultPaths, pageExtensions, deferredEntriesFilter, } = params @@ -419,7 +422,10 @@ export async function createEntrypoints( } // TODO: find a better place to do this - normalizeCatchAllRoutes(appPathsPerRoute) + normalizeCatchAllRoutes(appPathsPerRoute, { + strictRouteMatching: config.experimental.strictRouteMatching, + defaultAppPaths: Object.keys(appDefaultPaths ?? {}), + }) // Make sure to sort parallel routes to make the result deterministic. appPathsPerRoute = Object.fromEntries( @@ -433,6 +439,14 @@ export async function createEntrypoints( const getEntryHandler = (mappings: MappedPages, pagesType: PAGE_TYPES): ((page: string) => void) => async (page) => { + if ( + pagesType === PAGE_TYPES.APP && + config.experimental.strictRouteMatching && + !(normalizeAppPath(page) in appPathsPerRoute) + ) { + return + } + // Apply deferred entries filter if specified if (deferredEntriesFilter) { const isDeferred = isDeferredEntry(page, deferredEntries) @@ -497,6 +511,19 @@ export async function createEntrypoints( const isInstrumentation = isInstrumentationHookFile(page) && pagesType === PAGE_TYPES.ROOT + const matchedAppPaths = + pagesType === PAGE_TYPES.APP + ? (appPathsPerRoute[normalizeAppPath(page)] ?? null) + : null + const normalizedAppPage = normalizeAppPath(page) + const isFinalRouteMatcher = + config.experimental.strictRouteMatching && + matchedAppPaths?.length && + matchedAppPaths.some( + (appPath) => normalizeAppPath(appPath) === normalizedAppPage + ) && + selectAppPageEntry(normalizedAppPage, matchedAppPaths) === page + runDependingOnPageType({ page, pageRuntime: staticInfo.runtime, @@ -514,7 +541,6 @@ export async function createEntrypoints( }, onServer: () => { if (pagesType === 'app' && appDir) { - const matchedAppPaths = appPathsPerRoute[normalizeAppPath(page)] server[serverBundlePath] = getAppEntry({ page, name: serverBundlePath, @@ -535,6 +561,10 @@ export async function createEntrypoints( .explicitParallelRouteChildren ? true : undefined, + strictRouteMatching: config.experimental.strictRouteMatching + ? true + : undefined, + isFinalRouteMatcher: isFinalRouteMatcher ? true : undefined, }) } else if (isInstrumentation) { server[serverBundlePath.replace('src/', '')] = @@ -597,7 +627,6 @@ export async function createEntrypoints( }) } else { if (pagesType === 'app') { - const matchedAppPaths = appPathsPerRoute[normalizeAppPath(page)] appDirLoader = getAppEntry({ name: serverBundlePath, page, @@ -622,6 +651,10 @@ export async function createEntrypoints( .explicitParallelRouteChildren ? true : undefined, + strictRouteMatching: config.experimental.strictRouteMatching + ? true + : undefined, + isFinalRouteMatcher: isFinalRouteMatcher ? true : undefined, }).import } edgeServer[serverBundlePath] = getEdgeServerEntry({ diff --git a/packages/next/src/build/index.ts b/packages/next/src/build/index.ts index 83c332aa21b5..5c922ea57421 100644 --- a/packages/next/src/build/index.ts +++ b/packages/next/src/build/index.ts @@ -1473,6 +1473,7 @@ export default async function build( NextBuildContext.mappedPages = discovery.mappedPages || {} NextBuildContext.mappedAppPages = discovery.mappedAppPages + NextBuildContext.mappedAppDefaults = discovery.mappedAppDefaults NextBuildContext.mappedRootPaths = await nextBuildSpan .traceChild('create-root-mapping') .traceAsyncFn(() => @@ -2218,6 +2219,18 @@ export default async function build( } } + if (config.experimental.strictRouteMatching && pageKeys.app) { + const emittedAppPaths = new Set( + emittedAppPageKeys?.map((appPageKey) => + normalizeAppPath(appPageKey) + ) + ) + const retainedAppPaths = pageKeys.app.filter((appPath) => + emittedAppPaths.has(appPath) + ) + pageKeys.app = retainedAppPaths.length ? retainedAppPaths : undefined + } + await writeManifest( path.join(distDir, APP_PATH_ROUTES_MANIFEST), appPathRoutes diff --git a/packages/next/src/build/normalize-catchall-routes.test.ts b/packages/next/src/build/normalize-catchall-routes.test.ts index acc5013395c1..df3a628d671a 100644 --- a/packages/next/src/build/normalize-catchall-routes.test.ts +++ b/packages/next/src/build/normalize-catchall-routes.test.ts @@ -219,4 +219,292 @@ describe('normalizeCatchallRoutes', () => { // ensure values are correct after normalizing expect(appPaths).toMatchObject(initialAppPaths) }) + + describe('strictRouteMatching pruning', () => { + it('does not require an implicit children slot with no routes', () => { + const appPaths = { + '/[...slug]': ['/@catchall/[...slug]/page'], + '/foo': ['/@specific/foo/page'], + } + + normalizeCatchAllRoutes(appPaths, { strictRouteMatching: true }) + + expect(appPaths).toEqual({ + '/foo': ['/@specific/foo/page', '/@catchall/[...slug]/page'], + }) + }) + + it('keeps a named-only matcher when every declared slot matches', () => { + const appPaths = { + '/[...slug]': ['/@left/[...slug]/page', '/@right/[...slug]/page'], + } + const expected = structuredClone(appPaths) + + normalizeCatchAllRoutes(appPaths, { strictRouteMatching: true }) + + expect(appPaths).toEqual(expected) + }) + + it('does not infer children from built-in routes', () => { + const appPaths = { + '/_not-found': ['/built-in/global-not-found'], + '/_global-error': ['/built-in/app-error'], + '/[case]': ['/@slot/[case]/page'], + } + const expected = structuredClone(appPaths) + + normalizeCatchAllRoutes(appPaths, { + strictRouteMatching: true, + defaultAppPaths: [ + '/_not-found/page', + '/_global-error/page', + '/@slot/default', + ], + }) + + expect(appPaths).toEqual(expected) + }) + + it('does not apply page completeness to route handlers', () => { + const appPaths = { + '/': ['/page', '/@slot/page'], + '/icon.png': ['/@slot/icon/route'], + } + const expected = structuredClone(appPaths) + + normalizeCatchAllRoutes(appPaths, { strictRouteMatching: true }) + + expect(appPaths).toEqual(expected) + }) + + it('prunes only the incomplete catch-all matcher', () => { + const appPaths = { + '/foo': ['/foo/page'], + '/bar': ['/bar/page'], + '/[...parts]': ['/@slot/[...parts]/page'], + } + + normalizeCatchAllRoutes(appPaths, { strictRouteMatching: true }) + + expect(appPaths).toEqual({ + '/foo': ['/foo/page', '/@slot/[...parts]/page'], + '/bar': ['/bar/page', '/@slot/[...parts]/page'], + }) + }) + + it('prunes a children catch-all when a named slot is missing', () => { + const appPaths = { + '/[...slug]': ['/[...slug]/page'], + '/foo': ['/@first/foo/page'], + '/bar': ['/@second/bar/page'], + } + + normalizeCatchAllRoutes(appPaths, { strictRouteMatching: true }) + + expect(appPaths).toEqual({}) + }) + + it('prunes an optional children catch-all when a named slot is missing', () => { + const appPaths = { + '/[[...slug]]': ['/[[...slug]]/page'], + '/specific': ['/specific/page', '/@slot/specific/page'], + } + + normalizeCatchAllRoutes(appPaths, { strictRouteMatching: true }) + + expect(appPaths).toEqual({ + '/specific': ['/specific/page', '/@slot/specific/page'], + }) + }) + + it('prunes a catch-all with an incomplete nested parallel route', () => { + const appPaths = { + '/nested/[...slug]': [ + '/nested/[...slug]/page', + '/nested/@outer/[...slug]/page', + ], + '/nested/specific': [ + '/nested/specific/page', + '/nested/@outer/specific/page', + '/nested/@outer/@inner/specific/page', + ], + } + + normalizeCatchAllRoutes(appPaths, { strictRouteMatching: true }) + + expect(appPaths).toEqual({ + '/nested/specific': [ + '/nested/specific/page', + '/nested/@outer/specific/page', + '/nested/@outer/@inner/specific/page', + ], + }) + }) + + it('prunes an incomplete catch-all through a route group', () => { + const appPaths = { + '/grouped/[...slug]': ['/(pruning-group)/grouped/[...slug]/page'], + '/grouped/specific': [ + '/(pruning-group)/grouped/specific/page', + '/(pruning-group)/grouped/@slot/specific/page', + ], + } + + normalizeCatchAllRoutes(appPaths, { strictRouteMatching: true }) + + expect(appPaths).toEqual({ + '/grouped/specific': [ + '/(pruning-group)/grouped/specific/page', + '/(pruning-group)/grouped/@slot/specific/page', + ], + }) + }) + + it('keeps a catch-all matcher when every slot has catch-all coverage', () => { + const appPaths = { + '/[...slug]': ['/[...slug]/page', '/@slot/[...slug]/page'], + '/specific': ['/specific/page', '/@slot/specific/page'], + } + const expected = structuredClone(appPaths) + + normalizeCatchAllRoutes(appPaths, { strictRouteMatching: true }) + + expect(appPaths).toEqual(expected) + }) + + it('keeps catch-all routes when missing siblings have defaults', () => { + const appPaths = { + '/[...slug]': ['/[...slug]/page'], + '/specific': ['/@slot/specific/page'], + } + + normalizeCatchAllRoutes(appPaths, { + strictRouteMatching: true, + defaultAppPaths: ['/default', '/@slot/default'], + }) + + expect(appPaths).toEqual({ + '/[...slug]': ['/[...slug]/page'], + '/specific': ['/@slot/specific/page', '/[...slug]/page'], + }) + }) + + it('prunes a static matcher when a real children slot is unmatched', () => { + const appPaths = { + '/': ['/page'], + '/details': ['/@panel/details/page'], + } + + normalizeCatchAllRoutes(appPaths, { + strictRouteMatching: true, + defaultAppPaths: ['/@panel/default'], + }) + + expect(appPaths).toEqual({ + '/': ['/page'], + }) + }) + + it('keeps a static matcher when every declared slot matches', () => { + const appPaths = { + '/foo': ['/@first/foo/page', '/@second/foo/page'], + } + const expected = structuredClone(appPaths) + + normalizeCatchAllRoutes(appPaths, { strictRouteMatching: true }) + + expect(appPaths).toEqual(expected) + }) + + it('prunes static matchers with incompatible named slots', () => { + const appPaths = { + '/foo': ['/@first/foo/page'], + '/bar': ['/@second/bar/page'], + } + + normalizeCatchAllRoutes(appPaths, { strictRouteMatching: true }) + + expect(appPaths).toEqual({}) + }) + + it('keeps an interception catch-all when host slots are retained', () => { + const appPaths = { + '/': ['/@content/page', '/@secondary/page'], + '/photo/[...slug]': ['/@modal/(.)photo/[...slug]/page'], + } + const expected = structuredClone(appPaths) + + normalizeCatchAllRoutes(appPaths, { + strictRouteMatching: true, + defaultAppPaths: ['/@modal/default'], + }) + + expect(appPaths).toEqual(expected) + }) + + it('keeps real children and named slots at an interception host', () => { + const appPaths = { + '/': ['/page', '/@sidebar/page'], + '/photo/[...slug]': ['/@modal/(.)photo/[...slug]/page'], + } + const expected = structuredClone(appPaths) + + normalizeCatchAllRoutes(appPaths, { + strictRouteMatching: true, + defaultAppPaths: ['/@modal/default'], + }) + + expect(appPaths).toEqual(expected) + }) + + it('keeps named slots when children contains the interception catch-all', () => { + const appPaths = { + '/': ['/page', '/@sidebar/page'], + '/photo/[...slug]': ['/(.)photo/[...slug]/page'], + } + const expected = structuredClone(appPaths) + + normalizeCatchAllRoutes(appPaths, { strictRouteMatching: true }) + + expect(appPaths).toEqual(expected) + }) + + it('keeps slots owned by the immediate interception host through a route group', () => { + const appPaths = { + '/': ['/(host)/@modal/page', '/(host)/@modal/@sidebar/page'], + '/photo/[...slug]': ['/(host)/@modal/(.)photo/[...slug]/page'], + } + const expected = structuredClone(appPaths) + + normalizeCatchAllRoutes(appPaths, { strictRouteMatching: true }) + + expect(appPaths).toEqual(expected) + }) + + it('keeps an optional interception catch-all when host slots are retained', () => { + const appPaths = { + '/': ['/page', '/@sidebar/page'], + '/photo/[[...slug]]': ['/@modal/(.)photo/[[...slug]]/page'], + } + const expected = structuredClone(appPaths) + + normalizeCatchAllRoutes(appPaths, { + strictRouteMatching: true, + defaultAppPaths: ['/@modal/default'], + }) + + expect(appPaths).toEqual(expected) + }) + + it('prunes every incomplete matcher inside an interception subtree', () => { + const appPaths = { + '/photo/[...slug]': ['/@modal/(.)photo/@catchall/[...slug]/page'], + '/photo/specific': ['/@modal/(.)photo/@specific/specific/page'], + } + + normalizeCatchAllRoutes(appPaths, { strictRouteMatching: true }) + + expect(appPaths).toEqual({}) + }) + }) }) diff --git a/packages/next/src/build/normalize-catchall-routes.ts b/packages/next/src/build/normalize-catchall-routes.ts index 23abaee3c525..d361e8c9ce48 100644 --- a/packages/next/src/build/normalize-catchall-routes.ts +++ b/packages/next/src/build/normalize-catchall-routes.ts @@ -1 +1,11 @@ -export { normalizeCatchAllRoutes } from '../server/lib/router-utils/normalize-catchall-routes' +import { + normalizeCatchAllRoutes as normalizeCatchAllRoutesInternal, + type NormalizeCatchAllRoutesOptions, +} from '../server/lib/router-utils/normalize-catchall-routes' + +export function normalizeCatchAllRoutes( + appPaths: Record, + options: NormalizeCatchAllRoutesOptions = {} +) { + return normalizeCatchAllRoutesInternal(appPaths, undefined, options) +} diff --git a/packages/next/src/build/route-discovery.ts b/packages/next/src/build/route-discovery.ts index cc185848cc40..cd98a14b05ca 100644 --- a/packages/next/src/build/route-discovery.ts +++ b/packages/next/src/build/route-discovery.ts @@ -408,6 +408,7 @@ export interface RouteDiscoveryResult { pageApiRoutes: RouteInfo[] mappedAppPages?: MappedPages mappedAppLayouts?: MappedPages + mappedAppDefaults?: MappedPages mappedPages?: MappedPages /** Raw page file paths (post-filtering), useful for telemetry */ pagesPaths: string[] @@ -514,6 +515,7 @@ export async function discoverRoutes( let slots: SlotInfo[] = [] let mappedAppPages: MappedPages | undefined let mappedAppLayouts: MappedPages | undefined + let mappedAppDefaults: MappedPages | undefined if (appDir) { let appPaths: string[] @@ -537,19 +539,16 @@ export async function discoverRoutes( } // Map all app file types in parallel - let mappedDefaultFiles: MappedPages - ;[mappedAppPages, mappedAppLayouts, mappedDefaultFiles] = await Promise.all( - [ - mapPaths(appPaths, PAGE_TYPES.APP), - mapPaths(layoutPaths, PAGE_TYPES.APP), - mapPaths(defaultPaths, PAGE_TYPES.APP), - ] - ) + ;[mappedAppPages, mappedAppLayouts, mappedAppDefaults] = await Promise.all([ + mapPaths(appPaths, PAGE_TYPES.APP), + mapPaths(layoutPaths, PAGE_TYPES.APP), + mapPaths(defaultPaths, PAGE_TYPES.APP), + ]) // Extract slots from pages and default files slots = combineSlots( extractSlotsFromRoutes(mappedAppPages, SKIP_ROUTES), - extractSlotsFromRoutes(mappedDefaultFiles) + extractSlotsFromRoutes(mappedAppDefaults) ) // Process routes @@ -571,6 +570,7 @@ export async function discoverRoutes( pageApiRoutes, mappedAppPages, mappedAppLayouts, + mappedAppDefaults, mappedPages, pagesPaths, appDirOnly, diff --git a/packages/next/src/build/webpack-build/impl.ts b/packages/next/src/build/webpack-build/impl.ts index 0ea62617c63d..78595cc4de52 100644 --- a/packages/next/src/build/webpack-build/impl.ts +++ b/packages/next/src/build/webpack-build/impl.ts @@ -110,6 +110,7 @@ export async function webpackBuildImpl( appDir: NextBuildContext.appDir!, pages: NextBuildContext.mappedPages!, appPaths: NextBuildContext.mappedAppPages!, + appDefaultPaths: NextBuildContext.mappedAppDefaults, previewMode: NextBuildContext.previewProps!, rootPaths: NextBuildContext.mappedRootPaths!, hasInstrumentationHook: NextBuildContext.hasInstrumentationHook!, @@ -133,6 +134,7 @@ export async function webpackBuildImpl( appDir: NextBuildContext.appDir!, pages: NextBuildContext.mappedPages!, appPaths: NextBuildContext.mappedAppPages!, + appDefaultPaths: NextBuildContext.mappedAppDefaults, previewMode: NextBuildContext.previewProps!, rootPaths: NextBuildContext.mappedRootPaths!, hasInstrumentationHook: NextBuildContext.hasInstrumentationHook!, diff --git a/packages/next/src/build/webpack/loaders/next-app-loader/index.ts b/packages/next/src/build/webpack/loaders/next-app-loader/index.ts index b52035ca4597..22c6d1c2c7d0 100644 --- a/packages/next/src/build/webpack/loaders/next-app-loader/index.ts +++ b/packages/next/src/build/webpack/loaders/next-app-loader/index.ts @@ -65,6 +65,8 @@ export type AppLoaderOptions = { middlewareConfig: string isGlobalNotFoundEnabled: true | undefined explicitParallelRouteChildren: true | undefined + strictRouteMatching: true | undefined + isFinalRouteMatcher: true | undefined } type AppLoader = webpack.LoaderDefinitionFunction @@ -166,6 +168,8 @@ async function createTreeCodeFromPath( collectedDeclarations, isGlobalNotFoundEnabled, explicitParallelRouteChildren, + strictRouteMatching, + isFinalRouteMatcher, isDev, }: { page: string @@ -183,6 +187,8 @@ async function createTreeCodeFromPath( collectedDeclarations: [string, string][] isGlobalNotFoundEnabled: boolean explicitParallelRouteChildren: boolean + strictRouteMatching: boolean + isFinalRouteMatcher: boolean isDev: boolean } ): Promise<{ @@ -356,12 +362,14 @@ async function createTreeCodeFromPath( ): Promise<{ treeCode: string containsInterception: boolean + containsBuiltinNotFoundDefault: boolean }> { const segmentPath = segments.join('/') // Existing tree are the children of the current segment const props: Record = {} const interceptingParallelKeys = new Set() + let containsBuiltinNotFoundDefault = false // Root layer could be 1st layer of normal routes const isRootLayer = segments.length === 0 const isRootLayoutOrRootPage = segments.length <= 1 @@ -677,12 +685,14 @@ async function createTreeCodeFromPath( const { treeCode: pageSubtreeCode, containsInterception: subtreeContainsInterception, + containsBuiltinNotFoundDefault: subtreeContainsBuiltinNotFoundDefault, } = await createSubtreePropsFromSegmentPath( subSegmentPath, nestedCollectedDeclarations ) subtreeCode = pageSubtreeCode + containsBuiltinNotFoundDefault ||= subtreeContainsBuiltinNotFoundDefault if (subtreeContainsInterception) { interceptingParallelKeys.add(normalizedParallelKey) } @@ -712,6 +722,10 @@ async function createTreeCodeFromPath( interceptingParallelKeys.size > 0 function setSyntheticDefault(key: string, defaultPath: string) { + if (defaultPath === PARALLEL_ROUTE_DEFAULT_PATH) { + containsBuiltinNotFoundDefault = true + } + const varName = `default${nestedCollectedDeclarations.length}` nestedCollectedDeclarations.push([varName, defaultPath]) props[key] = `[ @@ -806,13 +820,28 @@ async function createTreeCodeFromPath( containsInterception: isInterceptionRouteAppPath(segmentPath) || interceptingParallelKeys.size > 0, + containsBuiltinNotFoundDefault, } } - const { treeCode } = await createSubtreePropsFromSegmentPath( - [], - collectedDeclarations - ) + const { treeCode, containsBuiltinNotFoundDefault } = + await createSubtreePropsFromSegmentPath([], collectedDeclarations) + + if ( + strictRouteMatching && + isFinalRouteMatcher && + !isInterceptionRouteAppPath(page) && + containsBuiltinNotFoundDefault && + !isNotFoundRoute && + !isAppErrorRoute + ) { + // A retained ordinary matcher must be able to construct its complete + // route tree. An interception tree is a partial update and may + // intentionally contain synthetic slots. + throw new Error( + `Invariant: strict route matching retained the incomplete route matcher ${page}` + ) + } return { treeCode: `${treeCode}.children;`, @@ -861,6 +890,8 @@ const nextAppLoader: AppLoader = async function nextAppLoader() { const isGlobalNotFoundEnabled = !!loaderOptions.isGlobalNotFoundEnabled const explicitParallelRouteChildren = !!loaderOptions.explicitParallelRouteChildren + const strictRouteMatching = !!loaderOptions.strictRouteMatching + const isFinalRouteMatcher = !!loaderOptions.isFinalRouteMatcher // Update FILE_TYPES on the very top-level of the loader if (!isGlobalNotFoundEnabled) { @@ -896,7 +927,6 @@ const nextAppLoader: AppLoader = async function nextAppLoader() { const normalizedAppPaths = typeof appPaths === 'string' ? [appPaths] : appPaths || [] - // All normalized app paths for computing static siblings across route groups const allNormalizedAppPaths = allNormalizedAppPathsOption ?? [] @@ -1195,6 +1225,8 @@ const nextAppLoader: AppLoader = async function nextAppLoader() { collectedDeclarations, isGlobalNotFoundEnabled, explicitParallelRouteChildren, + strictRouteMatching, + isFinalRouteMatcher, isDev: !!isDev, }) @@ -1256,6 +1288,8 @@ const nextAppLoader: AppLoader = async function nextAppLoader() { collectedDeclarations, isGlobalNotFoundEnabled, explicitParallelRouteChildren, + strictRouteMatching, + isFinalRouteMatcher, isDev: !!isDev, }) } diff --git a/packages/next/src/server/config-schema.ts b/packages/next/src/server/config-schema.ts index 3a8a49dc4869..e50c6fcf8a44 100644 --- a/packages/next/src/server/config-schema.ts +++ b/packages/next/src/server/config-schema.ts @@ -476,6 +476,7 @@ export const experimentalSchema = { .optional(), globalNotFound: z.boolean().optional(), explicitParallelRouteChildren: z.boolean().optional(), + strictRouteMatching: z.boolean().optional(), turbopackRustReactCompiler: z.boolean().optional(), browserDebugInfoInTerminal: z .union([ diff --git a/packages/next/src/server/config-shared.ts b/packages/next/src/server/config-shared.ts index b5d71bf56dba..d4d39625cd1c 100644 --- a/packages/next/src/server/config-shared.ts +++ b/packages/next/src/server/config-shared.ts @@ -1411,6 +1411,14 @@ export interface ExperimentalConfig { */ explicitParallelRouteChildren?: boolean + /** + * Omits catch-all-derived App Router matchers that cannot construct a + * complete parallel route tree for their URL. This requires + * `explicitParallelRouteChildren`; setting that option to `false` also + * disables strict route matching. + */ + strictRouteMatching?: boolean + /** * @experimental Use the Rust port of the React compiler (Turbopack only). * Requires `reactCompiler` to be enabled. @@ -2350,6 +2358,7 @@ export const defaultConfig = Object.freeze({ slowModuleDetection: undefined, globalNotFound: false, explicitParallelRouteChildren: true, + strictRouteMatching: false, browserDebugInfoInTerminal: 'warn', lockDistDir: true, disableResumeDataCacheCompression: false, diff --git a/packages/next/src/server/config.test.ts b/packages/next/src/server/config.test.ts index da36e17924c3..f3f6c1b80445 100644 --- a/packages/next/src/server/config.test.ts +++ b/packages/next/src/server/config.test.ts @@ -193,6 +193,36 @@ describe('loadConfig', () => { }) }) + describe('parallel route matching flags', () => { + it('allows explicit children detection without strict route matching', async () => { + const result = await loadConfig(PHASE_PRODUCTION_BUILD, __dirname, { + customConfig: { + experimental: { + explicitParallelRouteChildren: true, + strictRouteMatching: false, + }, + }, + }) + + expect(result.experimental.explicitParallelRouteChildren).toBe(true) + expect(result.experimental.strictRouteMatching).toBe(false) + }) + + it('disables strict route matching when explicit children detection is disabled', async () => { + const result = await loadConfig(PHASE_PRODUCTION_BUILD, __dirname, { + customConfig: { + experimental: { + explicitParallelRouteChildren: false, + strictRouteMatching: true, + }, + }, + }) + + expect(result.experimental.explicitParallelRouteChildren).toBe(false) + expect(result.experimental.strictRouteMatching).toBe(false) + }) + }) + describe('cacheHandlers validation', () => { it('should reject invalid keys', async () => { const invalidKeys = [ diff --git a/packages/next/src/server/config.ts b/packages/next/src/server/config.ts index 2fa3dcfbf01d..66c8ef8c1851 100644 --- a/packages/next/src/server/config.ts +++ b/packages/next/src/server/config.ts @@ -454,6 +454,13 @@ function assignDefaultsAndValidate( }, } + // Pruning assumes that children only exists when it is backed by an + // ordinary route branch. Restoring the legacy implicit children slot must + // therefore also restore the legacy matcher behavior. + if (!result.experimental.explicitParallelRouteChildren) { + result.experimental.strictRouteMatching = false + } + // Normalize prefetchInlining: true | { maxSize?, maxBundleSize? } into a // resolved object with concrete defaults, so consumers don't have to // resolve the values themselves. diff --git a/packages/next/src/server/dev/hot-reloader-webpack.ts b/packages/next/src/server/dev/hot-reloader-webpack.ts index 32c2ef6f45ba..f6ba5ba4b57c 100644 --- a/packages/next/src/server/dev/hot-reloader-webpack.ts +++ b/packages/next/src/server/dev/hot-reloader-webpack.ts @@ -998,6 +998,13 @@ export default class HotReloaderWebpack implements NextJsHotReloaderInterface { const isInstrumentation = isInstrumentationHookFile(page) && pageType === PAGE_TYPES.ROOT + const entryAppPaths = + 'appPaths' in entryData ? entryData.appPaths : null + const isFinalRouteMatcher = + pageType === PAGE_TYPES.APP && + this.config.experimental.strictRouteMatching && + !!entryAppPaths?.length + let pageRuntime = staticInfo?.runtime runDependingOnPageType({ @@ -1057,6 +1064,13 @@ export default class HotReloaderWebpack implements NextJsHotReloaderInterface { .explicitParallelRouteChildren ? true : undefined, + strictRouteMatching: this.config.experimental + .strictRouteMatching + ? true + : undefined, + isFinalRouteMatcher: isFinalRouteMatcher + ? true + : undefined, }).import : undefined @@ -1185,6 +1199,11 @@ export default class HotReloaderWebpack implements NextJsHotReloaderInterface { .explicitParallelRouteChildren ? true : undefined, + strictRouteMatching: this.config.experimental + .strictRouteMatching + ? true + : undefined, + isFinalRouteMatcher: isFinalRouteMatcher ? true : undefined, }) } else if (isAPIRoute(page)) { value = getRouteLoaderEntry({ diff --git a/packages/next/src/server/lib/router-utils/normalize-catchall-routes.ts b/packages/next/src/server/lib/router-utils/normalize-catchall-routes.ts index 13a52a865e43..8ae2e059465e 100644 --- a/packages/next/src/server/lib/router-utils/normalize-catchall-routes.ts +++ b/packages/next/src/server/lib/router-utils/normalize-catchall-routes.ts @@ -1,10 +1,29 @@ import { normalizeAppPath } from '../../../shared/lib/router/utils/app-paths' -import { isInterceptionRouteAppPath } from '../../../shared/lib/router/utils/interception-routes' +import { isAppPageRoute } from '../../../lib/is-app-page-route' +import { + INTERCEPTION_ROUTE_MARKERS, + isInterceptionRouteAppPath, +} from '../../../shared/lib/router/utils/interception-routes' +import { + UNDERSCORE_GLOBAL_ERROR_ROUTE_ENTRY, + UNDERSCORE_NOT_FOUND_ROUTE_ENTRY, +} from '../../../shared/lib/entry-constants' type AppPathNormalizer = { normalize(pathname: string): string } +export type NormalizeCatchAllRoutesOptions = { + strictRouteMatching?: boolean + defaultAppPaths?: Iterable +} + +type ParallelRouteLevel = { + parentSegments: string[] + namedSlots: Set + hasChildrenSlot: boolean +} + const defaultNormalizer: AppPathNormalizer = { normalize(pathname: string): string { return normalizeAppPath(pathname).replace(/%5F/g, '_') @@ -20,7 +39,11 @@ const defaultNormalizer: AppPathNormalizer = { */ export function normalizeCatchAllRoutes( appPaths: Record, - normalizer: AppPathNormalizer = defaultNormalizer + normalizer: AppPathNormalizer = defaultNormalizer, + { + strictRouteMatching = false, + defaultAppPaths = [], + }: NormalizeCatchAllRoutesOptions = {} ) { const catchAllRoutes = [ ...new Set( @@ -69,6 +92,174 @@ export function normalizeCatchAllRoutes( } } } + + if (strictRouteMatching) { + pruneUnrenderableRoutes(appPaths, defaultAppPaths) + } +} + +/** + * Removes routes that can never render because a declared slot at a matching + * layout level has neither a matching page nor an explicit default. + * + * The built-in default for such a slot always calls `notFound()`. Keeping the + * route in the matcher set would therefore retain a matcher that can never + * construct a complete loader tree. + */ +function pruneUnrenderableRoutes( + appPaths: Record, + defaultAppPaths: Iterable +) { + const allAppPaths = new Set([ + ...Object.values(appPaths).flat().filter(isUserAppPageRoute), + ...[...defaultAppPaths].filter( + (appPath) => !isBuiltinAppPageEntry(appPath) + ), + ]) + const levelsByParent = new Map() + + for (const appPath of allAppPaths) { + const segments = splitAppPath(appPath) + + for (let i = 0; i < segments.length - 1; i++) { + const segment = segments[i] + if (!isMatchableSlot(segment)) continue + + const parentSegments = segments.slice(0, i) + const parentKey = JSON.stringify(parentSegments) + let level = levelsByParent.get(parentKey) + if (!level) { + level = { + parentSegments, + namedSlots: new Set(), + hasChildrenSlot: false, + } + levelsByParent.set(parentKey, level) + } + level.namedSlots.add(segment) + } + } + + for (const appPath of allAppPaths) { + for (const level of levelsByParent.values()) { + if (isPathInSlot(appPath, level.parentSegments, 'children')) { + level.hasChildrenSlot = true + } + } + } + + for (const [route, matchedAppPaths] of Object.entries(appPaths)) { + const matchedPageAppPaths = matchedAppPaths.filter(isUserAppPageRoute) + if (matchedPageAppPaths.length === 0) continue + + if ( + hasIncompleteParallelRoute( + matchedPageAppPaths, + levelsByParent.values(), + allAppPaths + ) + ) { + const nonPageAppPaths = matchedAppPaths.filter( + (appPath) => !isUserAppPageRoute(appPath) + ) + if (nonPageAppPaths.length === 0) { + delete appPaths[route] + } else { + appPaths[route] = nonPageAppPaths + } + } + } +} + +function isUserAppPageRoute(appPath: string): boolean { + return isAppPageRoute(appPath) && !isBuiltinAppPageEntry(appPath) +} + +function isBuiltinAppPageEntry(appPath: string): boolean { + return ( + appPath === UNDERSCORE_NOT_FOUND_ROUTE_ENTRY || + appPath === UNDERSCORE_GLOBAL_ERROR_ROUTE_ENTRY + ) +} + +function hasIncompleteParallelRoute( + matchedAppPaths: string[], + levels: Iterable, + allAppPaths: Set +): boolean { + const matchedSegments = matchedAppPaths.map((appPath) => + splitAppPath(appPath).slice(0, -1) + ) + + for (const { parentSegments, namedSlots, hasChildrenSlot } of levels) { + const pathsAtLevel = matchedSegments.filter((segments) => + hasPathPrefix(segments, parentSegments) + ) + if (pathsAtLevel.length === 0) continue + + // An interception response replaces one slot while retaining every + // sibling owned by layouts up to the interception marker. Those siblings + // use the null retain marker rather than a page or default. Slots inside + // the newly selected subtree still match normally. + const retainsInterceptionSiblings = pathsAtLevel.some((segments) => { + const interceptionMarkerIndex = segments.findIndex((segment) => + INTERCEPTION_ROUTE_MARKERS.some((marker) => segment.startsWith(marker)) + ) + return ( + interceptionMarkerIndex !== -1 && + parentSegments.length <= interceptionMarkerIndex + ) + }) + + const slots = [...(hasChildrenSlot ? ['children'] : []), ...namedSlots] + for (const slot of slots) { + const hasMatchedPage = matchedAppPaths.some((appPath) => + isPathInSlot(appPath, parentSegments, slot) + ) + const hasDefault = allAppPaths.has( + getDefaultAppPath(parentSegments, slot) + ) + + if (!hasMatchedPage && !hasDefault && !retainsInterceptionSiblings) { + return true + } + } + } + + return false +} + +function splitAppPath(appPath: string): string[] { + return appPath.split('/').filter(Boolean) +} + +function hasPathPrefix(path: string[], prefix: string[]): boolean { + return prefix.every((segment, index) => path[index] === segment) +} + +function getSlotAtParent(path: string[], parent: string[]): string { + const segment = path[parent.length] + return segment?.startsWith('@') && segment !== '@children' + ? segment + : 'children' +} + +function isPathInSlot( + appPath: string, + parent: string[], + slot: string +): boolean { + const segments = splitAppPath(appPath) + return ( + hasPathPrefix(segments, parent) && + getSlotAtParent(segments, parent) === slot + ) +} + +function getDefaultAppPath(parent: string[], slot: string): string { + const segments = + slot === 'children' ? [...parent, 'default'] : [...parent, slot, 'default'] + return `/${segments.join('/')}` } function hasMatchedSlots(path1: string, path2: string): boolean { diff --git a/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts b/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts index ad18411a59b8..df41d53df4ee 100644 --- a/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts +++ b/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts @@ -464,6 +464,7 @@ async function startWatcher( const routedPages: string[] = [] const knownFiles = wp.getTimeInfoEntries() const appPaths: Record = {} + const defaultAppPaths = new Set() const pageNameSet = new Set() const conflictingAppPagePaths = new Set() const duplicatePagePaths = new Set() @@ -723,6 +724,11 @@ async function startWatcher( continue } + if (validFileMatcher.isAppDefaultPage(fileName)) { + defaultAppPaths.add(normalizedPageName.replace(/%5F/g, '_')) + continue + } + // Skip non-router pages (loading.tsx, error.tsx, etc.) if (!validFileMatcher.isAppRouterPage(fileName)) continue @@ -1049,7 +1055,10 @@ async function startWatcher( } } - normalizeCatchAllRoutes(appPagePaths) + normalizeCatchAllRoutes(appPagePaths, undefined, { + strictRouteMatching: nextConfig.experimental.strictRouteMatching, + defaultAppPaths, + }) for (const pageAppPaths of Object.values(appPagePaths)) { pageAppPaths.sort(compareAppPaths) } diff --git a/test/e2e/app-dir/interception-dynamic-segment/app/named-host/@canonical/named-catchall-target/[...slug]/page.tsx b/test/e2e/app-dir/interception-dynamic-segment/app/named-host/@canonical/named-catchall-target/[...slug]/page.tsx new file mode 100644 index 000000000000..4bc8b2de9f90 --- /dev/null +++ b/test/e2e/app-dir/interception-dynamic-segment/app/named-host/@canonical/named-catchall-target/[...slug]/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return

Canonical catch-all target

+} diff --git a/test/e2e/app-dir/interception-dynamic-segment/app/named-host/@canonical/page.tsx b/test/e2e/app-dir/interception-dynamic-segment/app/named-host/@canonical/page.tsx new file mode 100644 index 000000000000..6457550cab73 --- /dev/null +++ b/test/e2e/app-dir/interception-dynamic-segment/app/named-host/@canonical/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return

Named canonical slot

+} diff --git a/test/e2e/app-dir/interception-dynamic-segment/app/named-host/@content/page.tsx b/test/e2e/app-dir/interception-dynamic-segment/app/named-host/@content/page.tsx index 0ef4f2514d1f..5db1414444f7 100644 --- a/test/e2e/app-dir/interception-dynamic-segment/app/named-host/@content/page.tsx +++ b/test/e2e/app-dir/interception-dynamic-segment/app/named-host/@content/page.tsx @@ -6,6 +6,7 @@ export default function Page() {

Named content slot

+ ) } diff --git a/test/e2e/app-dir/interception-dynamic-segment/app/named-host/@modal/(.)named-catchall-target/[...slug]/page.tsx b/test/e2e/app-dir/interception-dynamic-segment/app/named-host/@modal/(.)named-catchall-target/[...slug]/page.tsx new file mode 100644 index 000000000000..1f23849d631c --- /dev/null +++ b/test/e2e/app-dir/interception-dynamic-segment/app/named-host/@modal/(.)named-catchall-target/[...slug]/page.tsx @@ -0,0 +1,5 @@ +export default function Page() { + return ( +

Intercepted named catch-all target

+ ) +} diff --git a/test/e2e/app-dir/interception-dynamic-segment/app/named-host/layout.tsx b/test/e2e/app-dir/interception-dynamic-segment/app/named-host/layout.tsx index a824ba439878..a47b1b5e7872 100644 --- a/test/e2e/app-dir/interception-dynamic-segment/app/named-host/layout.tsx +++ b/test/e2e/app-dir/interception-dynamic-segment/app/named-host/layout.tsx @@ -1,14 +1,17 @@ export default function Layout({ + canonical, content, modal, secondary, }: { + canonical: React.ReactNode content: React.ReactNode modal: React.ReactNode secondary: React.ReactNode }) { return (
+
{canonical}
{content}
{secondary}
{modal}
diff --git a/test/e2e/app-dir/interception-dynamic-segment/app/regular-route/default.tsx b/test/e2e/app-dir/interception-dynamic-segment/app/regular-route/default.tsx new file mode 100644 index 000000000000..4a73c8d9d131 --- /dev/null +++ b/test/e2e/app-dir/interception-dynamic-segment/app/regular-route/default.tsx @@ -0,0 +1,8 @@ +import { notFound } from 'next/navigation' + +// The children slot intentionally 404s at /regular-route. Make that behavior +// explicit so strict route matching can distinguish it from a synthesized +// fallback left behind by an incomplete matcher. +export default function Default() { + notFound() +} diff --git a/test/e2e/app-dir/interception-dynamic-segment/interception-dynamic-segment.test.ts b/test/e2e/app-dir/interception-dynamic-segment/interception-dynamic-segment.test.ts index 6ba45ece3fe8..59b4ed1a0f82 100644 --- a/test/e2e/app-dir/interception-dynamic-segment/interception-dynamic-segment.test.ts +++ b/test/e2e/app-dir/interception-dynamic-segment/interception-dynamic-segment.test.ts @@ -157,6 +157,34 @@ describe('interception-dynamic-segment', () => { }) } + if (isNextDev) { + it('should retain named host slots for an interception catch-all in development', async () => { + const { act, browser } = await createBrowserWithRouterAct('/named-host') + + await browser.elementById('retained-counter').click() + + await act(async () => { + await navigate(browser, '/named-host/named-catchall-target/photo') + }) + + expect( + await browser.elementById('named-host-catchall-modal').text() + ).toBe('Intercepted named catch-all target') + expect(await browser.elementById('named-host-content').text()).toContain( + 'Named content slot' + ) + expect( + await browser.elementById('named-host-secondary').text() + ).toContain('Named secondary slot without a default') + expect(await browser.elementById('named-host-canonical').text()).toBe( + 'Named canonical slot' + ) + expect(await browser.elementById('retained-counter').text()).toBe( + 'Retained count: 1' + ) + }) + } + if (!isNextDev) { /** * Test Case Validation: Ensure NO 404s occur during interception navigation @@ -292,6 +320,32 @@ describe('interception-dynamic-segment', () => { expect(await browser.hasElementByCss('#named-host')).toBe(false) }) + it('should retain named host slots for an interception catch-all', async () => { + const { act, browser } = await createBrowserWithRouterAct('/named-host') + + await browser.elementById('retained-counter').click() + + await act(async () => { + await navigate(browser, '/named-host/named-catchall-target/photo') + }) + + expect( + await browser.elementById('named-host-catchall-modal').text() + ).toBe('Intercepted named catch-all target') + expect( + await browser.elementById('named-host-content').text() + ).toContain('Named content slot') + expect( + await browser.elementById('named-host-secondary').text() + ).toContain('Named secondary slot without a default') + expect(await browser.elementById('named-host-canonical').text()).toBe( + 'Named canonical slot' + ) + expect(await browser.elementById('retained-counter').text()).toBe( + 'Retained count: 1' + ) + }) + it('should send and render a real default for a newly entered slot owner', async () => { const { act, browser } = await createBrowserWithRouterAct('/') diff --git a/test/e2e/app-dir/interception-dynamic-segment/next.config.js b/test/e2e/app-dir/interception-dynamic-segment/next.config.js index 1a89f53f4774..cce199489275 100644 --- a/test/e2e/app-dir/interception-dynamic-segment/next.config.js +++ b/test/e2e/app-dir/interception-dynamic-segment/next.config.js @@ -7,6 +7,10 @@ const nextConfig = { // intercepted layouts omit children, while a real children branch still // uses the marker needed to preserve its active state during navigation. explicitParallelRouteChildren: true, + // Interception catch-alls retain every sibling owned by the host layout. + // Strict matching must not mistake those retained slots for incomplete + // route coverage and prune the interception matcher. + strictRouteMatching: true, }, } diff --git a/test/e2e/app-dir/parallel-routes-leaf-segments/fixtures/no-build-error/app/interception-host/@content/dashboard/page.tsx b/test/e2e/app-dir/parallel-routes-leaf-segments/fixtures/no-build-error/app/interception-host/@content/dashboard/page.tsx new file mode 100644 index 000000000000..ec2636f19a39 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-leaf-segments/fixtures/no-build-error/app/interception-host/@content/dashboard/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return

Dashboard content

+} diff --git a/test/e2e/app-dir/parallel-routes-leaf-segments/fixtures/no-build-error/app/interception-host/@content/page.tsx b/test/e2e/app-dir/parallel-routes-leaf-segments/fixtures/no-build-error/app/interception-host/@content/page.tsx new file mode 100644 index 000000000000..59a1fe141e44 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-leaf-segments/fixtures/no-build-error/app/interception-host/@content/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return

Home content

+} diff --git a/test/e2e/app-dir/parallel-routes-leaf-segments/fixtures/no-build-error/app/interception-host/@modal/(.)dashboard/page.tsx b/test/e2e/app-dir/parallel-routes-leaf-segments/fixtures/no-build-error/app/interception-host/@modal/(.)dashboard/page.tsx new file mode 100644 index 000000000000..fd17b61b233e --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-leaf-segments/fixtures/no-build-error/app/interception-host/@modal/(.)dashboard/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return

Intercepted dashboard

+} diff --git a/test/e2e/app-dir/parallel-routes-leaf-segments/fixtures/no-build-error/app/interception-host/@modal/default.tsx b/test/e2e/app-dir/parallel-routes-leaf-segments/fixtures/no-build-error/app/interception-host/@modal/default.tsx new file mode 100644 index 000000000000..123e64947179 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-leaf-segments/fixtures/no-build-error/app/interception-host/@modal/default.tsx @@ -0,0 +1,3 @@ +export default function Default() { + return

No modal

+} diff --git a/test/e2e/app-dir/parallel-routes-leaf-segments/fixtures/no-build-error/app/interception-host/dashboard/page.tsx b/test/e2e/app-dir/parallel-routes-leaf-segments/fixtures/no-build-error/app/interception-host/dashboard/page.tsx new file mode 100644 index 000000000000..ba3e67a72631 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-leaf-segments/fixtures/no-build-error/app/interception-host/dashboard/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return

Dashboard page

+} diff --git a/test/e2e/app-dir/parallel-routes-leaf-segments/fixtures/no-build-error/app/interception-host/layout.tsx b/test/e2e/app-dir/parallel-routes-leaf-segments/fixtures/no-build-error/app/interception-host/layout.tsx new file mode 100644 index 000000000000..4e8c6246a382 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-leaf-segments/fixtures/no-build-error/app/interception-host/layout.tsx @@ -0,0 +1,17 @@ +export default function Layout({ + children, + content, + modal, +}: { + children: React.ReactNode + content: React.ReactNode + modal: React.ReactNode +}) { + return ( +
+
{children}
+
{content}
+ +
+ ) +} diff --git a/test/e2e/app-dir/parallel-routes-leaf-segments/fixtures/no-build-error/app/interception-host/page.tsx b/test/e2e/app-dir/parallel-routes-leaf-segments/fixtures/no-build-error/app/interception-host/page.tsx new file mode 100644 index 000000000000..282c00dca591 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-leaf-segments/fixtures/no-build-error/app/interception-host/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return

Home page

+} diff --git a/test/e2e/app-dir/parallel-routes-leaf-segments/fixtures/no-build-error/next.config.js b/test/e2e/app-dir/parallel-routes-leaf-segments/fixtures/no-build-error/next.config.js new file mode 100644 index 000000000000..bae6c3e02525 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-leaf-segments/fixtures/no-build-error/next.config.js @@ -0,0 +1,11 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + experimental: { + // This fixture exercises the legacy missing-default validation directly. + // Keep that coverage if strict route matching becomes the default; once + // strict matching is the only mode, this fixture can be removed. + strictRouteMatching: false, + }, +} + +module.exports = nextConfig diff --git a/test/e2e/app-dir/parallel-routes-leaf-segments/parallel-routes-leaf-segments.no-build-error.test.ts b/test/e2e/app-dir/parallel-routes-leaf-segments/parallel-routes-leaf-segments.no-build-error.test.ts index 2024bf02c392..887051ec6b2d 100644 --- a/test/e2e/app-dir/parallel-routes-leaf-segments/parallel-routes-leaf-segments.no-build-error.test.ts +++ b/test/e2e/app-dir/parallel-routes-leaf-segments/parallel-routes-leaf-segments.no-build-error.test.ts @@ -75,4 +75,17 @@ describe('parallel-routes-leaf-segments-no-build-error', () => { expect($('#children').text()).toBe('No Children Default') }) }) + + describe('interception host with a matching sibling route', () => { + it('should not require a default for the matched sibling slot', async () => { + // @content has no default, but it has a matching page for every ordinary + // child route. The interception matcher retains @content during a soft + // navigation, so compiling that matcher must not report a missing default. + const $ = await next.render$('/interception-host/dashboard') + + expect($('#children').text()).toBe('Dashboard page') + expect($('#content').text()).toBe('Dashboard content') + expect($('#modal').text()).toBe('No modal') + }) + }) }) diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/(pruning-group)/grouped/@slot/specific/page.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/(pruning-group)/grouped/@slot/specific/page.tsx new file mode 100644 index 000000000000..22436a107acd --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/(pruning-group)/grouped/@slot/specific/page.tsx @@ -0,0 +1,3 @@ +export default function GroupedSlotPage() { + return

grouped slot page

+} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/(pruning-group)/grouped/[...slug]/page.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/(pruning-group)/grouped/[...slug]/page.tsx new file mode 100644 index 000000000000..cacd5341639e --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/(pruning-group)/grouped/[...slug]/page.tsx @@ -0,0 +1,3 @@ +export default function GroupedCatchallPage() { + return

grouped catch-all page

+} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/(pruning-group)/grouped/layout.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/(pruning-group)/grouped/layout.tsx new file mode 100644 index 000000000000..90f7b48a16bc --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/(pruning-group)/grouped/layout.tsx @@ -0,0 +1,21 @@ +import type { ReactNode } from 'react' + +/** + * The route group must not affect completeness. The broad children catch-all + * lacks @slot coverage and is pruned, while /grouped/specific has both pages + * and remains in the matcher set. + */ +export default function GroupedLayout({ + children, + slot, +}: { + children: ReactNode + slot: ReactNode +}) { + return ( +
+ {children} + {slot} +
+ ) +} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/(pruning-group)/grouped/specific/page.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/(pruning-group)/grouped/specific/page.tsx new file mode 100644 index 000000000000..7fff4b611391 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/(pruning-group)/grouped/specific/page.tsx @@ -0,0 +1,3 @@ +export default function GroupedSpecificPage() { + return

grouped specific page

+} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/children-catchall/@first/foo/page.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/children-catchall/@first/foo/page.tsx new file mode 100644 index 000000000000..efb7a5a78e77 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/children-catchall/@first/foo/page.tsx @@ -0,0 +1,3 @@ +export default function FirstSlot() { + return

first slot

+} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/children-catchall/@second/bar/page.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/children-catchall/@second/bar/page.tsx new file mode 100644 index 000000000000..cc4f4e45e5b1 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/children-catchall/@second/bar/page.tsx @@ -0,0 +1,3 @@ +export default function SecondSlot() { + return

second slot

+} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/children-catchall/[...slug]/page.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/children-catchall/[...slug]/page.tsx new file mode 100644 index 000000000000..9026010d150e --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/children-catchall/[...slug]/page.tsx @@ -0,0 +1,3 @@ +export default function ChildrenCatchall() { + return

children catch-all

+} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/children-catchall/layout.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/children-catchall/layout.tsx new file mode 100644 index 000000000000..221a062cd925 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/children-catchall/layout.tsx @@ -0,0 +1,25 @@ +import type { ReactNode } from 'react' + +/** + * `children` matches every non-empty path, but @first only matches /foo and + * @second only matches /bar. Every candidate therefore synthesizes a + * not-found default for at least one named slot, so this route emits no + * matchers when pruning is enabled. + */ +export default function ChildrenCatchallLayout({ + children, + first, + second, +}: { + children: ReactNode + first: ReactNode + second: ReactNode +}) { + return ( +
+ {children} + {first} + {second} +
+ ) +} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/children-default/@slot/[...slug]/page.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/children-default/@slot/[...slug]/page.tsx new file mode 100644 index 000000000000..d000a7b2471c --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/children-default/@slot/[...slug]/page.tsx @@ -0,0 +1,3 @@ +export default function ChildrenDefaultSlotCatchall() { + return

slot catch-all

+} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/children-default/default.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/children-default/default.tsx new file mode 100644 index 000000000000..dc7a3d3872ff --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/children-default/default.tsx @@ -0,0 +1,3 @@ +export default function ChildrenDefault() { + return

children default

+} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/children-default/layout.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/children-default/layout.tsx new file mode 100644 index 000000000000..23c4a14ffb82 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/children-default/layout.tsx @@ -0,0 +1,20 @@ +import type { ReactNode } from 'react' + +/** + * The explicit default declares coverage for the implicit children slot, so + * the named catch-all produces a complete broad matcher that must be retained. + */ +export default function ChildrenDefaultLayout({ + children, + slot, +}: { + children: ReactNode + slot: ReactNode +}) { + return ( +
+ {children} + {slot} +
+ ) +} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/client-navigation.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/client-navigation.tsx new file mode 100644 index 000000000000..b683ab65d8ef --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/client-navigation.tsx @@ -0,0 +1,17 @@ +'use client' + +import { useRouter } from 'next/navigation' + +export function ClientNavigation({ paths }: { paths: string[] }) { + const router = useRouter() + + return paths.map((path) => ( + + )) +} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/complete-catchalls/@slot/[...slug]/page.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/complete-catchalls/@slot/[...slug]/page.tsx new file mode 100644 index 000000000000..b992bf68fa97 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/complete-catchalls/@slot/[...slug]/page.tsx @@ -0,0 +1,3 @@ +export default function CompleteSlotCatchall() { + return

slot catch-all

+} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/complete-catchalls/[...slug]/page.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/complete-catchalls/[...slug]/page.tsx new file mode 100644 index 000000000000..ef0b8da25836 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/complete-catchalls/[...slug]/page.tsx @@ -0,0 +1,3 @@ +export default function CompleteChildrenCatchall() { + return

children catch-all

+} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/complete-catchalls/layout.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/complete-catchalls/layout.tsx new file mode 100644 index 000000000000..5e0080311635 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/complete-catchalls/layout.tsx @@ -0,0 +1,20 @@ +import type { ReactNode } from 'react' + +/** + * Both children and @slot cover the same catch-all, so the broad matcher can + * construct a complete loader tree and must be retained when pruning is on. + */ +export default function CompleteCatchallsLayout({ + children, + slot, +}: { + children: ReactNode + slot: ReactNode +}) { + return ( +
+ {children} + {slot} +
+ ) +} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/default-not-found/@slot/default.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/default-not-found/@slot/default.tsx new file mode 100644 index 000000000000..e2b493cc78ae --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/default-not-found/@slot/default.tsx @@ -0,0 +1,5 @@ +import { notFound } from 'next/navigation' + +export default function DefaultNotFoundSlot() { + notFound() +} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/default-not-found/[...slug]/page.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/default-not-found/[...slug]/page.tsx new file mode 100644 index 000000000000..11f35f72a080 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/default-not-found/[...slug]/page.tsx @@ -0,0 +1,3 @@ +export default function DefaultNotFoundCatchall() { + return

default not-found catch-all

+} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/default-not-found/layout.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/default-not-found/layout.tsx new file mode 100644 index 000000000000..e04aa877c586 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/default-not-found/layout.tsx @@ -0,0 +1,21 @@ +import type { ReactNode } from 'react' + +/** + * @slot has a user-authored default, so the broad children catch-all remains + * in the matcher set. That default deliberately calls notFound(), proving an + * explicit default can preserve the old perma-404 behavior. + */ +export default function DefaultNotFoundLayout({ + children, + slot, +}: { + children: ReactNode + slot: ReactNode +}) { + return ( +
+ {children} + {slot} +
+ ) +} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/layout.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/layout.tsx new file mode 100644 index 000000000000..7f3d7fc9734d --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/layout.tsx @@ -0,0 +1,17 @@ +import type { ReactNode } from 'react' + +/** + * Pruned URLs fall through to the root not-found UI without rendering any of + * the route-level layouts below. Their tests use those layout IDs to verify + * that an incomplete matcher was omitted instead of selected. + */ +export default function Root({ children }: { children: ReactNode }) { + return ( + + +
root layout
+ {children} + + + ) +} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/named-catchall/@catchall/[...slug]/page.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/named-catchall/@catchall/[...slug]/page.tsx new file mode 100644 index 000000000000..e8b677baac90 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/named-catchall/@catchall/[...slug]/page.tsx @@ -0,0 +1,3 @@ +export default function NamedCatchall() { + return

named catch-all

+} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/named-catchall/@specific/foo/page.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/named-catchall/@specific/foo/page.tsx new file mode 100644 index 000000000000..aff30a3c536a --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/named-catchall/@specific/foo/page.tsx @@ -0,0 +1,3 @@ +export default function NamedSpecific() { + return

named specific page

+} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/named-catchall/layout.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/named-catchall/layout.tsx new file mode 100644 index 000000000000..e700a63955f1 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/named-catchall/layout.tsx @@ -0,0 +1,22 @@ +import type { ReactNode } from 'react' + +/** + * This layout intentionally has no children slot. /foo is retained because + * both declared named slots match it, while the broader catch-all is pruned + * because @specific has no matching page or default for other URLs. Strict + * matching does not synthesize a children slot at this level. + */ +export default function NamedCatchallLayout({ + catchall, + specific, +}: { + catchall: ReactNode + specific: ReactNode +}) { + return ( +
+ {catchall} + {specific} +
+ ) +} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/named-only-catchalls/@left/[...slug]/page.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/named-only-catchalls/@left/[...slug]/page.tsx new file mode 100644 index 000000000000..2d14bd687e98 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/named-only-catchalls/@left/[...slug]/page.tsx @@ -0,0 +1,3 @@ +export default function NamedOnlyLeftCatchall() { + return

left catch-all

+} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/named-only-catchalls/@right/[...slug]/page.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/named-only-catchalls/@right/[...slug]/page.tsx new file mode 100644 index 000000000000..4dd3cdae9b8f --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/named-only-catchalls/@right/[...slug]/page.tsx @@ -0,0 +1,3 @@ +export default function NamedOnlyRightCatchall() { + return

right catch-all

+} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/named-only-catchalls/layout.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/named-only-catchalls/layout.tsx new file mode 100644 index 000000000000..7a8a5f8102ef --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/named-only-catchalls/layout.tsx @@ -0,0 +1,21 @@ +import type { ReactNode } from 'react' + +/** + * Both named slots cover the catch-all and this layout intentionally has no + * children slot. The broad matcher is complete, and strict matching represents + * this level using only the declared left and right slots. + */ +export default function NamedOnlyCatchallsLayout({ + left, + right, +}: { + left: ReactNode + right: ReactNode +}) { + return ( +
+ {left} + {right} +
+ ) +} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/nested-parallel/@outer/@inner/specific/page.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/nested-parallel/@outer/@inner/specific/page.tsx new file mode 100644 index 000000000000..e9acff62f716 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/nested-parallel/@outer/@inner/specific/page.tsx @@ -0,0 +1,3 @@ +export default function NestedInnerSpecificPage() { + return

nested inner specific page

+} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/nested-parallel/@outer/[...slug]/page.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/nested-parallel/@outer/[...slug]/page.tsx new file mode 100644 index 000000000000..a854d7930681 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/nested-parallel/@outer/[...slug]/page.tsx @@ -0,0 +1,3 @@ +export default function NestedOuterCatchallPage() { + return

nested outer catch-all page

+} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/nested-parallel/@outer/layout.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/nested-parallel/@outer/layout.tsx new file mode 100644 index 000000000000..f7e775967815 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/nested-parallel/@outer/layout.tsx @@ -0,0 +1,20 @@ +import type { ReactNode } from 'react' + +/** + * @inner only has a /specific page. The outer children catch-all therefore + * makes every other URL incomplete at this nested parallel-route level. + */ +export default function NestedOuterLayout({ + children, + inner, +}: { + children: ReactNode + inner: ReactNode +}) { + return ( +
+ {children} + {inner} +
+ ) +} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/nested-parallel/@outer/specific/page.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/nested-parallel/@outer/specific/page.tsx new file mode 100644 index 000000000000..eb53b54e601f --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/nested-parallel/@outer/specific/page.tsx @@ -0,0 +1,3 @@ +export default function NestedOuterSpecificPage() { + return

nested outer specific page

+} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/nested-parallel/[...slug]/page.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/nested-parallel/[...slug]/page.tsx new file mode 100644 index 000000000000..72b1947c8100 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/nested-parallel/[...slug]/page.tsx @@ -0,0 +1,3 @@ +export default function NestedCatchallPage() { + return

nested catch-all page

+} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/nested-parallel/layout.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/nested-parallel/layout.tsx new file mode 100644 index 000000000000..53f3c3611301 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/nested-parallel/layout.tsx @@ -0,0 +1,21 @@ +import type { ReactNode } from 'react' + +/** + * Both top-level slots have catch-all coverage, but @outer contains another + * parallel level that is incomplete for the broad matcher. Pruning must find + * that nested synthesized default; only /specific has a complete tree. + */ +export default function NestedParallelLayout({ + children, + outer, +}: { + children: ReactNode + outer: ReactNode +}) { + return ( +
+ {children} + {outer} +
+ ) +} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/nested-parallel/specific/page.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/nested-parallel/specific/page.tsx new file mode 100644 index 000000000000..cb9d068e6373 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/nested-parallel/specific/page.tsx @@ -0,0 +1,3 @@ +export default function NestedSpecificPage() { + return

nested specific page

+} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/not-found.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/not-found.tsx new file mode 100644 index 000000000000..cdaaff709c75 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/not-found.tsx @@ -0,0 +1,3 @@ +export default function NotFound() { + return

root not found

+} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/optional-children-catchall/@slot/specific/page.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/optional-children-catchall/@slot/specific/page.tsx new file mode 100644 index 000000000000..44bffa38e883 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/optional-children-catchall/@slot/specific/page.tsx @@ -0,0 +1,3 @@ +export default function OptionalSlotPage() { + return

optional slot page

+} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/optional-children-catchall/[[...slug]]/page.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/optional-children-catchall/[[...slug]]/page.tsx new file mode 100644 index 000000000000..a13f051dd795 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/optional-children-catchall/[[...slug]]/page.tsx @@ -0,0 +1,3 @@ +export default function OptionalCatchallPage() { + return

optional catch-all page

+} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/optional-children-catchall/layout.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/optional-children-catchall/layout.tsx new file mode 100644 index 000000000000..1198e44f00b8 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/optional-children-catchall/layout.tsx @@ -0,0 +1,19 @@ +/** + * The optional children catch-all can match the segment root or any suffix, + * while @slot only matches /specific. The broad optional matcher is pruned, + * but /specific remains because both slots have an exact page for that URL. + */ +export default function OptionalChildrenCatchallLayout({ + children, + slot, +}: { + children: React.ReactNode + slot: React.ReactNode +}) { + return ( +
+ {children} + {slot} +
+ ) +} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/optional-children-catchall/specific/page.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/optional-children-catchall/specific/page.tsx new file mode 100644 index 000000000000..334f4d947ef0 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/optional-children-catchall/specific/page.tsx @@ -0,0 +1,3 @@ +export default function OptionalSpecificPage() { + return

optional specific page

+} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/page.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/page.tsx new file mode 100644 index 000000000000..e7e4d5e0243c --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/page.tsx @@ -0,0 +1,22 @@ +import { ClientNavigation } from './client-navigation' + +const navigationPaths = [ + '/named-catchall/anything', + '/named-only-catchalls/anything', + '/children-catchall/foo', + '/children-catchall/bar', + '/optional-children-catchall', + '/optional-children-catchall/anything', + '/split-matcher/anything', + '/nested-parallel/anything', + '/grouped/anything', +] + +export default function Page() { + return ( + <> +

home

+ + + ) +} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/split-matcher/@slot/[...parts]/page.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/split-matcher/@slot/[...parts]/page.tsx new file mode 100644 index 000000000000..5518a9a025da --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/split-matcher/@slot/[...parts]/page.tsx @@ -0,0 +1,3 @@ +export default function SplitSlotCatchall() { + return

split slot catch-all

+} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/split-matcher/bar/page.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/split-matcher/bar/page.tsx new file mode 100644 index 000000000000..5d7fc7fc0133 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/split-matcher/bar/page.tsx @@ -0,0 +1,3 @@ +export default function BarPage() { + return

bar page

+} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/split-matcher/foo/page.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/split-matcher/foo/page.tsx new file mode 100644 index 000000000000..6c04c5da332a --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/split-matcher/foo/page.tsx @@ -0,0 +1,3 @@ +export default function FooPage() { + return

foo page

+} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/split-matcher/layout.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/split-matcher/layout.tsx new file mode 100644 index 000000000000..92e40236cfb1 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/split-matcher/layout.tsx @@ -0,0 +1,21 @@ +import type { ReactNode } from 'react' + +/** + * The @slot catch-all is combined with the concrete children pages for /foo + * and /bar, so those complete matchers remain. Its standalone broad matcher + * has no children page or default and is pruned. + */ +export default function SplitMatcherLayout({ + children, + slot, +}: { + children: ReactNode + slot: ReactNode +}) { + return ( +
+ {children} + {slot} +
+ ) +} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/valid/@slot/default.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/valid/@slot/default.tsx new file mode 100644 index 000000000000..56e97c6517ce --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/valid/@slot/default.tsx @@ -0,0 +1,3 @@ +export default function SlotDefault() { + return

valid slot default

+} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/valid/@slot/special/page.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/valid/@slot/special/page.tsx new file mode 100644 index 000000000000..a1f86905edfe --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/valid/@slot/special/page.tsx @@ -0,0 +1,3 @@ +export default function SlotPage() { + return

valid slot page

+} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/valid/[...slug]/page.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/valid/[...slug]/page.tsx new file mode 100644 index 000000000000..b342ce05af5f --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/valid/[...slug]/page.tsx @@ -0,0 +1,3 @@ +export default function ValidCatchall() { + return

valid catch-all

+} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/app/valid/layout.tsx b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/valid/layout.tsx new file mode 100644 index 000000000000..bf64c1f22ef7 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/app/valid/layout.tsx @@ -0,0 +1,21 @@ +import type { ReactNode } from 'react' + +/** + * This is the retained control case. @slot/default covers the broad children + * catch-all, and @slot/special covers the concrete /special matcher, so both + * matcher shapes can construct a complete loader tree. + */ +export default function ValidLayout({ + children, + slot, +}: { + children: ReactNode + slot: ReactNode +}) { + return ( +
+ {children} + {slot} +
+ ) +} diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/next.config.js b/test/e2e/app-dir/parallel-routes-pruned-matchers/next.config.js new file mode 100644 index 000000000000..ff01ea9b86a3 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/next.config.js @@ -0,0 +1,10 @@ +/** + * @type {import('next').NextConfig} + */ +const nextConfig = { + experimental: { + strictRouteMatching: true, + }, +} + +module.exports = nextConfig diff --git a/test/e2e/app-dir/parallel-routes-pruned-matchers/parallel-routes-pruned-matchers.test.ts b/test/e2e/app-dir/parallel-routes-pruned-matchers/parallel-routes-pruned-matchers.test.ts new file mode 100644 index 000000000000..fb4ad57ebac7 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-pruned-matchers/parallel-routes-pruned-matchers.test.ts @@ -0,0 +1,225 @@ +import { nextTestSetup } from 'e2e-utils' +import { createRouterAct } from 'router-act' +import cheerio from 'cheerio' +import stripAnsi from 'strip-ansi' + +const prunedRoutes: Array<[path: string, layoutId: string]> = [ + ['/named-catchall/anything', 'named-catchall-layout'], + ['/children-catchall/foo', 'children-catchall-layout'], + ['/children-catchall/bar', 'children-catchall-layout'], + ['/optional-children-catchall', 'optional-children-catchall-layout'], + ['/optional-children-catchall/anything', 'optional-children-catchall-layout'], + ['/split-matcher/anything', 'split-matcher-layout'], + ['/nested-parallel/anything', 'nested-parallel-layout'], + ['/grouped/anything', 'grouped-layout'], +] + +function getAppRoutes(cliOutput: string): string[] { + const routes: string[] = [] + let inAppRoutes = false + + for (const line of stripAnsi(cliOutput).split('\n')) { + if (line.startsWith('Route (app)')) { + inAppRoutes = true + continue + } + if (!inAppRoutes) continue + + const match = line.match(/^[┌├└] +(?:\S+ +)?(\/\S+)/) + if (match) routes.push(match[1]) + if (line.startsWith('└')) break + } + + return routes +} + +describe('parallel-routes-pruned-matchers', () => { + const { next, isNextStart } = nextTestSetup({ + files: __dirname, + }) + + it.each(prunedRoutes)( + 'omits the permanently-not-found matcher for %s', + async (path, layoutId) => { + const response = await next.fetch(path) + const $ = cheerio.load(await response.text()) + + expect(response.status).toBe(404) + expect($.root().text()).toContain('root not found') + expect($(`#${layoutId}`).length).toBe(0) + } + ) + + it.each(prunedRoutes)( + 'renders the same 404 after client navigation to %s', + async (path, layoutId) => { + let act: ReturnType + const responseStatuses: number[] = [] + const browser = await next.browser('/', { + beforePageLoad(page) { + page.on('response', (response) => { + if (new URL(response.url()).pathname === path) { + responseStatuses.push(response.status()) + } + }) + act = createRouterAct(page, { allowErrorStatusCodes: [404] }) + }, + }) + + await act!(async () => { + await browser.elementByCss(`button[data-router-push="${path}"]`).click() + }) + + await browser.waitForElementByCss('#root-not-found') + expect(await browser.elementById('root-not-found').text()).toBe( + 'root not found' + ) + expect(await browser.hasElementByCss(`#${layoutId}`)).toBe(false) + expect(responseStatuses).toContain(404) + } + ) + + it('keeps a named-only matcher when every declared slot matches', async () => { + const $ = await next.render$('/named-catchall/foo') + + expect($('#named-catchall-page').text()).toBe('named catch-all') + expect($('#named-specific-page').text()).toBe('named specific page') + }) + + it('keeps a broad matcher composed entirely from named slots', async () => { + const browser = await next.browser('/named-only-catchalls/anything') + + expect(await browser.elementById('named-only-left-catchall').text()).toBe( + 'left catch-all' + ) + expect(await browser.elementById('named-only-right-catchall').text()).toBe( + 'right catch-all' + ) + }) + + it('renders a route with only named slots after client navigation', async () => { + let act: ReturnType + const path = '/named-only-catchalls/anything' + const browser = await next.browser('/', { + beforePageLoad(page) { + act = createRouterAct(page) + }, + }) + + await act!(async () => { + await browser.elementByCss(`button[data-router-push="${path}"]`).click() + }) + + await browser.waitForElementByCss('#named-only-catchalls-layout') + expect(await browser.elementById('named-only-left-catchall').text()).toBe( + 'left catch-all' + ) + expect(await browser.elementById('named-only-right-catchall').text()).toBe( + 'right catch-all' + ) + }) + + it('keeps a named catch-all when children has an explicit default', async () => { + const $ = await next.render$('/children-default/anything') + + expect($('#children-default').text()).toBe('children default') + expect($('#children-default-slot-catchall').text()).toBe('slot catch-all') + }) + + it('keeps a catch-all matcher when every slot has catch-all coverage', async () => { + const $ = await next.render$('/complete-catchalls/anything') + + expect($('#complete-children-catchall').text()).toBe('children catch-all') + expect($('#complete-slot-catchall').text()).toBe('slot catch-all') + }) + + it('keeps a catch-all matcher when the sibling slot has a default', async () => { + const $ = await next.render$('/valid/foo') + + expect($('#valid-catchall-page').text()).toBe('valid catch-all') + expect($('#valid-slot-default').text()).toBe('valid slot default') + }) + + it('keeps a catch-all matcher when every sibling slot matches', async () => { + const $ = await next.render$('/valid/special') + + expect($('#valid-catchall-page').text()).toBe('valid catch-all') + expect($('#valid-slot-page').text()).toBe('valid slot page') + }) + + it('keeps the specific sibling of a pruned optional catch-all', async () => { + const $ = await next.render$('/optional-children-catchall/specific') + + expect($('#optional-specific-page').text()).toBe('optional specific page') + expect($('#optional-slot-page').text()).toBe('optional slot page') + }) + + it.each(['foo', 'bar'])( + 'keeps /split-matcher/%s while pruning the broader matcher', + async (segment) => { + const $ = await next.render$(`/split-matcher/${segment}`) + + expect($(`#split-${segment}-page`).text()).toBe(`${segment} page`) + expect($('#split-slot-catchall').text()).toBe('split slot catch-all') + } + ) + + it('keeps a matcher when an explicit default calls notFound', async () => { + const response = await next.fetch('/default-not-found/anything') + + expect(response.status).toBe(404) + }) + + it('prunes a matcher with an incomplete nested parallel route', async () => { + const $ = await next.render$('/nested-parallel/specific') + + expect($('#nested-specific-page').text()).toBe('nested specific page') + expect($('#nested-outer-specific-page').text()).toBe( + 'nested outer specific page' + ) + expect($('#nested-inner-specific-page').text()).toBe( + 'nested inner specific page' + ) + }) + + it('keeps the specific sibling of a pruned route-group matcher', async () => { + const $ = await next.render$('/grouped/specific') + + expect($('#grouped-specific-page').text()).toBe('grouped specific page') + expect($('#grouped-slot-page').text()).toBe('grouped slot page') + }) + + if (isNextStart) { + it('lists only retained routes in the build output', () => { + const appRoutes = getAppRoutes(next.cliOutput) + const omittedRoutes = [ + '/named-catchall/[...slug]', + '/children-catchall/[...slug]', + '/optional-children-catchall/[[...slug]]', + '/split-matcher/[...parts]', + '/nested-parallel/[...slug]', + '/grouped/[...slug]', + ] + + expect( + appRoutes.filter((route) => omittedRoutes.includes(route)) + ).toEqual([]) + expect(appRoutes).toEqual( + expect.arrayContaining([ + '/named-catchall/foo', + '/named-only-catchalls/[...slug]', + '/children-default/[...slug]', + '/complete-catchalls/[...slug]', + '/valid/[...slug]', + '/valid/special', + '/optional-children-catchall/specific', + '/split-matcher/foo', + '/split-matcher/bar', + '/default-not-found/[...slug]', + '/nested-parallel/specific', + '/grouped/specific', + ]) + ) + }) + } +}) From 1f52cb42ad25c9cb121d78025a8d0a55569d8df0 Mon Sep 17 00:00:00 2001 From: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com> Date: Thu, 27 Aug 2026 23:22:53 +0200 Subject: [PATCH 4/9] Turbopack: shorten CSS module class names (#97944) The class names were unnecessarily long. Use the same as the lightningcss default: `[hash]_[local]` which is `_` Keep the previous longer mechanism to aid in debugging in dev Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> --- crates/next-core/src/next_client/context.rs | 1 + crates/next-core/src/next_server/context.rs | 1 + .../experimental-lightningcss.test.ts | 2 +- .../css-features/css-modules-support.test.ts | 20 +++++++-------- .../css-modules/css-modules.test.ts | 20 +++++++-------- turbopack/crates/turbopack-css/src/asset.rs | 4 +++ turbopack/crates/turbopack-css/src/process.rs | 25 +++++++++++++------ ...dules_input_style_module_1ihdpzrao37m7.css | 2 +- ...t_css_css-modules_input_0ppd-98kcgm8s._.js | 2 +- ...s_css-modules_input_0ppd-98kcgm8s._.js.map | 2 +- ...snapshot_css_css_input_028qdx4s25m2y._.css | 10 ++++---- ..._snapshot_css_css_input_1f3bnvictpga4._.js | 10 ++++---- ...pshot_css_css_input_1f3bnvictpga4._.js.map | 4 +-- ...ot_css_embed-url_input_0tnpx-5gohpd6._.css | 2 +- ...hot_css_embed-url_input_12dqxgxwmwtgt._.js | 2 +- ...css_embed-url_input_12dqxgxwmwtgt._.js.map | 2 +- ...css_minification_input_0sk3hln-l5sj4._.css | 2 +- ...minification_input_0sk3hln-l5sj4._.css.map | 2 +- ..._css_minification_input_1x1qn-bq9_gdc._.js | 2 +- ..._minification_input_1x1qn-bq9_gdc._.js.map | 2 +- ...smodules_composes_input_0-092p2ojcgki._.js | 4 +-- ...ules_composes_input_0-092p2ojcgki._.js.map | 2 +- ...poses_input_index_module_0cff4_jv4ks5t.css | 4 +-- ...lative-uri-import_input_0hybzimbac5hb._.js | 4 +-- ...ve-uri-import_input_0hybzimbac5hb._.js.map | 4 +-- ...ative-uri-import_input_1e9d8db7tuswg._.css | 4 +-- turbopack/crates/turbopack/src/lib.rs | 2 ++ .../turbopack/src/module_options/mod.rs | 7 ++++++ .../module_options/module_options_context.rs | 3 +++ .../src/module_options/module_rule.rs | 3 +++ 30 files changed, 93 insertions(+), 61 deletions(-) diff --git a/crates/next-core/src/next_client/context.rs b/crates/next-core/src/next_client/context.rs index 0f04bafbc8a2..39078f553e97 100644 --- a/crates/next-core/src/next_client/context.rs +++ b/crates/next-core/src/next_client/context.rs @@ -385,6 +385,7 @@ pub async fn get_client_module_options_context( source_maps, module_css_condition: Some(module_styles_rule_condition()), lightningcss_features: *next_config.lightningcss_feature_flags().await?, + module_css_debuggable_idents: next_mode.is_development(), ..Default::default() }, static_url_tag: Some(rcstr!("client")), diff --git a/crates/next-core/src/next_server/context.rs b/crates/next-core/src/next_server/context.rs index 13e177bebacb..e0116eb344e2 100644 --- a/crates/next-core/src/next_server/context.rs +++ b/crates/next-core/src/next_server/context.rs @@ -561,6 +561,7 @@ pub async fn get_server_module_options_context( source_maps, module_css_condition: Some(module_styles_rule_condition()), lightningcss_features: *next_config.lightningcss_feature_flags().await?, + module_css_debuggable_idents: next_mode.is_development(), ..Default::default() }, follow_reexports: true, diff --git a/test/development/app-dir/experimental-lightningcss/experimental-lightningcss.test.ts b/test/development/app-dir/experimental-lightningcss/experimental-lightningcss.test.ts index c22cb2ae6bdb..e8b3d2f43b3e 100644 --- a/test/development/app-dir/experimental-lightningcss/experimental-lightningcss.test.ts +++ b/test/development/app-dir/experimental-lightningcss/experimental-lightningcss.test.ts @@ -17,7 +17,7 @@ describe('experimental-lightningcss with default mode', () => { // lightningcss produces different class names in turbo mode if (isTurbopack) { - // swc_css does not include `-module` in the class name, while lightningcss does. + // Turbopack keeps the filename in CSS Module class names during development. expect($('p').attr('class')).toBe( 'search-keyword style-module__hlQ3RG__blue' ) diff --git a/test/production/css-features/css-modules-support.test.ts b/test/production/css-features/css-modules-support.test.ts index f711d53047f2..fdd15310c82f 100644 --- a/test/production/css-features/css-modules-support.test.ts +++ b/test/production/css-features/css-modules-support.test.ts @@ -21,7 +21,7 @@ describe('Basic CSS Module Support', () => { if (isTurbopack) { expect( cssContent.replace(/\/\*.*?\*\//g, '').trim() - ).toMatchInlineSnapshot(`".index-module__KWKY6G__redText{color:red}"`) + ).toMatchInlineSnapshot(`".KWKY6G_redText{color:red}"`) } else { expect( cssContent.replace(/\/\*.*?\*\//g, '').trim() @@ -46,7 +46,7 @@ describe('Basic CSS Module Support', () => { if (isTurbopack) { expect($('#verify-red').attr('class')).toMatchInlineSnapshot( - `"index-module__KWKY6G__redText"` + `"KWKY6G_redText"` ) } else { expect($('#verify-red').attr('class')).toMatchInlineSnapshot( @@ -73,7 +73,7 @@ describe('3rd Party CSS Module Support', () => { expect( cssContent.replace(/\/\*.*?\*\//g, '').trim() ).toMatchInlineSnapshot( - `".index-module__KWKY6G__foo{position:relative}:is(.index-module__KWKY6G__foo .bar,.index-module__KWKY6G__foo .baz){height:100%;overflow:hidden}.index-module__KWKY6G__foo .lol{width:80%}.index-module__KWKY6G__foo>.lel{width:80%}"` + `".KWKY6G_foo{position:relative}:is(.KWKY6G_foo .bar,.KWKY6G_foo .baz){height:100%;overflow:hidden}.KWKY6G_foo .lol{width:80%}.KWKY6G_foo>.lel{width:80%}"` ) } else { expect( @@ -101,7 +101,7 @@ describe('3rd Party CSS Module Support', () => { if (isTurbopack) { expect($('#verify-div').attr('class')).toMatchInlineSnapshot( - `"index-module__KWKY6G__foo"` + `"KWKY6G_foo"` ) } else { expect($('#verify-div').attr('class')).toMatchInlineSnapshot( @@ -137,7 +137,7 @@ describe('Valid CSS Module Usage from within node_modules', () => { const cssPreload = $('#nm-div') if (isTurbopack) { expect(cssPreload.text()).toMatchInlineSnapshot( - `"{"message":"Why hello there","default":{"message":"Why hello there"}} {"redText":"index-module__PIKFPa__redText","default":{"redText":"index-module__PIKFPa__redText"}}"` + `"{"message":"Why hello there","default":{"message":"Why hello there"}} {"redText":"PIKFPa_redText","default":{"redText":"PIKFPa_redText"}}"` ) } else { expect(cssPreload.text()).toMatchInlineSnapshot( @@ -158,7 +158,7 @@ describe('Valid CSS Module Usage from within node_modules', () => { if (isTurbopack) { expect( cssContent.replace(/\/\*.*?\*\//g, '').trim() - ).toMatchInlineSnapshot(`".index-module__PIKFPa__redText{color:red}"`) + ).toMatchInlineSnapshot(`".PIKFPa_redText{color:red}"`) } else { expect( cssContent.replace(/\/\*.*?\*\//g, '').trim() @@ -220,7 +220,7 @@ describe('CSS Module Composes Usage (Basic)', () => { expect( cssContent.replace(/\/\*.*?\*\//g, '').trim() ).toMatchInlineSnapshot( - `".index-module__KWKY6G__className{color:#ff0;background:red}.index-module__KWKY6G__subClass{background:#00f;}"` + `".KWKY6G_className{color:#ff0;background:red}.KWKY6G_subClass{background:#00f;}"` ) } else { expect( @@ -282,7 +282,7 @@ describe('Dynamic Route CSS Module Usage', () => { if (isTurbopack) { expect( cssContent.replace(/\/\*.*?\*\//g, '').trim() - ).toMatchInlineSnapshot(`".index-module__9fTRMq__home{background:red}"`) + ).toMatchInlineSnapshot(`"._9fTRMq_home{background:red}"`) } else { expect( cssContent.replace(/\/\*.*?\*\//g, '').trim() @@ -320,8 +320,8 @@ describe('Catch-all Route CSS Module Usage', () => { if (isTurbopack) { expect(cssContent.replace(/\/\*.*?\*\/\n?/g, '').trim()) .toMatchInlineSnapshot(` - ".index-module__vy7_gG__home{background:red} - .\\35 5css-module__c9Qeqa__home{color:green}" + ".vy7_gG_home{background:red} + .c9Qeqa_home{color:green}" `) } else { expect( diff --git a/test/production/css-modules/css-modules.test.ts b/test/production/css-modules/css-modules.test.ts index ba76d17fb0b5..bffc28ad80ef 100644 --- a/test/production/css-modules/css-modules.test.ts +++ b/test/production/css-modules/css-modules.test.ts @@ -28,7 +28,7 @@ describe('CSS Modules Production', () => { if (isTurbopack) { expect( cssContent.replace(/\/\*.*?\*\//g, '').trim() - ).toMatchInlineSnapshot(`".index-module__KWKY6G__redText{color:red}"`) + ).toMatchInlineSnapshot(`".KWKY6G_redText{color:red}"`) } else { expect( cssContent.replace(/\/\*.*?\*\//g, '').trim() @@ -53,7 +53,7 @@ describe('CSS Modules Production', () => { if (isTurbopack) { expect($('#verify-red').attr('class')).toMatchInlineSnapshot( - `"index-module__KWKY6G__redText"` + `"KWKY6G_redText"` ) } else { expect($('#verify-red').attr('class')).toMatchInlineSnapshot( @@ -89,7 +89,7 @@ describe('CSS Modules Production', () => { expect( cssContent.replace(/\/\*.*?\*\//g, '').trim() ).toMatchInlineSnapshot( - `".index-module__KWKY6G__foo{position:relative}:is(.index-module__KWKY6G__foo .bar,.index-module__KWKY6G__foo .baz){height:100%;overflow:hidden}.index-module__KWKY6G__foo .lol{width:80%}.index-module__KWKY6G__foo>.lel{width:80%}"` + `".KWKY6G_foo{position:relative}:is(.KWKY6G_foo .bar,.KWKY6G_foo .baz){height:100%;overflow:hidden}.KWKY6G_foo .lol{width:80%}.KWKY6G_foo>.lel{width:80%}"` ) } else { expect( @@ -117,7 +117,7 @@ describe('CSS Modules Production', () => { if (isTurbopack) { expect($('#verify-div').attr('class')).toMatchInlineSnapshot( - `"index-module__KWKY6G__foo"` + `"KWKY6G_foo"` ) } else { expect($('#verify-div').attr('class')).toMatchInlineSnapshot( @@ -203,7 +203,7 @@ describe('CSS Modules Production', () => { const cssPreload = $('#nm-div') if (isTurbopack) { expect(cssPreload.text()).toMatchInlineSnapshot( - `"{"message":"Why hello there","default":{"message":"Why hello there"}} {"redText":"index-module__PIKFPa__redText","default":{"redText":"index-module__PIKFPa__redText"}}"` + `"{"message":"Why hello there","default":{"message":"Why hello there"}} {"redText":"PIKFPa_redText","default":{"redText":"PIKFPa_redText"}}"` ) } else { expect(cssPreload.text()).toMatchInlineSnapshot( @@ -224,7 +224,7 @@ describe('CSS Modules Production', () => { if (isTurbopack) { expect( cssContent.replace(/\/\*.*?\*\//g, '').trim() - ).toMatchInlineSnapshot(`".index-module__PIKFPa__redText{color:red}"`) + ).toMatchInlineSnapshot(`".PIKFPa_redText{color:red}"`) } else { expect( cssContent.replace(/\/\*.*?\*\//g, '').trim() @@ -310,7 +310,7 @@ describe('CSS Modules Production', () => { expect( cssContent.replace(/\/\*.*?\*\//g, '').trim() ).toMatchInlineSnapshot( - `".index-module__KWKY6G__className{color:#ff0;background:red}.index-module__KWKY6G__subClass{background:#00f;}"` + `".KWKY6G_className{color:#ff0;background:red}.KWKY6G_subClass{background:#00f;}"` ) } else { expect( @@ -400,7 +400,7 @@ describe('CSS Modules Production', () => { if (isTurbopack) { expect( cssContent.replace(/\/\*.*?\*\//g, '').trim() - ).toMatchInlineSnapshot(`".index-module__9fTRMq__home{background:red}"`) + ).toMatchInlineSnapshot(`"._9fTRMq_home{background:red}"`) } else { expect( cssContent.replace(/\/\*.*?\*\//g, '').trim() @@ -447,8 +447,8 @@ describe('CSS Modules Production', () => { if (isTurbopack) { expect(cssContent.replace(/\/\*.*?\*\/\n?/g, '').trim()) .toMatchInlineSnapshot(` - ".index-module__vy7_gG__home{background:red} - .\\35 5css-module__c9Qeqa__home{color:green}" + ".vy7_gG_home{background:red} + .c9Qeqa_home{color:green}" `) } else { expect( diff --git a/turbopack/crates/turbopack-css/src/asset.rs b/turbopack/crates/turbopack-css/src/asset.rs index 844a14615c79..8656845223bf 100644 --- a/turbopack/crates/turbopack-css/src/asset.rs +++ b/turbopack/crates/turbopack-css/src/asset.rs @@ -43,6 +43,7 @@ pub struct CssModule { ty: CssModuleType, environment: Option>, lightningcss_features: LightningCssFeatureFlags, + module_css_debuggable_idents: bool, /// The path of `source`, precomputed so that `ResolveOrigin::origin_path` is synchronous. origin_path: FileSystemPath, } @@ -58,6 +59,7 @@ impl CssModule { import_context: Option>, environment: Option>, lightningcss_features: LightningCssFeatureFlags, + module_css_debuggable_idents: bool, ) -> Result> { Ok(Self::cell(CssModule { origin_path: source.ident().await?.path.clone(), @@ -67,6 +69,7 @@ impl CssModule { ty, environment, lightningcss_features, + module_css_debuggable_idents, })) } @@ -90,6 +93,7 @@ impl ParseCss for CssModule { this.ty, this.environment.as_deref().copied(), this.lightningcss_features, + this.module_css_debuggable_idents, )) } } diff --git a/turbopack/crates/turbopack-css/src/process.rs b/turbopack/crates/turbopack-css/src/process.rs index f581126ac764..c463226728fa 100644 --- a/turbopack/crates/turbopack-css/src/process.rs +++ b/turbopack/crates/turbopack-css/src/process.rs @@ -369,6 +369,7 @@ pub async fn parse_css( ty: CssModuleType, environment: Option>, feature_flags: LightningCssFeatureFlags, + module_css_debuggable_idents: bool, ) -> Result> { let span = tracing::info_span!( "parse css", @@ -394,6 +395,7 @@ pub async fn parse_css( ty, environment, feature_flags, + module_css_debuggable_idents, ) .await? } @@ -466,6 +468,7 @@ async fn process_content( ty: CssModuleType, environment: Option>, feature_flags: LightningCssFeatureFlags, + module_css_debuggable_idents: bool, ) -> Result> { #[allow(clippy::needless_lifetimes)] fn without_warnings<'i>(config: ParserOptions<'i>) -> ParserOptions<'static> { @@ -497,13 +500,21 @@ async fn process_content( css_modules: match ty { CssModuleType::Module => Some(lightningcss::css_modules::Config { pattern: Pattern { - segments: smallvec![ - Segment::Name, - Segment::Literal(Cow::Borrowed("__")), - Segment::Hash, - Segment::Literal(Cow::Borrowed("__")), - Segment::Local, - ], + segments: if module_css_debuggable_idents { + smallvec![ + Segment::Name, + Segment::Literal(Cow::Borrowed("__")), + Segment::Hash, + Segment::Literal(Cow::Borrowed("__")), + Segment::Local, + ] + } else { + smallvec![ + Segment::Hash, + Segment::Literal(Cow::Borrowed("_")), + Segment::Local, + ] + }, }, dashed_idents: false, grid: false, diff --git a/turbopack/crates/turbopack-tests/tests/snapshot/css/css-modules/output/0_9x_turbopack-tests_tests_snapshot_css_css-modules_input_style_module_1ihdpzrao37m7.css b/turbopack/crates/turbopack-tests/tests/snapshot/css/css-modules/output/0_9x_turbopack-tests_tests_snapshot_css_css-modules_input_style_module_1ihdpzrao37m7.css index f0bc42f934e3..de2f3ae41870 100644 --- a/turbopack/crates/turbopack-tests/tests/snapshot/css/css-modules/output/0_9x_turbopack-tests_tests_snapshot_css_css-modules_input_style_module_1ihdpzrao37m7.css +++ b/turbopack/crates/turbopack-tests/tests/snapshot/css/css-modules/output/0_9x_turbopack-tests_tests_snapshot_css_css-modules_input_style_module_1ihdpzrao37m7.css @@ -1,5 +1,5 @@ /* [project]/turbopack/crates/turbopack-tests/tests/snapshot/css/css-modules/input/style.module.css [test] (css) */ -.style-module__cu3fEW__module-style { +.cu3fEW_module-style { grid-template-areas: "checkbox avatar content actions menu"; } diff --git a/turbopack/crates/turbopack-tests/tests/snapshot/css/css-modules/output/1do3_crates_turbopack-tests_tests_snapshot_css_css-modules_input_0ppd-98kcgm8s._.js b/turbopack/crates/turbopack-tests/tests/snapshot/css/css-modules/output/1do3_crates_turbopack-tests_tests_snapshot_css_css-modules_input_0ppd-98kcgm8s._.js index 40b51334399f..1aa850023d76 100644 --- a/turbopack/crates/turbopack-tests/tests/snapshot/css/css-modules/output/1do3_crates_turbopack-tests_tests_snapshot_css_css-modules_input_0ppd-98kcgm8s._.js +++ b/turbopack/crates/turbopack-tests/tests/snapshot/css/css-modules/output/1do3_crates_turbopack-tests_tests_snapshot_css_css-modules_input_0ppd-98kcgm8s._.js @@ -10,7 +10,7 @@ console.log(__TURBOPACK__imported__module__$5b$project$5d2f$turbopack$2f$crates$ "[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/css-modules/input/style.module.css [test] (css module)", ((__turbopack_context__) => { __turbopack_context__.v({ - "module-style": "style-module__cu3fEW__module-style", + "module-style": "cu3fEW_module-style", }); }), ]); diff --git a/turbopack/crates/turbopack-tests/tests/snapshot/css/css-modules/output/1do3_crates_turbopack-tests_tests_snapshot_css_css-modules_input_0ppd-98kcgm8s._.js.map b/turbopack/crates/turbopack-tests/tests/snapshot/css/css-modules/output/1do3_crates_turbopack-tests_tests_snapshot_css_css-modules_input_0ppd-98kcgm8s._.js.map index 0977d9d8f294..684c77d667b8 100644 --- a/turbopack/crates/turbopack-tests/tests/snapshot/css/css-modules/output/1do3_crates_turbopack-tests_tests_snapshot_css_css-modules_input_0ppd-98kcgm8s._.js.map +++ b/turbopack/crates/turbopack-tests/tests/snapshot/css/css-modules/output/1do3_crates_turbopack-tests_tests_snapshot_css_css-modules_input_0ppd-98kcgm8s._.js.map @@ -3,5 +3,5 @@ "sources": [], "sections": [ {"offset": {"line": 4, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/css-modules/input/index.js"],"sourcesContent":["import style from './style.module.css'\n\nconsole.log(style, import('./style.module.css'))\n"],"names":["console","log"],"mappings":";AAAA;;AAEAA,QAAQC,GAAG,CAAC,0NAAK"}}, - {"offset": {"line": 11, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/css-modules/input/style.module.css [test] (css module)"],"sourcesContent":["__turbopack_context__.v({\n \"module-style\": \"style-module__cu3fEW__module-style\",\n});\n"],"names":[],"mappings":"AAAA;AACA;AACA"}}] + {"offset": {"line": 11, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/css-modules/input/style.module.css [test] (css module)"],"sourcesContent":["__turbopack_context__.v({\n \"module-style\": \"cu3fEW_module-style\",\n});\n"],"names":[],"mappings":"AAAA;AACA;AACA"}}] } \ No newline at end of file diff --git a/turbopack/crates/turbopack-tests/tests/snapshot/css/css/output/turbopack_crates_turbopack-tests_tests_snapshot_css_css_input_028qdx4s25m2y._.css b/turbopack/crates/turbopack-tests/tests/snapshot/css/css/output/turbopack_crates_turbopack-tests_tests_snapshot_css_css_input_028qdx4s25m2y._.css index 45d1b1a9089e..0ac7e35ce9ed 100644 --- a/turbopack/crates/turbopack-tests/tests/snapshot/css/css/output/turbopack_crates_turbopack-tests_tests_snapshot_css_css_input_028qdx4s25m2y._.css +++ b/turbopack/crates/turbopack-tests/tests/snapshot/css/css/output/turbopack_crates_turbopack-tests_tests_snapshot_css_css_input_028qdx4s25m2y._.css @@ -24,24 +24,24 @@ } /* [project]/turbopack/crates/turbopack-tests/tests/snapshot/css/css/input/node_modules/foo/style.module.css [test] (css) */ -.style-module__CEkn7G__foo-module-style { +.CEkn7G_foo-module-style { color: #00f; } /* [project]/turbopack/crates/turbopack-tests/tests/snapshot/css/css/input/style.module.css [test] (css) */ -.style-module__Iu_hLa__module-style { +.Iu_hLa_module-style { color: #f0f; } -.style-module__Iu_hLa__module-style > h1, .style-module__Iu_hLa__module-style + .style-module__Iu_hLa__inner { +.Iu_hLa_module-style > h1, .Iu_hLa_module-style + .Iu_hLa_inner { background: purple; } -.style-module__Iu_hLa__composed-module-style { +.Iu_hLa_composed-module-style { color: green; } -.style-module__Iu_hLa__another-composed-module-style { +.Iu_hLa_another-composed-module-style { color: #ff0; } diff --git a/turbopack/crates/turbopack-tests/tests/snapshot/css/css/output/turbopack_crates_turbopack-tests_tests_snapshot_css_css_input_1f3bnvictpga4._.js b/turbopack/crates/turbopack-tests/tests/snapshot/css/css/output/turbopack_crates_turbopack-tests_tests_snapshot_css_css_input_1f3bnvictpga4._.js index 46e80ad6430d..f91b77b4aa9f 100644 --- a/turbopack/crates/turbopack-tests/tests/snapshot/css/css/output/turbopack_crates_turbopack-tests_tests_snapshot_css_css_input_1f3bnvictpga4._.js +++ b/turbopack/crates/turbopack-tests/tests/snapshot/css/css/output/turbopack_crates_turbopack-tests_tests_snapshot_css_css_input_1f3bnvictpga4._.js @@ -15,16 +15,16 @@ console.log(__TURBOPACK__imported__module__$5b$project$5d2f$turbopack$2f$crates$ "[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/css/input/node_modules/foo/style.module.css [test] (css module)", ((__turbopack_context__) => { __turbopack_context__.v({ - "foo-module-style": "style-module__CEkn7G__foo-module-style", + "foo-module-style": "CEkn7G_foo-module-style", }); }), "[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/css/input/style.module.css [test] (css module)", ((__turbopack_context__) => { __turbopack_context__.v({ - "another-composed-module-style": "style-module__Iu_hLa__another-composed-module-style" + " " + __turbopack_context__.i("[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/css/input/node_modules/foo/style.module.css [test] (css module)")["foo-module-style"], - "composed-module-style": "style-module__Iu_hLa__composed-module-style" + " " + __turbopack_context__.i("[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/css/input/node_modules/foo/style.module.css [test] (css module)")["foo-module-style"], - "inner": "style-module__Iu_hLa__inner", - "module-style": "style-module__Iu_hLa__module-style", + "another-composed-module-style": "Iu_hLa_another-composed-module-style" + " " + __turbopack_context__.i("[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/css/input/node_modules/foo/style.module.css [test] (css module)")["foo-module-style"], + "composed-module-style": "Iu_hLa_composed-module-style" + " " + __turbopack_context__.i("[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/css/input/node_modules/foo/style.module.css [test] (css module)")["foo-module-style"], + "inner": "Iu_hLa_inner", + "module-style": "Iu_hLa_module-style", }); }), ]); diff --git a/turbopack/crates/turbopack-tests/tests/snapshot/css/css/output/turbopack_crates_turbopack-tests_tests_snapshot_css_css_input_1f3bnvictpga4._.js.map b/turbopack/crates/turbopack-tests/tests/snapshot/css/css/output/turbopack_crates_turbopack-tests_tests_snapshot_css_css_input_1f3bnvictpga4._.js.map index ad3437191732..74daaeff2d3b 100644 --- a/turbopack/crates/turbopack-tests/tests/snapshot/css/css/output/turbopack_crates_turbopack-tests_tests_snapshot_css_css_input_1f3bnvictpga4._.js.map +++ b/turbopack/crates/turbopack-tests/tests/snapshot/css/css/output/turbopack_crates_turbopack-tests_tests_snapshot_css_css_input_1f3bnvictpga4._.js.map @@ -3,6 +3,6 @@ "sources": [], "sections": [ {"offset": {"line": 4, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/css/input/index.js"],"sourcesContent":["import 'foo/style.css'\nimport 'foo'\nimport './style.css'\nimport fooStyle from 'foo/style.module.css'\nimport style from './style.module.css'\n\nconsole.log(style, fooStyle, import('foo'))\n"],"names":["console","log"],"mappings":";AAGA;AACA;;;;;;AAEAA,QAAQC,GAAG,CAAC,+MAAK,EAAE,sOAAQ"}}, - {"offset": {"line": 16, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/css/input/node_modules/foo/style.module.css [test] (css module)"],"sourcesContent":["__turbopack_context__.v({\n \"foo-module-style\": \"style-module__CEkn7G__foo-module-style\",\n});\n"],"names":[],"mappings":"AAAA;AACA;AACA","ignoreList":[0]}}, - {"offset": {"line": 22, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/css/input/style.module.css [test] (css module)"],"sourcesContent":["__turbopack_context__.v({\n \"another-composed-module-style\": \"style-module__Iu_hLa__another-composed-module-style\" + \" \" + __turbopack_context__.i(\"[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/css/input/node_modules/foo/style.module.css [test] (css module)\")[\"foo-module-style\"],\n \"composed-module-style\": \"style-module__Iu_hLa__composed-module-style\" + \" \" + __turbopack_context__.i(\"[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/css/input/node_modules/foo/style.module.css [test] (css module)\")[\"foo-module-style\"],\n \"inner\": \"style-module__Iu_hLa__inner\",\n \"module-style\": \"style-module__Iu_hLa__module-style\",\n});\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA"}}] + {"offset": {"line": 16, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/css/input/node_modules/foo/style.module.css [test] (css module)"],"sourcesContent":["__turbopack_context__.v({\n \"foo-module-style\": \"CEkn7G_foo-module-style\",\n});\n"],"names":[],"mappings":"AAAA;AACA;AACA","ignoreList":[0]}}, + {"offset": {"line": 22, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/css/input/style.module.css [test] (css module)"],"sourcesContent":["__turbopack_context__.v({\n \"another-composed-module-style\": \"Iu_hLa_another-composed-module-style\" + \" \" + __turbopack_context__.i(\"[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/css/input/node_modules/foo/style.module.css [test] (css module)\")[\"foo-module-style\"],\n \"composed-module-style\": \"Iu_hLa_composed-module-style\" + \" \" + __turbopack_context__.i(\"[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/css/input/node_modules/foo/style.module.css [test] (css module)\")[\"foo-module-style\"],\n \"inner\": \"Iu_hLa_inner\",\n \"module-style\": \"Iu_hLa_module-style\",\n});\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA"}}] } \ No newline at end of file diff --git a/turbopack/crates/turbopack-tests/tests/snapshot/css/embed-url/output/1do3_crates_turbopack-tests_tests_snapshot_css_embed-url_input_0tnpx-5gohpd6._.css b/turbopack/crates/turbopack-tests/tests/snapshot/css/embed-url/output/1do3_crates_turbopack-tests_tests_snapshot_css_embed-url_input_0tnpx-5gohpd6._.css index aa5154313e74..9f18d09d2876 100644 --- a/turbopack/crates/turbopack-tests/tests/snapshot/css/embed-url/output/1do3_crates_turbopack-tests_tests_snapshot_css_embed-url_input_0tnpx-5gohpd6._.css +++ b/turbopack/crates/turbopack-tests/tests/snapshot/css/embed-url/output/1do3_crates_turbopack-tests_tests_snapshot_css_embed-url_input_0tnpx-5gohpd6._.css @@ -1,5 +1,5 @@ /* [project]/turbopack/crates/turbopack-tests/tests/snapshot/css/embed-url/input/style.module.css [test] (css) */ -.style-module__3oVw9q__module-style { +._3oVw9q_module-style { cursor: url("../static/image.3mjyqwrpci25r.png"); background-image: url("../static/image.3mjyqwrpci25r.png"); list-style-image: url("../static/image.3mjyqwrpci25r.png"); diff --git a/turbopack/crates/turbopack-tests/tests/snapshot/css/embed-url/output/1do3_crates_turbopack-tests_tests_snapshot_css_embed-url_input_12dqxgxwmwtgt._.js b/turbopack/crates/turbopack-tests/tests/snapshot/css/embed-url/output/1do3_crates_turbopack-tests_tests_snapshot_css_embed-url_input_12dqxgxwmwtgt._.js index 91c3f106febd..4cacc3c4f65f 100644 --- a/turbopack/crates/turbopack-tests/tests/snapshot/css/embed-url/output/1do3_crates_turbopack-tests_tests_snapshot_css_embed-url_input_12dqxgxwmwtgt._.js +++ b/turbopack/crates/turbopack-tests/tests/snapshot/css/embed-url/output/1do3_crates_turbopack-tests_tests_snapshot_css_embed-url_input_12dqxgxwmwtgt._.js @@ -11,7 +11,7 @@ console.log(__TURBOPACK__imported__module__$5b$project$5d2f$turbopack$2f$crates$ "[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/embed-url/input/style.module.css [test] (css module)", ((__turbopack_context__) => { __turbopack_context__.v({ - "module-style": "style-module__3oVw9q__module-style", + "module-style": "_3oVw9q_module-style", }); }), ]); diff --git a/turbopack/crates/turbopack-tests/tests/snapshot/css/embed-url/output/1do3_crates_turbopack-tests_tests_snapshot_css_embed-url_input_12dqxgxwmwtgt._.js.map b/turbopack/crates/turbopack-tests/tests/snapshot/css/embed-url/output/1do3_crates_turbopack-tests_tests_snapshot_css_embed-url_input_12dqxgxwmwtgt._.js.map index 6d8cdf4c98b8..71892590ac1e 100644 --- a/turbopack/crates/turbopack-tests/tests/snapshot/css/embed-url/output/1do3_crates_turbopack-tests_tests_snapshot_css_embed-url_input_12dqxgxwmwtgt._.js.map +++ b/turbopack/crates/turbopack-tests/tests/snapshot/css/embed-url/output/1do3_crates_turbopack-tests_tests_snapshot_css_embed-url_input_12dqxgxwmwtgt._.js.map @@ -3,5 +3,5 @@ "sources": [], "sections": [ {"offset": {"line": 4, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/embed-url/input/index.js"],"sourcesContent":["import style from './style.module.css'\nimport './style.css'\n\nconsole.log(style, import('./style.module.css'), import('./style.css'))\n"],"names":["console","log"],"mappings":";AAAA;;;AAGAA,QAAQC,GAAG,CAAC,wNAAK"}}, - {"offset": {"line": 12, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/embed-url/input/style.module.css [test] (css module)"],"sourcesContent":["__turbopack_context__.v({\n \"module-style\": \"style-module__3oVw9q__module-style\",\n});\n"],"names":[],"mappings":"AAAA;AACA;AACA"}}] + {"offset": {"line": 12, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/embed-url/input/style.module.css [test] (css module)"],"sourcesContent":["__turbopack_context__.v({\n \"module-style\": \"_3oVw9q_module-style\",\n});\n"],"names":[],"mappings":"AAAA;AACA;AACA"}}] } \ No newline at end of file diff --git a/turbopack/crates/turbopack-tests/tests/snapshot/css/minification/output/1do3_crates_turbopack-tests_tests_snapshot_css_minification_input_0sk3hln-l5sj4._.css b/turbopack/crates/turbopack-tests/tests/snapshot/css/minification/output/1do3_crates_turbopack-tests_tests_snapshot_css_minification_input_0sk3hln-l5sj4._.css index b92788728dd1..8ab8ca8b1420 100644 --- a/turbopack/crates/turbopack-tests/tests/snapshot/css/minification/output/1do3_crates_turbopack-tests_tests_snapshot_css_minification_input_0sk3hln-l5sj4._.css +++ b/turbopack/crates/turbopack-tests/tests/snapshot/css/minification/output/1do3_crates_turbopack-tests_tests_snapshot_css_minification_input_0sk3hln-l5sj4._.css @@ -1,3 +1,3 @@ .style{color:#ff0} -.style-module__1v6dfa__module-style{color:#f0f}.style-module__1v6dfa__module-style>h1,.style-module__1v6dfa__module-style+.style-module__1v6dfa__inner{background:purple} +._1v6dfa_module-style{color:#f0f}._1v6dfa_module-style>h1,._1v6dfa_module-style+._1v6dfa_inner{background:purple} /*# sourceMappingURL=1do3_crates_turbopack-tests_tests_snapshot_css_minification_input_0sk3hln-l5sj4._.css.map*/ \ No newline at end of file diff --git a/turbopack/crates/turbopack-tests/tests/snapshot/css/minification/output/1do3_crates_turbopack-tests_tests_snapshot_css_minification_input_0sk3hln-l5sj4._.css.map b/turbopack/crates/turbopack-tests/tests/snapshot/css/minification/output/1do3_crates_turbopack-tests_tests_snapshot_css_minification_input_0sk3hln-l5sj4._.css.map index e2937aaf42c0..5e590f844819 100644 --- a/turbopack/crates/turbopack-tests/tests/snapshot/css/minification/output/1do3_crates_turbopack-tests_tests_snapshot_css_minification_input_0sk3hln-l5sj4._.css.map +++ b/turbopack/crates/turbopack-tests/tests/snapshot/css/minification/output/1do3_crates_turbopack-tests_tests_snapshot_css_minification_input_0sk3hln-l5sj4._.css.map @@ -3,5 +3,5 @@ "sources": [], "sections": [ {"offset": {"line": 0, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/minification/input/style.css"],"sourcesContent":[".style {\n color: yellow;\n}\n"],"names":[],"mappings":"AAAA"}}, - {"offset": {"line": 1, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/minification/input/style.module.css"],"sourcesContent":[".module-style {\n color: magenta;\n > h1,\n + .inner {\n background: purple;\n }\n}\n"],"names":[],"mappings":"AAAA,+CAEE"}}] + {"offset": {"line": 1, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/minification/input/style.module.css"],"sourcesContent":[".module-style {\n color: magenta;\n > h1,\n + .inner {\n background: purple;\n }\n}\n"],"names":[],"mappings":"AAAA,iCAEE"}}] } \ No newline at end of file diff --git a/turbopack/crates/turbopack-tests/tests/snapshot/css/minification/output/1do3_crates_turbopack-tests_tests_snapshot_css_minification_input_1x1qn-bq9_gdc._.js b/turbopack/crates/turbopack-tests/tests/snapshot/css/minification/output/1do3_crates_turbopack-tests_tests_snapshot_css_minification_input_1x1qn-bq9_gdc._.js index 7296a38f6028..06b4e7ab6a14 100644 --- a/turbopack/crates/turbopack-tests/tests/snapshot/css/minification/output/1do3_crates_turbopack-tests_tests_snapshot_css_minification_input_1x1qn-bq9_gdc._.js +++ b/turbopack/crates/turbopack-tests/tests/snapshot/css/minification/output/1do3_crates_turbopack-tests_tests_snapshot_css_minification_input_1x1qn-bq9_gdc._.js @@ -1,3 +1,3 @@ -(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["output/1do3_crates_turbopack-tests_tests_snapshot_css_minification_input_1x1qn-bq9_gdc._.js","[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/minification/input/index.js [test] (ecmascript)",s=>{"use strict";s.s([]),console.log(s.i("[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/minification/input/style.module.css [test] (css module)").default)},"[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/minification/input/style.module.css [test] (css module)",s=>{s.v({inner:"style-module__1v6dfa__inner","module-style":"style-module__1v6dfa__module-style"})}]); +(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["output/1do3_crates_turbopack-tests_tests_snapshot_css_minification_input_1x1qn-bq9_gdc._.js","[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/minification/input/index.js [test] (ecmascript)",s=>{"use strict";s.s([]),console.log(s.i("[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/minification/input/style.module.css [test] (css module)").default)},"[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/minification/input/style.module.css [test] (css module)",s=>{s.v({inner:"_1v6dfa_inner","module-style":"_1v6dfa_module-style"})}]); //# sourceMappingURL=1do3_crates_turbopack-tests_tests_snapshot_css_minification_input_1x1qn-bq9_gdc._.js.map \ No newline at end of file diff --git a/turbopack/crates/turbopack-tests/tests/snapshot/css/minification/output/1do3_crates_turbopack-tests_tests_snapshot_css_minification_input_1x1qn-bq9_gdc._.js.map b/turbopack/crates/turbopack-tests/tests/snapshot/css/minification/output/1do3_crates_turbopack-tests_tests_snapshot_css_minification_input_1x1qn-bq9_gdc._.js.map index 5f889c4f6b8f..fafef36cc5b9 100644 --- a/turbopack/crates/turbopack-tests/tests/snapshot/css/minification/output/1do3_crates_turbopack-tests_tests_snapshot_css_minification_input_1x1qn-bq9_gdc._.js.map +++ b/turbopack/crates/turbopack-tests/tests/snapshot/css/minification/output/1do3_crates_turbopack-tests_tests_snapshot_css_minification_input_1x1qn-bq9_gdc._.js.map @@ -1 +1 @@ -{"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/minification/input/index.js","turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/minification/input/style.module.css [test] (css module)"],"sourcesContent":["import './style.css'\nimport style from './style.module.css'\n\nconsole.log(style)\n","__turbopack_context__.v({\n \"inner\": \"style-module__1v6dfa__inner\",\n \"module-style\": \"style-module__1v6dfa__module-style\",\n});\n"],"names":["console","log"],"mappings":"+RAGAA,QAAQC,GAAG,CAFX,AAEY,EAFZ,CAAA,CAAA,yHAEY,OAAK,+HCHjB,EAAA,CAAA,CAAA,CACA,MAAA,8BACA,eAAA,oCACA"} \ No newline at end of file +{"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/minification/input/index.js","turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/css/minification/input/style.module.css [test] (css module)"],"sourcesContent":["import './style.css'\nimport style from './style.module.css'\n\nconsole.log(style)\n","__turbopack_context__.v({\n \"inner\": \"_1v6dfa_inner\",\n \"module-style\": \"_1v6dfa_module-style\",\n});\n"],"names":["console","log"],"mappings":"+RAGAA,QAAQC,GAAG,CAFX,AAEY,EAFZ,CAAA,CAAA,yHAEY,OAAK,+HCHjB,EAAA,CAAA,CAAA,CACA,MAAA,gBACA,eAAA,sBACA"} \ No newline at end of file diff --git a/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/composes/output/1do3_crates_turbopack-tests_tests_snapshot_cssmodules_composes_input_0-092p2ojcgki._.js b/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/composes/output/1do3_crates_turbopack-tests_tests_snapshot_cssmodules_composes_input_0-092p2ojcgki._.js index 7e98dfef26c8..09feec760bb0 100644 --- a/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/composes/output/1do3_crates_turbopack-tests_tests_snapshot_cssmodules_composes_input_0-092p2ojcgki._.js +++ b/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/composes/output/1do3_crates_turbopack-tests_tests_snapshot_cssmodules_composes_input_0-092p2ojcgki._.js @@ -9,8 +9,8 @@ var __TURBOPACK__imported__module__$5b$project$5d2f$turbopack$2f$crates$2f$turbo "[project]/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/composes/input/index.module.css [test] (css module)", ((__turbopack_context__) => { __turbopack_context__.v({ - "className": "index-module__H6xp9G__className", - "subClass": "index-module__H6xp9G__subClass" + " " + "index-module__H6xp9G__className", + "className": "H6xp9G_className", + "subClass": "H6xp9G_subClass" + " " + "H6xp9G_className", }); }), ]); diff --git a/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/composes/output/1do3_crates_turbopack-tests_tests_snapshot_cssmodules_composes_input_0-092p2ojcgki._.js.map b/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/composes/output/1do3_crates_turbopack-tests_tests_snapshot_cssmodules_composes_input_0-092p2ojcgki._.js.map index 5a91da45b83c..1c95f16f93c8 100644 --- a/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/composes/output/1do3_crates_turbopack-tests_tests_snapshot_cssmodules_composes_input_0-092p2ojcgki._.js.map +++ b/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/composes/output/1do3_crates_turbopack-tests_tests_snapshot_cssmodules_composes_input_0-092p2ojcgki._.js.map @@ -3,5 +3,5 @@ "sources": [], "sections": [ {"offset": {"line": 4, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/composes/input/index.js"],"sourcesContent":["import { subClass } from './index.module.css'\n"],"names":[],"mappings":";AAAA"}}, - {"offset": {"line": 10, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/composes/input/index.module.css [test] (css module)"],"sourcesContent":["__turbopack_context__.v({\n \"className\": \"index-module__H6xp9G__className\",\n \"subClass\": \"index-module__H6xp9G__subClass\" + \" \" + \"index-module__H6xp9G__className\",\n});\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA"}}] + {"offset": {"line": 10, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/composes/input/index.module.css [test] (css module)"],"sourcesContent":["__turbopack_context__.v({\n \"className\": \"H6xp9G_className\",\n \"subClass\": \"H6xp9G_subClass\" + \" \" + \"H6xp9G_className\",\n});\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA"}}] } \ No newline at end of file diff --git a/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/composes/output/1jsg_tests_snapshot_cssmodules_composes_input_index_module_0cff4_jv4ks5t.css b/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/composes/output/1jsg_tests_snapshot_cssmodules_composes_input_index_module_0cff4_jv4ks5t.css index 603896192db1..0410ccfd26fd 100644 --- a/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/composes/output/1jsg_tests_snapshot_cssmodules_composes_input_index_module_0cff4_jv4ks5t.css +++ b/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/composes/output/1jsg_tests_snapshot_cssmodules_composes_input_index_module_0cff4_jv4ks5t.css @@ -1,10 +1,10 @@ /* [project]/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/composes/input/index.module.css [test] (css) */ -.index-module__H6xp9G__className { +.H6xp9G_className { color: #ff0; background: red; } -.index-module__H6xp9G__subClass { +.H6xp9G_subClass { background: #00f; } diff --git a/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/relative-uri-import/output/1jsg_tests_snapshot_cssmodules_relative-uri-import_input_0hybzimbac5hb._.js b/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/relative-uri-import/output/1jsg_tests_snapshot_cssmodules_relative-uri-import_input_0hybzimbac5hb._.js index 4e685868054f..72c7b820a9f5 100644 --- a/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/relative-uri-import/output/1jsg_tests_snapshot_cssmodules_relative-uri-import_input_0hybzimbac5hb._.js +++ b/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/relative-uri-import/output/1jsg_tests_snapshot_cssmodules_relative-uri-import_input_0hybzimbac5hb._.js @@ -9,13 +9,13 @@ var __TURBOPACK__imported__module__$5b$project$5d2f$turbopack$2f$crates$2f$turbo "[project]/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/relative-uri-import/input/index.module.css [test] (css module)", ((__turbopack_context__) => { __turbopack_context__.v({ - "bar": "index-module__jZ0vmq__bar" + " " + __turbopack_context__.i("[project]/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/relative-uri-import/input/other.module.css [test] (css module)")["foo"], + "bar": "jZ0vmq_bar" + " " + __turbopack_context__.i("[project]/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/relative-uri-import/input/other.module.css [test] (css module)")["foo"], }); }), "[project]/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/relative-uri-import/input/other.module.css [test] (css module)", ((__turbopack_context__) => { __turbopack_context__.v({ - "foo": "other-module__NjlEuq__foo", + "foo": "NjlEuq_foo", }); }), ]); diff --git a/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/relative-uri-import/output/1jsg_tests_snapshot_cssmodules_relative-uri-import_input_0hybzimbac5hb._.js.map b/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/relative-uri-import/output/1jsg_tests_snapshot_cssmodules_relative-uri-import_input_0hybzimbac5hb._.js.map index 8ab56aaddbc5..e86fdd2c103e 100644 --- a/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/relative-uri-import/output/1jsg_tests_snapshot_cssmodules_relative-uri-import_input_0hybzimbac5hb._.js.map +++ b/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/relative-uri-import/output/1jsg_tests_snapshot_cssmodules_relative-uri-import_input_0hybzimbac5hb._.js.map @@ -3,6 +3,6 @@ "sources": [], "sections": [ {"offset": {"line": 4, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/relative-uri-import/input/index.js"],"sourcesContent":["import { foo } from './index.module.css'\n"],"names":[],"mappings":";AAAA"}}, - {"offset": {"line": 10, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/relative-uri-import/input/index.module.css [test] (css module)"],"sourcesContent":["__turbopack_context__.v({\n \"bar\": \"index-module__jZ0vmq__bar\" + \" \" + __turbopack_context__.i(\"[project]/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/relative-uri-import/input/other.module.css [test] (css module)\")[\"foo\"],\n});\n"],"names":[],"mappings":"AAAA;AACA;AACA"}}, - {"offset": {"line": 16, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/relative-uri-import/input/other.module.css [test] (css module)"],"sourcesContent":["__turbopack_context__.v({\n \"foo\": \"other-module__NjlEuq__foo\",\n});\n"],"names":[],"mappings":"AAAA;AACA;AACA"}}] + {"offset": {"line": 10, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/relative-uri-import/input/index.module.css [test] (css module)"],"sourcesContent":["__turbopack_context__.v({\n \"bar\": \"jZ0vmq_bar\" + \" \" + __turbopack_context__.i(\"[project]/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/relative-uri-import/input/other.module.css [test] (css module)\")[\"foo\"],\n});\n"],"names":[],"mappings":"AAAA;AACA;AACA"}}, + {"offset": {"line": 16, "column": 0}, "map": {"version":3,"sources":["turbopack:///[project]/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/relative-uri-import/input/other.module.css [test] (css module)"],"sourcesContent":["__turbopack_context__.v({\n \"foo\": \"NjlEuq_foo\",\n});\n"],"names":[],"mappings":"AAAA;AACA;AACA"}}] } \ No newline at end of file diff --git a/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/relative-uri-import/output/1jsg_tests_snapshot_cssmodules_relative-uri-import_input_1e9d8db7tuswg._.css b/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/relative-uri-import/output/1jsg_tests_snapshot_cssmodules_relative-uri-import_input_1e9d8db7tuswg._.css index 3e44d98853a0..0f55ea19b9a2 100644 --- a/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/relative-uri-import/output/1jsg_tests_snapshot_cssmodules_relative-uri-import_input_1e9d8db7tuswg._.css +++ b/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/relative-uri-import/output/1jsg_tests_snapshot_cssmodules_relative-uri-import_input_1e9d8db7tuswg._.css @@ -1,10 +1,10 @@ /* [project]/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/relative-uri-import/input/other.module.css [test] (css) */ -.other-module__NjlEuq__foo { +.NjlEuq_foo { background-color: red; } /* [project]/turbopack/crates/turbopack-tests/tests/snapshot/cssmodules/relative-uri-import/input/index.module.css [test] (css) */ -.index-module__jZ0vmq__bar { +.jZ0vmq_bar { } diff --git a/turbopack/crates/turbopack/src/lib.rs b/turbopack/crates/turbopack/src/lib.rs index ddeb4c97521d..028b06b36db9 100644 --- a/turbopack/crates/turbopack/src/lib.rs +++ b/turbopack/crates/turbopack/src/lib.rs @@ -255,6 +255,7 @@ async fn apply_module_type( ty, environment, lightningcss_features, + module_css_debuggable_idents, } => ResolvedVc::upcast( CssModule::new( *source, @@ -263,6 +264,7 @@ async fn apply_module_type( css_import_context.map(|c| *c), environment.as_deref().copied(), *lightningcss_features, + *module_css_debuggable_idents, ) .to_resolved() .await?, diff --git a/turbopack/crates/turbopack/src/module_options/mod.rs b/turbopack/crates/turbopack/src/module_options/mod.rs index c89ba9435c00..c1d235ba4399 100644 --- a/turbopack/crates/turbopack/src/module_options/mod.rs +++ b/turbopack/crates/turbopack/src/module_options/mod.rs @@ -260,6 +260,7 @@ impl ModuleOptions { source_maps: css_source_maps, ref module_css_condition, lightningcss_features, + module_css_debuggable_idents, .. }, ref static_url_tag, @@ -861,6 +862,7 @@ impl ModuleOptions { ty: CssModuleType::Module, environment, lightningcss_features, + module_css_debuggable_idents, })], ), ModuleRule::new( @@ -872,6 +874,7 @@ impl ModuleOptions { ty: CssModuleType::Default, environment, lightningcss_features, + module_css_debuggable_idents, })], ), ]); @@ -936,6 +939,7 @@ impl ModuleOptions { ty: CssModuleType::Module, environment, lightningcss_features, + module_css_debuggable_idents, })], ), // Ecmascript CSS Modules referencing the actual CSS module to include it @@ -950,6 +954,7 @@ impl ModuleOptions { ty: CssModuleType::Module, environment, lightningcss_features, + module_css_debuggable_idents, })], ), // Ecmascript CSS Modules referencing the actual CSS module to list the classes @@ -964,6 +969,7 @@ impl ModuleOptions { ty: CssModuleType::Module, environment, lightningcss_features, + module_css_debuggable_idents, })], ), ModuleRule::new( @@ -979,6 +985,7 @@ impl ModuleOptions { ty: CssModuleType::Default, environment, lightningcss_features, + module_css_debuggable_idents, })], ), ]); diff --git a/turbopack/crates/turbopack/src/module_options/module_options_context.rs b/turbopack/crates/turbopack/src/module_options/module_options_context.rs index e8dc63a61d5e..9aaf21f455bc 100644 --- a/turbopack/crates/turbopack/src/module_options/module_options_context.rs +++ b/turbopack/crates/turbopack/src/module_options/module_options_context.rs @@ -317,6 +317,9 @@ pub struct CssOptionsContext { /// User-specified lightningcss feature flags (include/exclude bitmasks). pub lightningcss_features: turbopack_css::LightningCssFeatureFlags, + /// Include the file name in CSS Module class names for easier debugging. + pub module_css_debuggable_idents: bool, + pub placeholder_for_future_extensions: (), } diff --git a/turbopack/crates/turbopack/src/module_options/module_rule.rs b/turbopack/crates/turbopack/src/module_options/module_rule.rs index e38279d69b80..4de4fb5af522 100644 --- a/turbopack/crates/turbopack/src/module_options/module_rule.rs +++ b/turbopack/crates/turbopack/src/module_options/module_rule.rs @@ -143,6 +143,7 @@ pub enum ModuleType { ty: CssModuleType, environment: Option>, lightningcss_features: turbopack_css::LightningCssFeatureFlags, + module_css_debuggable_idents: bool, }, StaticUrlJs { /// The tag that is passed to ChunkingContext::asset_url @@ -283,6 +284,8 @@ impl ConfiguredModuleType { ty: CssModuleType::Default, environment, lightningcss_features, + // This is global CSS, so the CSS Module naming pattern is unused. + module_css_debuggable_idents: false, }), ConfiguredModuleType::CssModule => ModuleRuleEffect::ModuleType(ModuleType::CssModule), ConfiguredModuleType::Json => { From 89ba4a32df219afed5c8c46c02aa33c4ea11103d Mon Sep 17 00:00:00 2001 From: Maya <121539073+mlekhi@users.noreply.github.com> Date: Thu, 27 Aug 2026 14:34:20 -0700 Subject: [PATCH 5/9] Mark deploy release test skill as internal (#97961) ## Summary - mark the deploy-release-test repository skill as internal - prevent public skill discovery and telemetry publication for this repo-only workflow ## Testing - validated the SKILL.md frontmatter parses with metadata.internal set to true - git diff --check Co-authored-by: Aurora Scharff <66901228+aurorascharff@users.noreply.github.com> --- .agents/skills/deploy-release-test/SKILL.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.agents/skills/deploy-release-test/SKILL.md b/.agents/skills/deploy-release-test/SKILL.md index b54e88bb17de..8f8c4dac9f70 100644 --- a/.agents/skills/deploy-release-test/SKILL.md +++ b/.agents/skills/deploy-release-test/SKILL.md @@ -9,6 +9,8 @@ description: > locally with pnpm test-deploy instead. Covers resolving the latest branch SHA, waiting for vercel-packages, preserving default workflow inputs, dispatching the workflow, and verifying the run. +metadata: + internal: true --- # Deploy Release Test From b882af0899031d8b5a6a5f31df6074e41c37b78b Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Thu, 27 Aug 2026 14:44:31 -0700 Subject: [PATCH 6/9] Add Cache Components option to create-next-app (#97695) When initializing a new project with create-next-app, the interactive prompt will now ask the user if they want to enable Cache Components. For now the option defaults to off. We'll flip the default back on once there's a flow that guides users to the right recommendation based on the kind of site they're building. The option can still be enabled interactively, or with the `--cache-components` flag. If the user chooses to enable Cache Components, the project will also be initialized with Partial Prefetching enabled. There's no separate prompt for Partial Prefetching; the only reason they are separate configs is because Cache Components shipped in 16.0 before Partial Prefetching existed. This is not a breaking change because it does not change any behavior in Next.js itself, only the create-next-app CLI. It has no impact on any existing Next.js projects. --- packages/create-next-app/create-app.ts | 3 ++ packages/create-next-app/index.ts | 35 +++++++++++++++++++ packages/create-next-app/templates/index.ts | 19 ++++++++++ packages/create-next-app/templates/types.ts | 1 + test/production/create-next-app/index.test.ts | 2 ++ 5 files changed, 60 insertions(+) diff --git a/packages/create-next-app/create-app.ts b/packages/create-next-app/create-app.ts index 2da12143b06b..805fb7cfde34 100644 --- a/packages/create-next-app/create-app.ts +++ b/packages/create-next-app/create-app.ts @@ -43,6 +43,7 @@ export async function createApp({ bundler, disableGit, reactCompiler, + cacheComponents, agentsMd, }: { appPath: string @@ -62,6 +63,7 @@ export async function createApp({ bundler: Bundler disableGit?: boolean reactCompiler: boolean + cacheComponents: boolean agentsMd: boolean }): Promise { let repoInfo: RepoInfo | undefined @@ -255,6 +257,7 @@ export async function createApp({ skipInstall, bundler, reactCompiler, + cacheComponents, }) } diff --git a/packages/create-next-app/index.ts b/packages/create-next-app/index.ts index 2a6020619c70..888b3eb00e6f 100644 --- a/packages/create-next-app/index.ts +++ b/packages/create-next-app/index.ts @@ -51,6 +51,7 @@ const program = new Command(packageJson.name) .option('--js, --javascript', 'Initialize as a JavaScript project.') .option('--tailwind', 'Initialize with Tailwind CSS config. (default)') .option('--react-compiler', 'Initialize with React Compiler enabled.') + .option('--cache-components', 'Initialize with Cache Components enabled.') .option('--eslint', 'Initialize with ESLint config.') .option('--biome', 'Initialize with Biome config.') .option('--app', 'Initialize as an App Router project.') @@ -245,6 +246,7 @@ async function run(): Promise { empty: false, disableGit: false, reactCompiler: false, + cacheComponents: false, agentsMd: true, } @@ -285,6 +287,11 @@ async function run(): Promise { values: { true: 'App Router', false: 'Pages Router' }, flags: { true: '--app', false: '--no-app' }, }, + { + key: 'cacheComponents', + values: { true: 'Cache Components', false: 'No Cache Components' }, + flags: { true: '--cache-components', false: '--no-cache-components' }, + }, { key: 'agentsMd', values: { true: 'AGENTS.md', false: 'No AGENTS.md' }, @@ -582,6 +589,32 @@ async function run(): Promise { } } + // Cache Components is an App Router feature, so only offer it when the App + // Router is in use. + if ( + opts.app && + !opts.api && + !opts.cacheComponents && + !args.includes('--no-cache-components') + ) { + if (skipPrompt) { + opts.cacheComponents = getPrefOrDefault('cacheComponents') + } else { + const styledCacheComponents = blue('Cache Components') + const { cacheComponents } = await prompts({ + onState: onPromptState, + type: 'toggle', + name: 'cacheComponents', + message: `Would you like to use ${styledCacheComponents}?`, + initial: getPrefOrDefault('cacheComponents'), + active: 'Yes', + inactive: 'No', + }) + opts.cacheComponents = Boolean(cacheComponents) + preferences.cacheComponents = Boolean(cacheComponents) + } + } + const importAliasPattern = /^[^*"]+\/\*\s*$/ if ( typeof opts.importAlias !== 'string' || @@ -725,6 +758,7 @@ async function run(): Promise { bundler, disableGit: opts.disableGit, reactCompiler: opts.reactCompiler, + cacheComponents: opts.cacheComponents, agentsMd: opts.agentsMd, }) } catch (reason) { @@ -760,6 +794,7 @@ async function run(): Promise { bundler, disableGit: opts.disableGit, reactCompiler: opts.reactCompiler, + cacheComponents: opts.cacheComponents, agentsMd: opts.agentsMd, }) } diff --git a/packages/create-next-app/templates/index.ts b/packages/create-next-app/templates/index.ts index 7810b69c707d..992e3b454ebb 100644 --- a/packages/create-next-app/templates/index.ts +++ b/packages/create-next-app/templates/index.ts @@ -60,6 +60,7 @@ export const installTemplate = async ({ skipInstall, bundler, reactCompiler, + cacheComponents, }: InstallTemplateArgs) => { console.log(bold(`Using ${packageManager}.`)); @@ -150,6 +151,24 @@ export const installTemplate = async ({ await fs.writeFile(nextConfigFile, configContent); } + if (cacheComponents) { + const nextConfigFile = path.join( + root, + mode === "js" ? "next.config.mjs" : "next.config.ts", + ); + let configContent = await fs.readFile(nextConfigFile, "utf8"); + + // `partialPrefetching` is enabled alongside `cacheComponents` because the + // only reason to disable it is when adopting `cacheComponents` from before + // the option existed. + configContent = configContent.replace( + "/* config options here */\n", + "/* config options here */\n cacheComponents: true,\n partialPrefetching: true,\n", + ); + + await fs.writeFile(nextConfigFile, configContent); + } + const tsconfigFile = path.join( root, mode === "js" ? "jsconfig.json" : "tsconfig.json", diff --git a/packages/create-next-app/templates/types.ts b/packages/create-next-app/templates/types.ts index 11f8eb7ce768..f4fd947d49e2 100644 --- a/packages/create-next-app/templates/types.ts +++ b/packages/create-next-app/templates/types.ts @@ -33,6 +33,7 @@ export interface InstallTemplateArgs { skipInstall: boolean; bundler: Bundler; reactCompiler: boolean; + cacheComponents: boolean; } export enum Bundler { diff --git a/test/production/create-next-app/index.test.ts b/test/production/create-next-app/index.test.ts index 17fd95a34ea3..cac8171ef0bf 100644 --- a/test/production/create-next-app/index.test.ts +++ b/test/production/create-next-app/index.test.ts @@ -187,6 +187,7 @@ describe('create-next-app', () => { " --eslint ESLint (use --biome for Biome, --no-eslint for None) --no-react-compiler No React Compiler (use --react-compiler for React Compiler) --no-src-dir No src/ directory (use --src-dir for src/ directory) + --no-cache-components No Cache Components (use --cache-components for Cache Components) --agents-md AGENTS.md (use --no-agents-md for No AGENTS.md) --import-alias "@/*"" `) @@ -207,6 +208,7 @@ describe('create-next-app', () => { '--no-src-dir', '--no-import-alias', '--no-react-compiler', + '--no-cache-components', '--no-agents-md', '--skip-install', ...(process.env.NEXT_RSPACK ? ['--rspack'] : []), From 12bf495e0e35f188d4a50eb15e7d1e90df548e81 Mon Sep 17 00:00:00 2001 From: Will Binns-Smith Date: Thu, 27 Aug 2026 14:54:23 -0700 Subject: [PATCH 7/9] Expand Turbopack dev cleanup (#97833) ## Summary Builds on #97591. Webpack currently removes most of `.next` on devserver startup. We can remove more as well. This keeps the age-based removal introduced in #97591 and expands it to `traces`, logs, etc. However, for any other directory that is not `cache` or `lock`, completely remove it on startup. This includes types, etc. Apply age-based deletion to Turbopack-owned output that can be restored from cache or recomputed, including route entries, chunks, assets, service workers, middleware, and instrumentation. Completely remove other dev output so logs, traces, generated types, diagnostics, and future framework-owned output cannot be left as mixed generations. Use exact-path exclusion sets in `recursiveDeleteSyncWithAsyncRetries`. The complete pass retains `cache`, `lock`, and Turbopack-owned paths; the age-based pass then sweeps those Turbopack paths. Both passes finish before project creation. ## Verification - `pnpm test-unit test/unit/recursive-delete.test.ts` - `pnpm --filter=next types` --------- Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Will Binns-Smith <755844+wbinnssmith@users.noreply.github.com> --- packages/next/src/build/index.ts | 2 +- packages/next/src/lib/recursive-delete.ts | 6 +-- .../src/server/dev/hot-reloader-turbopack.ts | 42 +++++++++++-------- .../src/server/dev/hot-reloader-webpack.ts | 2 +- test/unit/recursive-delete.test.ts | 36 ++++++++++++++-- 5 files changed, 62 insertions(+), 26 deletions(-) diff --git a/packages/next/src/build/index.ts b/packages/next/src/build/index.ts index 5c922ea57421..b18ee907abde 100644 --- a/packages/next/src/build/index.ts +++ b/packages/next/src/build/index.ts @@ -1282,7 +1282,7 @@ export default async function build( .traceAsyncFn(() => recursiveDeleteSyncWithAsyncRetries( distDir, - /^(cache|dev|lock|trace)/ + new Set(['cache', 'dev', 'lock', 'trace']) ) ) } diff --git a/packages/next/src/lib/recursive-delete.ts b/packages/next/src/lib/recursive-delete.ts index 385edece8a43..0c2a3eb085cb 100644 --- a/packages/next/src/lib/recursive-delete.ts +++ b/packages/next/src/lib/recursive-delete.ts @@ -94,7 +94,7 @@ export async function recursiveDeleteSyncWithAsyncRetries( /** Directory to delete the contents of */ dir: string, /** Exclude based on relative file path */ - exclude?: RegExp, + exclude?: ReadonlySet, /** * Only delete files whose mtime is at least this old. Directories are * removed once empty. @@ -118,7 +118,7 @@ async function deleteContents( futureMtimeThreshold, previousPath, }: { - exclude: RegExp | undefined + exclude: ReadonlySet | undefined staleBefore: number | undefined futureMtimeThreshold: number /** Relative path to the directory being deleted, used for exclude */ @@ -142,7 +142,7 @@ async function deleteContents( const absolutePath = join(dir, part.name) const pp = join(previousPath ?? '', part.name) - if (exclude?.test(pp)) { + if (exclude?.has(pp)) { keptAnything = true return } diff --git a/packages/next/src/server/dev/hot-reloader-turbopack.ts b/packages/next/src/server/dev/hot-reloader-turbopack.ts index 01ea722f3218..fdc757f67b9e 100644 --- a/packages/next/src/server/dev/hot-reloader-turbopack.ts +++ b/packages/next/src/server/dev/hot-reloader-turbopack.ts @@ -162,16 +162,26 @@ const sessionId = Math.floor(Number.MAX_SAFE_INTEGER * Math.random()) /** Output directory (relative to `distDir`) of server-HMR-managed chunks. */ const SERVER_HMR_CHUNKS_DIR = join('server', 'chunks') -const STALE_SWEPT_OUTPUT_DIRS = [ +const TURBOPACK_OUTPUT_DIRS = [ join('static', 'chunks'), join('static', 'media'), + join('static', 'service-worker'), join('server', 'app'), join('server', 'pages'), SERVER_HMR_CHUNKS_DIR, + join('server', 'assets'), join('server', 'edge', 'chunks'), join('server', 'edge', 'assets'), + join('server', 'middleware'), + join('server', 'instrumentation'), ] +const RETAINED_OUTPUT_PATHS = new Set([ + 'cache', + 'lock', + ...TURBOPACK_OUTPUT_DIRS, +]) + declare const __next__clear_chunk_cache__: (() => void) | null | undefined declare const __turbopack_server_hmr_apply__: @@ -401,6 +411,19 @@ export async function createHotReloaderTurbopack( ) } + // This must finish before Turbopack records any writes. Once turbo-tasks has + // recorded a write effect, it dedups by hash without checking the file. + await recursiveDeleteSyncWithAsyncRetries(distDir, RETAINED_OUTPUT_PATHS) + await Promise.all( + TURBOPACK_OUTPUT_DIRS.map((subDir) => + recursiveDeleteSyncWithAsyncRetries( + join(distDir, subDir), + undefined, + nextConfig.experimental.turbopackStaleOutputMaxAge + ) + ) + ) + // For the debugging purpose, check if createNext or equivalent next instance setup in test cases // works correctly. Normally `run-test` hides output so only will be visible when `--debug` flag is used. if (isTestMode) { @@ -457,23 +480,6 @@ export async function createHotReloaderTurbopack( }) } - // Clean up any old output files from previous runs. This is safe as Turbopack - // will restore any missing chunks from persistent cache or recompute them. - // - // That only holds here, before the project exists: once turbo-tasks has - // recorded a write effect for a path, the write dedups on the recorded hash - // without checking the file, so a deleted output stays missing for the rest - // of the session. - await Promise.all( - STALE_SWEPT_OUTPUT_DIRS.map((subDir) => - recursiveDeleteSyncWithAsyncRetries( - join(distDir, subDir), - undefined, - nextConfig.experimental.turbopackStaleOutputMaxAge! - ) - ) - ) - const project = await bindings.turbo.createProject( { rootPath, diff --git a/packages/next/src/server/dev/hot-reloader-webpack.ts b/packages/next/src/server/dev/hot-reloader-webpack.ts index f6ba5ba4b57c..ad05d42a6c59 100644 --- a/packages/next/src/server/dev/hot-reloader-webpack.ts +++ b/packages/next/src/server/dev/hot-reloader-webpack.ts @@ -687,7 +687,7 @@ export default class HotReloaderWebpack implements NextJsHotReloaderInterface { .traceAsyncFn(() => recursiveDeleteSyncWithAsyncRetries( join(this.dir, this.config.distDir), - /^(cache|lock)/ + new Set(['cache', 'lock']) ) ) } diff --git a/test/unit/recursive-delete.test.ts b/test/unit/recursive-delete.test.ts index 0de5ec110150..c250d95ba3d2 100644 --- a/test/unit/recursive-delete.test.ts +++ b/test/unit/recursive-delete.test.ts @@ -37,11 +37,41 @@ describe('recursiveDeleteSyncWithAsyncRetries', () => { await recursiveCopy(resolveDataDir, testpreservefileDir, { overwrite: true, }) - // preserve cache dir - await recursiveDeleteSyncWithAsyncRetries(testpreservefileDir, /^cache/) + await recursiveDeleteSyncWithAsyncRetries( + testpreservefileDir, + new Set(['cache']) + ) const result = await recursiveReadDir(testpreservefileDir) - expect(result.length).toBe(1) + expect(result).toEqual(['/cache/test.txt']) + } finally { + // Ensure test cleanup + await recursiveDeleteSyncWithAsyncRetries(testpreservefileDir) + + const cleanupResult = await recursiveReadDir(testpreservefileDir) + expect(cleanupResult.length).toBe(0) + } + }) + + it('should exclude a nested path', async () => { + expect.assertions(4) + try { + await recursiveCopy(resolveDataDir, testpreservefileDir, { + overwrite: true, + }) + await recursiveDeleteSyncWithAsyncRetries( + testpreservefileDir, + new Set([join('aa', 'cache.js')]) + ) + + const result = await recursiveReadDir(testpreservefileDir) + expect(result).toEqual(['/aa/cache.js']) + expect( + await fs.pathExists(join(testpreservefileDir, 'aa', 'cache.js')) + ).toBe(true) + expect( + await fs.pathExists(join(testpreservefileDir, 'aa', 'index.js')) + ).toBe(false) } finally { // Ensure test cleanup await recursiveDeleteSyncWithAsyncRetries(testpreservefileDir) From 49d3043e615b4bfff27a5c577f62e31db6347833 Mon Sep 17 00:00:00 2001 From: Aurora Scharff <66901228+aurorascharff@users.noreply.github.com> Date: Fri, 28 Aug 2026 00:05:19 +0200 Subject: [PATCH 8/9] ci: skip framework tests for agent evals and skills (#97998) Agent evals, skills, and the eval runner do not affect the framework runtime, but they currently make `Determine changes` schedule the full Rust, native, deployment, and framework test matrices. Classify changes confined to `evals/`, `skills/`, and `run-evals.js` with the existing lightweight documentation path. The always-on build and lint checks still run. Validated with Node syntax checking, Prettier, ESLint, and `git diff --check`. --- scripts/run-for-change.mjs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/run-for-change.mjs b/scripts/run-for-change.mjs index a7fcd4d983ef..8bbf8ae3ef0b 100644 --- a/scripts/run-for-change.mjs +++ b/scripts/run-for-change.mjs @@ -22,6 +22,11 @@ const CHANGE_ITEM_GROUPS = { '.github/ISSUE_TEMPLATE', '.github/actions/pr-auto-label/src/config.json', '.github/pull_request_template.md', + // Agent evals and skills do not affect the framework runtime. Keep them on + // the same lightweight CI path as documentation changes. + 'evals', + 'skills', + 'run-evals.js', 'packages/next-plugin-storybook/readme.md', 'packages/next/license.md', 'packages/next/README.md', From ee3a50ccb98e27a1cae594cd602390b057bfcf20 Mon Sep 17 00:00:00 2001 From: Luke Sandberg Date: Thu, 27 Aug 2026 15:40:19 -0700 Subject: [PATCH 9/9] Upgrade Turbopack to hashbrown 0.15 (#97808) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### What? Upgrade Turbopack's workspace dependencies to DashMap 7.0.0-rc2 and Hashbrown 0.15.4, removing Hashbrown 0.14 from the `turbo-tasks-backend` dependency graph. ### Why? `turbo-tasks-backend` previously compiled two Hashbrown versions because its custom DashMap helpers shared Hashbrown 0.14's private raw table types. Converging the backend graph on Hashbrown 0.15 removes that duplicate backend codegen path and slightly reduces the shipped native addon's size. A clean release A/B of the unstripped x86-64 `next-napi-bindings` cdylib measured: | Revision | `libnext_napi_bindings.so` | | --- | ---: | | canary base `80576037` | 147,824,920 bytes | | PR | 147,818,576 bytes | | **Delta** | **−6,344 bytes (−0.0043%)** | The final gain is small because unrelated SWC dependencies still pull DashMap 6 / Hashbrown 0.14 into the same addon; this change removes only backend-specific Hashbrown 0.14 codegen. Plus... nearly everything is monomorphized anyway ### How? DashMap 7 replaces its Hashbrown 0.14 raw table with Hashbrown 0.15's public `HashTable` API. The backend helpers now use that public API while retaining their existing shard locking and allocation behavior: - heterogeneous cache lookup and insertion still reuse a pre-located shard and precomputed hash; - the entry helper passes Hashbrown's native `hash_table::Entry` into a closure, preserving precise lock-release timing without a custom self-referential entry type or cloned hasher; - nonblocking removal still avoids lock-order deadlocks; - two-key mutation uses a release-mode disjointness assertion followed by `get_many_unchecked_mut` to avoid a redundant duplicate-pointer scan; - snapshot draining still transfers ownership of each shard table and releases task memory incrementally; - custom mutable guards remain non-`Send`. The migration also replaces several raw-pointer iteration and removal paths with safe `HashTable` operations. Existing raw-entry users in `auto-hash-map` and `turbopack-trace-server` explicitly enable Hashbrown 0.15's compatibility feature. ### Performance Performance testing with the overhead.rs benchmark revealed no regression/progression --------- Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Luke Sandberg <210140+lukesandberg@users.noreply.github.com> --- Cargo.lock | 57 ++++--- Cargo.toml | 4 +- .../turbo-tasks-auto-hash-map/Cargo.toml | 2 +- .../crates/turbo-tasks-backend/Cargo.toml | 2 +- .../turbo-tasks-backend/src/backend/mod.rs | 147 ++++++++++-------- .../src/backend/storage.rs | 67 +++----- .../src/utils/dash_map_entry.rs | 97 ++++++++++++ .../src/utils/dash_map_multi.rs | 136 ++++++++-------- .../src/utils/dash_map_raw_entry.rs | 143 ----------------- .../turbo-tasks-backend/src/utils/mod.rs | 2 +- .../crates/turbopack-trace-server/Cargo.toml | 2 +- 11 files changed, 312 insertions(+), 347 deletions(-) create mode 100644 turbopack/crates/turbo-tasks-backend/src/utils/dash_map_entry.rs delete mode 100644 turbopack/crates/turbo-tasks-backend/src/utils/dash_map_raw_entry.rs diff --git a/Cargo.lock b/Cargo.lock index bced8e223d0b..4a2ba8198d7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -405,7 +405,7 @@ version = "0.1.0" dependencies = [ "bincode 2.0.1", "codspeed-criterion-compat", - "hashbrown 0.14.5", + "hashbrown 0.15.4", "rustc-hash 2.1.1", "serde", "shrink-to-fit", @@ -2123,6 +2123,20 @@ dependencies = [ "parking_lot_core", ] +[[package]] +name = "dashmap" +version = "7.0.0-rc2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a1e35a65fe0538a60167f0ada6e195ad5d477f6ddae273943596d4a1a5730b" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "equivalent", + "hashbrown 0.15.4", + "lock_api", + "parking_lot_core", +] + [[package]] name = "data-encoding" version = "2.4.0" @@ -2529,7 +2543,7 @@ checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.3.5", "windows-sys 0.48.0", ] @@ -3147,7 +3161,6 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash 0.8.12", "allocator-api2", - "serde", ] [[package]] @@ -4389,11 +4402,10 @@ checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" [[package]] name = "lock_api" -version = "0.4.10" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", "serde", ] @@ -5552,15 +5564,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.8" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.18", "smallvec", - "windows-targets 0.48.5", + "windows-link 0.2.1", ] [[package]] @@ -6469,6 +6481,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.9.1", +] + [[package]] name = "ref-cast" version = "1.0.24" @@ -9948,7 +9969,7 @@ dependencies = [ "byteorder", "codspeed-criterion-compat", "crc32fast", - "dashmap 6.1.0", + "dashmap 7.0.0-rc2", "either", "fs-err", "jiff", @@ -10020,7 +10041,7 @@ dependencies = [ "bincode 2.0.1", "codspeed-criterion-compat", "concurrent-queue", - "dashmap 6.1.0", + "dashmap 7.0.0-rc2", "either", "erased-serde", "event-listener", @@ -10063,10 +10084,10 @@ dependencies = [ "bitfield", "codspeed-criterion-compat", "crossbeam-utils", - "dashmap 6.1.0", + "dashmap 7.0.0-rc2", "fs-err", "futures", - "hashbrown 0.14.5", + "hashbrown 0.15.4", "indexmap 2.14.0", "indoc", "jiff", @@ -10170,7 +10191,7 @@ dependencies = [ "bytes", "codspeed-criterion-compat", "concurrent-queue", - "dashmap 6.1.0", + "dashmap 7.0.0-rc2", "fs-err", "futures", "include_dir", @@ -10596,7 +10617,7 @@ dependencies = [ "bumpalo", "bytes-str", "codspeed-criterion-compat", - "dashmap 6.1.0", + "dashmap 7.0.0-rc2", "data-encoding", "either", "forked_react_compiler", @@ -10796,7 +10817,7 @@ dependencies = [ "bincode 2.0.1", "bytes", "const_format", - "dashmap 6.1.0", + "dashmap 7.0.0-rc2", "either", "futures", "futures-retry", @@ -10954,7 +10975,7 @@ dependencies = [ "anyhow", "either", "flate2", - "hashbrown 0.14.5", + "hashbrown 0.15.4", "indexmap 2.14.0", "itertools 0.10.5", "postcard", diff --git a/Cargo.toml b/Cargo.toml index adf583452376..3e41ece206ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -268,7 +268,7 @@ criterion = { package = "codspeed-criterion-compat", version = "4.3.0" } ctor = "1.0.7" crossbeam-channel = "0.5.8" crossbeam-utils = "0.8" -dashmap = "6.1.0" +dashmap = "7.0.0-rc2" data-encoding = "2.3.3" dhat = { version = "0.3.2" } dunce = "1.0.3" @@ -280,7 +280,7 @@ fs-err = "3.1.1" futures = "0.3.31" futures-retry = "0.6.0" futures-util = "0.3.31" -hashbrown = "0.14.5" +hashbrown = "0.15.4" image = { version = "0.25.8", default-features = false } indexmap = "2.14.0" indoc = "2.0.0" diff --git a/turbopack/crates/turbo-tasks-auto-hash-map/Cargo.toml b/turbopack/crates/turbo-tasks-auto-hash-map/Cargo.toml index 4e7e8af9b59c..c2da770f79f3 100644 --- a/turbopack/crates/turbo-tasks-auto-hash-map/Cargo.toml +++ b/turbopack/crates/turbo-tasks-auto-hash-map/Cargo.toml @@ -10,7 +10,7 @@ workspace = true [dependencies] bincode = { workspace = true } -hashbrown = { workspace = true, features = ["serde"]} +hashbrown = { workspace = true, features = ["raw-entry", "serde"]} rustc-hash = { workspace = true } serde = { workspace = true, features = ["derive"] } shrink-to-fit = { workspace = true, features = ["hashbrown"] } diff --git a/turbopack/crates/turbo-tasks-backend/Cargo.toml b/turbopack/crates/turbo-tasks-backend/Cargo.toml index b481bdbfcb70..792a2c889cb0 100644 --- a/turbopack/crates/turbo-tasks-backend/Cargo.toml +++ b/turbopack/crates/turbo-tasks-backend/Cargo.toml @@ -43,7 +43,7 @@ bitfield = { workspace = true } crossbeam-utils = { workspace = true } dashmap = { workspace = true, features = ["raw-api"]} fs-err = { workspace = true } -hashbrown = { workspace = true, features = ["raw"] } +hashbrown = { workspace = true } indexmap = { workspace = true } jiff = "0.2.10" lzzzz = { workspace = true, optional = true } diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs b/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs index 588919fff858..d31cdaf8ab58 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs @@ -22,6 +22,7 @@ use std::{ use anyhow::{Context, Result, bail}; use auto_hash_map::{AutoMap, AutoSet}; +use hashbrown::hash_table::Entry; use indexmap::IndexSet; use parking_lot::Mutex; use rustc_hash::{FxHashMap, FxHashSet, FxHasher}; @@ -80,7 +81,7 @@ use crate::{ error::TaskError, kv_backing_storage::TurboBackingStorage, utils::{ - dash_map_raw_entry::{RawEntry, get_shard, raw_entry_in_shard, raw_get_in_shard}, + dash_map_entry::{get_in_shard, get_shard, with_entry_in_shard}, shard_amount::compute_shard_amount, stopwatch::Stopwatch, }, @@ -1584,7 +1585,7 @@ impl TurboTasksBackend { // Use a read lock rather than a write lock to avoid contention. connect_child // may re-enter task_cache with a write lock, so we must not hold a write lock here. if let Some(task_id) = - raw_get_in_shard(shard, hash, |k| k.eq_components(native_fn, this, arg_ref)) + get_in_shard(shard, hash, |k| k.eq_components(native_fn, this, arg_ref)) { self.track_cache_hit_by_fn(native_fn); operation::ConnectChildOperation::run(parent_task, task_id, ctx); @@ -1601,76 +1602,88 @@ impl TurboTasksBackend { self.track_cache_hit_by_fn(native_fn); // Step 3a: Insert into in-memory cache using the pre-located shard. // Use the existing Arc from storage to avoid a duplicate allocation. - match raw_entry_in_shard(shard, self.storage.task_cache.hasher(), hash, |k| { - k.eq_components(native_fn, this, arg_ref) - }) { - RawEntry::Occupied(_) => {} - RawEntry::Vacant(e) => { - e.insert(stored_type, task_id); - } - }; + with_entry_in_shard( + shard, + self.storage.task_cache.hasher(), + hash, + arg, + |k, arg| k.eq_components(native_fn, this, arg.as_ref()), + |entry, _arg| { + if let Entry::Vacant(entry) = entry { + entry.insert((stored_type, task_id)); + } + }, + ); task_id } else { - match raw_entry_in_shard(shard, self.storage.task_cache.hasher(), hash, |k| { - k.eq_components(native_fn, this, arg_ref) - }) { - RawEntry::Occupied(e) => { - // Another thread beat us to creating this task — use their task_id. - // They will handle logging the new task as modified. - let task_id = *e.get(); - drop(e); - self.track_cache_hit_by_fn(native_fn); - task_id - } - RawEntry::Vacant(e) => { - // Only now do we force the allocation. - // NOTE: if our caller had to perform resolution, then this will have already - // been boxed and take_box just takes it. - let task_type = CachedTaskTypeArc::new(CachedTaskType { - native_fn, - this, - arg: arg.take_box(), - }); - let task_id = if transient { - self.transient_task_id_factory.get() - } else { - self.persisted_task_id_factory.get() - }; - // Initialize storage BEFORE making task_id visible in the cache. - // This ensures any thread that reads task_id from the cache sees - // the storage entry already initialized (restored flags set). - self.storage - .initialize_new_task(task_id, Some(task_type.clone())); - // insert() consumes e, releasing the shard write lock. - e.insert(task_type, task_id); - self.track_cache_miss_by_fn(native_fn); - // Update the aggregation number before connecting the child - // We don't need this on any of the task recovery paths above because the - // aggregation number will already be set. - if is_root { - AggregationUpdateQueue::run( - AggregationUpdateJob::UpdateAggregationNumber { - task_id, - base_aggregation_number: u32::MAX, - distance: None, - }, - &mut ctx, - ); - } else if native_fn.is_session_dependent && self.should_track_dependencies() { - const SESSION_DEPENDENT_AGGREGATION_NUMBER: u32 = u32::MAX >> 2; - AggregationUpdateQueue::run( - AggregationUpdateJob::UpdateAggregationNumber { - task_id, - base_aggregation_number: SESSION_DEPENDENT_AGGREGATION_NUMBER, - distance: None, - }, - &mut ctx, - ); - }; + let (task_id, created) = with_entry_in_shard( + shard, + self.storage.task_cache.hasher(), + hash, + arg, + |k, arg| k.eq_components(native_fn, this, arg.as_ref()), + |entry, arg| match entry { + Entry::Occupied(entry) => { + // Another thread beat us to creating this task — use their task_id. + // They will handle logging the new task as modified. + (entry.get().1, false) + } + Entry::Vacant(entry) => { + // Only now do we force the allocation. + // NOTE: if our caller had to perform resolution, then this will have + // already been boxed and take_box just takes it. + let task_type = CachedTaskTypeArc::new(CachedTaskType { + native_fn, + this, + arg: arg.take_box(), + }); + let task_id = if transient { + self.transient_task_id_factory.get() + } else { + self.persisted_task_id_factory.get() + }; + // Initialize storage BEFORE making task_id visible in the cache. + // This ensures any thread that reads task_id from the cache sees + // the storage entry already initialized (restored flags set). + self.storage + .initialize_new_task(task_id, Some(task_type.clone())); + entry.insert((task_type, task_id)); + (task_id, true) + } + }, + ); - task_id + // The entry closure has returned, so the task_cache shard lock is released before + // cache tracking or aggregation updates can re-enter the backend. + if created { + self.track_cache_miss_by_fn(native_fn); + // Update the aggregation number before connecting the child. We don't need this on + // recovery paths because the aggregation number will already be set. + if is_root { + AggregationUpdateQueue::run( + AggregationUpdateJob::UpdateAggregationNumber { + task_id, + base_aggregation_number: u32::MAX, + distance: None, + }, + &mut ctx, + ); + } else if native_fn.is_session_dependent && self.should_track_dependencies() { + const SESSION_DEPENDENT_AGGREGATION_NUMBER: u32 = u32::MAX >> 2; + AggregationUpdateQueue::run( + AggregationUpdateJob::UpdateAggregationNumber { + task_id, + base_aggregation_number: SESSION_DEPENDENT_AGGREGATION_NUMBER, + distance: None, + }, + &mut ctx, + ); } + } else { + self.track_cache_hit_by_fn(native_fn); } + + task_id }; operation::ConnectChildOperation::run(parent_task, task_id, ctx); diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/storage.rs b/turbopack/crates/turbo-tasks-backend/src/backend/storage.rs index ec4136da659c..456ddda81fe2 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/storage.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/storage.rs @@ -9,8 +9,7 @@ use std::{ }, }; -use dashmap::SharedValue; -use hashbrown::raw::RawIntoIter; +use hashbrown::hash_table; use thread_local::ThreadLocal; use tracing::span::Id; use turbo_bincode::TurboBincodeBuffer; @@ -24,8 +23,8 @@ use crate::{ database::key_value_database::KeySpace, utils::{ dash_map_drop_contents::drop_contents, - dash_map_multi::{RefMut, get_multiple_mut}, - dash_map_raw_entry::{TryLockAndRemove, try_lock_and_remove}, + dash_map_entry::{TryLockAndRemove, try_lock_and_remove}, + dash_map_multi::{RefMut, get_disjoint_mut}, }, }; @@ -340,25 +339,18 @@ impl Storage { let work = { let mut shard_guard = shard.write(); if drain_entries { - // SAFETY: shard_guard outlives the iterator and we hold it for the whole scan. - for bucket in unsafe { shard_guard.iter() } { - // Read the key and modified flag, then drop the borrow before any erase. - // SAFETY: the guard outlives the bucket reference. - let (key, modified_task) = { - let (key, shared_value) = unsafe { bucket.as_ref() }; - (*key, shared_value.get().flags.any_modified()) - }; + shard_guard.retain(|(key, task)| { + let modified_task = task.flags.any_modified(); if modified_task { debug_assert!( !key.is_transient(), "found a modified transient task: {key:?}" ); - } else { - // Unmodified entries are not part of the snapshot. Erase and free them - // now so the table we move out below holds only modified entries. - unsafe { shard_guard.erase(bucket) }; } - } + // Unmodified entries are not part of the snapshot. Remove and free them + // now so the table we move out below holds only modified entries. + modified_task + }); if shard_guard.is_empty() { // The shard held only unmodified entries, which we've now erased and freed. // No iterator is created for an empty shard. @@ -369,15 +361,12 @@ impl Storage { ShardWork::Drain(std::mem::take(&mut *shard_guard).into_iter()) } else { let mut modified = Vec::with_capacity(modified_count as usize); - // SAFETY: shard_guard outlives the iterator and we hold it for the whole scan. - for bucket in unsafe { shard_guard.iter() } { - // SAFETY: the guard outlives the bucket reference. - let (key, shared_value) = unsafe { bucket.as_ref() }; + for (key, task) in shard_guard.iter() { // Only check modified flags — transient tasks never have modified flags set // (track_modification guards against it), so this naturally excludes them. // new_task always comes with modified flags (set_persistent_task_type calls // track_modification), so any_modified() is sufficient. - if shared_value.get().flags.any_modified() { + if task.flags.any_modified() { debug_assert!( !key.is_transient(), "found a modified transient task: {key:?}" @@ -473,7 +462,7 @@ impl Storage { let snap_shard = &snapshot_shards[shard_idx]; // Acquire in documented order: map first, snapshots second. - let map_guard = map_shard.write(); + let mut map_guard = map_shard.write(); let mut snap_guard = snap_shard.write(); for (key, _) in snap_guard.drain() { @@ -482,11 +471,8 @@ impl Storage { // through `self.map.get_mut`, which would attempt to re-acquire this shard's // write lock and would also obscure the pairing. let hash = self.map.hasher().hash_one(key); - if let Some(bucket) = map_guard.find(hash, |(k, _)| *k == key) { - // SAFETY: We hold `map_shard`'s write lock for the duration of this - // access, so the bucket pointer is valid and no other thread can alias it. - let (_, shared_value) = unsafe { bucket.as_mut() }; - self.promote_during_snapshot_flags(shared_value.get_mut(), shard_idx); + if let Some((_, task)) = map_guard.find_mut(hash, |(k, _)| *k == key) { + self.promote_during_snapshot_flags(task, shard_idx); } } // If we are saving a non-trivial amount of memory just clear it out. @@ -531,7 +517,7 @@ impl Storage { key1: TaskId, key2: TaskId, ) -> (StorageWriteGuard<'_>, StorageWriteGuard<'_>) { - let (a, b) = get_multiple_mut(&self.map, key1, key2, || Box::new(TaskStorage::new())); + let (a, b) = get_disjoint_mut(&self.map, key1, key2, || Box::new(TaskStorage::new())); ( StorageWriteGuard { storage: self, @@ -587,21 +573,18 @@ impl Storage { // avoid a lock cycle with get_or_create_persistent_task, which takes task_cache // before map. Allocated lazily on first conflict. let mut deferred_task_cache_removals: Vec = Vec::new(); - // SAFETY: We hold the write lock for the duration of iteration. - for bucket in unsafe { shard.iter() } { - // SAFETY: The write lock guard outlives the bucket reference. - let (task_id, task) = unsafe { bucket.as_mut() }; + shard.retain(|(task_id, task)| { if task_id.is_transient() { evicted.unevictable_reasons[UnevictableReason::Transient.index()] += 1; - continue; + return true; } - let (key_evictability, value_evictability) = task.get().evictability(); + let (key_evictability, value_evictability) = task.evictability(); match key_evictability { KeyEvictability::Evictable => { // The task type is persisted to backing storage (new_task = false), // so task_cache is a pure perf cache. Remove it now; it will be // re-populated by task_by_type() on the next cache miss. - let task_type = task.get().get_persistent_task_type().unwrap(); + let task_type = task.get_persistent_task_type().unwrap(); // Only try to acquire the lock, if we cannot just remove at the end // Because `get_or_create_task` acquires 'task_cache' then `storage.map` and // we do the opposite we need to be defensive here. Attempting here is just @@ -624,12 +607,10 @@ impl Storage { } match value_evictability { ValueEvictability::Evictable { meta, data } => { - match task.get_mut().drop_partial(data, meta) { + match task.drop_partial(data, meta) { DropPartialOutcome::Empty => { - unsafe { - shard.erase(bucket); - } evicted.full += 1; + return false; } DropPartialOutcome::HasResidue => { if data && meta { @@ -647,7 +628,8 @@ impl Storage { evicted.unevictable_reasons[reason.index()] += 1; } } - } + true + }); // Shrink the shard if it's less than half full, to reclaim slack capacity // after bulk evictions. We already hold the write lock, so this is free // from a locking perspective. TaskId hashing is cheap (it's just an integer). @@ -932,7 +914,7 @@ enum ShardWork { /// (modified-only) shard table out of the map. The iterator owns that table and drains it /// directly, freeing each task box as it is serialized. No second map lookup, no flag /// bookkeeping (the whole map is discarded right after this snapshot). - Drain(RawIntoIter<(TaskId, SharedValue>)>), + Drain(hash_table::IntoIter<(TaskId, Box)>), } pub struct SnapshotShard<'l, P> { @@ -1016,7 +998,6 @@ where // bookkeeping the normal path does, since the entire map is discarded right after // this snapshot. let (task_id, inner) = entries.next()?; - let inner = inner.into_inner(); Some(serialize_task(task_id, &inner)) // we don't need to update any bits because everything is getting dropped. } diff --git a/turbopack/crates/turbo-tasks-backend/src/utils/dash_map_entry.rs b/turbopack/crates/turbo-tasks-backend/src/utils/dash_map_entry.rs new file mode 100644 index 000000000000..8f0d96dcf23e --- /dev/null +++ b/turbopack/crates/turbo-tasks-backend/src/utils/dash_map_entry.rs @@ -0,0 +1,97 @@ +use std::hash::{BuildHasher, Hash}; + +use crossbeam_utils::CachePadded; +use dashmap::{DashMap, RwLock}; +use hashbrown::{HashTable, hash_table}; + +/// The type of a single shard inside a [`DashMap`]. +/// +/// `dashmap::HashMap` is a private alias for `HashTable<(K, V)>`. +pub type Shard = CachePadded>>; + +/// Returns a reference to the shard that owns the given pre-computed hash, +/// without locking anything. +/// +/// Pass the returned reference to [`get_in_shard`] and +/// [`with_entry_in_shard`] so that the shard is only located once even when a +/// read-lock miss is followed by a write-lock retry. +pub fn get_shard( + map: &DashMap, + hash: u64, +) -> &Shard { + let idx = map.determine_shard(hash as usize); + &map.shards()[idx] +} + +/// Read-only heterogeneous lookup using a pre-located shard reference. +/// Returns `Some(value)` on hit, `None` on miss. Uses only a read lock. +pub fn get_in_shard( + shard: &Shard, + hash: u64, + eq: impl Fn(&K) -> bool, +) -> Option { + let guard = shard.read(); + guard.find(hash, |(k, _v)| eq(k)).map(|(_k, v)| *v) +} + +/// Runs `then` with the native Hashbrown entry for a pre-located DashMap shard. +/// +/// The shard write lock is held only for the duration of `then`. The caller +/// controls the precise point where the entry is consumed and the lock is +/// released by returning from the closure. +pub fn with_entry_in_shard( + shard: &Shard, + map_hasher: &S, + hash: u64, + query: &mut Q, + eq: impl Fn(&K, &Q) -> bool, + then: impl FnOnce(hash_table::Entry<'_, (K, V)>, &mut Q) -> R, +) -> R { + let mut guard = shard.write(); + let entry = guard.entry( + hash, + |(k, _v)| eq(k, query), + |(k, _v)| map_hasher.hash_one(k), + ); + then(entry, query) +} + +/// Outcome of [`try_lock_and_remove`]. +pub enum TryLockAndRemove { + /// The shard lock was acquired and a matching entry was removed. + Removed, + /// The shard lock was acquired but no matching entry was present. + NotFound, + /// The shard lock was contended; the caller should retry later after releasing + /// any other locks they are holding. + WouldBlock, +} + +/// Remove `key` from `map` without blocking on shard contention. +/// +/// Intended for call sites that already hold another lock and want to avoid a +/// cyclic wait. On contention (`WouldBlock`), the caller is expected to defer the +/// removal and retry after dropping the other lock. +pub fn try_lock_and_remove< + K: Eq + Hash + AsRef, + V, + Q: Eq + Hash + ?Sized, + S: BuildHasher + Clone, +>( + map: &DashMap, + key: &Q, +) -> TryLockAndRemove { + let hasher = map.hasher(); + let hash = hasher.hash_one(key); + let shard_idx = map.determine_shard(hash as usize); + let Some(mut shard) = map.shards()[shard_idx].try_write() else { + return TryLockAndRemove::WouldBlock; + }; + match shard.find_entry(hash, |(k, _v)| k.as_ref() == key) { + Ok(entry) => { + entry.remove(); + TryLockAndRemove::Removed + } + Err(_) => TryLockAndRemove::NotFound, + } +} diff --git a/turbopack/crates/turbo-tasks-backend/src/utils/dash_map_multi.rs b/turbopack/crates/turbo-tasks-backend/src/utils/dash_map_multi.rs index 53ec3c92079c..d1d5cf43216d 100644 --- a/turbopack/crates/turbo-tasks-backend/src/utils/dash_map_multi.rs +++ b/turbopack/crates/turbo-tasks-backend/src/utils/dash_map_multi.rs @@ -2,23 +2,25 @@ use std::{ hash::{BuildHasher, Hash}, marker::PhantomData, ops::{Deref, DerefMut}, + ptr::NonNull, sync::Arc, }; -use dashmap::{DashMap, RwLockWriteGuard, SharedValue}; -use hashbrown::raw::{Bucket, RawTable}; +use dashmap::{DashMap, RawRwLock}; +use hashbrown::HashTable; +use parking_lot::lock_api::RwLockWriteGuard; -type RwLockWriteTableGuard<'a, K, V> = RwLockWriteGuard<'a, RawTable<(K, SharedValue)>>; +type RwLockWriteTableGuard<'a, K, V> = RwLockWriteGuard<'a, RawRwLock, HashTable<(K, V)>>; pub enum RefMut<'a, K, V> { Base(dashmap::mapref::one::RefMut<'a, K, V>), Simple { _guard: RwLockWriteTableGuard<'a, K, V>, - bucket: Bucket<(K, SharedValue)>, + entry: NonNull<(K, V)>, }, Shared { _guard: Arc>, - bucket: Bucket<(K, SharedValue)>, + entry: NonNull<(K, V)>, // Ensures that RefMut is !Send, preventing holding RefMut across .await points in async // code, which can cause deadlocks. See safety comment on `unsafe impl Sync for RefMut` // below. @@ -34,13 +36,13 @@ pub enum RefMut<'a, K, V> { // while every other tokio worker piles up trying to take the same lock — leaving no thread free // to poll the parked future. Marking the type `!Send` makes the borrow checker reject those call // sites at compile time. -// SAFETY (Sync): `RefMut` contains a raw `Bucket` pointer into a `DashMap` shard's `RawTable`. +// SAFETY (Sync): `RefMut` contains a non-null pointer into a `DashMap` shard's `HashTable`. // Sharing `&RefMut` is safe because: -// - `Simple` variant: The `Bucket` is accessed under an exclusive `RwLockWriteGuard` on a single +// - `Simple` variant: The entry is accessed under an exclusive `RwLockWriteGuard` on a single // shard. The guard provides exclusive access to all data in that shard. -// - `Shared` variant: The `Bucket` is accessed under an `Arc`. The -// `get_multiple_mut` function asserts that bucket pointers do not alias, so each `RefMut` has -// exclusive access to its bucket even when sharing a guard. +// - `Shared` variant: The entry is accessed under an `Arc`. The +// `get_disjoint_mut` function validates that the keys differ before obtaining both references +// through `HashTable::get_many_unchecked_mut`. // - `K: Sync + V: Sync` bounds ensure the key and value types are safe to share across threads. unsafe impl Sync for RefMut<'_, K, V> {} @@ -60,14 +62,10 @@ impl RefMut<'_, K, V> { pub fn pair(&self) -> (&K, &V) { match self { RefMut::Base(r) => r.pair(), - RefMut::Simple { bucket, .. } | RefMut::Shared { bucket, .. } => { - // SAFETY: - // - The bucket is still valid, as we're holding a write guard on the shard - // - These bucket pointers are convertible to references - // - // https://doc.rust-lang.org/std/ptr/index.html#pointer-to-reference-conversion - let entry = unsafe { bucket.as_ref() }; - (&entry.0, entry.1.get()) + RefMut::Simple { entry, .. } | RefMut::Shared { entry, .. } => { + // SAFETY: The entry remains valid while the shard write guard is held. + let entry = unsafe { entry.as_ref() }; + (&entry.0, &entry.1) } } } @@ -75,13 +73,13 @@ impl RefMut<'_, K, V> { pub fn pair_mut(&mut self) -> (&K, &mut V) { match self { RefMut::Base(r) => r.pair_mut(), - RefMut::Simple { bucket, .. } | RefMut::Shared { bucket, .. } => { + RefMut::Simple { entry, .. } | RefMut::Shared { entry, .. } => { // SAFETY: Same as above in `pair`, plus aliasing is prevented via: // 1. The lifetime of `&mut self`. - // 2. `Simple` values come from separate shards (no aliasing possible) - // 3. `Shared` values are asserted in `get_multiple_mut` to have unique pointers - let entry = unsafe { bucket.as_mut() }; - (&entry.0, entry.1.get_mut()) + // 2. `Simple` values come from separate shards (no aliasing possible). + // 3. `Shared` values were validated as disjoint before the pointers were created. + let entry = unsafe { entry.as_mut() }; + (&entry.0, &mut entry.1) } } } @@ -110,7 +108,7 @@ where } } -pub fn get_multiple_mut( +pub fn get_disjoint_mut( map: &DashMap, key1: K, key2: K, @@ -135,49 +133,43 @@ where let shards = map.shards(); if s1 == s2 { - let mut guard = shards[s1].write(); - - // we need to call `find_or_find_insert_slot` to avoid overwriting existing entries, but we - // can't use the returned bucket until after we get `bucket2` (below) - let _ = guard - .find_or_find_insert_slot(h1, eq1, hash_entry) - .unwrap_or_else(|slot| unsafe { - // SAFETY: This slot was previously returned by `find_or_find_insert_slot`, and no - // mutation of the table has occurred since that call. - guard.insert_in_slot(h1, slot, (key1.clone(), SharedValue::new(insert_with()))) - }); + // Equal keys would resolve to a single entry below. This must be a release-mode assertion + // because the unchecked lookup relies on it for memory safety. + assert!( + key1 != key2, + "`get_disjoint_mut` was called with equal keys, which breaks mutable referencing rules" + ); - let bucket2 = guard - .find_or_find_insert_slot(h2, eq2, hash_entry) - .unwrap_or_else(|slot| unsafe { - // SAFETY: See previous call above - guard.insert_in_slot(h2, slot, (key2.clone(), SharedValue::new(insert_with()))) - }); + let mut guard = shards[s1].write(); - // Getting `bucket2` might invalidate the bucket pointer of the first entry, *even if no - // insert happens* as `RawTable::find_or_find_insert_slot` will *sometimes* resize the - // table, as it unconditionally reserves space for a potential insertion. - let bucket1 = guard.find(h1, eq1).expect( - "failed to find bucket of previously inserted item, is the hash or eq implementation \ - incorrect?", - ); + if guard.find(h1, eq1).is_none() { + guard.insert_unique(h1, (key1.clone(), insert_with()), hash_entry); + } + if guard.find(h2, eq2).is_none() { + guard.insert_unique(h2, (key2.clone(), insert_with()), hash_entry); + } - // this assertion is needed for memory safety reasons - assert!( - !std::ptr::eq(bucket1.as_ptr(), bucket2.as_ptr()), - "`get_multiple_mut` was called with equal keys, which breaks mutable referencing rules" - ); + // SAFETY: `key1 != key2` was asserted above. Since `K: Eq`, the two equality closures + // cannot select the same entry, even when the hashes collide. + let [entry1, entry2] = + unsafe { + guard.get_many_unchecked_mut([h1, h2], |index, entry| { + if index == 0 { eq1(entry) } else { eq2(entry) } + }) + }; + let entry1 = NonNull::from(entry1.expect("the first entry was inserted above")); + let entry2 = NonNull::from(entry2.expect("the second entry was inserted above")); let guard = Arc::new(guard); ( RefMut::Shared { _guard: guard.clone(), - bucket: bucket1, + entry: entry1, phantom: PhantomData, }, RefMut::Shared { _guard: guard, - bucket: bucket2, + entry: entry2, phantom: PhantomData, }, ) @@ -197,27 +189,31 @@ where } }; - let bucket1 = guard1 - .find_or_find_insert_slot(h1, eq1, hash_entry) - .unwrap_or_else(|slot| unsafe { - // SAFETY: See first insert_in_slot call - guard1.insert_in_slot(h1, slot, (key1.clone(), SharedValue::new(insert_with()))) - }); - let bucket2 = guard2 - .find_or_find_insert_slot(h2, eq2, hash_entry) - .unwrap_or_else(|slot| unsafe { - // SAFETY: See first insert_in_slot call - guard2.insert_in_slot(h2, slot, (key2.clone(), SharedValue::new(insert_with()))) - }); + if guard1.find(h1, eq1).is_none() { + guard1.insert_unique(h1, (key1.clone(), insert_with()), hash_entry); + } + if guard2.find(h2, eq2).is_none() { + guard2.insert_unique(h2, (key2.clone(), insert_with()), hash_entry); + } + let entry1 = NonNull::from( + guard1 + .find_mut(h1, eq1) + .expect("the first entry was inserted"), + ); + let entry2 = NonNull::from( + guard2 + .find_mut(h2, eq2) + .expect("the second entry was inserted"), + ); ( RefMut::Simple { _guard: guard1, - bucket: bucket1, + entry: entry1, }, RefMut::Simple { _guard: guard2, - bucket: bucket2, + entry: entry2, }, ) } @@ -250,7 +246,7 @@ mod tests { for indices in indices { s.spawn(|| { for i in indices { - let (mut a, mut b) = get_multiple_mut(map, i, i + 1, || 0); + let (mut a, mut b) = get_disjoint_mut(map, i, i + 1, || 0); *a += 1; *b += 1; } diff --git a/turbopack/crates/turbo-tasks-backend/src/utils/dash_map_raw_entry.rs b/turbopack/crates/turbo-tasks-backend/src/utils/dash_map_raw_entry.rs deleted file mode 100644 index 57714379824a..000000000000 --- a/turbopack/crates/turbo-tasks-backend/src/utils/dash_map_raw_entry.rs +++ /dev/null @@ -1,143 +0,0 @@ -use std::hash::{BuildHasher, Hash}; - -use crossbeam_utils::CachePadded; -use dashmap::{DashMap, RwLock, RwLockWriteGuard, SharedValue}; -use hashbrown::raw::{Bucket, InsertSlot, RawTable}; - -/// The type of a single shard inside a [`DashMap`]. -/// -/// `dashmap::HashMap` is a private alias for `RawTable<(K, SharedValue)>`. -pub type Shard = CachePadded)>>>; - -/// Returns a reference to the shard that owns the given pre-computed hash, -/// without locking anything. -/// -/// Pass the returned reference to [`raw_get_in_shard`] and -/// [`raw_entry_in_shard`] so that the shard is only located once even when a -/// read-lock miss is followed by a write-lock retry. -pub fn get_shard( - map: &DashMap, - hash: u64, -) -> &Shard { - let idx = map.determine_shard(hash as usize); - &map.shards()[idx] -} - -/// Read-only heterogeneous lookup using a pre-located shard reference. -/// Returns `Some(value)` on hit, `None` on miss. Uses only a read lock. -pub fn raw_get_in_shard( - shard: &Shard, - hash: u64, - eq: impl Fn(&K) -> bool, -) -> Option { - let guard = shard.read(); - // Safety: We have a read lock on the shard. - guard - .find(hash, |(k, _v)| eq(k)) - .map(|bucket| *unsafe { bucket.as_ref() }.1.get()) -} - -/// Write-lock entry lookup using a pre-located shard reference and -/// heterogeneous equality. -/// -/// Takes a pre-located `shard` (from [`get_shard`]) and `hash` so the shard is -/// not located a second time on a read-miss / write-retry path. -pub fn raw_entry_in_shard<'l, K: Eq + Hash, V, S: BuildHasher + Clone>( - shard: &'l Shard, - map_hasher: &S, - hash: u64, - eq: impl Fn(&K) -> bool, -) -> RawEntry<'l, K, V> { - let mut guard = shard.write(); - let result = - guard.find_or_find_insert_slot(hash, |(k, _v)| eq(k), |(k, _v)| map_hasher.hash_one(k)); - match result { - Ok(bucket) => RawEntry::Occupied(OccupiedEntry { - bucket, - shard: guard, - }), - Err(insert_slot) => RawEntry::Vacant(VacantEntry { - hash, - insert_slot, - shard: guard, - }), - } -} - -/// Outcome of [`try_lock_and_remove`]. -pub enum TryLockAndRemove { - /// The shard lock was acquired and a matching entry was removed. - Removed, - /// The shard lock was acquired but no matching entry was present. - NotFound, - /// The shard lock was contended; the caller should retry later after releasing - /// any other locks they are holding. - WouldBlock, -} - -/// Remove `key` from `map` without blocking on shard contention. -/// -/// Intended for call sites that already hold another lock and want to avoid a -/// cyclic wait. On contention (`WouldBlock`), the caller is expected to defer the -/// removal and retry after dropping the other lock. -pub fn try_lock_and_remove< - K: Eq + Hash + AsRef, - V, - Q: Eq + Hash + ?Sized, - S: BuildHasher + Clone, ->( - map: &DashMap, - key: &Q, -) -> TryLockAndRemove { - let hasher = map.hasher(); - let hash = hasher.hash_one(key); - let shard_idx = map.determine_shard(hash as usize); - let Some(mut shard) = map.shards()[shard_idx].try_write() else { - return TryLockAndRemove::WouldBlock; - }; - // SAFETY: we hold the write lock for the duration of the find/erase. - match shard.find(hash, |(k, _v)| k.as_ref() == key) { - Some(bucket) => { - unsafe { shard.erase(bucket) }; - TryLockAndRemove::Removed - } - None => TryLockAndRemove::NotFound, - } -} - -pub enum RawEntry<'l, K, V> { - Occupied(OccupiedEntry<'l, K, V>), - Vacant(VacantEntry<'l, K, V>), -} - -pub struct OccupiedEntry<'l, K, V> { - bucket: Bucket<(K, SharedValue)>, - #[allow(dead_code, reason = "kept to ensure the lock lives long enough")] - shard: RwLockWriteGuard<'l, RawTable<(K, SharedValue)>>, -} - -impl<'l, K, V> OccupiedEntry<'l, K, V> { - pub fn get(&self) -> &V { - // Safety: We have a write lock on the shard, so no other references to the value can - // exist. - unsafe { self.bucket.as_ref().1.get() } - } -} - -pub struct VacantEntry<'l, K, V> { - hash: u64, - insert_slot: InsertSlot, - shard: RwLockWriteGuard<'l, RawTable<(K, SharedValue)>>, -} - -impl<'l, K, V> VacantEntry<'l, K, V> { - pub fn insert(mut self, key: K, value: V) { - let shared_value = SharedValue::new(value); - // Safety: The insert slot is valid and the map has not been modified since we obtained it - // (we hold the write lock). - unsafe { - self.shard - .insert_in_slot(self.hash, self.insert_slot, (key, shared_value)); - } - } -} diff --git a/turbopack/crates/turbo-tasks-backend/src/utils/mod.rs b/turbopack/crates/turbo-tasks-backend/src/utils/mod.rs index 54dedd2665b8..2939e74e5ca5 100644 --- a/turbopack/crates/turbo-tasks-backend/src/utils/mod.rs +++ b/turbopack/crates/turbo-tasks-backend/src/utils/mod.rs @@ -1,6 +1,6 @@ pub mod dash_map_drop_contents; +pub mod dash_map_entry; pub mod dash_map_multi; -pub mod dash_map_raw_entry; pub mod markdown_table; pub mod ptr_eq_arc; pub mod shard_amount; diff --git a/turbopack/crates/turbopack-trace-server/Cargo.toml b/turbopack/crates/turbopack-trace-server/Cargo.toml index 62c97b6e0a87..bd61cce04065 100644 --- a/turbopack/crates/turbopack-trace-server/Cargo.toml +++ b/turbopack/crates/turbopack-trace-server/Cargo.toml @@ -19,7 +19,7 @@ bench = false anyhow = { workspace = true } either = { workspace = true } flate2 = { workspace = true } -hashbrown = { workspace = true, features = ["raw"] } +hashbrown = { workspace = true, features = ["raw-entry"] } indexmap = { workspace = true, features = ["serde"] } itertools = { workspace = true } postcard = { workspace = true }