* fix(a11y): skip alt-text on Next.js metadata image route files
Next.js App Router treats `opengraph-image.tsx`, `twitter-image.tsx`,
`icon.tsx`, and `apple-icon.tsx` (with optional numeric suffix) as
metadata image routes. Their default export returns an `ImageResponse`
from `next/og` that rasterizes JSX into a static PNG/JPG — no browser
DOM, no screen reader, so `alt` / `aria-*` are unactionable noise.
Extract `isNextjsMetadataImageRouteFilename` as a shared util (so other
DOM-flavoured a11y rules can opt in later) and early-return from
`alt-text.create()` when the filename matches. Adds a regression suite
covering both extensions, numeric suffixes, and the negative case
(`my-opengraph-image.tsx` still flags).
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore(architecture): demote only-export-components to warn
Mixing non-component exports in a component module is a Fast Refresh
hint, not a hard failure — projects routinely co-locate stable
constants alongside components and ship fine. Drop from `error` to
`warn` so triage output reflects the actual severity gradient.
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore(architecture): unregister react-compiler-destructure-method
The React-Compiler memoization premise didn't hold up for the three
canonical hooks the rule targeted: `useRouter`, `useSearchParams`, and
`useNavigation` all return stable references, so destructuring their
methods produces no measurable compiler win. On Pages Router
(`next/router`) destructuring `push` is actively wrong — it captures
a stale reference.
Introduce a `RULE_IDS_TO_SKIP_REGISTRATION` skiplist in the registry
generator (with a required justification comment per entry) so we can
park a rule without deleting its implementation. Drops the rule from
the registered set (297 → 296), `describe.skip`'s the regression
suite, and prunes the rule from the vercel-skill parity table. The
fixture seed is kept so re-enabling is a one-line skiplist edit.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(state-and-effects): promote no-adjust-state-on-prop-change to error
Adjusting derived state inside a useEffect when a prop changes always
forces an extra render with a stale UI between the two commits —
there is no benign instance of this pattern, so a warning understates
the cost. Promote to `severity: "error"` and rewrite the recommendation
and message in the authoritative "what's wrong → why → fix" shape used
by the rest of the error-level effect rules.
Upstream `eslint-plugin-react-you-might-not-need-an-effect` ships this
as a softened `type: "suggestion"`. Document the intentional divergence
(severity + copy only — detector logic is still a 1:1 port) in
`effect/SOURCE.md`, and update the registered-severity assertion in
`scan-resilience.test.ts` to expect `error` for this id.
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore: address review nits — drop explanatory comments, inline regex
Per @rayhanadev's review:
- drop the two `transient-and-async-issues.tsx` block comments around
`useRouter` / `LoginLink` (the test files document the rule's intent
on their own)
- drop the duplicate early-return rationale in `alt-text.ts` (the
shared util's header doc already explains the why)
- inline the metadata-image basename regex into the helper to remove
the named-constant indirection
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(a11y): use context.filename instead of removed getFilename
After merging main, `RuleContext` no longer exposes `getFilename` (PR
\#549 made it `Omit<BaseRuleContext, "getFilename">` and exposed the
absolute path as a property). Switch the alt-text early-return to read
`context.filename` to match the new API.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
setup-node's registry-url writes an .npmrc with _authToken placeholder
that short-circuits the OIDC exchange. Also need npm 11.5.1+ for
trusted publishing support.