Hard-navigate to app routes shadowed by a pages dynamic route (#95185)
When an App Router route begins with a dynamic segment (for example
`app/[locale]/about`), a client-side navigation from a Pages Router page
could render the wrong route. The Pages Router resolves the destination
against its own routes only, so `/en/about` would match a less specific
Pages Router dynamic route such as `pages/[locale]/[category]` instead
of the App Router page. The server pools app and pages routes and ranks
them by specificity, so a hard reload always rendered the correct App
Router page; only the client-side soft navigation diverged.
The client router filter is responsible for detecting destinations owned
by the other router and forcing a hard navigation, but
`createClientRouterFilter` only recorded the static prefix of a dynamic
route. App routes whose first segment is dynamic have no static prefix,
so they contributed nothing to the filter and the Pages Router never
learned to hand them off. The filter now also stores a normalized
pattern for those routes, with dynamic segments replaced by a
placeholder token (`/[locale]/about` becomes `/[]/about`). After the
Pages Router resolves a navigation to a dynamic route,
`hasDynamicFilterCandidate` reconstructs the candidate app-route
patterns from that route and the concrete path and triggers a hard
navigation when any candidate is present in the dynamic filter. This
also covers catch-all and optional catch-all pages routes, whose final
parameter absorbs a variable number of segments. The shared placeholder
token and the client-side check live in the new
`dynamic-filter-pattern.ts`, while the build-only encoder stays in
`create-client-router-filter.ts`.
The change only ever turns a soft navigation into a hard navigation, and
only when an app route is genuinely more specific than the resolved
pages route, so a structural match always agrees with the server's
resolution. Apps without dynamic app routes are unaffected because the
dynamic filter stays empty.
The `pages-to-app-routing` end-to-end suite is restructured around a
`fixtures/` directory and gains coverage for the dynamic-segment
shadowing, the catch-all and optional catch-all variants, `basePath`,
middleware, and a guard that legitimate Pages Router dynamic routes
still navigate client-side. The first request to a not-yet-compiled
dynamic route can return a transient 404 in development, a pre-existing
source of CI flakiness, so the tests now warm the route up with a direct
request before driving the browser.
fixes #74696