@mrciphersmith/keryx 0.2.164 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +85355 -56749
- package/dist/core.js +28605 -18901
- package/package.json +2 -2
- package/src/gdgraph/affected-report.ts +141 -0
- package/src/gdgraph/build.ts +170 -23
- package/src/gdgraph/service.ts +6 -0
- package/src/gdgraph/staleness.ts +253 -45
- package/src/gdskills/bundled/agents/codebase-navigator.md +55 -0
- package/src/gdskills/bundled/agents/design-advisor.md +64 -0
- package/src/gdskills/bundled/agents/docs-maintainer.md +56 -0
- package/src/gdskills/bundled/agents/end-to-end-tester.md +56 -0
- package/src/gdskills/bundled/agents/error-path-auditor.md +57 -0
- package/src/gdskills/bundled/agents/go-build-fixer.md +52 -0
- package/src/gdskills/bundled/agents/go-code-auditor.md +49 -0
- package/src/gdskills/bundled/agents/performance-auditor.md +63 -0
- package/src/gdskills/bundled/agents/python-build-fixer.md +52 -0
- package/src/gdskills/bundled/agents/python-code-auditor.md +49 -0
- package/src/gdskills/bundled/agents/refactoring-steward.md +61 -0
- package/src/gdskills/bundled/agents/security-auditor.md +62 -0
- package/src/gdskills/bundled/agents/test-first-driver.md +61 -0
- package/src/gdskills/bundled/agents/work-planner.md +62 -0
- package/src/gdskills/bundled/install-manifest.json +530 -0
- package/src/gdskills/bundled/rules/core/skill-lifecycle.mdc +29 -1
- package/src/gdskills/bundled/rules/core/skills-storage-workflow.mdc +2 -2
- package/src/gdskills/bundled/skills/review/code-style-review/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +74 -246
- package/src/gdskills/bundled/skills/review/review-orchestrator/output-contract.schema.json +19 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/reviewer-finding.schema.json +10 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/reviewer-input.schema.json +5 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/templates/pr-comment-backend.md +50 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/templates/pr-comment-frontend.md +52 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/templates/review-report.md +143 -0
- package/src/gdskills/bundled/stacks/go/agent-refs.json +3 -0
- package/src/gdskills/bundled/stacks/go/governance/eval.json +1745 -0
- package/src/gdskills/bundled/stacks/go/governance/scout.json +31 -0
- package/src/gdskills/bundled/stacks/go/pack.json +41 -0
- package/src/gdskills/bundled/stacks/go/rules/coding-style.mdc +85 -0
- package/src/gdskills/bundled/stacks/go/rules/patterns.mdc +65 -0
- package/src/gdskills/bundled/stacks/go/rules/security.mdc +73 -0
- package/src/gdskills/bundled/stacks/go/rules/testing.mdc +68 -0
- package/src/gdskills/bundled/stacks/go/skills/go-build-fix/SKILL.md +138 -0
- package/src/gdskills/bundled/stacks/go/skills/go-build-fix/evals.json +75 -0
- package/src/gdskills/bundled/stacks/go/skills/go-code-review/SKILL.md +121 -0
- package/src/gdskills/bundled/stacks/go/skills/go-code-review/evals.json +72 -0
- package/src/gdskills/bundled/stacks/go/skills/go-implementation/SKILL.md +122 -0
- package/src/gdskills/bundled/stacks/go/skills/go-implementation/evals.json +76 -0
- package/src/gdskills/bundled/stacks/go/skills/go-testing/SKILL.md +126 -0
- package/src/gdskills/bundled/stacks/go/skills/go-testing/evals.json +73 -0
- package/src/gdskills/bundled/stacks/python/agent-refs.json +3 -0
- package/src/gdskills/bundled/stacks/python/governance/eval.json +1758 -0
- package/src/gdskills/bundled/stacks/python/governance/scout.json +34 -0
- package/src/gdskills/bundled/stacks/python/pack.json +41 -0
- package/src/gdskills/bundled/stacks/python/rules/coding-style.mdc +63 -0
- package/src/gdskills/bundled/stacks/python/rules/patterns.mdc +88 -0
- package/src/gdskills/bundled/stacks/python/rules/security.mdc +84 -0
- package/src/gdskills/bundled/stacks/python/rules/testing.mdc +77 -0
- package/src/gdskills/bundled/stacks/python/skills/python-build-fix/SKILL.md +144 -0
- package/src/gdskills/bundled/stacks/python/skills/python-build-fix/evals.json +74 -0
- package/src/gdskills/bundled/stacks/python/skills/python-code-review/SKILL.md +155 -0
- package/src/gdskills/bundled/stacks/python/skills/python-code-review/evals.json +72 -0
- package/src/gdskills/bundled/stacks/python/skills/python-implementation/SKILL.md +143 -0
- package/src/gdskills/bundled/stacks/python/skills/python-implementation/evals.json +78 -0
- package/src/gdskills/bundled/stacks/python/skills/python-testing/SKILL.md +132 -0
- package/src/gdskills/bundled/stacks/python/skills/python-testing/evals.json +73 -0
- package/src/gdskills/bundled/stacks/react/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/react/governance/eval.json +2188 -0
- package/src/gdskills/bundled/stacks/react/governance/scout.json +40 -0
- package/src/gdskills/bundled/stacks/react/pack.json +42 -0
- package/src/gdskills/bundled/stacks/react/rules/coding-style.mdc +58 -0
- package/src/gdskills/bundled/stacks/react/rules/patterns.mdc +79 -0
- package/src/gdskills/bundled/stacks/react/rules/security.mdc +70 -0
- package/src/gdskills/bundled/stacks/react/rules/testing.mdc +60 -0
- package/src/gdskills/bundled/stacks/react/skills/react-build-fix/SKILL.md +139 -0
- package/src/gdskills/bundled/stacks/react/skills/react-build-fix/evals.json +72 -0
- package/src/gdskills/bundled/stacks/react/skills/react-code-review/SKILL.md +148 -0
- package/src/gdskills/bundled/stacks/react/skills/react-code-review/evals.json +74 -0
- package/src/gdskills/bundled/stacks/react/skills/react-implementation/SKILL.md +140 -0
- package/src/gdskills/bundled/stacks/react/skills/react-implementation/evals.json +74 -0
- package/src/gdskills/bundled/stacks/react/skills/react-testing/SKILL.md +142 -0
- package/src/gdskills/bundled/stacks/react/skills/react-testing/evals.json +83 -0
- package/src/gdskills/bundled/stacks/react/skills/react-upgrade-migration/SKILL.md +155 -0
- package/src/gdskills/bundled/stacks/react/skills/react-upgrade-migration/evals.json +74 -0
- package/src/gdskills/bundled/stacks/ts-js-node/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/ts-js-node/governance/eval.json +2155 -0
- package/src/gdskills/bundled/stacks/ts-js-node/governance/scout.json +40 -0
- package/src/gdskills/bundled/stacks/ts-js-node/pack.json +41 -0
- package/src/gdskills/bundled/stacks/ts-js-node/rules/coding-style.mdc +73 -0
- package/src/gdskills/bundled/stacks/ts-js-node/rules/patterns.mdc +61 -0
- package/src/gdskills/bundled/stacks/ts-js-node/rules/security.mdc +71 -0
- package/src/gdskills/bundled/stacks/ts-js-node/rules/testing.mdc +63 -0
- package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-build-fix/SKILL.md +137 -0
- package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-build-fix/evals.json +73 -0
- package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-code-review/SKILL.md +124 -0
- package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-code-review/evals.json +74 -0
- package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-esm-migration/SKILL.md +152 -0
- package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-esm-migration/evals.json +71 -0
- package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-implementation/SKILL.md +127 -0
- package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-implementation/evals.json +72 -0
- package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-testing/SKILL.md +134 -0
- package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-testing/evals.json +70 -0
|
@@ -0,0 +1,2188 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "1.0.0",
|
|
3
|
+
"reports": [
|
|
4
|
+
{
|
|
5
|
+
"schemaVersion": "1.0.0",
|
|
6
|
+
"skillId": "react/react-build-fix",
|
|
7
|
+
"strictness": "high",
|
|
8
|
+
"trials": 10,
|
|
9
|
+
"triggerAccuracy": {
|
|
10
|
+
"truePositive": 6,
|
|
11
|
+
"falsePositive": 0,
|
|
12
|
+
"positives": 6,
|
|
13
|
+
"negatives": 6
|
|
14
|
+
},
|
|
15
|
+
"evidence": "authored",
|
|
16
|
+
"scenarios": [
|
|
17
|
+
{
|
|
18
|
+
"id": "trigger-positive-1",
|
|
19
|
+
"kind": "trigger-positive",
|
|
20
|
+
"prompt": "This component's ref prop is throwing a TSX type error after the React 19 upgrade, fix it",
|
|
21
|
+
"strictness": "high",
|
|
22
|
+
"trials": 1,
|
|
23
|
+
"passes": 1,
|
|
24
|
+
"passRate": 1,
|
|
25
|
+
"passAtK": 1,
|
|
26
|
+
"grader": "trigger-rank-fork-family",
|
|
27
|
+
"status": "ran",
|
|
28
|
+
"deterministic": true
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "trigger-positive-2",
|
|
32
|
+
"kind": "trigger-positive",
|
|
33
|
+
"prompt": "eslint-plugin-react-hooks is failing on exhaustive-deps in this file",
|
|
34
|
+
"strictness": "high",
|
|
35
|
+
"trials": 1,
|
|
36
|
+
"passes": 1,
|
|
37
|
+
"passRate": 1,
|
|
38
|
+
"passAtK": 1,
|
|
39
|
+
"grader": "trigger-rank-fork-family",
|
|
40
|
+
"status": "ran",
|
|
41
|
+
"deterministic": true
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"id": "trigger-positive-3",
|
|
45
|
+
"kind": "trigger-positive",
|
|
46
|
+
"prompt": "The Vite build fails with a JSX syntax error in this component",
|
|
47
|
+
"strictness": "high",
|
|
48
|
+
"trials": 1,
|
|
49
|
+
"passes": 1,
|
|
50
|
+
"passRate": 1,
|
|
51
|
+
"passAtK": 1,
|
|
52
|
+
"grader": "trigger-rank-fork-family",
|
|
53
|
+
"status": "ran",
|
|
54
|
+
"deterministic": true
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"id": "trigger-positive-4",
|
|
58
|
+
"kind": "trigger-positive",
|
|
59
|
+
"prompt": "I'm getting a hydration mismatch warning on this page, fix it",
|
|
60
|
+
"strictness": "high",
|
|
61
|
+
"trials": 1,
|
|
62
|
+
"passes": 1,
|
|
63
|
+
"passRate": 1,
|
|
64
|
+
"passAtK": 1,
|
|
65
|
+
"grader": "trigger-rank-fork-family",
|
|
66
|
+
"status": "ran",
|
|
67
|
+
"deterministic": true
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"id": "trigger-positive-5",
|
|
71
|
+
"kind": "trigger-positive",
|
|
72
|
+
"prompt": "Fix this children prop type error in the component",
|
|
73
|
+
"strictness": "high",
|
|
74
|
+
"trials": 1,
|
|
75
|
+
"passes": 1,
|
|
76
|
+
"passRate": 1,
|
|
77
|
+
"passAtK": 1,
|
|
78
|
+
"grader": "trigger-rank-fork-family",
|
|
79
|
+
"status": "ran",
|
|
80
|
+
"deterministic": true
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"id": "trigger-positive-6",
|
|
84
|
+
"kind": "trigger-positive",
|
|
85
|
+
"prompt": "The tsc build fails because this event handler's type doesn't match onChange",
|
|
86
|
+
"strictness": "high",
|
|
87
|
+
"trials": 1,
|
|
88
|
+
"passes": 1,
|
|
89
|
+
"passRate": 1,
|
|
90
|
+
"passAtK": 1,
|
|
91
|
+
"grader": "trigger-rank-fork-family",
|
|
92
|
+
"status": "ran",
|
|
93
|
+
"deterministic": true
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"id": "trigger-negative-1",
|
|
97
|
+
"kind": "trigger-negative",
|
|
98
|
+
"prompt": "Write a new test for this component's error state",
|
|
99
|
+
"strictness": "high",
|
|
100
|
+
"trials": 1,
|
|
101
|
+
"passes": 1,
|
|
102
|
+
"passRate": 1,
|
|
103
|
+
"passAtK": 1,
|
|
104
|
+
"grader": "trigger-rank-fork-family",
|
|
105
|
+
"status": "ran",
|
|
106
|
+
"deterministic": true
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"id": "trigger-negative-2",
|
|
110
|
+
"kind": "trigger-negative",
|
|
111
|
+
"prompt": "Review this component for Rules of Hooks violations",
|
|
112
|
+
"strictness": "high",
|
|
113
|
+
"trials": 1,
|
|
114
|
+
"passes": 1,
|
|
115
|
+
"passRate": 1,
|
|
116
|
+
"passAtK": 1,
|
|
117
|
+
"grader": "trigger-rank-fork-family",
|
|
118
|
+
"status": "ran",
|
|
119
|
+
"deterministic": true
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"id": "trigger-negative-3",
|
|
123
|
+
"kind": "trigger-negative",
|
|
124
|
+
"prompt": "Migrate this codebase from React 18 to React 19",
|
|
125
|
+
"strictness": "high",
|
|
126
|
+
"trials": 1,
|
|
127
|
+
"passes": 1,
|
|
128
|
+
"passRate": 1,
|
|
129
|
+
"passAtK": 1,
|
|
130
|
+
"grader": "trigger-rank-fork-family",
|
|
131
|
+
"status": "ran",
|
|
132
|
+
"deterministic": true
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"id": "trigger-negative-4",
|
|
136
|
+
"kind": "trigger-negative",
|
|
137
|
+
"prompt": "Build a new dashboard component with charts",
|
|
138
|
+
"strictness": "high",
|
|
139
|
+
"trials": 1,
|
|
140
|
+
"passes": 1,
|
|
141
|
+
"passRate": 1,
|
|
142
|
+
"passAtK": 1,
|
|
143
|
+
"grader": "trigger-rank-fork-family",
|
|
144
|
+
"status": "ran",
|
|
145
|
+
"deterministic": true
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"id": "trigger-negative-5",
|
|
149
|
+
"kind": "trigger-negative",
|
|
150
|
+
"prompt": "Fix a Node.js module resolution error unrelated to any component",
|
|
151
|
+
"strictness": "high",
|
|
152
|
+
"trials": 1,
|
|
153
|
+
"passes": 1,
|
|
154
|
+
"passRate": 1,
|
|
155
|
+
"passAtK": 1,
|
|
156
|
+
"grader": "trigger-rank-fork-family",
|
|
157
|
+
"status": "ran",
|
|
158
|
+
"deterministic": true
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"id": "trigger-negative-6",
|
|
162
|
+
"kind": "trigger-negative",
|
|
163
|
+
"prompt": "This component re-renders too often, help me optimize it",
|
|
164
|
+
"strictness": "high",
|
|
165
|
+
"trials": 1,
|
|
166
|
+
"passes": 1,
|
|
167
|
+
"passRate": 1,
|
|
168
|
+
"passAtK": 1,
|
|
169
|
+
"grader": "trigger-rank-fork-family",
|
|
170
|
+
"status": "ran",
|
|
171
|
+
"deterministic": true
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"id": "no-disable-hooks-lint",
|
|
175
|
+
"kind": "behavior",
|
|
176
|
+
"prompt": "react-hooks/exhaustive-deps is failing on my useEffect because it's missing a dependency. What should I do to fix the build?",
|
|
177
|
+
"strictness": "high",
|
|
178
|
+
"trials": 10,
|
|
179
|
+
"passes": 3,
|
|
180
|
+
"passRate": 0.3,
|
|
181
|
+
"passAtK": 1,
|
|
182
|
+
"grader": "judge",
|
|
183
|
+
"status": "ran",
|
|
184
|
+
"trialRecords": [
|
|
185
|
+
{
|
|
186
|
+
"output": "You need the actual dependency situation, not a blanket fix. Here's how to work through it:\n\n1. **Read the exact warning.** It names the specific missing value. Don't guess from memory of the file.\n\n2. **Is that value actually reactive \u2014 meaning it can change between renders and the effect logic depends on the current value?**\n - **Yes** \u2192 add it to the dependency array. That's the fix. If adding it causes an infinite loop (because the value is recreated each render), fix the *source* \u2014 memoize it with `useMemo`/`useCallback`, or move the value's creation inside the effect if nothing outside needs it. Adding the dep and stabilizing its identity is the correct path, not suppressing the warning.\n - **No, it's genuinely stable** \u2192 e.g. a `ref` object (refs are stable and expected to be omitted), a `dispatch` from `useReducer`/`useState` setter, or a value you've *verified* never changes in a way this effect needs to react to. In that case the rule's own documented escape hatch applies \u2014 but only after you've confirmed stability by reading where the value comes from, not by assuming.\n\n3. **Do not reach for `// eslint-disable-next-line react-hooks/exhaustive-deps`.** That's the one thing this skill forbids. It hides exactly the class of bug the rule exists to catch: stale closures where the effect keeps reading an old value.\n\n4. **After fixing, verify the interest is real, not just lint-green:** re-run the failing lint command, then the full chain (type-check, lint, test, build) to confirm nothing regressed \u2014 a changed dep array changes when the effect fires, which can break tests.\n\nThe most common honest resolution is step 2a: add the dep, and if the effect now loops, stabilize the value's identity at its source. The most common *wrong* resolution is suppressing the warning or deleting the dep requirement, either of which leaves a latent stale-value bug in the code.\n\nIf you paste the effect body and the exact warning text, I can tell you which of those two paths applies for that specific case.",
|
|
187
|
+
"outputSha256": "7d9035a62eedeac4bfe688d90d251b4eb7e1a4a98479ea07150c49a7748a7385",
|
|
188
|
+
"promptSha256": "6a3110f47c119aa66daf5c1eab44447ec1c03cddbb75b6d9dd7cffa248103186",
|
|
189
|
+
"deterministic": [],
|
|
190
|
+
"judge": {
|
|
191
|
+
"verdict": "fail",
|
|
192
|
+
"reason": "Pass criterion 1 requires a concrete corrected effect/dependency array or useCallback/useRef restructuring with a named value; the answer only says 'add it to the dependency array' generically, showing no code and naming no specific dependency. Pass criterion 2 is also unmet: it mentions 'stale closures' only in passing, not explaining the stale-value consequence of the missing dep."
|
|
193
|
+
},
|
|
194
|
+
"passed": false
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"output": "The right fix depends on whether the value really needs to be in the dependency array. `exhaustive-deps` is flagging that your effect reads a reactive value (a prop, state, or a value derived from them) that isn't listed \u2014 usually because the array is stale and the effect is closing over an old value. Work through this in order:\n\n**1. Add the missing dependency if the effect genuinely should re-run when it changes.**\n\nAdding it is the correct fix in the large majority of cases. If you're worried it causes an infinite loop or too many re-renders, that worry is itself a signal the effect's structure has a problem \u2014 fix the structure, don't hide it by omitting the dependency.\n\n**2. If adding it genuinely causes a loop, fix why the dependency changes every render.**\n\nThe usual culprits:\n\n- A new object/array/function created inline each render. Wrap it in `useMemo`/`useCallback`, or move a constant outside the component.\n- A derived value recomputed with `Object.keys(obj)` / `.map()` / `.filter()` on every render. Memoize it.\n- State that's updated inside the effect and also listed as a dependency, causing the effect to retrigger itself. That's often a sign the derived state shouldn't be state at all \u2014 compute it during render instead.\n\n**3. If the value is intentionally stable and truly must not retrigger the effect**, use a ref to hold the latest value rather than listing it, e.g. keep a `latestValueRef` updated each render and read `latestValueRef.current` inside the effect. This is the pattern React's own docs describe for \"you want to read the latest value but not react to it.\" Only reach for this after you've confirmed the value cannot change in a way the effect needs to respond to.\n\n**What not to do:**\n\n- Don't add `// eslint-disable-next-line react-hooks/exhaustive-deps`. It silences a rule that exists to catch stale-closure and missed-update bugs, and it's exactly the shortcut this skill forbids.\n- Don't \"fix\" it by casting or restructuring to `any` \u2014 irrelevant here and equally off-limits.\n- Don't delete or weaken a test that starts failing once you add the dependency; that failure is telling you the effect's behavior actually changed, which is what you want to understand.\n\n**Verify before calling it done:** re-run the exact command that failed (type-check / lint / test / build) and confirm it exits 0, then run the full chain once to make sure adding the dependency didn't regress an earlier step. If three attempts at the same failure don't resolve it, stop and report what remains rather than reaching for a disable comment.\n\nIf you paste the actual `useEffect` code and the current dependency array, I can tell you specifically which value is missing and whether it's a restructuring case or a \"stable value\" case.",
|
|
198
|
+
"outputSha256": "cb01ed288a1e34027bb5e08a3b5a22d089eb7d8e91667997b60838bded89737d",
|
|
199
|
+
"promptSha256": "6a3110f47c119aa66daf5c1eab44447ec1c03cddbb75b6d9dd7cffa248103186",
|
|
200
|
+
"deterministic": [],
|
|
201
|
+
"judge": {
|
|
202
|
+
"verdict": "fail",
|
|
203
|
+
"reason": "Pass criterion 1 fails: no specific missing dependency is named and no corrected code/dependency array is shown. The answer only says 'add the missing dependency' generically and asks the user to paste code; the useRef example is a stability escape hatch, not a named dependency fix. Criterion 2 is met (stale-closure explanation), but all criteria must hold."
|
|
204
|
+
},
|
|
205
|
+
"passed": false
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
"output": "Add the missing dependency to the array. That's the fix in the overwhelming majority of cases, and it's what the rule is telling you to do.\n\nConcretely:\n\n1. **Read the lint message.** It names the exact value that's missing, e.g. `React Hook useEffect has a missing dependency: 'userId'`.\n\n2. **Add it to the array:**\n ```tsx\n useEffect(() => {\n fetchUser(userId).then(setUser);\n }, [userId]); // was []\n ```\n\n3. **If adding it causes an infinite loop or a re-fetch storm**, that's not a reason to suppress the rule \u2014 it means one of the dependencies is unstable. Fix the instability:\n - A new object/array/function created inline every render \u2192 move it out of the component, or wrap it in `useMemo`/`useCallback` keyed on its real inputs.\n - A function prop from a parent \u2192 memoize it at the parent with `useCallback`.\n - A ref's `.current` \u2192 refs are stable and don't belong in the array; use the ref object itself if you need identity, not `.current`.\n\n4. **If the value truly cannot change and doesn't need to trigger the effect** (a `dispatch` from `useReducer`, a `setState` function, a ref), the rule already knows these are stable and won't flag them. If it's flagging something else stable-by-construction, confirm that claim in the code before reaching for any exemption.\n\n5. **Only if you've genuinely established a value is intentionally stable** do you use the pattern the React team documents for that case \u2014 holding it in a ref and reading `ref.current` inside the effect \u2014 rather than an `eslint-disable-next-line react-hooks/exhaustive-deps` comment. A blanket disable is not an acceptable fix: it silences the rule for the whole effect, including dependencies that would have caught a real stale-closure bug on the next edit.\n\nWhat I would not do: add `// eslint-disable-next-line react-hooks/exhaustive-deps`, empty the array to make the warning go away, or cast anything to `any`. Those all make the build green while leaving the actual bug \u2014 a closure that reads stale state \u2014 in place.\n\nAfter the fix, re-run the failing lint command to confirm it exits 0, then re-run type-check, tests, and the bundler build to confirm nothing regressed.",
|
|
209
|
+
"outputSha256": "df71700e05e543c9ea5edef78ad0f37594b92bd7c42effdf72d0e65999f19517",
|
|
210
|
+
"promptSha256": "6a3110f47c119aa66daf5c1eab44447ec1c03cddbb75b6d9dd7cffa248103186",
|
|
211
|
+
"deterministic": [],
|
|
212
|
+
"judge": {
|
|
213
|
+
"verdict": "pass",
|
|
214
|
+
"reason": "Names missing dep 'userId' and shows corrected array `}, [userId]); // was []` (criterion 1). Explains staleness: 'a closure that reads stale state' left in place, and warns infinite loop means unstable dep (criterion 2). Explicitly rejects eslint-disable as a fix, offering ref/useCallback alternatives \u2014 no fail criterion triggered."
|
|
215
|
+
},
|
|
216
|
+
"passed": true
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
"output": "Add the missing dependency to the `useEffect` dependency array. That's the fix the rule is asking for and the one that keeps the effect correct.\n\nConcretely:\n\n1. Read the warning. It names the exact value(s) it wants added, e.g. `React Hook useEffect has a missing dependency: 'userId'`.\n2. If that value is read inside the effect and can change between renders, add it to the array. If the effect body references a function or object defined in the component, either move that definition *inside* the effect (so it's not a dependency at all) or wrap it in `useCallback`/`useMemo` and add the stable reference.\n3. Re-run lint and the build.\n\nDo **not** add `// eslint-disable-next-line react-hooks/exhaustive-deps`. That silences a rule that exists to catch stale-closure bugs \u2014 the effect keeps using the old value and the bug is now invisible.\n\nThe one legitimate alternative: if the value is genuinely stable and should never re-trigger the effect \u2014 a `ref` (`.current` is stable), a `setState` dispatcher, or a value you've confirmed is constant for the component's lifetime \u2014 the rule documents an escape hatch for exactly that case (omit it and use the rule's own intended pattern for stable values), but only after confirming the value truly cannot change in a way the effect needs to react to. When in doubt, add the dependency; a re-run of a correct effect is cheap, a stale closure is not.\n\nAfter the fix, re-run the full chain (type-check, lint, test, build) to confirm nothing regressed, and confirm via `git status` that only the file with the failing effect changed \u2014 no disable comment, no cast.\n\nIf you paste the failing warning and the effect body, I can point at the exact dependency and whether moving the definition inside the effect is the cleaner fix.",
|
|
220
|
+
"outputSha256": "39bca60316b824bd068e2eca73647c89a47f3b227a3c21da84a15616bb40a856",
|
|
221
|
+
"promptSha256": "6a3110f47c119aa66daf5c1eab44447ec1c03cddbb75b6d9dd7cffa248103186",
|
|
222
|
+
"deterministic": [],
|
|
223
|
+
"judge": {
|
|
224
|
+
"verdict": "pass",
|
|
225
|
+
"reason": "Names concrete dependency 'userId' with example warning, and shows corrected approaches: add to array, move definition inside effect, or wrap in useCallback/useMemo. Explains staleness: 'the effect keeps using the old value' / 'stale-closure bugs'. Warns against eslint-disable as the fix, offering only the documented stability escape hatch (ref, setState dispatcher) with justification."
|
|
226
|
+
},
|
|
227
|
+
"passed": true
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
"output": "Add the missing dependency to the dependency array. That's the fix.\n\nConcretely:\n\n1. Read the lint message \u2014 it names the exact value it wants added. Add that value to the `useEffect` dependency array.\n2. Confirm the effect should actually re-run when that value changes. If it should, adding it is the whole fix.\n3. If adding it causes an infinite loop or an unwanted re-run, the real problem is upstream: the value is being recreated on every render (an object, array, or function defined inline in the component body). Fix that by hoisting it out of the component, wrapping it in `useMemo`/`useCallback`, or \u2014 best \u2014 moving it inside the effect body if it's only used there. Then add it (or don't need to).\n\nDo not add `// eslint-disable-next-line react-hooks/exhaustive-deps` to make the build pass. That's the one thing to avoid here: the rule is flagging a real stale-closure bug class, and the disable comment just defers it to runtime.\n\nThe narrow exception: if the value is provably stable and the author has confirmed the effect must not re-run on it (a ref object, a `dispatch` from `useReducer`, a value from a context that never changes), the rule documents its own escape hatch for that specific case. Use that, not a blanket disable \u2014 and only after you've actually verified the value can't change in a way the effect needs to see.\n\nOnce fixed, re-run the exact command that failed and confirm it exits 0, then re-run type-check, lint, tests, and build once to make sure nothing regressed.\n\n```\nFixed: src/components/SearchBox.tsx:24 \u2014 added `query` to the useEffect\n dependency array; react-hooks/exhaustive-deps now passes.\n```",
|
|
231
|
+
"outputSha256": "0fef9223e238207c8e9354d5c92ae581f81583d1e907cf98c27953537cea4ae5",
|
|
232
|
+
"promptSha256": "6a3110f47c119aa66daf5c1eab44447ec1c03cddbb75b6d9dd7cffa248103186",
|
|
233
|
+
"deterministic": [],
|
|
234
|
+
"judge": {
|
|
235
|
+
"verdict": "fail",
|
|
236
|
+
"reason": "Criterion 1 requires a named dependency and shown corrected code. The answer only says 'add the missing dependency' generically; the illustrative example names `query` but shows no actual corrected effect/dependency array code. Criterion 2 (staleness consequence) is also not concretely explained \u2014 it only says 'real stale-closure bug class' without describing the effect using an old value from an earlier render."
|
|
237
|
+
},
|
|
238
|
+
"passed": false
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
"output": "Short answer: figure out which value is missing and whether it should actually be in the dependency array \u2014 the fix is almost never a disable comment.\n\n**Step 1 \u2014 read the exact lint message.** ESLint will name the specific missing dependency (e.g. \"React Hook useEffect has a missing dependency: 'userId'\"). Do not guess; the message tells you exactly which value.\n\n**Step 2 \u2014 classify the missing value and fix accordingly:**\n\n1. **A prop or piece of state the effect genuinely reacts to** (e.g. `userId`, `query`, `page`): add it to the array. This is the correct fix in the large majority of cases. If adding it causes an infinite loop, that is a separate bug \u2014 usually the effect is also setting that value, or a derived object is being recreated every render.\n\n2. **A function defined in the component body** (e.g. `fetchData`): the function's identity changes every render, so adding it naively loops. Fix by wrapping the function in `useCallback` with its own correct deps, then adding it to the effect's array \u2014 OR, if the function is only used inside this effect, move the function definition inside the effect and it no longer needs to be a dependency at all.\n\n3. **A value you've confirmed is intentionally stable and must not retrigger the effect** (a `ref`, a dispatch from `useReducer`, a stable module-level constant, a setter from `useState`): the react-hooks plugin already knows `useState` setters and `useRef` current values are stable and will not flag them. If it is flagging something you believe is stable, verify that belief first \u2014 a `useRef` object itself is stable, but reading `.current` inside an effect does not make it reactive, and that is often the real bug. If after that verification the value truly is stable, the correctly scoped fix is a targeted comment disabling only that specific line with a written reason, not a blanket `// eslint-disable-next-line`, and not on the whole hook.\n\n4. **An object or array literal created inline** (e.g. `{ id, name }` or `[a, b]`): this is a new reference every render, so listing it loops forever. Fix by resolving the primitive values the effect actually uses (`id`, `name`, `a`, `b`) and depending on those, or memoize with `useMemo` if you genuinely need the object identity.\n\n**Step 3 \u2014 verify.** After the fix, re-run the exact lint command that failed and confirm it exits 0, then re-run the full chain (`tsc --noEmit`, lint, tests, build) once to confirm the dependency change did not alter runtime behavior \u2014 a dependency fix can legitimately change when an effect re-runs, so a behavioral test is your real confirmation, not just a green lint.\n\n**What not to do:**\n\n- Do not add `// eslint-disable-next-line react-hooks/exhaustive-deps` to make it pass. That rule exists specifically to catch stale-closure bugs where the effect is reading a value that has moved on, and the disable hides exactly the bug class it was written for.\n- Do not refactor the effect to use a `ref` to hold the value just to dodge the dependency \u2014 that silently removes the reactivity the effect was supposed to have.\n- Do not delete or skip the failing step.\n\nIf you paste the exact lint message and the `useEffect` body, I can point at the specific value and which of the four cases above it falls into.",
|
|
242
|
+
"outputSha256": "f5cca71bcb91889c9842599273b392a51068149b1bf2c90a879636ebc21a18b2",
|
|
243
|
+
"promptSha256": "6a3110f47c119aa66daf5c1eab44447ec1c03cddbb75b6d9dd7cffa248103186",
|
|
244
|
+
"deterministic": [],
|
|
245
|
+
"judge": {
|
|
246
|
+
"verdict": "pass",
|
|
247
|
+
"reason": "Names concrete deps (userId/query/page, fetchData) and shows fixes: add to array, useCallback, move function inside effect, depend on primitives. Explains staleness: 'stale-closure bugs where the effect is reading a value that has moved on.' Warns against eslint-disable as the fix, not recommending it."
|
|
248
|
+
},
|
|
249
|
+
"passed": true
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
"output": "Add the missing dependency to the array. That's the fix in the overwhelming majority of cases: eslint-plugin-react-hooks is telling you the effect closes over a value that can change between renders, so omitting it means the effect can run against a stale value. Put it in the array, then re-run lint.\n\nOnly if you can concretely justify that the value must not re-trigger the effect should you do anything else \u2014 and even then the answer is the rule's own documented escape hatch for that specific case, not a blanket `eslint-disable-next-line react-hooks/exhaustive-deps`. Concretely:\n\n- If the value is a function defined inside the component, the usual right fix is to wrap it in `useCallback` (with its own correct deps) and then list it \u2014 not to omit it.\n- If it's a ref, a `dispatch` from `useReducer`/`useState`, or a stable module-level value, the rule already treats it as stable and won't flag it; if it's being flagged, it isn't actually stable, so add it.\n- If you genuinely want \"run this once on mount,\" don't fake it by leaving deps out \u2014 express that intent directly (e.g. an empty-dep effect whose body only touches refs and stable setters, with the changing value read from a ref updated elsewhere). This makes the intent explicit instead of accidental.\n\nWhat you should not do: add the disable comment, or restructure the effect so the linter can't see the dependency (e.g. hoisting the value into a `useRef` purely to dodge the warning). Those turn a caught stale-closure bug into a silent one.\n\nAfter the change, run the failing command again to confirm it exits 0, then re-run the full chain (type-check, lint, tests, build) to make sure adding the dependency didn't introduce a render loop or break behavior \u2014 that's the one real risk here, and if it does loop, the fix is to stabilize the value (useCallback/useMemo or moving it out of the component), not to remove the dependency again.",
|
|
253
|
+
"outputSha256": "0e5de6fe68a5da79c3da52e02ddf71ab9f47e661a2e8cd5c97da80c73951f946",
|
|
254
|
+
"promptSha256": "6a3110f47c119aa66daf5c1eab44447ec1c03cddbb75b6d9dd7cffa248103186",
|
|
255
|
+
"deterministic": [],
|
|
256
|
+
"judge": {
|
|
257
|
+
"verdict": "fail",
|
|
258
|
+
"reason": "Pass criterion 1 requires a named specific dependency and concrete corrected code (completed dep array or useCallback/useRef restructuring). The answer only says 'add the missing dependency' generically, names no value, and shows no code example. It mentions useCallback/useRef conceptually but never shows a corrected effect or array. Criterion 2 (staleness) is met, but criterion 1 is not."
|
|
259
|
+
},
|
|
260
|
+
"passed": false
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
"output": "Add the missing dependency to the effect's dependency array, then re-run the failing command to confirm it exits 0.\n\nConcretely: read the lint error, which names the exact value the effect reads that is not in the array, and add it. Then verify the effect still behaves correctly \u2014 if the effect genuinely should not re-run when that value changes, that's a separate design question, and the fix is to restructure the code (move the value into a ref, or split the effect) rather than leaving the dependency out.\n\nTwo things to avoid:\n\n- Don't add `// eslint-disable-next-line react-hooks/exhaustive-deps`. That silences the rule without addressing why it flagged the value \u2014 usually a stale-closure bug where the effect captures an old value and never sees updates.\n- Don't \"fix\" it by wrapping the effect body in `useCallback` or memoizing the value just to satisfy the linter, unless the value is actually stable by design (a ref, a `useState` setter, a dispatch function \u2014 the rule already knows to skip those, so if it's flagging your dep, it isn't one of them).\n\nIf after adding the dependency the effect re-runs more often than intended, that is the real problem to solve: derive the value inside the effect, move the effect's trigger to a narrower dependency, or use a functional state update so the effect doesn't need to close over the changing value at all.\n\nAfter the change, run the full chain \u2014 type-check, lint, test, build \u2014 not just lint, since an effect that now fires on more inputs can break a test or cause an infinite render loop at runtime that lint alone won't catch.",
|
|
264
|
+
"outputSha256": "66253cc4b30ecc69362fd001cb06def273f763f7b249998c3856d05e9007701c",
|
|
265
|
+
"promptSha256": "6a3110f47c119aa66daf5c1eab44447ec1c03cddbb75b6d9dd7cffa248103186",
|
|
266
|
+
"deterministic": [],
|
|
267
|
+
"judge": {
|
|
268
|
+
"verdict": "fail",
|
|
269
|
+
"reason": "Criterion 1 fails: no specific dependency value is named and no corrected code/dependency array is shown \u2014 it only says 'add the missing dependency' generically. Criterion 2 is met ('stale-closure bug where the effect captures an old value and never sees updates'), but the concrete named-dependency fix with code is absent."
|
|
270
|
+
},
|
|
271
|
+
"passed": false
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
"output": "Add the missing dependency to the array, or if you've confirmed the value genuinely doesn't need to retrigger the effect, use the escape hatch the rule documents for that case \u2014 never a blanket `eslint-disable-next-line react-hooks/exhaustive-deps`.\n\nMore concretely, in order:\n\n**1. Look at what the rule flagged.** It names the missing value. Ask whether that value can change between renders in a way the effect needs to react to.\n\n**2. If it can change** (a prop, state, a value derived from either): add it to the dependency array. This is the correct fix in the large majority of cases. If the effect body closes over a value, that value belongs in deps.\n\n**3. If it's a function or object defined inside the component**, adding it as-is will make the effect re-run every render (new identity each time). Fix the root cause rather than suppressing:\n - Wrap the function in `useCallback` and depend on that, or\n - Move the function *inside* the effect if nothing outside it uses it, or\n - Move it *outside* the component entirely if it doesn't close over component state.\n\n**4. If the value is genuinely stable and re-running would be wrong** \u2014 e.g. a `dispatch` from `useReducer`, a ref's `.current` you intentionally read once, or a value from a `useRef` holding a non-reactive handle \u2014 the rule has documented handling for each of these cases. Apply that specific pattern (often: depend on the ref object itself, not `ref.current`; or read the latest value through a ref when you deliberately want no re-run). Reach for this only after confirming in the code that the value truly cannot change in a way the effect must observe.\n\n**5. Never** add `// eslint-disable-next-line react-hooks/exhaustive-deps` to get the build green. That removes the check for every future edit at that call site, and the class of bug it guards against \u2014 a stale closure that reads an old value forever \u2014 is exactly the bug that's easy to ship and hard to notice.\n\nFor a React 19 codebase, note that `ref` as an ordinary prop doesn't affect this rule, but the same principle applies to anything you forward: if the effect reads it, it's reactive; if you've intentionally made it non-reactive, that intent must be expressed with the documented ref pattern, not a disable comment.\n\nVerify by running lint again and confirming the specific warning is gone rather than the whole rule disabled \u2014 then run the full type-check/test/build chain once to confirm nothing regressed.",
|
|
275
|
+
"outputSha256": "aeca62096974dac8af1fa7d09488adcf878ba09da02d5d95ffdf18fa9acfe3f7",
|
|
276
|
+
"promptSha256": "6a3110f47c119aa66daf5c1eab44447ec1c03cddbb75b6d9dd7cffa248103186",
|
|
277
|
+
"deterministic": [],
|
|
278
|
+
"judge": {
|
|
279
|
+
"verdict": "fail",
|
|
280
|
+
"reason": "Criterion 1 fails: no specific dependency value is named and no corrected code/dependency array is shown \u2014 it only says 'add it to the dependency array' generically, with no concrete example. Criterion 2 also fails: it never explains the stale-closure consequence (effect reading an old value from an earlier render); it only mentions 'stale closure' in passing at step 5 without explaining the staleness mechanism."
|
|
281
|
+
},
|
|
282
|
+
"passed": false
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
"output": "Add the missing dependency to the array \u2014 unless you can prove the value is genuinely stable, in which case use the specific documented escape hatch for that case, never a blanket disable comment.\n\nConcretely, in order:\n\n1. **Identify the missing dep.** Read the eslint message; it names the exact value used inside the effect that isn't in the array.\n\n2. **Default fix \u2014 add it.** Put the value in the dependency array. This is correct far more often than people assume. If the value is an object/array/function, though, adding it naively can cause the effect to re-run every render, which usually means the value itself needs stabilization:\n - Wrap an object/array/function defined in the component in `useMemo`/`useCallback` so its identity is stable across renders.\n - For a function that doesn't need component scope, move it outside the component entirely.\n - For a value only read inside the effect and never needing to trigger a re-run, hold it in a `useRef`.\n\n3. **The one legitimate escape hatch.** If, after the above, the value truly never needs to trigger the effect (a ref object, a `dispatch` from `useReducer`, a setter from `useState` \u2014 these are guaranteed stable by React), the rule's own documented inline escape is acceptable:\n\n ```tsx\n useEffect(() => {\n // ...\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n ```\n\n Only use this after confirming, in writing in your own head at least, *why* the value cannot change in a way the effect must react to. \"It's annoying\" is not that reason.\n\nWhat **not** to do:\n- Don't reach for any `eslint-disable` as the first move \u2014 that's the failure mode this rule exists to prevent.\n- Don't fix an `exhaustive-deps` warning by deleting the effect's use of the value (e.g., copying it into a ref just to silence lint) \u2014 that changes behavior and often reintroduces the stale-closure bug the rule was flagging.\n- Don't add a dep you know is wrong (e.g., a new object literal each render) just to make the message go away \u2014 that trades a lint error for an infinite render loop.\n\nAfter the fix, re-run lint, then type-check and build, to confirm nothing regressed. If the effect still misbehaves, the missing dep was likely a symptom of an unstable value upstream \u2014 stabilize that instead.",
|
|
286
|
+
"outputSha256": "a0b5b281ba0d051d66f00881a97b981e3995a7a258f22f6a588d82ac91939139",
|
|
287
|
+
"promptSha256": "6a3110f47c119aa66daf5c1eab44447ec1c03cddbb75b6d9dd7cffa248103186",
|
|
288
|
+
"deterministic": [],
|
|
289
|
+
"judge": {
|
|
290
|
+
"verdict": "fail",
|
|
291
|
+
"reason": "Pass 1 fails: no specific missing dependency is named and no corrected dependency array is shown \u2014 the answer only says 'add the missing dependency' generically. Pass 2 fails: no staleness consequence (old value from an earlier render) is explained. Also fail criterion 1 holds: it shows an eslint-disable-next-line react-hooks/exhaustive-deps snippet as the escape hatch without a concrete named-dependency fix alongside."
|
|
292
|
+
},
|
|
293
|
+
"passed": false
|
|
294
|
+
}
|
|
295
|
+
]
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
"id": "no-any-cast",
|
|
299
|
+
"kind": "behavior",
|
|
300
|
+
"prompt": "I have a TSX prop type error on an event handler and the build is failing. Should I just cast it to any to unblock the build?",
|
|
301
|
+
"strictness": "high",
|
|
302
|
+
"trials": 10,
|
|
303
|
+
"passes": 10,
|
|
304
|
+
"passRate": 1,
|
|
305
|
+
"passAtK": 1,
|
|
306
|
+
"grader": "judge",
|
|
307
|
+
"status": "ran",
|
|
308
|
+
"trialRecords": [
|
|
309
|
+
{
|
|
310
|
+
"output": "No \u2014 casting to `any` removes the exact check that is failing and hides a real mismatch between the handler you wrote and the element it is attached to. Fix the type instead.\n\nConcretely, for an event handler on a JSX element:\n\n1. **Find the actual mismatch.** Read the full `tsc` message \u2014 it names the expected signature and the one you provided. Usual causes:\n - Missing/extra parameter (`(e) => ...` where the prop expects `() => void`, or vice versa).\n - Wrong event type \u2014 e.g. you typed `React.MouseEvent` but attached it to an `<input onChange>`, which needs `React.ChangeEvent<HTMLInputElement>`.\n - Wrong element generic \u2014 `React.MouseEvent<HTMLDivElement>` on a `<button>`.\n\n2. **Fix the narrowest thing.** Either correct the handler's parameter type to the specific React event type for that element and event, or correct the prop's declared type if the call site is right. Don't widen the prop to a union of unrelated things to make the error go away.\n\n3. **Re-run the failing command** and confirm exit 0, then re-run the full chain (type-check, lint, test, build) to be sure the fix didn't regress an earlier step.\n\nTwo things not to do, even under build pressure:\n- Don't add `eslint-disable` comments to route around a related lint failure.\n- Don't loosen or delete a test assertion to make the build green.\n\nIf after three genuine fix attempts the same error remains, stop and report what is left \u2014 the specific expected type, what you provided, and where \u2014 rather than casting to `any`. A short report is recoverable; an `any` on a shared prop type is not, because it silently disables checking for everyone who touches that component next.",
|
|
311
|
+
"outputSha256": "ff57cd8caca528293353f6c77123b68f5de01a5feec5ee9b7ae553cb46d52b97",
|
|
312
|
+
"promptSha256": "b184f2be8d72538eeee2a60d4cff67c72a9f8d89c676d29f910b316e2c10dc2f",
|
|
313
|
+
"deterministic": [],
|
|
314
|
+
"judge": {
|
|
315
|
+
"verdict": "pass",
|
|
316
|
+
"reason": "Answer opens with 'No \u2014 casting to `any` ... Fix the type instead' (criterion 1). It names concrete React event types: 'React.ChangeEvent<HTMLInputElement>' for `<input onChange>` and 'React.MouseEvent<HTMLButtonElement>'/'React.MouseEvent<HTMLDivElement>' (criterion 2). No fail criterion: it never recommends `as any`, only warns against it."
|
|
317
|
+
},
|
|
318
|
+
"passed": true
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
"output": "No. Casting the event handler to `any` removes the type check entirely at that point \u2014 including for the next person who edits that handler \u2014 and it is exactly the shortcut this workflow forbids. Fix the actual mismatch instead.\n\nWhat to do:\n\n1. Read the full error message, not just the file/line. It will tell you what type the handler is being assigned to versus what it actually is.\n\n2. Type the handler against the specific element and event it is attached to. For a click handler on a button:\n\n ```tsx\n const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {\n // ...\n };\n ```\n\n For a text input change:\n\n ```tsx\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n // ...\n };\n ```\n\n Match the generic to the actual DOM element and event, rather than reaching for a loose hand-rolled type or `any`.\n\n3. If the error is on the prop declaration rather than the handler body, fix the `interface`/`type` so it reflects how the component is actually called \u2014 the prop should accept `React.MouseEvent<HTMLButtonElement>` (or whatever the real signature is), not a widened `any`/`unknown`.\n\nTwo common root causes worth checking before you edit anything:\n\n- A copy-pasted handler from a different element type (e.g. a `MouseEvent<HTMLDivElement>` handler wired to a `<button>`).\n- A React 19 types migration where `ref` is now an ordinary prop on function components \u2014 if the component mixes `forwardRef` and the new plain-`ref` pattern, the generic signature will disagree and produce a confusing event/prop type error. Pick one pattern and make the generics match it.\n\nAfter the fix:\n\n- Re-run the exact command that failed and confirm it exits 0.\n- Re-run the full chain \u2014 type-check, lint, test, build \u2014 to confirm the fix did not regress an earlier step.\n- Verify no `any` cast and no `eslint-disable` for a hooks rule was added.\n- Keep the change minimal; `git status` should show only the file you were fixing (plus a shared types file if that is where the prop type lives).\n\nIf you have iterated three times on the same error without resolving it, stop and report what remains rather than escalating to a cast or a disable comment.",
|
|
322
|
+
"outputSha256": "d8da59b09116b3fce095e830eb37525c737153cc0f30ee6f14147fe160116b4b",
|
|
323
|
+
"promptSha256": "b184f2be8d72538eeee2a60d4cff67c72a9f8d89c676d29f910b316e2c10dc2f",
|
|
324
|
+
"deterministic": [],
|
|
325
|
+
"judge": {
|
|
326
|
+
"verdict": "pass",
|
|
327
|
+
"reason": "Answer says 'No' and that casting to any 'removes the type check entirely' and is 'exactly the shortcut this workflow forbids' (criterion 1). It names specific types: React.MouseEvent<HTMLButtonElement> for a button click and React.ChangeEvent<HTMLInputElement> for input change, with code (criterion 2). No any-cast is recommended; it explicitly warns against it."
|
|
328
|
+
},
|
|
329
|
+
"passed": true
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
"output": "No \u2014 don't cast it to `any`. That erases the type check at that exact point and the next person (or you, next sprint) inherits a handler whose signature nothing verifies against the element it's attached to. It also leaves the error in the code; you've just hidden it from the compiler.\n\nHere's what to actually do:\n\n1. Run the type-check and read the *full* message. It names the expected handler signature and the actual one \u2014 the mismatch is usually one of three things: wrong event type on the parameter, wrong element in the generic, or a handler defined for `MouseEvent` being attached to something that emits `ChangeEvent`, or vice versa.\n\n2. Type the parameter to match the element that actually emits the event. If it's a button click, that's `React.MouseEvent<HTMLButtonElement>`; if it's an input change, `React.ChangeEvent<HTMLInputElement>`. If you're forwarding the handler as a prop, type the prop with the same signature rather than a loose `(e: any) => void`.\n\n3. If the error is on the prop declaration rather than the call site, fix the prop type to match how it's genuinely used \u2014 don't widen it to `any`/`unknown` to make the error go away.\n\n4. If the project is on the React 19 types where `ref` is an ordinary prop on function components, don't mix that pattern with `forwardRef` in the same component \u2014 pick one and match its generic signature.\n\n5. If you genuinely can't reconcile the types, safe alternatives that keep checking are a discriminated union of handler props (e.g. separate `onClick` vs `onChange` prop types) or a properly generic handler. Narrowing beats widening here.\n\nThen re-run the failing command and confirm it exits 0, and re-run the full chain \u2014 type-check, lint, test, build \u2014 to make sure the fix didn't regress an earlier step. If you've made three honest attempts at the same failure, stop and report what remains rather than escalating to a cast.\n\nOne caveat: if the \"event handler\" error is actually a `react-hooks` lint failure in disguise (e.g. an effect callback's signature), the same rule applies \u2014 fix the dependency or placement, don't add an `eslint-disable-next-line` and don't cast.",
|
|
333
|
+
"outputSha256": "f8369fa66d8cde8ce93458bb2cfe2ed873dc6a1d63c191ff73c56d9cfcfa498c",
|
|
334
|
+
"promptSha256": "b184f2be8d72538eeee2a60d4cff67c72a9f8d89c676d29f910b316e2c10dc2f",
|
|
335
|
+
"deterministic": [],
|
|
336
|
+
"judge": {
|
|
337
|
+
"verdict": "pass",
|
|
338
|
+
"reason": "Answer opens 'No \u2014 don't cast it to any' and explains it erases the type check (criterion 1). It names concrete event types: 'React.MouseEvent<HTMLButtonElement>' for button click and 'React.ChangeEvent<HTMLInputElement>' for input change (criterion 2). No fail criterion: it never recommends `as any`, even as a fallback, and explicitly says don't widen to any/unknown."
|
|
339
|
+
},
|
|
340
|
+
"passed": true
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
"output": "No \u2014 don't cast it to `any`. That unblocks the build but deletes the type check at that exact point, so the next person (or you, in a month) can pass a handler with the wrong signature and TypeScript will stay silent until it breaks at runtime instead of at compile time. It's also explicitly off-limits in this workflow: the rule is never to route around a prop/ref/event type error with `any`/`as any`. And practically, it usually isn't even the fastest fix \u2014 a real fix is typically a one-line type annotation, while a cast leaves the underlying mismatch for someone else to untangle.\n\nThe right fix depends on which of the two sides is actually wrong. Read the full error message first \u2014 don't guess from the line.\n\n**Case 1: the handler's declared type is too loose or hand-rolled.** This is the most common cause. Someone wrote something like `onSave: (e) => void` or used a DOM event type instead of a React one:\n\n```tsx\n// Wrong\ninterface Props {\n onSave: (e: Event) => void;\n}\n\n// Right \u2014 specific React event, specific element\ninterface Props {\n onSave: (e: React.MouseEvent<HTMLButtonElement>) => void;\n}\n```\n\nFor form inputs use `React.ChangeEvent<HTMLInputElement>`; for keyboard use `React.KeyboardEvent<HTMLInputElement>`; and so on. The element type parameter should match the element the handler is actually attached to.\n\n**Case 2: the prop type is right and the call site is wrong.** If the interface correctly says `React.MouseEvent<HTMLButtonElement>` but the passed handler is typed for a different element or event, fix the handler at the call site to match \u2014 or, if the component should genuinely accept a broader event, widen it deliberately (e.g. to `React.MouseEvent<HTMLElement>`) only if every use actually satisfies that, not as a reflexive escape.\n\n**Case 3: React 19 ref/event types changed under you.** If this appeared after a `@types/react` bump, the fix is to match the new signatures, not to cast. For a function component forwarding a ref, type it as `React.Ref<ElementType>` or `React.RefObject<ElementType>` to match what the component actually forwards. If the project still uses `forwardRef`, stay consistent with `forwardRef`'s own generics \u2014 don't mix the two patterns in one component, since that itself produces this kind of error.\n\nAfter changing it, re-run the exact failing command (`tsc --noEmit`, or whatever script failed) and confirm it exits 0, then run the full chain \u2014 type-check, lint, test, build \u2014 once, to make sure the type fix didn't regress an earlier step.\n\nIf you paste the actual error text and the surrounding prop declaration and call site, I can point at which of the three cases it is and give you the exact annotation.",
|
|
344
|
+
"outputSha256": "0186bcdd15ba2548cc4d3f8532a0ed4b5782682c665ad56b7dcc78a2c71b94ee",
|
|
345
|
+
"promptSha256": "b184f2be8d72538eeee2a60d4cff67c72a9f8d89c676d29f910b316e2c10dc2f",
|
|
346
|
+
"deterministic": [],
|
|
347
|
+
"judge": {
|
|
348
|
+
"verdict": "pass",
|
|
349
|
+
"reason": "Answer says \"No \u2014 don't cast it to `any`\" and explains it deletes the type check, satisfying criterion 1. It names specific React event types: \"React.MouseEvent<HTMLButtonElement>\", \"React.ChangeEvent<HTMLInputElement>\", \"React.KeyboardEvent<HTMLInputElement>\", satisfying criterion 2. No fail criterion: it never recommends `as any`, only warns against it."
|
|
350
|
+
},
|
|
351
|
+
"passed": true
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
"output": "No \u2014 don't cast it to `any`. That removes the type check at exactly the point where the mismatch is trying to tell you something, and the next person who edits that handler inherits a silent gap. Fix the actual type.\n\nHere's what to do instead:\n\n1. **Read the full error.** It tells you the expected handler signature and the type you actually gave it. Those two disagree \u2014 find out which side is wrong.\n2. **Match the event type to the element and event.** For a click handler on a button, that's `React.MouseEvent<HTMLButtonElement>`; for a text input change, `React.ChangeEvent<HTMLInputElement>`. If you hand-rolled a loose type, replace it with the specific React event type.\n3. **Decide which side is the bug.**\n - If the handler function is doing something the element can't support (e.g. reading `value` off a click event), the handler is wrong \u2014 fix the handler.\n - If the handler is correct but its declared parameter type is wrong, fix the declaration.\n - If the component's own prop signature is wrong for how it's used, fix the prop type \u2014 but don't widen it to `any`/`unknown` just to make the error vanish.\n4. **If it's a ref or a React 19 types change**, type it as `React.Ref<ElementType>` / `React.RefObject<ElementType>` matching what's actually forwarded. Don't mix the React 19 \"ref as a prop\" pattern with `forwardRef` in the same component \u2014 pick the one the project is on.\n\nThen verify properly before calling it done:\n\n- Re-run the exact failing command (`tsc --noEmit` or the project's type-check script) and confirm it exits 0.\n- Re-run the full chain \u2014 type-check, lint, test, build \u2014 once more to make sure the fix didn't regress an earlier step.\n- Confirm you didn't add `as any`/`: any` anywhere to route around it.\n\nIf you're on your third attempt at the same error and still stuck, stop and report what's left rather than reaching for `any` \u2014 a cast is not an unblock, it's a deferred failure.",
|
|
355
|
+
"outputSha256": "e941613279fdad7979ddf2d5323da4b4f47369dd2e46cd49d8eccb7641f7d520",
|
|
356
|
+
"promptSha256": "b184f2be8d72538eeee2a60d4cff67c72a9f8d89c676d29f910b316e2c10dc2f",
|
|
357
|
+
"deterministic": [],
|
|
358
|
+
"judge": {
|
|
359
|
+
"verdict": "pass",
|
|
360
|
+
"reason": "Answer says \"No \u2014 don't cast it to `any`\" (crit 1) and names specific types: \"React.MouseEvent<HTMLButtonElement>\" for button click and \"React.ChangeEvent<HTMLInputElement>\" for input change (crit 2). It never recommends `as any`; it explicitly warns against it, so no fail criterion holds."
|
|
361
|
+
},
|
|
362
|
+
"passed": true
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
"output": "No \u2014 casting to `any` is the one thing this skill explicitly forbids, because it removes the type check entirely and hides the real mismatch for whoever edits that component next. Unblock the build by fixing the type.\n\nWhat to do instead:\n\n1. **Reproduce the actual error.** Run your type-check (`tsc --noEmit` or your project's script) and read the full message. For an event handler prop error, the message usually names both the expected signature and the one you actually passed.\n\n2. **Type the handler against the real element and event.** Use the specific React event type rather than a loose or hand-rolled one:\n - `React.ChangeEvent<HTMLInputElement>` for input changes\n - `React.MouseEvent<HTMLButtonElement>` for button clicks\n - `React.FormEvent<HTMLFormElement>` for form submits\n\n The generic parameter is the DOM element that actually emits the event, so match it to the JSX you're attaching the handler to.\n\n3. **Decide which side is wrong.** Either:\n - The prop's declared type is too narrow/wrong \u2192 fix the `interface`/`type` to match how the component is genuinely used, or\n - The call site is passing something it shouldn't \u2192 fix the call site.\n \n Don't widen the prop to `any`/`unknown` to make the error vanish.\n\n4. **If it's a `ref`-related signature error**, note that after the React 19 types change, `ref` is an ordinary prop on function components. Type it as `React.Ref<ElementType>` / `React.RefObject<ElementType>` matching what the component forwards. If the code still uses `forwardRef`, match `forwardRef`'s own generic signature \u2014 don't mix the two patterns in one component.\n\n5. **Verify.** Re-run the exact command that failed and confirm it exits 0, then re-run the whole chain (type-check, lint, test, build) once to make sure the fix didn't regress an earlier step.\n\nIf you paste the actual error text, I can point at the specific type that needs to change. If after three genuine fix attempts you're still stuck on the same failure, stop and report what remains rather than escalating to a cast.",
|
|
366
|
+
"outputSha256": "38beefd14a0fd6ed570f6c76b07e5023674dc693245b3c23ec8631cf4ced7065",
|
|
367
|
+
"promptSha256": "b184f2be8d72538eeee2a60d4cff67c72a9f8d89c676d29f910b316e2c10dc2f",
|
|
368
|
+
"deterministic": [],
|
|
369
|
+
"judge": {
|
|
370
|
+
"verdict": "pass",
|
|
371
|
+
"reason": "Answer opens with 'No \u2014 casting to `any` is the one thing this skill explicitly forbids' (criterion 1). It names concrete event types: 'React.ChangeEvent<HTMLInputElement> for input changes' and 'React.MouseEvent<HTMLButtonElement> for button clicks' (criterion 2). No fail criterion: it never recommends `as any`, only warns against it."
|
|
372
|
+
},
|
|
373
|
+
"passed": true
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
"output": "No \u2014 don't cast it to `any`. That removes the type check at exactly the point where a wrong type matters most (event handlers are where value/event mismatches actually cause runtime bugs), and it lets the same mistake pass silently on the next edit.\n\nWhat to do instead:\n\n1. Read the full error message, not just the file name. It'll tell you which side is wrong \u2014 the handler's declared parameter type or the prop type it's being assigned to.\n2. If the handler is the problem, type the parameter with the specific React event type for that element and event, e.g. `React.ChangeEvent<HTMLInputElement>` for an input's change handler, `React.MouseEvent<HTMLButtonElement>` for a button click. Generic loose types like `{ target: { value: string } }` or `(...args: any[]) => void` are the same mistake in a slightly different costume.\n3. If the prop signature is the problem, fix the `interface`/`type` so it matches how the component is actually called \u2014 or fix the call site, if the call site is the thing that's wrong.\n4. If the component uses `forwardRef` vs. the React 19 `ref`-as-a-prop pattern, match whichever pattern the component actually uses; mixing them in one component is a common source of this exact class of error. For a ref, type it as `React.Ref<ElementType>` / `React.RefObject<ElementType>` matching what the component forwards.\n\nThen re-run the command that failed. If you're going in circles \u2014\n\n- The literal shape of an event type is easy to get nearly-right and still fail; second-guessing it a couple of times is normal, not a sign you should bail to `any`.\n\nStop after 3 fix iterations on the same failure and report what's still failing rather than escalating to a cast. Disabling the type check to unblock the build just moves the failure to runtime, where it's harder to find and harder to justify having shipped.\n\nVerification: the original failing command exits 0, the full chain (type-check, lint, test, build) still passes afterward, and no `as any` / `: any` was introduced to route around the error.",
|
|
377
|
+
"outputSha256": "3b5bb66fe716e7c96e33036bbc1add64618f15486bba59345c7f311f1a033dfb",
|
|
378
|
+
"promptSha256": "b184f2be8d72538eeee2a60d4cff67c72a9f8d89c676d29f910b316e2c10dc2f",
|
|
379
|
+
"deterministic": [],
|
|
380
|
+
"judge": {
|
|
381
|
+
"verdict": "pass",
|
|
382
|
+
"reason": "Answer opens with 'No \u2014 don't cast it to `any`', satisfying criterion 1. Criterion 2 is met concretely: it names `React.ChangeEvent<HTMLInputElement>` for an input change handler and `React.MouseEvent<HTMLButtonElement>` for a button click. No fail criterion holds \u2014 it never recommends `as any`, and explicitly warns against it."
|
|
383
|
+
},
|
|
384
|
+
"passed": true
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
"output": "No. Casting to `any` throws away the type check at that exact point \u2014 including for the next person who edits that handler \u2014 which is precisely the kind of change this skill exists to prevent.\n\nFix the actual mismatch instead:\n\n1. Read the full error message first \u2014 don't guess from the file name.\n2. Type the handler with the specific React event type for the element and event, e.g. `React.ChangeEvent<HTMLInputElement>` or `React.MouseEvent<HTMLButtonElement>`, not a hand-rolled loose type.\n3. Make the `interface`/`type` and the call site agree. Either correct the prop type to match how the component is actually used, or correct the call site \u2014 but don't widen the prop to `any`/`unknown` to make the error disappear.\n4. If a ref is involved and the project has moved to the React 19 types (where `ref` is an ordinary prop), type it as `React.Ref<ElementType>` / `React.RefObject<ElementType>` matching what the component forwards. If the project is still on `forwardRef`, match `forwardRef`'s own generic signature \u2014 don't mix the two patterns in one component.\n\nThen verify:\n\n- Re-run the exact command that failed (e.g. `tsc --noEmit`) and confirm it exits 0.\n- Re-run the full chain \u2014 type-check, lint, test, bundler build \u2014 to confirm the fix didn't regress an earlier step.\n- Confirm no `as any` / `: any` was introduced to route around the error.\n\nIf you get three iterations deep on the same failure and it still won't resolve, stop and report what remains rather than escalating to a cast. The cast is the thing to avoid, not the fallback.",
|
|
388
|
+
"outputSha256": "08bb8850266ba180563031875ae85370386bc8ff6a4d78bd26286e90bb94034d",
|
|
389
|
+
"promptSha256": "b184f2be8d72538eeee2a60d4cff67c72a9f8d89c676d29f910b316e2c10dc2f",
|
|
390
|
+
"deterministic": [],
|
|
391
|
+
"judge": {
|
|
392
|
+
"verdict": "pass",
|
|
393
|
+
"reason": "Answer opens 'No. Casting to `any` throws away the type check' (criterion 1), and names concrete types: 'React.ChangeEvent<HTMLInputElement>' and 'React.MouseEvent<HTMLButtonElement>' (criterion 2). It never recommends `as any`; it explicitly says 'Confirm no `as any` / `: any` was introduced' and 'The cast is the thing to avoid.' No fail criterion holds."
|
|
394
|
+
},
|
|
395
|
+
"passed": true
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
"output": "No. Casting to `any` removes the type check at exactly the point you're editing, and it also removes it for every future edit at that call site \u2014 the error you're seeing is the compiler telling you the prop type and the handler's actual signature disagree, and `any` just hides that disagreement instead of resolving it.\n\nFix the mismatch properly. For an event handler prop, that usually means one of two things: the prop type is wrong (widen/narrow it to the actual event type, e.g. `React.ChangeEvent<HTMLInputElement>` or `React.MouseEvent<HTMLButtonElement>` for the real element and event), or the handler being passed has the wrong signature and should be adjusted to match. Pick whichever side is actually incorrect and change that one.\n\nIf the build is blocking you right now, do the real fix \u2014 for a single event handler it's typically a one-line type correction \u2014 rather than an `any` you'll have to come back and undo. And if you're stuck after a couple of attempts, report the remaining error instead of escalating to a cast: a documented failing type is far cheaper than an `any` that silently swallows the next real bug.",
|
|
399
|
+
"outputSha256": "3f9aea915d6e78ed913ed7316ff65daf7d7f0351709510926488086a6e9b6dfe",
|
|
400
|
+
"promptSha256": "b184f2be8d72538eeee2a60d4cff67c72a9f8d89c676d29f910b316e2c10dc2f",
|
|
401
|
+
"deterministic": [],
|
|
402
|
+
"judge": {
|
|
403
|
+
"verdict": "pass",
|
|
404
|
+
"reason": "Answer says 'No. Casting to `any` removes the type check...' (criterion 1) and names specific types: 'React.ChangeEvent<HTMLInputElement>' and 'React.MouseEvent<HTMLButtonElement>' for the real element/event (criterion 2). It never recommends `as any`; it explicitly rejects it even as a fallback ('report the remaining error instead of escalating to a cast')."
|
|
405
|
+
},
|
|
406
|
+
"passed": true
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
"output": "No. Casting the handler to `any` unblocks the build by deleting the type check at exactly the point that was catching a real signature mismatch \u2014 the error would come back the moment someone touches that prop, and now with no guard at all. Fix the actual type instead.\n\nFor a typical event handler prop error, the root cause is one of two things:\n\n- **The handler's parameter type is too loose or hand-rolled.** Type it with the specific React event type for the element and event \u2014 `React.MouseEvent<HTMLButtonElement>`, `React.ChangeEvent<HTMLInputElement>` \u2014 not a custom loose shape.\n- **The call site doesn't match the declared prop.** Either narrow the prop's interface to what the component actually does with it, or fix how the parent is invoking it. Do not widen the prop to `any`/`unknown` to make the error vanish.\n\nSo, concretely:\n\n1. Look at what the handler actually receives (`event.currentTarget` type, which element it's bound to) and what the parent passes.\n2. Make the declared prop type and the call site agree, using the precise React event generic.\n3. Re-run `tsc --noEmit` (or the project's type-check script) \u2014 the original failing command should now exit 0.\n4. Re-run the full chain \u2014 type-check, lint (with `react-hooks` on), tests, bundler \u2014 to confirm the type fix didn't regress anything.\n\nTwo things I won't do even if asked: add an `eslint-disable` for a hooks rule, or cast to `any`. Both are the same move \u2014 routing around the check instead of satisfying it. If you paste the actual error text and the component's prop declaration, I can point at the exact line to change.",
|
|
410
|
+
"outputSha256": "726b319f9cacee105e99d8435007c8f30758972eb6ac31ad4e3f343ca6cfab8f",
|
|
411
|
+
"promptSha256": "b184f2be8d72538eeee2a60d4cff67c72a9f8d89c676d29f910b316e2c10dc2f",
|
|
412
|
+
"deterministic": [],
|
|
413
|
+
"judge": {
|
|
414
|
+
"verdict": "pass",
|
|
415
|
+
"reason": "Answer says 'No' and that casting to any 'deletes the type check' and is not the fix ('cast to any' listed among things it won't do). It names specific React event types: 'React.MouseEvent<HTMLButtonElement>, React.ChangeEvent<HTMLInputElement>'. No fail criterion: it never recommends any-casting, even as fallback."
|
|
416
|
+
},
|
|
417
|
+
"passed": true
|
|
418
|
+
}
|
|
419
|
+
]
|
|
420
|
+
}
|
|
421
|
+
],
|
|
422
|
+
"verdict": "fail",
|
|
423
|
+
"scope": "bundled",
|
|
424
|
+
"skillDigest": "4e5f73a6a1544a634315210e8ec7fe7739920756a832db153a1cd5c4bcefacd2",
|
|
425
|
+
"catalogDigest": "4f4016d410837e394a27e5b247e38ef2f57a1ee0baba4436ce7d3d71e223333d",
|
|
426
|
+
"judgePromptVersion": "2026-09-25.1",
|
|
427
|
+
"runner": "deepseek",
|
|
428
|
+
"model": "deepseek-chat",
|
|
429
|
+
"runnerPromptVersion": "2026-09-25.1",
|
|
430
|
+
"recordedAt": "2026-09-25T05:18:52.307Z",
|
|
431
|
+
"judge": "deepseek",
|
|
432
|
+
"judgeModel": "deepseek-chat"
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
"schemaVersion": "1.0.0",
|
|
436
|
+
"skillId": "react/react-code-review",
|
|
437
|
+
"strictness": "high",
|
|
438
|
+
"trials": 10,
|
|
439
|
+
"triggerAccuracy": {
|
|
440
|
+
"truePositive": 6,
|
|
441
|
+
"falsePositive": 0,
|
|
442
|
+
"positives": 6,
|
|
443
|
+
"negatives": 6
|
|
444
|
+
},
|
|
445
|
+
"evidence": "authored",
|
|
446
|
+
"scenarios": [
|
|
447
|
+
{
|
|
448
|
+
"id": "trigger-positive-1",
|
|
449
|
+
"kind": "trigger-positive",
|
|
450
|
+
"prompt": "Review this pull request's React components for Rules of Hooks violations",
|
|
451
|
+
"strictness": "high",
|
|
452
|
+
"trials": 1,
|
|
453
|
+
"passes": 1,
|
|
454
|
+
"passRate": 1,
|
|
455
|
+
"passAtK": 1,
|
|
456
|
+
"grader": "trigger-rank-fork-family",
|
|
457
|
+
"status": "ran",
|
|
458
|
+
"deterministic": true
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
"id": "trigger-positive-2",
|
|
462
|
+
"kind": "trigger-positive",
|
|
463
|
+
"prompt": "Check this useEffect for a dependency array bug before we merge",
|
|
464
|
+
"strictness": "high",
|
|
465
|
+
"trials": 1,
|
|
466
|
+
"passes": 1,
|
|
467
|
+
"passRate": 1,
|
|
468
|
+
"passAtK": 1,
|
|
469
|
+
"grader": "trigger-rank-fork-family",
|
|
470
|
+
"status": "ran",
|
|
471
|
+
"deterministic": true
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
"id": "trigger-positive-3",
|
|
475
|
+
"kind": "trigger-positive",
|
|
476
|
+
"prompt": "Look at this diff for stale closures in the event handlers",
|
|
477
|
+
"strictness": "high",
|
|
478
|
+
"trials": 1,
|
|
479
|
+
"passes": 1,
|
|
480
|
+
"passRate": 1,
|
|
481
|
+
"passAtK": 1,
|
|
482
|
+
"grader": "trigger-rank-fork-family",
|
|
483
|
+
"status": "ran",
|
|
484
|
+
"deterministic": true
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
"id": "trigger-positive-4",
|
|
488
|
+
"kind": "trigger-positive",
|
|
489
|
+
"prompt": "Review this list render for key misuse",
|
|
490
|
+
"strictness": "high",
|
|
491
|
+
"trials": 1,
|
|
492
|
+
"passes": 1,
|
|
493
|
+
"passRate": 1,
|
|
494
|
+
"passAtK": 1,
|
|
495
|
+
"grader": "trigger-rank-fork-family",
|
|
496
|
+
"status": "ran",
|
|
497
|
+
"deterministic": true
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
"id": "trigger-positive-5",
|
|
501
|
+
"kind": "trigger-positive",
|
|
502
|
+
"prompt": "Check this component diff for a dangerouslySetInnerHTML XSS risk",
|
|
503
|
+
"strictness": "high",
|
|
504
|
+
"trials": 1,
|
|
505
|
+
"passes": 1,
|
|
506
|
+
"passRate": 1,
|
|
507
|
+
"passAtK": 1,
|
|
508
|
+
"grader": "trigger-rank-fork-family",
|
|
509
|
+
"status": "ran",
|
|
510
|
+
"deterministic": true
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
"id": "trigger-positive-6",
|
|
514
|
+
"kind": "trigger-positive",
|
|
515
|
+
"prompt": "Review this modal component for accessibility gaps",
|
|
516
|
+
"strictness": "high",
|
|
517
|
+
"trials": 1,
|
|
518
|
+
"passes": 1,
|
|
519
|
+
"passRate": 1,
|
|
520
|
+
"passAtK": 1,
|
|
521
|
+
"grader": "trigger-rank-fork-family",
|
|
522
|
+
"status": "ran",
|
|
523
|
+
"deterministic": true
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
"id": "trigger-negative-1",
|
|
527
|
+
"kind": "trigger-negative",
|
|
528
|
+
"prompt": "Review the MobX store actions and reactions in this diff",
|
|
529
|
+
"strictness": "high",
|
|
530
|
+
"trials": 1,
|
|
531
|
+
"passes": 1,
|
|
532
|
+
"passRate": 1,
|
|
533
|
+
"passAtK": 1,
|
|
534
|
+
"grader": "trigger-rank-fork-family",
|
|
535
|
+
"status": "ran",
|
|
536
|
+
"deterministic": true
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
"id": "trigger-negative-2",
|
|
540
|
+
"kind": "trigger-negative",
|
|
541
|
+
"prompt": "Review this diff against our repository's CLAUDE.md frontend conventions",
|
|
542
|
+
"strictness": "high",
|
|
543
|
+
"trials": 1,
|
|
544
|
+
"passes": 1,
|
|
545
|
+
"passRate": 1,
|
|
546
|
+
"passAtK": 1,
|
|
547
|
+
"grader": "trigger-rank-fork-family",
|
|
548
|
+
"status": "ran",
|
|
549
|
+
"deterministic": true
|
|
550
|
+
},
|
|
551
|
+
{
|
|
552
|
+
"id": "trigger-negative-3",
|
|
553
|
+
"kind": "trigger-negative",
|
|
554
|
+
"prompt": "Write a React Testing Library test for this component",
|
|
555
|
+
"strictness": "high",
|
|
556
|
+
"trials": 1,
|
|
557
|
+
"passes": 1,
|
|
558
|
+
"passRate": 1,
|
|
559
|
+
"passAtK": 1,
|
|
560
|
+
"grader": "trigger-rank-fork-family",
|
|
561
|
+
"status": "ran",
|
|
562
|
+
"deterministic": true
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
"id": "trigger-negative-4",
|
|
566
|
+
"kind": "trigger-negative",
|
|
567
|
+
"prompt": "Fix the failing react-hooks lint error in this file",
|
|
568
|
+
"strictness": "high",
|
|
569
|
+
"trials": 1,
|
|
570
|
+
"passes": 1,
|
|
571
|
+
"passRate": 1,
|
|
572
|
+
"passAtK": 1,
|
|
573
|
+
"grader": "trigger-rank-fork-family",
|
|
574
|
+
"status": "ran",
|
|
575
|
+
"deterministic": true
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
"id": "trigger-negative-5",
|
|
579
|
+
"kind": "trigger-negative",
|
|
580
|
+
"prompt": "Review this NestJS controller's DTO validation",
|
|
581
|
+
"strictness": "high",
|
|
582
|
+
"trials": 1,
|
|
583
|
+
"passes": 1,
|
|
584
|
+
"passRate": 1,
|
|
585
|
+
"passAtK": 1,
|
|
586
|
+
"grader": "trigger-rank-fork-family",
|
|
587
|
+
"status": "ran",
|
|
588
|
+
"deterministic": true
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
"id": "trigger-negative-6",
|
|
592
|
+
"kind": "trigger-negative",
|
|
593
|
+
"prompt": "Build a new component that renders a paginated user list",
|
|
594
|
+
"strictness": "high",
|
|
595
|
+
"trials": 1,
|
|
596
|
+
"passes": 1,
|
|
597
|
+
"passRate": 1,
|
|
598
|
+
"passAtK": 1,
|
|
599
|
+
"grader": "trigger-rank-fork-family",
|
|
600
|
+
"status": "ran",
|
|
601
|
+
"deterministic": true
|
|
602
|
+
},
|
|
603
|
+
{
|
|
604
|
+
"id": "flag-rules-of-hooks",
|
|
605
|
+
"kind": "behavior",
|
|
606
|
+
"prompt": "Review this snippet: `function UserPanel({ id }) { if (!id) return null; const [user, setUser] = useState(null); useEffect(() => { fetchUser(id).then(setUser); }, [id]); return <div>{user?.name}</div>; }` What's wrong?",
|
|
607
|
+
"strictness": "high",
|
|
608
|
+
"trials": 10,
|
|
609
|
+
"passes": 10,
|
|
610
|
+
"passRate": 1,
|
|
611
|
+
"passAtK": 1,
|
|
612
|
+
"grader": "judge",
|
|
613
|
+
"status": "ran",
|
|
614
|
+
"trialRecords": [
|
|
615
|
+
{
|
|
616
|
+
"output": "**CRITICAL \u2014 Rules of Hooks violation.** `function UserPanel({ id }) { if (!id) return null; const [user, setUser] = useState(null); ... }`\n\nThe `if (!id) return null;` guard sits **above** the `useState` and `useEffect` calls. React requires the same hooks in the same order on every render. When `id` is falsy the component returns early and calls **zero** hooks; when `id` is truthy it calls two. The hook count and order differ between renders, so React will error (\"Rendered fewer hooks than expected\") the moment `id` flips from truthy to falsy (or it silently desyncs hook state across the two shapes). This is the most common shape of the bug \u2014 an early-return guard above the hooks.\n\nFix direction: move **every** hook above the first `return`, then branch inside the returned JSX instead of returning early:\n\n```\nfunction UserPanel({ id }) {\n const [user, setUser] = useState(null);\n useEffect(() => {\n if (!id) return; // guard goes *inside* the effect\n fetchUser(id).then(setUser);\n }, [id]);\n if (!id) return null; // early return now sits *after* all hooks\n return <div>{user?.name}</div>;\n}\n```\n\n**Additional concerns to confirm (lower severity, but check them):**\n\n- **Effect cleanup / race (HIGH).** `fetchUser(id).then(setUser)` sets state on a userId that may be stale by the time the promise resolves \u2014 flip `id` quickly and an older response can overwrite a newer one, and setting state after unmount warns (or leaks). Add an \"ignore\" flag or an `AbortController` and return a cleanup from the effect.\n- **Stale user across id change (MEDIUM).** When `id` changes, the old `user` stays in state until the new fetch resolves, so the panel briefly shows the previous user's name. Consider resetting `user` to `null` at the start of the effect (alongside the cleanup above) so you don't render another user's data.\n\nNeither of the above is a dependency-array bug \u2014 `[id]` is correct for what the effect body reads. The `if (!id)` guard being **duplicated** inside the effect is intentional and safe; it is not a reactive-value omission.\n\nNo MobX/store patterns and no repo-convention concerns appear in this snippet, so those are out of scope here.",
|
|
617
|
+
"outputSha256": "1fe67b4600a3e6e5d5ff208495040724fb1c247731d14ea2a54d233555bc6178",
|
|
618
|
+
"promptSha256": "631c33e636fed061c24ee3c9ad335e8f4e84a6d32b09f91d5730fd15c0f3f6cb",
|
|
619
|
+
"deterministic": [],
|
|
620
|
+
"judge": {
|
|
621
|
+
"verdict": "pass",
|
|
622
|
+
"reason": "Names useState and useEffect and states the `if (!id) return null` guard sits above them, so falsy id yields zero hooks vs two (criterion 1). Calls it a CRITICAL Rules of Hooks correctness bug with 'Rendered fewer hooks than expected', not style (criterion 2). Shows concrete fix: hoist both hooks above the guard, put `if (!id) return` inside the effect, early return after hooks (criterion 3)."
|
|
623
|
+
},
|
|
624
|
+
"passed": true
|
|
625
|
+
},
|
|
626
|
+
{
|
|
627
|
+
"output": "CRITICAL: `UserPanel` violates the Rules of Hooks \u2014 `useState` and `useEffect` are called *after* the early return `if (!id) return null;`. When `id` is falsy the component returns before reaching either hook; when `id` is truthy it reaches both. React's per-render hook order therefore differs between renders and between sibling renders, which React cannot reconcile \u2014 this throws the \"Rendered fewer hooks than expected\" / \"Rendered more hooks than during the previous render\" error as soon as `id` flips between falsy and truthy across renders of the same mounted component, and it corrupts hook state for any component that relies on consistent ordering.\n\nConcrete fix direction: move every hook call above the first `return`, then branch afterward \u2014 for example call `useState`/`useEffect` unconditionally at the top, and put the `if (!id) return null;` guard *after* the hooks (the effect body can early-return internally when `id` is falsy, and the JSX can render nothing or the guard can sit just before the `return` statement that follows the hooks). The rule is: no early return above a hook, ever.\n\nSecondary notes on the same snippet (fetch side, not the headline bug):\n\n- HIGH: the effect has no cleanup/cancellation. If `id` changes (or the component unmounts) while `fetchUser(id)` is in flight, the older promise still resolves and calls `setUser` with a stale response \u2014 the classic race where a slow earlier request overwrites a faster later one. Fix direction: cancel via `AbortController` (pass `signal` to `fetchUser`) or an `ignore`/`active` flag set in the effect's cleanup, and only apply the result if the effect instance is still current.\n- MEDIUM: effect errors are unhandled. `fetchUser(id).then(setUser)` has no `.catch`, so a rejected fetch produces an unhandled rejection and leaves `user` untouched with no error state. Consider an error/loading state if the UI needs to distinguish them.\n\nNot in scope / not findings here: this is a plain-React correctness bug, not a store-pattern or convention-doc issue. The dependency array `[id]` itself is correct for what the effect reads (`id`), so there is no missing-dependency finding \u2014 the problem is *where* the effect sits, not what it depends on.",
|
|
628
|
+
"outputSha256": "2106cb0602cc0ef80d65dcf088d36edeba67aadb4707417b3f85296baa7ae998",
|
|
629
|
+
"promptSha256": "631c33e636fed061c24ee3c9ad335e8f4e84a6d32b09f91d5730fd15c0f3f6cb",
|
|
630
|
+
"deterministic": [],
|
|
631
|
+
"judge": {
|
|
632
|
+
"verdict": "pass",
|
|
633
|
+
"reason": "Names useState and useEffect sitting after `if (!id) return null`, so falsy id returns before reaching them (criterion 1). Calls it a CRITICAL Rules of Hooks correctness bug with per-render hook order mismatch and thrown errors, not style (criterion 2). Gives concrete fix: move hooks above the first return, guard after hooks, effect body early-returns internally (criterion 3). No fail criteria hold."
|
|
634
|
+
},
|
|
635
|
+
"passed": true
|
|
636
|
+
},
|
|
637
|
+
{
|
|
638
|
+
"output": "**CRITICAL: Rules of Hooks violation \u2014 hook called after an early return.**\n\nThe guard `if (!id) return null;` sits *above* both `useState` and `useEffect`. React requires the same hooks to run in the same order on every render. When `id` is falsy, this component returns before reaching `useState`/`useEffect`; when `id` is truthy, it calls them. That means the number and order of hooks differ between renders of the same component instance.\n\nIn practice this produces a runtime error from React (\"Rendered fewer hooks than expected...\"), and it can also corrupt hook state for sibling hooks once the condition flips, because React matches hooks by call order, not by name. This is the single most common shape of the bug: a bail-out guard written as `return` before the hooks.\n\nFix direction: move every hook call above the first `return`, then branch inside the JSX instead:\n\n```\nfunction UserPanel({ id }) {\n const [user, setUser] = useState(null);\n useEffect(() => { /* ... */ }, [id]);\n if (!id) return null;\n return <div>{user?.name}</div>;\n}\n```\n\n**Secondary issue: the effect has no cleanup and can set state from a stale/interleaved fetch.**\n\n`fetchUser(id).then(setUser)` has no cancellation. If `id` changes quickly (or the component unmounts) while a fetch is in flight, an older response can resolve *after* a newer one and overwrite `user` with stale data \u2014 a classic race. It can also call `setUser` on an unmounted component. The `[id]` dependency array itself is correct here (the effect only reads `id`), so this is not a missing-dependency finding \u2014 it's a missing-cleanup finding.\n\nFix direction: guard the async result with an ignore flag or an `AbortController`, e.g.:\n\n```\nuseEffect(() => {\n let ignore = false;\n fetchUser(id).then(u => { if (!ignore) setUser(u); });\n return () => { ignore = true; };\n}, [id]);\n```\n\n**Not a finding here:** `[id]` correctly lists the only reactive value the effect reads, so there's no missing-dependency bug \u2014 do not \"fix\" the dependency array. And there's no index-as-key, mutation, or accessibility/XSS concern in this snippet as written.\n\nReport as CRITICAL (Rules of Hooks, line 2/3/4) and MEDIUM (missing cleanup/stale-fetch race, `useEffect`). Both belong to the author or a fix skill \u2014 this review is read-only.",
|
|
639
|
+
"outputSha256": "894b829773a8b84b9ebd22d78a02f0c5b7d7f1b4e3a62fcae888994b1c54d34c",
|
|
640
|
+
"promptSha256": "631c33e636fed061c24ee3c9ad335e8f4e84a6d32b09f91d5730fd15c0f3f6cb",
|
|
641
|
+
"deterministic": [],
|
|
642
|
+
"judge": {
|
|
643
|
+
"verdict": "pass",
|
|
644
|
+
"reason": "Answer names useState and useEffect and states the guard `if (!id) return null;` sits above both, so falsy id returns before reaching them (criterion 1). It calls it a CRITICAL Rules of Hooks correctness bug with runtime error and hook-order corruption, not style (criterion 2). It shows concrete restructuring: hoisting useState/useEffect above the first return with code, branching in JSX (criterion 3). No fail criteria hold."
|
|
645
|
+
},
|
|
646
|
+
"passed": true
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
"output": "**CRITICAL: `UserPanel` \u2014 Rules of Hooks violation.** `useState` and `useEffect` are called *after* the early `return null` guard for `!id`. That means when `id` is falsy the component returns after zero hook calls, and when `id` is truthy it calls two hooks. React identifies hooks by call order per render, so the hook count changing between renders breaks the model. Depending on the transition this throws \"Rendered fewer hooks than expected\" / \"Rendered more hooks than during the previous render,\" or silently misaligns hook state. The fix is to move both hook calls above the guard, then branch *inside* the returned JSX (the guard clause above a hook is the classic shape this skill is built to catch \u2014 see Step 2).\n\nFor example, the shape should be: call `useState`/`useEffect` unconditionally first (the effect can itself no-op when `id` is absent), then do `if (!id) return null;` only after every hook has run, or render the guard as a conditional in the JSX.\n\n**MEDIUM: the effect body vs. `[id]` dependency.** The dependency array itself (`[id]`) is actually correct \u2014 `id` is the only reactive value the effect reads, and `fetchUser`/`setUser` are stable. So this is *not* a missing-dependency finding. The real gap is elsewhere:\n\n- **No cleanup / race condition.** If `id` changes rapidly (or the component unmounts) while a `fetchUser(id)` promise is still in flight, the later resolution can call `setUser` with a stale user, or set state after unmount. There's no cancellation (AbortController) or \"ignore if superseded\" flag. Worth flagging as a correctness issue on a component whose `id` can change.\n\n**LOW / note:** `<div>{user?.name}</div>` renders `undefined` as nothing while loading and gives no accessible status. If the project has a loading/announce pattern, a `role=\"status\"` or equivalent is the usual direction \u2014 only flag against a concrete convention, not a generic \"consider accessibility.\"\n\n**Out of scope here:** anything about how `fetchUser` reaches a store or state-management library, and any repository convention docs \u2014 those belong to the store-review and convention-review skills respectively, not this one.\n\nNet: one CRITICAL (hooks-after-early-return) and one MEDIUM (uncleaned async effect). The dependency array is fine as written.",
|
|
650
|
+
"outputSha256": "10fcd50b90e2e67cbd15895cc9a60b8173f5bd27d523d6889acbf1567ce94664",
|
|
651
|
+
"promptSha256": "631c33e636fed061c24ee3c9ad335e8f4e84a6d32b09f91d5730fd15c0f3f6cb",
|
|
652
|
+
"deterministic": [],
|
|
653
|
+
"judge": {
|
|
654
|
+
"verdict": "pass",
|
|
655
|
+
"reason": "Names useState and useEffect as called after the `if (!id) return null` guard, explaining falsy id returns after zero hooks (criterion 1). Calls it a CRITICAL Rules of Hooks correctness bug breaking per-render hook matching, not style (criterion 2). Gives concrete fix: move both hooks above the guard, branch inside JSX, effect no-ops when id absent (criterion 3). No fail criteria hold."
|
|
656
|
+
},
|
|
657
|
+
"passed": true
|
|
658
|
+
},
|
|
659
|
+
{
|
|
660
|
+
"output": "There's one clear correctness bug here, and it's a Rules of Hooks violation.\n\n**CRITICAL: `useState` and `useEffect` are called after an early `return`.**\n\n```js\nfunction UserPanel({ id }) {\n if (!id) return null; // <-- early return ABOVE the hooks\n const [user, setUser] = useState(null); // hook #1\n useEffect(() => { fetchUser(id).then(setUser); }, [id]); // hook #2\n return <div>{user?.name}</div>;\n}\n```\n\nReact identifies hooks by their **call order on every render**. When `id` is falsy the component returns before reaching `useState`/`useEffect`, so zero hooks run that render; when `id` is truthy, two hooks run. Toggling `id` between falsy and truthy changes the number of hooks called between renders, which violates the Rules of Hooks and produces the classic \"Rendered fewer hooks than expected\" / hook-order error (and can corrupt state association in the worst case).\n\n**Fix direction:** move every hook call *above* the first `return`, then branch *inside* the returned JSX (or gate inside the effect):\n\n```js\nfunction UserPanel({ id }) {\n const [user, setUser] = useState(null);\n useEffect(() => {\n if (!id) return; // guard inside the hook, not above it\n fetchUser(id).then(setUser);\n }, [id]);\n if (!id) return null; // now safe: all hooks already ran\n return <div>{user?.name}</div>;\n}\n```\n\n(An equivalent alternative is to keep an `<UserPanel>` wrapper that guards and render an inner component that owns the hooks \u2014 but the guard must never sit above hook calls in the same component.)\n\n**Note on the rest of the snippet (not bugs, so not findings):**\n\n- The effect's dependency array `[id]` is correct \u2014 `id` is the only reactive value read in the body, and `setUser`/`fetchUser` are stable. No staleness bug here.\n- `<div>{user?.name}</div>` with no `key`, mutation, or XSS sink is fine.\n\n**Out of scope for this review:** nothing here touches MobX/store patterns or repo convention docs, so no cross-skill handoff is needed.\n\n**One thing I could not verify without more context:** whether `fetchUser(id)` needs race-condition handling (e.g. a stale response from a previous `id` overwriting a newer one). That's a real concern for a fetch-keyed-by-changing-`id` effect, but the snippet alone doesn't show whether `fetchUser` already cancels/ignores stale requests or whether the project has an established pattern for it \u2014 so I'm flagging it as a question to confirm, not asserting it as a finding. If it isn't handled, the fix direction is an `AbortController` (or a `let cancelled = false` cleanup) in the effect's cleanup function.",
|
|
661
|
+
"outputSha256": "85907e20831eb79ffaee7e7558dbe1517740df8d93d0ed5f785cdd3e0baa601b",
|
|
662
|
+
"promptSha256": "631c33e636fed061c24ee3c9ad335e8f4e84a6d32b09f91d5730fd15c0f3f6cb",
|
|
663
|
+
"deterministic": [],
|
|
664
|
+
"judge": {
|
|
665
|
+
"verdict": "pass",
|
|
666
|
+
"reason": "Answer names useState and useEffect and states they sit after `if (!id) return null`, so on falsy id React never reaches them (zero hooks vs two). Calls it a correctness bug / Rules of Hooks violation with hook-order error, not style. Shows concrete fix: hoist both hooks above the guard, with `if (!id) return;` inside the effect and the early return moved below the hooks."
|
|
667
|
+
},
|
|
668
|
+
"passed": true
|
|
669
|
+
},
|
|
670
|
+
{
|
|
671
|
+
"output": "CRITICAL: `UserPanel` calls `useState` and `useEffect` *after* a conditional early return `if (!id) return null;`. That violates the Rules of Hooks \u2014 the number and order of hooks called must be identical on every render. On renders where `id` is falsy the component bails out before reaching either hook; on renders where `id` is truthy it reaches both. Switching between those two states (e.g. an initially-undefined `id` that gets populated, or one that gets cleared) changes the hook count/order between renders, which React will throw a \"Rendered fewer hooks than expected\" (or more hooks) error on, or silently corrupt the state slot mapping. This is a real correctness bug, not a style note.\n\nFix direction: move every hook call above the first `return`, then branch on `id` inside the returned JSX or inside the effect body. For example: declare `useState` and `useEffect` first, make the effect a no-op when `!id` (`if (!id) return;` *inside* the effect callback \u2014 that's fine, it's not a hook-ordering issue), and put the `if (!id) return null;` guard in the JSX/return path after the hooks.\n\nHIGH: `src/.../UserPanel.tsx` (effect body) \u2014 the effect in the snippet as written reads `id` and lists it in the dependency array `[id]`, so the dependency itself is fine as far as `id` goes. But note the fetch has no cancellation/cleanup: a rapid `id` change can let an earlier `fetchUser` resolve *after* a later one and call `setUser` with stale data (a race, not a dependency bug). Fix direction: guard the effect with a cancellation flag or `AbortController` keyed to the effect run and skip `setUser` for superseded runs. (If the effect body had also read `user` or another prop without listing it, that would be a genuine missing-dependency staleness bug \u2014 re-check against the actual body once the hooks are reordered.)\n\nScope note: this is a plain-React hooks correctness finding. Nothing here is a MobX/store-pattern issue or a repository convention-doc issue, so no cross-skill handoff is needed. I have not edited the code \u2014 findings only.",
|
|
672
|
+
"outputSha256": "94cf0afc30f44b3d5be578fbd5a3f5353f1a9add74473467686388224d610ba5",
|
|
673
|
+
"promptSha256": "631c33e636fed061c24ee3c9ad335e8f4e84a6d32b09f91d5730fd15c0f3f6cb",
|
|
674
|
+
"deterministic": [],
|
|
675
|
+
"judge": {
|
|
676
|
+
"verdict": "pass",
|
|
677
|
+
"reason": "Names useState and useEffect sitting after `if (!id) return null`, so falsy id bails before hooks (criterion 1). Calls it a real correctness bug, not style, citing hook count/order mismatch and 'Rendered fewer hooks' (criterion 2). Concrete fix: hoist both hooks above the return, put `if (!id) return;` inside the effect body, guard in JSX (criterion 3). No fail criteria hold."
|
|
678
|
+
},
|
|
679
|
+
"passed": true
|
|
680
|
+
},
|
|
681
|
+
{
|
|
682
|
+
"output": "CRITICAL: UserPanel \u2014 Rules of Hooks violation. The early `if (!id) return null;` sits **above** the `useState` and `useEffect` calls, so on renders where `id` is falsy the component returns before reaching `useState`/`useEffect`, while on renders where `id` is truthy it calls them. React requires the same hooks to run in the same order on every render; this breaks that invariant and produces the \"rendered fewer hooks than expected\" error (or hook-order mismatches) as soon as `id` toggles between truthy and falsy across renders. This is a correctness bug, not a style nit.\n\nFix direction: move every hook call above the first `return`, then branch in the returned JSX or inside the effect. For example:\n\n```jsx\nfunction UserPanel({ id }) {\n const [user, setUser] = useState(null);\n useEffect(() => {\n if (!id) return;\n fetchUser(id).then(setUser);\n }, [id]);\n if (!id) return null;\n return <div>{user?.name}</div>;\n}\n```\n\nThat keeps the hook order stable across renders while still bailing out of the fetch and the render when `id` is absent.\n\nOne related, lower-severity note (not a separate severity bucket, just a design smell to confirm intent on): the effect calls `setUser` from an async result without a cleanup/ignore flag or abort. If `id` changes while a previous `fetchUser(id)` request is still in flight, the older response can resolve last and overwrite the newer user \u2014 a stale-response race, distinct from the stale-closure issue. If `fetchUser` is cancelable, abort the prior request in a cleanup function; otherwise track a `let cancelled = false` flag in the effect and skip `setUser` when it has been cleaned up.\n\nThe `useEffect` dependency array itself (`[id]`) is correct \u2014 `id` is the only reactive value read in the effect body, and `setUser` is a stable state setter that does not need to be listed. So the dependency array is not the finding here; the hook ordering above the early return is.",
|
|
683
|
+
"outputSha256": "24abab1b09837d273d3246caf200429fa9ebdbb6154c625f89601cf246864ddd",
|
|
684
|
+
"promptSha256": "631c33e636fed061c24ee3c9ad335e8f4e84a6d32b09f91d5730fd15c0f3f6cb",
|
|
685
|
+
"deterministic": [],
|
|
686
|
+
"judge": {
|
|
687
|
+
"verdict": "pass",
|
|
688
|
+
"reason": "Names useState/useEffect sitting above `if (!id) return null`, so falsy id returns before reaching them (\"returns before reaching useState/useEffect\"). Calls it a correctness bug, not style nit, citing hook-order invariant. Shows concrete fix: hoists both hooks above the guard and pushes `if (!id) return;` inside the effect body, with code."
|
|
689
|
+
},
|
|
690
|
+
"passed": true
|
|
691
|
+
},
|
|
692
|
+
{
|
|
693
|
+
"output": "The snippet has one real correctness bug and one notable gap.\n\n**CRITICAL \u2014 Rules of Hooks violation (early return above a hook call).**\n\n`if (!id) return null;` sits *before* `const [user, setUser] = useState(null);` and the `useEffect`. That means on renders where `id` is falsy the component returns early and calls **zero** hooks; on renders where `id` is truthy it calls **two** hooks. React identifies hooks by call order per component instance, so this produces the \"Rendered fewer hooks than expected\" error the moment `id` flips from truthy to falsy (or vice versa) across renders. This is a correctness bug, not a lint nag.\n\nFix direction: move all hook calls above the first `return`, then branch in the returned JSX:\n\n```\nfunction UserPanel({ id }) {\n const [user, setUser] = useState(null);\n useEffect(() => {\n if (!id) {\n setUser(null); // reset when id goes away\n return;\n }\n let cancelled = false;\n fetchUser(id).then((u) => { if (!cancelled) setUser(u); });\n return () => { cancelled = true; };\n }, [id]);\n if (!id) return null;\n return <div>{user?.name}</div>;\n}\n```\n\n**HIGH \u2014 no cleanup / race on the fetch.** The effect fires `fetchUser(id).then(setUser)` with no cancellation. If `id` changes before the first fetch resolves, the older response can land *after* the newer one and overwrite it with a stale user \u2014 the classic out-of-order-response race. The dependency array `[id]` is itself correct (the effect body reads `id`, and `setUser` is a stable setState function, so it does not need to be listed). The fix is an abort flag/`AbortController` as shown above, not a change to the deps.\n\n**Not a finding:** the `[id]` dependency array is fine \u2014 don't flag it, and don't \"fix\" it by adding `setUser`. Also note `dangerouslySetInnerHTML`/URL XSS and key/accessibility concerns don't apply to this tiny snippet.\n\n**Out of scope here:** nothing MobX/store-related, and no repo convention-doc issue \u2014 those belong to their own reviewers.\n\nSo: the hook-order violation is the must-fix, the fetch race is the should-fix.",
|
|
694
|
+
"outputSha256": "ce1a70c3563b8fd11f2319b40583dcd1f1eef61be187f8353f98603b9f295093",
|
|
695
|
+
"promptSha256": "631c33e636fed061c24ee3c9ad335e8f4e84a6d32b09f91d5730fd15c0f3f6cb",
|
|
696
|
+
"deterministic": [],
|
|
697
|
+
"judge": {
|
|
698
|
+
"verdict": "pass",
|
|
699
|
+
"reason": "Answer names useState and useEffect sitting after `if (!id) return null`, explaining zero hooks on falsy id vs two on truthy id (criterion 1). Calls it a correctness bug, not a lint nag (criterion 2). Shows concrete fix: hoists useState/useEffect above the guard, branches inside effect body with `if (!id)`, and keeps `if (!id) return null` after hooks (criterion 3). No fail criteria hold."
|
|
700
|
+
},
|
|
701
|
+
"passed": true
|
|
702
|
+
},
|
|
703
|
+
{
|
|
704
|
+
"output": "CRITICAL: `UserPanel` \u2014 Rules of Hooks violation. `const [user, setUser] = useState(null)` and the `useEffect` are called *after* the early `return null` guard `if (!id) return null`. On renders where `id` is falsy, zero hooks run; on renders where `id` is truthy, two hooks run. React requires the same hooks in the same order on every render, so toggling `id` between falsy and truthy changes the hook count and ordering and will throw or corrupt hook state. The fix is to move all hook calls above the first `return`, then guard inside the JSX (or inside the effect), e.g. `const [user, setUser] = useState(null); useEffect(() => { if (!id) { setUser(null); return; } fetchUser(id).then(setUser); }, [id]); if (!id) return null; return <div>{user?.name}</div>;`.\n\nThat is the primary, blocking bug. Secondary notes on the same snippet, in severity order:\n\nHIGH-ish / correctness detail: the effect has no cleanup, so a stale in-flight `fetchUser(id)` response can resolve after `id` has changed (or after unmount) and call `setUser` with the wrong user \u2014 a classic race that shows the previous id's name. Because `id` is in the dependency array the effect re-runs correctly, but the missing cancellation/ignore flag means an older request can still win. Fix direction: track cancellation, e.g. an `ignore` flag set in the cleanup (`let ignore = false; ... if (!ignore) setUser(u); return () => { ignore = true; };`) or an `AbortController`.\n\nLOW / robustness: `fetchUser(id).then(setUser)` has no rejection handling \u2014 a failed fetch produces an unhandled rejection and leaves `user` at its previous value with no error state. Consider a `.catch`/error state or an `await` in an async effect.\n\nNot a finding: the `[id]` dependency array itself is correct here \u2014 `id` is the only reactive value the effect body reads, so there is no missing-dependency bug to report beyond the race above.\n\nScope note: this is purely a React hooks/rendering finding. There is no store (MobX or otherwise) usage in this snippet to review, so nothing here is out of scope for this skill.",
|
|
705
|
+
"outputSha256": "33af35560df04f7891b09559df67c74bab7e044d9a3d8b9fb96853be59be2a53",
|
|
706
|
+
"promptSha256": "631c33e636fed061c24ee3c9ad335e8f4e84a6d32b09f91d5730fd15c0f3f6cb",
|
|
707
|
+
"deterministic": [],
|
|
708
|
+
"judge": {
|
|
709
|
+
"verdict": "pass",
|
|
710
|
+
"reason": "Names useState and useEffect as sitting after `if (!id) return null`, so on falsy id zero hooks run vs two on truthy \u2014 a Rules of Hooks correctness bug (hook count/order changes, throws/corrupts state). Concrete fix: hoist hooks above the return, guard inside effect/JSX, with code shown. No fail criteria met."
|
|
711
|
+
},
|
|
712
|
+
"passed": true
|
|
713
|
+
},
|
|
714
|
+
{
|
|
715
|
+
"output": "The core bug: **Rules of Hooks violation.**\n\n```\nfunction UserPanel({ id }) {\n if (!id) return null; // <-- early return BEFORE hooks\n const [user, setUser] = useState(null);\n useEffect(() => { fetchUser(id).then(setUser); }, [id]);\n return <div>{user?.name}</div>;\n}\n```\n\n`useState` and `useEffect` sit *after* the `if (!id) return null` guard. When `id` is falsy React returns before reaching any hook; when `id` is truthy it reaches both. So the number and order of hooks called varies between renders. React keys hook state by call order, so on the render where `id` flips from falsy to truthy (or back) the hook list changes \u2192 \"Rendered fewer hooks than expected\" / \"Rendered more hooks than during the previous render\" error, and any hooks after the guard would get paired with the wrong state slots. This is CRITICAL.\n\n**Fix:** hoist every hook above the first `return`, then branch inside the returned JSX (or inside the hook):\n\n```tsx\nfunction UserPanel({ id }) {\n const [user, setUser] = useState(null);\n useEffect(() => {\n if (!id) return;\n let cancelled = false;\n fetchUser(id).then(u => { if (!cancelled) setUser(u); });\n return () => { cancelled = true; };\n }, [id]);\n if (!id) return null;\n return <div>{user?.name}</div>;\n}\n```\n\n**Second issue (HIGH) \u2014 stale async result / no cleanup.** In the original, `fetchUser(id).then(setUser)` has no cancellation guard. If `id` changes before the first request resolves, an older response can land after a newer one and `setUser` writes stale data (out-of-order race). The dependency array `[id]` is correct, but the effect needs a cancellation flag or `AbortController` in the cleanup, as above. Also consider a loading state so the panel doesn't briefly render `undefined` name.\n\nNothing else is wrong here: `[id]` is the right dependency array (the effect reads `id`), `setUser` is stable and correctly omitted, and the optional chaining `user?.name` is fine.\n\nNot findings (out of scope for this review): no MobX/store pattern involved, and no repository convention-doc concern.",
|
|
716
|
+
"outputSha256": "40828d2b5d3fd3b658872b879b53aff08cb01d9151bd6ae250f95e0e43f88c4b",
|
|
717
|
+
"promptSha256": "631c33e636fed061c24ee3c9ad335e8f4e84a6d32b09f91d5730fd15c0f3f6cb",
|
|
718
|
+
"deterministic": [],
|
|
719
|
+
"judge": {
|
|
720
|
+
"verdict": "pass",
|
|
721
|
+
"reason": "Answer names useState and useEffect sitting after `if (!id) return null`, explains React keys hooks by call order so varying hook count breaks matching (CRITICAL, not style), and shows concrete fix hoisting both hooks above the guard with the id-check pushed inside the effect body. No fail criteria hold."
|
|
722
|
+
},
|
|
723
|
+
"passed": true
|
|
724
|
+
}
|
|
725
|
+
]
|
|
726
|
+
},
|
|
727
|
+
{
|
|
728
|
+
"id": "no-mobx-scope",
|
|
729
|
+
"kind": "behavior",
|
|
730
|
+
"prompt": "Review this component diff: it uses a MobX observer wrapping a component with a useEffect that has a missing dependency, and the store action mutates an array in place. What do you flag, and what do you leave to another reviewer?",
|
|
731
|
+
"strictness": "high",
|
|
732
|
+
"trials": 10,
|
|
733
|
+
"passes": 10,
|
|
734
|
+
"passRate": 1,
|
|
735
|
+
"passAtK": 1,
|
|
736
|
+
"grader": "judge",
|
|
737
|
+
"status": "ran",
|
|
738
|
+
"trialRecords": [
|
|
739
|
+
{
|
|
740
|
+
"output": "# Review of this diff\n\n## What I flag (this skill's scope \u2014 plain React)\n\n**HIGH \u2014 missing effect dependency (`src/**/\u2026tsx`, the `useEffect` inside the `observer(...)` component).**\nThe effect reads a reactive value (a prop or `useState` value) that does not appear in its dependency array. This is a staleness bug: the effect closes over the value from an earlier render and will keep using it after it changes. Fix direction: add the read value to the array, or \u2014 if the omission is deliberate \u2014 restructure so the effect does not read it (a ref, or derive the value during render instead of syncing it in an effect). Confirm intent explicitly rather than assuming the omission is safe.\n\n**MEDIUM/context-dependent \u2014 \"derive, don't sync.\"**\nIf the effect's only job is to call `setState` in response to a prop/state change it could instead compute during render, flag the design, not just the dependency. A missing dependency here often signals the effect shouldn't exist at all.\n\n**Note on `observer`:** the wrapping itself does not change any of the above. Hooks still run in render order inside the wrapped component; the Rules of Hooks and dependency analysis are identical whether or not `observer` is present. I check the component's own hook usage normally. I verify I am not misreading `observer` as changing hook semantics \u2014 it does not.\n\n## What I explicitly do NOT flag (leave to another reviewer)\n\n**The store action mutating an array in place \u2014 out of scope.** That is a state-management-library concern (action semantics, observable mutation, reaction invalidation). It goes to the MobX/store-review skill, not here. I will note in my report that the store needs its own pass, but I will not raise the mutation as a finding of mine, and I will not blur the two scopes.\n\n**Caveat worth stating, not resolving here:** if the same in-place-mutated array is also the value a *component* holds as a prop and relies on for referential-identity re-render detection, that can surface as a plain React rendering bug on the component side. But I only flag it here if I can point at the concrete component-side symptom (e.g., a memoized child that won't re-render because the array reference never changes). The store action's correctness is still store-review's call. I do not silently fold it in.\n\n## Verification before I report\n\n- Each finding cites file:line and a concrete bug, not a vague concern.\n- The dependency finding was checked against the values actually read in the effect body, not inferred from the array alone.\n- No finding is a MobX/store-pattern issue or a repo convention-doc issue.\n- Fix direction given; no code edited \u2014 this skill is read-only.\n\n**Bottom line:** one HIGH finding (missing effect dependency) on the component side, at most a design note on syncing-in-effects, and an explicit hand-off of the store's in-place mutation to the store-review skill.",
|
|
741
|
+
"outputSha256": "cb5805ee1725631d983e4985bdeea50576dfd26aa21ab55927f39b4e6440977d",
|
|
742
|
+
"promptSha256": "b82a7c51d5a1842a351316f3f366d39262841678a49a08b2150cbe9b201657f6",
|
|
743
|
+
"deterministic": [],
|
|
744
|
+
"judge": {
|
|
745
|
+
"verdict": "pass",
|
|
746
|
+
"reason": "Pass 1: answer explains staleness concretely \u2014 'the effect closes over the value from an earlier render and will keep using it after it changes.' Pass 2: explicitly names 'the MobX/store-review skill' as the destination for the in-place array mutation, and declines to raise it as its own finding. No fail criteria hold: it defers the mutation rather than fixing it, and does flag the missing dependency."
|
|
747
|
+
},
|
|
748
|
+
"passed": true
|
|
749
|
+
},
|
|
750
|
+
{
|
|
751
|
+
"output": "# Review scope call\n\nThis diff spans two review domains by design: the React rendering/hooks\nmodel (this skill) and MobX store/action usage (a store-review skill). I\nflag the React side only and hand the rest off \u2014 mixing them is exactly\nthe blur the Scope boundary is meant to prevent.\n\n## What I flag (React side, this skill)\n\n**1. Missing effect dependency \u2014 HIGH (verify before reporting).**\nThe `useEffect` inside the `observer`-wrapped component reads a reactive\nvalue (prop or state) that is absent from its dependency array. I confirm\nagainst the *actual values read inside the effect body*, not the array\nalone, then report:\n\n```\nHIGH: <file>:<line> \u2014 effect reads `<value>` but the dependency array\n omits it; the effect will capture the first-render value and never\n re-run when `<value>` changes. Add `<value>` to the array, or move the\n read out of the effect if it should not re-run (derive during render).\n```\n\nTwo things I explicitly do **not** do here:\n- I do not silently drop it as \"probably intentional.\" A missing dep on a\n value the body genuinely reads is a staleness bug more often than not;\n the author confirms intent, not me.\n- I do not edit the array \u2014 read-only, even for a one-liner.\n\n**2. `observer` wrapper is *not* itself a finding.** `observer(...)` is a\nMobX integration detail; wrapping a component in it is not a Rules-of-Hooks\nviolation and I do not comment on the wrapper choice. What I *do* still\ncheck inside the wrapped component: Rules of Hooks (no hook after an early\nreturn / in a conditional), stale closures in the effect/callback, list\n`key` correctness, in-place mutation of **React-owned** values (props,\nstate, or values derived from them \u2014 `.push`/`.sort`/direct assignment),\nre-render hot spots, and JSX/accessibility/`dangerouslySetInnerHTML` sinks.\nThose are rendering-model concerns and stay in scope regardless of the\nstore behind them.\n\n## What I leave to another reviewer\n\n**The store action mutating an array in place \u2014 out of scope here.**\nThis is the outer action on the store mutating an array with `.push`/\n`.sort`/index assignment rather than replacing it. That is a MobX/state-\nmanagement concern (observability, action semantics, `makeObservable`/\n`makeAutoObservable` config, reaction triggers), and belongs to the\nstore-review skill, not this one \u2014 *even though* it's the same array the\neffect reads. I note it so it isn't lost:\n\n```\nOUT OF SCOPE (this skill): <file>:<line> \u2014 store action mutates an\n array in place. Route to a MobX/store-review skill; not a React\n rendering-model finding.\n```\n\nI do **not** fold it into my findings, and I do **not** use it to\nrationalize the missing dependency (\"the mutation is the real bug, so the\ndep is fine\") \u2014 the two are independent and each needs its own reviewer.\n\n**Not mine either:** repository convention-doc issues (CLAUDE.md-style\nrules, i18n string placement, styling tokens) go to a convention-review\nskill. Naming/formatting preferences are never correctness findings for\nanyone in this diff.\n\n## Handoff summary\n\n| Item | Owner |\n|---|---|\n| Missing `useEffect` dependency (React staleness) | **this skill** \u2014 HIGH |\n| Rules of Hooks / stale closure / key / re-render inside the observer component | **this skill** \u2014 as found |\n| Store action mutating array in place (MobX) | **store-review skill** \u2014 noted, not claimed |\n| Convention-doc / i18n / styling / naming | **convention-review skill** \u2014 not claimed |\n| `observer(...)` wrapper existence | nobody \u2014 not a bug |\n\nNet: I own the effect-dependency finding and any other rendering-model gap\ninside the component; the in-place store mutation is flagged as a handoff,\nnot as my finding.",
|
|
752
|
+
"outputSha256": "f259d1eec4006467c508985cf46af1c2776e1ff06dc25692838fada359bf5802",
|
|
753
|
+
"promptSha256": "b82a7c51d5a1842a351316f3f366d39262841678a49a08b2150cbe9b201657f6",
|
|
754
|
+
"deterministic": [],
|
|
755
|
+
"judge": {
|
|
756
|
+
"verdict": "pass",
|
|
757
|
+
"reason": "Criterion 1: answer explains the effect 'will capture the first-render value and never re-run when <value> changes' \u2014 concrete staleness, not just 'missing dep'. Criterion 2: explicitly routes the in-place array mutation to 'the store-review skill' / 'MobX/store-review skill', marked OUT OF SCOPE, not folded into findings. No fail criteria hold."
|
|
758
|
+
},
|
|
759
|
+
"passed": true
|
|
760
|
+
},
|
|
761
|
+
{
|
|
762
|
+
"output": "I only flag the React-side finding; the MobX pieces go to a store reviewer.\n\n**Flag (this skill's scope):**\n\n```\nHIGH: <changed file>:<useEffect line> \u2014 the effect body reads a reactive\n value (prop/state) that is absent from the dependency array, so the\n effect will keep operating on the first-render value forever (stale\n closure). Add the missing dependency, or \u2014 if the omission is genuinely\n intentional \u2014 restructure so the value is read from a ref or the effect\n is split; don't leave a bare suppression unexplained.\n```\n\nI'd verify this against what the effect body actually reads, not just eyeball the array. If the effect only exists to copy a prop/state change into local state, I'd additionally flag it under \"derive, don't sync\" \u2014 that's usually a design bug rather than a missing-dependency bug.\n\n**Leave to another reviewer (out of scope here):**\n\n- The MobX `observer(...)` wrapper \u2014 observer/reaction plumbing, observable reads during render, whether the component re-renders correctly under MobX's tracking. That's a store-review skill's job, even though it sits in the same diff.\n- The store action mutating an array in place \u2014 `push`/`sort`/direct assignment inside an `action`. This is a store-semantics concern (does the mutation notify observers correctly, is the store immutable-by-contract), not a React rendering concern. The React-side analog \u2014 mutating props/state/a derived value *inside the component* breaking referential identity \u2014 I would flag if it appeared in the component; the store's internal mutation I hand off.\n\n**Boundary note to include in the report:** the component renders from a store that mutates arrays in place, so the two findings may interact \u2014 the store reviewer should confirm whether that mutation is what makes any staleness visible. I report the React finding, name the store concern as needing its own pass, and don't blur the two or attempt a fix for either (this skill is read-only).\n\n**Not flagged:** naming/formatting, and I won't touch the observer/action patterns even though they're in the same hunk.",
|
|
763
|
+
"outputSha256": "224e3c4c4b017c5e8317e215d149b891af8b86db968214e9b610b388a32936ad",
|
|
764
|
+
"promptSha256": "b82a7c51d5a1842a351316f3f366d39262841678a49a08b2150cbe9b201657f6",
|
|
765
|
+
"deterministic": [],
|
|
766
|
+
"judge": {
|
|
767
|
+
"verdict": "pass",
|
|
768
|
+
"reason": "Pass 1: answer explains the effect 'will keep operating on the first-render value forever (stale closure)' \u2014 concrete staleness, not just 'missing dependency'. Pass 2: explicitly names 'a store-review skill's job' for the observer and the in-place array mutation (push/sort/assignment in an action), deferring it. No fail criteria: it does not fix the store mutation, and it does flag the missing dependency."
|
|
769
|
+
},
|
|
770
|
+
"passed": true
|
|
771
|
+
},
|
|
772
|
+
{
|
|
773
|
+
"output": "Scope: this diff spans two review skills. Here is what I flag and what I leave alone.\n\n**What I flag (React-side, this skill's scope):**\n\n1. **CRITICAL/HIGH \u2014 missing effect dependency.** The `useEffect` reads a reactive value (prop or state the component renders with) that is absent from its dependency array. If that missing value is read inside the effect body and is not a ref or a setState function, the effect closes over a stale value from the first render and never re-runs when it changes. This is a real staleness bug, not a lint nag. Report with file:line, the specific variable read, and the fix direction (add the read value to the array, or if the intent is a mount-only run, say so explicitly and note the resulting stale read). Note: the fact that the component is wrapped in `observer` does not change this \u2014 MobX's reactive tracking does not substitute for a correct dependency array on `useEffect`.\n\n2. **Re-render note, if applicable.** If the diff also passes a newly created inline object/array/function into a memoized child, or runs an expensive computation on every render with no `useMemo` and no React Compiler to catch it, flag it as a re-render hot spot. Only if the diff actually shows it \u2014 do not invent findings.\n\n3. **Rules of Hooks check.** Confirm none of the component's hooks sit behind an early return or inside a conditional/loop. If a guard clause was introduced above a hook call in this same diff, that is a correctness bug and gets flagged. If the diff does not touch it, do not flag it.\n\n**What I explicitly do NOT flag (out of scope, belongs to another reviewer):**\n\n- **The store action mutating an array in place.** That is a state-management/store concern, and this skill's scope boundary says off-limits. I do not label it a bug here, and I do not flag the surrounding `action`/`observable` patterns either.\n- Any `observer`, `action`, `runInAction`, `observable`, or `reaction` usage anywhere in the diff \u2014 all store-library territory.\n- Repository convention concerns (CLAUDE.md-style rules, i18n placement, styling tokens) \u2014 a separate convention reviewer.\n\n**The one connection I do make, without crossing the boundary:** I note that a in-place-mutated observable array can produce rendering symptoms that look like a React bug \u2014 a memoized child or a `useMemo` keyed on that array's identity will not update, and a list keyed on the array's contents may behave oddly \u2014 but the root cause is the store mutation, not the component. I say that explicitly so the two findings get routed to the two reviewers rather than merged into one report.\n\n**AI-facing handoff:** the store mutation goes to the appropriate store-review skill; the missing dependency, any Rules-of-Hooks issue, and any re-render hot spot stay here in this skill's findings.\n\nI cannot produce exact line numbers from a description alone \u2014 a real review would cite file:line for each. But the split above is what the scope boundary mandates: React rendering/hooks correctness from me, the MobX store pattern from a store reviewer, neither blurred into the other.",
|
|
774
|
+
"outputSha256": "dc476781f93efb04de5bceac81c514ad3fcf987b0f80f8b196d2e5cf738c3726",
|
|
775
|
+
"promptSha256": "b82a7c51d5a1842a351316f3f366d39262841678a49a08b2150cbe9b201657f6",
|
|
776
|
+
"deterministic": [],
|
|
777
|
+
"judge": {
|
|
778
|
+
"verdict": "pass",
|
|
779
|
+
"reason": "Criterion 1: answer explains the effect 'closes over a stale value from the first render and never re-runs when it changes' \u2014 concrete staleness, not just 'missing dependency'. Criterion 2: explicitly routes the in-place array mutation to 'the appropriate store-review skill' / 'a store reviewer', naming MobX/store review as separate. No fail criteria: it defers the mutation rather than fixing it, and does flag the dependency."
|
|
780
|
+
},
|
|
781
|
+
"passed": true
|
|
782
|
+
},
|
|
783
|
+
{
|
|
784
|
+
"output": "**Flag (in scope for this skill):**\n\n1. **Missing effect dependency** \u2014 the `useEffect` reads a reactive value (prop, state, or closure value) that isn't in its dependency array. This is a staleness bug: the effect keeps using the value from the render it was created on, not the current one. Cite the file:line, name the specific value being read, and give the fix direction (add it to the array, or restructure so the effect doesn't need it \u2014 e.g. derive during render, or use a ref if it's genuinely only meant to be read once and the author confirms that intent explicitly).\n\n2. **In-place array mutation inside the component/hook side** \u2014 *if* the mutation happens in code that constructs or derives props/state/values passed into or read by the component (e.g. the component pushes onto an array it then passes down, or mutates a prop-derived array), that's a rendering-correctness bug: React and any memoized children rely on referential identity to detect change. Flag it with file:line and the fix direction (replace with a new array \u2014 spread, `filter`, `map`, `toSorted`/`[...arr].sort()`).\n\n**Leave to another reviewer (out of scope here):**\n\n- **The `observer(...)` wrapper itself and any MobX store/action/reaction semantics are not this skill's job.** This skill checks React's own hooks/rendering model, not state-management-library usage.\n- **The store action mutating an array in place** \u2014 if that write lives inside the MobX store's action (not in the component), that is store-review territory: whether the store should be making the mutation observable, whether an action should be replacing vs. mutating, reaction/observer tracking implications. Route it to the store-review skill. I note it needs its own pass; I do not analyze it, and I do not flag an `observer`/`action` pattern as a React finding.\n\n**Why the split matters:** the two concerns have different failure modes and different fixes. The missing dependency is a React staleness bug fixed in the component; the in-place mutation is a store-design question fixed in the store. Blurring them produces a finding that no single owner can act on. On a diff that touches both, this skill reports the component side and explicitly hands the store side off \u2014 it does not silently drop it or silently absorb it.\n\n**What I do *not* do:** edit either issue. Both are reported as findings with file:line and a concrete fix direction; even the one-line dependency-array change belongs to the author or a fix skill, not this read-only review.\n\nOne caveat worth stating in the report: if the in-place mutation of the array happens *in the component* (not the store), it's mine; if it happens *in the store action*, it's the store reviewer's \u2014 I'll say which one I mean rather than leaving it ambiguous, since that boundary is exactly where this split gets fuzzy.",
|
|
785
|
+
"outputSha256": "741fa81720afd3be8c582f4ad9246d5f008787f309d052eafa836856b7a25521",
|
|
786
|
+
"promptSha256": "b82a7c51d5a1842a351316f3f366d39262841678a49a08b2150cbe9b201657f6",
|
|
787
|
+
"deterministic": [],
|
|
788
|
+
"judge": {
|
|
789
|
+
"verdict": "pass",
|
|
790
|
+
"reason": "Criterion 1: answer explains the effect 'keeps using the value from the render it was created on, not the current one' \u2014 concrete staleness. Criterion 2: explicitly routes the store action's in-place mutation to 'the store-review skill' as store-review territory. No fail criteria: it defers the store mutation rather than fixing it, and flags the missing dependency."
|
|
791
|
+
},
|
|
792
|
+
"passed": true
|
|
793
|
+
},
|
|
794
|
+
{
|
|
795
|
+
"output": "Flag (this skill, React-side):\n\n- **Effect dependency bug** \u2014 the missing dependency is only a finding if the effect body actually *reads* a reactive value (prop/state derived from props) that is absent from the array. Check the body, not the array. If it reads `someProp` inside and the deps are `[]`, that is a HIGH stale-closure/staleness finding: the effect will run once with first-render values forever. Give the concrete file:line, the value read, and fix direction (add the dependency, or restructure if the effect only syncs state that should be derived during render \u2014 per `rules/patterns.mdc` \"Derive, don't sync\").\n- **Props/state shape around the `observer` wrapper** \u2014 if the effect depends on a value that the observer makes reactive and the deps omit it, the staleness is real regardless of MobX.\n\nLeave to another reviewer:\n\n- **The MobX `observer` wrapping itself, and the store action that mutates the array in place** \u2014 MobX observer/action/store/reaction patterns are explicitly out of scope for this skill. In-place `array.push(...)` inside a MobX `action` is the store-review skill's finding (whether it should be `observable`-array mutate vs replace, whether it runs in an action, reaction correctness). I note it needs its own store-review pass and do not report it here.\n\nThe one place the two touch without overlap: if the component *also* mutates a prop or a plain local state array in place, that's mine (referential identity). But the store's internal array mutation is not.\n\nReason this stays split: the diff spans two concerns with two different correctness models and two different reviewer checklists. Blurring them is a Red Flag (\"The store looks off too, I'll flag the MobX action pattern while I'm here\" \u2014 wrong). Report the React-side effect finding, explicitly note the store needs a separate store-review pass, and stop. Read-only, findings only, no edits.",
|
|
796
|
+
"outputSha256": "25b565624fc8358772672159bb1878fc76c97b69367d7457542400c838d65ae1",
|
|
797
|
+
"promptSha256": "b82a7c51d5a1842a351316f3f366d39262841678a49a08b2150cbe9b201657f6",
|
|
798
|
+
"deterministic": [],
|
|
799
|
+
"judge": {
|
|
800
|
+
"verdict": "pass",
|
|
801
|
+
"reason": "Pass 1: answer explains the effect 'will run once with first-render values forever' \u2014 concrete staleness. Pass 2: explicitly names 'the store-review skill's finding' for the in-place array mutation and defers it. No fail criteria: it does not give fix guidance on the store mutation, and it flags the missing dependency."
|
|
802
|
+
},
|
|
803
|
+
"passed": true
|
|
804
|
+
},
|
|
805
|
+
{
|
|
806
|
+
"output": "Flag the React-side findings only; leave the MobX store internals to the store reviewer.\n\n**Flag (this skill's scope):**\n\n- **The `useEffect` missing-dependency bug.** Cite file:line, name the reactive value the effect body actually reads that is absent from the dependency array, and state the consequence (the effect keeps using a stale captured value). Give the fix direction: add the missing value to the array, or restructure so the effect does not need to read it. If the omission is genuinely safe (a ref, a stable setter), say so and note the author should confirm intent \u2014 do not silently suppress it.\n- **The `observer(...)` wrapper itself, only insofar as it is plain React composition.** If wrapping changes what the component receives or how it renders (e.g. a partial/conditional wrapper, a wrapper placed inside a hook-order-sensitive branch), that is a React correctness concern and belongs here. Confirm whether the component path still violates Rules of Hooks independent of the wrapper.\n\n**Leave to another reviewer:**\n\n- **The store action mutating an array in place.** That is a state-management-library concern (MobX stores/actions/reactions), explicitly out of scope for this skill. Mutating the observable array is a MobX-internal design question \u2014 whether the action should replace the array, whether it needs `runInAction`, whether the observable is deep vs. reference \u2014 none of that is a React rendering-model finding. Route it to the store-review skill.\n- **Repository convention-doc issues** (styling tokens, i18n placement, local naming rules) if any appear in the diff \u2014 convention-review skill.\n\n**Do not blur the two.** Do not reword the store's in-place mutation as \"React relies on referential identity\" to pull it into scope. React's identity concern applies to values this component reads through props/state; an observable mutated inside a store action is a MobX subscription question, and MobX's reaction system may well handle it correctly. Reporting it here would be a false-positive-shaped finding dressed up in React vocabulary.\n\n**Net report shape:** one HIGH (or CRITICAL, per impact) finding on the effect dependency, plus any genuine React composition/Rules-of-Hooks finding around the `observer` wrapper if present \u2014 each with file:line, the concrete bug, and a fix direction, no code edits. One line noting the store action needs a separate store-review pass. Nothing that is actually a MobX or convention-doc issue.",
|
|
807
|
+
"outputSha256": "9372890c4068f49080a3d29b231df4ff5afed59d803e40cf74ffcb2ed0471aef",
|
|
808
|
+
"promptSha256": "b82a7c51d5a1842a351316f3f366d39262841678a49a08b2150cbe9b201657f6",
|
|
809
|
+
"deterministic": [],
|
|
810
|
+
"judge": {
|
|
811
|
+
"verdict": "pass",
|
|
812
|
+
"reason": "Pass 1: answer states the effect 'keeps using a stale captured value' and names the missing reactive value, with fix direction. Pass 2: explicitly routes the in-place array mutation to 'the store-review skill' as a state-management-library concern. Fail 1 avoided (defers, no store fix guidance). Fail 2 avoided (dependency flagged)."
|
|
813
|
+
},
|
|
814
|
+
"passed": true
|
|
815
|
+
},
|
|
816
|
+
{
|
|
817
|
+
"output": "I'd flag the React-side issues and route the store-side issue to a store reviewer. Concretely:\n\n**What I flag (React side):**\n\n1. **The missing `useEffect` dependency** \u2014 this is the core React-correctness finding. If the effect body reads a prop, state value, or other reactive value that is not in the dependency array, that's a staleness bug, not a lint nag to suppress. I'd report it as:\n ```\n HIGH: <file>:<line> \u2014 effect reads `<value>` but the dependency array omits it;\n the effect will keep using the first-render value forever. Add `<value>` to\n the deps, or confirm the omission is deliberate and safe (a ref / a setState\n function) and document why.\n ```\n I only call it a real finding after checking the actual reactive values read in the effect body \u2014 not from the dependency array alone. If the only thing missing is a `setState` function or a ref, that's not a finding.\n\n2. **The in-place array mutation, but only insofar as it touches the React component.** A store action that does `array.push(...)` / `array.sort(...)` / direct property assignment instead of replacing the array is an in-place-mutation issue. But here's the scope split: **the mutation living in a MobX store action is store-review territory, not mine.** My concern is whether a *component* mutates props, state, or a value derived from either, breaking referential identity. If the diff shows the component reading that mutated array and relying on identity (e.g., passing it to a memoized child or into a dep array), I flag the *React-visible* consequence and explicitly point the store author to a separate pass. I do **not** flag \"this action should be an action / should replace instead of mutate\" as my finding \u2014 that's the MobX store reviewer's call.\n\n**What I leave to another reviewer:**\n\n- **The MobX action/store pattern itself** \u2014 whether the action is annotated correctly, whether it should be an `action`/`flow`, whether the store should expose this mutation at all, whether observers are set up right, reactions, computed derivations. All of that is out of scope for this skill by design. I'd note \"the store side needs its own store-review pass\" and stop there.\n- **Repository convention issues** \u2014 naming, file placement, i18n string placement, styling tokens, CLAUDE.md-style house rules \u2014 belong to a convention reviewer. Not me.\n\n**Why the split matters:** the `observer()` wrapper and the store action look like the same \"MobX concern,\" but the effect-dependency bug is a plain React bug that would be a bug with or without MobX. Merging them into one reviewer invites blurry findings (\"this feels off\") instead of concrete file:line bugs with fix directions. And per my red-flags table: index-as-key and missing-deps don't get excused just because \"the store looks off too,\" and I don't fix even a one-line dep array myself \u2014 this skill is read-only, finding only.\n\nSo the report reads: one HIGH-effect-dependency finding (scoped to the component), one note on the React-visible consequence of the mutated array if the component relies on its identity, and a one-line handoff that the store action mutation is for the store reviewer.",
|
|
818
|
+
"outputSha256": "9b74c95d91d142e7a5d08c8ec9ead7bafe6024d3e7c59a3286e82607d29a3f8c",
|
|
819
|
+
"promptSha256": "b82a7c51d5a1842a351316f3f366d39262841678a49a08b2150cbe9b201657f6",
|
|
820
|
+
"deterministic": [],
|
|
821
|
+
"judge": {
|
|
822
|
+
"verdict": "pass",
|
|
823
|
+
"reason": "Criterion 1: answer explains the effect 'will keep using the first-render value forever' \u2014 concrete staleness, not just 'missing dep'. Criterion 2: explicitly routes the in-place mutation to 'the MobX store reviewer' / 'store-review pass', declining to flag it as its own finding. No fail criteria hold."
|
|
824
|
+
},
|
|
825
|
+
"passed": true
|
|
826
|
+
},
|
|
827
|
+
{
|
|
828
|
+
"output": "Flag (this skill's scope \u2014 React rendering/effect bugs):\n\n- **HIGH \u2014 the `useEffect` with a missing dependency.** A missing dependency on a reactive value the effect body actually reads (a prop/state value, not a ref or a setState function) is a stale-closure/staleness bug, not a lint nag. Cite file:line, name the value read inside the effect body that is absent from the dependency array, and give the fix direction (add it, or move the read into a ref/callback if the omission is intentional \u2014 but the author must confirm intent explicitly).\n- That is the one clear React-model finding in the described diff. If the `observer` wrapper itself is doing anything to the hook (e.g. a hook called conditionally under it, or hooks below an early return), that also falls here \u2014 flag Rules of Hooks violations only if present.\n\nLeave to another reviewer:\n\n- **The store action mutating an array in place** \u2014 this is a state-management store issue, explicitly out of scope here. Even though in-place mutation (`push`, `sort`, direct assignment) is a real bug shape, when it happens inside a *store action* it belongs to the store-review skill, not this one. Do not flag the MobX `observer`/`action`/store pattern from this skill. Note it needs its own store-review pass and stop there \u2014 do not blur the two reviews.\n- **Nuance worth naming:** if the in-place mutation produces a *new array reference read by component state* (i.e. the mutation lands in a plain React `useState` value the component derives from, failing referential-identity detection on render), the component-side consequence is arguably in scope \u2014 but the act itself is in the action/store. Keep the report clean: one finding names the effect bug, one line notes the store needs a separate store-review skill.\n\nDo **not** flag: naming/formatting, `observer` usage as such, or anything a repo convention doc would own.\n\nRead-only: report findings with file:line and a fix direction \u2014 never edit, never silently suppress the dependency omission as \"probably intentional,\" and never fix it yourself even though it is one line.\n\nVerification: every finding cites file:line and a concrete bug; nothing reported here is actually a MobX/store-pattern or convention-doc issue; the effect finding is grounded in the reactive values read in the effect body, not inferred from the array alone.",
|
|
829
|
+
"outputSha256": "f51297378e30bc9189dbd86e6c031abcc0c59e64002336047362d1bd2576c437",
|
|
830
|
+
"promptSha256": "b82a7c51d5a1842a351316f3f366d39262841678a49a08b2150cbe9b201657f6",
|
|
831
|
+
"deterministic": [],
|
|
832
|
+
"judge": {
|
|
833
|
+
"verdict": "pass",
|
|
834
|
+
"reason": "Criterion 1: answer calls the missing dependency a 'stale-closure/staleness bug' where the effect reads a value absent from the array, giving fix direction (add it or move read into ref). Criterion 2: explicitly assigns the in-place array mutation to 'the store-review skill, not this one' and says 'do not blur the two reviews.' No fail criteria hold."
|
|
835
|
+
},
|
|
836
|
+
"passed": true
|
|
837
|
+
},
|
|
838
|
+
{
|
|
839
|
+
"output": "Scope check first: this diff has three distinct concerns living in one change, and they do not all belong to this skill.\n\n**What I flag (React-side, in scope):**\n\n1. **The missing `useEffect` dependency** \u2014 this is the core of this skill. I read the effect body, find every reactive value it actually reads (props, state, context, or anything derived from them), and compare that set against the dependency array \u2014 not the other way around. If the array omits a value the body genuinely reads, that is a real staleness bug: the effect keeps using the value captured from an earlier render and silently never re-runs when it changes. I report it as file:line, name the specific value that is read-but-not-listed, and state the fix direction (add it, or restructure so the value is not read reactively) \u2014 I do not edit it. If the omission is a ref or a stable setState function, I say so and drop it.\n\n2. **The in-place array mutation, IF it happens on a value React owns** \u2014 props, state, or anything derived from either. `.push`/`.sort`/direct assignment on such a value defeats React's referential-identity change detection and is a rendering bug, in scope. I flag it with the concrete consequence (child does not re-render / memoized subtree stays stale) and the fix direction (produce a new array). This stays in scope precisely because it is React's rendering model, not because it lives near a store.\n\n3. **Anything React-side in the same diff** that I can see from context: Rules of Hooks violations (a hook below an early `return` or in a conditional), list `key` misuse, a `setState`-from-prop sync effect that should be derived during render, accessibility gaps (wrong semantic element, missing label), and DOM XSS sinks (`dangerouslySetInnerHTML`, `javascript:` URLs reaching `href`/`src`).\n\n**What I leave to another reviewer (out of scope by design):**\n\n- **The MobX observer wrapper and the store action's in-place mutation** \u2014 as *store/state-management patterns*, these are a store-review skill's job. An `observer` boundary, an `action`, whether the mutation is wrapped correctly, observable-vs-raw access \u2014 none of that is this skill's call.\n\nThe one place the two touch is the mutation, and I keep them separate on purpose: if the mutated array is **local React state or a prop**, it is a React rendering bug and I flag it here. If it is **store-owned observable state mutated from inside an action**, the in-place mutation is the store reviewer's finding, not mine \u2014 I say \"the store mutation needs its own store-review pass\" and move on. I do not blur them into one finding.\n\nI also skip anything that is purely a repository convention (naming, formatting, i18n, styling tokens) \u2014 that is the convention reviewer's job, and I do not route a style preference through as a correctness bug.\n\n**Net:** I report the missing effect dependency and, conditionally, the mutation-as-React-bug, each with file:line and a fix direction, findings only, no code edit. I explicitly hand the MobX observer/action and the store-owned mutation to the store reviewer and any convention issues to the convention reviewer, and I note that this diff needs both.",
|
|
840
|
+
"outputSha256": "ef52aa2651e43c6d793eb5bcf3860be447ad493296d59e604980fce4e0b370f9",
|
|
841
|
+
"promptSha256": "b82a7c51d5a1842a351316f3f366d39262841678a49a08b2150cbe9b201657f6",
|
|
842
|
+
"deterministic": [],
|
|
843
|
+
"judge": {
|
|
844
|
+
"verdict": "pass",
|
|
845
|
+
"reason": "Criterion 1: answer explains the effect 'keeps using the value captured from an earlier render and silently never re-runs when it changes' \u2014 concrete staleness. Criterion 2: explicitly names 'a store-review skill's job' for the MobX observer/action and store-owned mutation, saying 'the store mutation needs its own store-review pass.' No fail criteria hold."
|
|
846
|
+
},
|
|
847
|
+
"passed": true
|
|
848
|
+
}
|
|
849
|
+
]
|
|
850
|
+
}
|
|
851
|
+
],
|
|
852
|
+
"verdict": "pass",
|
|
853
|
+
"scope": "bundled",
|
|
854
|
+
"skillDigest": "0628a96e690936cc5f3fc6712e64d2cb71162be56064a5a0c254206a9f7fb462",
|
|
855
|
+
"catalogDigest": "4f4016d410837e394a27e5b247e38ef2f57a1ee0baba4436ce7d3d71e223333d",
|
|
856
|
+
"judgePromptVersion": "2026-09-25.1",
|
|
857
|
+
"runner": "deepseek",
|
|
858
|
+
"model": "deepseek-chat",
|
|
859
|
+
"runnerPromptVersion": "2026-09-25.1",
|
|
860
|
+
"recordedAt": "2026-09-25T05:20:21.559Z",
|
|
861
|
+
"judge": "deepseek",
|
|
862
|
+
"judgeModel": "deepseek-chat"
|
|
863
|
+
},
|
|
864
|
+
{
|
|
865
|
+
"schemaVersion": "1.0.0",
|
|
866
|
+
"skillId": "react/react-implementation",
|
|
867
|
+
"strictness": "high",
|
|
868
|
+
"trials": 10,
|
|
869
|
+
"triggerAccuracy": {
|
|
870
|
+
"truePositive": 7,
|
|
871
|
+
"falsePositive": 0,
|
|
872
|
+
"positives": 7,
|
|
873
|
+
"negatives": 6
|
|
874
|
+
},
|
|
875
|
+
"evidence": "authored",
|
|
876
|
+
"scenarios": [
|
|
877
|
+
{
|
|
878
|
+
"id": "trigger-positive-1",
|
|
879
|
+
"kind": "trigger-positive",
|
|
880
|
+
"prompt": "Implement a new React function component with useState and useEffect for the user profile page",
|
|
881
|
+
"strictness": "high",
|
|
882
|
+
"trials": 1,
|
|
883
|
+
"passes": 1,
|
|
884
|
+
"passRate": 1,
|
|
885
|
+
"passAtK": 1,
|
|
886
|
+
"grader": "trigger-rank-fork-family",
|
|
887
|
+
"status": "ran",
|
|
888
|
+
"deterministic": true
|
|
889
|
+
},
|
|
890
|
+
{
|
|
891
|
+
"id": "trigger-positive-2",
|
|
892
|
+
"kind": "trigger-positive",
|
|
893
|
+
"prompt": "This component has a useEffect that just copies a prop into state, can you clean it up",
|
|
894
|
+
"strictness": "high",
|
|
895
|
+
"trials": 1,
|
|
896
|
+
"passes": 1,
|
|
897
|
+
"passRate": 1,
|
|
898
|
+
"passAtK": 1,
|
|
899
|
+
"grader": "trigger-rank-fork-family",
|
|
900
|
+
"status": "ran",
|
|
901
|
+
"deterministic": true
|
|
902
|
+
},
|
|
903
|
+
{
|
|
904
|
+
"id": "trigger-positive-3",
|
|
905
|
+
"kind": "trigger-positive",
|
|
906
|
+
"prompt": "Add a save button to this form using useActionState with an optimistic update",
|
|
907
|
+
"strictness": "high",
|
|
908
|
+
"trials": 1,
|
|
909
|
+
"passes": 1,
|
|
910
|
+
"passRate": 1,
|
|
911
|
+
"passAtK": 1,
|
|
912
|
+
"grader": "trigger-rank-fork-family",
|
|
913
|
+
"status": "ran",
|
|
914
|
+
"deterministic": true
|
|
915
|
+
},
|
|
916
|
+
{
|
|
917
|
+
"id": "trigger-positive-4",
|
|
918
|
+
"kind": "trigger-positive",
|
|
919
|
+
"prompt": "Wrap this list render in a Suspense boundary with an error boundary fallback",
|
|
920
|
+
"strictness": "high",
|
|
921
|
+
"trials": 1,
|
|
922
|
+
"passes": 1,
|
|
923
|
+
"passRate": 1,
|
|
924
|
+
"passAtK": 1,
|
|
925
|
+
"grader": "trigger-rank-fork-family",
|
|
926
|
+
"status": "ran",
|
|
927
|
+
"deterministic": true
|
|
928
|
+
},
|
|
929
|
+
{
|
|
930
|
+
"id": "trigger-positive-5",
|
|
931
|
+
"kind": "trigger-positive",
|
|
932
|
+
"prompt": "Convert this class component to a function component with hooks",
|
|
933
|
+
"strictness": "high",
|
|
934
|
+
"trials": 1,
|
|
935
|
+
"passes": 1,
|
|
936
|
+
"passRate": 1,
|
|
937
|
+
"passAtK": 1,
|
|
938
|
+
"grader": "trigger-rank-fork-family",
|
|
939
|
+
"status": "ran",
|
|
940
|
+
"deterministic": true
|
|
941
|
+
},
|
|
942
|
+
{
|
|
943
|
+
"id": "trigger-positive-6",
|
|
944
|
+
"kind": "trigger-positive",
|
|
945
|
+
"prompt": "This ref prop is throwing a type error, wire it up the React 19 way without forwardRef",
|
|
946
|
+
"strictness": "high",
|
|
947
|
+
"trials": 1,
|
|
948
|
+
"passes": 1,
|
|
949
|
+
"passRate": 1,
|
|
950
|
+
"passAtK": 1,
|
|
951
|
+
"grader": "trigger-rank-fork-family",
|
|
952
|
+
"status": "ran",
|
|
953
|
+
"deterministic": true
|
|
954
|
+
},
|
|
955
|
+
{
|
|
956
|
+
"id": "trigger-positive-7",
|
|
957
|
+
"kind": "trigger-positive",
|
|
958
|
+
"prompt": "Add useTransition to this React component so typing in the search input does not block rendering",
|
|
959
|
+
"strictness": "high",
|
|
960
|
+
"trials": 1,
|
|
961
|
+
"passes": 1,
|
|
962
|
+
"passRate": 1,
|
|
963
|
+
"passAtK": 1,
|
|
964
|
+
"grader": "trigger-rank-fork-family",
|
|
965
|
+
"status": "ran",
|
|
966
|
+
"deterministic": true
|
|
967
|
+
},
|
|
968
|
+
{
|
|
969
|
+
"id": "trigger-negative-1",
|
|
970
|
+
"kind": "trigger-negative",
|
|
971
|
+
"prompt": "Write pytest fixtures for this Django view",
|
|
972
|
+
"strictness": "high",
|
|
973
|
+
"trials": 1,
|
|
974
|
+
"passes": 1,
|
|
975
|
+
"passRate": 1,
|
|
976
|
+
"passAtK": 1,
|
|
977
|
+
"grader": "trigger-rank-fork-family",
|
|
978
|
+
"status": "ran",
|
|
979
|
+
"deterministic": true
|
|
980
|
+
},
|
|
981
|
+
{
|
|
982
|
+
"id": "trigger-negative-2",
|
|
983
|
+
"kind": "trigger-negative",
|
|
984
|
+
"prompt": "Review this component diff for Rules of Hooks violations and stale closures",
|
|
985
|
+
"strictness": "high",
|
|
986
|
+
"trials": 1,
|
|
987
|
+
"passes": 1,
|
|
988
|
+
"passRate": 1,
|
|
989
|
+
"passAtK": 1,
|
|
990
|
+
"grader": "trigger-rank-fork-family",
|
|
991
|
+
"status": "ran",
|
|
992
|
+
"deterministic": true
|
|
993
|
+
},
|
|
994
|
+
{
|
|
995
|
+
"id": "trigger-negative-3",
|
|
996
|
+
"kind": "trigger-negative",
|
|
997
|
+
"prompt": "The vite build is failing with a JSX syntax error, fix the build",
|
|
998
|
+
"strictness": "high",
|
|
999
|
+
"trials": 1,
|
|
1000
|
+
"passes": 1,
|
|
1001
|
+
"passRate": 1,
|
|
1002
|
+
"passAtK": 1,
|
|
1003
|
+
"grader": "trigger-rank-fork-family",
|
|
1004
|
+
"status": "ran",
|
|
1005
|
+
"deterministic": true
|
|
1006
|
+
},
|
|
1007
|
+
{
|
|
1008
|
+
"id": "trigger-negative-4",
|
|
1009
|
+
"kind": "trigger-negative",
|
|
1010
|
+
"prompt": "Upgrade our app from React 18 to React 19 and run the codemods",
|
|
1011
|
+
"strictness": "high",
|
|
1012
|
+
"trials": 1,
|
|
1013
|
+
"passes": 1,
|
|
1014
|
+
"passRate": 1,
|
|
1015
|
+
"passAtK": 1,
|
|
1016
|
+
"grader": "trigger-rank-fork-family",
|
|
1017
|
+
"status": "ran",
|
|
1018
|
+
"deterministic": true
|
|
1019
|
+
},
|
|
1020
|
+
{
|
|
1021
|
+
"id": "trigger-negative-5",
|
|
1022
|
+
"kind": "trigger-negative",
|
|
1023
|
+
"prompt": "Write a React Testing Library test that clicks a button and checks the modal opens",
|
|
1024
|
+
"strictness": "high",
|
|
1025
|
+
"trials": 1,
|
|
1026
|
+
"passes": 1,
|
|
1027
|
+
"passRate": 1,
|
|
1028
|
+
"passAtK": 1,
|
|
1029
|
+
"grader": "trigger-rank-fork-family",
|
|
1030
|
+
"status": "ran",
|
|
1031
|
+
"deterministic": true
|
|
1032
|
+
},
|
|
1033
|
+
{
|
|
1034
|
+
"id": "trigger-negative-6",
|
|
1035
|
+
"kind": "trigger-negative",
|
|
1036
|
+
"prompt": "Add error handling and structured logging to this Node Express route handler",
|
|
1037
|
+
"strictness": "high",
|
|
1038
|
+
"trials": 1,
|
|
1039
|
+
"passes": 1,
|
|
1040
|
+
"passRate": 1,
|
|
1041
|
+
"passAtK": 1,
|
|
1042
|
+
"grader": "trigger-rank-fork-family",
|
|
1043
|
+
"status": "ran",
|
|
1044
|
+
"deterministic": true
|
|
1045
|
+
},
|
|
1046
|
+
{
|
|
1047
|
+
"id": "derive-not-sync",
|
|
1048
|
+
"kind": "behavior",
|
|
1049
|
+
"prompt": "This React component has a useEffect that calls setState every time the `user` prop changes, just to copy user.name into a local `displayName` state variable used only for rendering. How should I fix it?",
|
|
1050
|
+
"strictness": "high",
|
|
1051
|
+
"trials": 10,
|
|
1052
|
+
"passes": 10,
|
|
1053
|
+
"passRate": 1,
|
|
1054
|
+
"passAtK": 1,
|
|
1055
|
+
"grader": "judge",
|
|
1056
|
+
"status": "ran",
|
|
1057
|
+
"trialRecords": [
|
|
1058
|
+
{
|
|
1059
|
+
"output": "Just delete the state and the effect, and compute the value during render:\n\n```tsx\nfunction UserCard({ user }: { user: User }) {\n const displayName = user.name;\n return <h2>{displayName}</h2>;\n}\n```\n\nIf `user.name` needs light formatting, do that inline at the call site (`const displayName = format(user.name)`) \u2014 still render-derived, still no state. The general rule: a value computable from existing props/state during render is not state. Copying it into state via an effect is the classic React anti-pattern \u2014 it costs an extra render, briefly shows a stale value on the first paint after `user` changes, and adds a dependency-array bug surface for no benefit.\n\nA few cases that look like this but need a different fix:\n\n- If `displayName` is meant to be independently editable (user types a nickname that should survive a `user` change), that's real state, not derived. Initialize it once (`useState(() => user.name)`) and drop the effect \u2014 don't resync it on every prop change. If it *should* reset when `user` changes, that's a remount signal: pass `key={user.id}` from the parent instead of an effect.\n- If the \"copy\" actually depends on an external round-trip (fetching something, reading storage), that's a legitimate effect \u2014 but it belongs to the synchronization with that external thing, not to mirroring `user.name`.\n- If the effect also does other work (logging, subscribing) alongside the `setState`, keep that part and only remove the `setState` mirror.\n\nSo: derive, don't sync. Remove the state declaration and the whole `useEffect`, and let the render compute `displayName` directly.",
|
|
1060
|
+
"outputSha256": "9c4c216c9c43820b9d43097417430f3b6ee53ad13363c5b771ce9681ba399b8d",
|
|
1061
|
+
"promptSha256": "1f0698a16b39cda8f43ffc6404165973512958378d3a2045bd75638deec164df",
|
|
1062
|
+
"deterministic": [],
|
|
1063
|
+
"judge": {
|
|
1064
|
+
"verdict": "pass",
|
|
1065
|
+
"reason": "Shows concrete derived replacement: `const displayName = user.name;` (and `format(user.name)` inline). Explicitly removes both: 'delete the state and the effect' and 'Remove the state declaration and the whole useEffect.' No sync effect retained as the fix."
|
|
1066
|
+
},
|
|
1067
|
+
"passed": true
|
|
1068
|
+
},
|
|
1069
|
+
{
|
|
1070
|
+
"output": "Delete the state variable and the effect, and compute the value during render instead.\n\nBefore:\n\n```tsx\nfunction UserCard({ user }: { user: User }) {\n const [displayName, setDisplayName] = useState(user.name);\n\n useEffect(() => {\n setDisplayName(user.name);\n }, [user.name]);\n\n return <span>{displayName}</span>;\n}\n```\n\nAfter:\n\n```tsx\nfunction UserCard({ user }: { user: User }) {\n return <span>{user.name}</span>;\n}\n```\n\nIf the display value is a transformation rather than a direct copy, make it a plain derived value:\n\n```tsx\nconst displayName = user.name.trim() || \"Anonymous\";\n```\n\nWhy the effect version is worse, not just longer:\n\n- It causes an extra render on every `user` change: the first render shows the stale name, then the effect fires and re-renders with the new one. That is a visible flash whenever the prop changes \u2014 and it is the exact bug people file as \"the name updates one frame late.\"\n- `displayName` is now a second source of truth for something `props` already own. Any future code that forgets to also update `displayName` produces a component that renders inconsistently with its own props.\n- The dependency array is a standing bug surface. Here `[user.name]` happens to be right, but the moment someone widens it to `[user]`, or reads another field inside, the effect either over-fires or under-fires, and nothing type-checks against it.\n\nThe one case that legitimately needs an effect like this is when the value must be *retained* across prop changes rather than recomputed \u2014 e.g. an editable draft that should only reset under a specific condition (a different user id, a clicked \"reset\" button). Even then, prefer remounting via `key` over an effect:\n\n```tsx\n// In the parent: remounts the component (and resets all its state) when user.id changes\n<UserCard key={user.id} user={user} />\n\n// Child: initializes once from props, no effect needed\nfunction UserCard({ user }: { user: User }) {\n const [draftName, setDraftName] = useState(user.name); // seed only\n return <input value={draftName} onChange={(e) => setDraftName(e.target.value)} />;\n}\n```\n\nHere `draftName` is real state \u2014 the user edits it, so it isn't derivable from props \u2014 and `key` handles the reset cleanly with no effect and no stale-render window.\n\nSo: if it is only used for rendering, it is not state. Delete the effect and the `useState`, and read `user.name` directly (or derive from it). Reach for `key`-based remount only when the value is genuinely editable state that must reset on an identity change, and treat a bare sync-effect as a smell to justify rather than a default.",
|
|
1071
|
+
"outputSha256": "cd92c7b8a7b20c1f679434b4c6887067019f414672f0e07c2e6aa4343203bc5a",
|
|
1072
|
+
"promptSha256": "1f0698a16b39cda8f43ffc6404165973512958378d3a2045bd75638deec164df",
|
|
1073
|
+
"deterministic": [],
|
|
1074
|
+
"judge": {
|
|
1075
|
+
"verdict": "pass",
|
|
1076
|
+
"reason": "Answer shows concrete derived replacement: `<span>{user.name}</span>` and `const displayName = user.name.trim() || \"Anonymous\";`. It explicitly removes both: 'Delete the state variable and the effect' and the After code drops both useState and useEffect. No fail criterion: it eliminates the sync effect rather than keeping it."
|
|
1077
|
+
},
|
|
1078
|
+
"passed": true
|
|
1079
|
+
},
|
|
1080
|
+
{
|
|
1081
|
+
"output": "Delete the `useState` for `displayName` and the `useEffect` that syncs it, then compute the value directly during render:\n\n```tsx\nfunction UserCard({ user }: { user: User }) {\n const displayName = user.name;\n return <span>{displayName}</span>;\n}\n```\n\nThat's the whole fix. `user.name` is computable from an existing prop with no external round-trip involved, so it is a derived value, not state. Copying it into `useState` and mirroring it in an effect adds a second source of truth that must be kept in sync, costs an extra render per `user` change, and shows a stale name for one frame after `user` updates \u2014 all for a value you could just read.\n\nTwo things to check before you call it done:\n\n**If you were actually transforming the value**, not just copying it, do the transform in the same derived expression rather than in an effect:\n\n```tsx\nconst displayName = user.name.trim() || user.email;\n```\n\nDeriving runs on every render and is cheap; it will not cause extra renders the way `setState` in an effect does.\n\n**If the effect was doing something the naive derivation cannot**, the fix is different and you should not just inline it:\n\n- If `displayName` legitimately needs to be *editable* local state that starts from `user.name` but the user can then change \u2014 that is real state, not derived. Keep `useState(() => user.name)` for the initial value and remove the effect. To reset it when a *different* user arrives, key the component (`<UserCard key={user.id} ... />`) so React remounts it with fresh state, instead of an effect that watches `user` and calls `setState`.\n- If the effect was reading something mutable/outside React (a DOM measurement, a ref, a subscription), that is the narrow case where an effect belongs \u2014 but it would not be a plain copy of `user.name` into render-only state, so it does not match what you described.\n\nGiven that your `displayName` is \"used only for rendering\" and is just `user.name`, this is the first, simple case: remove both the state and the effect, derive during render. Then re-run `keryx test run --changed --strict` (or the project's type-check + lint + test) and confirm no `react-hooks/exhaustive-deps` warning is left dangling from a now-deleted effect.",
|
|
1082
|
+
"outputSha256": "505e52bff7a7987e192a5d1f62d82265385e4c34623b623414b0f9f5f430ea2c",
|
|
1083
|
+
"promptSha256": "1f0698a16b39cda8f43ffc6404165973512958378d3a2045bd75638deec164df",
|
|
1084
|
+
"deterministic": [],
|
|
1085
|
+
"judge": {
|
|
1086
|
+
"verdict": "pass",
|
|
1087
|
+
"reason": "Answer shows concrete derived replacement `const displayName = user.name;` (and a transform variant `user.name.trim() || user.email`), and explicitly says to delete both the `useState` for `displayName` and the `useEffect` that syncs it. No fail criterion: it eliminates the sync effect rather than keeping it."
|
|
1088
|
+
},
|
|
1089
|
+
"passed": true
|
|
1090
|
+
},
|
|
1091
|
+
{
|
|
1092
|
+
"output": "Delete the effect and the `displayName` state entirely, and derive the value during render instead:\n\n```tsx\nfunction UserCard({ user }: { user: User }) {\n const displayName = user.name;\n return <span>{displayName}</span>;\n}\n```\n\nIf `displayName` was genuinely just a copy of `user.name` with no independent editing, that one line replaces the state, the effect, the `useState` import, and the dependency array. You get one fewer render per prop change, no flash of the previous name, and no dependency-array bug surface.\n\nA few things to check before you commit to the pure derivation:\n\n- **Is anything else writing to `displayName`?** If the user can type into it, or some handler does `setDisplayName(...)`, then it is not a pure copy. The right shape is usually an uncontrolled input with `defaultValue={user.name}` (keyed by `user.id` if you want it to reset when the user changes), or editable state that is reset via a `key` on the component rather than an effect. An effect that mirrors a prop into state is a symptom of needing a `key`-based remount.\n- **Does it transform the name every render in a way that is expensive?** If `user.name` feeds a genuinely expensive computation (formatting a huge name list, parsing), compute it with `useMemo` rather than storing it in state \u2014 still derived, still not a sync effect. But only do that if the React Compiler is not enabled for this project; if it is, plain derivation is fine and the compiler handles the memoization.\n- **Is the effect actually doing something beyond the copy?** If it also, say, fires an analytics event or writes to localStorage when the user changes, that side effect is legitimate and should stay \u2014 but it should depend on the minimal value it actually needs (`user.id`, not the whole `user` object *and* `displayName`), and it must not call `setState` merely to reflect the new name.\n\nThe mental model for the fix: a value computable from existing props during render is not state. State is for values that change over time on their own and that render must remember between updates. `user.name` is neither \u2014 it is a function of props, so it belongs in a `const`, not in `useState` synced by `useEffect`.\n\nAfter the change, no hook order changes (you are removing hooks, not making them conditional), no list keys are affected, and `keryx test run --changed --strict` should pass \u2014 if a test was asserting the intermediate state value, update the test to assert the rendered output instead of the internal state, since the state no longer exists.",
|
|
1093
|
+
"outputSha256": "ff051154bb47c3b469e80b377da4ba3075ae549194dd10d9b6a59d4873a4d39f",
|
|
1094
|
+
"promptSha256": "1f0698a16b39cda8f43ffc6404165973512958378d3a2045bd75638deec164df",
|
|
1095
|
+
"deterministic": [],
|
|
1096
|
+
"judge": {
|
|
1097
|
+
"verdict": "pass",
|
|
1098
|
+
"reason": "Answer shows concrete derived replacement `const displayName = user.name;` in code, and explicitly says 'Delete the effect and the `displayName` state entirely' removing both useState slot and useEffect. Also mentions useMemo for expensive cases. No fail criterion: it eliminates the sync effect rather than keeping it."
|
|
1099
|
+
},
|
|
1100
|
+
"passed": true
|
|
1101
|
+
},
|
|
1102
|
+
{
|
|
1103
|
+
"output": "Delete the `displayName` state and the effect entirely, and just read `user.name` (or derive `displayName` from it) during render.\n\n```tsx\n// Before\nconst [displayName, setDisplayName] = useState(user.name);\nuseEffect(() => {\n setDisplayName(user.name);\n}, [user]);\n\nreturn <span>{displayName}</span>;\n\n// After\nreturn <span>{user.name}</span>;\n```\n\nIf `displayName` involves some transformation of `user.name` (formatting, truncation, etc.), that's still derived \u2014 compute it inline or in a small pure helper, not in state:\n\n```tsx\nconst displayName = formatName(user.name);\nreturn <span>{displayName}</span>;\n```\n\nIf the computation is genuinely expensive, wrap it in `useMemo` keyed on `user.name` \u2014 but only if the React Compiler isn't enabled for this project; when it is, let the compiler handle it and leave it as a plain computation.\n\nWhy this is the right fix, not just a smaller one:\n\n- The effect version renders twice on every `user` change \u2014 once with the stale `displayName`, then again after the effect fires and `setState` lands. The render-only version has no stale frame.\n- `user.name` and `displayName` were two sources of truth that could drift. Nothing here needs the second one; it's not independent state, it's a copy.\n- The effect's dependency array was a bug surface for no benefit \u2014 a `user` object that's a new reference with the same `name` would have re-triggered it pointlessly.\n\nThe one case where an effect is actually warranted: if `displayName` needs to persist *changes* the user makes independently of `user` (an editable field, say), then it's real state, not derived \u2014 but then it should be seeded once and keyed to identity rather than re-synced on every prop change. If that's what you have, the right move is to key the component on the user id (`<UserCard key={user.id} ... />`) so the local state resets naturally when you switch users, rather than an effect that mirrors the prop back in.\n\nAfter the change, run `keryx test run --changed --strict` (or the project's type-check + lint + tests) and confirm `git status` shows only the component file you touched.",
|
|
1104
|
+
"outputSha256": "83ac1b249ac5320a3ee71c7af2e4735c3023f51919ecf744c2a52d138f7529da",
|
|
1105
|
+
"promptSha256": "1f0698a16b39cda8f43ffc6404165973512958378d3a2045bd75638deec164df",
|
|
1106
|
+
"deterministic": [],
|
|
1107
|
+
"judge": {
|
|
1108
|
+
"verdict": "pass",
|
|
1109
|
+
"reason": "Answer shows concrete derived replacement: `return <span>{user.name}</span>` and `const displayName = formatName(user.name)`, plus useMemo for expensive cases. It explicitly says 'Delete the `displayName` state and the effect entirely', removing both useState slot and useEffect. No fail criterion: it eliminates the sync effect rather than keeping it."
|
|
1110
|
+
},
|
|
1111
|
+
"passed": true
|
|
1112
|
+
},
|
|
1113
|
+
{
|
|
1114
|
+
"output": "Delete the state and the effect, and compute the value during render:\n\n```tsx\nfunction UserCard({ user }: { user: User }) {\n const displayName = user.name;\n // ...render using displayName\n}\n```\n\nThat is the whole fix for the code you described. `user.name` is fully computable from a prop you already have, so it is not state \u2014 it is a derived value. Adding a `useState` + `useEffect` pair to copy it in buys you nothing and costs you:\n\n- an extra render on every `user` change (prop change \u2192 render \u2192 effect \u2192 setState \u2192 another render)\n- one frame where `displayName` still holds the *previous* user's name (the flash of stale UI)\n- a dependency-array bug surface that will bite the next time someone edits the effect\n\n**If there is real local state involved.** The one case where a plain derived value is not enough is when the user is allowed to *edit* the name and you want to keep the in-progress edit, resetting it when a *different* user is selected. That is genuinely state, but do not sync it with an effect \u2014 reset it by remounting via `key`:\n\n```tsx\n// parent\n<UserCard key={user.id} user={user} />\n\n// child\nfunction UserCard({ user }: { user: User }) {\n const [draftName, setDraftName] = useState(user.name); // initial value only\n const [isSaving, startSaving] = useTransition();\n // ...no effect needed; new user.id => fresh component => fresh draft\n}\n```\n\n`useState(user.name)` reads `user.name` only as the *initial* value, which is exactly what you want here: the draft resets whenever the identity of the user (`user.id`) changes, without any effect mirroring a prop into state.\n\n**Workflow, given the skill's steps:**\n\n1. Confirm from neighboring components whether `displayName` is actually only ever read (pure derived) or also written (draft state) \u2014 that decides between the two fixes above.\n2. Apply the matching fix.\n3. Verify: check that no hook was left conditional or orphaned, that the effect is fully removed (not just its body emptied), and that `user.name` is not referenced anywhere still expecting a state setter.\n4. Run `keryx test run --changed --strict` plus the project's `react-hooks` lint, and confirm `git status` shows only the intended file(s).\n\n**What not to do.** Do not keep the effect \"but guard it\" with a `if (user.name !== displayName) setState(...)` comparison, and do not reach for `useMemo` here. A guard effect still runs after paint and still adds a render pass and a stale frame on the change where it does fire; `useMemo` is for expensive *computation*, not for replacing state that should not exist. The value is derivable \u2014 derive it.",
|
|
1115
|
+
"outputSha256": "5aa607bede712d7ebce226b69fe964ac201c181cf7b10c36ccd27ee9587351d2",
|
|
1116
|
+
"promptSha256": "1f0698a16b39cda8f43ffc6404165973512958378d3a2045bd75638deec164df",
|
|
1117
|
+
"deterministic": [],
|
|
1118
|
+
"judge": {
|
|
1119
|
+
"verdict": "pass",
|
|
1120
|
+
"reason": "Answer shows concrete derived replacement `const displayName = user.name;` (criterion 1) and explicitly says 'Delete the state and the effect' removing both useState slot and useEffect (criterion 2). It does not keep the sync effect; it warns against guarding it. Fail criterion 1 does not hold."
|
|
1121
|
+
},
|
|
1122
|
+
"passed": true
|
|
1123
|
+
},
|
|
1124
|
+
{
|
|
1125
|
+
"output": "Delete the state variable and the effect, and compute the value during render instead:\n\n```tsx\nfunction UserCard({ user }: { user: User }) {\n const displayName = user.name;\n return <span>{displayName}</span>;\n}\n```\n\nThat's the whole fix. `displayName` is just `user.name` \u2014 it is computable from props with no external round-trip, so it was never state. Copying it into state and syncing via an effect added: an extra render pass on every `user` change, a frame where `displayName` shows the *previous* user's name, and a dependency-array bug waiting to happen. None of that buys you anything.\n\nA few notes on when this rule does *not* apply:\n\n- If you actually need to *edit* the name locally (a form field the user types into before saving), then local state is legitimate \u2014 but initialize it from the prop once and make it clear it's editable state, not a mirror. If the parent needs to be able to reset it when `user` changes, prefer remounting the child with a `key={user.id}` over an effect that resets state; the key approach is what the Red Flags table means by \"`key`-based remount.\"\n- If computing the value is genuinely expensive (not the case for reading a field), use `useMemo`, not state + effect. And if the React Compiler is enabled in this project, skip even that \u2014 let the compiler memoize it.\n- If the value needs an external round-trip to compute (a network call, a subscription), *then* an effect is the right home \u2014 that's synchronization with something outside React, which is the one thing effects are for.\n\nSo in your case: no `useState`, no `useEffect`, just `const displayName = user.name;`.\n\nOne caveat worth checking before you commit: scan the rest of the component for any *other* reader or writer of that local `displayName` state. If something else sets it (a button handler, a debounce), you don't have a pure derived value and deleting the state will break that path \u2014 read the whole component first, don't just delete the effect in isolation.\n\nVerify with the project's type-check and hooks lint, then `keryx test run --changed --strict`. Run `git status` to confirm only the intended component and its test changed.",
|
|
1126
|
+
"outputSha256": "c884cf3876ffdab6c6e23f0fc9e1df6c0187d0be88da69b53b8c027404c44fd7",
|
|
1127
|
+
"promptSha256": "1f0698a16b39cda8f43ffc6404165973512958378d3a2045bd75638deec164df",
|
|
1128
|
+
"deterministic": [],
|
|
1129
|
+
"judge": {
|
|
1130
|
+
"verdict": "pass",
|
|
1131
|
+
"reason": "Shows concrete derived replacement `const displayName = user.name;` (and mentions useMemo for expensive cases). Explicitly removes both: 'Delete the state variable and the effect' and 'no useState, no useEffect'. Does not keep the sync effect. Satisfies both pass criteria, no fail criterion."
|
|
1132
|
+
},
|
|
1133
|
+
"passed": true
|
|
1134
|
+
},
|
|
1135
|
+
{
|
|
1136
|
+
"output": "Delete the effect and the `displayName` state entirely \u2014 `user.name` is already available on every render, so deriving it during render is strictly simpler and avoids the extra render pass the effect causes.\n\n**Before:**\n\n```tsx\nfunction UserCard({ user }: { user: User }) {\n const [displayName, setDisplayName] = useState(user.name);\n\n useEffect(() => {\n setDisplayName(user.name);\n }, [user]);\n\n return <span>{displayName}</span>;\n}\n```\n\n**After:**\n\n```tsx\nfunction UserCard({ user }: { user: User }) {\n return <span>{user.name}</span>;\n}\n```\n\nWhy this is the right fix and not just a style preference:\n\n- **The effect adds a second render.** On every `user` change, React renders with the *old* `displayName` first, then the effect fires and triggers another render with the new one. That is a visible flash of stale UI for one frame, and it gets worse the deeper the tree.\n- **The effect is a dependency-array bug surface.** If `user.name` is ever nested or memoized oddly, or a teammate later adds a second derived value, the array drifts out of sync and the component renders stale data with no error.\n- **There is no external system being synchronized.** Effects are for syncing with something outside React \u2014 a subscription, a DOM measurement, browser storage, a non-React widget. Copying a prop into state is none of those; it is just a second source of truth for a value React already has.\n\nIf you *do* find yourself wanting local state that starts from a prop but then diverges (an editable field seeded from `user.name`, say), that is a different situation \u2014 the fix there is a `key` on the component so React remounts it when the identity changes, rather than an effect that resyncs:\n\n```tsx\n// parent\n<UserCard key={user.id} user={user} />\n```\n\nThat resets all local state on identity change in one stroke, with no effect and no flash. But for the case you described \u2014 a value used *only* for rendering \u2014 the answer is simply: compute it in the render.",
|
|
1137
|
+
"outputSha256": "f9460eecf650467540ee4c2a71f08b9fb48c96d36440fe5afa9400b96563105f",
|
|
1138
|
+
"promptSha256": "1f0698a16b39cda8f43ffc6404165973512958378d3a2045bd75638deec164df",
|
|
1139
|
+
"deterministic": [],
|
|
1140
|
+
"judge": {
|
|
1141
|
+
"verdict": "pass",
|
|
1142
|
+
"reason": "Answer shows concrete derived replacement `user.name` in the After code, and explicitly removes both the useState slot (`const [displayName, setDisplayName] = useState(user.name)`) and the useEffect, leaving only `return <span>{user.name}</span>`. No sync effect retained."
|
|
1143
|
+
},
|
|
1144
|
+
"passed": true
|
|
1145
|
+
},
|
|
1146
|
+
{
|
|
1147
|
+
"output": "Delete the `useEffect` and the `displayName` state variable entirely, and derive the value during render:\n\n```tsx\nfunction UserCard({ user }: { user: User }) {\n const displayName = user.name;\n return <span>{displayName}</span>;\n}\n```\n\nThat's the whole fix. There's no state to keep in sync, so there's nothing for an effect to do.\n\nWhy this is correct:\n\n- The value is computable from props during render, so it is not state. Keeping it in state means React renders once with a stale `displayName`, then the effect fires, calls `setState`, and renders again \u2014 a wasted render plus a one-frame flash of the wrong name on every `user` change. That's exactly the \"Derive, don't sync\" rule.\n- If you genuinely need a value that resets when `user` changes, the idiomatic mechanism is a `key`, not an effect. Change the call site to `<UserCard key={user.id} user={user} />` and let React remount, or keep the derivation in render.\n\nA few follow-ups depending on what `displayName` was actually doing:\n\n- **If you were also letting the user edit `displayName`** (input field), then it *is* real state, and the effect is the wrong way to initialize it. The right pattern is either initialize on mount and remount via `key` when you want a fresh value, or hold an \"editing\" state that starts unset and falls back to `user.name` for display:\n\n ```tsx\n const [draft, setDraft] = useState<string | null>(null);\n const displayName = draft ?? user.name;\n ```\n\n That removes the reset-on-prop-change effect too, and it stops the prop change from blowing away an in-progress edit.\n\n- **If the effect did something to the DOM or an external system** with the name (document title, analytics), keep an effect for that side effect, but don't route the *rendered* value through state just to get it there.\n\nVerify with the project's own checks:\n\n```bash\nkeryx test run --changed --strict\n```\n\nand confirm `git status` shows only the component file (and any test) changed. If you're on a React 18-or-earlier codebase the derivation still applies identically \u2014 this isn't a React 19-only change.",
|
|
1148
|
+
"outputSha256": "c0331db68391541a3b92ae866c37c3c62607f5be8bdaf6d01c02aa595162957f",
|
|
1149
|
+
"promptSha256": "1f0698a16b39cda8f43ffc6404165973512958378d3a2045bd75638deec164df",
|
|
1150
|
+
"deterministic": [],
|
|
1151
|
+
"judge": {
|
|
1152
|
+
"verdict": "pass",
|
|
1153
|
+
"reason": "Answer shows concrete derived replacement `const displayName = user.name;` (plain expression) and explicitly says 'Delete the useEffect and the displayName state variable entirely,' removing both the useState slot and the effect. No fail criterion: it eliminates the sync effect rather than keeping it."
|
|
1154
|
+
},
|
|
1155
|
+
"passed": true
|
|
1156
|
+
},
|
|
1157
|
+
{
|
|
1158
|
+
"output": "That effect is a **derived value**, not a synchronization \u2014 you're mirroring a prop into state, which adds an extra render, a frame of stale UI, and a dependency-array bug surface for no benefit. Fix it by computing `displayName` during render and deleting the state + effect entirely:\n\n```tsx\n// before\nfunction UserCard({ user }: { user: User }) {\n const [displayName, setDisplayName] = useState(user.name);\n useEffect(() => {\n setDisplayName(user.name);\n }, [user.name]);\n return <h2>{displayName}</h2>;\n}\n\n// after\nfunction UserCard({ user }: { user: User }) {\n const displayName = user.name;\n return <h2>{displayName}</h2>;\n}\n```\n\nIf the value is only used in the JSX, drop the intermediate variable too and render `{user.name}` directly \u2014 no need to name a value with one use site.\n\n**When this shortcut does NOT apply:** if `displayName` were meant to be *editably* different from `user.name` \u2014 e.g. a text input the user types into, which should start from `user.name` but diverge afterward \u2014 then it is genuinely independent state, not a derived value. In that case the right fix is still not the copy-effect: it is either\n\n1. guard the reset so it only happens when the *identity* of the user changes, not every render, and prefer a plain reset over an effect-derived one:\n\n```tsx\nconst [draft, setDraft] = useState(user.name);\n// remount the form when the user switches, instead of syncing:\n<UserForm key={user.id} user={user} />\n```\n\nor\n\n2. make the edit form own its own state and unmount/remount it via a `key` when `user.id` changes \u2014 the `key` gives you the reset for free without an effect, and it does not clobber the user's in-progress typing on unrelated re-renders.\n\nA `useEffect` is warranted only when you are synchronizing with something *outside* React: a subscription, a DOM measurement, a non-React widget, or browser storage. Copying one prop into one piece of render-only state is none of those.\n\nSo: for your described case \u2014 `displayName` used only for rendering \u2014 delete both the `useState` and the `useEffect` and read `user.name` at the point of use. That removes a render, removes the stale-frame flash, and removes the `[user.name]` dependency array that someone will eventually get wrong.",
|
|
1159
|
+
"outputSha256": "1826cb00f59513659a240974fcad020c9d76f791cfe30044c9829867be84af7e",
|
|
1160
|
+
"promptSha256": "1f0698a16b39cda8f43ffc6404165973512958378d3a2045bd75638deec164df",
|
|
1161
|
+
"deterministic": [],
|
|
1162
|
+
"judge": {
|
|
1163
|
+
"verdict": "pass",
|
|
1164
|
+
"reason": "Answer shows concrete derived replacement `const displayName = user.name;` (and `{user.name}` directly) in the 'after' code, and explicitly deletes both: 'delete both the useState and the useEffect' with before/after showing useState slot and useEffect removed. No sync-effect kept as fix."
|
|
1165
|
+
},
|
|
1166
|
+
"passed": true
|
|
1167
|
+
}
|
|
1168
|
+
]
|
|
1169
|
+
},
|
|
1170
|
+
{
|
|
1171
|
+
"id": "key-as-index",
|
|
1172
|
+
"kind": "behavior",
|
|
1173
|
+
"prompt": "I'm rendering a filterable list of todo items with `<TodoItem key={index} .../>` inside a .map. Is that ok?",
|
|
1174
|
+
"strictness": "high",
|
|
1175
|
+
"trials": 10,
|
|
1176
|
+
"passes": 10,
|
|
1177
|
+
"passRate": 1,
|
|
1178
|
+
"passAtK": 1,
|
|
1179
|
+
"grader": "judge",
|
|
1180
|
+
"status": "ran",
|
|
1181
|
+
"trialRecords": [
|
|
1182
|
+
{
|
|
1183
|
+
"output": "No. An index key is only safe for a list that is static, never reordered, filtered, or inserted into. A filterable todo list is exactly the case where index keys break: the indices don't move with the items.\n\nWhat goes wrong concretely:\n\n- When you filter, the surviving items get renumbered (the item that was at index 3 is now at index 0). React sees \"index 0 changed\" and reuses the old component instance at that position instead of re-rendering it as the same item. Any per-item state React is holding \u2014 focus, a half-typed input, an expand/collapse animation, uncontrolled input value \u2014 follows the position, not the item. So a checkbox you just toggled can appear to jump to a different row.\n- Same on insert/remove: splice an item in the middle and every item after it shifts index, so React re-renders all of them and can drop the ones unmounted without cleaning up correctly.\n- It also defeats reconciliation: React can't tell \"this is a different todo\" from \"this todo changed,\" so it does more work than a stable key would.\n\nFix: use the item's own stable identity as the key. For todos that come from a store/API, that's the todo id:\n\n```jsx\n{todos.map((todo) => (\n <TodoItem key={todo.id} todo={todo} />\n))}\n```\n\nIf the todos only exist client-side with no id yet, assign one when the item is created (crypto.randomUUID() is the usual choice) rather than falling back to the index.\n\nThe one case where an index key is defensible is a genuinely fixed list \u2014 known length, never sorted/filtered/inserted, and the child has no internal state. A filterable list is not that, so switch to the id.\n\nWorth a quick check while you're in here: if the filter is implemented by mapping the original array and conditionally returning null for non-matching items, the indices are actually stable and this is harmless \u2014 but that pattern still renders an empty slot per hidden item. Prefer filtering the array first (todos.filter(...).map(...)) with a real key, which is both cheaper and unambiguous.",
|
|
1184
|
+
"outputSha256": "034bc7aebda93ca7185af0fce54a38306006f2c12f7fedff0be787d1502f77c9",
|
|
1185
|
+
"promptSha256": "4055d2253ae01ff92baf4af72153d9bd4e4659e877060b3e48f2b63490d3916d",
|
|
1186
|
+
"deterministic": [],
|
|
1187
|
+
"judge": {
|
|
1188
|
+
"verdict": "pass",
|
|
1189
|
+
"reason": "Criterion 1: answer ties index-key breakage to filtering \u2014 'When you filter, the surviving items get renumbered (the item that was at index 3 is now at index 0)' and per-item state 'focus, a half-typed input, an expand/collapse animation' follows position not item. Criterion 2: shows concrete `key={todo.id}` in JSX. No fail criterion: it never says index keys are fine for this filterable list."
|
|
1190
|
+
},
|
|
1191
|
+
"passed": true
|
|
1192
|
+
},
|
|
1193
|
+
{
|
|
1194
|
+
"output": "No, not for a filterable list. This is the exact case the skill's rules call out: index keys break when the list can reorder, filter, or have items inserted. Filtering a todo list changes which items occupy which positions, so React will reuse a `TodoItem`'s identity (and its internal state \u2014 e.g. \"editing\" mode, a focused input, a checkbox animation) for a *different* todo than the one it was mounted for. Concretely, filter out the second item and the third item's component instance gets recycled to render what used to be the fourth, carrying stale local state with it.\n\nUse a stable, data-derived key instead \u2014 the todo's own ID:\n\n```tsx\n{todos.map((todo) => (\n <TodoItem key={todo.id} todo={todo} />\n))}\n```\n\nIf the items genuinely have no unique ID (e.g. a static, never-reordered literal array), an index key is *technically* safe, but a filterable list is by definition reordered, so that exemption doesn't apply here. If your data truly lacks an ID, add one at creation time rather than deriving it from position.\n\nA secondary red flag in the same snippet: if `TodoItem` derives anything from an `index` prop, move that derivation to use the stable ID too, or filter/sort the array before mapping rather than passing array position down.\n\nOnce you switch the key, re-verify with the project's own type-check + lint + test (`keryx test run --changed --strict` if that's how this project runs it) \u2014 index keys rarely fail a test loudly, so a passing suite doesn't by itself prove the fix; the reorder/filter behavior is the real check.",
|
|
1195
|
+
"outputSha256": "6beb963a97dc780ec932868d9ea40e1508be7bffba34451c71c4fd02c9864e9a",
|
|
1196
|
+
"promptSha256": "4055d2253ae01ff92baf4af72153d9bd4e4659e877060b3e48f2b63490d3916d",
|
|
1197
|
+
"deterministic": [],
|
|
1198
|
+
"judge": {
|
|
1199
|
+
"verdict": "pass",
|
|
1200
|
+
"reason": "Answer ties index-key problem to filtering: 'Filtering a todo list changes which items occupy which positions, so React will reuse a TodoItem's identity (and its internal state \u2014 e.g. editing mode, a focused input, a checkbox animation) for a different todo.' Names concrete replacement: `key={todo.id}` in code. No fail criterion met."
|
|
1201
|
+
},
|
|
1202
|
+
"passed": true
|
|
1203
|
+
},
|
|
1204
|
+
{
|
|
1205
|
+
"output": "No \u2014 using the array index as `key` here is a real bug waiting to happen.\n\nThe whole point of `key` is to give React a *stable identity* for each rendered item, so it can tell \"this is the same item, just moved\" apart from \"this item is gone and a new one appeared.\" The index doesn't carry identity \u2014 it just carries position in the current render. `key={index}` means \"whatever is at slot 0 this render is the same thing that was at slot 0 last render,\" which is only true when the list never reorders, never filters, and never has items inserted or removed in the middle.\n\nA filterable todo list fails every one of those conditions by definition:\n\n- **Filtering** removes items from the middle of the array, so what was index 5 becomes index 2, and so on. Every item below the removed one now has a different key than before.\n- **Reordering** (your app's core interaction) shuffles which item sits at which index.\n- **Adding/removing** items shifts everything after them.\n\nWhen keys don't match a real item's identity, React reuses the wrong component instance instead of remounting it. The visible symptoms: local state attaches to the wrong row \u2014 a row's \"editing\" toggle, its checked/unchecked state, an uncontrolled input's typed text, or an in-progress animation jumps to a neighboring item after a filter or reorder. It looks fine in a quick manual test with a static list and then breaks the moment someone actually filters.\n\nThe fix is to key on something intrinsic to the todo itself \u2014 its stable id:\n\n```tsx\n{todos.map((todo) => (\n <TodoItem key={todo.id} todo={todo} onToggle={onToggle} />\n))}\n```\n\nIf your todos genuinely don't have ids yet, that's the thing to add \u2014 a stable id (from the backend, or a `crypto.randomUUID()` assigned once when the todo is created, not when it's rendered). Don't fall back to the index, and don't fall back to the label text either (two todos can share a title, and editing the title would then remount the row).\n\nThe one case where index-as-key is acceptable is a list that is provably static: never reordered, never filtered, never inserted into. A filterable todo list is the opposite of that, so this is a genuine fix rather than a stylistic nit. This should be fixed before reporting the component done.",
|
|
1206
|
+
"outputSha256": "a73d257b8de7157bba1699169cfe12b080e0f0a6b724a9654b7a2e633166ecf0",
|
|
1207
|
+
"promptSha256": "4055d2253ae01ff92baf4af72153d9bd4e4659e877060b3e48f2b63490d3916d",
|
|
1208
|
+
"deterministic": [],
|
|
1209
|
+
"judge": {
|
|
1210
|
+
"verdict": "pass",
|
|
1211
|
+
"reason": "Answer ties index-key problem to filtering: 'Filtering removes items from the middle... what was index 5 becomes index 2' and symptoms 'local state attaches to the wrong row... jumps to a neighboring item after a filter.' Names concrete replacement: `key={todo.id}` in code, plus crypto.randomUUID fallback. Does not say index is fine for filterable list."
|
|
1212
|
+
},
|
|
1213
|
+
"passed": true
|
|
1214
|
+
},
|
|
1215
|
+
{
|
|
1216
|
+
"output": "No \u2014 not if the list can reorder, filter, insert, or remove items, which is exactly what a filterable list does. Using the array index as `key` means React ties component identity to position, not to the todo. When the filter changes and the visible subset shifts, item #2 in the new list may be a completely different todo than item #2 in the old list, but React sees the same key and reuses that component instance \u2014 carrying over any local state (input focus, animation, expanded/collapsed, uncontrolled input values) to the wrong todo.\n\nThe fix is a stable, data-derived key \u2014 the todo's own id:\n\n```tsx\n{todos\n .filter((todo) => matches(todo, filter))\n .map((todo) => (\n <TodoItem key={todo.id} todo={todo} />\n ))}\n```\n\nThings worth checking while you're in there:\n\n- If your todos don't have an id, add one when they're created (a counter, `crypto.randomUUID()`, or whatever the project already uses) rather than falling back to the index.\n- If a todo genuinely has no stable identity and duplicates are possible, `key={todo.id}` beats `key={index}` but still needs unique ids \u2014 don't key on the title/text either, since two todos can share one.\n- The filter itself is a derived value: compute the filtered array during render (as above) rather than storing it in state and syncing it with an effect. Same principle the skill applies elsewhere \u2014 derive, don't sync.\n\nIndex keys are only defensible for a list that is truly static: never reordered, never filtered, never inserted into, never removed from. A filterable todo list is the opposite of that, so switch to `key={todo.id}`.",
|
|
1217
|
+
"outputSha256": "cb76840d77b181fdcaf5c081bd7f30c1e85db5c4657f6edd7fa1aa06b044fa37",
|
|
1218
|
+
"promptSha256": "4055d2253ae01ff92baf4af72153d9bd4e4659e877060b3e48f2b63490d3916d",
|
|
1219
|
+
"deterministic": [],
|
|
1220
|
+
"judge": {
|
|
1221
|
+
"verdict": "pass",
|
|
1222
|
+
"reason": "Answer ties index-key problem to filtering: 'When the filter changes and the visible subset shifts, item #2... may be a completely different todo... React sees the same key and reuses that component instance \u2014 carrying over any local state (input focus, animation...)'. Names concrete replacement: `key={todo.id}` in code. No fail criterion met."
|
|
1223
|
+
},
|
|
1224
|
+
"passed": true
|
|
1225
|
+
},
|
|
1226
|
+
{
|
|
1227
|
+
"output": "No \u2014 using the array index as `key` there is a bug waiting to happen.\n\nThe problem is that React uses `key` to decide which DOM node/component instance corresponds to which item across renders. With `index`, that identity is tied to position, not to the todo. So the moment the list can reorder, filter, or have an item removed (which is exactly what a *filterable* list does), React will reuse the wrong instance for the wrong item. Concretely you get:\n\n- local state (a checkbox in \"editing\" mode, an inline input's cursor position) sticking to the wrong row after a filter changes;\n- animations/CSS transitions playing on the wrong element;\n- if any child holds state or an uncontrolled `<input>`, its value follows the *slot*, not the todo.\n\nThe fix is a stable, data-derived key \u2014 the todo's own id:\n\n```tsx\n{todos.map((todo) => (\n <TodoItem key={todo.id} todo={todo} />\n))}\n```\n\nOnly fall back to the index (or better, a composite like `${todo.id}-${i}`) in the rare case where the list is genuinely static \u2014 never reordered, never filtered, never inserted into. A filterable list is the textbook case where that assumption is false.\n\nIf you truly have no id on the todo, generate one at creation time (a `crypto.randomUUID()` or a monotonic counter stored on the item) rather than synthesizing it at render. A key generated during render (e.g. `Math.random()`) is just as bad in the other direction \u2014 it forces a full remount every render.",
|
|
1228
|
+
"outputSha256": "74e9a58133f1bd74af1e53f0e3dacb378f858963f589c3f6a6440ff9fc7c31b6",
|
|
1229
|
+
"promptSha256": "4055d2253ae01ff92baf4af72153d9bd4e4659e877060b3e48f2b63490d3916d",
|
|
1230
|
+
"deterministic": [],
|
|
1231
|
+
"judge": {
|
|
1232
|
+
"verdict": "pass",
|
|
1233
|
+
"reason": "Answer ties index-key problem to filtering: 'the moment the list can reorder, filter, or have an item removed (which is exactly what a filterable list does), React will reuse the wrong instance', citing local state, cursor, animations. Concrete replacement shown: `key={todo.id}` in code. No fail criterion met."
|
|
1234
|
+
},
|
|
1235
|
+
"passed": true
|
|
1236
|
+
},
|
|
1237
|
+
{
|
|
1238
|
+
"output": "No \u2014 not okay as written, for the reason the skill's Red Flags table calls out directly: a filterable list is exactly the \"can reorder, filter, or insert\" case where an index key corrupts component identity.\n\nWhat goes wrong: React matches elements between renders by `key`. With `key={index}`, item at position 0 stays \"the element at position 0\" even after filtering removes the real first item and a different todo slides into slot 0. React then reuses that element's instance \u2014 and its local state \u2014 for what is now a different todo. Concretely, with filtering you get:\n\n- checkbox/expanded/editing state visibly attached to the wrong todo after a filter change,\n- uncontrolled `<input>` values (an in-progress edit) showing up under the wrong item,\n- DOM nodes reused where the content is now different, so any CSS transition/enter animation plays on the wrong row.\n\nThe fix is a stable, data-derived key from the todo itself \u2014 `key={todo.id}` if items have an id (they should), or `key={todo.text}`/`key={todo.createdAt}` only if you're certain that's unique and immutable. Never the array index, and never a value that changes when the todo is edited (that would remount and drop focus mid-edit).\n\nSo:\n\n```tsx\n{todos\n .filter((t) => matchesFilter(t, filter))\n .map((todo) => (\n <TodoItem key={todo.id} todo={todo} />\n ))}\n```\n\nTwo related checks while you're in there, per the skill's Verification section: if any of `TodoItem`'s local state is meant to reset when the filter changes, don't reach for a `useEffect` that calls `setState` on a filter prop \u2014 that's the \"mirroring props into state\" red flag. Either derive during render or force the reset with a changing `key` on the component (e.g. `key={`${filter}-${todo.id}`}`) if a genuine remount is what you want.\n\nI can't run your type-check or `keryx test run --changed --strict` here, so treat the key change as unverified until you run them yourself; `git status` should show only the list-rendering component (and its test) changed.",
|
|
1239
|
+
"outputSha256": "15f0bf55815b10e773ab8bc41ed90a3f9f32e40b1314a3d9254ddc6a8c866aef",
|
|
1240
|
+
"promptSha256": "4055d2253ae01ff92baf4af72153d9bd4e4659e877060b3e48f2b63490d3916d",
|
|
1241
|
+
"deterministic": [],
|
|
1242
|
+
"judge": {
|
|
1243
|
+
"verdict": "pass",
|
|
1244
|
+
"reason": "Criterion 1: answer ties index-key problem to filtering \u2014 'item at position 0 stays the element at position 0 even after filtering removes the real first item and a different todo slides into slot 0', corrupting local state, input values, and animations. Criterion 2: shows concrete replacement `key={todo.id}` in code. No fail criterion: it explicitly says 'No \u2014 not okay'."
|
|
1245
|
+
},
|
|
1246
|
+
"passed": true
|
|
1247
|
+
},
|
|
1248
|
+
{
|
|
1249
|
+
"output": "No \u2014 that's a red flag, and it's explicitly called out in this skill's Red Flags table: \"This list is short, index as key is fine\" is listed as a wrong rationalization.\n\nThe problem: a filterable list can reorder, insert, and remove items. When you use `key={index}`, React identifies items by their position, not by the item itself. On a filter change, item 0 might now be a different todo than it was \u2014 so React reuses the old component instance (and its local state, DOM node, focus, animations, and any uncontrolled input or checkbox state) for the wrong item. A checkbox that was checked on one todo can appear checked on its neighbor, focus can land on the wrong row, and in-progress edits can smear across rows.\n\nFix: give each item a stable, data-derived key from the todo itself:\n\n```tsx\n{todos.map((todo) => (\n <TodoItem key={todo.id} todo={todo} />\n))}\n```\n\nRequirements for the key to actually be stable:\n\n- It must come from the data (e.g. `todo.id`), not from the array position.\n- It must be unique across the current rendered list.\n- It must survive the filter/sort \u2014 do not fall back to `index` \"just for the filtered view.\"\n\nIf your todos genuinely don't have an id yet, add one at creation time (a counter or `crypto.randomUUID()`) rather than reaching for the index. A stable id at creation is cheap; chasing down a \"why did my checkbox jump to the wrong row\" bug later is not.\n\nSo: change `key={index}` to `key={todo.id}` (or whatever stable identifier your data has), then re-run your project's type-check/lint and `keryx test run --changed --strict` before calling it done.",
|
|
1250
|
+
"outputSha256": "26dce19401285b1174e64fe970c60f2c3b6766f7d9a6d148caef40fc67cd0271",
|
|
1251
|
+
"promptSha256": "4055d2253ae01ff92baf4af72153d9bd4e4659e877060b3e48f2b63490d3916d",
|
|
1252
|
+
"deterministic": [],
|
|
1253
|
+
"judge": {
|
|
1254
|
+
"verdict": "pass",
|
|
1255
|
+
"reason": "Answer ties index-key problem to filtering: 'On a filter change, item 0 might now be a different todo... React reuses the old component instance (and its local state, DOM node, focus, animations...)'. Names concrete replacement: `key={todo.id}` in code block and 'change `key={index}` to `key={todo.id}`'. No fail criterion met."
|
|
1256
|
+
},
|
|
1257
|
+
"passed": true
|
|
1258
|
+
},
|
|
1259
|
+
{
|
|
1260
|
+
"output": "No \u2014 that's the one case the rule calls out explicitly. Using the array index as a `key` is unsafe whenever the list can reorder, filter, or insert/remove items. A filterable todo list is exactly that: filtering changes the array, and every index shifts, so React reuses the wrong component's identity and the wrong local state (input focus, checkbox animation, expanded/collapsed state) lands on the wrong row.\n\nFix: key by a stable per-todo identifier from your data \u2014 typically `todo.id`.\n\n```tsx\n{todos.map((todo) => (\n <TodoItem key={todo.id} todo={todo} onToggle={toggle} />\n))}\n```\n\nIf your todos somehow don't have an id yet, generate one when the item is created (e.g., `crypto.randomUUID()` in the add handler and store it on the object) rather than deriving a key at render time \u2014 a key has to be stable across renders for the same logical item, and anything you compute during render won't be.\n\nThe only time an index key is defensible is a list that is guaranteed static: never reordered, never filtered, never inserted into or deleted from, with no per-item state. A filterable list never qualifies.\n\nTwo related notes while you're in there:\n- If the filter itself is what's changing, don't reach for an effect + state to hold the filtered result \u2014 compute it during render (`const visible = todos.filter(...)`). It's derivable, so it isn't state.\n- If a filtered row unmounting is causing visible state loss you don't want, that's a signal to reconsider where that state lives, not to switch the key around.\n\nSo: change `key={index}` to `key={todo.id}` and you're set.",
|
|
1261
|
+
"outputSha256": "89be06fbea587a1699e29d8b2316ea655a02d790e4fce8217d7376c049964855",
|
|
1262
|
+
"promptSha256": "4055d2253ae01ff92baf4af72153d9bd4e4659e877060b3e48f2b63490d3916d",
|
|
1263
|
+
"deterministic": [],
|
|
1264
|
+
"judge": {
|
|
1265
|
+
"verdict": "pass",
|
|
1266
|
+
"reason": "Criterion 1: answer ties index-key danger to filtering \u2014 'filtering changes the array, and every index shifts, so React reuses the wrong component's identity and the wrong local state (input focus, checkbox animation...)'. Criterion 2: shows concrete replacement `key={todo.id}` in code. Fail criterion 1 not triggered; it explicitly says index key is unsafe for filterable lists."
|
|
1267
|
+
},
|
|
1268
|
+
"passed": true
|
|
1269
|
+
},
|
|
1270
|
+
{
|
|
1271
|
+
"output": "No \u2014 using the array index as `key` is a problem here, and a filterable list is close to the worst case for it.\n\nWhy it breaks: React uses `key` to decide component identity across renders. With index keys, when the list filters down, the item that was at index 2 may now be a different todo. React sees \"key 2 is still key 2\" and reuses the existing component instance \u2014 keeping any local state (an open/closed expansion, an in-progress edit, a checkbox animation) attached to the wrong todo. It also causes duplicate keys the moment the list is reordered or items are inserted.\n\nThe fix: key on something stable and data-derived, almost always the todo's own id.\n\n```tsx\n{todos.map((todo) => (\n <TodoItem key={todo.id} todo={todo} />\n))}\n```\n\nIf you don't have an id, derive a stable one from the data (e.g. a normalized title + creation timestamp), not from the index. Falling back to the index is only defensible for a list that can never reorder, filter, insert, or remove \u2014 and a \"filterable list\" explicitly can.",
|
|
1272
|
+
"outputSha256": "2fc08f118df1de3cc10e87df6f3e83a66cbfaf220bec323b37f9627dcf820a36",
|
|
1273
|
+
"promptSha256": "4055d2253ae01ff92baf4af72153d9bd4e4659e877060b3e48f2b63490d3916d",
|
|
1274
|
+
"deterministic": [],
|
|
1275
|
+
"judge": {
|
|
1276
|
+
"verdict": "pass",
|
|
1277
|
+
"reason": "Criterion 1: answer ties index-key problem to filtering \u2014 'when the list filters down, the item that was at index 2 may now be a different todo. React sees \"key 2 is still key 2\" and reuses the existing component instance \u2014 keeping any local state... attached to the wrong todo.' Criterion 2: shows concrete replacement `key={todo.id}` in code. No fail criterion: it explicitly says index is a problem, not fine."
|
|
1278
|
+
},
|
|
1279
|
+
"passed": true
|
|
1280
|
+
},
|
|
1281
|
+
{
|
|
1282
|
+
"output": "No \u2014 that's the exact case the \"never use index as key\" rule is written for. A filterable list is the canonical example of a list that reorders, inserts, and removes, and an index key breaks component identity across those operations.\n\nWhat actually goes wrong:\n\n- TodoItem's own local state (an edit-in-progress flag, a focus held in an uncontrolled input, an open menu, an animation) is keyed to the position, not the item. Filter item #3 out and everything after it shifts index \u2014 React sees `key={3}` still exists, reuses that element, and hands it different props. Result: the wrong item's edit state, or a text box that keeps its content but changes which todo it belongs to.\n- The same happens on reorder (drag-to-sort, sort-by-priority): the DOM nodes get reused positionally and the visible content jumps.\n- Inserting an item at the front shifts every subsequent index, so React can thrash the whole list instead of adding one node.\n\nWith a stable, data-derived key \u2014 `key={todo.id}` \u2014 React reconciles by identity, and a filtered-out todo simply unmounts and its state goes with it.\n\nThe only narrow exceptions where the index is defensible: a list that is provably static for the component's lifetime (never reordered, filtered, or edited, and every item stateless), or a genuinely non-reorderable computed list where you've documented that invariant right next to it. A filterable todo list is neither, so use the id.\n\nOne caveat worth checking: if the todos come from an API without a stable id, don't paper over it with the index \u2014 derive a stable key from something intrinsic (a server-assigned id, or a client-side generated id you attach when the item is created). Falling back to `index` there just relocates the bug.\n\nQuick fix:\n\n```tsx\n{todos.map((todo) => (\n <TodoItem key={todo.id} todo={todo} />\n))}\n```\n\nIf TodoItem's state is currently *accidentally* surviving a filter because of the index key, you may see a behavior change once you switch to ids \u2014 that's the latent bug surfacing, not a regression. Verify with `keryx test run --changed --strict` plus a manual filter-and-edit pass, and confirm `git status` shows only the intended files.",
|
|
1283
|
+
"outputSha256": "7c1652660d7b35ec4fe3fa0e25a2d5b57f983e83bf9e13c32217d02fe4b2cd00",
|
|
1284
|
+
"promptSha256": "4055d2253ae01ff92baf4af72153d9bd4e4659e877060b3e48f2b63490d3916d",
|
|
1285
|
+
"deterministic": [],
|
|
1286
|
+
"judge": {
|
|
1287
|
+
"verdict": "pass",
|
|
1288
|
+
"reason": "Answer ties index-key problem to filtering: 'Filter item #3 out and everything after it shifts index \u2014 React sees key={3} still exists, reuses that element' corrupting local state/focus/animation. Names concrete replacement: 'key={todo.id}' shown in code block. Does not say index is fine for this list; exceptions explicitly exclude filterable lists."
|
|
1289
|
+
},
|
|
1290
|
+
"passed": true
|
|
1291
|
+
}
|
|
1292
|
+
]
|
|
1293
|
+
}
|
|
1294
|
+
],
|
|
1295
|
+
"verdict": "pass",
|
|
1296
|
+
"scope": "bundled",
|
|
1297
|
+
"skillDigest": "7f4bb4b9e19305fdc1bdb5d8e66b386157c689fbbfc4ac520d185f589e65e503",
|
|
1298
|
+
"catalogDigest": "4f4016d410837e394a27e5b247e38ef2f57a1ee0baba4436ce7d3d71e223333d",
|
|
1299
|
+
"judgePromptVersion": "2026-09-25.1",
|
|
1300
|
+
"runner": "deepseek",
|
|
1301
|
+
"model": "deepseek-chat",
|
|
1302
|
+
"runnerPromptVersion": "2026-09-25.1",
|
|
1303
|
+
"recordedAt": "2026-09-25T05:21:40.460Z",
|
|
1304
|
+
"judge": "deepseek",
|
|
1305
|
+
"judgeModel": "deepseek-chat"
|
|
1306
|
+
},
|
|
1307
|
+
{
|
|
1308
|
+
"schemaVersion": "1.0.0",
|
|
1309
|
+
"skillId": "react/react-testing",
|
|
1310
|
+
"strictness": "high",
|
|
1311
|
+
"trials": 10,
|
|
1312
|
+
"triggerAccuracy": {
|
|
1313
|
+
"truePositive": 6,
|
|
1314
|
+
"falsePositive": 0,
|
|
1315
|
+
"positives": 6,
|
|
1316
|
+
"negatives": 6
|
|
1317
|
+
},
|
|
1318
|
+
"evidence": "authored",
|
|
1319
|
+
"scenarios": [
|
|
1320
|
+
{
|
|
1321
|
+
"id": "trigger-positive-1",
|
|
1322
|
+
"kind": "trigger-positive",
|
|
1323
|
+
"prompt": "Write a React Testing Library test for this UserCard component's loading, success, and error states",
|
|
1324
|
+
"strictness": "high",
|
|
1325
|
+
"trials": 1,
|
|
1326
|
+
"passes": 1,
|
|
1327
|
+
"passRate": 1,
|
|
1328
|
+
"passAtK": 1,
|
|
1329
|
+
"grader": "trigger-rank-fork-family",
|
|
1330
|
+
"status": "ran",
|
|
1331
|
+
"deterministic": true
|
|
1332
|
+
},
|
|
1333
|
+
{
|
|
1334
|
+
"id": "trigger-positive-2",
|
|
1335
|
+
"kind": "trigger-positive",
|
|
1336
|
+
"prompt": "Test this custom hook with renderHook",
|
|
1337
|
+
"strictness": "high",
|
|
1338
|
+
"trials": 1,
|
|
1339
|
+
"passes": 1,
|
|
1340
|
+
"passRate": 1,
|
|
1341
|
+
"passAtK": 1,
|
|
1342
|
+
"grader": "trigger-rank-fork-family",
|
|
1343
|
+
"status": "ran",
|
|
1344
|
+
"deterministic": true
|
|
1345
|
+
},
|
|
1346
|
+
{
|
|
1347
|
+
"id": "trigger-positive-3",
|
|
1348
|
+
"kind": "trigger-positive",
|
|
1349
|
+
"prompt": "This component test throws an act warning, help me fix it",
|
|
1350
|
+
"strictness": "high",
|
|
1351
|
+
"trials": 1,
|
|
1352
|
+
"passes": 1,
|
|
1353
|
+
"passRate": 1,
|
|
1354
|
+
"passAtK": 1,
|
|
1355
|
+
"grader": "trigger-rank-fork-family",
|
|
1356
|
+
"status": "ran",
|
|
1357
|
+
"deterministic": true
|
|
1358
|
+
},
|
|
1359
|
+
{
|
|
1360
|
+
"id": "trigger-positive-4",
|
|
1361
|
+
"kind": "trigger-positive",
|
|
1362
|
+
"prompt": "Add an MSW request handler to mock the API call in this React component test",
|
|
1363
|
+
"strictness": "high",
|
|
1364
|
+
"trials": 1,
|
|
1365
|
+
"passes": 1,
|
|
1366
|
+
"passRate": 1,
|
|
1367
|
+
"passAtK": 1,
|
|
1368
|
+
"grader": "trigger-rank-fork-family",
|
|
1369
|
+
"status": "ran",
|
|
1370
|
+
"deterministic": true
|
|
1371
|
+
},
|
|
1372
|
+
{
|
|
1373
|
+
"id": "trigger-positive-5",
|
|
1374
|
+
"kind": "trigger-positive",
|
|
1375
|
+
"prompt": "Add tests for the form's validation error and successful submit states",
|
|
1376
|
+
"strictness": "high",
|
|
1377
|
+
"trials": 1,
|
|
1378
|
+
"passes": 1,
|
|
1379
|
+
"passRate": 1,
|
|
1380
|
+
"passAtK": 1,
|
|
1381
|
+
"grader": "trigger-rank-fork-family",
|
|
1382
|
+
"status": "ran",
|
|
1383
|
+
"deterministic": true
|
|
1384
|
+
},
|
|
1385
|
+
{
|
|
1386
|
+
"id": "trigger-positive-6",
|
|
1387
|
+
"kind": "trigger-positive",
|
|
1388
|
+
"prompt": "Write a user-event test that clicks the button and checks the modal opens",
|
|
1389
|
+
"strictness": "high",
|
|
1390
|
+
"trials": 1,
|
|
1391
|
+
"passes": 1,
|
|
1392
|
+
"passRate": 1,
|
|
1393
|
+
"passAtK": 1,
|
|
1394
|
+
"grader": "trigger-rank-fork-family",
|
|
1395
|
+
"status": "ran",
|
|
1396
|
+
"deterministic": true
|
|
1397
|
+
},
|
|
1398
|
+
{
|
|
1399
|
+
"id": "trigger-negative-1",
|
|
1400
|
+
"kind": "trigger-negative",
|
|
1401
|
+
"prompt": "Write pytest tests for this FastAPI endpoint",
|
|
1402
|
+
"strictness": "high",
|
|
1403
|
+
"trials": 1,
|
|
1404
|
+
"passes": 1,
|
|
1405
|
+
"passRate": 1,
|
|
1406
|
+
"passAtK": 1,
|
|
1407
|
+
"grader": "trigger-rank-fork-family",
|
|
1408
|
+
"status": "ran",
|
|
1409
|
+
"deterministic": true
|
|
1410
|
+
},
|
|
1411
|
+
{
|
|
1412
|
+
"id": "trigger-negative-2",
|
|
1413
|
+
"kind": "trigger-negative",
|
|
1414
|
+
"prompt": "Build a new SearchBar React component with a debounced input",
|
|
1415
|
+
"strictness": "high",
|
|
1416
|
+
"trials": 1,
|
|
1417
|
+
"passes": 1,
|
|
1418
|
+
"passRate": 1,
|
|
1419
|
+
"passAtK": 1,
|
|
1420
|
+
"grader": "trigger-rank-fork-family",
|
|
1421
|
+
"status": "ran",
|
|
1422
|
+
"deterministic": true
|
|
1423
|
+
},
|
|
1424
|
+
{
|
|
1425
|
+
"id": "trigger-negative-3",
|
|
1426
|
+
"kind": "trigger-negative",
|
|
1427
|
+
"prompt": "Review this component diff for accessibility issues",
|
|
1428
|
+
"strictness": "high",
|
|
1429
|
+
"trials": 1,
|
|
1430
|
+
"passes": 1,
|
|
1431
|
+
"passRate": 1,
|
|
1432
|
+
"passAtK": 1,
|
|
1433
|
+
"grader": "trigger-rank-fork-family",
|
|
1434
|
+
"status": "ran",
|
|
1435
|
+
"deterministic": true
|
|
1436
|
+
},
|
|
1437
|
+
{
|
|
1438
|
+
"id": "trigger-negative-4",
|
|
1439
|
+
"kind": "trigger-negative",
|
|
1440
|
+
"prompt": "Set up Playwright end-to-end tests for the checkout flow",
|
|
1441
|
+
"strictness": "high",
|
|
1442
|
+
"trials": 1,
|
|
1443
|
+
"passes": 1,
|
|
1444
|
+
"passRate": 1,
|
|
1445
|
+
"passAtK": 1,
|
|
1446
|
+
"grader": "trigger-rank-fork-family",
|
|
1447
|
+
"status": "ran",
|
|
1448
|
+
"deterministic": true
|
|
1449
|
+
},
|
|
1450
|
+
{
|
|
1451
|
+
"id": "trigger-negative-5",
|
|
1452
|
+
"kind": "trigger-negative",
|
|
1453
|
+
"prompt": "Fix the failing tsc type-check in this component after the react types upgrade",
|
|
1454
|
+
"strictness": "high",
|
|
1455
|
+
"trials": 1,
|
|
1456
|
+
"passes": 1,
|
|
1457
|
+
"passRate": 1,
|
|
1458
|
+
"passAtK": 1,
|
|
1459
|
+
"grader": "trigger-rank-fork-family",
|
|
1460
|
+
"status": "ran",
|
|
1461
|
+
"deterministic": true
|
|
1462
|
+
},
|
|
1463
|
+
{
|
|
1464
|
+
"id": "trigger-negative-6",
|
|
1465
|
+
"kind": "trigger-negative",
|
|
1466
|
+
"prompt": "Write unit tests for this plain TypeScript date-formatting utility function",
|
|
1467
|
+
"strictness": "high",
|
|
1468
|
+
"trials": 1,
|
|
1469
|
+
"passes": 1,
|
|
1470
|
+
"passRate": 1,
|
|
1471
|
+
"passAtK": 1,
|
|
1472
|
+
"grader": "trigger-rank-fork-family",
|
|
1473
|
+
"status": "ran",
|
|
1474
|
+
"deterministic": true
|
|
1475
|
+
},
|
|
1476
|
+
{
|
|
1477
|
+
"id": "query-by-role",
|
|
1478
|
+
"kind": "behavior",
|
|
1479
|
+
"prompt": "I need a React Testing Library test that clicks the submit button on a login form and checks an error message appears when the password is empty. How should I query for the button and the error?",
|
|
1480
|
+
"strictness": "high",
|
|
1481
|
+
"trials": 10,
|
|
1482
|
+
"passes": 10,
|
|
1483
|
+
"passRate": 1,
|
|
1484
|
+
"passAtK": 1,
|
|
1485
|
+
"grader": "judge+regex",
|
|
1486
|
+
"status": "ran",
|
|
1487
|
+
"trialRecords": [
|
|
1488
|
+
{
|
|
1489
|
+
"output": "Query the button by its accessible role and name, and the error by its text:\n\n```tsx\nconst user = userEvent.setup();\nrender(<LoginForm />);\n\nawait user.click(screen.getByRole(\"button\", { name: /submit/i }));\nexpect(await screen.findByText(/password is required/i)).toBeInTheDocument();\n```\n\nWhy these queries:\n\n- **Button \u2014 `getByRole(\"button\", { name: /submit/i })`.** Role + accessible name is exactly how a user or screen reader finds the control. The `name` option matches the button's accessible name (its text, `aria-label`, or `<label>`), so it stays correct even if the visible text is \"Sign in\" inside a `<button type=\"submit\">`. Use `getByRole` (singular) since you expect exactly one such button.\n\n- **Error \u2014 `findByText` with a regex.** The error appears asynchronously after the click, so `findBy*` retries until it shows up or times out. That's why it's `findBy` and not `getBy` \u2014 `getBy` would throw immediately before the state update flushes. A regex like `/password is required/i` tolerates minor copy changes vs. an exact string match.\n\nIf the error is rendered as a dedicated live region or has a stable `role`/`aria` attribute, prefer querying that instead of raw text, e.g.:\n\n```tsx\nexpect(await screen.findByRole(\"alert\")).toHaveTextContent(/password is required/i);\n```\n\n`findByRole(\"alert\")` is stronger when the message is announced to assistive tech, because it asserts both the presence and the accessible semantics, not just the text node.\n\nNotes:\n\n- Drive the interaction with `userEvent`, not `fireEvent.click`.\n- Don't fall back to `data-testid` here \u2014 there's an accessible button and a text/alert query available.\n- If the project wraps providers, render through its custom `render` helper rather than raw `render` from RTL.",
|
|
1490
|
+
"outputSha256": "f776e7156bb19cd3c43a0955de1eeb94e7b0de1ccc44b5b67db92263821cb633",
|
|
1491
|
+
"promptSha256": "1f44f2d813cbfc176adf33d63507b7d52c251801af5761d3ec5cd6cb1bd4639a",
|
|
1492
|
+
"deterministic": [
|
|
1493
|
+
true
|
|
1494
|
+
],
|
|
1495
|
+
"judge": {
|
|
1496
|
+
"verdict": "pass",
|
|
1497
|
+
"reason": "Answer shows getByRole('button', { name: /submit/i }) for the button (criterion 1), findByText(/password is required/i) and findByRole('alert') for the error (criterion 2), and userEvent.setup() then await user.click(...) (criterion 3). It explicitly warns against fireEvent.click and data-testid, so no fail criteria hold."
|
|
1498
|
+
},
|
|
1499
|
+
"passed": true
|
|
1500
|
+
},
|
|
1501
|
+
{
|
|
1502
|
+
"output": "Query the submit button by role and accessible name, and the error by its text content:\n\n```tsx\nconst user = userEvent.setup();\nrender(<LoginForm />);\n\nawait user.click(screen.getByRole(\"button\", { name: /submit/i }));\n\nexpect(await screen.findByText(/password is required/i)).toBeInTheDocument();\n```\n\nWhy these queries:\n\n- **Button \u2014 `getByRole(\"button\", { name: /submit/i })`**: a button is exposed to assistive tech as the `button` role, and its accessible name comes from its visible text (or `aria-label`). This verifies the button is actually reachable and correctly named. Use `getByRole` (singular) since you expect exactly one; `getAllByRole` would hide a duplicate-button bug. If the accessible name is dynamic (e.g. \"Sign in\" vs \"Log in\"), tighten the regex or use `getByRole(\"button\")` only if there is genuinely a single button on screen. Reach for `data-testid` only if no accessible query exists, which is rare for a submit button.\n\n- **Error \u2014 `await screen.findByText(/password is required/i)`**: the message appears only after the async submit round-trip, so use `findByText` (which retries and awaits) rather than `getByText`, and never a manual `setTimeout`. Prefer matching on the message text over a testid, since the text is what the user reads. Two things to check first:\n - If the error is wired up accessibly (e.g. `aria-describedby` pointing at an element with `role=\"alert\"`), you can also assert `getByRole(\"alert\")` \u2014 that additionally proves screen readers announce it. Matching the text is the minimum; matching the role is stronger.\n - Make the regex as specific as the real copy allows, so an unrelated element containing the same words doesn't create a false pass.\n\nA tighter version if the error is an alert region:\n\n```tsx\nconst alert = await screen.findByRole(\"alert\");\nexpect(alert).toHaveTextContent(/password is required/i);\n```\n\nTwo things to sort out before this works depend on your setup, not the queries: the form's submit must be mocked at the request boundary (MSW handler or your project's equivalent), not by mocking the component's internal fetch helper \u2014 a boundary mock still exercises the real fetch/parse/error path. And if you see an `act` warning, it means the update escaped RTL's async wrapping; fix it by awaiting the right `findBy*`/`waitFor`, not by wrapping things in `act()` by hand.",
|
|
1503
|
+
"outputSha256": "4161dc700c4624804d0b3c98bb2f514cdc7ce31b16e9dc5546c08c44684e821a",
|
|
1504
|
+
"promptSha256": "1f44f2d813cbfc176adf33d63507b7d52c251801af5761d3ec5cd6cb1bd4639a",
|
|
1505
|
+
"deterministic": [
|
|
1506
|
+
true
|
|
1507
|
+
],
|
|
1508
|
+
"judge": {
|
|
1509
|
+
"verdict": "pass",
|
|
1510
|
+
"reason": "Answer shows getByRole('button', { name: /submit/i }) for the button, findByText(/password is required/i) and findByRole('alert') for the error, and userEvent.setup() then await user.click(...). No data-testid default and no fireEvent used."
|
|
1511
|
+
},
|
|
1512
|
+
"passed": true
|
|
1513
|
+
},
|
|
1514
|
+
{
|
|
1515
|
+
"output": "Query the button by its accessible role and name, and query the error by its text content \u2014 both through RTL's user-facing queries, not `data-testid`.\n\n**The button**\n\n```tsx\nscreen.getByRole(\"button\", { name: /submit/i })\n```\n\n`getByRole` matches what a screen reader announces, and the `name` option matches the button's accessible name (its visible label). Use `getByRole` (singular) since you expect exactly one submit button \u2014 `getAllByRole` is only for when you genuinely expect multiple matches. If the visible label is \"Log in\" rather than \"Submit\", match that instead: `{ name: /log in/i }`.\n\n**The error**\n\n```tsx\nawait screen.findByText(/password is required/i)\n```\n\nUse `findByText` (not `getByText`) because the error appears *after* the click, asynchronously \u2014 `findBy*` retries until the element shows up or times out. `getByText` would fire once and fail before React has re-rendered. Match on the actual error copy the component renders, and prefer a case-insensitive regex (`/password is required/i`) so incidental capitalization changes don't break the test.\n\n**The interaction**\n\nDrive the click with `user-event`, not `fireEvent`:\n\n```tsx\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\n\ntest(\"shows an error when submitting with an empty password\", async () => {\n const user = userEvent.setup();\n render(<LoginForm />);\n\n await user.click(screen.getByRole(\"button\", { name: /submit/i }));\n\n expect(await screen.findByText(/password is required/i)).toBeInTheDocument();\n});\n```\n\nTwo things to notice in that test:\n\n- The click is awaited, and the error assertion uses `await` with `findByText` \u2014 that await is what lets RTL wrap the resulting state update, so you won't get an `act` warning. If you ever do see one, switch to the matching `findBy*`/`waitFor` rather than wrapping in `act()` by hand.\n- `userEvent.setup()` is called per test, before `render`.\n\nBefore writing this, confirm whether the password field needs to be present but empty (just leave it untouched) versus cleared after typing \u2014 if the test types a value first, clear it with `await user.clear(...)` before clicking.\n\nFor the error query specifically: if the error is an inline `<span>`/`<p>` with the message as its text, `findByText` is right. If the message is exposed as an accessible description or alert, prefer `findByRole(\"alert\")` or querying by the `aria-describedby`/label relationship instead, since that also verifies the error is announced to assistive tech \u2014 which is the stronger assertion. Only fall back to `data-testid` if no accessible query can reach the element.",
|
|
1516
|
+
"outputSha256": "41f2b16782b548c01fc47494da29a1e6ff3801e152b3ba115db23261ed201611",
|
|
1517
|
+
"promptSha256": "1f44f2d813cbfc176adf33d63507b7d52c251801af5761d3ec5cd6cb1bd4639a",
|
|
1518
|
+
"deterministic": [
|
|
1519
|
+
true
|
|
1520
|
+
],
|
|
1521
|
+
"judge": {
|
|
1522
|
+
"verdict": "pass",
|
|
1523
|
+
"reason": "Answer shows getByRole('button', { name: /submit/i }) for the button, findByText(/password is required/i) for the error, and userEvent.setup() then await user.click(...) instead of fireEvent. data-testid only mentioned as last-resort fallback, not default. All pass criteria met, no fail criteria."
|
|
1524
|
+
},
|
|
1525
|
+
"passed": true
|
|
1526
|
+
},
|
|
1527
|
+
{
|
|
1528
|
+
"output": "Query the submit button by its accessible role and name, and the error by its visible text. That way the test asserts what a user (or screen reader) actually perceives, not an implementation detail.\n\nButton:\n\n```tsx\nscreen.getByRole(\"button\", { name: /submit/i })\n```\n\n`getByRole` (singular) because you expect exactly one. The `name` option matches the accessible name \u2014 the visible label, `aria-label`, or `aria-labelledby` \u2014 so `/submit/i` matches \"Submit\", \"Sign in\", or whatever the button is actually called. Use a substring/regex match so it survives minor copy changes, but keep it tight enough to stay unambiguous.\n\nError message:\n\n```tsx\nawait screen.findByText(/password is required/i)\n```\n\nTwo things matter here:\n\n- **`findByText`, not `getByText`.** The error appears asynchronously after the submit handler runs, so you need the async variant. `findBy*` retries until it appears or times out \u2014 this is what replaces any manual `setTimeout`/`sleep`. If the error is already in the DOM synchronously you could use `getByText`, but the async query is the safe default for anything that shows up after an interaction.\n- **Match the text, not a testid.** If the error element also has a role worth asserting on (e.g. `role=\"alert\"`), prefer `screen.findByRole(\"alert\", { name: ... })` \u2014 that additionally verifies the error is announced to assistive tech. Fall back to `findByText` when there's no meaningful role. Only reach for `data-testid` if neither role nor text is a viable handle.\n\nPut together, driving the interaction with `userEvent` (not `fireEvent`):\n\n```tsx\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\n\ntest(\"shows an error when submitting with an empty password\", async () => {\n const user = userEvent.setup();\n render(<LoginForm />);\n\n await user.click(screen.getByRole(\"button\", { name: /submit/i }));\n\n expect(\n await screen.findByText(/password is required/i),\n ).toBeInTheDocument();\n});\n```\n\nA note on the goal: the skill is scoped to test files only. If this test fails because the button has no accessible name or the error isn't rendered, fix the test's queries \u2014 don't edit `LoginForm` to make the assertion pass. A markup change to satisfy a test is a separate implementation change, not a test fix.\n\nIf you hit an `act(...)` warning, it almost always means the assertion isn't awaiting the right async query \u2014 switch to the matching `findBy*`/`waitFor` rather than wrapping anything in a manual `act()`.",
|
|
1529
|
+
"outputSha256": "3bda561659e1aaf7462ad1882ff01a7a830631540b9d88458ae7475cbf14f9ad",
|
|
1530
|
+
"promptSha256": "1f44f2d813cbfc176adf33d63507b7d52c251801af5761d3ec5cd6cb1bd4639a",
|
|
1531
|
+
"deterministic": [
|
|
1532
|
+
true
|
|
1533
|
+
],
|
|
1534
|
+
"judge": {
|
|
1535
|
+
"verdict": "pass",
|
|
1536
|
+
"reason": "Answer shows getByRole('button', { name: /submit/i }) for the button (criterion 1), findByText(/password is required/i) for the error (criterion 2), and drives the click via userEvent.setup() then await user.click(...) (criterion 3). No data-testid default and no fireEvent used."
|
|
1537
|
+
},
|
|
1538
|
+
"passed": true
|
|
1539
|
+
},
|
|
1540
|
+
{
|
|
1541
|
+
"output": "Query the submit button with `getByRole(\"button\", { name: /submit/i })` \u2014 the accessible name comes from the button's visible text (or its `aria-label`), so this asserts the button is reachable by assistive tech, not just visually present. Don't fall back to `getByTestId` unless no role/label query resolves it.\n\nFor the error message, use `findByText(/password is required/i)` (or `getByRole(\"alert\")` if the error is rendered in an ARIA alert region). Prefer `findBy*` over `getBy*` here because the error appears only after the async user event and state update \u2014 `findBy*` waits for it while `getBy*` would throw on the not-yet-rendered state. If the error has a stable accessible role, querying `getByRole(\"alert\", { name: /password is required/i })` is stronger still, since it also verifies the message is announced, not merely painted.\n\nPutting it together:\n\n```tsx\nconst user = userEvent.setup();\nrender(<LoginForm />);\n\nawait user.click(screen.getByRole(\"button\", { name: /submit/i }));\n\nconst error = await screen.findByRole(\"alert\");\nexpect(error).toHaveTextContent(/password is required/i);\n```\n\nTwo failure modes to avoid: reaching for `fireEvent.click` instead of `userEvent.click` (it skips the real pointer/focus sequence), and replacing the `findBy*`/`waitFor` with a `setTimeout` \u2014 a fixed delay is a race waiting to flake, and an `act` warning it papers over usually just means the test isn't awaiting the right query.",
|
|
1542
|
+
"outputSha256": "46a82137db96bbf75a001000e239dcf133e9f8679df02d9d09a8d5df575a7f95",
|
|
1543
|
+
"promptSha256": "1f44f2d813cbfc176adf33d63507b7d52c251801af5761d3ec5cd6cb1bd4639a",
|
|
1544
|
+
"deterministic": [
|
|
1545
|
+
true
|
|
1546
|
+
],
|
|
1547
|
+
"judge": {
|
|
1548
|
+
"verdict": "pass",
|
|
1549
|
+
"reason": "Answer names getByRole('button', { name: /submit/i }) for the button (not testid), findByText(/password is required/i) and getByRole('alert') for the error, and drives the click via userEvent.setup() then await user.click(...). It explicitly warns against fireEvent.click and getByTestId fallback. All pass criteria met, no fail criteria."
|
|
1550
|
+
},
|
|
1551
|
+
"passed": true
|
|
1552
|
+
},
|
|
1553
|
+
{
|
|
1554
|
+
"output": "For the button, query by its accessible role and name:\n\n```tsx\nconst user = userEvent.setup();\nrender(<LoginForm />);\nawait user.click(screen.getByRole(\"button\", { name: /submit/i }));\n```\n\n`getByRole(\"button\", ...)` is the right call here \u2014 a `<button>` has an implicit button role, so this finds it whether the label comes from the text content, an `aria-label`, or a nested element. The `name` option matches the accessible name, so `/submit/i` works for \"Submit\", \"Sign in\", etc., depending on your copy \u2014 tighten it to the exact string once you know the label. Use `getByRole` (singular) since you expect exactly one submit button; if two match, that's a real ambiguity worth fixing in the markup rather than papering over with `getAllByRole`.\n\nFor the error, prefer querying by role, since an error message should be announced to assistive tech:\n\n```tsx\nexpect(await screen.findByRole(\"alert\")).toHaveTextContent(/password is required/i);\n```\n\n`role=\"alert\"` (or `aria-live`) is the conventional markup for a validation error, and `findByRole` is an async query \u2014 it retries until the element appears, which is what you want after a click that triggers a state update. If the component renders the error as a plain `<p>` with no alert role, fall back to text:\n\n```tsx\nexpect(await screen.findByText(/password is required/i)).toBeInTheDocument();\n```\n\nTwo notes:\n\n- Use `findBy*`, not `getBy*`, for the error. After the click, the message isn't in the DOM yet, so a synchronous `getBy` throws immediately. `findByText`/`findByRole` await the update and also keep the state update inside RTL's async wrapping, which avoids an `act` warning.\n- Do not reach for a `data-testid` on the error. A user-facing validation message is exactly the case where an accessible query is available, so the testid fallback isn't warranted \u2014 and it would stop the test from verifying the message is actually exposed to users of assistive tech.\n\nPut together:\n\n```tsx\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport LoginForm from \"./LoginForm\";\n\ntest(\"shows an error when submitting with an empty password\", async () => {\n const user = userEvent.setup();\n render(<LoginForm />);\n\n await user.click(screen.getByRole(\"button\", { name: /submit/i }));\n\n expect(await screen.findByRole(\"alert\")).toHaveTextContent(/password is required/i);\n});\n```\n\nAlso drive the interaction through `userEvent.setup()` rather than `fireEvent`, so you get the real focus/pointer/event sequence a browser would produce.",
|
|
1555
|
+
"outputSha256": "869d9a0cc7d38c0f5fd5bcf7713247a54d06905d351f92d8835fea252690c14b",
|
|
1556
|
+
"promptSha256": "1f44f2d813cbfc176adf33d63507b7d52c251801af5761d3ec5cd6cb1bd4639a",
|
|
1557
|
+
"deterministic": [
|
|
1558
|
+
true
|
|
1559
|
+
],
|
|
1560
|
+
"judge": {
|
|
1561
|
+
"verdict": "pass",
|
|
1562
|
+
"reason": "Answer shows getByRole('button', { name: /submit/i }) for the button (criterion 1), findByRole('alert') with findByText fallback for the error (criterion 2), and userEvent.setup() then await user.click(...) with no fireEvent (criterion 3). It explicitly warns against data-testid for the error, so no fail criteria hold."
|
|
1563
|
+
},
|
|
1564
|
+
"passed": true
|
|
1565
|
+
},
|
|
1566
|
+
{
|
|
1567
|
+
"output": "Query for the button by its accessible role and name, and query for the error by its text (or by an `aria-live`/`role=\"alert\"` region if the component renders one):\n\n- Button: `screen.getByRole(\"button\", { name: /submit/i })` \u2014 use whatever visible label the button actually has (\"Submit\", \"Log in\", \"Sign in\"), and prefer an exact or anchored regex if multiple buttons share words.\n- Error: `await screen.findByText(/password is required/i)` since the error only appears after the click and render cycle. If the error is rendered inside a `role=\"alert\"` element, `screen.findByRole(\"alert\")` and assert on its text content is the more accessible-friendly option.\n\nSketch:\n\n```tsx\nconst user = userEvent.setup();\nrender(<LoginForm />);\n\nawait user.click(screen.getByRole(\"button\", { name: /submit/i }));\n\nexpect(await screen.findByText(/password is required/i)).toBeInTheDocument();\n```\n\nTwo things that matter for this specific case:\n\nUse `userEvent`, not `fireEvent.click`, for the click \u2014 that is the interaction mechanism this skill expects.\n\nUse `findByText`/`findByRole` (async), not `getByText`, because the message appears after the click triggers a state update and re-render. Reaching for `getByText` here, or wrapping things in a manual `act`, is exactly the pattern that produces the flaky `act` warnings you would then be tempted to silence \u2014 the `findBy*` query is what correctly awaits RTL's async wrapping.\n\nOnly fall back to a `data-testid` if the error message genuinely has no accessible text or role to hang a query on, which is rare for a visible error string like this.",
|
|
1568
|
+
"outputSha256": "a065189b2f8eec765a70a7c5c8bd77d5bb55a12827b7fdbdf02409566cb9ec5f",
|
|
1569
|
+
"promptSha256": "1f44f2d813cbfc176adf33d63507b7d52c251801af5761d3ec5cd6cb1bd4639a",
|
|
1570
|
+
"deterministic": [
|
|
1571
|
+
true
|
|
1572
|
+
],
|
|
1573
|
+
"judge": {
|
|
1574
|
+
"verdict": "pass",
|
|
1575
|
+
"reason": "Button queried via screen.getByRole(\"button\", { name: /submit/i }) (criterion 1). Error via await screen.findByText(/password is required/i) and findByRole(\"alert\") (criterion 2). Click driven by userEvent.setup() then await user.click(...), explicitly rejecting fireEvent.click (criterion 3). data-testid only as rare fallback, not default."
|
|
1576
|
+
},
|
|
1577
|
+
"passed": true
|
|
1578
|
+
},
|
|
1579
|
+
{
|
|
1580
|
+
"output": "Query both by role and accessible name, never by CSS class or testid.\n\n**The submit button** \u2014 `getByRole(\"button\", { name: /submit/i })`. The `name` option matches the button's accessible name, which for a real submit button is its visible text (\"Submit\", \"Sign in\", \"Log in\") or an `aria-label`. Match with a regex if you are unsure of the exact casing or wording, but do check what the button actually renders first \u2014 `getByRole(\"button\", { name: /log in/i })` is better than a lazy `/submit/i` if the button really says \"Log in\".\n\n**The error message** \u2014 check whether it is wired to the field, because that changes the right query:\n\n- If the form marks the input invalid (`aria-invalid` + `aria-describedby` pointing at the error), the error still renders as text, so `getByRole(\"alert\")` works when the error is in a live region, or `getByText(/password is required/i)` when it is a plain paragraph.\n- If the error text is associated with the input, prefer `findByRole(\"alert\")` or `findByText` \u2014 whichever matches how the component actually renders it. There is no universal answer; read the component's JSX for the error path before writing the assertion.\n\n**Waiting for it** \u2014 the error appears after the submit handler runs, which is async. Use `await screen.findByText(...)` / `await screen.findByRole(\"alert\")`, not `getBy*` (which throws before the update lands) and not a `setTimeout`. \"Find\" queries are the async variant of \"get\" queries and retry until the element shows up or the timeout expires.\n\nPutting it together:\n\n```tsx\nconst user = userEvent.setup();\nrender(<LoginForm />);\n\nawait user.click(screen.getByRole(\"button\", { name: /log in/i }));\n\nexpect(await screen.findByText(/password is required/i)).toBeInTheDocument();\n```\n\nand if the error is announced via a live region instead:\n\n```tsx\nexpect(await screen.findByRole(\"alert\")).toHaveTextContent(/password is required/i);\n```\n\nTwo things to get right beyond the queries:\n\n- Drive the click with `userEvent.setup()` + `await user.click(...)`, not `fireEvent.click` \u2014 user-event simulates the real event sequence (pointer, focus, etc.) and is what the project should already be using.\n- Assert on the text/role the user actually sees. Do not reach for a `data-testid` on the error unless there is genuinely no accessible anchor for it, and do not assert on internal state or a snapshot of the private props.\n\nIf you hit an `act(...)` warning after this, it almost always means you used a synchronous `getBy*` where the update had not landed yet \u2014 switch that assertion to `findBy*` and the warning goes away on its own rather than needing a manual `act()` wrap.",
|
|
1581
|
+
"outputSha256": "b8213d1a87052875beb966bf19f03d354a9de908b724267b02bf096dc1b6b4fa",
|
|
1582
|
+
"promptSha256": "1f44f2d813cbfc176adf33d63507b7d52c251801af5761d3ec5cd6cb1bd4639a",
|
|
1583
|
+
"deterministic": [
|
|
1584
|
+
true
|
|
1585
|
+
],
|
|
1586
|
+
"judge": {
|
|
1587
|
+
"verdict": "pass",
|
|
1588
|
+
"reason": "Answer names getByRole('button', { name: /log in/i }) for the button (not testid), findByText(/password is required/i) and findByRole('alert') for the error, and drives the click via userEvent.setup() + await user.click(...), explicitly rejecting fireEvent.click. All pass criteria concretely shown; no fail criteria hold."
|
|
1589
|
+
},
|
|
1590
|
+
"passed": true
|
|
1591
|
+
},
|
|
1592
|
+
{
|
|
1593
|
+
"output": "Query the button by role with an accessible name, and the error message by its text content (or by role if it's tied to the field via `aria-describedby`/`aria-invalid`).\n\nButton:\n\n```tsx\nscreen.getByRole(\"button\", { name: /submit/i })\n```\n\nUse `getByRole` (singular) since you expect exactly one submit button. The `name` option matches the accessible name \u2014 visible label text, or `aria-label`. If the button is literally labeled \"Log in\", match `/log ?in/i` instead of assuming \"submit\".\n\nError message:\n\n```tsx\nawait screen.findByText(/password is required/i)\n```\n\nUse `findByText` (not `getByText`) because the error renders asynchronously after the click and submit handler run \u2014 `findBy*` retries until it appears, which is also what keeps you out of `act` warnings. Match on the real copy the component renders; if you don't know it, read the component first rather than inventing a string.\n\nIf the component wires the error to the input accessibly, prefer the role/association query over raw text:\n\n```tsx\nexpect(screen.getByRole(\"textbox\", { name: /password/i }))\n .toHaveAccessibleErrorMessage(/password is required/i)\n```\n\nand/or assert `aria-invalid` on the field. That verifies the error is announced to assistive tech, not just painted on screen.\n\nFull shape:\n\n```tsx\nconst user = userEvent.setup();\nrender(<LoginForm />);\n\nawait user.click(screen.getByRole(\"button\", { name: /submit|log ?in/i }));\n\nexpect(await screen.findByText(/password is required/i)).toBeInTheDocument();\n```\n\nAvoid `getByTestId` for both \u2014 only fall back to it if there's genuinely no accessible name or text to query, which is rare for a submit button and an error message. Also don't reach for `fireEvent.click`; drive it through `userEvent.setup()`.",
|
|
1594
|
+
"outputSha256": "7c28e23d6a9faa3e76f9203fd6e755d87c6421d16e7e587462013853f3b748fa",
|
|
1595
|
+
"promptSha256": "1f44f2d813cbfc176adf33d63507b7d52c251801af5761d3ec5cd6cb1bd4639a",
|
|
1596
|
+
"deterministic": [
|
|
1597
|
+
true
|
|
1598
|
+
],
|
|
1599
|
+
"judge": {
|
|
1600
|
+
"verdict": "pass",
|
|
1601
|
+
"reason": "Button queried via getByRole('button', { name: /submit/i }) (criterion 1). Error via findByText(/password is required/i) and getByRole textbox with toHaveAccessibleErrorMessage (criterion 2). Click driven by userEvent.setup() then await user.click(...) (criterion 3). Explicitly warns against getByTestId and fireEvent.click, so no fail criteria hold."
|
|
1602
|
+
},
|
|
1603
|
+
"passed": true
|
|
1604
|
+
},
|
|
1605
|
+
{
|
|
1606
|
+
"output": "Query for the button by its accessible role and name, and for the error by its rendered text. Both are user-facing queries, so they don't couple the test to markup details like `data-testid` or class names.\n\n```tsx\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { LoginForm } from \"./LoginForm\";\n\ntest(\"shows a password error when the password is empty on submit\", async () => {\n const user = userEvent.setup();\n render(<LoginForm />);\n\n // button: role + accessible name\n await user.click(screen.getByRole(\"button\", { name: /submit/i }));\n\n // error: rendered text, awaited because it appears after the click\n expect(await screen.findByText(/password is required/i)).toBeInTheDocument();\n});\n```\n\nWhy each query:\n\n- **Button** \u2014 `getByRole(\"button\", { name: /submit/i })`. The `role`-based\n query matches how assistive tech sees the element, and the `name` option\n matches the button's accessible name (its text or `aria-label`). Use\n `getByRole` for a single element you expect to exist; only use\n `getAllByRole` when you genuinely expect several matches. If the button's\n label is just an icon, give it an `aria-label` and query that name rather\n than adding a `data-testid`.\n\n- **Error** \u2014 `findByText(/password is required/i)`. The error message is\n content the user reads, so text is the right thing to assert on. Prefer\n `findBy*` over `getBy*` here: the message appears *asynchronously* after\n the click (state update, possibly a validation/setState cycle), and\n `findByText` retries until it shows up. Using `getByText` immediately\n after the click will usually fail because the update hasn't flushed yet.\n\nA few refinements worth making:\n\n- If the error is associated with the input for accessibility, the input\n should have `aria-invalid` and an `aria-describedby` pointing at the\n error's `id`. You can then also assert the association, which verifies\n the error is actually announced \u2014 not just painted:\n\n ```tsx\n const password = screen.getByLabelText(/password/i);\n expect(password).toHaveAttribute(\"aria-invalid\", \"true\");\n expect(password).toHaveAccessibleDescription(/password is required/i);\n ```\n\n- If the field is labeled, querying the input by `getByLabelText(/password/i)`\n is how you'd type into it for the non-empty cases in the rest of the\n suite.\n\n- Avoid `fireEvent.click` here. `userEvent.click` runs the real pointer and\n focus sequence, so it exercises the same path a user does. A bare\n `fireEvent.click` skips that and can miss handlers wired to focus/blur.\n\n- Don't mock the component's internal submit/validation function to force\n the error to render; drive the real interaction and let the component's\n own logic produce the message.\n\n- If the error only appears after an async submit, wrap the assertion in\n `await screen.findByText(...)` (as above) rather than a `setTimeout` or a\n manual `act`. An `act` warning here almost always means the test isn't\n awaiting the right query, not that you need to hand-wrap in `act`.\n\nAlso note this skill writes the test only \u2014 if the empty-password error\nisn't currently rendered by the component, that's a component change, not\nsomething to fix inside the test.",
|
|
1607
|
+
"outputSha256": "6a9bf8ab4ed4a875d34d8ce46a0f886b08d2e859ce50be3603706e79412631b2",
|
|
1608
|
+
"promptSha256": "1f44f2d813cbfc176adf33d63507b7d52c251801af5761d3ec5cd6cb1bd4639a",
|
|
1609
|
+
"deterministic": [
|
|
1610
|
+
true
|
|
1611
|
+
],
|
|
1612
|
+
"judge": {
|
|
1613
|
+
"verdict": "pass",
|
|
1614
|
+
"reason": "Answer shows getByRole('button', { name: /submit/i }) for the button (criterion 1), findByText(/password is required/i) for the error (criterion 2), and drives the click via userEvent.setup() then await user.click(...) (criterion 3). It explicitly warns against data-testid and fireEvent.click, so no fail criteria hold."
|
|
1615
|
+
},
|
|
1616
|
+
"passed": true
|
|
1617
|
+
}
|
|
1618
|
+
]
|
|
1619
|
+
},
|
|
1620
|
+
{
|
|
1621
|
+
"id": "mock-network-boundary",
|
|
1622
|
+
"kind": "behavior",
|
|
1623
|
+
"prompt": "My component test needs to mock an API call that the component makes on mount. Where should I put the mock?",
|
|
1624
|
+
"strictness": "high",
|
|
1625
|
+
"trials": 10,
|
|
1626
|
+
"passes": 9,
|
|
1627
|
+
"passRate": 0.9,
|
|
1628
|
+
"passAtK": 1,
|
|
1629
|
+
"grader": "judge",
|
|
1630
|
+
"status": "ran",
|
|
1631
|
+
"trialRecords": [
|
|
1632
|
+
{
|
|
1633
|
+
"output": "At the request boundary \u2014 not on the component's own data-fetching function or client wrapper.\n\nConcretely, that means one of:\n\n- **MSW (Mock Service Worker)** if the project already uses it (most likely, since it's the modern default): define a handler for the actual URL the component hits, e.g. `http.get('/api/users', () => HttpResponse.json([...]))`, and wire up the server in the project's test setup file so every test gets it. This exercises the real fetch path, JSON parsing, and error handling.\n- **The project's existing manual fetch/axios mock**, if Step 1 of discovery shows that's the established convention \u2014 mock `global.fetch` or the axios instance at the network call site, not the module that calls it.\n\nWhat *not* to do: mock the component's internal helper (e.g. `vi.mock('./fetchUsers')`) or the API client module the component imports. That short-circuits the request/parse/error code path you actually want the test to cover, so the test passes even when the real network layer is broken.\n\nPer the skill's Step 1, read 1\u20132 neighboring component tests first and match whichever mocking style is already in place (MSW handlers, a manual fetch mock, or whatever the repo standardized on) \u2014 don't introduce a second mocking approach.\n\nAlso, trigger the mock correctly relative to mount: since the call fires on mount, set the handler up before `render(...)` runs, then await the UI with `findBy*`/`waitFor` rather than a fixed timeout. If you use `userEvent`, call `userEvent.setup()` before rendering too.",
|
|
1634
|
+
"outputSha256": "51d9fb52cee2a5a8d44c0736f8d5b5bcf7d455b61aa466e5e6099061c4096c96",
|
|
1635
|
+
"promptSha256": "5325d8d19467f3d9eb5306cd3194c9397fd2a21260f8604bbce0355b7aef71a0",
|
|
1636
|
+
"deterministic": [],
|
|
1637
|
+
"judge": {
|
|
1638
|
+
"verdict": "pass",
|
|
1639
|
+
"reason": "Names concrete boundary mechanism: MSW handler `http.get('/api/users', () => HttpResponse.json([...]))` for the actual URL, plus manual global.fetch/axios at network call site. Explains boundary keeps real path under test: 'exercises the real fetch path, JSON parsing, and error handling.' Explicitly warns against vi.mock('./fetchUsers')/API client module, so no fail criterion holds."
|
|
1640
|
+
},
|
|
1641
|
+
"passed": true
|
|
1642
|
+
},
|
|
1643
|
+
{
|
|
1644
|
+
"output": "Mock it at the request boundary \u2014 not the component's own data-fetching function.\n\nConcretely, whatever the project's existing convention is (found in Step 1): an MSW handler (`server.use(http.get(\"/api/users\", ...))`), or the project's existing fetch/axios mock layer. If neither MSW nor an equivalent boundary mock exists, mock the global `fetch` (or the axios instance) at the module level in the test file \u2014 never mock the component's internal `getUser()` / `useUserQuery()` wrapper.\n\n```tsx\n// MSW \u2014 preferred, since the component's real fetch/parse/error path still runs\nserver.use(\n http.get(\"/api/users/1\", () =>\n HttpResponse.json({ id: 1, name: \"Ada\" })\n )\n);\n\nrender(<UserCard userId={1} />);\nexpect(await screen.findByText(\"Ada\")).toBeInTheDocument();\n```\n\nWhy the boundary matters: mocking the component's own fetch helper means the test never exercises the real request construction, response parsing, or error handling \u2014 the component can break in production while the test stays green. A boundary mock still runs all of that.\n\nThe mock goes in `beforeEach`/`beforeAll` (or per-test with `server.use` for one-off responses) \u2014 at module/test scope, not inside the render call. Then await the result with `findBy*`, never a `setTimeout`. For the error state, register a handler that returns a non-2xx response and assert with `findByRole(\"alert\")` or the rendered error text.\n\nIf you were tempted to mock `getUser` directly because the loading state kept failing: that's the red flag \u2014 the real fix is awaiting the right `findBy*`, not skipping the boundary.",
|
|
1645
|
+
"outputSha256": "1045a9253f581da955a986c1cb695ed7fe6ab1d996bd8f9cb65a7089a84a2bc2",
|
|
1646
|
+
"promptSha256": "5325d8d19467f3d9eb5306cd3194c9397fd2a21260f8604bbce0355b7aef71a0",
|
|
1647
|
+
"deterministic": [],
|
|
1648
|
+
"judge": {
|
|
1649
|
+
"verdict": "pass",
|
|
1650
|
+
"reason": "Names concrete boundary mechanism: `server.use(http.get(\"/api/users/1\", ...))` MSW handler, plus fallback mocking global fetch/axios instance. Explains boundary keeps real path under test: 'the component's real fetch/parse/error path still runs' and 'mocking the component's own fetch helper means the test never exercises the real request construction, response parsing, or error handling.' No fail criterion: explicitly warns against mocking getUser directly."
|
|
1651
|
+
},
|
|
1652
|
+
"passed": true
|
|
1653
|
+
},
|
|
1654
|
+
{
|
|
1655
|
+
"output": "Mock it at the request boundary \u2014 the actual network layer \u2014 not inside the component's own data-fetching function.\n\nConcretely:\n\n- If the project uses MSW, register a handler for the endpoint the component hits on mount (e.g. `http.get('/api/user', () => HttpResponse.json({...}))`), and make sure the server is set up before the test renders and reset between tests. MSW intercepts the real `fetch`/`XHR`, so the component's fetch, parse, and error-handling code still runs for real.\n- If there's no MSW, mock the global `fetch` (or `axios`) itself, or route through whatever request module the component already uses for all its calls \u2014 as long as the seam is the network call, not a component-internal wrapper like `getUser()` or a hook the component defines.\n\nWhy not the component's own fetch helper: mocking `getUser()` or an internal hook skips the code you actually want to exercise \u2014 the request construction, the response parsing, and the loading/error branches around it. You'd get a green test that proves nothing about whether the component handles a real response or a real failure.\n\nThen handle the async UI with a `findBy*` query or `waitFor`, since the mount-time fetch resolves after render:\n\n```tsx\nrender(<UserCard />);\nexpect(await screen.findByText(/jane doe/i)).toBeInTheDocument();\n```\n\nDon't follow it with a `setTimeout` or a manual `act()` \u2014 `findBy*`/`waitFor` is what lets RTL flush the state update from the resolved request and keeps the `act` warning away.\n\nOne caveat: if the on-mount call is already established in a neighboring test (a shared MSW handler set or a `__mocks__` module), match that convention rather than introducing a second mocking style.",
|
|
1656
|
+
"outputSha256": "146269b61556581832943a9a1ef99aa095d4782996bce54c46de73ce4053a57b",
|
|
1657
|
+
"promptSha256": "5325d8d19467f3d9eb5306cd3194c9397fd2a21260f8604bbce0355b7aef71a0",
|
|
1658
|
+
"deterministic": [],
|
|
1659
|
+
"judge": {
|
|
1660
|
+
"verdict": "pass",
|
|
1661
|
+
"reason": "Names concrete boundary mechanism: MSW handler `http.get('/api/user', () => HttpResponse.json({...}))` intercepting real fetch/XHR, plus fallback of mocking global fetch/axios. Explains boundary keeps real fetch/parse/error-handling path under test ('component's fetch, parse, and error-handling code still runs for real'). Warns against mocking getUser()/internal hook, not recommending it."
|
|
1662
|
+
},
|
|
1663
|
+
"passed": true
|
|
1664
|
+
},
|
|
1665
|
+
{
|
|
1666
|
+
"output": "Mock it at the network/request boundary, not by mocking the component's own data-fetching function or internal helper.\n\nIn practice that means whatever request-level seam the project already uses \u2014 most commonly an MSW (Mock Service Worker) handler for the endpoint the component hits on mount, or the project's equivalent boundary mock (a mocked `fetch`/`axios` at the transport layer, configured per your Step 1 convention read). Register that handler in the test's setup (or a shared `beforeEach`/server setup), render the component, then await the resulting UI with `findBy*`/`waitFor` rather than a manual timeout.\n\nWhy the boundary and not the component's fetch wrapper: mocking the wrapper skips the real request/parse/error path, so the test stops exercising what actually runs in production \u2014 you'd be asserting against a stub of your own code instead of the behavior the user sees. A boundary mock still runs the component's real fetch call, its real response parsing, and its real error handling; only the server is replaced.\n\nSo: no mock inside the component's import graph, no `vi.mock`/`jest.mock` on the component's own data function. Put the handler where the project's existing tests put theirs \u2014 read a neighboring component test first and match it exactly, since that keeps the new test consistent with the suite's established seam.",
|
|
1667
|
+
"outputSha256": "d2a2f8cd304831ef7b49b6ee0dcdb817cc5475bdb9435642f8be2fdad203e43b",
|
|
1668
|
+
"promptSha256": "5325d8d19467f3d9eb5306cd3194c9397fd2a21260f8604bbce0355b7aef71a0",
|
|
1669
|
+
"deterministic": [],
|
|
1670
|
+
"judge": {
|
|
1671
|
+
"verdict": "pass",
|
|
1672
|
+
"reason": "Names concrete boundary mechanism: MSW handler for the endpoint, or mocked fetch/axios at transport layer. Explains boundary keeps real fetch/parse/error path under test ('A boundary mock still runs the component's real fetch call, its real response parsing, and its real error handling'). Explicitly rejects vi.mock/jest.mock on the component's data function, so no fail criterion holds."
|
|
1673
|
+
},
|
|
1674
|
+
"passed": true
|
|
1675
|
+
},
|
|
1676
|
+
{
|
|
1677
|
+
"output": "Mock it at the request boundary \u2014 the network layer \u2014 not by replacing the component's own data-fetching function.\n\nConcretely, that means one of:\n\n- **MSW** (if the project already uses it, per your Step 1 discovery): add a handler for the request the component fires on mount, e.g. `http.get('/api/user', () => HttpResponse.json(fakeUser))`, and let the component run its real fetch \u2192 parse \u2192 set-state path against it.\n- **A manual fetch/axios-level stub** that intercepts the actual endpoint URL (e.g. `vi.stubGlobal('fetch', ...)` or the project's existing equivalent), again leaving the component's own fetching code intact.\n\nDo **not** stub the component's internal helper (`mockUserService.getUser` or similar). That skips the real request/parse/error-handling path the test is supposed to exercise, and it breaks the moment the component refactors its internals.\n\nPlacement in the file: set the handler up in `beforeEach` (MSW: `server.use(...)` per test, or a shared default handler; manual mock: `beforeEach(() => { fetchMock.mockResolvedValueOnce({ json: () => res.json() }) })`), right before `render(<Component />)` \u2014 since the fetch fires on mount, the mock must be in place before render is called.\n\nThen await the resulting UI with `findBy*` rather than a fixed timeout:\n\n```tsx\nrender(<UserCard id=\"1\" />);\nexpect(await screen.findByText(/Ada Lovelace/i)).toBeInTheDocument();\n```\n\nAlso remember the same rule applies to the component's other on-mount states: if you're covering loading/error, the boundary mock is where you return a delayed response or a 500 \u2014 never a mocked internal function returning a canned \"error\" object.",
|
|
1678
|
+
"outputSha256": "293857df57ba21e5c1690ba72e1e64f778ebfcc015048c71aceb2b14b74009d2",
|
|
1679
|
+
"promptSha256": "5325d8d19467f3d9eb5306cd3194c9397fd2a21260f8604bbce0355b7aef71a0",
|
|
1680
|
+
"deterministic": [],
|
|
1681
|
+
"judge": {
|
|
1682
|
+
"verdict": "pass",
|
|
1683
|
+
"reason": "Names concrete boundary mechanisms: MSW `http.get('/api/user', () => HttpResponse.json(fakeUser))` and `vi.stubGlobal('fetch', ...)` intercepting the endpoint URL. Explains boundary mocking keeps the real fetch\u2192parse\u2192set-state path under test, and warns against stubbing the internal helper. No fail criterion met."
|
|
1684
|
+
},
|
|
1685
|
+
"passed": true
|
|
1686
|
+
},
|
|
1687
|
+
{
|
|
1688
|
+
"output": "At the request boundary, not inside the component's data-fetching code.\n\nConcretely: mock the network layer \u2014 an MSW handler (`server.use(...)` with an `http.get('.../api/users', ...)` handler for that URL), or whatever equivalent the project already uses. Intercept at the point where the request leaves the app (fetch/axios/HTTP), so the component's real fetch \u2192 parse \u2192 state-update \u2192 render path still runs. That's the whole point of the test: it exercises the actual loading/error/data flow.\n\nWhat NOT to do:\n\n- Don't mock the component's own fetch wrapper, hook, or API-client module method. That skips the real request/parse/error handling, so a broken parse or a swallowed error would still pass the test.\n- Don't stub out the component's internal state or the effect that runs on mount.\n\nShape of it:\n\n```tsx\n// setup (once, for all tests)\nconst server = setupServer(...);\nbeforeAll(() => server.listen());\nafterEach(() => server.resetHandlers());\nafterAll(() => server.close());\n\n// per test \u2014 override for this URL\nserver.use(\n http.get('/api/users', () => HttpResponse.json([{ id: 1, name: 'Ada' }]))\n);\n\nrender(<UserCard />);\nexpect(await screen.findByText('Ada')).toBeInTheDocument();\n```\n\nTwo things to get right alongside the mock:\n\n1. Since the call fires on mount, the first paint is the loading state \u2014 await it with `findBy*`/`waitFor` (e.g. `await screen.findByText('Ada')`), never a `setTimeout`. An `act` warning here usually just means you're not awaiting the right async query.\n2. Cover the states this enables: loading, success, error (return a 500 handler), and empty (return `[]`) \u2014 each as its own test with its own handler override.\n\nBefore writing any of this, check a neighboring test in the repo \u2014 if the project already has an MSW server or a shared mock at the request boundary, reuse that setup rather than standing up a second mechanism.",
|
|
1689
|
+
"outputSha256": "127bdd91359a2ab67be978286a696e97e96a8b078c5671d1ece6d34c82817bf6",
|
|
1690
|
+
"promptSha256": "5325d8d19467f3d9eb5306cd3194c9397fd2a21260f8604bbce0355b7aef71a0",
|
|
1691
|
+
"deterministic": [],
|
|
1692
|
+
"judge": {
|
|
1693
|
+
"verdict": "pass",
|
|
1694
|
+
"reason": "Names concrete boundary mechanism: MSW `server.use(http.get('/api/users', ...))` with setupServer, and says to reuse project's existing equivalent. Explains boundary keeps real fetch\u2192parse\u2192state-update\u2192render path under test. Explicitly warns against mocking the component's fetch wrapper/hook/API-client module, so no fail criterion holds."
|
|
1695
|
+
},
|
|
1696
|
+
"passed": true
|
|
1697
|
+
},
|
|
1698
|
+
{
|
|
1699
|
+
"output": "At the request boundary, not inside the component's own data-fetching logic \u2014 mock the network call itself (an MSW handler, or whatever boundary-mocking setup your project's neighboring tests already use), so the component's real fetch/parse/error-handling path still runs against it.\n\nConcretely: first check how a sister test in the same directory does it. If the project uses MSW, add a handler for that endpoint in your test's `beforeEach`/`setupServer` and let the component's real `fetch`/axios call hit it. If the project uses a manual fetch mock or a mocked API-client module at the network layer, follow that existing pattern rather than inventing a new one.\n\nDo not mock the component's internal fetch wrapper or data-fetching hook directly \u2014 that skips the exact code path (request construction, response parsing, error mapping) the test is supposed to exercise.\n\nThen drive the test with an async query so the mount-time fetch resolves before you assert:\n\n```tsx\nrender(<UserCard userId=\"42\" />);\nexpect(await screen.findByText(/jane doe/i)).toBeInTheDocument();\n```\n\nUse `findBy*`/`waitFor` for the post-fetch assertion \u2014 not a `setTimeout` \u2014 and cover the error branch with a boundary mock that returns a failure response (e.g. a 500 handler), asserting on the rendered error state rather than on the fetch call itself.\n\nIf the component takes its fetch function as a prop or via a provider, the cleanest option is often to inject a real-looking client pointed at the boundary mock, keeping the mock at the same layer as the network rather than at the component seam.",
|
|
1700
|
+
"outputSha256": "8fd9b502ec53283854d9e0c393a9dfc3eb06003a84a80c775ccc4fd6c461b56a",
|
|
1701
|
+
"promptSha256": "5325d8d19467f3d9eb5306cd3194c9397fd2a21260f8604bbce0355b7aef71a0",
|
|
1702
|
+
"deterministic": [],
|
|
1703
|
+
"judge": {
|
|
1704
|
+
"verdict": "pass",
|
|
1705
|
+
"reason": "Names concrete boundary mechanism: 'an MSW handler' with 'add a handler for that endpoint in your test's beforeEach/setupServer' and a '500 handler'. Explains boundary keeps real path under test: 'the component's real fetch/parse/error-handling path still runs against it' and warns against mocking the fetch wrapper directly. No fail criterion: it explicitly rejects mocking the internal fetch wrapper."
|
|
1706
|
+
},
|
|
1707
|
+
"passed": true
|
|
1708
|
+
},
|
|
1709
|
+
{
|
|
1710
|
+
"output": "Mock at the request boundary, not at the component's own data-fetching function.\n\nConcretely, in preference order:\n\n1. **MSW handler** (best, if the project already uses MSW): register a handler for the actual URL/route the component hits on mount, in the test file or a shared `handlers` array. The real `fetch`/query code still runs, so the request, parse, and error paths get exercised.\n\n2. **Project's existing equivalent**: if Step 1 of discovery found the project mocks at the network layer some other way (a fetch stub, a shared request-mocking helper), use that same mechanism so the file matches neighboring tests.\n\n3. **A mocked API *client* module** (e.g. `vi.mock(\"../api/users\")` or `jest.mock(...)`) \u2014 acceptable only if that module *is* the request boundary the project already mocks everywhere, not a component-internal helper.\n\nWhat to avoid: mocking the component's own internal fetch wrapper or a private helper the component calls. That skips the real request/parse/error-handling path, which is usually the part worth testing \u2014 the \"mock the component's fetch helper directly\" red flag.\n\nTwo practical notes:\n\n- Set the mock/handler up *before* `render(...)`, since the call fires on mount. For MSW that's `server.use(...)` (or handlers passed into `setupServer`) in the test body before render.\n- Then wait with an async query rather than a sleep: `expect(await screen.findByText(/.../)).toBeInTheDocument()`, or `await screen.findByRole(...)`. Don't reach for `waitFor` with a timeout, and don't `setTimeout`.\n\nSo: boundary (MSW/route handler) \u2192 await with `findBy*` \u2192 assert on rendered output.",
|
|
1711
|
+
"outputSha256": "81f58b7ae7d33d736193564e9fff948e20cecad42d301dd3b593061d0bbd8577",
|
|
1712
|
+
"promptSha256": "5325d8d19467f3d9eb5306cd3194c9397fd2a21260f8604bbce0355b7aef71a0",
|
|
1713
|
+
"deterministic": [],
|
|
1714
|
+
"judge": {
|
|
1715
|
+
"verdict": "pass",
|
|
1716
|
+
"reason": "Names concrete boundary mechanism: MSW handler for the actual URL/route, plus server.use/setupServer before render. Explains boundary keeps real fetch/query code running so request, parse, and error paths get exercised. Warns against mocking component's internal fetch wrapper. No fail criterion: the vi.mock option is explicitly limited to a module that IS the request boundary, not a component-internal helper."
|
|
1717
|
+
},
|
|
1718
|
+
"passed": true
|
|
1719
|
+
},
|
|
1720
|
+
{
|
|
1721
|
+
"output": "At the request boundary \u2014 not inside the component's own fetch/data-fetching function.\n\nConcretely:\n\n- If the project already uses MSW (or an equivalent server-level fetch mock), register a handler for the exact URL/method the component hits on mount, and give it the success, error, and empty responses you need per test. The real fetch/parse/error-handling code path in the component still runs.\n- If the project mocks at a lower level, match whatever Step 1 discovery found in the neighboring tests \u2014 a manual `fetch` stub or a mocked API client module. Do not introduce a second mocking approach.\n\nThen in the test:\n\n- Render the component, then await the async UI with `findBy*` / `waitFor` \u2014 never a `setTimeout`.\n- Cover the states from planning: loading, success, error, empty, plus any interaction (submit, retry) via `userEvent`.\n- Assert on rendered output and accessible state (text, role, `aria-*`), not on internals or a snapshot of private props.\n\nWhy not mock the component's internal fetch helper directly: it skips the real request/parse/error path, so the test passes even when that path is broken.\n\nOne note specific to \"on mount\": the mock must be installed *before* `render(...)`, since the effect fires immediately. With MSW that means the handler is registered in `beforeEach`/at setup, before render \u2014 not after.\n\nAlso: fix failures by fixing the test, not the component. If a test goes red because the component's markup is off, that's a separate `react-implementation` change, not something to patch while writing tests.",
|
|
1722
|
+
"outputSha256": "6eca76a415ce9ea4b57e08d3004ac37f72ec3f03abe483af928a357263a56cd0",
|
|
1723
|
+
"promptSha256": "5325d8d19467f3d9eb5306cd3194c9397fd2a21260f8604bbce0355b7aef71a0",
|
|
1724
|
+
"deterministic": [],
|
|
1725
|
+
"judge": {
|
|
1726
|
+
"verdict": "pass",
|
|
1727
|
+
"reason": "Answer names concrete boundary mechanism: 'register a handler for the exact URL/method the component hits on mount' with MSW, and states 'The real fetch/parse/error-handling code path in the component still runs.' It explicitly warns against mocking the internal fetch helper ('skips the real request/parse/error path'). No fail criterion holds."
|
|
1728
|
+
},
|
|
1729
|
+
"passed": true
|
|
1730
|
+
},
|
|
1731
|
+
{
|
|
1732
|
+
"output": "Mock it at the network/request boundary, not at the component's own data-fetching helper.\n\nIf the project already uses MSW (or an equivalent request-layer interceptor), add a handler for the specific endpoint and let the component's real fetch/parse/error path run \u2014 that exercises the code you actually care about. If the project instead mocks at the module level, match what the neighboring tests do: mock the API client module the component imports, not the component's internal wrapper around it.\n\nConcretely:\n\n- Find the convention first \u2014 read 1-2 existing component tests to see whether they use MSW handlers, a mocked API client, or a manual fetch stub, and follow that.\n- Put the mock before render, since the call fires on mount. With MSW that means the handler is registered in the test's server setup so `render(...)` triggers it; with a module mock it means the mock is declared at the top of the test file.\n- Then await the UI with `findBy*`/`waitFor` rather than a `setTimeout`, and assert on rendered output.\n- Do not mock the component's internal fetch wrapper \u2014 that skips the real request/parse/error-handling path and makes the test pass without verifying anything meaningful.\n\nA quick sketch with MSW:\n\n```tsx\nserver.use(\n http.get(\"/api/user\", () => HttpResponse.json({ name: \"Ada\" }))\n);\nrender(<UserCard />);\nexpect(await screen.findByText(/ada/i)).toBeInTheDocument();\n```\n\nIf the project has no request-boundary mocking set up, suggest adding MSW rather than reaching for a deeper internal mock.",
|
|
1733
|
+
"outputSha256": "f5a54244dd6f265887f3c7fa7bacef85ebdba4e62014dc63f7750f6b89ba2935",
|
|
1734
|
+
"promptSha256": "5325d8d19467f3d9eb5306cd3194c9397fd2a21260f8604bbce0355b7aef71a0",
|
|
1735
|
+
"deterministic": [],
|
|
1736
|
+
"judge": {
|
|
1737
|
+
"verdict": "fail",
|
|
1738
|
+
"reason": "Names MSW boundary handler (http.get('/api/user')) and explains it exercises the real fetch/parse/error path, satisfying pass 1-2. But it also recommends module-level mocking: 'mock the API client module the component imports' and 'with a module mock it means the mock is declared at the top of the test file' \u2014 directly mocking the API client method, triggering fail criterion 1."
|
|
1739
|
+
},
|
|
1740
|
+
"passed": false
|
|
1741
|
+
}
|
|
1742
|
+
]
|
|
1743
|
+
}
|
|
1744
|
+
],
|
|
1745
|
+
"verdict": "pass",
|
|
1746
|
+
"scope": "bundled",
|
|
1747
|
+
"skillDigest": "052484aef158301757af2501550b5d7cf942f1c5550f78a68c55ffab756129e4",
|
|
1748
|
+
"catalogDigest": "4f4016d410837e394a27e5b247e38ef2f57a1ee0baba4436ce7d3d71e223333d",
|
|
1749
|
+
"judgePromptVersion": "2026-09-25.1",
|
|
1750
|
+
"runner": "deepseek",
|
|
1751
|
+
"model": "deepseek-chat",
|
|
1752
|
+
"runnerPromptVersion": "2026-09-25.1",
|
|
1753
|
+
"recordedAt": "2026-09-25T05:22:56.299Z",
|
|
1754
|
+
"judge": "deepseek",
|
|
1755
|
+
"judgeModel": "deepseek-chat"
|
|
1756
|
+
},
|
|
1757
|
+
{
|
|
1758
|
+
"schemaVersion": "1.0.0",
|
|
1759
|
+
"skillId": "react/react-upgrade-migration",
|
|
1760
|
+
"strictness": "high",
|
|
1761
|
+
"trials": 10,
|
|
1762
|
+
"triggerAccuracy": {
|
|
1763
|
+
"truePositive": 6,
|
|
1764
|
+
"falsePositive": 0,
|
|
1765
|
+
"positives": 6,
|
|
1766
|
+
"negatives": 6
|
|
1767
|
+
},
|
|
1768
|
+
"evidence": "authored",
|
|
1769
|
+
"scenarios": [
|
|
1770
|
+
{
|
|
1771
|
+
"id": "trigger-positive-1",
|
|
1772
|
+
"kind": "trigger-positive",
|
|
1773
|
+
"prompt": "Upgrade this codebase from React 18 to React 19",
|
|
1774
|
+
"strictness": "high",
|
|
1775
|
+
"trials": 1,
|
|
1776
|
+
"passes": 1,
|
|
1777
|
+
"passRate": 1,
|
|
1778
|
+
"passAtK": 1,
|
|
1779
|
+
"grader": "trigger-rank-fork-family",
|
|
1780
|
+
"status": "ran",
|
|
1781
|
+
"deterministic": true
|
|
1782
|
+
},
|
|
1783
|
+
{
|
|
1784
|
+
"id": "trigger-positive-2",
|
|
1785
|
+
"kind": "trigger-positive",
|
|
1786
|
+
"prompt": "Remove all string refs from this project before the React upgrade",
|
|
1787
|
+
"strictness": "high",
|
|
1788
|
+
"trials": 1,
|
|
1789
|
+
"passes": 1,
|
|
1790
|
+
"passRate": 1,
|
|
1791
|
+
"passAtK": 1,
|
|
1792
|
+
"grader": "trigger-rank-fork-family",
|
|
1793
|
+
"status": "ran",
|
|
1794
|
+
"deterministic": true
|
|
1795
|
+
},
|
|
1796
|
+
{
|
|
1797
|
+
"id": "trigger-positive-3",
|
|
1798
|
+
"kind": "trigger-positive",
|
|
1799
|
+
"prompt": "Migrate ReactDOM.render to createRoot across the app",
|
|
1800
|
+
"strictness": "high",
|
|
1801
|
+
"trials": 1,
|
|
1802
|
+
"passes": 1,
|
|
1803
|
+
"passRate": 1,
|
|
1804
|
+
"passAtK": 1,
|
|
1805
|
+
"grader": "trigger-rank-fork-family",
|
|
1806
|
+
"status": "ran",
|
|
1807
|
+
"deterministic": true
|
|
1808
|
+
},
|
|
1809
|
+
{
|
|
1810
|
+
"id": "trigger-positive-4",
|
|
1811
|
+
"kind": "trigger-positive",
|
|
1812
|
+
"prompt": "Fix the propTypes deprecation warnings after upgrading React",
|
|
1813
|
+
"strictness": "high",
|
|
1814
|
+
"trials": 1,
|
|
1815
|
+
"passes": 1,
|
|
1816
|
+
"passRate": 1,
|
|
1817
|
+
"passAtK": 1,
|
|
1818
|
+
"grader": "trigger-rank-fork-family",
|
|
1819
|
+
"status": "ran",
|
|
1820
|
+
"deterministic": true
|
|
1821
|
+
},
|
|
1822
|
+
{
|
|
1823
|
+
"id": "trigger-positive-5",
|
|
1824
|
+
"kind": "trigger-positive",
|
|
1825
|
+
"prompt": "Run the official React codemods for the ref-as-prop change",
|
|
1826
|
+
"strictness": "high",
|
|
1827
|
+
"trials": 1,
|
|
1828
|
+
"passes": 1,
|
|
1829
|
+
"passRate": 1,
|
|
1830
|
+
"passAtK": 1,
|
|
1831
|
+
"grader": "trigger-rank-fork-family",
|
|
1832
|
+
"status": "ran",
|
|
1833
|
+
"deterministic": true
|
|
1834
|
+
},
|
|
1835
|
+
{
|
|
1836
|
+
"id": "trigger-positive-6",
|
|
1837
|
+
"kind": "trigger-positive",
|
|
1838
|
+
"prompt": "Update @types/react to match the new React major we just installed",
|
|
1839
|
+
"strictness": "high",
|
|
1840
|
+
"trials": 1,
|
|
1841
|
+
"passes": 1,
|
|
1842
|
+
"passRate": 1,
|
|
1843
|
+
"passAtK": 1,
|
|
1844
|
+
"grader": "trigger-rank-fork-family",
|
|
1845
|
+
"status": "ran",
|
|
1846
|
+
"deterministic": true
|
|
1847
|
+
},
|
|
1848
|
+
{
|
|
1849
|
+
"id": "trigger-negative-1",
|
|
1850
|
+
"kind": "trigger-negative",
|
|
1851
|
+
"prompt": "Add a settings page that lets a user update their email address",
|
|
1852
|
+
"strictness": "high",
|
|
1853
|
+
"trials": 1,
|
|
1854
|
+
"passes": 1,
|
|
1855
|
+
"passRate": 1,
|
|
1856
|
+
"passAtK": 1,
|
|
1857
|
+
"grader": "trigger-rank-fork-family",
|
|
1858
|
+
"status": "ran",
|
|
1859
|
+
"deterministic": true
|
|
1860
|
+
},
|
|
1861
|
+
{
|
|
1862
|
+
"id": "trigger-negative-2",
|
|
1863
|
+
"kind": "trigger-negative",
|
|
1864
|
+
"prompt": "Write unit tests for this date-formatting utility function",
|
|
1865
|
+
"strictness": "high",
|
|
1866
|
+
"trials": 1,
|
|
1867
|
+
"passes": 1,
|
|
1868
|
+
"passRate": 1,
|
|
1869
|
+
"passAtK": 1,
|
|
1870
|
+
"grader": "trigger-rank-fork-family",
|
|
1871
|
+
"status": "ran",
|
|
1872
|
+
"deterministic": true
|
|
1873
|
+
},
|
|
1874
|
+
{
|
|
1875
|
+
"id": "trigger-negative-3",
|
|
1876
|
+
"kind": "trigger-negative",
|
|
1877
|
+
"prompt": "Review this component diff for accessibility",
|
|
1878
|
+
"strictness": "high",
|
|
1879
|
+
"trials": 1,
|
|
1880
|
+
"passes": 1,
|
|
1881
|
+
"passRate": 1,
|
|
1882
|
+
"passAtK": 1,
|
|
1883
|
+
"grader": "trigger-rank-fork-family",
|
|
1884
|
+
"status": "ran",
|
|
1885
|
+
"deterministic": true
|
|
1886
|
+
},
|
|
1887
|
+
{
|
|
1888
|
+
"id": "trigger-negative-4",
|
|
1889
|
+
"kind": "trigger-negative",
|
|
1890
|
+
"prompt": "The build is failing with a plain TSX syntax error, unrelated to any version upgrade",
|
|
1891
|
+
"strictness": "high",
|
|
1892
|
+
"trials": 1,
|
|
1893
|
+
"passes": 1,
|
|
1894
|
+
"passRate": 1,
|
|
1895
|
+
"passAtK": 1,
|
|
1896
|
+
"grader": "trigger-rank-fork-family",
|
|
1897
|
+
"status": "ran",
|
|
1898
|
+
"deterministic": true
|
|
1899
|
+
},
|
|
1900
|
+
{
|
|
1901
|
+
"id": "trigger-negative-5",
|
|
1902
|
+
"kind": "trigger-negative",
|
|
1903
|
+
"prompt": "Bump the pinned Node.js version in package.json engines and the CI workflow",
|
|
1904
|
+
"strictness": "high",
|
|
1905
|
+
"trials": 1,
|
|
1906
|
+
"passes": 1,
|
|
1907
|
+
"passRate": 1,
|
|
1908
|
+
"passAtK": 1,
|
|
1909
|
+
"grader": "trigger-rank-fork-family",
|
|
1910
|
+
"status": "ran",
|
|
1911
|
+
"deterministic": true
|
|
1912
|
+
},
|
|
1913
|
+
{
|
|
1914
|
+
"id": "trigger-negative-6",
|
|
1915
|
+
"kind": "trigger-negative",
|
|
1916
|
+
"prompt": "Fix a re-render performance issue in this list component",
|
|
1917
|
+
"strictness": "high",
|
|
1918
|
+
"trials": 1,
|
|
1919
|
+
"passes": 1,
|
|
1920
|
+
"passRate": 1,
|
|
1921
|
+
"passAtK": 1,
|
|
1922
|
+
"grader": "trigger-rank-fork-family",
|
|
1923
|
+
"status": "ran",
|
|
1924
|
+
"deterministic": true
|
|
1925
|
+
},
|
|
1926
|
+
{
|
|
1927
|
+
"id": "read-guide-first",
|
|
1928
|
+
"kind": "behavior",
|
|
1929
|
+
"prompt": "We're upgrading from React 18 to React 19. Where should I start?",
|
|
1930
|
+
"strictness": "high",
|
|
1931
|
+
"trials": 10,
|
|
1932
|
+
"passes": 10,
|
|
1933
|
+
"passRate": 1,
|
|
1934
|
+
"passAtK": 1,
|
|
1935
|
+
"grader": "judge",
|
|
1936
|
+
"status": "ran",
|
|
1937
|
+
"trialRecords": [
|
|
1938
|
+
{
|
|
1939
|
+
"output": "Start by reading React 19's own upgrade guide before touching any code \u2014 don't rely on memory of the 18 migration, since the removed-API list differs per version jump.\n\nThen, in order:\n\n**1. Get the baseline.** Record current `react`, `react-dom`, `@types/react`, `@types/react-dom` versions from `package.json`. Grep the codebase for the APIs React 19 removes, so you know the full scope up front rather than discovering it file by file:\n\n- String refs (`ref=\"myRef\"`) \u2014 replace with `useRef`/`createRef` or the ref-as-prop pattern\n- Legacy context (`contextTypes`/`getChildContext`) \u2014 replace with `createContext`/`useContext`\n- `propTypes` on function components \u2014 no runtime effect in 19; convert to TS prop types or an explicit runtime check\n- `defaultProps` on function components \u2014 use default parameter values instead\n- `ReactDOM.render` / `ReactDOM.hydrate` \u2014 `createRoot(container).render(...)` / `hydrateRoot(...)` from `react-dom/client`\n- `ReactDOM.unmountComponentAtNode`, `ReactDOM.findDOMNode` \u2014 fully removed\n- `act` \u2014 import from `react` (check your RTL version first)\n\n**2. Run the official codemods.** Use `react-codemod` / `types-react-codemod` for the mechanical rewrites before hand-editing \u2014 review the diff, then hand-fix only what the codemod can't express (usually the propTypes\u2192types work and app-specific render wrappers).\n\n**3. Align types.** Bump `@types/react`/`@types/react-dom` to match the new major and resolve those errors *first*; a version mismatch produces type noise unrelated to the real migration.\n\n**4. Stage the rollout.** The React version bump itself is atomic \u2014 you can't run two majors side by side. What you *can* slice is the prep work (legacy removal, codemods, type fixes) directory by directory, keeping type-check/lint/test/build green for each slice. Then the dependency bump lands as one small, low-risk commit on an already-compatible tree.\n\n**5. Identify peer-dependency blockers early.** If a library is pinned to React 18 and has no 19-compatible release, that's a blocker \u2014 report it (wait, replace, or patch), don't paper over it with a resolution override.\n\n**6. Verify.** Type-check, lint (with `react-hooks`), test, build \u2014 full chain green, no file mixing old and new forms of the same API, and the diff scoped to just the migration.",
|
|
1940
|
+
"outputSha256": "19fb901fc863e11d50602927b9008bfcff2b9ecf5899c4da3450113e3dd5d239",
|
|
1941
|
+
"promptSha256": "8b4a8ec9cbb76bdff87b717b8247425235c7a8c72917d59c705d9d1f9b8be80d",
|
|
1942
|
+
"deterministic": [],
|
|
1943
|
+
"judge": {
|
|
1944
|
+
"verdict": "pass",
|
|
1945
|
+
"reason": "Opens with reading React 19's own upgrade guide before touching code, explicitly warning against relying on memory of the 18 migration (satisfies criterion 1, avoids fail criteria 1 and 2). Criterion 2 met concretely: greps for removed APIs (string refs, legacy context, propTypes, defaultProps, ReactDOM.render, findDOMNode, act) and runs react-codemod/types-react-codemod before hand-editing."
|
|
1946
|
+
},
|
|
1947
|
+
"passed": true
|
|
1948
|
+
},
|
|
1949
|
+
{
|
|
1950
|
+
"output": "Start by establishing the facts, not by editing code.\n\n1. Read `package.json` for the current `react`, `react-dom`, `@types/react`, `@types/react-dom` versions, and read the official React 18\u219219 upgrade guide before touching anything. Don't rely on memory of a different version jump \u2014 the removed-API list differs per major.\n\n2. Inventory the codebase for the APIs React 19 removed or changed, so you know the scope up front:\n - String refs (`ref=\"myRef\"`) \u2192 `useRef`/`createRef` or ref-as-prop. Removed outright.\n - Legacy context (`contextTypes`/`getChildContext`) \u2192 `createContext`/`useContext`. Removed.\n - `propTypes`/`defaultProps` on function components \u2192 `propTypes` has no runtime effect; convert to TS types or an explicit runtime check. Replace `defaultProps` with default parameter values (`function C({ x = 1 }: Props)`).\n - `ReactDOM.render`/`ReactDOM.hydrate` \u2192 `createRoot(...).render(...)` / `hydrateRoot(...)` from `react-dom/client`. Check every entry point and test helper that boots a root.\n - `ReactDOM.unmountComponentAtNode` and `ReactDOM.findDOMNode` \u2014 fully removed, not just deprecated.\n - `react-dom/test-utils`' `act` \u2192 import `act` from `react`; verify your React Testing Library version's compatibility before changing the import broadly.\n\n3. Then run the official codemods (`react-codemod`, `types-react-codemod`, e.g. the ref-as-prop and StrictMode transforms) for the mechanical rewrites, review the diff, and hand-fix only what they can't express \u2014 typically the `propTypes`\u2192TS-type conversion and any app-specific wrapper around `ReactDOM.render`.\n\n4. Fix `@types/react`/`@types/react-dom` to match the new major before chasing other type errors \u2014 a mismatched types package produces noise unrelated to the real migration.\n\nOn sequencing: the React version bump itself is one atomic change \u2014 you can't run two React majors in one tree. What you can slice by directory/feature is the preparation work (remove legacy APIs, run codemods, fix types), keeping each slice green on type-check, lint, test, build. Then the dependency bump lands as a single low-risk commit with the tree already compatible, instead of one large flip that leaves everything red.\n\nTwo things to watch:\n- Before starting, identify libraries still pinned to React 18. An incompatible peer dependency is a blocker, not something to paper over with a resolution override \u2014 that just converts an install-time failure into a runtime bug. Surface it and let the author decide (wait, replace, or patch).\n- Don't leave the old and new form of the same API mixed in one file without flagging it.\n\nVerify with the full chain (type-check, lint with the `react-hooks` plugin, test, build) and confirm `git status` shows only migration-scoped changes \u2014 no unrelated refactor bundled in.",
|
|
1951
|
+
"outputSha256": "ca1f3272339628809678d406222a92db411ddd03470f2a179a12cf95ea4a12e4",
|
|
1952
|
+
"promptSha256": "8b4a8ec9cbb76bdff87b717b8247425235c7a8c72917d59c705d9d1f9b8be80d",
|
|
1953
|
+
"deterministic": [],
|
|
1954
|
+
"judge": {
|
|
1955
|
+
"verdict": "pass",
|
|
1956
|
+
"reason": "Step 1 explicitly says read the official React 18\u219219 upgrade guide before touching anything, and warns against relying on memory of a different version jump. Step 2 inventories removed/changed APIs (string refs, legacy context, propTypes/defaultProps, ReactDOM.render, findDOMNode, test-utils act), and step 3 names running official codemods (react-codemod, types-react-codemod). Both pass criteria concretely met."
|
|
1957
|
+
},
|
|
1958
|
+
"passed": true
|
|
1959
|
+
},
|
|
1960
|
+
{
|
|
1961
|
+
"output": "Start by establishing the starting point and the target before touching any code:\n\n1. **Pin the exact current versions.** Read `react`, `react-dom`, `@types/react`, and `@types/react-dom` out of `package.json`. \"React 18\" is not precise enough \u2014 the upgrade path differs slightly depending on whether you're on 18.2/18.3 and what types versions you're on.\n\n2. **Read the React 19 upgrade guide first.** Do not work from memory of the 18\u219219 migration or any other jump. The removed/changed API list is version-specific.\n\n3. **Inventory the codebase** for the APIs React 19 removes or changes, so you know the scope before you start rather than discovering it file-by-file:\n - String refs (`ref=\"myRef\"`) \u2014 removed, replace with `useRef`/`createRef` or the ref-as-prop pattern.\n - Legacy context (`contextTypes`/`getChildContext`) \u2014 removed, replace with `createContext`/`useContext`.\n - `propTypes` on function components \u2014 no runtime effect in 19; replace with TypeScript types or explicit runtime checks.\n - `defaultProps` on function components \u2014 removed; use default parameter values in the function signature.\n - `ReactDOM.render`/`ReactDOM.hydrate` \u2014 replace with `createRoot(...).render(...)` / `hydrateRoot(...)` from `react-dom/client`. This touches the app entry point *and* every test helper that bootstraps a root.\n - `act` \u2014 import from `react` instead of `react-dom/test-utils`, but check your React Testing Library version's compatibility first.\n - Also `unmountComponentAtNode` and `findDOMNode`, both fully removed in 19.\n\nThen the execution order:\n\n4. **Run the official codemods** (`react-codemod` / `types-react-codemod`) for the mechanical rewrites \u2014 especially the ref-as-prop transform \u2014 and review the diff before committing. Hand-fix only what a codemod can't express, which is usually the `propTypes` \u2192 TypeScript conversion and any app-specific wrapper around `ReactDOM.render`.\n\n5. **Bump `@types/react` and `@types/react-dom` to match the new major.** A types-package mismatch produces type errors unrelated to the actual migration, so resolve that before chasing anything else.\n\n6. **Stage the rollout.** You can't partially roll out the runtime version \u2014 `package.json` pins one React major for the whole tree. What you *can* slice by directory/feature is the preparation work: legacy-API removal, codemods, and type fixes, each slice landing green on type-check, lint, test, and build. Then the dependency bump itself is a small, low-risk commit on an already-compatible tree instead of one big flip that leaves everything red.\n\n7. **Surface peer-dependency blockers early.** Any library pinned to React 18 is a hard blocker at that dependency, not in your app code. Report it as a blocker \u2014 don't paper over it with a resolution/override, which converts an install-time error into a runtime bug.\n\n8. **Verify** the full chain: type-check, lint (with the `react-hooks` plugin), test, build \u2014 and confirm the diff contains only the migration, no unrelated refactor.\n\nThe key thing to avoid: running through a mental checklist from a previous major and hand-editing dozens of call sites a codemod would handle. Read 19's guide, inventory first, codemod the mechanical parts, stage the prep work so the final version bump is trivial.",
|
|
1962
|
+
"outputSha256": "ba774f9b2416a6980ea56ab82cc1f7e231bce549d6fa91e322178b3f052f3cf1",
|
|
1963
|
+
"promptSha256": "8b4a8ec9cbb76bdff87b717b8247425235c7a8c72917d59c705d9d1f9b8be80d",
|
|
1964
|
+
"deterministic": [],
|
|
1965
|
+
"judge": {
|
|
1966
|
+
"verdict": "pass",
|
|
1967
|
+
"reason": "Step 2 explicitly says 'Read the React 19 upgrade guide first' and warns against working from memory of the 18\u219219 migration. Step 3 inventories removed/changed APIs (string refs, legacy context, defaultProps, ReactDOM.render, act, findDOMNode), and step 4 names running the official codemods (react-codemod / types-react-codemod). Both pass criteria concretely met; no fail criteria hold."
|
|
1968
|
+
},
|
|
1969
|
+
"passed": true
|
|
1970
|
+
},
|
|
1971
|
+
{
|
|
1972
|
+
"output": "Start by reading React 19's own upgrade guide before touching any code \u2014 the removed/changed APIs differ per version jump, so don't work from memory of an earlier migration.\n\nThen work in this order:\n\n**1. Establish the baseline**\nRead current `react`/`react-dom`/`@types/react`/`@types/react-dom` versions from `package.json`. Then grep the whole codebase for the APIs React 19 removes or changes, so you know the scope up front rather than discovering it file by file:\n\n- String refs (`ref=\"myRef\"`) \u2014 removed, not just deprecated\n- Legacy context (`contextTypes` / `getChildContext`) \u2014 removed\n- `propTypes` and `defaultProps` on function components \u2014 `propTypes` no longer does anything at runtime; `defaultProps` is removed for function components\n- `ReactDOM.render` / `ReactDOM.hydrate` \u2014 replaced by `createRoot().render()` / `hydrateRoot()` from `react-dom/client`\n- `ReactDOM.unmountComponentAtNode` and `ReactDOM.findDOMNode` \u2014 fully removed in 19\n- `act` imported from `react-dom/test-utils` \u2014 now comes from `react` (check your React Testing Library version before changing this broadly)\n\n**2. Replace the legacy APIs**\n- String refs \u2192 `useRef`/`createRef`, or the React 19 ref-as-prop pattern for a component receiving a ref from its parent.\n- Legacy context \u2192 `createContext`/`useContext`.\n- `propTypes` \u2192 TypeScript types if the project is TS (preferred), otherwise an explicit runtime check; `defaultProps` \u2192 default parameter in the function signature.\n- `ReactDOM.render`/`hydrate` \u2192 `createRoot`/`hydrateRoot` in the app entry point and in every test helper that boots a root.\n\n**3. Codemod first, hand-fix the rest**\nRun the official `react-codemod` / `types-react-codemod` transforms for the mechanical rewrites (e.g. ref-as-prop) and review the diff \u2014 don't hand-edit 40 call sites a codemod already covers. Hand-fix only what it can't express, typically the `propTypes` \u2192 types conversion and any project-specific wrapper around `ReactDOM.render`.\n\n**4. Fix the types package before chasing type errors**\nBump `@types/react`/`@types/react-dom` to versions matching the new major. A mismatched types package produces type errors unrelated to the actual runtime migration \u2014 resolve that first or you'll waste time on phantom errors.\n\n**5. Stage the rollout**\nThe React version bump itself is atomic \u2014 you can't pin two majors at once. What you *can* slice is the code preparation: remove legacy APIs, run codemods, fix types, slice by slice, each slice green (type-check, lint, test, build). Then the dependency bump lands as one small, low-risk commit against an already-compatible tree instead of a big red flip.\n\n**6. Surface blockers, don't hide them**\nIdentify libraries still pinned to the React 18 major before you start. If one is incompatible with 19, that's a blocker at the dependency \u2014 report it (wait, replace, or patch) rather than papering over it with a resolution override that converts an install-time error into a runtime bug.\n\n**7. Verify**\nFull chain green: type-check, lint (with the `react-hooks` plugin), test, build. Confirm no file mixes the old and new form of the same migrated API, types match the target major, and `git status` shows only migration-scoped changes \u2014 no incidental refactor bundled in.\n\nFastest useful first move: read the React 19 upgrade guide, then run the grep inventory so you know exactly which of the above applies to your tree.",
|
|
1973
|
+
"outputSha256": "29409f3c8c7ccb619aaacb3f14a4eb28e89b6538fdbf83543a8d791427830cdc",
|
|
1974
|
+
"promptSha256": "8b4a8ec9cbb76bdff87b717b8247425235c7a8c72917d59c705d9d1f9b8be80d",
|
|
1975
|
+
"deterministic": [],
|
|
1976
|
+
"judge": {
|
|
1977
|
+
"verdict": "pass",
|
|
1978
|
+
"reason": "Opens with 'Start by reading React 19's own upgrade guide before touching any code' and warns against working from memory of an earlier migration (crit 1). Names concrete mechanism: grep inventory of removed/changed APIs (string refs, legacy context, propTypes, ReactDOM.render, etc.) and running official react-codemod/types-react-codemod transforms (crit 2). No fail criteria triggered."
|
|
1979
|
+
},
|
|
1980
|
+
"passed": true
|
|
1981
|
+
},
|
|
1982
|
+
{
|
|
1983
|
+
"output": "Start by pinning down the exact starting point and target, then build an inventory of what needs to change before you touch any code.\n\n**Step 1 \u2014 Establish the starting point and target.**\nRead the current `react`, `react-dom`, `@types/react`, and `@types/react-dom` versions from `package.json` to confirm the target jump is 18 \u2192 19. Then read the official React 19 upgrade guide (or the release notes if vendored) \u2014 do not work from memory of a previous migration, since the removed/changed API list differs per version jump.\n\n**Step 2 \u2014 Inventory the removed/changed APIs.**\nGrep the codebase for each of these before starting, so you know the scope up front rather than discovering it file-by-file:\n\n- **String refs** (`ref=\"myRef\"`) \u2014 removed outright; replace with `useRef`/`createRef` or the React 19 ref-as-prop pattern.\n- **Legacy context** (`contextTypes`/`getChildContext`) \u2014 removed; replace with `createContext`/`useContext`.\n- **`propTypes`/`defaultProps` on function components** \u2014 `propTypes` no longer has runtime effect; convert validation to TypeScript types (preferred if the project is TS) or an explicit runtime check. Replace `defaultProps` with default parameter values in the function signature.\n- **`ReactDOM.render`/`ReactDOM.hydrate`** \u2014 replace with `createRoot(...).render(...)` and `hydrateRoot(...)` from `react-dom/client`. This usually centers on the app entry point but also hits every test helper that bootstraps a root.\n- **`act`** \u2014 import from `react` (or the deprecated `react-dom/test-utils` re-export if your React Testing Library version still expects it \u2014 check RTL compatibility before changing the import broadly).\n- **Other removals in this jump** \u2014 notably `ReactDOM.unmountComponentAtNode` and `ReactDOM.findDOMNode`, both fully removed in React 19. Treat the list above as the starting checklist, not necessarily exhaustive.\n\n**Step 3 \u2014 Codemods first, hand-fixes second.**\nRun the official `react-codemod`/`types-react-codemod` transforms for the mechanical rewrites (e.g. ref-as-prop, StrictMode-related) and review their diff before committing. Hand-fix only what a codemod can't express \u2014 typically the `propTypes` \u2192 TypeScript conversion and any app-specific wrapper around `ReactDOM.render`. Then bump `@types/react`/`@types/react-dom` to match the new major; a mismatched types package produces type errors unrelated to the actual migration, so resolve that before chasing anything else.\n\n**Step 4 \u2014 Stage the rollout.**\nThe React/react-dom version bump itself is atomic \u2014 `package.json` can't pin two majors for different directories, so there's no per-directory rollout of the runtime. What you *can* slice by directory or feature is the preparation work: removing legacy APIs, running codemods, and fixing type errors slice-by-slice, each slice green (type-check, lint, test, build). When the dependency bump finally lands, the tree is already compatible and the bump is a single low-risk commit instead of a big flip that leaves everything red.\n\nAlso identify libraries pinned to the old React major *before* you start. An incompatible dependency blocks the upgrade at that dependency, not in your app code \u2014 surface it as a blocker rather than forcing it with a version override that hides a real incompatibility.\n\n**Step 5 \u2014 Verify.**\nRun the full chain \u2014 type-check, lint (with the `react-hooks` plugin), test, build \u2014 per your `agentProfile.buildCommands`. Only report done when every inventoried removed API is migrated or explicitly out of scope with a reason, no file mixes old and new forms of the same API, the types packages match the new major, the build chain is green, and the diff contains nothing outside the migration's scope.",
|
|
1984
|
+
"outputSha256": "d25c85fc7305430de9b85e18d06831f84a3a550415d8461dbe9a7de8cc473175",
|
|
1985
|
+
"promptSha256": "8b4a8ec9cbb76bdff87b717b8247425235c7a8c72917d59c705d9d1f9b8be80d",
|
|
1986
|
+
"deterministic": [],
|
|
1987
|
+
"judge": {
|
|
1988
|
+
"verdict": "pass",
|
|
1989
|
+
"reason": "Step 1 names reading the official React 19 upgrade guide as the first step before touching code, explicitly warning against memory of a previous migration. Step 2 inventories removed/changed APIs (string refs, legacy context, propTypes, ReactDOM.render, act, findDOMNode) and Step 3 names running official react-codemod/types-react-codemod transforms. Both pass criteria hold; no fail criteria."
|
|
1990
|
+
},
|
|
1991
|
+
"passed": true
|
|
1992
|
+
},
|
|
1993
|
+
{
|
|
1994
|
+
"output": "Start by establishing exactly what you're moving from and to, then read React 19's own upgrade guide before touching any code \u2014 don't act on a remembered checklist from a different version jump, because the removed/changed API list differs per release.\n\nThen inventory the codebase (grep) for the APIs React 19 removes or changes, so you know the scope up front:\n\n- **String refs** (`ref=\"myRef\"`) \u2014 removed. Replace with `useRef`/`createRef`, or React 19's ref-as-prop pattern for a component receiving a ref from its parent.\n- **Legacy context** (`contextTypes`/`getChildContext`) \u2014 removed. Replace with `createContext`/`useContext`.\n- **`propTypes`/`defaultProps` on function components** \u2014 `propTypes` no longer does runtime validation in React 19; `defaultProps` on function components is gone. Convert validation to TypeScript types (preferred) or an explicit runtime check, and move defaults into the function signature (`function C({ x = 1 }: Props)`).\n- **`ReactDOM.render`/`ReactDOM.hydrate`** \u2014 replaced by `createRoot(container).render(...)` and `hydrateRoot(container, ...)` from `react-dom/client`. This touches your entry point plus any test helper that bootstraps a root.\n- **`act` from `react-dom/test-utils`** \u2014 import it from `react` instead, but check your React Testing Library version first, since an older RTL may still expect the old path.\n- **Other removals specific to 19** \u2014 `ReactDOM.unmountComponentAtNode` and `ReactDOM.findDOMNode` are fully removed, not just deprecated. Confirm against the guide rather than assuming this list is exhaustive.\n\nOnce you have the inventory, run the official codemods (`react-codemod`/`types-react-codemod`, e.g. the ref-as-prop transform) for the mechanical rewrites and review their diff, then hand-fix only what the codemod can't express \u2014 typically the `propTypes` \u2192 TypeScript conversion and any app-specific wrapper around `ReactDOM.render`. Update `@types/react`/`@types/react-dom` to the versions matching React 19 early; a mismatched types package throws type errors unrelated to the real migration and will send you chasing ghosts.\n\nOne thing to be clear about on rollout: the React/react-dom dependency bump itself can't be sliced per directory \u2014 `package.json` can't pin two React majors at once. What you *can* slice is all the code preparation that precedes it: remove legacy APIs, run codemods, fix type errors directory-by-directory or feature-by-feature, keeping each slice green (type-check, lint, test, build). Then the version bump lands as a single low-risk commit because the whole tree is already compatible, instead of a big flip that leaves everything red.\n\nTwo guardrails that matter:\n\n- Don't hand-edit 40 call sites when a maintained codemod covers the pattern \u2014 run the codemod, then fix the remainder by hand.\n- Identify React-18-pinned peer dependencies up front. If a library isn't compatible with 19, that's where the upgrade is blocked, and you should surface it as a blocker rather than papering over it with a resolution/version override that hides a real incompatibility until it blows up at runtime.\n\nReport it done only when: every inventoried removed API is migrated or explicitly out of scope with a reason; no file mixes old and new forms of the same API; `@types/react`/`@types/react-dom` match the target major; the full chain (type-check, lint, test, build) is green; and `git status` shows only the migration's intended scope.",
|
|
1995
|
+
"outputSha256": "13a95c3b06827feab599c4b4b46735c648a9fbe1cc8af0240d1c6c1e7fde6cba",
|
|
1996
|
+
"promptSha256": "8b4a8ec9cbb76bdff87b717b8247425235c7a8c72917d59c705d9d1f9b8be80d",
|
|
1997
|
+
"deterministic": [],
|
|
1998
|
+
"judge": {
|
|
1999
|
+
"verdict": "pass",
|
|
2000
|
+
"reason": "Answer opens with reading React 19's own upgrade guide before touching code, explicitly warning against a remembered checklist from a different version jump (satisfies PC1, avoids FC2). It names concrete mechanisms: grep inventory of removed/changed APIs (string refs, legacy context, propTypes/defaultProps, ReactDOM.render, act, findDOMNode) and running official codemods (react-codemod/types-react-codemod) with diff review (satisfies PC2). No bump-and-fix-first recommendation (avoids FC1)."
|
|
2001
|
+
},
|
|
2002
|
+
"passed": true
|
|
2003
|
+
},
|
|
2004
|
+
{
|
|
2005
|
+
"output": "Start with inventory, not edits. Read the React 19 upgrade guide first (don't work from memory of an earlier migration), confirm your current `react`/`react-dom`/`@types/react`/`@types/react-dom` versions in `package.json`, and grep the codebase for the APIs React 19 removes so you know the scope up front. That list is:\n\n- String refs (`ref=\"myRef\"`) \u2014 removed, replace with `useRef`/`createRef` or the ref-as-prop pattern.\n- Legacy context (`contextTypes`/`getChildContext`) \u2014 removed, replace with `createContext`/`useContext`.\n- `propTypes` on function components \u2014 no longer has runtime effect; move validation to TypeScript types or an explicit runtime check.\n- `defaultProps` on function components \u2014 removed; use default parameter values instead.\n- `ReactDOM.render` / `ReactDOM.hydrate` \u2014 replace with `createRoot(container).render(...)` / `hydrateRoot(container, ...)` from `react-dom/client`, in the entry point and any test helpers that bootstrap a root.\n- `react-dom/test-utils`' `act` \u2014 import from `react` instead (check your React Testing Library version's expectation before changing broadly).\n- Also check for `ReactDOM.unmountComponentAtNode` and `ReactDOM.findDOMNode`, both fully removed in 19.\n\nThen apply the official `react-codemod`/`types-react-codemod` transforms for the mechanical rewrites and review the diff \u2014 don't hand-edit dozens of call sites a codemod already covers. Hand-fix only what it can't express, typically the `propTypes`-to-TS-type conversion and any app-specific wrapper around `ReactDOM.render`. Bump `@types/react`/`@types/react-dom` to match the new major and resolve those type errors before chasing unrelated ones, since a mismatched types package produces noise.\n\nOn sequencing: the react/react-dom bump is one atomic dependency change \u2014 you can't pin two React majors in one `package.json`, so there's no per-directory rollout of the runtime version. What you *can* slice is the prep work: remove legacy APIs, run codemods, fix types directory-by-directory or feature-by-feature, keeping each slice green (type-check, lint, test, build) so the final dependency bump is a single low-risk commit instead of a big red flip.\n\nOne blocker to surface early: check your libraries' peer deps against React 19 before starting. If something isn't compatible, report it rather than forcing a resolution override that hides a real incompatibility until runtime.\n\nVerify with the full chain (type-check, lint with the `react-hooks` plugin, test, build), confirm no file mixes old and new forms of the same migrated API, and keep the diff scoped to the migration \u2014 no unrelated refactors bundled in.",
|
|
2006
|
+
"outputSha256": "e3aabcd563b6811121c31c60071e69550f86e14699a2271622c377d66e3c18db",
|
|
2007
|
+
"promptSha256": "8b4a8ec9cbb76bdff87b717b8247425235c7a8c72917d59c705d9d1f9b8be80d",
|
|
2008
|
+
"deterministic": [],
|
|
2009
|
+
"judge": {
|
|
2010
|
+
"verdict": "pass",
|
|
2011
|
+
"reason": "Criterion 1: 'Read the React 19 upgrade guide first (don't work from memory of an earlier migration)' \u2014 names the official guide as the concrete first step before edits. Criterion 2: inventories removed APIs (string refs, legacy context, defaultProps, ReactDOM.render, etc.) and names official codemods 'react-codemod/types-react-codemod'. No fail criteria hold."
|
|
2012
|
+
},
|
|
2013
|
+
"passed": true
|
|
2014
|
+
},
|
|
2015
|
+
{
|
|
2016
|
+
"output": "Start by establishing the starting point and target, not by editing code.\n\n1. Read the current versions of react, react-dom, @types/react, and @types/react-dom from package.json, and confirm the target is React 19.\n2. Read the actual React 18 -> 19 upgrade guide (react.dev's upgrade guide or the release notes) before touching anything. Don't rely on a mental model from an earlier migration \u2014 the removed/changed API list differs per version jump.\n3. Inventory where the codebase uses the things React 19 removes, so you know the scope up front. For 18 -> 19 that checklist is:\n - String refs (ref=\"x\") \u2014 removed; replace with useRef/createRef or ref-as-prop.\n - Legacy context (contextTypes/getChildContext) \u2014 removed; replace with createContext/useContext.\n - propTypes on function components \u2014 no longer has runtime effect; replace with TS types or an explicit runtime check.\n - defaultProps on function components \u2014 removed; use a default parameter value in the signature.\n - ReactDOM.render / ReactDOM.hydrate \u2014 replace with createRoot(...).render(...) / hydrateRoot(...) from react-dom/client.\n - unmountComponentAtNode and findDOMNode \u2014 fully removed.\n - act \u2014 import from react rather than react-dom/test-utils (check your React Testing Library version compatibility first).\n\nThen, in order:\n\n- Run the official codemods (react-codemod / types-react-codemod) for the mechanical rewrites, review the diff, and hand-fix only what they can't express \u2014 typically the propTypes -> TypeScript conversion and any app-specific wrapper around ReactDOM.render.\n- Bump @types/react and @types/react-dom to the versions matching React 19. Do this early, before chasing other type errors, so a type-package mismatch doesn't masquerade as a migration bug.\n- Slice the code-preparation work (legacy API removal, codemods, type fixes) by directory/feature with each slice green, and identify libraries still pinned to the React 18 peer dependency up front. The runtime version bump itself can't be staged per-directory \u2014 package.json pins one React major for the whole tree \u2014 so the goal is to get the tree fully compatible first, then land the dependency bump as a single low-risk commit.\n\nTwo things to avoid: don't leave old and new forms of the same API mixed in the same file without flagging it, and don't force a react-18-only library to work with a version override/resolution \u2014 report that as a blocker instead.\n\nVerify with the full chain (type-check, lint with react-hooks, test, build) after the bump, and confirm the diff contains only migration-scoped changes.",
|
|
2017
|
+
"outputSha256": "94ec6f2d2e4f80c184b890fed1d184d5203456cbbee305a725e807d379c6e74f",
|
|
2018
|
+
"promptSha256": "8b4a8ec9cbb76bdff87b717b8247425235c7a8c72917d59c705d9d1f9b8be80d",
|
|
2019
|
+
"deterministic": [],
|
|
2020
|
+
"judge": {
|
|
2021
|
+
"verdict": "pass",
|
|
2022
|
+
"reason": "Step 2 names reading react.dev's React 18->19 upgrade guide/release notes before touching code, explicitly warning against relying on a prior migration's mental model. Step 3 inventories removed/changed APIs (string refs, legacy context, propTypes, defaultProps, ReactDOM.render, findDOMNode, act) and the answer names running official codemods (react-codemod / types-react-codemod). Both pass criteria hold; no fail criteria."
|
|
2023
|
+
},
|
|
2024
|
+
"passed": true
|
|
2025
|
+
},
|
|
2026
|
+
{
|
|
2027
|
+
"output": "Start by reading the official React 19 upgrade guide before touching any code \u2014 don't work from memory of a previous migration, since the removed/changed API list differs per version jump. Then inventory the codebase so you know the scope up front rather than discovering it file by file.\n\nConcretely, the APIs to check for in an 18\u219219 move:\n\n- String refs (`ref=\"myRef\"`) \u2014 replace with `useRef`/`createRef` or the React 19 ref-as-prop pattern. These are removed, not just deprecated.\n- Legacy context (`contextTypes`/`getChildContext`) \u2014 replace with `createContext`/`useContext`.\n- `propTypes` on function components \u2014 no longer has runtime effect; convert to TypeScript types, or an explicit runtime check if you're not on TS.\n- `defaultProps` on function components \u2014 removed; replace with default parameter values.\n- `ReactDOM.render`/`ReactDOM.hydrate` \u2014 replace with `createRoot(...).render(...)` / `hydrateRoot(...)` from `react-dom/client`. Check test helpers too, since they often bootstrap a root as well.\n- `ReactDOM.unmountComponentAtNode` and `ReactDOM.findDOMNode` \u2014 fully removed.\n- `act` from `react-dom/test-utils` \u2014 import from `react` instead, but check your React Testing Library version first, since an older RTL may still expect the old import.\n\nThen run the official codemods (`react-codemod` / `types-react-codemod`) for the mechanical rewrites, review their diff, and hand-fix only what they can't express \u2014 usually the `propTypes`\u2192type conversion and any app-specific wrapper around `ReactDOM.render`. Bump `@types/react` and `@types/react-dom` to match React 19 early; a types-package mismatch produces noise that masks real errors.\n\nOn staging: the React version bump itself is one atomic dependency change \u2014 you can't run two React majors side by side. What you can slice by directory or feature is the prep work (legacy API removal, codemods, type fixes), keeping each slice green on type-check, lint, test, and build. That way the dependency bump lands as one small, low-risk commit against an already-compatible tree instead of a big flip that leaves everything red.\n\nOne thing to surface early: check your third-party libraries for React 19 peer-dependency support. If something is still pinned to 18, that's a real blocker \u2014 report it rather than forcing it through with a resolution override. Last, always run the official upgrade guide's checklist against your specific inventory; the list above is the 18\u219219 set, not a universal one.",
|
|
2028
|
+
"outputSha256": "0f2a8236fe43cc95384a80d4ded3602da76dc1ad89bbf823de50188a76c54783",
|
|
2029
|
+
"promptSha256": "8b4a8ec9cbb76bdff87b717b8247425235c7a8c72917d59c705d9d1f9b8be80d",
|
|
2030
|
+
"deterministic": [],
|
|
2031
|
+
"judge": {
|
|
2032
|
+
"verdict": "pass",
|
|
2033
|
+
"reason": "Opens with reading the official React 19 upgrade guide before touching code, explicitly warning against memory of a previous migration. Names concrete mechanisms: inventorying removed/changed APIs (string refs, legacy context, propTypes, defaultProps, ReactDOM.render, etc.) and running official codemods (react-codemod / types-react-codemod). No fail criteria hold."
|
|
2034
|
+
},
|
|
2035
|
+
"passed": true
|
|
2036
|
+
},
|
|
2037
|
+
{
|
|
2038
|
+
"output": "Start by reading the actual React 19 upgrade guide for the 18\u219219 jump before touching any code \u2014 don't rely on how an earlier major's migration went, since the removed/changed API list is specific to each version range. Then:\n\n1. **Establish the starting point.** Read your `react`, `react-dom`, `@types/react`, and `@types/react-dom` versions from `package.json`, confirm the target is React 19, and open the official 18\u219219 upgrade guide.\n\n2. **Inventory the legacy APIs before editing anything.** Grep the codebase for the APIs React 19 removed or changed so you know the scope up front rather than discovering it file by file:\n - String refs (`ref=\"myRef\"`) \u2192 `useRef`/`createRef` or the ref-as-prop pattern\n - Legacy context (`contextTypes`/`getChildContext`) \u2192 `createContext`/`useContext`\n - `propTypes` on function components (no longer has runtime effect) \u2192 TypeScript types or explicit runtime checks\n - `defaultProps` on function components (removed) \u2192 default parameter values in the signature\n - `ReactDOM.render`/`ReactDOM.hydrate` \u2192 `createRoot(...).render(...)` / `hydrateRoot(...)` from `react-dom/client`, including in test helpers\n - `react-dom/test-utils`' `act` \u2192 import from `react` (check your RTL version first)\n - Other removed APIs the guide lists for 18\u219219 (e.g. `ReactDOM.unmountComponentAtNode`, `ReactDOM.findDOMNode`) \u2014 the list above isn't exhaustive\n\n3. **Run the official codemods, then hand-fix the rest.** Use `react-codemod`/`types-react-codemod` for the mechanical rewrites and review the diff; hand-fix what the codemod can't express (typically the `propTypes`\u2192type conversion and app-specific wrappers around `ReactDOM.render`). Bump `@types/react`/`@types/react-dom` to match React 19 as part of this \u2014 mismatched types generate noise errors that mask the real ones.\n\n4. **Stage it.** React/react-dom can't be pinned to two majors at once, so the version bump itself is one atomic commit. What you can slice by directory/feature is the preparation work (legacy API removal, codemods, type fixes), keeping each slice green on type-check, lint, test, and build. When the bump then lands, the tree is already compatible and the flip is low-risk. Identify peer libraries still pinned to React 18 before starting and surface an incompatible one as a blocker \u2014 do not paper over it with a resolution override.\n\n5. **Verify and report.** Run type-check, lint (with the `react-hooks` plugin), test, and build. Don't report done until: every inventoried removed API is migrated or explicitly scoped out with a reason; no file mixes old and new forms of the same API; the types packages match React 19; the full chain is green; and the diff contains only the migration's intended scope.\n\nA practical first move: do step 1 and step 2 today \u2014 a grep-based inventory of the six APIs above. That tells you whether this is a day's work or a multi-slice project before you change a single line.",
|
|
2039
|
+
"outputSha256": "cef785697eca8d68ead4a653df7763ab517c2e85748c2d228e96c82811fe2046",
|
|
2040
|
+
"promptSha256": "8b4a8ec9cbb76bdff87b717b8247425235c7a8c72917d59c705d9d1f9b8be80d",
|
|
2041
|
+
"deterministic": [],
|
|
2042
|
+
"judge": {
|
|
2043
|
+
"verdict": "pass",
|
|
2044
|
+
"reason": "Step 1 explicitly says read the official 18\u219219 upgrade guide before touching code and warns against relying on an earlier major's migration. Step 2 inventories removed/changed APIs (string refs, legacy context, propTypes, defaultProps, ReactDOM.render, test-utils act) via grep; step 3 names running react-codemod/types-react-codemod. Both pass criteria concretely met."
|
|
2045
|
+
},
|
|
2046
|
+
"passed": true
|
|
2047
|
+
}
|
|
2048
|
+
]
|
|
2049
|
+
},
|
|
2050
|
+
{
|
|
2051
|
+
"id": "no-forced-resolution",
|
|
2052
|
+
"kind": "behavior",
|
|
2053
|
+
"prompt": "One of our dependencies isn't compatible with React 19 yet. Should I just force the version with a package.json resolution override so the install succeeds?",
|
|
2054
|
+
"strictness": "high",
|
|
2055
|
+
"trials": 10,
|
|
2056
|
+
"passes": 10,
|
|
2057
|
+
"passRate": 1,
|
|
2058
|
+
"passAtK": 1,
|
|
2059
|
+
"grader": "judge",
|
|
2060
|
+
"status": "ran",
|
|
2061
|
+
"trialRecords": [
|
|
2062
|
+
{
|
|
2063
|
+
"output": "No \u2014 don't force it with a resolution/override.\n\nA resolution override doesn't make the library compatible with React 19; it just silences the install-time complaint about incompatibility. The library still calls whatever React API it was built against, so if React actually removed or changed that API, you'll trade a clear, immediate \"this dependency isn't compatible yet\" signal for a delayed runtime bug that's much harder to trace back to the override.\n\nWhat to do instead:\n\n1. **Report it as a blocker**, not a workaround. Identify the specific library, the version range it declares, and why it's incompatible (which React 18 API it depends on). That's a decision the maintainer needs to make consciously, not something to paper over.\n\n2. **Pick a real path forward**, one of:\n - Check whether a newer version of the dependency already supports React 19 (or a prerelease/RC does).\n - Check the library's issue tracker \u2014 an official compat release may be imminent.\n - Find a maintained fork or an actively-maintained alternative library.\n - Pin the project to React 18 until the dependency catches up, if the upgrade isn't urgent.\n - As a last resort, patch the library itself (e.g. `patch-package`) with an explicit, reviewable fix \u2014 this is visible in the diff and documented, unlike a silent override.\n\n3. **Keep the rest of the migration moving in parallel.** The code-preparation work (removing string refs, legacy context, `propTypes`/`defaultProps` on function components, migrating `ReactDOM.render`/`hydrate` to `createRoot`/`hydrateRoot`, running the codemods, updating `@types/react`) can all proceed and be verified green on the old React major where the APIs still exist. That way, when the blocker resolves, the version bump itself is a single low-risk commit instead of a large flip.\n\nThe only case where an override is defensible is if you've confirmed the library's React 19 incompatibility is purely a stale peer-dependency declaration \u2014 i.e. it actually works fine at runtime under React 19 and the maintainer just hasn't bumped the range yet. Even then, document that reasoning explicitly in the PR and pin to it, rather than adding a silent resolution.",
|
|
2064
|
+
"outputSha256": "d4474520ac9733ec993c6b26cece487f833768cd1db4a356e67b3a8b65134fd0",
|
|
2065
|
+
"promptSha256": "7a5d6b22395eab1143f1cdf85953c11082ffee4f1761623ddbdf222c0f4de1de",
|
|
2066
|
+
"deterministic": [],
|
|
2067
|
+
"judge": {
|
|
2068
|
+
"verdict": "pass",
|
|
2069
|
+
"reason": "Opens with 'No \u2014 don't force it with a resolution/override' (crit.1). Names concrete alternatives: newer version/RC, fork/alternative library, pin to React 18, and patch via patch-package (crit.2). The override caveat is limited to a confirmed stale peer-dep declaration, not a stopgap for a real incompatibility, so fail criterion 1 does not hold."
|
|
2070
|
+
},
|
|
2071
|
+
"passed": true
|
|
2072
|
+
},
|
|
2073
|
+
{
|
|
2074
|
+
"output": "No. Don't force it with a resolution/override.\n\nA library that declares a peer dependency on React 18 and hasn't published React 19 support is telling you something real: it may be calling an API React 19 removed (string refs, legacy context, `ReactDOM.render`, `findDOMNode`, function-component `defaultProps`/`propTypes`, `test-utils`' `act` re-export, etc.), or it may rely on rendering behavior that changed. An override silences the install-time signal and converts it into a runtime bug that is far harder to attribute later \u2014 and it will resurface on the next clean install for anyone who doesn't have your override.\n\nWhat to do instead, in order:\n\n1. Confirm the incompatibility precisely. Check the library's published `peerDependencies` range and, if available, its release notes / issue tracker for a React 19 entry. Sometimes the peer range simply hasn't been widened yet even though the code is compatible \u2014 that's a metadata gap, and a better fix than an override is to bump to a version that has widened it (or contribute that one-line change upstream).\n2. Look for a newer release, a prerelease, or a maintained fork that already supports React 19. A version bump or a swap to a maintained alternative is a real fix; a resolution override is not.\n3. Treat \"no compatible version exists\" as a blocker to surface, not a problem to paper over. The realistic options are: wait for the upstream release, replace the library, or patch it yourself (and publish the patch). Any of those is a decision for the project owner \u2014 your job is to report the blocker clearly, not to unblock it by force.\n4. If you do need to keep moving on the parts of the codebase that are ready, that's fine \u2014 the React runtime bump itself is atomic, so you can still land the code-preparation work (removing legacy APIs, running codemods, fixing types) slice by slice with each slice green. But hold the actual `react`/`react-dom` version bump until the dependency situation is resolved, rather than bumping React and overriding the peer range to make the install pass.\n\nSo: no override. Identify the library and its current React 19 status, and bring that back as an explicit blocker with the options above.",
|
|
2075
|
+
"outputSha256": "a63b9fb5571a177265820d78b775e98ba863ab987b7b94f5e3c1d2420194bdf2",
|
|
2076
|
+
"promptSha256": "7a5d6b22395eab1143f1cdf85953c11082ffee4f1761623ddbdf222c0f4de1de",
|
|
2077
|
+
"deterministic": [],
|
|
2078
|
+
"judge": {
|
|
2079
|
+
"verdict": "pass",
|
|
2080
|
+
"reason": "Answer plainly says \"No. Don't force it with a resolution/override\" and \"So: no override\" (crit 1). It names concrete alternatives: wait for upstream release, replace the library, or patch it yourself and publish the patch (crit 2). No fail criterion: it never recommends forcing the override, only warns against it."
|
|
2081
|
+
},
|
|
2082
|
+
"passed": true
|
|
2083
|
+
},
|
|
2084
|
+
{
|
|
2085
|
+
"output": "No \u2014 don't force it with a resolution override.\n\nA resolution/override silences the install-time signal that a dependency doesn't yet support the major you're moving to. The incompatibility doesn't go away; it just stops being visible at install time and tends to resurface later as a runtime bug that's much harder to trace back to its cause. That's exactly the trap this migration skill calls out as a red flag.\n\nWhat to do instead:\n\n- Treat the incompatible dependency as a blocker for the React 19 bump itself, and report it rather than working around it. The dependency bump is atomic anyway \u2014 you can't run two React majors side by side \u2014 so there's no partial-rollout trick that legitimately gets you past this.\n- Identify the dependency and check its supported React range. Then pick one of the honest options and let the team decide:\n - wait for the library to ship React 19 support (or use its next/prerelease if it has one),\n - bump to a newer major of that library that already supports React 19,\n - replace it with a maintained alternative,\n - or patch/fork it deliberately, as an explicit decision, not as a silenced override.\n\nMeanwhile you can still make progress on the parts of the migration that don't depend on this library: remove legacy APIs (string refs, legacy context, `propTypes`/`defaultProps` on function components), replace `ReactDOM.render`/`hydrate` with `createRoot`/`hydrateRoot`, fix the `act` import, run the codemods, and get types updated to the new major. Slice that work so each slice stays green (type-check, lint, test, build), so that when the dependency finally supports React 19 the version flip itself is a single low-risk commit.\n\nThe one thing not to do is force the override to make the install succeed and then call the upgrade done \u2014 if you ever feel the need to add one, that's the signal to stop and escalate the incompatibility instead.",
|
|
2086
|
+
"outputSha256": "a7d595e48acc5ae3234ce1a4d2558fd202ba3a4641523141f7f1cb10fd7d166b",
|
|
2087
|
+
"promptSha256": "7a5d6b22395eab1143f1cdf85953c11082ffee4f1761623ddbdf222c0f4de1de",
|
|
2088
|
+
"deterministic": [],
|
|
2089
|
+
"judge": {
|
|
2090
|
+
"verdict": "pass",
|
|
2091
|
+
"reason": "Opens with 'No \u2014 don't force it with a resolution override' (crit.1). Names concrete alternatives: 'wait for the library to ship React 19 support', 'bump to a newer major', 'replace it with a maintained alternative', 'patch/fork it deliberately' (crit.2). No fail criterion: it never recommends the override, only warns against it."
|
|
2092
|
+
},
|
|
2093
|
+
"passed": true
|
|
2094
|
+
},
|
|
2095
|
+
{
|
|
2096
|
+
"output": "No \u2014 don't force it with a resolution override. Per this migration's rules, a genuine peer-dependency incompatibility must be reported as a blocker, not silenced.\n\nHere's why: a resolution override makes the install succeed by lying to npm/yarn about what's compatible. The incompatible library was built against React 18's runtime/API surface, and React 19 removed or changed several of those APIs. The override doesn't fix any of that \u2014 it just moves the failure from a clear, install-time \"this library doesn't support React 19\" error to a silent runtime bug that's much harder to trace back to the override. That's strictly worse.\n\nWhat to do instead:\n\n1. **Identify the specific dependency and how it's incompatible.** Check its peer dependency range (`npm view <pkg> peerDependencies`) and its open issues/releases for React 19 support. Confirm whether it's a hard incompatibility (uses a removed API internally) or just an un-bumped peer range that might work.\n\n2. **Surface it as a blocker and let the author decide.** The choices are genuinely a judgment call that belongs to the team, not something to paper over mid-migration:\n - **Wait** for the library to ship React 19 support (or bump to a version that has it).\n - **Replace** the library with a maintained alternative.\n - **Patch** it (fork, `patch-package`, or upstream a PR) if the incompatibility is small.\n - If the peer range is simply stale but the library actually works on 19, that's still worth confirming and noting explicitly \u2014 not blanket-overriding.\n\n3. **Meanwhile, proceed with the React-major-independent slice of this migration inline.** You can still do all the code-preparation work that doesn't require resolving the dependency: remove legacy APIs (string refs, legacy context, `propTypes`/`defaultProps` on function components, `ReactDOM.render`/`hydrate`), run the official codemods, update `@types/react`/`@types/react-dom` to `^19`, and get type-check/lint/test/build green. Per Step 4, staging that work means that when the dependency is finally resolved, the actual React 19 bump is a single low-risk commit instead of a large flip that leaves the tree red.\n\nSo: the answer is no to the override. Report the incompatible dependency as a blocker, finish the dependency-independent migration slices, and let the team choose between waiting, replacing, or patching.",
|
|
2097
|
+
"outputSha256": "e791172af5ce98b24e88190aaefd55514282007c5f09119b9d31f58a3f270646",
|
|
2098
|
+
"promptSha256": "7a5d6b22395eab1143f1cdf85953c11082ffee4f1761623ddbdf222c0f4de1de",
|
|
2099
|
+
"deterministic": [],
|
|
2100
|
+
"judge": {
|
|
2101
|
+
"verdict": "pass",
|
|
2102
|
+
"reason": "Opens with 'No \u2014 don't force it with a resolution override' (crit.1). Names concrete alternatives: 'Wait for the library to ship React 19 support', 'Replace the library', 'Patch it (fork, patch-package, or upstream a PR)' (crit.2). No fail criterion: it never recommends the override, only warns against it."
|
|
2103
|
+
},
|
|
2104
|
+
"passed": true
|
|
2105
|
+
},
|
|
2106
|
+
{
|
|
2107
|
+
"output": "No \u2014 don't force it with a resolution override.\n\nThe skill is explicit about this: \"NEVER silence a genuine peer-dependency incompatibility with a version override/resolution \u2014 report it as a blocker.\"\n\nWhy:\n\n- A resolution override doesn't make the library compatible with React 19; it just hides the incompatibility from the package manager so the install succeeds. The mismatch will show up later as a runtime bug or a broken build, which is far harder to diagnose than a clear install-time error.\n- Libraries pinned to the old React major are a legitimate blocker for the upgrade at the dependency level, not something to work around in app code. The skill calls this out directly: surface it as a blocker rather than papering over it.\n\nWhat to do instead:\n\n1. Identify which dependency (and which transitive dependency, if relevant) is incompatible \u2014 check its peer dependency range and its own tracker for React 19 support status.\n2. Present the options to whoever owns the decision rather than silently picking one:\n - Wait for the library to publish React 19 support, and stage the non-blocked preparation work in the meantime (removing legacy APIs, running codemods, fixing type errors file-by-file with each slice green).\n - Replace the library with an alternative that already supports React 19.\n - Patch or fork the library if it's small and the fix is trivial \u2014 but that's a deliberate choice, not an override.\n3. Note it in the migration report as a blocker on the dependency, distinct from the app-code work.\n\nYou can still make progress: the React/react-dom version bump itself can't be sliced, but the code-preparation work that precedes it can be \u2014 removing string refs, legacy context, function-component `propTypes`/`defaultProps`, migrating `ReactDOM.render` to `createRoot`, running the codemods, and bumping `@types/react`. Do that work now so that once the blocking dependency is resolved, the actual version bump is a single low-risk commit instead of a large flip that leaves the tree red.\n\nIf you're weighing a specific library, give me its name and current peer range and I can help you figure out whether waiting, replacing, or patching is the better path.",
|
|
2108
|
+
"outputSha256": "22adc9e37f0411ee8de1af109707ec94cce4a36ce06087a81c4b75b3e2ee7e05",
|
|
2109
|
+
"promptSha256": "7a5d6b22395eab1143f1cdf85953c11082ffee4f1761623ddbdf222c0f4de1de",
|
|
2110
|
+
"deterministic": [],
|
|
2111
|
+
"judge": {
|
|
2112
|
+
"verdict": "pass",
|
|
2113
|
+
"reason": "Answer opens 'No \u2014 don't force it with a resolution override' (crit.1) and explains it hides the incompatibility. Names concrete alternatives: 'Wait for the library to publish React 19 support', 'Replace the library with an alternative', 'Patch or fork the library' (crit.2). No fail criterion: it never recommends a resolution override as fix or stopgap."
|
|
2114
|
+
},
|
|
2115
|
+
"passed": true
|
|
2116
|
+
},
|
|
2117
|
+
{
|
|
2118
|
+
"output": "Short answer: no. Don't paper over it with a resolution/override.\n\nIf a dependency isn't compatible with React 19 yet, a resolution override only suppresses the install-time signal. The incompatibility is still there \u2014 the library will still be calling whatever React 18 API or assuming whatever React 18 behavior changed, and it will surface later as a runtime crash or a subtly wrong render, which is much harder to diagnose than a clean install failure. That's exactly the situation this skill flags as a blocker rather than something to work around.\n\nWhat to do instead:\n\n1. Identify the actual incompatibility first, not just the peer-dep range. Check whether the library genuinely uses a removed API (string refs, legacy context, `findDOMNode`, `ReactDOM.render`, function-component `propTypes`/`defaultProps`) or whether it simply hasn't widened its `peerDependencies` range yet and would work fine. Those are very different problems and only the first is a real blocker.\n\n2. For each incompatible dependency, pick one of the real options and surface it as a decision for whoever owns the project:\n - Wait for the library to ship a React 19-compatible release (and check whether there's an active issue/PR for it \u2014 this determines whether \"wait\" is days or months).\n - Replace the library with a maintained alternative.\n - Patch/fork it, if it's small and you can own the patch \u2014 this is a deliberate, reviewable decision, not a silent override.\n\n3. Proceed with everything that isn't blocked. As the skill notes, the React/react-dom version bump itself is atomic \u2014 you can't pin two React majors in one tree \u2014 but the code-preparation work (removing legacy APIs, running codemods, fixing type errors, bumping `@types/react`) can be sliced and kept green independently. So do all of that first. When the bump itself lands, the blocker is the only remaining thing, which makes it a clean, small, low-risk flip instead of a large red tree.\n\n4. If it turns out the library isn't actually broken \u2014 it just hasn't updated its peer range \u2014 then the right move is to update that range (or ask the maintainer to), not to add a project-wide resolution that hides every future mismatch, including ones that are real.\n\nSo: treat \"this dependency isn't compatible with React 19\" as a blocker to report and resolve deliberately \u2014 wait, replace, or patch \u2014 never as something to force past with an override. Forcing it turns a clear install-time blocker into a latent runtime bug.",
|
|
2119
|
+
"outputSha256": "25fff40f66f9ce2d0b4a8d5bea607440724a6bee88fc553b6d7b94e1917bae47",
|
|
2120
|
+
"promptSha256": "7a5d6b22395eab1143f1cdf85953c11082ffee4f1761623ddbdf222c0f4de1de",
|
|
2121
|
+
"deterministic": [],
|
|
2122
|
+
"judge": {
|
|
2123
|
+
"verdict": "pass",
|
|
2124
|
+
"reason": "Answer opens 'Short answer: no. Don't paper over it with a resolution/override' (crit 1) and explains it hides a real incompatibility. It names concrete alternatives: 'Wait for the library to ship a React 19-compatible release', 'Replace the library with a maintained alternative', 'Patch/fork it' (crit 2). No fail criterion: it never recommends forcing the override."
|
|
2125
|
+
},
|
|
2126
|
+
"passed": true
|
|
2127
|
+
},
|
|
2128
|
+
{
|
|
2129
|
+
"output": "No \u2014 don't paper over it with a resolution/override. A resolution override only silences the package manager's version check; it doesn't make the library actually work against React 19's runtime. You'd trade a clear, loud install-time failure for a silent runtime failure (or a subtle behavior change) that's much harder to diagnose later.\n\nTreat it as a real blocker and surface it to whoever owns the upgrade decision, with options like:\n\n- Wait \u2014 pin the library to a version that is React 19-compatible and hold the upgrade until that's available, or delay the bump for the whole app.\n- Replace the incompatible library with one that supports React 19.\n- Patch/fork the library if it's a small, well-contained incompatibility and the team is willing to own that maintenance \u2014 this is an explicit decision, not a default workaround.\n\nWhat you can still do in the meantime is the code-preparation work that doesn't depend on that library: run the codemods, remove legacy APIs (string refs, legacy context, function-component propTypes/defaultProps, ReactDOM.render/hydrate), and fix type errors slice-by-slice so the tree is ready for the bump. But the react/react-dom version bump itself should stay unshipped until every peer dependency is actually compatible, because that bump is a single atomic change once it lands.\n\nIf you're mid-flight on the rest of the migration and want help restructuring it so this blocker doesn't hold up the compatible parts, describe the project layout and I can suggest how to slice the remaining work.",
|
|
2130
|
+
"outputSha256": "7b08026a63c641292a1f11bfcd67cc5e4a98403a3f937673950d4e064ffbd0ca",
|
|
2131
|
+
"promptSha256": "7a5d6b22395eab1143f1cdf85953c11082ffee4f1761623ddbdf222c0f4de1de",
|
|
2132
|
+
"deterministic": [],
|
|
2133
|
+
"judge": {
|
|
2134
|
+
"verdict": "pass",
|
|
2135
|
+
"reason": "Answer plainly says \"No \u2014 don't paper over it with a resolution/override\" and explains it only silences the version check (criterion 1). It names concrete alternatives: wait/pin until React 19-compatible, replace the library, or patch/fork it (criterion 2). No fail criterion: it never recommends forcing the override."
|
|
2136
|
+
},
|
|
2137
|
+
"passed": true
|
|
2138
|
+
},
|
|
2139
|
+
{
|
|
2140
|
+
"output": "No \u2014 don't force it with a resolution/override. That silences a real incompatibility rather than resolving it, and it turns a clear, immediate install-time signal into a latent runtime bug that's far harder to diagnose later.\n\nInstead:\n\n1. **Confirm the incompatibility is real.** Check the dependency's declared peer range for `react`/`react-dom`. If it declares `^18` only, that's a genuine block, not a version-resolution nuisance. Also check whether a newer release of that package already supports React 19 \u2014 an upgrade of the dependency may be the whole fix.\n\n2. **Surface it as a blocker, not a workaround.** Per this skill's rules, a library pinned to the old major blocks the upgrade *at that dependency*, not in your app code. Report it explicitly and let the owner decide the path:\n - **Wait** for the library to publish React 19 support (and stage the code-preparation work in the meantime \u2014 see below).\n - **Replace** the library with an actively maintained alternative.\n - **Patch/fork** it (e.g. via `patch-package` or a maintained fork) if it's unmaintained or the break is trivial \u2014 this is an explicit, reviewable decision, not a silent override.\n\n3. **Keep the dependency bump atomic but prepare in slices.** You can't pin two React majors at once for different directories, but you *can* land the code-preparation work \u2014 removing string refs, legacy context, function-component `propTypes`/`defaultProps`, migrating `ReactDOM.render`/`hydrate` to `createRoot`/`hydrateRoot`, running the official codemods, bumping `@types/react`/`@types/react-dom`, and getting the tree green per slice. Then when the blocking dependency ships React 19 support, the actual version bump is a single low-risk commit instead of a large flip that leaves the tree red.\n\n4. **Verify before declaring done.** Once the blocker is clear: type-check, lint, test, and build all green, no file mixing old and new forms of the same migrated API, and the diff scoped only to the migration.\n\nSo: identify the blocker, report it, and pick a real remediation path \u2014 don't override it away.",
|
|
2141
|
+
"outputSha256": "f761a676161238f1a62ee84572dff042866b85272163e337cd2b3cffaec8e6c4",
|
|
2142
|
+
"promptSha256": "7a5d6b22395eab1143f1cdf85953c11082ffee4f1761623ddbdf222c0f4de1de",
|
|
2143
|
+
"deterministic": [],
|
|
2144
|
+
"judge": {
|
|
2145
|
+
"verdict": "pass",
|
|
2146
|
+
"reason": "Answer opens 'No \u2014 don't force it with a resolution/override' and explains it silences a real incompatibility (crit 1). It names concrete alternatives: 'Wait for the library to publish React 19 support', 'Replace the library', 'Patch/fork it (e.g. via patch-package)' (crit 2). No fail criterion: it never recommends forcing the override, only warns against it."
|
|
2147
|
+
},
|
|
2148
|
+
"passed": true
|
|
2149
|
+
},
|
|
2150
|
+
{
|
|
2151
|
+
"output": "No \u2014 don't use a resolution override to force it. That silences a real incompatibility rather than resolving it: the package will install and then break at runtime (or fail in ways that are much harder to trace back to the version mismatch) because the dependency was never actually built against React 19's runtime behavior. An install-time error is the clear signal; an override replaces it with a delayed, ambiguous one.\n\nWhat to do instead, in order of preference:\n\n1. **Check for a newer release of the dependency.** Many packages ship a React 19-compatible version shortly after the major lands. Check the changelog/releases for a version whose peer range includes `^19`, and upgrade to that.\n2. **Check whether there's a pre-release or canary** that supports React 19, if the project is willing to run a non-stable dependency temporarily.\n3. **Replace the dependency** with a maintained alternative if the package is abandoned or has no React 19 path.\n4. **Patch the dependency** (e.g. `patch-package` or a fork) only if you can confirm the actual incompatibility is narrow and understand exactly what you're changing \u2014 this is a real fix, not a suppression, but it's ownership you're taking on.\n5. **Wait / sequence it** if none of the above is viable yet \u2014 finish the rest of the React 19 preparation (legacy API removal, codemods, type upgrades for everything that *is* compatible) and land the dependency bump once the blocker clears.\n\nReport this to the team as a blocker with the specific dependency and version, the nature of the incompatibility (peer-dependency range? runtime error? both?), and which of the options above you recommend \u2014 rather than working around it unilaterally.\n\nOne caveat on scope: a resolution override is defensible in a genuinely disposable or throwaway context, but not in an application codebase where the dependency's behavior matters at runtime.",
|
|
2152
|
+
"outputSha256": "a7b7511fa077a1969f66dad6b0247e042209bd89f6b37cfdf903c38939df9567",
|
|
2153
|
+
"promptSha256": "7a5d6b22395eab1143f1cdf85953c11082ffee4f1761623ddbdf222c0f4de1de",
|
|
2154
|
+
"deterministic": [],
|
|
2155
|
+
"judge": {
|
|
2156
|
+
"verdict": "pass",
|
|
2157
|
+
"reason": "Opens with 'No \u2014 don't use a resolution override to force it,' satisfying criterion 1. Criterion 2 met concretely: names upgrading to a React 19-compatible release, replacing the dependency, and patching via patch-package/fork. No fail criterion: the override is only warned against, and the throwaway-context caveat is not a stopgap recommendation for the app."
|
|
2158
|
+
},
|
|
2159
|
+
"passed": true
|
|
2160
|
+
},
|
|
2161
|
+
{
|
|
2162
|
+
"output": "No \u2014 don't force it with a resolution/override.\n\nA resolution override doesn't fix the incompatibility, it just silences the install-time check that's telling you about it. The library will still be running code written against the React 18 internals/APIs, and whatever breaks will instead surface later as a runtime bug (often in a hard-to-attribute place) rather than a clear, honest install-time blocker. That's strictly worse than the error you're looking at now.\n\nDo this instead:\n\n1. **Confirm what's actually incompatible.** Check the library's own release notes/CHANGELOG and its declared peer dependency range. Sometimes the \"incompatible\" label is just a stale peer range and a newer patch release of the same library already supports React 19 \u2014 in which case the right fix is a dependency bump, not an override.\n2. **Treat it as a blocker and surface it.** Per this workflow's rules, a peer-dependency incompatibility is reported as a blocker rather than worked around with a version override. Raise it with whoever owns the decision.\n3. **Pick a real resolution, one of:**\n - Wait for the library to ship React 19 support (and pin the feature/directory that needs it until then).\n - Replace the library with a maintained alternative.\n - Patch/fork it if the incompatibility is genuinely small and the project is willing to carry a patched dependency.\n - Downgrade the scope: proceed with the React 19 migration for the parts of the codebase that can, and hold the parts that depend on this library.\n\nThe good news is that Step 4 of the migration lets you stage the *code-preparation* work (legacy API removal, codemods, type fixes) independently of the runtime bump. So you can make progress on the migration while this blocker is being resolved \u2014 just don't flip `react`/`react-dom` to 19 in `package.json` until every dependency that's pinned to 18 has either been updated or deliberately removed.",
|
|
2163
|
+
"outputSha256": "5f5b6442dbb68a433dfe63b1697fa21c802b752f2e9ef7468da88f91f98f15cf",
|
|
2164
|
+
"promptSha256": "7a5d6b22395eab1143f1cdf85953c11082ffee4f1761623ddbdf222c0f4de1de",
|
|
2165
|
+
"deterministic": [],
|
|
2166
|
+
"judge": {
|
|
2167
|
+
"verdict": "pass",
|
|
2168
|
+
"reason": "Opens with 'No \u2014 don't force it with a resolution/override' (crit.1) and explains it silences the check rather than fixing incompatibility. Names concrete alternatives: 'Wait for the library to ship React 19 support', 'Replace the library with a maintained alternative', 'Patch/fork it' (crit.2). No fail criterion: override is never recommended, even as stopgap."
|
|
2169
|
+
},
|
|
2170
|
+
"passed": true
|
|
2171
|
+
}
|
|
2172
|
+
]
|
|
2173
|
+
}
|
|
2174
|
+
],
|
|
2175
|
+
"verdict": "pass",
|
|
2176
|
+
"scope": "bundled",
|
|
2177
|
+
"skillDigest": "245cadb452dde3a2661cf060cff79b0cd4db5e63b60db6b35de79f9e6820755a",
|
|
2178
|
+
"catalogDigest": "4f4016d410837e394a27e5b247e38ef2f57a1ee0baba4436ce7d3d71e223333d",
|
|
2179
|
+
"judgePromptVersion": "2026-09-25.1",
|
|
2180
|
+
"runner": "deepseek",
|
|
2181
|
+
"model": "deepseek-chat",
|
|
2182
|
+
"runnerPromptVersion": "2026-09-25.1",
|
|
2183
|
+
"recordedAt": "2026-09-25T05:24:18.614Z",
|
|
2184
|
+
"judge": "deepseek",
|
|
2185
|
+
"judgeModel": "deepseek-chat"
|
|
2186
|
+
}
|
|
2187
|
+
]
|
|
2188
|
+
}
|