### What
The generated `nextjs-agent-rules` block in `AGENTS.md` glued the `<!--
BEGIN:nextjs-agent-rules -->` / `<!-- END:nextjs-agent-rules -->`
comment markers directly against the `#` heading and the surrounding
paragraphs:
```md
<!-- BEGIN:nextjs-agent-rules -->
# This is NOT the Next.js you know
...
<!-- END:nextjs-agent-rules -->
```
Markdown formatters (Prettier, and CommonMark tooling in general) treat
block-level HTML comments as needing surrounding blank lines, so
formatting an `AGENTS.md` reflows the block:
```md
<!-- BEGIN:nextjs-agent-rules -->
# This is NOT the Next.js you know
...
<!-- END:nextjs-agent-rules -->
```
On the next `next dev`, the generator detects the block as non-current
and rewrites it back to the glued form. For any project that runs
Prettier over `AGENTS.md`, this produces churn on every commit — the
opposite of what the block's own "commit it to keep the tree clean" note
promises.
### Change
Emit blank lines around the markers so the generated block is
formatter-stable. Both generators are updated together:
- `packages/next/src/server/lib/generate-agent-files.ts` (`next dev`
writer)
- `packages/create-next-app/helpers/generate-agent-files.ts`
The `next-codemod` `agents-md` refresh delegates to the installed
package's generator, so it needs no change. The docs sample in
`ai-agents.mdx` and this repo's own `packages/next/AGENTS.md` already
use the blank-line shape, so they now match what's generated.
### Notes
- Existing projects get a one-time block rewrite on their next `next
dev` (expected — the block is described as re-added by `next dev`),
after which it stays stable under formatting.
- The `agent-rules-auto-generate` test asserts against the live
generator output, so it tracks the new shape automatically.
Agents have started flagging the managed `nextjs-agent-rules` block as
prompt injection and refusing to follow or commit it (#95130). Headless
A/B evals (Claude Code + Sonnet 5, pnpm-monorepo and standalone-npm
fixtures, `next@16.3.0-preview.5`) isolate the triggers:
`node_modules/next/dist/docs/` doesn't resolve from the workspace root
in non-hoisted installs, the "maintained by `next dev`" claim is
unverifiable, and the **Keep this block** paragraph reads as a
self-persisting instruction. The failure concentrates on the uncommitted
state a fresh `next dev` run produces.
The block now anchors the docs path to the block file's directory with a
monorepo note, and replaces the commit imperative with a verify pointer
to `generate-agent-files.js` plus the reason committing it keeps the
tree clean. This is the only change in this PR, and it was ablated in
isolation — an A/A run of the baseline first established the noise floor
(block committal 0/5, 0/8, 1/8 across three independent baseline
batches):
| Metric | Old block | New block |
|
---------------------------------------------------------------------------------
| --------- | --------- |
| Uncommitted block, diff-review prompt — committed the block | 1/21 |
8/20 |
| Uncommitted block, diff-review prompt — final message calls it a
prompt injection | 12/21 | 4/20 |
| Committed block, task prompts — injection flags | 0/15 | 0/15 |
| Docs reads/run — standalone app, write tests (n=2) | 4.5 | 5.0 |
| Docs reads/run — monorepo, add page (n=3) | 3.7 | 4.0 |
| Docs reads/run — monorepo, write tests (n=10) | 3.4 | 2.6 |
Committal improves at p = 0.008 and injection accusations drop at p =
0.016 (Fisher exact); runs that follow the verify pointer conclude
"genuine framework-scaffolded content, not injected". A candidate
follow-up — appending "commit this change" to the dev-server log line —
was ablated separately and does nothing: the line reaches the agent's
transcript in 12/12 dev-session runs but committal stayed 0/6 vs 0/6, so
it is not included. The upsert machinery that delivers this rewording to
existing projects is #95470.
## What?
Render the filename in the "edit the … file" line using **Geist Mono**
across all `create-next-app` templates, so the preloaded font is
actually used.
## Why?
Every fresh app logged a browser warning: the Geist Mono `.woff2` is
preloaded but never used, because the redesign in #81945 dropped the
last monospace element (the old `<code>app/page.tsx</code>`). Rather
than stop preloading the font (this PR's original approach), we _use_ it
— the filename renders in Geist Mono. That clears the warning at its
source and keeps the template showcasing both flagship fonts (two fonts,
no flash of unstyled text).
## How?
- Reverted the `preload: false` change.
- Wrapped the filename in the intro line in a `<code>` rendered in Geist
Mono, styled as a small inline code chip (slightly smaller size, subtle
rounded background, padding) so it reads as a code token rather than
plain heading text:
- Tailwind templates → utility classes (`font-mono` maps to
`--font-geist-mono` via `@theme inline`).
- CSS-module templates → a `.code` class using `var(--font-geist-mono)`;
the chip background uses `color-mix(currentColor …)` so it adapts to
light/dark automatically.
- Applied across all four families (`app`, `app-tw`, `default`,
`default-tw`), JS + TS.
## Screenshots
| | Before | After |
| --------- | --------------------------------------------------- |
-------------------------------------------- |
| **Light** | <img width="2400" height="1520"
alt="cna-geist-mono-before"
src="https://github.com/user-attachments/assets/cf374265-7fdf-4c3b-9f66-05436f966fc9"
/> | <img width="2400" height="1520" alt="cna-geist-mono-after"
src="https://github.com/user-attachments/assets/ae55166d-26d8-4cc2-8268-90c5996d2431"
/> |
| **Dark** | <img width="2400" height="1520"
alt="cna-geist-mono-before-dark"
src="https://github.com/user-attachments/assets/d813adac-85c0-4044-813f-9489b0b4faa7"
/> | <img width="2400" height="1520" alt="cna-geist-mono-after-dark"
src="https://github.com/user-attachments/assets/01199df9-4842-46e0-a73e-a3ba0ef78ad2"
/> |
Before: filename in Geist Sans, preloaded-but-unused warning in the
console. After: filename in Geist Mono, no warning.
<!-- NEXT_JS_LLM_PR -->
## Summary
The default `create-next-app` Tailwind templates (`app-tw` and
`default-tw`) intermittently log the `next/image` warning "Image with
src … has either width or height modified, but not the other" for the
`next.svg` and `vercel.svg` logos.
Tailwind v4 Preflight ships `img { height: auto }`, which overrides the
`height` attribute `next/image` writes. The browser then computes height
from `width × the SVG viewBox ratio`. The declared dimensions do not
match the viewBox ratio (`vercel.svg`: `16×14` vs viewBox `1155:1000`;
`next.svg`: `100×20` vs viewBox `394:80`), so the computed height is
fractional (`13.85px`, `20.30px`) and snaps to a different integer
depending on SVG decode timing, Fast Refresh reloads, and device pixel
ratio / zoom. That timing/rounding sensitivity is why the warning
appears only sometimes.
This pins both axes with utility classes (`h-5 w-[100px]` on `next.svg`,
`h-[14px] w-4` on `vercel.svg`) so neither dimension falls back to
`auto`. The rendered size always matches the declared props, so the
check cannot trip, at any DPR.
Only the Tailwind templates are affected; the non-Tailwind
`app`/`default` templates have no `img { height: auto }` reset and do
not produce this warning.
This extends the both-axes approach from #95252 to `default-tw` as well.
#93826 adjusted the dimensions but the mismatch remained (`13.85 ≠ 14`).
## Verification
- `prettier --check` on the four changed files passed.
- Not run: `create-next-app-default` regression test. It runs headless
at DPR 1, where the fractional-pixel rounding does not occur, so it
passed even while the warning was present and is not a reliable oracle
here. Reproduced/validated by reasoning about Preflight specificity and
the removed computed axis.
<!-- NEXT_JS_LLM_PR -->