Skip to content

feat(packs): react 18 → 19 candidate pack (pack test fails, draft) - #45

Closed
lucasbraum wants to merge 3 commits into
mainfrom
claude/pensive-galileo-8j1ysv
Closed

lucasbraum wants to merge 3 commits into
mainfrom
claude/pensive-galileo-8j1ysv

Conversation

@lucasbraum

Copy link
Copy Markdown
Collaborator

Pack

react >=18 <19 → >=19 <20 (companions: react-dom, @types/react, @types/react-dom). First todo entry in packs/queue.json; no open pack-request issue or PR existed. Scaffolded with uptide pack new react --from ">=18 <19" --to ">=19 <20" and scored only with uptide pack test react.

Status: candidate, and pack test react FAILS (477 false positives among breaking findings). The failure comes from what check reports, not from the rules or from an edited expectation; see "Tooling friction". This draft should not merge as is. A human decision is needed (below).

Sources

Rules and behavior notes

Rewrites:

  • use-ref-argument (breaking, TS2554): useRef<T>() → useRef<T>(undefined). Same edit as types-react-codemod useRef-required-initial; declines two calls on a line.
  • ref-callback-return (breaking, detect through the syntax tree of a ref attribute): ref={(el) => (x = el)} → ref={(el) => { x = el; }}. Object keys named ref and non-assigning callbacks are left alone.
  • mutable-ref-object (deprecated, TS6385): React.MutableRefObject → React.RefObject; an imported bare MutableRefObject is declined (needs the import edited).

To the agent, with a guide:

  • ref-object-nullable (RefObject<T> → RefObject<T | null>, fixed in the declaration, not at the error), global-jsx-namespace, element-props-unknown, removed-react-dom (render, hydrate, unmountComponentAtNode, findDOMNode; says what the react/19/migration-recipe codemod leaves), props-with-ref.

Behavior notes (all decision): errors-not-rethrown, removed-legacy-apis (propTypes/defaultProps, legacy context, string refs, createFactory, shallow renderer), act-moved-to-react, element-ref-deprecated, strict-mode-memoization, suspense-fallback-first, umd-removed, javascript-urls-blocked, use-reducer-types. None has detect, so none is scored.

Ground-truth repositories

Expected findings: the repository's own TypeScript with the target and its companions linked in (new diagnostics only), plus the lines the upgrade commit changed for react; read by hand (ground-truth.json $comment lists what was left out on purpose).

Repository Pinned commit (parent) Upgrade Versions Expected
excalidraw/excalidraw 9ee0b8ffcbd3664a47748a93262860321a203821 #9182, 31e8476c78aca31df1342dc26903e42301d2c3fd react 18.2.0 → 19.0.0 (+ react-dom 19.0.0, @types/react 19.0.10, @types/react-dom 19.0.4) 73
tldraw/tldraw 5957867a9c5c205a0b0c3665be443138b8014a79 #7317, f7c148187c8113aa102592e07d87b7877a937623 react 18.3.1 → 19.2.1 (+ react-dom 19.2.1, @types/react 19.2.7, @types/react-dom 19.2.3) 1804

1751 of tldraw's expected sites are one cause (the JSX namespace; the upgrade added jsxImportSource: "react" to its tsconfigs), kept as the compiler reports them.

uptide pack test output

pnpm uptide pack test react --write (fixtures pass: 3 cases, 6 sites):

excalidraw/excalidraw@9ee0b8ffcbd3  react 18.2.0 → 19.0.0  predicted 3, expected 73, false positives 2, false negatives 72
  ✗ react-dom: the upgrade moved it to 19.0.0, check leaves it
  ✗ @types/react: the upgrade moved it to 19.0.10, check leaves it
  ✗ @types/react-dom: the upgrade moved it to 19.0.4, check leaves it
tldraw/tldraw@5957867a9c5c          react 18.3.1 → 19.2.1  predicted 501, expected 1804, false positives 482, false negatives 1784
  with next-mdx-remote-client 1.1.2 → 2.1.12
  ✗ react-dom: the upgrade moved it to 19.2.1, check leaves it
  ✗ @types/react: the upgrade moved it to 19.2.7, check leaves it
  ✗ @types/react-dom: the upgrade moved it to 19.2.3, check leaves it

  rule                         precision  recall    tp   fp   fn
  element-props-unknown             100%     20%     2    0    8
  generic                             0%      0%     0  500    3
  global-jsx-namespace              100%      0%     0    0 1783
  mutable-ref-object                100%      0%     0    0    2
  props-with-ref                    100%      0%     0    0    1
  ref-callback-return               100%    100%     1    0    0
  ref-object-nullable               100%      2%     1    0   53
  removed-react-dom                 100%      0%     0    0   18
  use-ref-argument                  100%      0%     0    0    5
  overall (sites)                     4%      1%    20  484 1856
  breaking (sites)                    4%      1%    20  477 1854

FAIL react: 477 false positives among breaking findings

Breaking precision 4% (20 of 497), recall 1% (20 of 1874). verification.json records candidate.

Existing packs, unchanged (verification.json untouched, all PASS): zod verified, breaking precision 100%, recall 71%; stripe verified, 100% / 90%; ai verified, 100% / 36%.

pnpm lint, pnpm typecheck and pnpm test pass (core 878 passed, cli 345 passed). Changeset added.

What isn't covered

  • The 477 breaking false positives are type-diff changes check reports as breaking at sites the compiler accepts: RefObject#current, ReactNode widened, useContext, ChangeEvent#target, ReactElement, JSX.Element (most of them in tldraw). No rule can lower a finding's severity, and I did not edit the ground truth.
  • Recall is near zero for the compiler-visible rules: check skipped the compile signal in most files (see below), so use-ref-argument, global-jsx-namespace and removed-react-dom rarely fire there. Fixtures cover their rewrites.
  • Left out of the ground truth on purpose: excalidraw's forwardRef → ref-prop rewrite (React 19 does not require it); tldraw's casts on spread menu-item props (no diagnostic, not attributable to a react API); tests of other packages (vitest, testing-library).
  • Not rules: useReducer type arguments (a note), propTypes/defaultProps, legacy context, string refs (notes; the compiler barely sees them).

Tooling friction

  1. check does not move @types/react / @types/react-dom with react (nothing in their manifests links them), and reported no react-dom companion in either repository, although companionsOf finds react-dom 19.0.0 for react 19.0.0 when given the registry manifests. pack test fails the pack on that (leftBehind), whatever the findings. Without the types moved, the compile runs against the latest @types/react (19.3.0), not the one the upgrade chose.
  2. Compile signal skipped in most files: "90 of 163 files cannot resolve their imports at the installed version" (tldraw), "4 of 5" (excalidraw). Workspace packages need a build that a scripts-off install does not do. Those files keep the type-diff verdict, which is what makes the false positives breaking.
  3. A library workspace that only peer-depends on react is not analyzed: packages/excalidraw ("imports react without declaring it, and it does not resolve"). Most of excalidraw's expected sites are there.
  4. pack test exits non-zero for a candidate with breaking false positives, so a candidate pack cannot land with CI green unless its findings are clean.
  5. packs:truth could not install tldraw (Yarn 4 via corepack) until COREPACK_NPM_REGISTRY=https://registry.npmjs.org was set; the compiler draft of 1.8k sites needs hand grouping by cause.

Decisions for you

  • Fix the engine (companions for @types/*; severity of unconfirmed type-diff changes; peer-only workspaces) before this pack, or merge as a failing candidate: CI's Packs job will be red as long as there are breaking false positives.

  • Whether tldraw's JSX-namespace diagnostics (1751 sites, one tsconfig cause) belong in the ground truth one by one or as a single site.

  • Every rule and note comes from a source listed in meta.sources

  • Every rule that rewrites or detects has a fixture case, with a keep site

  • Ground truth from public repositories, never copied from Uptide's output

  • At least two public repositories for verified: met, but precision is not 100%, so it ships as a candidate

  • pnpm uptide pack test react passes: it fails (477 false positives)

  • pnpm lint && pnpm typecheck && pnpm test pass

  • Changeset added

  • No private code, names or paths

🤖 Generated with Claude Code

https://claude.ai/code/session_01PiuU3en7qKGdWHBGbAZirY


Generated by Claude Code

claude added 2 commits October 8, 2026 06:37
…ding)

Scaffold only: the example rule is still in place and ground-truth.json is empty.
The tldraw ground truth could not be drafted because Yarn 4 cannot be downloaded
by corepack behind the sandbox proxy.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiuU3en7qKGdWHBGbAZirY
Signed-off-by: Claude <noreply@anthropic.com>
…ldraw

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiuU3en7qKGdWHBGbAZirY
Signed-off-by: Claude <noreply@anthropic.com>
@lucasbraum lucasbraum added the pack-candidate Migration pack proposed by the daily pack task label Oct 8, 2026 — with Claude
… are one site, at the tsconfig that causes them

apps/dotcom/client reads JSX element types through the global JSX namespace
("jsx": "preserve", no "jsxImportSource"); after the upgrade its lockfile
keeps a second copy of @types/react (18.3.23, for a dependency pinned to
16 || 17 || 18) beside @types/react 19, and every element in every file
errors with one fix, the "jsxImportSource": "react" the upgrade added.
Those 1745 expected sites are one, at that line of the tsconfig, with the
cause in its note. verification.json records the run of uptide-dev/uptide
with the check engine fixes (breaking precision 88%, recall 64%).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018u9b3Pp5H68DdYZGp5Qg1U
Signed-off-by: Lucas Braum <lucas.braum@deskfy.io>
lucasbraum added a commit that referenced this pull request Oct 8, 2026
…ckstep companions, root causes at the edit (#46)

## What and why

The react pack's ground truth (#45) scored `check` at 4% precision / 1%
recall on breaking findings: `check` did not move `@types/react` or
`react-dom` with `react`, compiled almost none of the monorepos' files,
and called every type-surface change at a site the compiler never saw
"breaking". This fixes the engine, in five steps, with `pack test react`
on #45 as the acceptance test.

1. **Companions.** `@types/<pkg>` moves with `<pkg>` by name, to the
release that types the target (newest at the target's major.minor, else
its major, `src/check/types-release.ts`; `prepare` diffs the types at
that version). A package whose release at the target's own version asks
for the target (react-dom 19.0.0 peer-requires react ^19.0.0) is
released in lockstep and moves too, even when its installed copy would
accept the target. The closure is transitive (`@types/react-dom` follows
`react-dom`, which follows `react`), every workspace's dependencies are
candidates (the root's hoisted `@types/react` serves the app that
declares `react`), and the plan is carried by every report of the
package and kept by the merged entry. `check react` 18 → 19 moves react,
react-dom, @types/react and @types/react-dom together, and `fix`
installs them together. A workspace that only peer-depends on a package
(excalidraw's `packages/excalidraw`) resolved it through its types and
then looked for the runtime copy in its own `node_modules` alone; it now
finds the hoisted copy, as Node does.

2. **Compile coverage.** Why most files were not compiled, found and
fixed:
- workspace packages declared `workspace:*` under Yarn Berry (lockfile
`0.0.0-use.local`) and npm were never mapped to their source, and the
bundled compiler does not resolve a `main` pointing at a `.ts` file from
`node_modules`: the declared specifier is now what the engine reads,
hoisted links are found from ancestor `node_modules`, and the mapping
follows the linked packages' own links (`@tldraw/editor` →
`@tldraw/utils`);
- a scoped program dropped the tsconfig's ambient `.d.ts` files
(`vite-env.d.ts`, `css.d.ts`), so every `*.module.css` import failed;
they are added;
- the bundled compiler is TypeScript 6, whose defaults for unset options
differ from TypeScript 5's (`strict` on, no automatic `@types`, modern
target/module/resolution, interop on): a repository on TypeScript 5 is
now read with TypeScript 5's defaults made explicit
(`src/adapters/typescript/legacy-options.ts`). excalidraw's
`packages/excalidraw` went from 4,212 baseline errors (missing jest
globals under strict) to 15.

Every analyzed package prints `compiled N of M files in K workspaces;
skipped: <reason (count)>`, and the verdict says `types partly verified:
...` instead of `compiled against <version>` when not every file was
(`CompileSignal.coverage`, `PackageReport.compile.coverage`,
`src/check/verdict.ts`).

3. **Breaking means confirmed, in every tier.** `confirmBreaking` (was
`confirmGeneric`) runs for the verified tier too: a finding is breaking
only with evidence (compiler, runtime probe, module format, removed
export, or the pack's own `detect`). Unconfirmed surface changes are
"possible impact": counted apart in the row (`✗ 3 breaking, 17
possible`), listed under their own line in the package section, never
counted as breaking. Packs keep their rules.

4. **One finding for a root cause that is one edit.** Two new anchors,
marked `anchorOnly` (one site, the diagnostics as evidence; `fix`, the
plan, `sitesOf` and `pack test` count one):
- a compiler option: when the target drops the global `JSX` namespace
and the workspace's `"jsx": "preserve"`/`"react"` reads it (no
`jsxImportSource`), the TS7026/TS2602 diagnostics are anchored at the
`jsx` line of the tsconfig that sets it, through `extends`
(`src/adapters/typescript/config-cause.ts`);
- a repository parameter: an argument (TS2345) or JSX attribute (TS2322)
rejected at a parameter or prop declared in the repository with a type
from outside it, when two or more sites trip it (`parameterCause` in
`cause.ts`): `usePassThroughWheelEvents(ref: RefObject<HTMLElement>)` in
`packages/editor`, tripped at eleven call sites across tldraw's packages
once `useRef` returns `RefObject<HTMLElement | null>`, is one finding at
the parameter, under its repository path.

A pack rule claims an anchor when it claims most of the errors under it
(`ruleFor`).

5. **Ground truth of #45.** The only expectation change: tldraw's 1,745
JSX element diagnostics in `apps/dotcom/client` are one site, at the
`"jsx"` line of its tsconfig (pushed to #45 as 3180034, with
`verification.json` recorded from this engine). What the compile showed
while doing it: those errors come from **two copies** of the React
types. tldraw's lockfile after its own upgrade still keeps
`@types/react` 18.3.23 for a dependency pinned to `16 || 17 || 18`,
beside `@types/react` 19; with one copy (what the overlay links) `"jsx":
"preserve"` resolves element types through the UMD `React` namespace and
nothing errors. `check` does not model the package manager keeping a
nested copy, so it does not predict that site (the one
`global-jsx-namespace` false negative left in tldraw at the tsconfig).
The config-cause anchor fires when the namespace really is gone from the
program.

## How it was verified

`pnpm uptide pack test <pack>` before (main + #45) and after (this
branch + #45), same machine, same ground-truth cache.

| pack | before: breaking precision / recall | after |
| --- | --- | --- |
| react (#45) | 4% / 1% (477 FP, 20 TP, 1854 FN); react-dom,
@types/react, @types/react-dom left behind | **88% / 64%** (11 FP, 83
TP, 46 FN); all three moved, plus next-mdx-remote-client in tldraw |
| zod | 100% / 71% (PASS, verified) | 100% / 71% (PASS, verified),
identical |
| stripe | 100% / 90% (PASS, verified; 18 TP, 2 FN over 3 repositories)
| 100% / 90% (PASS, verified), identical |
| ai | 100% / 36% (PASS, verified) | 100% / 36% (PASS, verified),
identical |

react, per rule, after: removed-react-dom 100%/100% (18),
use-ref-argument 100%/80%, ref-callback-return, mutable-ref-object,
props-with-ref 100%/100%, element-props-unknown 100%/60%,
ref-object-nullable 91%/37%, global-jsx-namespace 100%/36%. The 11
breaking false positives left are listed one by one below.

**Storefront, before and after** (`check zod stripe` on
`fixtures/repos/storefront`, built CLI, main vs this branch): the rows,
rules and counts are identical; the only difference is the coverage line
under each package. The README sample is updated with those two lines,
and the tier table now says that breaking means confirmed in both tiers
and what "possible impact" is. There is no `site/` on main.

```console
zod   28 breaking · compiled against 4.6.5: 28 new type errors
  compiled 11 of 11 files in 2 workspaces
  ✗ New error API (required_error → error)   24 sites          auto-fixable
  ...
stripe   4 breaking · compiled against 23.0.0: 4 new type errors
  compiled 7 of 7 files in 1 workspace
  ✗ Test fixture casts widened                   renewal.test.ts:7   auto-fixable
```

A package with possible impact prints, under its rules:

```console
  ? possible impact: 17 sites in 9 files   the types changed where this code uses them; nothing confirmed that it breaks, so these are not counted as breaking
    ? RefObject#current changed type   12 sites   needs the agent (LLM)
```

**The 11 remaining react false positives** (breaking, `pack test react`
after; for #45, not fixed here):

1. `packages/excalidraw/components/ButtonIconSelect.tsx:52`,
`IconPicker.tsx:140`, `JSONExportDialog.tsx:90`, `LayerUI.tsx:346`,
`MobileMenu.tsx:92`, `Stats/index.tsx:231` (6): TS2322 `Type 'Element'
is not assignable to type 'ReactNode'` at a JSX child. The same error
the ground truth expects a few lines away in each file
(`ButtonIconSelect.tsx:40`, `IconPicker.tsx:117/221`,
`JSONExportDialog.tsx:45`, `LayerUI.tsx:332/352`,
`MobileMenu.tsx:76-146`, `Stats/index.tsx:188`): the truth was drafted
with the repository's TypeScript 4.9, which reports it at the element's
first line, and the bundled TypeScript 6 reports it at the offending
child. A line-tolerant match, or re-drafting the truth with the same
compiler, resolves these.
2. `dev-docs/src/components/Homepage/index.tsx:52`: the same TS2322 in
dev-docs, where the truth lists only line 1 (the `import React` the
upgrade removed).
3. `examples/excalidraw/with-script-in-browser/index.tsx:16`:
`createRoot(rootElement)`, react-dom's `createRoot` container parameter
changed; the example's one file cannot resolve `@excalidraw/excalidraw`
(unbuilt `dist`), so the compiler skipped it and the finding stands on
the companion's diff plus the runtime probe. The truth has no site in
that example.
4.
`apps/examples/src/examples/custom-components/CustomComponentsExample.tsx:11`
and `packages/tldraw/src/lib/canvas/TldrawSelectionForeground.tsx:59`:
`useTransform(rSvg, ...)`, TS2345 `RefObject<SVGSVGElement | null>` not
assignable to the hook's `RefObject<HTMLElement | SVGElement>`. The
truth expects the parameter
(`packages/editor/src/lib/hooks/useTransform.ts:6`), which `check` also
reports where two sites in one workspace trip it; these two workspaces
each trip it once, and the ≥2 rule is per workspace compile. Clustering
across workspaces resolves them.
5. `apps/docs/components/content/index.tsx:18`: TS2307 `Cannot find
module 'next-mdx-remote-client/rsc'` after the companion move of
next-mdx-remote-client 1.1.2 → 2.1.12 (the version the upgrade also
chose). A real break of the companion, reported under react; the truth
lists react's sites only.

**CI and stripe's third repository.** `nextjs/saas-starter` pins
`pnpm@12.10.1`. It failed to install here only because this machine runs
pnpm through a corepack shim (corepack 0.34 looks for `bin/pnpm.cjs`,
which pnpm 12 no longer ships). The Packs job installs pnpm with
`pnpm/action-setup`, not corepack, and `pack test` sets
`npm_config_manage_package_manager_versions`; run that way here, pnpm 10
switches itself to 12.10.1 and the repository installs ("Done in 58s
using pnpm v10.28.0"), after which stripe scores 100% / 90% over all
three repositories, before and after. CI should not hit the problem; if
it does, the Packs job log will show the same `MODULE_NOT_FOUND` for
`bin/pnpm.cjs`.

Coverage, from `check react` with the built CLI:

- excalidraw: `compiled 355 of 356 files in 5 workspaces; skipped: most
files cannot resolve their imports at the installed version (1)`
(before: `4 of 5 files cannot resolve their imports ... compile signal
skipped`, `packages/excalidraw` not analyzed at all);
- tldraw, `apps/dotcom/client`: `compiled 164 of 164 files in 1
workspace` (before: `130 of 163 files cannot resolve their imports`);
- supabase/supabase (fixtures/corpus.json, react 19.2.6 → 19.3.0):
`list` output byte-identical to main; `check react` prints `compiled
1109 of 1110 files in 12 workspaces; skipped: most files cannot resolve
their imports at the installed version (1)` and `↑ upgrades with
@types/react 19.2.14 → 19.3.0, @types/react-dom 19.2.3 → 19.3.0,
react-dom 19.2.6 → 19.3.0`. `apps/studio` is over the memory budget on
this machine with main and with this branch alike; `apps/www` (449
files) is analyzed now, where main left it out, and reports 64
compiler-confirmed TS2322 sites against @types/react 19.3.0's SVG props.
Main reported `0 breaking · compiled against 19.3.0: 0 new type errors`
for the same upgrade with that workspace unanalyzed.
- tldraw, whole repository, built CLI: `compiled 616 of 616 files in 18
workspaces`, 2m42s (before: 7m, most workspaces skipped).

`pnpm lint`, `pnpm typecheck`, `pnpm test` pass (core 895, cli 346). New
tests: companions (react 18 → 19, lockstep, workspace visibility),
types-release, legacy-options, config-cause, parameter cause
(cause.test.ts), anchor-only clusters (anchor.test.ts,
unattributed.test.ts), verdict coverage, workspace-source (hoisted,
transitive), readInstalled (`workspace:*` under Yarn Berry), CLI
possible-impact and coverage lines.

Known limits, said plainly:

- the engine thread briefly ran out of heap on tldraw and supabase while
this was being built: gathering every workspace's manifests for the
companion plan loaded a program per workspace; manifests are now read
from disk (d82a922) and both repositories run through the built CLI
again.
- a parameter cause clusters per workspace compile; the same parameter
tripped once in each of two workspaces stays two sites.
- `check` links one copy of a target's types; an install that keeps a
nested older copy (tldraw's `@types/react` 18 beside 19) is not modeled.

## Checklist

- [x] Tests for the change (a bug fix starts with a failing test)
- [x] `pnpm lint && pnpm typecheck && pnpm test` pass
- [x] README and docs updated if behavior changed
(`docs/architecture.md`, `docs/cli.md`, README sample and tier table)
- [x] Changeset added if a user would notice (`pnpm changeset`): minor,
"What changed in check"
- [x] No new dependency, or it was agreed in an issue
- [x] No private code, names or paths in fixtures, tests or docs
- [x] The rules in CONTRIBUTING.md ("Rules that do not bend") still hold

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_018u9b3Pp5H68DdYZGp5Qg1U

---------

Signed-off-by: Lucas Braum <lucas.braum@deskfy.io>
Co-authored-by: Lucas Braum <lucas.braum@deskfy.io>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
lucasbraum added a commit that referenced this pull request Oct 8, 2026
…y as fallback (#48)

## What and why

`check`'s compile signal judged every repository with the TypeScript
ts-morph bundles (6.0.2), whatever the repository installs. A repository
on TypeScript 4.9 was read by TypeScript 6's rules and its error
positions: a JSX child "not assignable to ReactNode" sits on the
element's first line in 4.9 and on the child in 6, which alone accounted
for 7 of the react pack's 11 remaining false positives in excalidraw.

`check` now compiles with the repository's own TypeScript, found the way
`fix`'s verification has found it since #40 (`node_modules/typescript`
in the workspace or the nearest ancestor, never `NODE_PATH` or a global
folder), and falls back to the bundled compiler only when the repository
installs none. The coverage line says which one judged:

```
compiled 11 of 11 files in 2 workspaces with the repo's TypeScript 5.9.3
compiled 355 of 356 files in 5 workspaces with the repo's TypeScript 4.9.4 and 5.4.5
```

How:

- `adapters/typescript/compiler.ts` holds the resolver (moved from
`fix/verify.ts`, same behavior, same tests) plus `repositoryCompiler`
(loaded once per install directory) and `describeCompiler`.
- With the repository's compiler, a baseline program is built once per
workspace from the ts-morph program's root files and the options that
compiler reads from the tsconfig (an option a newer compiler dropped or
defaults differently is the repository's compiler's to read), with the
same workspace-source `paths`; the overlay shares that baseline. With
the bundled compiler the ts-morph program stays the baseline, as before.
Signal A (usages) still runs on ts-morph.
- The host resolver is installed both ways compilers ask:
`resolveModuleNameLiterals` (TypeScript 5+) and `resolveModuleNames`
(TypeScript 4).
- The cause tracers take the compiler with the programs: node kinds and
enum flags are the judging compiler's, not the bundled one's.
- `CompileCoverage.compilers` records which compiler judged, merged
across workspaces (one entry per distinct version); `coverageLine`
prints it.
- Found on the way: a workspace without a tsconfig of its own
(excalidraw-app, covered by the root's `include`) got synthetic options.
The bundled compiler's defaults papered over that (esModuleInterop,
JSX); TypeScript 4.9 did not, and every default import and JSX element
in that workspace errored at the target. Both programs now read the
nearest ancestor tsconfig's options (never its file list) when the
workspace has none. This also lifted excalidraw's coverage from 355 of
356 to 358 of 358 (the `with-script-in-browser` example resolves its
imports now).
- Security and privacy text: SECURITY.md, the CONTRIBUTING.md rules, the
README privacy section and the CLI's `--help` privacy statement (kept
word for word with the README) now say that `check` and `verify` load
the `typescript` package the repository installs, resolved from its
`node_modules`, to compile with, and never run the repository's scripts
or other code outside `fix`'s verification step.

Cost: a repository with its own TypeScript is parsed twice (ts-morph for
Signal A, its compiler for Signal B). The react pack test went from
about 12 to 9 minutes anyway, since the baseline is built once per
workspace and shared.

## How it was verified

New tests: `fix/compiler.test.ts` (compiler naming, bundled fallback,
one load per install directory), `check/verdict.test.ts` (coverage line
with one, several and mixed compilers), `check/check.test.ts` (compilers
merged across workspaces), `adapters/typescript/compile.test.ts`
(coverage names this repository's own TypeScript 5.9.3, which the
fixture finds above it), `adapters/typescript/repo-inherited.test.ts`
(inherited tsconfig options, own file list). `config-cause.test.ts`
passes the compiler. The whole suite runs the overlay through the native
path, since the fixtures sit under this repository's TypeScript.

`pnpm lint && pnpm typecheck && pnpm test` pass (core 900 tests, cli
346).

Storefront (`fixtures/repos/storefront`, built CLI): findings, counts
and verdicts identical to main; only the coverage lines changed, to
`compiled 11 of 11 files in 2 workspaces with the repo's TypeScript
5.9.3` and `compiled 7 of 7 files in 1 workspace with the repo's
TypeScript 5.9.3`. README sample updated accordingly.

Pack tests, before (main, same commit as #46's final numbers) and after,
breaking findings:

| pack | before | after |
|---|---|---|
| react (#45's pack, uncommitted here) | 88% precision / 64% recall, 11
FP, 46 FN | **96% / 69%, 4 FP, 40 FN** |
| zod | 100% / 71% | 100% / 71%, identical |
| stripe | 100% / 90% on 3 repos | 100% / 90%, identical |
| ai | 100% / 36% | 100% / 36%, identical |

react per repo: excalidraw 9 FP → 2 (predicted 61, expected 73), tldraw
unchanged at 9 FP / 26 FN. The 6 "TS 6 vs 4.9 location" false positives
in `packages/excalidraw` are gone, as is the `with-script-in-browser`
one. The 4 remaining breaking false positives:
`dev-docs/.../Homepage/index.tsx:52` (TS2322 at a JSX child in a
workspace on the root's 4.9, same shape as before), `tldraw
apps/docs/components/content/index.tsx:18` (`next-mdx-remote-client/rsc`
after the companion move), and the two `useTransform(rSvg)`
single-site-per-workspace `ref-object-nullable` findings. The other 7 of
the 11 are deprecated or unverified, never counted as breaking. These go
to #45.

## Checklist

- [x] Tests for the change (a bug fix starts with a failing test)
- [x] `pnpm lint && pnpm typecheck && pnpm test` pass
- [x] README and docs updated if behavior changed
- [x] Changeset added if a user would notice (`pnpm changeset`)
- [x] No new dependency, or it was agreed in an issue
- [x] No private code, names or paths in fixtures, tests or docs
- [x] The rules in CONTRIBUTING.md ("Rules that do not bend") still hold

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_018u9b3Pp5H68DdYZGp5Qg1U

---------

Signed-off-by: Lucas Braum <lucas.braum@deskfy.io>
Co-authored-by: Lucas Braum <lucas.braum@deskfy.io>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

Copy link
Copy Markdown
Collaborator Author

react pack on main after #46 and #48 (rebased, rerun)

PR #45's head (claude/pensive-galileo-8j1ysv) is still on the old base. I rebased its 3 commits on main (94611a9) and added one commit with the new verification.json on claude/pensive-galileo-mw5yx8 (6908329), because this session may only push there. A human needs to decide how to land it (see the end).

pack test react (candidate, FAILS): breaking precision 96% (89 of 93), recall 69%, 4 breaking false positives (was 477, then 11). zod 100% / 71% PASS and ai 100% / 36% PASS, unchanged. stripe: storefront and unkey unchanged (100% / 89%), but nextjs/saas-starter does not install in this sandbox (pnpm MODULE_NOT_FOUND, with or without --frozen-lockfile), so pack test stripe fails here for a reason outside this change. pnpm lint, typecheck and test pass (core 900, cli 346).

None of the 4 remaining false positives is fixable in the pack or its ground truth without editing an expectation to fit Uptide's output, so I stopped:

  1. excalidraw dev-docs/src/components/Homepage/index.tsx:52 (TS2322 "Element not assignable to ReactNode", the duplicate-global-JSX shape). The ground truth expects Homepage/index.tsx:1 and pages/index.tsx:1; packs:truth reports those as TS1259 (esModuleInterop) under the repo's TypeScript 4.9.4. dev-docs/tsconfig.json extends @tsconfig/docusaurus, which is not installed (dev-docs is its own yarn project), and says it "is not used in compilation". The upgrade commit only changed dev-docs/package.json. So the draft and check compile that workspace with different effective options, and neither is a place a human must change. Engine: a workspace whose extends cannot be resolved should not be judged with inherited or synthetic options. A separate human decision: whether the 2 expected dev-docs sites (line 1) stay in the ground truth.
  2. tldraw apps/examples/.../CustomComponentsExample.tsx:11 and packages/tldraw/src/lib/canvas/TldrawSelectionForeground.tsx:59 (ref-object-nullable, useTransform(rSvg, ...)). Upstream fixed both with one edit, packages/editor/src/lib/hooks/useTransform.ts:6 (RefObject<HTMLElement | SVGElement | null>), which is already expected and found. The call sites are one root cause but sit in other workspaces than the declaration, and each has a single diagnostic in its workspace, so the cause tracer does not fold them. Engine: trace the cause to the declaration across workspaces, and with one diagnostic.
  3. tldraw apps/docs/components/content/index.tsx:18 (next-mdx-remote-client/rsc). check moves the companion next-mdx-remote-client 1.1.2 → 2.1.12; the real upgrade never touched it (apps/docs/package.json and the lockfile only move react, react-dom and the types). The import break is the 2.x API, a different upgrade. Engine: a companion whose peer range already accepts the target should stay put, and a major move of an unrelated package should not be counted for the pack.

No other breaking false positive is left. The pack stays a candidate with verification.json recording 96% / 69%.

Decision for you: push claude/pensive-galileo-mw5yx8 over claude/pensive-galileo-8j1ysv (or retarget this PR), and fix the three engine items before this can be verified.


Generated by Claude Code

lucasbraum added a commit that referenced this pull request Oct 9, 2026
…cting peer in place and report it (#49)

## What and why

Two of the three engine issues behind the react pack's remaining
breaking false positives (#45). The third (several diagnostics, one
local declaration, across workspaces) is not in this PR; see the end.

1. **A project whose tsconfig cannot be resolved is not compiled.** A
tsconfig that `extends` a package or file that is not installed
describes options the repository never builds with. `check` skips it
(and any file governed by it as the nearest tsconfig, even when a parent
workspace reaches the file), never turns its diagnostics into findings,
and says so in the coverage line: `compiled 5 of 7 files in 2
workspaces; skipped: tsconfig cannot be resolved (2); not compiled:
docs/tsconfig.json (extends "@tsconfig/docusaurus/tsconfig.json" cannot
be resolved)`. The ground-truth draft script (`pnpm packs:truth`) skips
such projects the same way and prints them under `notCompiled`. Case:
excalidraw `dev-docs`, which the root workspace reached and judged under
its own options.
2. **Companions: lockstep moves, a rejecting peer stays and is
reported.** (Replaces my first version of this item, which moved any
package whose peer range rejected the target to the lowest release that
accepted it. Real upgrades often leave such a package in place: moving
`next-mdx-remote-client` to 2.0.0 broke three more lines than leaving
it.)
- **Always moves with the package**, even when the installed range
already accepts the target (the old `react-dom` test is back): the exact
pins of the target; a release-group member published at the target's own
version (`react-dom`); `@types/*` by name; and the packages a pack names
in its optional `companions` list.
- **Every other package whose installed peer range rejects the target**
is not moved and not compiled at another version. `check` lists it under
possible impact as a peer conflict, `next-mdx-remote-client 1.1.2
declares react >= 18.3.0 < 19.0.0`, in `PackageReport.peerConflicts` and
the report's package block. It is not a finding, so it never counts as
breaking, and it does not block `fix` the way an inconsistent group
does.
3. **A pack's `companions` carry an official source.** Entries are `{
name, source }`, where `source` is the `https` URL of the official
migration guide or changelog that says the package moves with the
leader. docs/packs.md states the rule: companions come from official
docs, never from ground truth alone. `uptide pack test` fails a pack
that has an entry with no source, a non-https source, or the pack's own
package, and scores nothing (`companionProblems`, run for every pack).

## How it was verified

New tests: `unresolved-config.test.ts`; `compile.test.ts` (a workspace
whose own tsconfig is unresolvable; a nested project that is, with the
workspace's own file still judged); `verdict.test.ts` (coverage line);
`companions.test.ts` (accepting `react-dom` still moves; a
peer-rejecting package is left in place and reported, with the real
`next-mdx-remote-client` ranges; a package the pack names moves even
though its range accepts, and does not when the pack is silent);
`contract.test.ts` (`companions` carried by `definePack`;
`companionProblems` for no source, empty, plain text, http, file, no
name, own package); `pack-test.test.ts` (`testPack` fails the ai pack
with a source-less companion and scores nothing; every registered pack
passes); `format-check.test.ts` (the peer-conflict line, not added to
the breaking count).

`pnpm lint`, `pnpm typecheck`, `pnpm test` pass (core 912, cli 347).

Pack tests, breaking sites, before (main + the react pack of #45,
rebased) and after (this PR + the same pack, kept out of this PR):

| pack | precision before → after | recall before → after | breaking FP
before → after |
|---|---|---|---|
| react | 96% → 98% | 69% → 69% | 4 → 2 |
| zod | 100% → 100% | 71% → 71% | 0 → 0 |
| stripe | 100% → 100% | 89% → 89% | 0 → 0 |
| ai | 100% → 100% | 36% → 36% | 0 → 0 |

These numbers are from the run before the source requirement; that
commit changes the shape of `companions` and the pack-test problems
only, not what `check` finds, and the ai pack still passes `pack test`
problems checks (`pack-test.test.ts`). I did not rerun the four pack
tests after it.

zod and ai pass, unchanged. stripe is unchanged on the two repositories
that install here (storefront, unkey); `nextjs/saas-starter` does not
install in this sandbox (pnpm MODULE_NOT_FOUND), before and after, so
`pack test stripe` fails here for that reason alone.

react: `dev-docs/.../Homepage/index.tsx:52` and
`apps/docs/components/content/index.tsx:18` are gone, and
`next-mdx-remote-client` stays at 1.1.2 as in the real upgrade. The two
left are the `useTransform(rSvg, …)` call sites (below). No breaking
finding comes from the peer conflict.

**The ai companion and its source.** With the new rule, `ai-sdk-ollama`
(a community provider that only peers on `ai`; its 3.x range rejects
`ai` 7) was left behind and `pack test ai` failed, because the real
upgrade moved it to 4.1.0 and nothing in the registry distinguishes it
from `next-mdx-remote-client`. The ai pack now declares it, in the two
commits after the main one so they can be dropped or argued separately.
**The AI SDK's own 7.0 migration guide does not name `ai-sdk-ollama` or
community providers** (I searched it for both), so there is no AI SDK
guide link that says it must move. The `source` is the package's own
documentation,
https://github.com/jagreehal/ai-sdk-ollama/blob/main/README.md, which
states that each `ai-sdk-ollama` major targets one `ai` major (4.x is
`ai@^7`, 3.x stays on `ai@^6`). If you want a different source, it is
one line in `packs/ai/index.ts`. I did not feed the ground-truth `with`
lists to `check`: that would hand the scorer's answer to the code it
scores.

## Not in this PR: shared-root findings across workspaces

In tldraw, `useTransform(rSvg, …)` is rejected at one call site in
`apps/examples` and one in `packages/tldraw`, both because of the
declaration `useTransform.ts:6`, already found once through another
workspace. `compile.ts` anchors a repository parameter at the
declaration only when two or more sites in the same compile trip over
it; a single site stays its own diagnostic. Folding them needs the
candidate cause carried on every single-site diagnostic, a merge step in
`check.ts` that joins the workspaces' findings by declaration, and a
scorer change. That touches the report model, so it goes in its own PR.

## Checklist

- [x] Tests for the change (a bug fix starts with a failing test)
- [x] `pnpm lint && pnpm typecheck && pnpm test` pass
- [x] README and docs updated if behavior changed (docs/packs.md:
`companions` and their source rule, peer conflicts)
- [x] Changeset added if a user would notice (`pnpm changeset`)
- [x] No new dependency, or it was agreed in an issue
- [x] No private code, names or paths in fixtures, tests or docs (the
repositories named are public)
- [x] The rules in CONTRIBUTING.md ("Rules that do not bend") still hold

🤖 Generated with [Claude
Code](https://claude.ai/code/session_01AKRx4kuV6oi25MP5WguxW9)

https://claude.ai/code/session_01AKRx4kuV6oi25MP5WguxW9

---------

Signed-off-by: Claude <noreply@anthropic.com>
Co-authored-by: Claude <noreply@anthropic.com>

Copy link
Copy Markdown
Collaborator Author

react pack on main after #49 (rerun, nothing pushed)

Ran pack test react on main (2e5c510) with this PR's pack commits (the 4 from claude/pensive-galileo-mw5yx8) cherry-picked on top. Rules, expectations and ground truth untouched.

pack test react (candidate, still FAILS): breaking precision 98% (89 of 91), recall 69% (89 of 129), 2 breaking false positives (was 4 after #48). Overall 91% / 70%. excalidraw: 1 false positive, dev-docs/src/theme/ReactLiveScope/index.js:10 (generic, unverified, not breaking). The dev-docs tsconfig item and the next-mdx-remote-client companion item from the previous comment no longer produce breaking false positives (#49).

Remaining breaking false positives, both engine (cause not traced across workspaces):

  • tldraw apps/examples/src/examples/custom-components/CustomComponentsExample.tsx:11
  • tldraw packages/tldraw/src/lib/canvas/TldrawSelectionForeground.tsx:59

Both are ref-object-nullable via useTransform(rSvg, ...); upstream fixed them with one edit in packages/editor/src/lib/hooks/useTransform.ts:6, already expected and found. The call sites sit in other workspaces than the declaration and have one diagnostic each, so the cause tracer does not fold them.

Existing packs unchanged: zod verified 100% / 71% PASS, ai verified 100% / 36% PASS. stripe: breaking 100% / 89% unchanged, but pack test stripe exits non-zero here because nextjs/saas-starter does not install in this sandbox (pnpm MODULE_NOT_FOUND), an environment issue, not a regression.

Sandbox note: tldraw (Yarn 4) only installed after corepack enable plus COREPACK_NPM_REGISTRY=https://registry.npmjs.org.

verification.json on claude/pensive-galileo-mw5yx8 records 96% / 4 false positives and no longer matches this run (pack test reports "verification.json does not match this run"). I did not rewrite it or open a superseding PR, since the PR is blocked on the engine item above. Still needs a human decision: how to land claude/pensive-galileo-mw5yx8 over the PR head, and the cross-workspace cause tracing before this can be verified.


Generated by Claude Code

lucasbraum added a commit that referenced this pull request Oct 9, 2026
…to one declaration (#51)

## What and why

Engine item from the #45 thread: cause not traced across workspaces. A
mismatch at an argument traces to the repository parameter it lands on
(`parameterCause`), but the compile signal anchored the finding there
only when two or more sites in the *same* workspace tripped it. tldraw's
`useTransform(ref)` is called from three workspaces; `apps/examples` and
`packages/tldraw` have one site each, so `check` reported those two as
findings of their own beside the anchor in `packages/editor`, the one
place upstream edited (`useTransform.ts:6`). Those two were the last
breaking false positives of the react pack.

Now, when diagnostics in different workspaces trace back to the same
local declaration (same file and symbol), `check` emits one breaking
finding anchored at that declaration and lists the call sites under it
as evidence.

- **Tracer** (`adapters/typescript/compile.ts`): a diagnostic keeps the
declaration it traces to as `root`, whether or not enough sites in its
workspace share it for `cause` to be set. `parameterCause` itself is
unchanged.
- **Findings** (`check/unattributed.ts`): a call-site finding carries
`Finding.root` (workspace-relative, like `usage.file`); an in-workspace
parameter anchor carries it too.
- **Merge** (`check/check.ts` → `check/root-cause.ts`
`foldSharedRoots`): after the workspaces are merged and roots are
prefixed to repository paths, two or more sites at one root, or a site
plus the anchor another workspace already produced, become (or join) one
anchored finding: `anchorOnly`, `downstream` = the sites (each with its
`workspace`), `details` opening with `N call sites in M workspaces`,
`fixability: assisted`, the pack rule the sites agreed on. The anchor
counts as one site in the verdict.
- **Report model**: JSON gains `root` on call-site and anchored findings
and `workspace` on `downstream` entries; nothing is renamed or removed.
The terminal `--details` prints `N call sites in M workspaces, for
example` with up to 8 sites under the anchor (instead of `evidence: N
errors at the target` for a declaration root; config anchors keep that
wording). The HTML report lists the call sites with their workspaces
under the anchor site.
- **Scorer** (`packs/tooling/pack-test.ts` `predictedSites`): the anchor
is the predicted site, so a ground-truth finding at the declaration
matches it; a call site listed as evidence under an anchored finding is
neither a true nor a false positive, even when another finding lands on
that line.

On tldraw the merged finding reads:
`packages/editor/src/lib/hooks/useTransform.ts:6` · `7 call sites in 3
workspaces` (5 in `packages/editor`, 1 in `apps/examples`, 1 in
`packages/tldraw`).

No pack rule, expectation, ground truth or companion was edited.

## How it was verified

New tests: `adapters/typescript/cause.test.ts` (a single site keeps its
`root`), `check/root-cause.test.ts` (fold across workspaces, join an
existing anchor, a lone site stays, the call-sites line),
`check/check.test.ts` (`mergeAcrossWorkspaces` prefixes `../editor/...`
roots from two workspaces to one repository path and folds them),
`packs/tooling/pack-test.test.ts` (anchor scored, evidence excluded even
under another finding), `format-check-details.test.ts` and
`html/render.test.ts` (the `N call sites` line and the listed sites).

`pnpm lint && pnpm typecheck && pnpm test` pass (core 917 tests, cli
349).

Pack tests, before (main at 2e5c510) and after, run in a scratch
worktree with the react pack from #45's branch
(`claude/pensive-galileo-mw5yx8`) copied in uncommitted; breaking
findings:

| pack | before: precision / recall, breaking FP | after: precision /
recall, breaking FP |
|---|---|---|
| react (candidate) | 98% / 69%, 2 (the two `useTransform` sites) |
**100% / 69%, 0** |
| zod | 100% / 71%, 0 | 100% / 71%, 0, identical |
| stripe | 100% / 90%, 0 | 100% / 90%, 0, identical |
| ai | 100% / 36%, 0 | 100% / 36%, 0, identical |

react overall (every severity): 91% / 70% → 93% / 70%; the 7 remaining
non-breaking false positives (6 deprecated, 1 unverified) are unchanged.
`pack test react` still exits non-zero only because #45's
`verification.json` records 96% / 4 false positives and no longer
matches; it is not edited here.

## Checklist

- [x] Tests for the change (a bug fix starts with a failing test)
- [x] `pnpm lint && pnpm typecheck && pnpm test` pass
- [x] README and docs updated if behavior changed
(`docs/architecture.md`, `docs/cli.md`)
- [x] Changeset added if a user would notice (`pnpm changeset`): minor
- [x] No new dependency, or it was agreed in an issue
- [x] No private code, names or paths in fixtures, tests or docs
- [x] The rules in CONTRIBUTING.md ("Rules that do not bend") still hold

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_018u9b3Pp5H68DdYZGp5Qg1U

---
_Generated by [Claude
Code](https://claude.ai/code/session_018u9b3Pp5H68DdYZGp5Qg1U)_

---------

Signed-off-by: Lucas Braum <lucas.braum@deskfy.io>
Co-authored-by: Lucas Braum <lucas.braum@deskfy.io>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
@lucasbraum lucasbraum mentioned this pull request Oct 9, 2026
8 tasks done

Copy link
Copy Markdown
Collaborator Author

Superseded by #53


Generated by Claude Code

@lucasbraum lucasbraum closed this Oct 9, 2026
lucasbraum added a commit that referenced this pull request Oct 9, 2026
## Pack

`react`, `>=18 <19` → `>=19 <20`. Companions move with it: `react-dom`,
`@types/react` and `@types/react-dom`, as the engine's companion rules
decide (not listed by the pack). Supersedes #45: same pack and ground
truth, brought onto current main (merge of `main` at 0de525c, no
conflict), with the changeset, the README table and a test fixed up.

## Sources

- React 19 Upgrade Guide:
https://react.dev/blog/2024/04/25/react-19-upgrade-guide
- React 19.0.0 changelog:
https://github.com/facebook/react/blob/main/CHANGELOG.md#1900-december-5-2024
- types-react-codemod (preset-19):
https://github.com/eps1lon/types-react-codemod

## Rules and behavior notes

Rewrites by rule:
- `use-ref-argument`: `useRef<T>()` → `useRef<T>(undefined)`.
- `ref-callback-return`: `ref={(el) => (x = el)}` → a block body (sites
from `detect`).
- `mutable-ref-object`: `React.MutableRefObject` → `React.RefObject`.

To the agent, with a guide:
- `ref-object-nullable`: a parameter or prop typed `RefObject<T>` once
`useRef` returns `RefObject<T | null>`.
- `global-jsx-namespace`: the removed global `JSX` namespace (one
finding at the tsconfig's `jsx` line, the elements as evidence).
- `element-props-unknown`: untyped `element.props`.
- `removed-react-dom`: `ReactDOM.render`, `hydrate`,
`unmountComponentAtNode`, `findDOMNode`.
- `props-with-ref`.

Behavior notes, listed for review (the compiler cannot see them):
`errors-not-rethrown`, `removed-legacy-apis`, `act-moved-to-react`,
`element-ref-deprecated`, `strict-mode-memoization`,
`suspense-fallback-first`, `umd-removed`, `javascript-urls-blocked`,
`use-reducer-types`.

## Ground-truth repositories

| Repository | Pinned commit (before the upgrade) | Upgrade | With |
Expected findings |
|---|---|---|---|---|
| excalidraw/excalidraw | 9ee0b8ffcbd3664a47748a93262860321a203821 |
react 18.2.0 → 19.0.0 | react-dom 19.0.0, @types/react 19.0.10,
@types/react-dom 19.0.4 | 73 |
| tldraw/tldraw | 5957867a9c5c205a0b0c3665be443138b8014a79 | react
18.3.1 → 19.2.1 | react-dom 19.2.1, @types/react 19.2.7,
@types/react-dom 19.2.3 | 60 |

Expected findings were drafted with `pnpm packs:truth` from each
repository's own TypeScript and its upgrade commit, and read by hand;
tldraw's 1,745 JSX element diagnostics are one site at the tsconfig that
causes them (#45). Rules, expectations, ground truth and companions are
unchanged from #45's last state.

## `uptide pack test` output

`verification.json` on this branch records the run on main after #51
(commit 8573cc0); `pack test react` was not rerun here, since the merge
touched neither `packages/core/src/check` nor the pack. That run:

```
uptide pack test react · verified
fixtures      3 cases, 6 sites · pass
ground truth
  excalidraw/excalidraw@9ee0b8ffcbd3           react 18.2.0 → 19.0.0  predicted 60, expected 73, false positives 1, false negatives 14
  tldraw/tldraw@5957867a9c5c                   react 18.3.1 → 19.2.1  predicted 39, expected 60, false positives 6, false negatives 26

  rule                         precision  recall    tp   fp   fn
  element-props-unknown             100%     60%     6    0    4
  generic                             4%     33%     1   26    2
  global-jsx-namespace              100%     51%    20    0   19
  mutable-ref-object                100%    100%     2    0    0
  props-with-ref                    100%    100%     1    0    0
  ref-callback-return               100%    100%     1    0    0
  ref-object-nullable               100%     37%    20    0   34
  removed-react-dom                 100%    100%    18    0    0
  use-ref-argument                  100%     80%     4    0    1
  overall (sites)                    93%     70%    92    7   40
  breaking (sites)                  100%     69%    89    0   40
```

Breaking findings: precision 100% (89 sites, none false), recall 69%, on
2 public repositories. The 7 non-breaking false positives are deprecated
or unverified findings and do not count toward the verdict.

## What is not covered

- Recall on breaking sites is 69%: 40 expected sites are missed, most of
them `ref-object-nullable` (34) and `global-jsx-namespace` (19 in the
per-rule table, counted at the tsconfig anchor) sites the compiler does
not reach, plus 4 `element-props-unknown` and 1 `use-ref-argument`. They
are listed by `pack test react` as false negatives.
- Behavior changes the compiler cannot see are reported as notes for
review, never rewritten.
- The engine links one copy of the target types; a nested second
`@types/react` copy, as a real install can keep, is not modeled.

## This PR on top of #45

- `.changeset/react-pack.md` is `minor` (a new verified pack) and opens
with the two repositories; numbers unchanged.
- README "Verified packs" regenerated with `pnpm docs:packs`; `pnpm
docs:packs --check` and `pnpm docs:links` pass.
- `packages/core/src/fix/isolate.test.ts` used react as its example of a
package without a pack; it is vitest now, like the two `cli.test.ts`
cases. No other test did.
- `pnpm lint`, `pnpm typecheck`, core (919 tests) and CLI (350 tests)
pass.

## Checklist

- [x] Every rule and note comes from a source listed in `meta.sources`
- [x] Every rule that rewrites or detects has a fixture case, with a
site it must leave alone (`keep`)
- [x] Ground truth: public repositories at the commit before their
upgrade; expected findings read from their own upgrade commit and the
compiler, never copied from Uptide's output
- [x] At least two public repositories for `verified`; otherwise the
pack ships as a candidate
- [x] `pnpm uptide pack test <package>` passes and its output is pasted
above; `verification.json` written with `--write`
- [x] `pnpm lint && pnpm typecheck && pnpm test` pass
- [x] Changeset added (`pnpm changeset`): a new pack is something users
notice
- [x] No private code, names or paths anywhere: fixtures are synthetic,
ground truth is public

Supersedes #45.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_018u9b3Pp5H68DdYZGp5Qg1U

---
_Generated by [Claude
Code](https://claude.ai/code/session_018u9b3Pp5H68DdYZGp5Qg1U)_

---------

Signed-off-by: Claude <noreply@anthropic.com>
Signed-off-by: Lucas Braum <lucas.braum@deskfy.io>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Lucas Braum <lucas.braum@deskfy.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pack-candidate Migration pack proposed by the daily pack task

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants