native-modules-and-new-architecture
Writes New Architecture native code in typed local Expo modules.
Skill by zakariaf
Install
Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.
npx agentmarket install skill/zakariaf-native-modules-and-new-architecture
Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.
Get the weekly diff
The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.
About
Reviews
Write a reviewNo reviews yet. Installed it? Reviews come from verified installers only. How reviews work
Permissions
What this skill can do and what it asks for, from its SKILL.md and code. Checked Oct 26, 2026.
- Tools
- No tools found
- Secrets
- None asked for
- Install scripts
- None found
- Runs as
- Node.js package
Network hosts it talks to 1
codeload.github.com· found in install command
Files it touches
~/.claude/skills/native-modules-and-new-architecture
What's inside
- Skill name
native-modules-and-new-architecture- Description
- Writes New Architecture native code in typed local Expo modules. Use when adding Swift or Kotlin, a local or Turbo module, codegen, or checking a library for New Architecture. Not for config plugins (expo-prebuild-and-config-plugins).
- License
- MIT
- Instructions
- 215 lines in SKILL.md · view on GitHub
Before installing
- Ships executable scripts
Files (69)
- SKILL.md
- shared.json · assets/
- native-libraries.json · examples/
- TaskChecksum.podspec · examples/turbo-module/
- CMakeLists.txt · examples/turbo-module/android/
- build.gradle · examples/turbo-module/android/
- task-checksum-jni.cpp · examples/turbo-module/android/src/main/cpp/
- NativeTaskChecksumModule.kt · examples/turbo-module/android/src/main/java/com/example/taskchecksum/
- NativeTaskChecksumPackage.kt · examples/turbo-module/android/src/main/java/com/example/taskchecksum/
- TaskChecksumJni.kt · examples/turbo-module/android/src/main/java/com/example/taskchecksum/
- task-checksum.cpp · examples/turbo-module/cpp/
- task-checksum.h · examples/turbo-module/cpp/
- RCTNativeTaskChecksum.h · examples/turbo-module/ios/
- RCTNativeTaskChecksum.mm · examples/turbo-module/ios/
- package.json · examples/turbo-module/
- NativeTaskChecksum.ts · examples/turbo-module/src/
- wiring-the-port.md · examples/
- decisions.md · references/
- expo-modules-api.md · references/
- library-compatibility.md · references/
- testing-and-proof.md · references/
- turbo-modules-and-codegen.md · references/
- check-lib.mjs · scripts/
- check-native-libraries.mjs · scripts/
- check-native-modules.mjs · scripts/
- native-scan.mjs · scripts/lib/
- source-scan.mjs · scripts/lib/
- selftest.mjs · scripts/
- native-libraries.json · templates/contracts/
- native-libraries.schema.json · templates/contracts/
- build.gradle · templates/modules/time-change/android/
- AndroidManifest.xml · templates/modules/time-change/android/src/main/
- TimeChangeModule.kt · templates/modules/time-change/android/src/main/java/expo/modules/timechange/
- TimeChangeReceiver.kt · templates/modules/time-change/android/src/main/java/expo/modules/timechange/
- TimeChangeSystem.kt · templates/modules/time-change/android/src/main/java/expo/modules/timechange/
- expo-module.config.json · templates/modules/time-change/
- TimeChange.podspec · templates/modules/time-change/ios/
- TimeChangeModule.swift · templates/modules/time-change/ios/
- TimeChangeSystem.swift · templates/modules/time-change/ios/
- fake-time-change.test.ts · templates/src/services/time-change/
- fake-time-change.ts · templates/src/services/time-change/
- native-time-change-adapter.test.ts · templates/src/services/time-change/
- native-time-change-adapter.ts · templates/src/services/time-change/
- time-change-port.ts · templates/src/services/time-change/
- package.json · tests/bases/app/
- task-list.ts · tests/bases/app/src/features/tasks/
- tsconfig.json · tests/bases/app/
- RNCMaskedView.podspec · tests/bases/libs/fixture-node-modules/@react-native-masked-view/masked-view/
- build.gradle · tests/bases/libs/fixture-node-modules/@react-native-masked-view/masked-view/android/
- package.json · tests/bases/libs/fixture-node-modules/@react-native-masked-view/masked-view/
- expo-module.config.json · tests/bases/libs/fixture-node-modules/expo-haptics/
- ExpoHaptics.podspec · tests/bases/libs/fixture-node-modules/expo-haptics/ios/
- package.json · tests/bases/libs/fixture-node-modules/expo-haptics/
- package.json · tests/bases/libs/fixture-node-modules/expo-router/
- expo-module.config.json · tests/bases/libs/fixture-node-modules/expo/
- package.json · tests/bases/libs/fixture-node-modules/expo/
- build.gradle · tests/bases/libs/fixture-node-modules/react-native-safe-area-context/android/
- package.json · tests/bases/libs/fixture-node-modules/react-native-safe-area-context/
- react-native-safe-area-context.podspec · tests/bases/libs/fixture-node-modules/react-native-safe-area-context/
- package.json · tests/bases/libs/fixture-node-modules/react-native/
- …and 9 more
SKILL.md (first lines)
# Native modules and the New Architecture
Makes the app's own native code safe to own on iOS and Android: native code only where no package
fits, written with the Expo Modules API in a local module under `modules/<name>/`, reached through one
typed, lazy, optional binding in its adapter behind a port, with the binding type and the Swift and
Kotlin definitions proven equal; Turbo Modules only where shared C++ or publishing needs them; and
every third-party native library either native on the New Architecture or a recorded decision. Two
zero-dependency checkers prove it.
## Rules that must hold
1. **Native code comes last, and lives in a local Expo module.** First an Expo SDK package, then a
library that is native on the New Architecture, then a config plugin; only then a local module made
by `npx create-expo-module@latest --local`, in Swift and Kotlin with the Expo Modules API. No native
source outside `modules/<name>/` (no inline modules, no edits to generated `ios/`/`android/`) and no
legacy bridge code (`RCT_EXPORT_MODULE`, `ReactContextBaseJavaModule`, `@ReactMethod`). Why: SDK 55 <!-- stack-pin-ignore: the release that changed it -->
and later run only on the New Architecture, legacy modules run only through interop layers React
Native will remove, and native code outside the module is never built or reviewed.
(`native-placement`, `legacy-native-api`)
2. **The module folder is complete.** `expo-module.config.json` declares each platform and lists every
`Module` class (Swift class names, Kotlin full names); every listed class exists, extends `Module`
and is `public` in Swift; `ios/*.podspec` depends on `ExpoModulesCore`; `android/build.gradle`
applies `expo-module-gradle-plugin`. Why: autolinking registers only listed classes, and a missing
piece fails one platform's build or leaves the module unreachable.
(`module-config`, `module-class`, `module-build-files`)
3. **One JavaScript name per module.** `Name("TimeChange")` is written explicitly, is identical in
Swift and Kotlin, and is unique in the app. Why: the name is the lookup key; inferred or differing
names find the module on one platform only. (`module-name`)
4. **One binding per module, in its adapter, behind a port.** `requireOptionalNativeModule` and
`requireNativeView` appear only in the module's adapter, `src/services/<concern>/native-<concern>-adapter.ts`
(the checker accepts any `<library>-<concern>-adapter.ts(x)` in `src/services`, following
architecture-and-boundaries), with a literal name a local module or spec defines; every module is
bound, by exactly one adapter (platform variants `-adapter.ios.ts`/`.android.ts` count as one);
`TurboModuleRegistry.get` appears only in a codegen spec (`Native<Name>.ts`); no
`NativeModules`, `requireNativeComponent`, `NativeEventEmitter` or `UIManager`. The port, fake and
composition root follow architecture-and-boundaries. Why: one line reaches native code, so one
place guards it and everything above it runs in Jest on the fake; a second binding is a second,
unguarded copy of the contract. (`binding-place`, `binding-unique`, `binding-name`, `codegen-spec`,
`legacy-js-api`)
5. **The binding is lazy and optional.** The lookup runs in the factory's default parameter, never at
module scope, and uses `requireOptionalNativeModule` for both kinds of module (for a Turbo Module it
falls back to `TurboModuleRegistry.get`): a missing module becomes a typed `unavailable` failure.
Why: the module is missing in Jest, on web, in a dev client or store build older than the module,
and when an update reaches an older binary; the throwing forms crash at import there.
(`binding-lazy`, `binding-optional`)
6. **The binding type is the cross-language contract.** `requireOptionalNativeModule<TimeChangeBinding>`
names a type declared in the adapter that lists every native function, property and event by its
native name, and nothing else; Swift, Kotlin and the type change in one commit. Why: the Expo Modules
API has no cross-platform codegen (`expo-type-information` drafts a type from Swift only), so a
renamed member compiles everywhere and fails at runtime. For a Turbo Module the codegen spec is the
contract: bind `requireOptionalNativeModule<Spec>(...)` with `import type { Spec }` from its
`Native<Name>.ts`; a hand-written type is compared with the spec. (`binding-type`, `contract-drift`)
7. **Same surface on iOS and Android, or a declared difference.** A member one platform lacks is
optional in the binding type (`isAutomaticTimeEnabledAsync?()`) and the adapter maps its absence to
`unsupported`; a one-platform module says so in `platforms`. Why: an undeclared gap is "is not a
function" on one platform only, found by users. (`platform-parity`)
8. **Definitions stay thin, respect threads and release what they observe.** Each closure calls a plain
helper that imports no Expo type; `Function`/`Property`/`Constant` only do cheap reads; UI work runs
in `AsyncFunction(...).runOnQueue(.main)` (`Queues.MAIN`); OS observation starts in
`OnStartObserving`, stops in `OnStopObserving` (same event, or the unnamed form) and `OnDestroy` in
the Module itself, and every observer or receiver added is removed in the same file. Why: sync
functions block the JavaScript thread, async ones default to a background queue, and a leaked
observer keeps firing and keeps the module alive; a helper with a correct `stop()` still leaks when
the Module never calls it. (`main-thread`, `observer-release`, `observer-stop`)
9. **Turbo Modules only for shared C++ or publishing, built as a local package.** `modules/<name>/` with
`package.json` `codegenConfig` (`name`, `type`, a `jsSrcsDir` holding specs,
`android.javaPackageName`), its podspec at the package root, specs named `Native<Name>.ts` declaring
`export interface Spec extends TurboModule`; never `codegenConfig` in the app's `package.json`. Why:
codegen reads only those files, autolinking looks for a React Native module's podspec only at its
root, and CNG deletes app-level native code. (`codegen-config`, `codegen-spec`)
10. **Every native dependency runs on the New Architecture natively, or is a recorded decision.** A
dependency without `expo-module.config.json`, `codegenConfig` or a Nitro marker has an entry in
`contracts/native-libraries.json` (architecture, keep or replace, reason, evidence, `recheckBy`);
entries stay true and are rechecked by their date. Why: such a library runs through the interop
layer, whose limits and removal the app must plan for, not discover.
(`library-legacy`, `library-entry`, `library-stale`, `library-recheck`)
11. **expo-doctor's React Native Directory check stays on.** No `enabled: false`; every exact-name
`exclude` has a decision entry; no regex exclusions. Why: the directory check is the only automatic
warning for unmaintained or New-Architecture-incompatible packages. (`doctor-check`)
12. **Proven on both platforms before done; a native change is a new binary.** Jest covers the fake and
the adapter's mapping; `npx expo-modules-autolinking resolve` lists the module for apple and
android; the Swift type-checks and the Kotlin helpers compile (testing-and-proof.md); a dev client
on a named simulator and emulator exercises every member. Why: Fast Refresh and over-the-air
updates never carry native code, and each layer catches what the others cannot.
13. **Stop and ask before** a new native module, a Turbo Module instead of an Expo module, keeping an
interop library, an expo-doctor exclusion, or a `nativeSourceDirs` entry. Why: each binds the app
for every later SDK and OS move.
## Workflow
1. **Decide whether native code is needed.** Read [references/decisions.md](references/decisions.md)
sections 1 to 4; walk the order of reach. A native package: go to step 7. Ask the developer before
writing a module (rule 13).
2. **Create the module.** From the app root:
`EXPO_NONINTERACTIVE=1 npx create-expo-module@latest --local <name> --name <Pascal> --platform apple android --features Function AsyncFunction Event`.
Delete its `src/` folder (the binding lives in the adapter). Shape it like
`templates/modules/time-change/` (config, podspec, Gradle file, a thin `<Pascal>Module` per platform
over a plain `<Pascal>System` helper). API details: [references/expo-modules-api.md](references/expo-modules-api.md).
3. **Write the port, fake and adapter.** Copy `templates/src/services/time-change/` as the pattern:
`<concern>-port.ts` (Result failures, unsubscribe), `fake-<concern>.ts` + test first, then
`native-<concern>-adapter.ts` with the binding type, the lazy optional binding and the error mapping,
and its test against a stand-in native object. Wire it as
[examples/wiring-the-port.md](examples/wiring-the-port.md) shows (architecture-and-boundaries owns the
composition root).
4. **Need shared C++ or a module for non-Expo apps?** Read
[references/turbo-modules-and-codegen.md](references/turbo-modules-and-codegen.md) and follow
[examples/turbo-module/](examples/turbo-module/) instead of steps 2 and 3's module half.
5. **Check the contract:** `node ${CLAUDE_SKILL_DIR}/scripts/check-native-modules.mjs .`; fix every
`FAIL` line and rerun.
6. **Prove it builds and links** ([references/testing-and-proof.md](references/testing-and-proof.md)):
`npm test`, `npx expo-modules-autolinking resolve --platform apple --json` and `--platform android`,
the Swift type-check, the Kotlin helper compile; then a dev client on a named iOS simulator and
Android emulator (ios-simulator-build, android-emulator-build), every member on both, and the manual
check for OS events.
7. **Check native libraries.** After `npm ci`:
`node ${CLAUDE_SKILL_DIR}/scripts/check-native-libraries.mjs .` and `npx expo-doctor`. An `interop`
library: replace it, wrap what you need in a module, or (after asking) record it in
`contracts/native-libraries.json` from `templates/contracts/` ([examples/native-libraries.json](examples/native-libraries.json);
[references/library-compatibility.md](references/library-compatibility.md)).From zakariaf/ReactNative-Skills, MIT.
More from zakariaf/ReactNative-Skills
- 0.606score
debug-menu-and-test-hooks
zakariaf
Gates test-only code behind one literal TEST_ONLY switch: debug menu, debug link, simulated clock and offline.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
accessibility-as-code
zakariaf
Enforces a11y labels, 44pt targets, 200% text, VoiceOver, TalkBack.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
adaptive-layout
zakariaf
Lays screens out by window, not device: breakpoints, master-detail panes, readable widths, grids.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
admob-ads-and-consent
zakariaf
Builds AdMob ads behind a port - banners, interstitials, rewarded, UMP consent then ATT, pacing, ADS_MODE test IDs.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
analytics-and-consent
zakariaf
Gates analytics on opt-in consent: no SDK before a yes, typed events without personal data, ATT.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
android-release-play
zakariaf
Ships Android App Bundles to Google Play: upload key, Play App Signing, AAB gate, versionCode, tracks, rollout.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
architecture-and-boundaries
zakariaf
Places app code in the src/ layout and checks import boundaries: one-way zones, pure core, ports with fakes, one composition root.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
async-safety
zakariaf
Prevents silent async bugs: effect races, stale results, lost promises, leaked listeners and timers, AbortSignal gaps.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
audio-and-haptics-feedback
zakariaf
Builds SFX and haptics behind ports.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
auth-and-secure-storage
zakariaf
Builds sign-in - tokens in expo-secure-store, shared refresh, protected routes, OAuth PKCE, Apple sign-in.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
background-tasks
zakariaf
Builds expo-background-task jobs: idempotent, budgeted runs.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
canvas-gestures-and-hit-testing
zakariaf
Turns canvas taps, swipes, drags and aim into typed intents: one gesture module, hit-testing on the drawn layout.
SkillAFreshness: A★ 11Claude Code +9
Signals
- Stars
- 11
- Forks
- 2
- Open issues
- 0
- Last commit (this folder)
- Oct 17, 2026 (11 days ago)
From GitHub (fetched Oct 24, 2026).
Details
- License
- MIT
- Repository
- github.com/zakariaf/ReactNative-Skills
- First seen
- Oct 24, 2026
- Last checked
- Oct 24, 2026
- Source
- GitHubgithub-graphql
More from zakariaf
- 0.606score
admob-ads-and-consent
zakariaf
Builds AdMob ads behind a port - banners, interstitials, rewarded, UMP consent then ATT, pacing, ADS_MODE test IDs.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
accessibility-as-code
zakariaf
Enforces a11y labels, 44pt targets, 200% text, VoiceOver, TalkBack.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
canvas-gestures-and-hit-testing
zakariaf
Turns canvas taps, swipes, drags and aim into typed intents: one gesture module, hit-testing on the drawn layout.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
adaptive-layout
zakariaf
Lays screens out by window, not device: breakpoints, master-detail panes, readable widths, grids.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
debug-menu-and-test-hooks
zakariaf
Gates test-only code behind one literal TEST_ONLY switch: debug menu, debug link, simulated clock and offline.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
analytics-and-consent
zakariaf
Gates analytics on opt-in consent: no SDK before a yes, typed events without personal data, ATT.
SkillAFreshness: A★ 11Claude Code +9
Cite this page
native-modules-and-new-architecture skill. Agent Mart. https://agentmart.dev/skills/zakariaf-native-modules-and-new-architecture (last updated 2026-10-24)