Thanks for your interest in contributing.
We are not yet accepting pull requests from external contributors while we stabilize the SDK. Feedback and bug reports are welcome.
- Use the SDK: try it in an Expo app and share feedback.
- Report bugs: open a GitHub issue.
- Platform issues: for API keys, OAuth redirect URIs, rate limits, or account support, contact YouVersion Platform Support.
The sections below are for internal development of this repo.
- Node.js >= 24 (an
.nvmrcis provided, sonvm usepicks the right version) - pnpm >= 11
- Expo SDK 57
- A YouVersion Platform API key for running the example app
- A dev build for native development; Expo Go is not supported
This repo uses nodeLinker: hoisted in pnpm-workspace.yaml for Expo DOM and pnpm compatibility.
For supply-chain protection, pnpm-workspace.yaml sets minimumReleaseAge: 4320 (a 3-day cooldown) — installs reject package versions published less than 3 days ago — and dependencies are pinned to exact versions. See the Supply-Chain Protection section in AGENTS.md.
pnpm installSet EXPO_PUBLIC_YOUVERSION_APP_KEY in your environment or an .env file before starting the example app.
For auth flows, register youversionauth://callback as the callback URI for your app key in the YouVersion Platform console. The sample app declares it in apps/example/app/_layout.tsx and handles the redirect in apps/example/app/callback.tsx; app.json carries the matching "scheme": "youversionauth" so Android can route it.
An app key has exactly one callback URI, and both browser round-trips — sign-in and the data-exchange permission grant — come back through it. If the value in _layout.tsx and the console entry disagree, sign-in fails with invalid_request and permission grants silently report cancel.
Run the iOS example from the repository root:
pnpm dev:iosThis command discovers available iPhones through simctl, boots one, chooses
an unused Metro port, builds the dev client, and opens that client against the
same server. Each worktree receives its own Metro cache, and the command never
reuses an occupied port because that port may belong to another worktree. Set
IOS_SIMULATOR or pass --device <name-or-UDID> to pick a simulator explicitly.
Concurrent Metro servers are supported, but one simulator can run only one copy
of the example app. Use a different simulator UDID for simultaneous app sessions.
Or for Android:
cd apps/example
pnpm build:androidCheck the local iOS toolchain and simulator state without building:
pnpm doctor:iosAdded a native dependency or suspect stale native output? Regenerate the iOS project, clear Metro's cache, rebuild, and reconnect with one command:
pnpm dev:ios:cleanRestarting Metro only reloads JavaScript; it cannot link a new native module. The tell is a runtime redbox
Cannot find native module 'X'even though the package is installed and listed inios/Podfile.lock.expo install --fixonly reconciles versions, so it does not repair an unlinked pod.
Xcode 27. Two upstream packages lag the Swift 6.4 / iOS 15 minimum it enforces. Both are handled in the repo, but know where to look if a fresh build fails:
expo-modules-jsimust be ≥ 56.0.13 — earlier versions fail to compileJavaScriptRuntime.swift. Pinned viapnpm-lock.yaml; do not letexpo install --fixwalk it back.react-native-svg'sRNSVGFiltersresource bundle ships at iOS 12.4, which Xcode 27 rejects.apps/example/plugins/with-resource-bundle-deployment-target.jslifts it during prebuild. Delete the plugin once react-native-svg releases #3022.
Reanimated pin. Keep
react-native-reanimatedat4.5.3or newer in the example app. Expo 57's recommended4.5.1can move native sheets offscreen after a delayed React render. Do not letexpo install --fixwalk it back. Rebuild the native app after changing the version.
When an approved collaborator on platform-sdk-reactnative-expo_automation
opens a PR from a branch in this repository, BrowserStack App Live PR
Build dispatches the existing automation build for the PR's exact head
commit — Android and iOS. New commits do not rebuild automatically. To
upload the current PR head again, an approved collaborator comments one of
the following on the open PR:
| Comment | Platforms rebuilt |
|---|---|
/app-live <sha> |
Android and iOS |
/app-live-ios <sha> |
iOS only |
/app-live-android <sha> |
Android only |
<sha> is the full 40-character sha of the head commit being approved, and
the comment must contain nothing else. The single-platform commands are
cheaper/faster for iterating on a platform-specific change — matching
build-rn-app.yml's own guidance to pick a single target rather than always
paying for both. On any of these explicit rebuild paths, the commenter
authorizes that one same-repository revision; the PR author does not also
need access to the automation repository. The automation repository builds
the example app's .apk and/or .ipa for whichever platform(s) were
requested, uploads them to BrowserStack App Live, and returns the bs://...
app id(s) in the SDK workflow summary.
After a successful upload, github-actions[bot] creates or updates one PR
comment with the latest build details (only the platform(s) actually
built) and the rebuild instructions.
Naming the commit is what binds the approval to a revision. Because a comment event carries no head commit, the workflow has to read the head when it runs, which is not when the comment was posted — so without the sha, a push landing in between would inherit the approval and send an unreviewed revision into a build that holds the automation repository's credentials. With the sha, a moved head refuses the build and the workflow log names the sha to re-issue. Three notes on the exact wording accepted:
- The sha has to be all 40 characters. An abbreviation could only be compared as a prefix, and a 7-character prefix is 28 bits — grinding a second commit that shares it is ordinary vanity-hash work, and the author can pre-compute it against their own commit's prefix before the approval is even posted. So abbreviations are refused rather than resolved.
- The bare form of each command (no sha) is accepted when the PR author is
themselves an approved collaborator on
platform-sdk-reactnative-expo_automation, since winning that race would grant them nothing they cannot already do directly. - Surrounding whitespace is ignored, and anything else starting with
/app-live— a typo like/app-live-widnows, or a command with trailing prose — is refused with a notice in the workflow log. It never falls back to building both platforms.
Builds are numbered per PR: the key is the branch's ticket key plus the PR
number, incrementing for each upload, for example rn-YPE-3011-pr14-1
and rn-YPE-3011-pr14-2. A sanitized 10-character branch label plus the
PR number is used when the branch has no ticket key, for example
feature/rework-reader becomes rn-rework-rea-pr14-1. The exact source
SHA is recorded separately in the workflow summary. The numbering is
shared across /app-live, /app-live-ios, and /app-live-android — it
isn't tracked per platform.
This initial bridge produces App Live builds only. It does not run the Hinqa
corpus or upload to App Automate. Ported from the same bridge in
platform-sdk-swift (YPE-3011); when one changes, check the other. Unlike
the Swift/Kotlin bridges, the initial PR-open build always builds both
platforms — the automation workflow requires a platform input and runs
two jobs (android, ios) per dispatch — and only the explicit rebuild
comments can narrow that to one.
From the repo root:
pnpm build # turbo build
pnpm dev:ios # boot a simulator and run the iOS example in this worktree
pnpm dev:ios:clean # regenerate ios/, clear Metro, rebuild, and run
pnpm doctor:ios # report Xcode, simulator, dependency, and Metro state
pnpm typecheck # turbo typecheck
pnpm test # turbo test
pnpm lint # oxlint (type-aware TypeScript, Expo DOM, i18n, anti-slop)
pnpm lint:fix # oxlint --fix
pnpm lint:native-i18n # regression harness for native i18n rule
pnpm lint:no-raw-color # regression harness for design-tokens/no-raw-color
pnpm format:check # prettier check
pnpm format # prettier writeFrom apps/example/:
pnpm build:ios # expo run:ios
pnpm build:android # expo run:android
pnpm web # expo start --web
pnpm typecheck # tsc --noEmitFrom packages/ui/ or packages/core/:
pnpm typecheck # tsc --noEmit
pnpm test # jest
pnpm test:watch # jest --watchAll
pnpm test:coverage # jest --coveragepnpm lint runs oxlint (not ESLint). Under packages/ui/src/**, raw color literals are banned outside the token allow-list.
Banned (string literals and static JSX attribute strings):
- Hex:
#rgb,#rrggbb,#rgba,#rrggbbaa oklch(...)rgb(...)— but notrgba(...)
Allowed instead:
- Tokens from
packages/ui/src/theme/(palette,getTokens,useTokens, sheet theme helpers likeSHEET_HANDLE) - Alpha fills via
withAlpha()inpackages/ui/src/lib/color.ts - Template-interpolated hex such as
`#${mixSrgb(...)}`(expressions are skipped)
Allow-list (hex literals permitted):
packages/ui/src/theme/**packages/ui/src/native/bible-app-logo.tsxpackages/ui/src/native/youversion-platform-logo.tsx**/__tests__/**,**/*.test.ts,**/*.test.tsx
Run pnpm lint:no-raw-color to exercise the fixture harness that proves the rule flags intentional violations.
packages/ui/src/
dom/ Expo DOM scripture surfaces (reader, text view, footnotes)
native/ React Native chrome, provider, pickers, settings, and sheets
lib/ Shared adapters, hooks, constants, and pure logic
packages/core/src/
auth/ PKCE OAuth, token storage, useYVAuth
passage-anchor.ts biblePassageAnchorFromPassageId
search/ useSearch
storage/ MMKV and SecureStore adapters
apps/example/ Expo Router app consuming both packages via workspace:*
- Packages:
@youversion/platform-react-native-expo-ui(components) and@youversion/platform-react-native-expo-core(provider, auth, search, storage). The example app depends on both; UI re-exports the public component API and wraps the core provider. - Expo DOM: DOM components use
'use dom'and run in Expo's DOM/WebView runtime. Mount Web SDK components only inside those wrappers. Scripture uses them. Toolbar, pickers, settings, and other chrome stay native. - Provider setup:
GestureHandlerRootViewmust wrapYouVersionProviderso bottom-sheet gestures have the right native ancestor. - Exports: keep public exports in each package's
src/index.tsbarrel files. Auth hooks and types live in core; Bible components live in UI. - Metro: keep
apps/example/metro.config.jsminimal withgetDefaultConfig(__dirname)only. Expo SDK 52+ handles monorepo support.apps/example/index.jsre-exportsexpo-router/entry— required for Metro monorepo resolution; do not inline the entry. - Distribution: packages publish a compiled
build/output viaexpo-module-scripts(expo-module build). Locally, Metro resolves TypeScript from source (main→src/);publishConfigswaps tobuild/at publish time.tscpreserves'use dom'and Expo processes it from compiled files. See ADR 0011. - Releases: this repo uses Changesets — run
pnpm changeseton any PR that should ship to npm. See PUBLISHING.md for the full flow and RELEASE-RUNBOOK.md for RN-specific failure modes (peer-dep skew,workspace:*rewrite). - Native UI localization: user-visible strings in
packages/ui/src/native/**must useuseSdkTranslation()and keys inen.json— see docs/contributing/native-i18n.md.