Imported from zapPilot/zapEngine (
apps/app/AGENTS.md). Install upstream withnpx skills add zapPilot/zapEngine --skill app. Copyright stays with the author.
See @../AGENTS.md for shared application guidelines.
Mobile app guardrails
Platform boundaries
src/integration/**is shared business logic and must stay free ofreact-native, screen/router APIs, and native-only UI modules.- React Native UI/runtime imports belong in screens, components, providers, or platform-specific files.
- Do not import DOM/web-only packages such as
lucide-react,react-router-dom,recharts,hls.js, orreact-dominto native code paths. - Use
.web.ts/.web.tsxplatform splits for genuinely web-only implementations.
Podcast persistence
- Native progress and playback-speed state must use durable device storage, never
globalThis.localStorage. - Flush current position on background/inactive, pause, episode/section change, and tracker unmount.
- Resume waits for storage hydration. Hydration must merge changes made while loading, and async writes must preserve newest-value ordering.
- Changes to progress, tracker, or storage behavior must pass the podcast persistence/lifecycle tests.
UI implementation
- Use NativeWind classes backed by
@zapengine/design-tokensrather than introducing a parallel token system. - Runtime-loaded font weights use explicit font-family variants; do not rely on React Native to synthesize the desired weight.
- Keep native scrolling/gradient/glow primitives on the existing React Native/Expo implementations rather than importing web substitutes.
Verification
Before handoff for app changes, run the relevant workspace gates through Turbo. The full app gate is:
pnpm turbo run type-check lint test build --filter=@zapengine/app
pnpm --filter @zapengine/app format:check
pnpm turbo run deadcode dup:check --filter=@zapengine/app
For iOS dependency/config/release changes on macOS also run:
pnpm turbo run test:ios:release-smoke --filter=@zapengine/app
Store releases
Canonical procedures live in docs/android-release.md and docs/ios-release.md. Keep these invariants aligned with them:
scripts/eas.mjsis the single pinned EAS CLI entry point.- Store builds use
autoIncrement; failed build attempts can consume store version numbers, so release workflow concurrency/cancellation semantics are load-bearing. - Submission selects the intended finished production/store build by ID. Do not replace that logic with an unfiltered
eas submit --latest.
Native workspace freshness
- Xcode bundling does not run Turbo.
metro.config.jsusesscripts/assert-workspace-dist-fresh.cjsto catch missing/stale internal package output. - Rebuild affected internal packages instead of bypassing the freshness guard. Do not add nested Turbo builds to
devordev:web. - The generated
ios/tree can outlive JavaScript dependency changes. Runpnpm --filter @zapengine/app ios:native:syncbefore native work andpnpm --filter @zapengine/app ios:archivefor supported archives. - Open the generated
.xcworkspace, not.xcodeproj. scripts/assert-ios-native-dependencies.cjsand the iOS release smoke test are the guardrails against stale Pods/native dependency drift.
Patched Expo modules
- The repo patches
expo-audioandexpo-videofor lock-screen/media-session behavior. These are native patches, so JS-only tests cannot prove they are active. - Android can prefer Expo's prebuilt AAR and ignore Kotlin source patches.
apps/app/package.jsondeliberately listsexpo-audioinexpo.autolinking.android.buildFromSource; removing it can silently disable the patch while builds stay green. - When changing the Kotlin patch or autolinking mode, verify the source project resolves/compiles with
./gradlew :expo-audio:compileReleaseKotlinfrom the generated Android project.