You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Due for payment 2026-11-16] tsconfig cleanup, phase 2: split the app into web and native programs #99287
Shared types.ts contracts for the platform-split modules, in a few batches by area. No config change and no runtime change, so they can merge right away. That's most of the 184 files.
The changes that actually touch behavior (PopoverAnchorTooltip, BaseVideoPlayer, and the marginBottom in useMarkdownStyle), with real QA. Right now they sit inside a [No QA] PR.
The third-party typing fixes. I checked these: moduleSuffixes also switches imports inside node_modules to the libs' .web.d.ts files, which is why the patches were needed. The gesture-handler patch is obsolete now since main is on 3.3.0, which has the fix. svg is fixed upstream but not released yet, so that patch stays for now.
The tsconfig flip itself, which should be small once 1-3 are in.
I'm still reviewing my own plan because there is new evidence from Claude during my investigations that might change a few things, will post here as soon they are finished.
I ran #99495's web and native tsconfigs against current main. Here's what I found and how I'd like to move forward, @roryabraham the plan got considerably bigger due to new findings so I will leave review and final decision to you.
What I found
[No QA] Split app TypeScript into web and native programs #99495's native tsconfig was never actually free of DOM types. On its lockfile, react-native-nitro-fetch@1.5.4 bundles web-streams-polyfill, whose typings contain /// <reference lib="dom" />. That loaded the full DOM lib into the native check, which is why CI passed. Without it, the PR has 1,304 native errors. Main's nitro-fetch no longer pulls it in.
On main (Nov 1):
Web: 72 errors in 38 files, almost all third-party typing.
Native: 1,766 errors in 410 files.
957 are in 144 web implementations that have a .native sibling. Native never loads them, but the native tsconfig checks all of src, so they just need a .web rename.
The other 809 (266 files) come from a few root causes: BaseTextInputRef includes HTMLFormElement, the Pressable onPress event type includes the DOM KeyboardEvent, native no-ops have narrower signatures than web, some React Native runtime globals (performance, queueMicrotask, requestIdleCallback) aren't declared, and some web-only code is imported by shared code.
Fixing errors uncovers more, so the real total is probably 20–25% higher.
Checking only what the native entry point reaches doesn't work. Screens load through require('@pages/…'), and TypeScript doesn't follow those calls. So the native tsconfig has to keep include: ["src"].
Nothing crashes on native today. The DOM usage that native does load is behind runtime checks. So the value here is preventing the next unguarded DOM call and keeping platform implementations in sync, not fixing broken things.
The gap keeps growing: about +5% in two weeks.
Proposal: switch early, with a baseline for native
Fixing all of that before switching would be around 430 files and several weeks before any protection kicks in. Instead:
Web: a hard gate at 0 errors.
Native: a per-file error baseline, like the ESLint seatbelt. Existing errors are recorded in a checked-in file, a new error or a higher count in a file fails CI, and counts can only go down.
That gets the protection in place within days and stops the gap from growing. The rest gets removed in a planned series of PRs after the switch, until the native baseline reaches 0 and goes away.
Gets web to 0 (74 errors down to 4; those 4 belong to the switch).
Type patches for bugs in the libraries' .web.d.ts files: react-native-svg, react-native-gesture-handler, expo-image-manipulator, expo-video and react-native-nitro-fetch. Each one is a real upstream bug, and the upstream PRs are being opened. Two are already fixed upstream and just wait for a release.
Small type fixes in App code (svg props, Lottie refs and a few others).
The switch with the native baseline
New web/native tsconfigs, the src/types split into app, web and native, declarations for the React Native globals, lint mapping and docs.
The baseline check in scripts/typecheck.ts.
A guard that fails if the DOM lib ever ends up in the native tsconfig through a dependency's typings again.
After this, bumping a lib that ships .web.d.ts (skia, screens, reanimated, expo…) can surface web type errors. When it does, we patch it in the bump PR and open the upstream fix at the same time, then drop the patch once it's released.
.web renames, runtime-neutral, right after the switch
About 180 pure renames of web-only files, plus knip.config.ts so the knip check understands the suffixes.
No bundler impact.
These can't go first: today's tsconfig has no moduleSuffixes, so imports of renamed .web files wouldn't resolve. So the switch starts with a bigger baseline (~1,800), and the renames cut about 1,100 from it.
Planned removals, in order. Each PR lowers the baseline and says by how much:
Per-platform type aliases for the input ref and the Pressable event/ref types (~75 files). Type-only. I prototyped this, and it removes about 100 native errors.
The remaining DOM types in shared types: keyboard shortcuts, drag/drop, Plaid, WebAuthn (~22 files). Type-only.
Native no-op versions for web-only modules used by shared code (~30 files). QA on iOS/Android.
Moving inline web-only branches into platform files (~30 files). QA on all platforms.
Once the baseline is empty, remove it, so native becomes a hard gate like everything else.
1–3 can go in parallel right after the switch. 4 and 5 come after them.
Enforce contracts
STYLE.md already asks for a shared types.ts in platform-split modules, but many don't have one.
A lint rule (existing violations grandfathered in the seatbelt) makes new modules follow it.
The gesture-handler patch. It's fixed upstream and we're already on 3.3.0.
The live-markdown bump. Main is already on 0.1.343.
The DateUtils.getNextNthOfMonth / NextStepUtils change. It was working around the monthly test that failed on the 2nd of the month, and main fixed that test in 60fc6f8.
marginBottom: 4 in useMarkdownStyle. I'll find a type-only fix instead, so nothing changes visually.
PR 1 first, then the switch. Once the switch lands, the renames, the planned removals and PR 5 can start, and the renames go first because they cut the most.
Order
graph LR
B["third-party types"] --> C["switch with native baseline"]
C --> A[".web renames"]
C --> R1["shared contracts"]
C --> R2["input refs + Pressable events"]
C --> R3["remaining DOM types"]
C --> D1["contracts lint rule"]
R1 --> R4["native platform splits"]
R2 --> R4
R3 --> R4
R1 --> R5["runtime guards"]
R2 --> R5
R3 --> R5
A --> R6["remove native baseline"]
R4 --> R6
R5 --> R6
D1 --> D2["contracts backfill"]
changed the title [-]tsconfig cleanup, phase 2: split the app into web and native programs[/-][+][Due for payment 2026-11-16] tsconfig cleanup, phase 2: split the app into web and native programs[/+]on Nov 9, 2026
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.5.34-1 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:
If no regressions arise, payment will be issued on 2026-11-16. 🎊
The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the Contributor+ Checklist from here into a new comment on this GH and complete it. If you have the K2 extension, you can simply click: [this button]. If no checklist is needed for this issue, you can click: [no checklist button]
Activity
roryabraham commentedon Sep 2, 2026
It became simpler to split off Jest first: #100124
roryabraham commentedon Sep 16, 2026
#99495 is proving to be a real bear of a PR.
fabioh8010 commentedon Oct 20, 2026
Hey 👋 As discussed in DMs I will be taking over this issue, commenting for assignment cc @roryabraham
fabioh8010 commentedon Oct 20, 2026
The plan we agreed to split the PR and move on:
roryabraham commentedon Oct 20, 2026
thanks @fabioh8010 !
4 remaining items
fabioh8010 commentedon Oct 28, 2026
I'm still reviewing my own plan because there is new evidence from Claude during my investigations that might change a few things, will post here as soon they are finished.
fabioh8010 commentedon Nov 1, 2026
I ran #99495's web and native tsconfigs against current main. Here's what I found and how I'd like to move forward, @roryabraham the plan got considerably bigger due to new findings so I will leave review and final decision to you.
What I found
react-native-nitro-fetch@1.5.4bundlesweb-streams-polyfill, whose typings contain/// <reference lib="dom" />. That loaded the full DOM lib into the native check, which is why CI passed. Without it, the PR has 1,304 native errors. Main's nitro-fetch no longer pulls it in..nativesibling. Native never loads them, but the native tsconfig checks all ofsrc, so they just need a.webrename.BaseTextInputRefincludesHTMLFormElement, the PressableonPressevent type includes the DOMKeyboardEvent, native no-ops have narrower signatures than web, some React Native runtime globals (performance,queueMicrotask,requestIdleCallback) aren't declared, and some web-only code is imported by shared code.require('@pages/…'), and TypeScript doesn't follow those calls. So the native tsconfig has to keepinclude: ["src"].Proposal: switch early, with a baseline for native
Fixing all of that before switching would be around 430 files and several weeks before any protection kicks in. Instead:
That gets the protection in place within days and stops the gap from growing. The rest gets removed in a planned series of PRs after the switch, until the native baseline reaches 0 and goes away.
The PRs
Third-party types
[No QA]: [No QA] TSConfig cleanup – Phase 2: fix third-party typing so the web tsconfig has no errors #105816.web.d.tsfiles:react-native-svg,react-native-gesture-handler,expo-image-manipulator,expo-videoandreact-native-nitro-fetch. Each one is a real upstream bug, and the upstream PRs are being opened. Two are already fixed upstream and just wait for a release.The switch with the native baseline
src/typessplit intoapp,webandnative, declarations for the React Native globals, lint mapping and docs.scripts/typecheck.ts..web.d.ts(skia, screens, reanimated, expo…) can surface web type errors. When it does, we patch it in the bump PR and open the upstream fix at the same time, then drop the patch once it's released..webrenames, runtime-neutral, right after the switchknip.config.tsso the knip check understands the suffixes.moduleSuffixes, so imports of renamed.webfiles wouldn't resolve. So the switch starts with a bigger baseline (~1,800), and the renames cut about 1,100 from it.Planned removals, in order. Each PR lowers the baseline and says by how much:
types.tscontracts for platform modules whose signatures disagree (~60 files). Type-only.1–3 can go in parallel right after the switch. 4 and 5 come after them.
Enforce contracts
types.tsin platform-split modules, but many don't have one.Not porting from #99495:
DateUtils.getNextNthOfMonth/NextStepUtilschange. It was working around the monthly test that failed on the 2nd of the month, and main fixed that test in 60fc6f8.marginBottom: 4inuseMarkdownStyle. I'll find a type-only fix instead, so nothing changes visually.PR 1 first, then the switch. Once the switch lands, the renames, the planned removals and PR 5 can start, and the renames go first because they cut the most.
Order
graph LR B["third-party types"] --> C["switch with native baseline"] C --> A[".web renames"] C --> R1["shared contracts"] C --> R2["input refs + Pressable events"] C --> R3["remaining DOM types"] C --> D1["contracts lint rule"] R1 --> R4["native platform splits"] R2 --> R4 R3 --> R4 R1 --> R5["runtime guards"] R2 --> R5 R3 --> R5 A --> R6["remove native baseline"] R4 --> R6 R5 --> R6 D1 --> D2["contracts backfill"]roryabraham commentedon Nov 2, 2026
Plan LGTM 👍🏼
Hopefully the seatbelt for type errors is short-lived and can be removed once this is all done.
fabioh8010 commentedon Nov 5, 2026
Updates:
fabioh8010 commentedon Nov 6, 2026
Heads up that I will be OOO next week and will return on Nov 16th. I will continue here when I'm back.
jponikarchuk commentedon Nov 9, 2026
Deploy Blocker #106350 was identified to be related to the PR #105816 that attempted to fix this issue.
[-]tsconfig cleanup, phase 2: split the app into web and native programs[/-][+][Due for payment 2026-11-16] tsconfig cleanup, phase 2: split the app into web and native programs[/+]melvin-bot commentedon Nov 9, 2026
@truph01
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.5.34-1 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:
If no regressions arise, payment will be issued on 2026-11-16. 🎊
The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the Contributor+ Checklist from here into a new comment on this GH and complete it. If you have the K2 extension, you can simply click: [this button]. If no checklist is needed for this issue, you can click: [no checklist button]