From 2843941aff9b1cc1a5fb92eda707be1125c700f6 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Fri, 5 Jun 2026 10:41:48 -0400 Subject: [PATCH 1/4] chore: remove lingering dev branch references --- packages/react-router/__tests__/router/fetchers-test.ts | 2 +- scripts/pr.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-router/__tests__/router/fetchers-test.ts b/packages/react-router/__tests__/router/fetchers-test.ts index fea1fc24b4..ba9db91f08 100644 --- a/packages/react-router/__tests__/router/fetchers-test.ts +++ b/packages/react-router/__tests__/router/fetchers-test.ts @@ -3807,7 +3807,7 @@ describe("fetchers", () => { }); it("does not mutate the Map reference handed to subscribers (fetcher revalidation during navigation)", async () => { - // getUpdatedRevalidatingFetchers() (dev branch) calls state.fetchers.set() + // getUpdatedRevalidatingFetchers() calls state.fetchers.set() // on the current Map before returning a copy. This mutates MapPrev. // Later, processLoaderData mutates the Map that subscribers received for // the "loading" revalidation state. Test that the subscriber's loading diff --git a/scripts/pr.ts b/scripts/pr.ts index 42b5339630..a0e31a4ef2 100644 --- a/scripts/pr.ts +++ b/scripts/pr.ts @@ -132,7 +132,7 @@ async function runChecks() { } async function changeFileCheck(ctx: CheckContext): Promise { - if (ctx.baseBranch !== "dev") return []; + if (ctx.baseBranch !== "main") return []; if (!["opened", "synchronize", "reopened"].includes(ctx.eventAction)) { return []; } From 2b0a3e30ae6a2af0dd9f49e6dc167a578035c4b3 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Fri, 5 Jun 2026 11:37:35 -0400 Subject: [PATCH 2/4] Label release PRs --- .github/workflows/release.yml | 1 + scripts/changes/pr.ts | 20 +++++++++++++++++++- scripts/utils/github.ts | 11 +++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 49d25fe4be..f4c76e5aef 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -72,6 +72,7 @@ jobs: runs-on: ubuntu-latest permissions: contents: write # enable pushing changes to the origin + issues: write # enable applying labels to the release PR pull-requests: write # enable opening a PR for the release steps: - name: Checkout diff --git a/scripts/changes/pr.ts b/scripts/changes/pr.ts index 875032e908..17cef96c93 100644 --- a/scripts/changes/pr.ts +++ b/scripts/changes/pr.ts @@ -7,7 +7,13 @@ * Environment: * GITHUB_TOKEN - Required (unless --preview) */ -import { closePr, createPr, findOpenPr, updatePr } from "../utils/github.ts"; +import { + addPrLabels, + closePr, + createPr, + findOpenPr, + updatePr, +} from "../utils/github.ts"; import { logAndExec } from "../utils/process.ts"; import type { PackageRelease } from "./changes.ts"; import { @@ -25,6 +31,7 @@ if (!preview && !["main", "hotfix"].includes(baseBranch)) { } let prBranch = baseBranch === "hotfix" ? "hotfix-pr" : "release-pr"; +let prLabels = ["pkg:react-router"]; // GitHub has a 65,536 character limit for PR body. We use 60,000 to be safe. let maxBodyLength = 60_000; @@ -135,6 +142,13 @@ async function main() { head: prBranch, base: baseBranch, }); + try { + await addPrLabels(newPr.number, prLabels); + } catch (error) { + console.warn( + `⚠️ Unable to add labels (${prLabels.join(", ")}) to PR #${newPr.number}: ${getErrorMessage(error)}`, + ); + } console.log(`\n✅ Created PR #${newPr.number}: ${newPr.html_url}`); } } @@ -213,6 +227,10 @@ function generatePackageChangelog(release: PackageRelease): string { }); } +function getErrorMessage(error: unknown): string { + return error instanceof Error ? error.message : String(error); +} + function truncateChangelogs( releases: PackageRelease[], maxLength: number, diff --git a/scripts/utils/github.ts b/scripts/utils/github.ts index 4338937465..594b2578fd 100644 --- a/scripts/utils/github.ts +++ b/scripts/utils/github.ts @@ -238,6 +238,17 @@ export async function deletePrComment(commentId: number) { }); } +/** + * Add labels to a PR (or issue) + */ +export async function addPrLabels(prNumber: number, labels: string[]) { + await request("POST /repos/{owner}/{repo}/issues/{issue_number}/labels", { + ...requestOptions(), + issue_number: prNumber, + labels, + }); +} + /** * Remove a label from a PR (or issue) */ From f2283a0176eea8aeec34fe32958133b9a9a84c82 Mon Sep 17 00:00:00 2001 From: Zelys Date: Fri, 5 Jun 2026 18:33:51 +0200 Subject: [PATCH 3/4] Add regression test for catchall route meta disappearing on hash navigation (#15154) --- contributors.yml | 1 + integration/fog-of-war-test.ts | 91 ++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/contributors.yml b/contributors.yml index 06bbf577e4..1322cdb5cf 100644 --- a/contributors.yml +++ b/contributors.yml @@ -501,6 +501,7 @@ - yuleicul - yuri-poliantsev - zeevick10 +- Zelys-DFKH - zeromask1337 - zeroqs - zheng-chuang diff --git a/integration/fog-of-war-test.ts b/integration/fog-of-war-test.ts index e2ed5f57c9..5c415455e4 100644 --- a/integration/fog-of-war-test.ts +++ b/integration/fog-of-war-test.ts @@ -1531,6 +1531,97 @@ test.describe("Fog of War", () => { expect(currentUrl).toContain("#section1"); }); + test("Preserves meta tags on hash links in splat routes", async ({ + page, + }) => { + let fixture = await createFixture({ + files: { + "app/routes.ts": js` + import { type RouteConfig, index, route } from "@react-router/dev/routes"; + export default [ + index("routes/_index.tsx"), + route("*", "routes/catchall.tsx"), + ] satisfies RouteConfig; + `, + "app/root.tsx": js` + import { Links, Meta, Outlet, Scripts } from "react-router"; + export default function Root() { + return ( + + + + + + + + + + + ); + } + `, + "app/routes/_index.tsx": js` + import { Link } from "react-router"; + export function meta() { + return [{ title: "Home" }]; + } + export default function Index() { + return ( +
+

Home

+ + Go to catchall + +
+ ); + } + `, + "app/routes/catchall.tsx": js` + import { Link } from "react-router"; + export function meta() { + return [{ title: "Catchall" }]; + } + export default function Catchall() { + return ( +
+

Catchall route

+ Hash link +
+ ); + } + `, + }, + }); + + let appFixture = await createAppFixture(fixture); + let app = new PlaywrightFixture(appFixture, page); + + // / => /catch-all => /catch-all#hash + await app.goto("/"); + expect(await page.title()).toBe("Home"); + await page.waitForSelector("[data-testid='go-catchall']"); + await page.click("[data-testid='go-catchall']"); + await page.waitForSelector("[data-testid='catchall-heading']"); + expect(await page.title()).toBe("Catchall"); + + await page.click("[data-testid='hash-link']"); + // Hash navigation doesn't trigger a load event; waitForFunction polls the DOM directly + await page.waitForFunction(() => window.location.hash === "#hash"); + expect(await page.title()).toBe("Catchall"); + + // /catch-all => /catch-all#hash + await app.goto("/catchall"); + await page.waitForSelector("[data-testid='catchall-heading']"); + expect(await page.title()).toBe("Catchall"); + + await page.click("[data-testid='hash-link']"); + // Hash navigation doesn't trigger a load event; waitForFunction polls the DOM directly + await page.waitForFunction(() => window.location.hash === "#hash"); + expect(await page.title()).toBe("Catchall"); + + appFixture.close(); + }); + test.describe("routeDiscovery=initial", () => { test("loads full manifest on initial load", async ({ page }) => { let fixture = await createFixture({ From 00aa485c80b1907cd062743bc0714cc6b46b204d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20L=C3=AA?= <115204145+DucMinhNe@users.noreply.github.com> Date: Fri, 5 Jun 2026 23:49:40 +0700 Subject: [PATCH 4/4] docs: fix Headers.append() call to use two arguments (#15149) * docs: fix Headers.append() call to use two arguments * chore: sign the Remix CLA --- contributors.yml | 1 + docs/how-to/headers.md | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/contributors.yml b/contributors.yml index 1322cdb5cf..c7d2621cd0 100644 --- a/contributors.yml +++ b/contributors.yml @@ -121,6 +121,7 @@ - dokeet - doytch - Drishtantr +- DucMinhNe - EdgeOneDev - edmundhung - edwin177 diff --git a/docs/how-to/headers.md b/docs/how-to/headers.md index c04c144080..4501951099 100644 --- a/docs/how-to/headers.md +++ b/docs/how-to/headers.md @@ -110,7 +110,8 @@ The easiest way is to simply append to the parent headers. This avoids overwriti ```tsx export function headers({ parentHeaders }: HeadersArgs) { parentHeaders.append( - "Permissions-Policy: geolocation=()", + "Permissions-Policy", + "geolocation=()", ); return parentHeaders; }