rn-add-screen — add a new screen to a scaffolded RN/Expo app
Contract
See references/contracts.md (vendored from dev-flow). Key facts:
- Reads
<project-root>/.workflow/meta.json#stack.framework — must be "expo-rn".
- Requires
meta.json#phase ≥ "scaffolded" (run rn-bootstrap first).
- Reads
.workflow/DESIGN.md (the contract location) for the design tokens; the NativeWind classes it emits must exist in the tailwind.config.js that rn-bootstrap generated from it.
- Writes ONE new file under
<project-root>/app/... per call — routes only: Expo Router treats every file under app/ as a route, so nothing else may live there. Optional: screen-private components under <project-root>/components/<feature>/ (L0, OUTSIDE app/ — see Folder structure rules below), hooks under <project-root>/lib/queries/.
- Sets
meta.json#phase = "page_generated" after the first screen, then leaves it (subsequent screens are still page_generated).
- Always idempotent: re-adding the same route detects the existing file and reports.
When this skill applies
- Phase is
scaffolded or page_generated.
- The user describes a screen: name, content, behavior. Or provides a screenshot/wireframe to translate.
- Orchestrator routes here from
dev-flow.
Knowledge dependencies (read these first)
rn-fundamentals/SKILL.md — file layout, modern primitives.
rn-styling/references/patterns.md — root screen pattern, dark mode, FlashList.
rn-expo-router/references/concepts.md — file-based routing rules.
rn-expo-router/references/patterns.md — modal vs push, search params, auth gates.
rn-data-fetching/references/patterns.md — if the screen fetches data, use the patterns there.
rn-components-apis/references/decision-tree.md — which primitive (FlashList vs ScrollView, Pressable, etc.).
Workflow
Step 1 — Verify preconditions
Read .workflow/meta.json. Abort with clear message if:
stack.framework != "expo-rn" → "Wrong stack."
phase < "scaffolded" → "Run rn-bootstrap first."
Step 2 — Understand the screen
Gather from user (one round-trip max — collect everything at once):
- Route path in Expo Router convention:
/profile/[id] → app/profile/[id].tsx. Group? (auth)/sign-in? Modal? See references/screen-patterns.md for the mapping.
- Top-level layout: scroll vs FlashList vs form. Modal or full-screen.
- Data: does it need a query? A mutation? Static?
- Navigation in: how does the user reach it (link from another screen, push, modal, deep link)?
- Navigation out: what does it do on success / back?
If a screenshot is provided, infer the above and confirm with the user before generating.
Step 3 — Pick the right scaffold template
See references/screen-patterns.md for canonical templates:
- List screen (FlashList + TanStack Query)
- Detail screen (typed search params + query)
- Form screen (KeyboardAvoidingView + controlled inputs + mutation)
- Modal screen (presentation: "modal", dismiss button)
- Auth-gated screen (inside
(app)/ group)
Pick ONE template; do not mix unless the screen is genuinely hybrid.
Step 4 — Generate the file
Write to <project-root>/app/<route>.tsx. The file MUST:
- Use
SafeAreaView from react-native-safe-area-context at root.
- Use NativeWind classes from existing
tailwind.config.js tokens (no magic colors).
- Use
Pressable, expo-image, FlashList as appropriate.
- If using data, use TanStack Query with a centralized query key (add to
lib/query-keys.ts if it doesn't exist).
- If using a form, use controlled state + a
useMutation hook for submit.
Step 5 — Update related files (only if necessary)
- If the screen uses a NEW component, write it under
components/<feature>/ (L0, e.g. components/posts/PostCard.tsx for a /posts route) — NEVER under app/<route>/_components/ (Expo Router has no private-folder convention; every file under app/ becomes a route — see the Folder structure rules below). Promote to components/shared/<dominio>/ (L2) only per the Rule of Three.
- If the screen uses a NEW query/mutation, write the hook under
<project-root>/lib/queries/ or lib/mutations/.
- If the screen is reachable from another screen, add a
<Link> there ONLY IF the user explicitly asks.
Step 6 — Verify
Run:
npx tsc --noEmit from project root → must pass.
npx expo-router-typegen (if available) so typed routes refresh.
If typing fails, fix and re-verify before reporting done.
Step 7 — Update meta.json + commit
meta.json#phase: if currently "scaffolded", set to "page_generated". Otherwise leave.
meta.json#history: append { skill: "rn-add-screen", ran_at: <iso>, outputs: [<file paths>], phase_before: <prev>, phase_after: <current> }.
- If git repo:
git add the new files + git commit -m "feat(<route>): add <screen-name> screen".
Common anti-patterns (NEVER do)
- ❌ Add layout configuration (
<Tabs.Screen>, <Stack.Screen>) inside the screen file. Layout lives in _layout.tsx.
- ❌ Rewrite an existing screen unless the user explicitly says "rewrite".
- ❌ Hardcode colors or spacing in the new file — use Tailwind tokens.
- ❌ Use
Image from react-native — expo-image.
- ❌ Use
FlatList for a long list — FlashList.
- ❌ Use
fetch + useEffect for production data — TanStack Query.
- ❌ Touch unrelated files (
tailwind.config.js, app.json, other routes).
Updating meta.json (recommended pattern)
When this skill modifies state (artifact written, phase advanced, history appended), use the canonical script when available:
# Wherever dev-flow is installed (e.g. ~/.claude/skills/dev-flow/), invoke:
python3 .../dev-flow/scripts/update_meta.py <project-root> record-artifact \
--path <relative-path> --produced-by '<this-skill-name>' [--derived-from <p1> <p2> ...]
python3 .../dev-flow/scripts/update_meta.py <project-root> set-phase <new_phase>
python3 .../dev-flow/scripts/update_meta.py <project-root> append-history \
--skill '<this-skill-name>' --inputs '{...}' --outputs '{...}' --phase-after <new_phase>
The script enforces phase monotonicity, normalizes legacy kebab-case aliases (e.g. module-added → module_added), and writes the canonical sha256 + timestamp into meta.json#artifacts. Fall back to direct JSON editing only if the script is not on PATH (and warn the user).
Folder structure rules (canonical — Expo Router hybrid)
Non-negotiable Expo Router constraint: app/ is file-based routing only. Unlike Next.js App Router, Expo Router has no convention for "private", non-routable folders — there is no _-prefix skip rule. Every .tsx/.ts file placed under app/ (aside from a few reserved names like _layout.tsx, +not-found.tsx) is registered as a real route. Putting a component at app/<route>/_components/PostCard.tsx creates a ghost route at that path, not a private folder.
This is Expo Router's settled design, not a gap. The request (expo/expo#44696) was closed won't fix on 2026-06-01: the maintainers "explicitly want people to fall into a pit of success where screens / UI / business logic … are moved into a separate folder", and consider filename conventions brittle because _, + and - are all legal in pathnames. Confirmed mechanically at expo-router@57.0.16: the route scanner has no underscore rule. So assume no private folders inside app/ — permanently, not provisionally. Detail in promote-component/references/colocation-rules.md.
Given that constraint, the rule for this skill is:
app/ = routes only. No components, no hooks, no utils — ever.
Components go OUTSIDE app/, in components/<feature>/<Component>.tsx (kebab-case feature folder named after the screen/domain, e.g. components/posts/PostCard.tsx; complex/compound components get their own subfolder). This is the mobile equivalent of what _components/ does on web.
No src/ prefix: keep app/, components/, lib/ at the project root — consistent with rn-bootstrap's scaffold and the default create-expo-app templates (which don't use src/).
The model (Rule of Three, L0 → L1 → L2 promotion ladder) is unchanged from the general dev-flow contract — only the mobile target paths differ from the web ones documented in references/contracts.md:
| Level |
Web target (_components/ valid) |
Mobile target (this skill) |
| L0 (page-private) |
app/<route>/_components/<Component>.tsx |
components/<feature>/<Component>.tsx |
| L1 (route-group shared) |
app/(group)/_components/<Component>.tsx |
components/<feature>/<Component>.tsx (same physical folder as L0 — Expo has no route-group-scoped component folder; the 2nd use is a tolerated duplicate copy inside the second feature's folder) |
| L2 (globally shared) |
components/shared/<dominio>/<Component>.tsx |
components/shared/<dominio>/<Component>.tsx (same as web) |
Never app/<route>/_components/ on mobile. A previous revision of this skill pointed there by mistake (copied from the Next.js convention) — that guidance is corrected here.
Promotion via promote-component: when the same component pattern appears 3+ times (i.e., copies exist across 3+ components/<feature>/ folders), call the dedicated skill to lift to L2 with import rewriting.
components/shared/<dominio>/ for L2: domain-based naming only (no "shared", "common", "misc").
Sources
- Course: codewithbeto.dev/rnCourse — modules "Components and APIs" + "Style and Design" (paid, distilled).
- Knowledge skills consumed (see above).
1---2name: rn-add-screen3description: Use to add a new screen to an existing Expo + RN app: from a description, a wireframe, or a screenshot, generate the route file in app/ (Expo Router file-based), wire up data fetching via TanStack Query if needed, apply NativeWind classes from the project DESIGN.md tokens, and respect the scaffolded folder layout. Reads .workflow/meta.json with stack.framework="expo-rn" and phase ≥ "scaffolded". Use when dev-flow routes here from scaffolded+expo-rn, or the user says "add a login screen", "create a profile screen from this screenshot", "aggiungi una schermata X". Not for: scaffolding the app (rn-bootstrap), adding backend modules (rn-module-add Wave 3), pure styling changes to an existing screen (rn-styling).4---56# rn-add-screen — add a new screen to a scaffolded RN/Expo app78## Contract910See `references/contracts.md` (vendored from `dev-flow`). Key facts:11- Reads `<project-root>/.workflow/meta.json#stack.framework` — must be `"expo-rn"`.12- Requires `meta.json#phase ≥ "scaffolded"` (run `rn-bootstrap` first).13- Reads `.workflow/DESIGN.md` (the contract location) for the design tokens; the NativeWind classes it emits must exist in the `tailwind.config.js` that `rn-bootstrap` generated from it.14- Writes ONE new file under `<project-root>/app/...` per call — **routes only**: Expo Router treats every file under `app/` as a route, so nothing else may live there. Optional: screen-private components under `<project-root>/components/<feature>/` (L0, OUTSIDE `app/` — see Folder structure rules below), hooks under `<project-root>/lib/queries/`.15- Sets `meta.json#phase = "page_generated"` after the first screen, then leaves it (subsequent screens are still `page_generated`).16- Always idempotent: re-adding the same route detects the existing file and reports.1718## When this skill applies1920- Phase is `scaffolded` or `page_generated`.21- The user describes a screen: name, content, behavior. Or provides a screenshot/wireframe to translate.22- Orchestrator routes here from `dev-flow`.2324## Knowledge dependencies (read these first)2526- `rn-fundamentals/SKILL.md` — file layout, modern primitives.27- `rn-styling/references/patterns.md` — root screen pattern, dark mode, FlashList.28- `rn-expo-router/references/concepts.md` — file-based routing rules.29- `rn-expo-router/references/patterns.md` — modal vs push, search params, auth gates.30- `rn-data-fetching/references/patterns.md` — if the screen fetches data, use the patterns there.31- `rn-components-apis/references/decision-tree.md` — which primitive (FlashList vs ScrollView, Pressable, etc.).3233## Workflow3435### Step 1 — Verify preconditions3637Read `.workflow/meta.json`. Abort with clear message if:38- `stack.framework != "expo-rn"` → "Wrong stack."39- `phase < "scaffolded"` → "Run rn-bootstrap first."4041### Step 2 — Understand the screen4243Gather from user (one round-trip max — collect everything at once):4445- **Route path** in Expo Router convention: `/profile/[id]` → `app/profile/[id].tsx`. Group? `(auth)/sign-in`? Modal? See `references/screen-patterns.md` for the mapping.46- **Top-level layout**: scroll vs FlashList vs form. Modal or full-screen.47- **Data**: does it need a query? A mutation? Static?48- **Navigation in**: how does the user reach it (link from another screen, push, modal, deep link)?49- **Navigation out**: what does it do on success / back?5051If a screenshot is provided, infer the above and confirm with the user before generating.5253### Step 3 — Pick the right scaffold template5455See `references/screen-patterns.md` for canonical templates:56- List screen (FlashList + TanStack Query)57- Detail screen (typed search params + query)58- Form screen (KeyboardAvoidingView + controlled inputs + mutation)59- Modal screen (presentation: "modal", dismiss button)60- Auth-gated screen (inside `(app)/` group)6162Pick ONE template; do not mix unless the screen is genuinely hybrid.6364### Step 4 — Generate the file6566Write to `<project-root>/app/<route>.tsx`. The file MUST:67- Use `SafeAreaView` from `react-native-safe-area-context` at root.68- Use NativeWind classes from existing `tailwind.config.js` tokens (no magic colors).69- Use `Pressable`, `expo-image`, `FlashList` as appropriate.70- If using data, use TanStack Query with a centralized query key (add to `lib/query-keys.ts` if it doesn't exist).71- If using a form, use controlled state + a `useMutation` hook for submit.7273### Step 5 — Update related files (only if necessary)7475- If the screen uses a NEW component, write it under `components/<feature>/` (L0, e.g. `components/posts/PostCard.tsx` for a `/posts` route) — NEVER under `app/<route>/_components/` (Expo Router has no private-folder convention; every file under `app/` becomes a route — see the Folder structure rules below). Promote to `components/shared/<dominio>/` (L2) only per the Rule of Three.76- If the screen uses a NEW query/mutation, write the hook under `<project-root>/lib/queries/` or `lib/mutations/`.77- If the screen is reachable from another screen, add a `<Link>` there ONLY IF the user explicitly asks.7879### Step 6 — Verify8081Run:82- `npx tsc --noEmit` from project root → must pass.83- `npx expo-router-typegen` (if available) so typed routes refresh.8485If typing fails, fix and re-verify before reporting done.8687### Step 7 — Update meta.json + commit8889- `meta.json#phase`: if currently `"scaffolded"`, set to `"page_generated"`. Otherwise leave.90- `meta.json#history`: append `{ skill: "rn-add-screen", ran_at: <iso>, outputs: [<file paths>], phase_before: <prev>, phase_after: <current> }`.91- If git repo: `git add` the new files + `git commit -m "feat(<route>): add <screen-name> screen"`.9293## Common anti-patterns (NEVER do)9495- ❌ Add layout configuration (`<Tabs.Screen>`, `<Stack.Screen>`) inside the screen file. Layout lives in `_layout.tsx`.96- ❌ Rewrite an existing screen unless the user explicitly says "rewrite".97- ❌ Hardcode colors or spacing in the new file — use Tailwind tokens.98- ❌ Use `Image` from `react-native` — `expo-image`.99- ❌ Use `FlatList` for a long list — `FlashList`.100- ❌ Use `fetch + useEffect` for production data — TanStack Query.101- ❌ Touch unrelated files (`tailwind.config.js`, `app.json`, other routes).102103## Updating meta.json (recommended pattern)104105When this skill modifies state (artifact written, phase advanced, history appended), use the canonical script when available:106107```bash108# Wherever dev-flow is installed (e.g. ~/.claude/skills/dev-flow/), invoke:109python3 .../dev-flow/scripts/update_meta.py <project-root> record-artifact \110 --path <relative-path> --produced-by '<this-skill-name>' [--derived-from <p1> <p2> ...]111python3 .../dev-flow/scripts/update_meta.py <project-root> set-phase <new_phase>112python3 .../dev-flow/scripts/update_meta.py <project-root> append-history \113 --skill '<this-skill-name>' --inputs '{...}' --outputs '{...}' --phase-after <new_phase>114```115116The script enforces phase monotonicity, normalizes legacy kebab-case aliases (e.g. `module-added` → `module_added`), and writes the canonical sha256 + timestamp into `meta.json#artifacts`. **Fall back to direct JSON editing only if the script is not on PATH** (and warn the user).117118## Folder structure rules (canonical — Expo Router hybrid)119120**Non-negotiable Expo Router constraint**: `app/` is **file-based routing only**. Unlike Next.js App Router, Expo Router has no convention for "private", non-routable folders — there is no `_`-prefix skip rule. Every `.tsx`/`.ts` file placed under `app/` (aside from a few reserved names like `_layout.tsx`, `+not-found.tsx`) is registered as a real route. Putting a component at `app/<route>/_components/PostCard.tsx` creates a ghost route at that path, not a private folder.121122**This is Expo Router's settled design, not a gap.** The request ([expo/expo#44696](https://github.com/expo/expo/issues/44696)) was closed **`won't fix`** on 2026-06-01: the maintainers *"explicitly want people to fall into a pit of success where screens / UI / business logic … are moved into a separate folder"*, and consider filename conventions brittle because `_`, `+` and `-` are all legal in pathnames. Confirmed mechanically at `expo-router@57.0.16`: the route scanner has no underscore rule. So assume no private folders inside `app/` — permanently, not provisionally. Detail in `promote-component/references/colocation-rules.md`.123124Given that constraint, the rule for this skill is:125126- **`app/` = routes only.** No components, no hooks, no utils — ever.127- **Components go OUTSIDE `app/`, in `components/<feature>/<Component>.tsx`** (kebab-case feature folder named after the screen/domain, e.g. `components/posts/PostCard.tsx`; complex/compound components get their own subfolder). This is the mobile equivalent of what `_components/` does on web.128- No `src/` prefix: keep `app/`, `components/`, `lib/` at the project root — consistent with `rn-bootstrap`'s scaffold and the default `create-expo-app` templates (which don't use `src/`).129- The **model** (Rule of Three, L0 → L1 → L2 promotion ladder) is unchanged from the general dev-flow contract — only the mobile **target paths** differ from the web ones documented in `references/contracts.md`:130131 | Level | Web target (`_components/` valid) | Mobile target (this skill) |132 |---|---|---|133 | L0 (page-private) | `app/<route>/_components/<Component>.tsx` | `components/<feature>/<Component>.tsx` |134 | L1 (route-group shared) | `app/(group)/_components/<Component>.tsx` | `components/<feature>/<Component>.tsx` (same physical folder as L0 — Expo has no route-group-scoped component folder; the 2nd use is a tolerated duplicate copy inside the second feature's folder) |135 | L2 (globally shared) | `components/shared/<dominio>/<Component>.tsx` | `components/shared/<dominio>/<Component>.tsx` (same as web) |136137- **Never** `app/<route>/_components/` on mobile. A previous revision of this skill pointed there by mistake (copied from the Next.js convention) — that guidance is corrected here.138- **Promotion via `promote-component`**: when the same component pattern appears 3+ times (i.e., copies exist across 3+ `components/<feature>/` folders), call the dedicated skill to lift to L2 with import rewriting.139- **`components/shared/<dominio>/`** for L2: domain-based naming only (no "shared", "common", "misc").140141## Sources142143- Course: codewithbeto.dev/rnCourse — modules "Components and APIs" + "Style and Design" (paid, distilled).144- Knowledge skills consumed (see above).