Mobile parity (from web)
Use this pattern when you already have a member- or customer-facing web product and need an Expo / React Native (or similar) peer that matches real behavior, not a slide deck.
Adapted from an internal agent skill; paths and stack names are intentionally generic so you can map them to your repo.
When to use it
Reach for this when the shipping web app is the behavioral spec: routes, validation, empty states, error handling, and API usage are already proven in production.
Skip it or narrow the scope when:
- the surface is admin-only or internal tools with no native counterpart
- work is server-only or native platform plumbing with no web peer
- you would be guessing product behavior instead of reading working code
Source of truth
Treat the running web implementation as canonical unless product notes explicitly say native should differ. Parity is an architecture decision for customer-facing flows, not a one-off experiment.
Native is not the product in isolation. Every parity slice should name the web route or component that defines the behavior. If you only ship a native screen without that link and shared behavior written down, the slice is incomplete.
Durable pointers that help agents (and humans) stay aligned:
- an architecture note or ADR for the mobile stack and package layout
- per-feature component-mapping markdown in repo (see below)
- short worked examples for auth, upgrades, or other high-risk flows
Inspect before you code
- web route or component that owns the visible states
- direct children that own validation or side effects
- API layer: operations, generated clients, error shapes
- existing automated tests, E2E notes, or manual verification logs
- existing mapping file for the feature, if any
Code shape
Keep orchestration out of JSX. A typical split:
| Concern | Where it lives |
|---|---|
| Headless state machine, mutation order, error mapping, navigation targets | Shared package: e.g. use*Flow modules (no UI framework imports in the core) |
| Shared copy, validation helpers, view-model types | Same shared package or adjacent modules |
| Web-only adapters (browser storage, web-only SDKs, DOM) | Web adapter layer next to the shared module |
| Native-only adapters (secure storage, native ceremonies, mobile navigation) | Native *Deps or thin bridge modules consumed by screens |
| Web shell | Web app / design-system components |
| Native shell | Native screens: layout, lists, platform chrome |
Rules of thumb:
- Shared headless modules stay free of your web UI kit, DOM APIs, RN primitives, and navigation frameworks.
- Web-first wiring: when you add shared modules for parity, refactor the canonical web consumer in the same change unless you document a deferral. Native-only imports of new shared code while web still duplicates logic is a drift risk.
- Platform APIs sit behind injected dependencies or small adapters.
- Hooks return state and callbacks; navigation is injected, not hard-coded.
- Native mirrors outcomes, not web JSX structure.
- Intentional gaps belong in the mapping file, not only in chat history.
Workflow
- Identify user-visible behavior and data contracts from the web implementation.
- List stable behavior scenarios before coding (
LOGIN-1,ERROR-1,A11Y-1, etc.). - Extract reusable orchestration, copy, and validation only when it serves both platforms.
- Refactor the canonical web implementation to consume the shared module in the same delivery. Replacing web call sites proves the extraction matches production behavior.
- Add the native adapter layer and screen.
- Update or create the component-mapping doc for the feature.
- Add shared logic tests and focused web/native tests where behavior is risky.
- Record simulator or device verification, manual gaps, and deferred E2E.
- Run a native UX review before calling the screen complete.
Exception: if touching the web shell is unsafe in one pass (hot path, release freeze), document the gap under Deferred behavior, link a follow-up, and still add tests on the shared module so the extraction is verified somewhere other than only native.
Stop and ask when parity would require guessing product behavior, weakening auth or security, or adding a new dependency without team agreement.
Delivery checklist (ordered)
- Name the web spec — exact route and/or component (and parent layout) that defines user-visible behavior.
- Scenarios — stable scenario IDs before implementation.
- Shared headless code — extend the shared package; keep the core UI-agnostic.
- Wire the web consumer — same change, unless deferral is documented in the mapping.
- Native shell — screen, deps adapter, navigation handoff.
- Mapping file — create or update the feature mapping (paths, shared modules, API operations, parallel behavior, divergences, deferred items, test table).
- Index — if you added a new mapping file, add a row to your mappings index.
- Tests — unit tests on shared logic; native tests for risky wiring; run CI targets for touched packages.
- Verification — fill the Verification block (below): shared, web, native, device smoke, UX review date. Do not mark complete with an empty Verification section.
Mapping template (copy into your repo)
# <Feature> mobile mapping
**Last reviewed:** YYYY-MM-DD
## Web
- Component: `<path>`
- Route or parent surface: `<path or route>`
## Native
- Component: `<path>`
- Shell or navigator: `<path>`
## Shared logic
- Hook/module: `<path>`
- Web deps: `<path or none>`
- Native deps: `<path or none>`
- API: `<operation names or endpoints>`
## Parallel behavior
- `<behavior that must stay aligned>`
## Intentional divergences
- `<platform difference and reason>`
## Deferred behavior
- `<known gap or later phase>`
## Test scenarios
| ID | Scenario | Web coverage | Native coverage | Status |
|----|----------|--------------|-----------------|--------|
| `<ID>` | `<expected behavior>` | `<unit/e2e/manual/none>` | `<component/e2e/manual/none>` | `<covered/deferred/manual-only>` |
## Verification
- Shared tests: `<path or none>`
- Web tests: `<path or none>`
- Native tests: `<path or none>`
- Simulator/device checks: `<summary>`
- UX review: `<date/outcome or pending>`
Optional: if the canonical route differs from a smaller embedded web component, name both in the Web section and record whether parity or intentional divergence applies.
React Native / Expo pitfalls
- Keep React, React DOM, React Native, and test renderer versions aligned; mismatches often show up at Metro runtime.
- Expo Go cannot load arbitrary native modules. Guard unsupported modules and track compatibility in tests or docs when you add native packages.
- Install Expo-aligned native dependencies using the workflow your monorepo documents (often from the repo root).
- Watch environment and dev-server ports: mismatched API origins, public env prefixes between web and native, and simulator versus device base URLs are common time sinks.