Replace next-ppr-optimizer with next-cache-components-optimizer (#94035)
## Summary
Replaces `next-ppr-optimizer` (shipped in #93943) with a merged
`next-cache-components-optimizer` skill that covers both page-render and
in-app-nav optimization. The two diagnostics share a preflight, lever
set, anti-pattern list, and reference table; the gating requirement
(`cacheComponents: true`) is what unifies them, hence the rename.
- `SKILL.md` — top-level router; shared preflight, shared `instant
cookie` section (value tuple + set command + p-prefix convention — both
loops reference this rather than duplicating it), refactor levers,
plan-mode gate, no-shell bailout, visible-delta verify (with required
screenshots and dev-overlay-hidden capture), anti-patterns, gotchas
(including a concrete DOM-settle poll and a flakiness re-run rule),
reference table with shapes for the remaining primitives (`agent-browser
react suspense --only-dynamic --json` boundary schema + raw blocker
names, `__nextjs_original-stack-frames` request body, `mcp get_logs`
origin, `cacheLife` profiles), teardown.
- `ppr-loop.md` — page-render diagnose loop. Same `react suspense
--only-dynamic --json` primitive as the nav loop (consistency);
pixel-area ranking on shell-only render; gauge step that skips marginal
refactors.
- `instant-nav-loop.md` — nav diagnose loop. Single capture on B
post-`pushstate` via `react suspense --only-dynamic --json`; each
boundary's `suspended_by[].name` is classified into client-hook /
request-api / server-fetch / cache. **Client-hook blockers
(`usePathname`, `useSearchParams`, `useRouter`, …) are dropped** — they
suspend only during SSR prerender and resolve instantly on SPA nav, so
"fixing" them is wasted work. Shared layouts (the path A and B have in
common) are filtered out implicitly — React keeps them mounted across
the navigation, so their boundaries don't re-suspend and never enter the
capture. With multiple real candidates, rank by where the boundary sits
in B's new-segments tree; fix the one closest to where A's and B's paths
diverge first. Plain-language framing throughout (no LCA/graph-theory
jargon). Includes a gauge step that skips marginal refactors. Plus the
nav-only **third lever**: `'use cache: private'` for I/O reading
`cookies()` / `headers()` / `searchParams`, paired with
`unstable_prefetch = 'force-runtime'` on a route segment that owns the
private content. The framework resolves private-cached content at
link-prefetch time so the click commits with cookie-derived data already
in place.
## Why
- **Dedup.** Both loops share the same refactor levers (push-down +
cache), `cacheLife` profile list, instant-cookie protocol, null-fallback
anti-pattern, no-shell bailout, DOM-settle polling pattern, and teardown
— no duplication across files.
- **Discoverability.** \`cache-components\` matches the canonical config
flag developers search for.
## Workflow guarantees
- **Manual user setup precondition.** The skill doesn't drive
auth/SSO/MFA. The user opens the headed browser (via `next-dev-loop`),
logs in if needed, and navigates to the starting page; only then does
the skill take over. Spelled out in both shared and per-loop preflights.
- **Plan mode for every refactor.** Agent presents a plan (candidate,
lever, file path, expected delta, freshness preset for cache) before
applying. User can iterate before agreeing.
- **Visible-delta verify.** A baseline screenshot is taken before any
change; an after-screenshot is taken in verify. Both paths are reported
in the final summary. Identical-looking captures = refactor didn't land
→ undo. Nav loop captures via cookie-locked SPA-nav (be on A → set
cookie → `pushstate <B>` → settle → screenshot); page-render loop
captures via shell-only render.
- **Dev overlay hidden during capture.** `nextjs-portal` (instant-nav
guidance, error overlay, route indicators) is hidden via JS, screenshot
taken, restored — so the before/after isn't polluted by dev chrome.
- **No-shell bailout.** If the route is fully blocking (HTTP 500 with
`blocking-route` / `NEXT_STATIC_GEN_BAILOUT`, or zero Suspense
boundaries on a visibly-rendered page), the optimizer stops; the user
fixes the structural wrap first.
- **Gauge before applying.** Each loop's diagnose includes a gauge step
using the same capture as verify (shell-only render for PPR,
cookie-locked SPA-nav for nav). If the top-ranked candidate is
sub-viewport, the optimizer offers to audit other routes / nav pairs
instead of forcing a low-ROI refactor.
## Validation
Headless evals against a `cacheComponents` demo app via `claude -p`:
| Scenario | Outcome |
|---|---|
| Already-optimal page (no-fix branch) | Declined refactor correctly |
| Unwrapped page-level \`await\` (push-down) | Extracted I/O into
Suspense-wrapped child, autonomous |
| Uncached real I/O (cache + collab gate) | Asked for \`cacheLife\`,
applied to helper |
| Multi-candidate w/ recursion trap | Ranked by LCA-most-first, recursed
into wrapper rather than blind-wrap |
| PPR-flavored prompt → ppr-loop | Decision step picked the right
sub-loop; sub-reference loaded correctly |
| Nav-flavored prompt → instant-nav-loop (polished) | Decision routing +
terser prose preserved behavior end-to-end |
## Notes
- Removes `skills/next-ppr-optimizer/` shipped in #93943.
- A drafted `next-instant-nav-optimizer` was built during development
but never committed; not part of this diff.
## Test plan
- [ ] Manual: invoke \`/next-cache-components-optimizer\` against a
\`cacheComponents\` app; confirm the agent picks the right sub-loop
based on the request phrasing.
- [ ] Manual: confirm the preflight inherits from \`next-dev-loop\`
correctly (user-driven browser session must be open first).
<!-- NEXT_JS_LLM_PR -->