diff --git a/agents/react-build-resolver.md b/agents/react-build-resolver.md index 937c60ba..d36a7456 100644 --- a/agents/react-build-resolver.md +++ b/agents/react-build-resolver.md @@ -43,8 +43,8 @@ test -f vite.config.js -o -f vite.config.ts -o -f vite.config.mjs # Vite test -f rsbuild.config.js -o -f rsbuild.config.ts # Rsbuild grep -l "react-scripts" package.json # CRA test -f webpack.config.js -o -f webpack.config.ts # webpack -test -f .parcelrc -o $(grep -l 'parcel' package.json) # Parcel -test -f bunfig.toml -a -n "$(grep '"bun"' package.json)" # Bun +{ test -f .parcelrc || grep -q '"parcel"' package.json; } # Parcel +{ test -f bunfig.toml && grep -q '"bun"' package.json; } # Bun ``` ## Diagnostic Commands @@ -160,7 +160,10 @@ Common triggers: npm ls react # check for duplicates npm ls @types/react # check version alignment npm dedupe # consolidate duplicates -npm i react@^19 react-dom@^19 # upgrade as a pair, never independently +# Only when `npm ls react` reports duplicates or a version mismatch with `@types/react`. +# Upgrade react and react-dom as a pair (matching the major already in use) — never independently. +# Replace with the project's React major (17 / 18 / 19); jumping majors is a separate, deliberate change. +# npm i react@^ react-dom@^ ``` When a library throws on hook usage, it almost always means React is duplicated. diff --git a/commands/react-review.md b/commands/react-review.md index 646a51b1..59cda76a 100644 --- a/commands/react-review.md +++ b/commands/react-review.md @@ -84,7 +84,7 @@ npx eslint . --ext .tsx,.jsx,.ts,.js # Typecheck (skip cleanly for JS-only projects) npm run typecheck --if-present -tsc --noEmit -p tsconfig.json +[ -f tsconfig.json ] && tsc --noEmit -p tsconfig.json # Targeted a11y rules npx eslint . --rule 'jsx-a11y/alt-text: error' \ diff --git a/commands/react-test.md b/commands/react-test.md index d8f481d5..5cff7207 100644 --- a/commands/react-test.md +++ b/commands/react-test.md @@ -45,7 +45,7 @@ Prefer Vitest for new Vite-based projects; respect Jest for existing setups. ## Example Session -```text +````text User: /react-test I need a SearchInput component with debounced search Agent: @@ -173,7 +173,7 @@ $ vitest run --coverage src/components/SearchInput.test.tsx ``` ## TDD Complete! -``` +```` ## Test Patterns diff --git a/rules/react/security.md b/rules/react/security.md index bfb3cb12..0697a190 100644 --- a/rules/react/security.md +++ b/rules/react/security.md @@ -108,7 +108,7 @@ Prefixed env vars are bundled into the client. Treat them as public. |---|---|---| | Next.js | `NEXT_PUBLIC_*` | All others | | Vite | `VITE_*` | `.env` server-side only | -| Create React App | `REACT_APP_*` | None — CRA exposes all | +| Create React App | `REACT_APP_*`, plus `NODE_ENV` and `PUBLIC_URL` | All others (anything without the `REACT_APP_` prefix is server-side only) | | Remix | `process.env` access in `loader`/`action` only | Same | ```ts diff --git a/skills/react-testing/SKILL.md b/skills/react-testing/SKILL.md index bba6316f..fb58f17d 100644 --- a/skills/react-testing/SKILL.md +++ b/skills/react-testing/SKILL.md @@ -203,8 +203,13 @@ test("useCounter accepts initial value", () => { }); test("useUser fetches user data", async () => { + // Instantiate QueryClient ONCE per test outside the wrapper so it survives re-renders. + // Creating it inside the wrapper closure resets cache state on every render, producing flaky tests. + const queryClient = new QueryClient({ + defaultOptions: { queries: { retry: false } }, + }); const wrapper = ({ children }: { children: React.ReactNode }) => ( - {children} + {children} ); const { result } = renderHook(() => useUser("1"), { wrapper }); @@ -385,16 +390,20 @@ function Broken() { } test("error boundary renders fallback", () => { - // Suppress React's console.error noise for expected throw - vi.spyOn(console, "error").mockImplementation(() => {}); + // Suppress React's console.error noise for the expected throw, then restore so + // the spy does not leak across tests and hide real errors elsewhere. + const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + try { + render( + Something went wrong}> + + , + ); - render( - Something went wrong}> - - , - ); - - expect(screen.getByText("Something went wrong")).toBeInTheDocument(); + expect(screen.getByText("Something went wrong")).toBeInTheDocument(); + } finally { + errorSpy.mockRestore(); + } }); ``` diff --git a/undefined/.claude/session-aliases.json b/undefined/.claude/session-aliases.json deleted file mode 100644 index 4543d65b..00000000 --- a/undefined/.claude/session-aliases.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "version": "1.0.0", - "aliases": { - "__proto__": { - "sessionPath": "/evil/path", - "createdAt": "2026-05-20T09:16:26.009Z", - "title": "Prototype Pollution Attempt" - }, - "normal": { - "sessionPath": "/normal/path", - "createdAt": "2026-05-20T09:16:26.009Z", - "title": "Normal Alias" - } - }, - "metadata": { "totalCount": 2, "lastUpdated": "2026-05-20T09:16:26.009Z" } -} \ No newline at end of file