Skip to content

Commit aabf4a1

Browse files
committed
Merge branch 'main' into release
2 parents 0c1a0ff + 45554ad commit aabf4a1

7 files changed

Lines changed: 307 additions & 75 deletions

File tree

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: 🗑️ Delete Changeset Bot Comments
2+
3+
on:
4+
workflow_dispatch:
5+
6+
jobs:
7+
delete-comments:
8+
name: 🗑️ Delete Changeset Bot Comments
9+
if: github.repository == 'remix-run/react-router'
10+
runs-on: ubuntu-latest
11+
permissions:
12+
pull-requests: write
13+
14+
steps:
15+
- name: ⬇️ Checkout repo
16+
uses: actions/checkout@v6
17+
18+
- name: 📦 Setup pnpm
19+
uses: pnpm/action-setup@v4
20+
21+
- name: ⎔ Setup node
22+
uses: actions/setup-node@v6
23+
with:
24+
node-version-file: ".nvmrc"
25+
cache: pnpm
26+
27+
- name: 📥 Install deps
28+
run: pnpm install --frozen-lockfile
29+
30+
- name: 🗑️ Delete changeset-bot comments
31+
env:
32+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
33+
run: |
34+
node scripts/changes/delete-changeset-bot-comments.ts

CHANGELOG.md

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -174,15 +174,9 @@ Date: 2026-04-13
174174

175175
### Patch Changes
176176

177-
- `react-router` - Fix a potential race condition that can occur when rendering a `HydrateFallback` and initial loaders land before the `router.subscribe` call happens in the `RouterProvider` layout effect
178-
- `react-router` - Normalize double-slashes in redirect paths
179-
- `@react-router/architect` - Add TypeScript 6 support to peer dependency ranges
180-
- `@react-router/cloudflare` - Add TypeScript 6 support to peer dependency ranges
181-
- `@react-router/dev` - Add TypeScript 6 support to peer dependency ranges
182-
- `@react-router/express` - Add TypeScript 6 support to peer dependency ranges
183-
- `@react-router/fs-routes` - Add TypeScript 6 support to peer dependency ranges
184-
- `@react-router/node` - Add TypeScript 6 support to peer dependency ranges
185-
- `@react-router/remix-routes-option-adapter` - Add TypeScript 6 support to peer dependency ranges
177+
- `react-router` - Fix a potential race condition that can occur when rendering a `HydrateFallback` and initial loaders land before the `router.subscribe` call happens in the `RouterProvider` layout effect ([#14497](https://github.com/remix-run/react-router/pull/14497))
178+
- `react-router` - Normalize double-slashes in redirect paths ([#14962](https://github.com/remix-run/react-router/pull/14962))
179+
- `@react-router/dev` - Add TypeScript 6 support to peer dependency ranges ([#14935](https://github.com/remix-run/react-router/pull/14935))
186180

187181
**Full Changelog**: [`v7.14.0...v7.14.1`](https://github.com/remix-run/react-router/compare/react-router@7.14.0...react-router@7.14.1)
188182

docs/explanation/styling.md

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
---
2+
title: Styling
3+
---
4+
5+
# Styling
6+
7+
[MODES: framework]
8+
9+
<br/>
10+
<br/>
11+
12+
Framework mode uses the React Router Vite plugin, so the styling story is mostly just Vite's styling story.
13+
14+
React Router does not have a separate CSS pipeline for Framework mode. In practice, there are three patterns that matter:
15+
16+
1. Import CSS as a side effect
17+
2. Use the route module `links` export
18+
3. Render a stylesheet `<link>` directly
19+
20+
## Side-Effect CSS Imports
21+
22+
Because Framework mode uses Vite, you can import CSS files as side effects:
23+
24+
```tsx filename=app/root.tsx
25+
import "./app.css";
26+
```
27+
28+
```tsx filename=app/routes/dashboard.tsx
29+
import "./dashboard.css";
30+
```
31+
32+
This is often the simplest option. Global styles can be imported in `root.tsx`, and route or component styles can be imported next to the module that uses them.
33+
34+
## `links` Export
35+
36+
React Router also supports adding stylesheets through the route module `links` export.
37+
38+
This is useful when you want a stylesheet URL from Vite and need React Router to render a real `<link rel="stylesheet">` tag for the route:
39+
40+
```tsx filename=app/routes/dashboard.tsx
41+
import dashboardHref from "./dashboard.css?url";
42+
43+
export function links() {
44+
return [{ rel: "stylesheet", href: dashboardHref }];
45+
}
46+
```
47+
48+
The `links` export feeds the [`<Links />`][links-component] component in your root route. This is the React Router-specific styling API in Framework mode. For more on route module exports, see [Route Module][route-module].
49+
50+
## Direct `<link>` Rendering
51+
52+
If you're using React 19, you can also render a stylesheet `<link>` directly in your route component:
53+
54+
```tsx filename=app/routes/dashboard.tsx
55+
import dashboardHref from "./dashboard.css?url";
56+
57+
export default function Dashboard() {
58+
return (
59+
<>
60+
<link
61+
rel="stylesheet"
62+
href={dashboardHref}
63+
precedence="default"
64+
/>
65+
<h1>Dashboard</h1>
66+
</>
67+
);
68+
}
69+
```
70+
71+
This uses React's built-in [`<link>`][react-link] support, which hoists the stylesheet into the document `<head>`. That gives you another way to colocate stylesheet tags with the route that needs them.
72+
73+
## Everything Else
74+
75+
For CSS Modules, Tailwind, PostCSS, Sass, Vanilla Extract, and other styling tools, use the normal Vite setup for those tools.
76+
77+
See:
78+
79+
- [Vite CSS Features][vite-css]
80+
- [Vite Static Asset Handling][vite-assets]
81+
- [`<Links />`][links-component]
82+
83+
[links-component]: ../api/components/Links
84+
[react-link]: https://react.dev/reference/react-dom/components/link
85+
[route-module]: ../start/framework/route-module
86+
[vite-assets]: https://vite.dev/guide/assets.html
87+
[vite-css]: https://vite.dev/guide/features.html#css

docs/start/framework/route-module.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,10 @@ export default function Root() {
411411
}
412412
```
413413

414+
See also:
415+
416+
- [Styling][styling]
417+
414418
## `meta`
415419

416420
Route meta defines [meta tags][meta-element] to be rendered in the `<Meta />` component, usually placed in the `<head>`.
@@ -520,3 +524,4 @@ Next: [Rendering Strategies](./rendering)
520524
[data-mode-should-revalidate]: ../data/route-object#shouldrevalidate
521525
[spa-mode]: ../../how-to/spa
522526
[client-data]: ../../how-to/client-data
527+
[styling]: ../../explanation/styling

scripts/changes/add.ts

Lines changed: 66 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,72 @@ import * as path from "node:path";
99
import prompts from "prompts";
1010
import { getAllPackageDirNames, getPackagePath } from "../utils/packages.ts";
1111

12+
// Common English stop words that add no meaning to a filename slug
13+
const STOP_WORDS = new Set([
14+
"a",
15+
"an",
16+
"the",
17+
"and",
18+
"or",
19+
"but",
20+
"so",
21+
"nor",
22+
"yet",
23+
"in",
24+
"on",
25+
"at",
26+
"by",
27+
"for",
28+
"to",
29+
"of",
30+
"from",
31+
"with",
32+
"into",
33+
"onto",
34+
"about",
35+
"as",
36+
"via",
37+
"is",
38+
"are",
39+
"was",
40+
"were",
41+
"be",
42+
"been",
43+
"being",
44+
"have",
45+
"has",
46+
"had",
47+
"do",
48+
"does",
49+
"did",
50+
"it",
51+
"its",
52+
"this",
53+
"that",
54+
"these",
55+
"those",
56+
"we",
57+
"us",
58+
"our",
59+
"i",
60+
"me",
61+
"my",
62+
"you",
63+
"your",
64+
"he",
65+
"him",
66+
"his",
67+
"she",
68+
"her",
69+
"they",
70+
"them",
71+
"their",
72+
"now",
73+
"then",
74+
"also",
75+
"just",
76+
]);
77+
1278
const bumpTypes = ["patch", "minor", "major", "unstable"] as const;
1379

1480
interface Package {
@@ -121,72 +187,6 @@ function getPackages(): Package[] {
121187
});
122188
}
123189

124-
// Common English stop words that add no meaning to a filename slug
125-
const STOP_WORDS = new Set([
126-
"a",
127-
"an",
128-
"the",
129-
"and",
130-
"or",
131-
"but",
132-
"so",
133-
"nor",
134-
"yet",
135-
"in",
136-
"on",
137-
"at",
138-
"by",
139-
"for",
140-
"to",
141-
"of",
142-
"from",
143-
"with",
144-
"into",
145-
"onto",
146-
"about",
147-
"as",
148-
"via",
149-
"is",
150-
"are",
151-
"was",
152-
"were",
153-
"be",
154-
"been",
155-
"being",
156-
"have",
157-
"has",
158-
"had",
159-
"do",
160-
"does",
161-
"did",
162-
"it",
163-
"its",
164-
"this",
165-
"that",
166-
"these",
167-
"those",
168-
"we",
169-
"us",
170-
"our",
171-
"i",
172-
"me",
173-
"my",
174-
"you",
175-
"your",
176-
"he",
177-
"him",
178-
"his",
179-
"she",
180-
"her",
181-
"they",
182-
"them",
183-
"their",
184-
"now",
185-
"then",
186-
"also",
187-
"just",
188-
]);
189-
190190
/**
191191
* Converts a free-text description into a kebab-case slug of at most 6
192192
* meaningful words. Stop words and non-alphanumeric characters are stripped.
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/**
2+
* Finds and deletes comments from `changeset-bot` on open PRs created since 1/1/2026
3+
*
4+
* Usage:
5+
* node scripts/changes/delete-changeset-bot-comments.ts [--dry-run]
6+
*
7+
* Environment:
8+
* GITHUB_TOKEN - Required. GitHub token with pull-requests:write permission.
9+
*/
10+
import {
11+
createPrComment,
12+
deletePrComment,
13+
getPrComments,
14+
listOpenPrs,
15+
} from "../utils/github.ts";
16+
17+
const CHANGESET_BOT = "changeset-bot[bot]";
18+
const CUTOFF = new Date(2026, 0, 1);
19+
20+
const ADD_CHANGE_FILE =
21+
"👋 We've moved away from Changesets to our own internal " +
22+
"[changes process](https://github.com/remix-run/react-router/blob/main/docs/community/contributing.md#change-files). " +
23+
"Please manually add a change file to this branch, or you can merge in the " +
24+
"latest `dev` branch and run `pnpm run changes:add` to add a change file.";
25+
26+
const MIGRATE_CHANGE_FILE =
27+
"👋 We've moved away from Changesets to our own internal " +
28+
"[changes process](https://github.com/remix-run/react-router/blob/main/docs/community/contributing.md#change-files). " +
29+
"Please convert your changesets file to a change file in the proper package directory " +
30+
"(i.e., `packages/react-router/.changes/patch.fix-some-bug.md`).";
31+
32+
const dryRun = process.argv.includes("--dry-run");
33+
34+
if (dryRun) {
35+
console.log("[DRY RUN] No comments will be deleted.\n");
36+
}
37+
38+
console.log(
39+
`Fetching open PRs created after ${CUTOFF.toISOString().slice(0, 10)}...\n`,
40+
);
41+
42+
let prs = await listOpenPrs({
43+
createdAfter: CUTOFF,
44+
base: "dev",
45+
});
46+
console.log(`Found ${prs.length} open PR${prs.length === 1 ? "" : "s"}.\n`);
47+
48+
let totalDeleted = 0;
49+
let totalSkipped = 0;
50+
51+
for (let pr of prs) {
52+
let comments = await getPrComments(pr.number);
53+
let botComments = comments.filter((c) => c.user?.login === CHANGESET_BOT);
54+
55+
if (botComments.length === 0) continue;
56+
57+
console.log(`PR #${pr.number}: ${pr.title}`);
58+
59+
for (let comment of botComments) {
60+
let preview = (comment.body ?? "").slice(0, 80).replace(/\n/g, " ");
61+
if (dryRun) {
62+
console.log(
63+
` [DRY RUN] Would delete comment #${comment.id}: "${preview}"`,
64+
);
65+
totalSkipped++;
66+
} else {
67+
let hasChangeFile = comment.body?.includes("Changeset detected");
68+
await deletePrComment(comment.id);
69+
await createPrComment(
70+
pr.number,
71+
hasChangeFile ? MIGRATE_CHANGE_FILE : ADD_CHANGE_FILE,
72+
);
73+
console.log(` Deleted comment #${comment.id}: "${preview}"`);
74+
totalDeleted++;
75+
}
76+
}
77+
78+
console.log();
79+
}
80+
81+
if (dryRun) {
82+
console.log(
83+
`Done (dry run): ${totalSkipped} comment${totalSkipped === 1 ? "" : "s"} would be deleted.`,
84+
);
85+
} else {
86+
console.log(
87+
`Done: ${totalDeleted} comment${totalDeleted === 1 ? "" : "s"} deleted.`,
88+
);
89+
}

0 commit comments

Comments
 (0)