Skip to content

Fix top React Doctor findings + representative migration samples#233

Open
Kelbie wants to merge 4 commits into
refactor/structural-overhaul-knip-analyzefrom
chore/react-doctor-top-fixes
Open

Fix top React Doctor findings + representative migration samples#233
Kelbie wants to merge 4 commits into
refactor/structural-overhaul-knip-analyzefrom
chore/react-doctor-top-fixes

Conversation

@Kelbie

@Kelbie Kelbie commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Fixes the highest-signal React Doctor findings and seeds representative samples for the migration-scale rules. Full sweeps are deferred to follow-up pending sign-off — each sample confirms the canonical recipe holds first.

Verified against the real tool: react-doctor 2857 → 2847, with 0 newly-introduced findings. type-check, knip, the loggerChild test (19/19), and prettier (changed files) all pass.

Full fixes

  • Security · build-pipeline-secret-boundary — both CI jobs now bun install --ignore-scripts while the GitHub Packages token is in scope (the token is only needed to auth the registry fetch, not to run lifecycle code), then run trusted dependency scripts (@shopify/react-native-skia) + the first-party postinstall in a separate token-free step.
  • Perf · no-json-parse-stringify-clonestructuredClone at loggerChild.test.ts:318.

Representative samples (recipe confirmed; remaining sites need sign-off)

  • react-compiler-no-manual-memoization (274 files) — dropped redundant useCallback in the home + notifications layouts, hoisting the closure-free handler to module scope. drawer/_layout.tsx:131 left intact: its React.memo is a load-bearing render-bailout for drawer animation timing — a rule false positive.
  • set-state-in-effect (62 files) — derive isChecking = !permission in the camera hook (1 verified-safe site; others would introduce stale-data bugs).
  • js-combine-iterations (52 files) — single-pass for…of in useAllSearchResults + pollParse.
  • no-giant-component (45 files) — split PostComposer (405 → 300 lines) into PostComposerHeader / MediaTray / Toolbar, preserving every VisualLayoutProbe log-taxonomy string.

Deferred

refs (timing-sensitive same-frame caches), plus the full sweeps of the four sampled rules.

Not self-verified

The CI install behavior (--ignore-scripts + token-free bun pm trust --all && bun run postinstall for skia + patches) can only be confirmed by an actual CI run — this CI is JS-only (lint/type-check/test), so it should be fine, but please watch the first run.

Kelbie added 4 commits June 24, 2026 01:27
Address the highest-signal React Doctor findings and seed representative
samples for the migration-scale rules (full sweeps deferred to follow-up).

Full fixes:
- Security/build-pipeline-secret-boundary: install with --ignore-scripts
  while the GitHub Packages token is in scope (it is only needed to auth the
  registry fetch), then run trusted dependency scripts (skia) and the
  first-party postinstall in a separate token-free step. Applied to both CI
  jobs.
- Perf/no-json-parse-stringify-clone: structuredClone in the loggerChild test.

Representative samples (recipe confirmed; remaining sites need sign-off):
- react-compiler-no-manual-memoization: drop redundant useCallback in the
  home + notifications layouts, hoisting the closure-free handler to module
  scope. drawer/_layout.tsx:131 left intact: its React.memo is a load-bearing
  render-bailout for drawer animation timing (rule false positive).
- set-state-in-effect: derive isChecking from permission in the camera hook.
- js-combine-iterations: single-pass for...of in useAllSearchResults and
  pollParse.
- no-giant-component: split PostComposer (405->300 lines) into Header,
  MediaTray and Toolbar sub-components, preserving the VisualLayoutProbe
  log taxonomy.

Deferred: refs (timing-sensitive), plus the full sweeps of the above rules.

Verified: react-doctor 2857->2847 with no new findings; type-check, knip,
loggerChild test, and prettier (changed files) all pass.
React Compiler is enabled (app.json experiments.reactCompiler), so manual
memo/useMemo/useCallback the compiler already covers is dead weight. An AST
codemod unwraps ONLY React's memo/useMemo/useCallback (and removes the
now-unused imports), across 125 files — 360 wrappers removed.

Kept as load-bearing (verified against a fresh react-doctor run + cross-model
review, NOT swept):
- files where removal introduced a new compiler bailout (preserve-manual-
  memoization / refs / immutability / purity / no-effect-with-fresh-deps)
- a side-effecting useMemo factory (NDKCacheAdapterSqlite in NostrNDKProvider)
- memos with explicit human justification (stable prop identity, list-row
  render bailouts)

The rn-no-non-native-navigator (x3) and js-length-check-first (x2) findings
from the same react-doctor pass are confirmed false positives for this Expo
Router setup and were intentionally left untouched: no native drawer exists in
react-navigation v7, and the flagged array compares are an intentional prefix
match and an already-guarded equality check.

react-doctor total 2847 -> 2507; react-compiler-no-manual-memoization 1320 ->
960 (-360); all compiler-health rules flat. type-check, eslint (0 errors), and
1664 tests pass.
Audit of the repo-root config/dotfiles, cross-checked with a second model.
Remove five files that no longer have a load path:

- app-env.d.ts / nativewind-env.d.ts: redundant `uniwind/types`
  references (nativewind is no longer a dependency; the app migrated to
  uniwind). The reference is already provided by the uniwind-generated
  uniwind-types.d.ts, and both were byte-identical leftovers.
- components.json / tailwind.config.js: shadcn CLI config plus the
  Tailwind JS config it pointed at. shadcn is not a dependency, Tailwind
  v4 does not auto-load a JS config without an `@config` directive (and
  global.css has none), and uniwind never reads it. The colors it
  defined already live in global.css `@theme`.
- .DS_Store: macOS cruft (already gitignored, was untracked).

Drop the now-dead "nativewind-env.d.ts" entry from tsconfig `include`;
the `**/*.ts` glob still covers the remaining uniwind types.

type-check passes; lint shows only pre-existing prettier warnings in
unrelated files.
…mbine-iterations)

Three genuine app-runtime list transforms over medium/large arrays — thread
replies, a notifications page, and the swap-group timeline — rewritten from
.map().filter() / .filter().map() to a single .flatMap() pass (the rule's own
canonical fix). Identical semantics, comparable readability, one fewer traversal
and intermediate allocation each.

Scoped, cross-model-validated sample (me + Codex agreed): the remaining
js-combine-iterations sites and ALL js-set-map-lookups sites are
false-positive-dominated in this RN/Expo codebase and were left as-is —
offline codereview/scripts tooling (not in the app bundle), string-receiver
misfires (msg.includes / field.indexOf), and sub-10-item or already-memoized
arrays where a second pass is negligible.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant