Skip to main content

mobile-patterns

Use when working in apps/mobileAppYC. Covers React Native architecture, navigation, Redux Toolkit state management, and mobile-specific conventions.

Source facts

Repository
YosemiteCrew/Yosemite-Crew
Last source activity
August 26, 2026 at 02:18
Detected SKILL.md language
English
Stars
2,019
Forks
84

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
mobile-patterns
description
Use when working in apps/mobileAppYC. Covers React Native architecture, navigation, Redux Toolkit state management, and mobile-specific conventions.
# Mobile App Patterns — Yosemite Crew ## Description Use this skill when working on apps/mobileAppYC. Covers React Native architecture, navigation, Redux state management, and mobile-specific conventions. TRIGGER: any task in apps/mobileAppYC — screens, components, navigation, state, or native integrations. --- ## Architecture ``` apps/mobileAppYC/ src/ app/ ← Redux store.ts + typed hooks.ts features/ ← feature slices: <domain>/{screens,components,hooks,services,<domain>Slice.ts,selectors.ts} shared/ ← cross-feature components, screens, stores, services, utils navigation/ ← React Navigation config localization/ ← translation files (i18next) theme/ ← design tokens and theming config/ ← environment/config values context/ ← React contexts types/ ← shared TS types assets/ ← images, fonts ``` New code follows the feature-slice pattern: put screens, components, and services inside the owning `src/features/<domain>/` directory, not in global folders. --- ## State Management **Redux Toolkit** (not Zustand — that's frontend only). Redux Persist is enabled. ```ts // Define a slice import { createSlice } from '@reduxjs/toolkit'; const appointmentSlice = createSlice({ name: 'appointments', initialState, reducers: { ... }, }); ``` Never mix Redux and local `useState` for the same piece of data. Local state is for ephemeral UI state (modal open, input focus). Shared/persisted state goes in Redux. --- ## Navigation **React Navigation 7** with bottom tabs + native stack + drawer. ```ts // Type your navigation params type RootStackParamList = { Home: undefined; AppointmentDetail: { appointmentId: string }; }; ``` Never navigate with bare strings — always use typed route names. --- ## Forms **react-hook-form + Yup** for all forms. ```tsx const schema = yup.object({ name: yup.string().required() }); const { control, handleSubmit } = useForm({ resolver: yupResolver(schema) }); ``` --- ## Internationalisation All user-visible strings must go through **i18next**. ```tsx import { useTranslation } from 'react-i18next'; const { t } = useTranslation(); <Text>{t('appointments.title')}</Text>; ``` Never hardcode English strings in components. ### UI copy normalization - Never render raw backend enums or role acronyms directly in UI text (example: `PAYMENT_AT_CLINIC`, `VET`). - Map technical values to user-friendly labels before rendering. - Avoid `Actor` as a user-facing label; prefer contextual labels (`Lead`, `Support`) or neutral `Updated by`. --- ## Authentication SuperTokens is the mobile auth provider (email OTP + social through the provider; `supertokens-react-native` manages sessions). Firebase remains for push notifications only. Never bypass the SuperTokens session layer for core auth. --- ## Payments `@stripe/stripe-react-native` — use the SDK's pre-built UI sheets where possible. Never build custom card input from scratch. --- ## Testing - **Jest 30** + Testing Library for React Native. Jest 30 renamed the targeting flag to `--testPathPatterns` (plural) - the old singular form errors. - **Detox** for E2E (run separately, not part of standard CI). - Target tests: `pnpm --filter mobileAppYC run test -- --testPathPatterns="path/to/file"` - Never run the full suite without `--testPathPatterns`. ### Coverage Mandate — Non-Negotiable **Target: ≥ 95% Statements, Branches, Functions, Lines across `apps/mobileAppYC`. Every change must move coverage upward, never downward.** #### Rules that apply to every task — add, modify, remove 1. **Any file you touch must finish with equal or higher coverage than you found it.** Run the targeted test and confirm before handoff. 2. **Any file you create must hit ≥ 90% Statements, Branches, Functions on first commit.** New code with no tests is a blocker — do not declare the task done. 3. **When you delete code**, delete the corresponding test code too. Dead test scaffolding inflates noise and hides real gaps. 4. **When you modify behaviour** (rename, refactor, add a branch, change a conditional), update every existing test covering the changed path AND add new cases for new branches. 5. **Snapshot tests count but do not substitute** for behavioural assertions. Every logical branch needs at least one assertion that validates the outcome. #### Test types required — use all of them, not just one | Layer | Tool | When required | | --------- | ---------------------------- | ---------------------------------------------------------------------------------- | | Unit | Jest | Every service, Redux slice, hook, utility, helper | | Component | React Testing Library for RN | Every screen and reusable component — render + interaction + conditional rendering | | Snapshot | Jest `toMatchSnapshot` | Stable UI layouts — complement behavioural tests, never replace them | | E2E | Detox | Auth flows, booking, checkout, payment, any critical user journey | All four layers must grow together. Do not add unit tests while leaving Detox untouched for critical flows, and vice versa. #### Coverage enforcement workflow ```bash # After every change, run coverage for the touched file(s): pnpm --filter mobileAppYC run test -- --testPathPatterns="<YourFile>" --coverage --collectCoverageFrom="src/path/to/YourFile.tsx" # Check — if Statements/Branches/Functions dropped vs what you started with, add tests before declaring done. ``` --- ### New code = new tests (mandatory) **Every new module, screen, service, hook, slice, or utility added to `apps/mobileAppYC` must ship with tests in the same batch. No exceptions.** | What you add | What you must also add | | ------------------------------------------- | -------------------------------------------------------------- | | Service function / API call | Jest unit: success + all error branches | | Redux slice | Jest: every reducer, action creator, selector, and async thunk | | Custom hook | `renderHook` covering all return values and state branches | | Utility function | Jest unit with full branch coverage | | Screen component | Jest + Testing Library render + key interaction | | E2E-critical flow (auth, booking, checkout) | Detox test | **Coverage bar for any new file you author: Statements ≥ 90%, Branches ≥ 90%, Functions ≥ 90%.** Never leave an existing file in a worse coverage state than you found it. ### Mandatory pre-commit checks (run in order, never skip) ```bash npx tsc --noemit # from apps/mobileAppYC/ pnpm --filter mobileAppYC run lint pnpm --filter mobileAppYC run test -- --testPathPatterns="<YourFile>" ``` --- ## App Store Submission Full reference: [`docs/guide/mobile-app-submission-guide.md`](../../../docs/guide/mobile-app-submission-guide.md) ### Pre-Submission Checklist Before every submission run these checks — **do not bump versions mid-review**: 1. **Production config** — `src/config/variables.local.ts`: - `USE_DEV_API = false` - `UI_FEATURE_FLAGS.forceLiquidGlassBorder = false` - `MOBILE_CONFIG_BEHAVIOR.overrides.forceLiquidGlassBorder = false` 2. **Silence console output** - nothing to do. The console-silencing block in `App.tsx` is guarded by `if (!__DEV__)`, so a release build is quiet and a Debug build keeps its logs (search for `const noop` to confirm it is intact): ```ts if (!__DEV__) { const noop = () => {}; console.log = noop; console.info = noop; console.debug = noop; console.trace = noop; } ``` It used to run unconditionally, which muted Debug builds too and is why a token-expiry bug warned on every call for a whole release without anyone seeing it. Do not remove the `__DEV__` guard to "make release quieter" - it already is. 3. **Version bumps** (only for a new submission or after rejection): | Platform | File | Field | | -------- | --------------------------------- | ------------------------- | | Android | `android/app/build.gradle` | `versionCode` | | Android | `android/app/build.gradle` | `versionName` | | iOS | `mobileAppYC.xcodeproj` (pbxproj) | `MARKETING_VERSION` | | iOS | `mobileAppYC.xcodeproj` (pbxproj) | `CURRENT_PROJECT_VERSION` | Read the current values from those files and bump by one — never trust a hardcoded version table in docs; they go stale after every release. ### Android Build ```bash # From apps/mobileAppYC/android/ — clean first rm -rf app/build build .cxx .gradle && ./gradlew clean # APK (testing/sideload) ./gradlew assembleRelease # Output: android/app/build/outputs/apk/release/app-release.apk # AAB (Play Store) ./gradlew bundleRelease # Output: android/app/build/outputs/bundle/release/app-release.aab ``` Keystore `my-release-key.keystore` must be at `android/app/` and `android/gradle.properties` must have `YC_RELEASE_STORE_FILE`, `YC_RELEASE_STORE_PASSWORD`, `YC_RELEASE_KEY_ALIAS`, `YC_RELEASE_KEY_PASSWORD`. ### iOS Build ```bash # From apps/mobileAppYC/ios/ — clean first rm -rf Pods build Podfile.lock ~/Library/Developer/Xcode/DerivedData/* pod deintegrate && pod install ``` Archive in Xcode: 1. Open `ios/mobileAppYC.xcworkspace` (**not** `.xcodeproj`). 2. Select a physical device or "Any iOS Device (arm64)" — **not** a Simulator. 3. **Product → Clean Build Folder** (`⇧⌘K`) → **Product → Archive**. 4. In Organizer: **Distribute App** → upload to both **Preflight** and **App Store Connect**. ### Post-Approval After both stores go live, update `README.md`: - **Current production releases** table with new versions. - **Release history** table — add a row per platform with version + feature summary. ### Common Pitfalls - Opening `.xcodeproj` instead of `.xcworkspace` — Pods will not be linked. - Forgetting `pod install` after cleaning — build fails with missing headers. - `USE_DEV_API = true` left on — app hits dev backend in production. - Wrong keystore signing AAB — Play Store upload rejected. - Bumping version mid-review — Apple treats it as a new binary and restarts review. --- ## Gotchas - `react-native-permissions` requires explicit permission requests before accessing camera, location, contacts — never assume granted. - `react-native-fs` paths differ between iOS and Android — use `RNFS.DocumentDirectoryPath` not hardcoded paths. - Redux Persist can cause stale state after schema changes — bump the persist version key when changing slice shape. - `@gorhom/bottom-sheet` requires `GestureHandlerRootView` at app root — it's already there, don't remove it. - Reactotron is dev-only — guard with `__DEV__` checks. - i18n resource files are in `src/localization/` (there is no `src/i18n/`) — add new keys there before using `t()`.
View on GitHub