Skip to content

fix(react): report RefObject<T> declarations that need | null (UPT-87) - #201

Merged
lucasbraum merged 1 commit into
mainfrom
lucasbraum/upt-87-react-pack-raise-breaking-recall-from-69-using-ground-truth
Oct 17, 2026
Merged

lucasbraum merged 1 commit into
mainfrom
lucasbraum/upt-87-react-pack-raise-breaking-recall-from-69-using-ground-truth

Conversation

@lucasbraum

Copy link
Copy Markdown
Collaborator

Fixes UPT-87.

Reconciliation first

The committed verification.json said 69% (89 TP / 40 FN). A fresh local pack test react --offline measures 107 TP / 22 FN (83%), and CI's pack react job on 4c3dc23 measures the same numbers (artifact pack-test-react of run 39009240360). So it is a stale record, not the environment: #198 left the react tally as it was (its message says so), and the scorer changed in #143 and #155 after the record was written.

Reach, then rules (22 breaking misses)

cause misses
reached; the compiler reports at the use site, truth marks the RefObject<T> declaration (ref-object-nullable) 16
global-jsx-namespace with no finding in the file 4
generic in dev-docs 2

Most of the 16 declarations use the global React.RefObject with no react import, so detect never read them.

Change

  • ref-object-nullable gets a syntax-tree detect: RefObject<T> / React.RefObject<T> where T is an element type or a type parameter bounded by one, has no null, and is not a cast.
  • The pack also reads every project file containing RefObject< (same pattern as the ai pack, UPT-33).
  • Near-miss fixtures: | null, RefObject<number>, a cast. A cast is excluded because it would be a breaking false positive at TldrawUiContextualToolbar.tsx:56 (truth lists :55, not :56); :55 stays missed.
  • verification.json, README and docs/compatibility.md regenerated.

Before / after (same pinned ground truth, not touched)

before (measured) after
breaking recall 83% (107/129) 94.6% (122/129)
breaking FP 0 0
unverified FP 8 8
ref-object-nullable 38 TP / 16 FN 53 TP / 1 FN
other rules unchanged unchanged

Pack stays verified; thresholds untouched.

Remaining 7 misses

  • dev-docs/src/pages/index.tsx:1, dev-docs/src/components/Homepage/index.tsx:1 (excalidraw): its tsconfig says it is not used in compilation.
  • examples/excalidraw/with-nextjs/src/app/page.tsx:19: a separate example package, outside the analyzed program.
  • apps/dotcom/client/tsconfig.json:15 (tldraw): a config file; truth marks the cause, no diagnostic lands there.
  • ButtonIconCycle.tsx:23, apps/examples/src/ExampleWrapper.tsx:111: global-JSX sites with no finding in the file; not investigated beyond that.
  • TldrawUiContextualToolbar.tsx:55: the cast, see above.

Suspect truth entries to review (not changed): :55 vs :56 above are the same cast, one listed and one not.

Core tests pass (pnpm test in packages/core).

🤖 Generated with Claude Code

Fixes UPT-87.

The committed verification.json (69%, 89 TP / 40 FN) was a stale tally.
A fresh `pack test react` on main measures 107 TP / 22 FN (83%), and so
does CI's `pack react` job on 4c3dc23: the environment is not the cause.
#198 left the react tally as it was, and the scorer changed in #143/#155.

Reach, then rules, for the 22 breaking misses:

| cause | misses |
|---|---|
| reached, reported at the use site; truth marks the `RefObject<T>` declaration (`ref-object-nullable`) | 16 |
| global-jsx-namespace, no finding in the file | 4 |
| `generic` in dev-docs (tsconfig "not used in compilation") | 2 |

Most of the 16 declarations use `React.RefObject` without importing
react, so `detect` never read them. `ref-object-nullable` gets a
syntax-tree detector (element type or a type parameter bounded by one,
no `null`, not a cast) and the pack reads every project file that
contains `RefObject<`. Casts are a near-miss: one would be a breaking
false positive at TldrawUiContextualToolbar.tsx:56, so :55 stays missed.

Breaking recall 83% -> 94.6% (122/129), 0 breaking FP, unverified FP
unchanged (8), still verified. ref-object-nullable 38 -> 53 TP.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Lucas Braum <lucasbraum@gmail.com>
@lucasbraum
lucasbraum merged commit 4550da5 into main Oct 17, 2026
28 checks passed
@lucasbraum
lucasbraum deleted the lucasbraum/upt-87-react-pack-raise-breaking-recall-from-69-using-ground-truth branch October 17, 2026 18:40
@uptide-release uptide-release Bot mentioned this pull request Oct 17, 2026
lucasbraum pushed a commit that referenced this pull request Oct 17, 2026
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and publish to npm
yourself or [setup this action to publish
automatically](https://github.com/changesets/action#with-publishing). If
you're not ready to do a release yet, that's fine, whenever you add more
changesets to main, this PR will be updated.


# Releases
## uptide@0.15.0

### Minor Changes

- [#203](#203)
[`19dd649`](19dd649)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - `fix` estimates
what the agent's sites will cost and prints the estimate before its
first call. When the estimate is above the limit (`--max-cost`, default
$1), it stops before spending anything and exits 2: `estimated $3.00 for
50 sites; limit $1.00. Run again with --max-cost 3.00`. Action and App
runs never stop on the estimate: they work within `maxCost`, and the
summary and the pull request name the sites left and the `maxCost` in
`uptide.yml` that finishes them. When the agent stops at the limit, the
summary and the pull request name the estimate and the limit that
continues. The summary (`Phases`), the pull request's run details and
`report.json` (`phases`) give wall time and agent spend per phase.

- [#207](#207)
[`4a90e9f`](4a90e9f)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - `fix` names the
first major of a peer blocker that accepts the target (`no 1.x accepts
react 19; 2.2.0 does`) and suggests adding it to the command instead of
`--allow-peer`. When a verified pack covers that major, the blocker
moves as a companion and its pack runs in the same fix: its rules, its
detect-based findings and its inventory, each at the companion's own
versions. When no pack covers it, the PR says the major needs its own
migration and the risk is High. `--allow-peer` stays the suggestion only
when no release accepts the target. A group member that the target's own
range already accepts (`@casl/ability` under `@casl/react` 5) is no
longer reported as a conflict.

### Patch Changes

- [#208](#208)
[`5fdb60e`](5fdb60e)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - The express pack
reports the `const { id } = req.params` line as a site when the compiler
reports a `string | string[]` error on one of its names, because that
line is where the guide types the values once.

- [#211](#211)
[`213e1a5`](213e1a5)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - fix runs prisma
generate / graphql-codegen in its verification clone before the baseline
and after the upgrade; check names the missing generator step

- [#204](#204)
[`65b7edb`](65b7edb)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - `check` and `fix`
now measure how many of the files that use the upgraded package the
repository actually type-checks (TypeScript, plus JavaScript under
`checkJs` or `// @ts-check`). When that is under half, the summaries say
`types cover N of M affected files` and no longer present a clean
compile as proof: `fix` passes only when a test run or a build passed,
and says the verdict rests on them.

- [#205](#205)
[`c0332eb`](c0332eb)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - `fix` no longer
fails on npm repositories whose lockfile carries optional platform
packages such as sharp's `@emnapi/*`. npm 11.6 drops them on the first
lock-only install and writes a lockfile its own `npm ci` rejects; `fix`
now runs the lock-only install twice, at the upgrade and at the parent
commit alike, so the lockfile it validates is one `npm ci` accepts.

- [#201](#201)
[`4550da5`](4550da5)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - The react pack
now reports `RefObject<HTMLElement>` annotations that need `| null` at
the declaration, including in files that type a prop with the global
`React.RefObject` and never import react. The compiler reports these
where a ref is passed in, so the declaration to edit was missed.

- [#216](#216)
[`95a3bf5`](95a3bf5)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - setup PRs pin
Action v0.14.3

- [#209](#209)
[`48df030`](48df030)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - The zod pack
reports `.refine(check, { message })`, the type names zod 4 marks
deprecated (`ZodTypeAny`, `ZodIssue`, `ZodSchema`, `ZodErrorMap` and the
others, including `import('zod').ZodTypeAny`) and `errorMap` options
with `ctx.defaultError`, as deprecated findings.

- [#200](#200)
[`4e4cc3e`](4e4cc3e)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - The zod pack
reports names zod 3 exported from its root and zod 4 exports only from
`zod/v3` (`SafeParseReturnType`, `ZodTypeDef`, `ZodEffects`, the
`Zod*Def` interfaces), which the surface diff counted as unchanged, and
`fix` now rewrites `z.string().uuid()` to `z.guid()` instead of
`z.uuid()`, so ids that zod 3 accepted still validate.

Co-authored-by: uptide-release[bot] <338861287+uptide-release[bot]@users.noreply.github.com>
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