chore(lint): adopt the React Compiler rules, holding set-state-in-effect back - #177
Merged
Merged
Conversation
…ect back Step 3 of the frontend toolchain sweep in docs/upgrades/frontend-toolchain-86.md. No dependency version moves: package.json and package-lock.json are byte-identical. Step 2's holding edit is replaced by the ...reactHooks.configs.recommended.rules spread it was standing in for, taking the resolved config 121 -> 135 rules across all three file classes. Twelve of the fourteen added rules report nothing against the current tree, including immutability, purity and preserve-manual-memoization. The cost is two rules and 24 findings. react-hooks/refs (6 findings) is fixed by hand: all six are one idiom in ScansPage, a useRef whose .current was read during render to seed six useState initializers. A lazy useState initializer runs viewFromParams once on first render exactly as the ref did, without the render-time ref read. react-hooks/set-state-in-effect (18 findings) is left off, with the reason in the config and the work tracked in #176. Only 6 of the 18 are the synchronous setState the rule's rationale describes, and two of those are deliberate effects that each closed a real bug (L17/P2-2 and P3-2), so #176 names them so they are not "fixed" blind. The other 12 are the fetch-on-mount idiom where every setState runs after an await; a probe showed the report tracks what the compiler can see rather than a behavioural difference, so they are reported rather than worked around and are deliberately outside #176's scope. See docs/ARCHIVE.md § Deviations for changes made in this step.
This was referenced Aug 9, 2026
tyler-rich
added a commit
that referenced
this pull request
Aug 9, 2026
…r 8 (#189) Three Track A edits in docs/ROADMAP.md, each gated on a live check made this session rather than on a prior session's claim: - Strike the "Frontend tooling majors from Dependabot #86" item as Done 2026-08-09, naming all eight sweep PRs (#171, #174, #177, #179, #180, #183, #185, #187) and both deliberately-excluded packages (TypeScript 7 and @types/node 26). All eight squash commits re-confirmed present on dev before writing the bullet. - Strike the GHSA-qwww-vcr4-c8h2 re-cut request: the advisory has already been re-cut upstream to >= 7.12.0, < 7.18.2 plus >= 8.0.0, < 8.3.0, with the 8.x range untouched. Verified against the record in github/advisory-database and independently against the npm registry's advisory endpoint, which returns nothing for 7.18.2. - Reword react-router 7 -> 8 as blocked on a React 19 decision rather than on a tooling bump, and move it out of the tooling-majors grouping into Longer-term / speculative. react-router@8.3.0 peers react and react-dom at >=19.2.7; this repo pins both at 18.3.1. See docs/ARCHIVE.md section 14 for the dated record of what was verified for each.
tyler-rich
added a commit
that referenced
this pull request
Aug 9, 2026
…a current reason (#193) Comment-only change to the npm /frontend entry. No ignore rule, group, schedule, target-branch or any other key is added, removed or modified. The paragraph's instruction — leave the frontend tooling majors unignored so Dependabot keeps surfacing them — is still correct. Its stated reason was not: it called them "the deferred #86 sweep" and said to surface them "until that PR is done". The sweep completed 2026-08-09 across #171, #174, #177, #179, #180, #183, #185 and #187, and docs/ROADMAP.md marks the item Done (verified before writing). So the comment had begun arguing against its own instruction — a reader who checked the roadmap would find the work finished and reasonably conclude the exemption had expired, whose obvious next move is to add the typescript ignore, which is the one thing that must not happen. Rewritten so the instruction rests on two current reasons. The tooling majors stay unignored as ordinary policy: we want to see them, evaluate them and land them, which is exactly what the sweep did (pointing at docs/upgrades/frontend-toolchain-86.md rather than restating it). typescript stays unignored for a sharper reason: TypeScript 7 is wanted, the blocker is entirely upstream, and the regenerating Dependabot PR proposing 7.x IS the notification that tells us when typescript-eslint ships support — an ignore would suppress the signal while changing nothing about the blocker. The re-check command is inlined so the next reader can test it without a session, along with the expected shape of support (a new typescript-eslint MAJOR built against TS 7's ./unstable/* API, not a point-release range widen). Verified by parsing rather than by reading: both versions load with yaml.safe_load to byte-identical canonical JSON (SHA-256 07e71c6c on each side), and every changed raw line matches ^[+-]\s*#.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Step 3 of the frontend toolchain sweep in
docs/upgrades/frontend-toolchain-86.md— the step thatdocument flags as "a separate decision; may be declined" and prices as "unbounded until
measured". Adopted, with one rule held back.
No dependency version moves.
frontend/package.jsonandfrontend/package-lock.jsonarebyte-identical; the lockfile SHA-256 was captured before the baseline install and re-verified after
the final one.
typescriptstays at 5.7.2 (step 4).The cost, measured before anything landed
Obtained with a throwaway probe config (a copy of
eslint.config.jswith the spread restored, runvia
--config, then deleted), so the tracked tree was never modified to take the measurement.react-hooks/set-state-in-effectreact-hooks/refsTwelve of the fourteen rules report nothing — including
immutability,purityandpreserve-manual-memoization, which the scoping doc's Step 3 row expected to fire in volume.What this PR does
...reactHooks.configs.recommended.rules, removing step 2's holding edit.react-hooks/set-state-in-effect: 'off'with the reasoning inline and a pointer to Enable react-hooks/set-state-in-effect: six genuine synchronous-setState sites to refactor first #176.react-hooks/refsfindings by hand.react-hooks/refs— 6 findings, fixedAll six are one idiom in
ScansPage.tsx: auseRef(viewFromParams(searchParams))whose.currentwas read during render to seed six
useStateinitializers. Replaced with a lazyuseStateinitializer, which runs
viewFromParamsexactly once on first render as the ref did, without therender-time ref read.
initialViewhad no other use in the file;useRefstays imported forhistoryGuard.react-hooks/set-state-in-effect— 18 findings, rule held back (#176)Only 6 of the 18 are the synchronous
setStatethe rule's rationale describes. #176 tracksthem, and names the two that are deliberate effects which each closed a real bug —
ScanDetailPage's:scanIdreset (L17/P2-2) andScansPage's compare reconcile (P3-2) — so a latersession doesn't "fix" them blind and re-open what they closed.
The other 12 are the fetch-on-mount idiom, where every
setStateruns after anawait. Theseare reported, not worked around, and are deliberately outside #176's scope. A three-shape probe
against the installed 7.1.1, with
loaddefined in the component body:void load()void (async () => { await load(); })()load().catch(() => {})The first two are semantically identical, and moving the same
loadbehind a custom hook silencesall three. So the report tracks what the compiler can see through, not a behavioural difference —
and the available "fixes" are a wrapper that changes nothing or a data-fetching refactor that hides
the call from the analyser. Neither is an improvement; if that refactor is ever worth doing it is
its own decision.
Verification
eslint --print-configbefore and after, on an app.tsx, a library.tsand a test override —all three moved identically, 121 → 135 rules, with nothing else changed at any severity:
error(11)config,error-boundaries,gating,globals,immutability,preserve-manual-memoization,purity,refs,set-state-in-render,static-components,use-memowarn(2)incompatible-library,unsupported-syntaxoff(1)set-state-in-effectCounts reconcile:
configs.recommendedis 16 rules, of whichrules-of-hooksandexhaustive-depswere already present from step 2, so the delta is exactly +14.
Suites, from a fresh
rm -rf node_modules && npm ci: lint clean,format:checkclean, 79 testsacross 21 files,
npm audit0 vulnerabilities, build green.The bundle moves, and should:
index-BNB6IweX.js645.18 kB →index-Vvdzytcz.js645.14 kB.Steps 1 and 2 were provably lint-only with identical asset hashes; this step changes runtime code in
ScansPage.tsx, so the new hash is the honest signal that it did. CSS untouched(
index-D2wHtcHV.css).Not done
No
--fix, in bulk or individually. No rule disabled beyond the single authorised override. No step4 work.
docs/upgrades/frontend-toolchain-86.mdanddocs/ROADMAP.mdnot edited — correcting thesequence document is a maintainer call.
See
docs/ARCHIVE.md§ Deviations for changes made in this step.