# Rn Bootstrap

> Scaffold a new Expo + React Native app from a PROJECT.md + PRD.md + DESIGN.md using the opinionated stack (Expo Router, TypeScript, NativeWind v4, Zustand, TanStack Query, Reanimated, expo-image, FlashList). Reads .workflow/meta.json with stack.framework="expo-rn" and phase in {prd_drafted, design_extracted}. Produces a running Expo app at the project root, sets phase to "scaffolded". Always idempotent: re-running detects existing files and skips. Use when dev-flow routes here from prd_drafted+expo-rn, or the user says "scaffolda app expo from PRD", "create RN app from this PRD/DESIGN", "bootstrap expo app". Not for: adding screens (rn-add-screen, Wave 2), modules (rn-module-add, Wave 3), Next.js scaffolding (design-md-to-app — different stack).

- Skill: `lukedj78/rn-bootstrap` (Agent Skill, multi-file: 11 files)
- Install (CLI): `npx skillmds@latest add lukedj78/rn-bootstrap`
- Raw SKILL.md: https://api.skillmd.com/api/skills/lukedj78/rn-bootstrap/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Product & Planning
- Author: lukedj78 (https://skillmd.com/u/lukedj78)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/lukedj78/rn-bootstrap

---


# rn-bootstrap — scaffold Expo + RN from PRD/DESIGN

## Contract

See `references/contracts.md` (vendored from `dev-flow`). Key facts:
- Reads `<project-root>/.workflow/meta.json#stack.framework` — must be `"expo-rn"`.
- Reads `<project-root>/.workflow/{PROJECT.md, PRD.md, DESIGN.md}` (the contract location — a `DESIGN.md` at the project root is accepted as a legacy fallback with a warning). DESIGN.md is required for tokens; if absent, uses defaults from `references/stack-defaults.md`.
- Writes the app to `<project-root>/` (the same directory).
- Sets `meta.json#phase = "scaffolded"` on success.
- Always idempotent: re-running detects existing `package.json` + `app/` and exits 0.

## When this skill applies

- Orchestrator routes here from `dev-flow` when `meta.json#stack.framework == "expo-rn"` and `meta.json#phase ∈ {prd_drafted, design_extracted}`.
- User says: "scaffolda app expo", "create RN app from PRD".

## Knowledge dependencies (read these first)

- `rn-fundamentals/SKILL.md` — confirms Expo SDK + New Architecture + TypeScript + npm.
- `rn-styling/references/nativewind-setup.md` — the 7-step NativeWind setup (this skill automates it).
- `rn-expo-router/references/concepts.md` — folder layout for `app/`.
- `references/i18n-rn.md` — **golden rule 2**: every frontend ships i18n from day one. Wire it during this scaffold (device-locale detection, `locales/{en,it}.json`, provider in `app/_layout.tsx`, persisted locale choice) and record `stack.i18n` + `stack.locales = ["en","it"]` in `meta.json`. Follow that reference — don't improvise the setup; adding i18n later touches every screen.

## Workflow

### Step 1 — Verify preconditions

Read `<project-root>/.workflow/meta.json`. Abort with a clear message if:
- file missing → "Run `dev-flow init_workflow.py` first."
- `stack.framework != "expo-rn"` → "This skill is for stack.framework=expo-rn. For Next.js use design-md-to-app."
- `phase ∉ {prd_drafted, design_extracted}` → "Expected phase prd_drafted or design_extracted, got X."

If `package.json` + `app/` already exist at project root: print "Already bootstrapped, nothing to do", set phase to scaffolded if not already, exit 0.

### Step 2 — Run create-expo-app

Run `scripts/init-expo-app.sh <project-root> <app-name>`. The app name comes from `meta.json#project_name` (or `PROJECT.md` title).

### Step 3 — Install opinionated stack

Run `scripts/install-stack.sh <project-root>`. Installs NativeWind v4 (with Tailwind 3.4 pin), Zustand, TanStack Query, Reanimated, RNGH, expo-image, FlashList.

### Step 4 — Wire NativeWind from DESIGN.md tokens

Run `npx tsx scripts/wire-nativewind.ts <project-root>`. Generates `tailwind.config.js`, `global.css`, `babel.config.js`, `metro.config.js`, `nativewind-env.d.ts` and `declarations.d.ts` (ambient `declare module` for `*.css` and the formatjs polyfills — TypeScript 6 rejects side-effect imports without a declaration, error TS2882). Reads tokens from a fenced ` ```json tokens ` block in `.workflow/DESIGN.md` (accepts LF + CRLF line endings).

### Step 5 — Generate folder structure + boilerplate

Create (only if absent — idempotent):
- `app/_layout.tsx` — Stack root, imports `../global.css`, renders `<Stack />`.
- `app/index.tsx` — hello-world screen using NativeWind classes (wraps in `SafeAreaView` from `react-native-safe-area-context`).
- `components/`, `lib/`, `store/`, `types/`, `assets/` — empty dirs with `.gitkeep`.
- `.env.example` — empty stub with `EXPO_PUBLIC_API_URL=`.
- `tsconfig.json` — extend `expo/tsconfig.base`, add `paths` for `@/*` **without `baseUrl`**: TypeScript 6 makes `baseUrl` a hard error (TS5101, removed in 7.0); `paths` resolve relative to the tsconfig on their own. Add `"types": ["jest"]` once `rn-write-tests` installs the test stack. Include `declarations.d.ts` (written by step 4) in `include` if the project narrows it.

Also patch `app.json`:
- `expo.scheme` — set to a kebab-case of `meta.json#project_name`.
- `expo.experiments.typedRoutes` — `true`.
- `expo.newArchEnabled` — **do not write it.** Always on since SDK 55; the key is ignored and absent from the config schema.
- `expo.plugins` — add `"expo-router"`.

### Step 6 — Verify (scripts/verify.ts)

Run `npx tsx scripts/verify.ts <project-root>`. If exit code != 0, do NOT bump phase. Report failures from `references/post-bootstrap-checklist.md`.

The last check runs `npx expo export --platform ios` into a temp dir: it is the only check that proves Metro can build the app, which `tsc` cannot — a missing expo-router peer (`expo-linking`, `expo-constants`, `react-native-screens`) type-checks clean and fails on the first `expo start`. It takes 30–60 s; `RN_BOOTSTRAP_SKIP_BUNDLE=1` skips it when the user is iterating on the scaffold. Then **run the empty app once on a simulator before adding anything** — an app that has never started has nowhere to debug from when a later package breaks it.

### Step 7 — Update meta.json + commit

Update `meta.json`:
- `stack`: merge `{ framework: "expo-rn", ui: "nativewind", auth: null, db: null, payments: null, deploy: null }` (existing keys preserved if already set).
- `stack_config`: merge `{ expo_sdk: "<X>", state_lib: "zustand", data_lib: "tanstack-query" }`.
- `phase`: set to `"scaffolded"`.
- `history`: append `{ skill: "rn-bootstrap", ran_at: <iso>, outputs: ["package.json", "app/", "tailwind.config.js", ...], phase_before: <prev>, phase_after: "scaffolded" }`.

If `<project-root>` is a git repo, create a commit: `chore: scaffold Expo + RN app via rn-bootstrap`.

## Updating meta.json (recommended pattern)

When this skill modifies state (artifact written, phase advanced, history appended), use the canonical script when available:

```bash
# 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 private-folder convention — there is no `_`-prefix skip rule, so every file placed under `app/` (aside from reserved names like `_layout.tsx`, `+not-found.tsx`) becomes a real route. A previous revision of this skeleton put `_components/` folders inside `app/`, copied from the Next.js convention — that was wrong for Expo (it creates ghost routes) and is corrected below. **Still true at `expo-router@57.0.16`**, read off the shipped `build/getRoutesCore.js`: the entire
ignore list is `+html`, `+native-intent`, `+api`, `+middleware` and whatever you pass as
`options.ignore` — **no underscore rule anywhere**. The only underscore-aware names in the build are
`_layout` and `_sitemap`. So `app/` still has zero tolerance for non-route files, and `_components/`
still becomes a ghost route. And it is **settled, not pending** — I wrote "the request is still open" here earlier today without
checking, and it is wrong: [expo/expo#44696](https://github.com/expo/expo/issues/44696) was closed
**`won't fix`** on 2026-06-01, because Expo Router wants components outside `app/` by design.
`options.ignore` is the escape hatch that exists today, and it is configuration rather than convention.

This skill scaffolds the canonical RN structure, hybrid model (spec: `docs/superpowers/specs/2026-06-06-folder-structure-refactor.md`, adapted for Expo Router):

```
app/                        # ROUTES ONLY — no components, no hooks, no utils
├── (auth)/                 # opt-in based on stack.route_groups
│   ├── _layout.tsx
│   └── sign-in.tsx
├── (app)/
│   ├── _layout.tsx         # redirect to (auth) if !session
│   ├── (tabs)/             # opt-in based on stack.route_groups
│   │   ├── _layout.tsx
│   │   ├── feed/
│   │   └── profile/
│   └── settings.tsx
└── _layout.tsx             # root: GestureHandlerRootView + ThemeProvider

components/                 # ALL non-route UI lives here, outside app/
├── ui/                     # NativeWind primitives
├── theme/                  # ThemeProvider, useThemeColor
├── shared/                 # L2 per dominio (created empty)
└── <feature>/              # L0/L1 screen-private components (e.g. components/feed/, components/profile/)
                             # created on demand by rn-add-screen, not scaffolded empty here

lib/                        # api, supabase, secure-store, utils
store/                      # Zustand cross-feature (auth-store, app-preferences)
hooks/                      # RN-specific (useColorScheme, useKeyboard)
assets/                     # images/, fonts/
```

No `src/` prefix: `app/`, `components/`, `lib/` etc. stay at the project root — matches the default `create-expo-app` templates (which don't use `src/`) and keeps `rn-add-screen` / `promote-component` consistent with this scaffold.

Read `meta.json#stack.route_groups` to decide which route groups to scaffold. Empty array → flat routing (rare for mobile).

Components follow Rule of Three for promotion (L0 → L1 → L2), targeting `components/<feature>/` (L0/L1) and `components/shared/<dominio>/` (L2) — **never** `app/<route>/_components/`. See `rn-add-screen` for screen-level details and `promote-component` for the promotion mechanics.

## Sources

- Course: codewithbeto.dev/rnCourse — free lessons 5-6 (Creating Your First App, Project Structure).
- Official: https://docs.expo.dev/get-started/create-a-project/
- Official: https://www.nativewind.dev/docs/getting-started/installation — ⚠️ the old `nativewind.dev/v4/getting-started/expo-router` URL **404s** as of 2026-08-26 (the site dropped the `/v4/` prefix and no longer has an Expo-Router-specific page); Expo Router setup is folded into Installation.

