Free prompt

Turn your Lovable app
into a real mobile app

Copy the prompt below, paste it into Claude Code, and it ports your Lovable web app into a production React Native app — same Supabase project, same data, same look, rebuilt for iOS and Android. The video walks you through the whole thing.

Before you start

Your exported Lovable code, sitting in a folder on your machine.

Where to paste

Claude Code, opened in that same folder — not inside the web app itself.

What you get

An Expo app beside your web app, wired to the same backend, plus a parity report.

The prompt

# Port the Lovable web app to React Native (Expo)

You are scaffolding a React Native (Expo) mobile app from the Kealy Studio production mobile template, and then **porting an existing React web app (exported from Lovable) into it** — same product, same name, same Supabase backend, same data, same visual identity, rebuilt natively for iOS and Android.

The web app already exists in this workspace. It is the **source of truth for everything**: the app's name, its branding, its schema, its behaviour. Read it and infer. **Do not ask me for values you can discover yourself.**

Treat the web app as **read-only**: read every file you need, change nothing inside it.

Follow these steps exactly and in order. If a step fails, stop, show me the full error, and wait — do not improvise an alternative approach.

Stay in the same terminal for the whole prompt, and run it from the workspace folder that already contains the web app.

## The layout we're creating

```
<workspace>/          # the folder you're in now — do not rename it
├── <web app>/        # the Lovable React web app  (already here — READ ONLY)
├── <slug>-mobile/    # the React Native (Expo) app (you create this)
├── CLAUDE.md         # a map of both projects for future AI sessions
└── PORT.md           # route-by-route parity report
```

## Hard rules for this whole prompt

- **Do not modify anything inside the web app folder.** Not one file.
- **Do not rename the workspace folder** or anything else that already exists.
- **Keep Supabase.** Lovable manages the project. The mobile app connects to the **same Supabase project**, the same tables, the same rows, the same users.
- **Do not create migrations, tables, columns, buckets, or edge functions.** The schema is whatever the web app already uses.
- **Assume no Supabase dashboard access** — see Step 6. This constrains the auth implementation.
- **Do not run** `supabase start`, `supabase db push`, `gh auth login`, `eas build`, or any native build.
- **Never** copy a `service_role` key or any other server-side secret into the mobile app.

---

## Step 0 — Read before you write any code

Do not write a single line of code until you have read:

1. The web app in full — `src/App.tsx` (the route table), every file under `src/pages/`, `src/components/`, `src/hooks/`, `src/lib/`, `src/integrations/supabase/`, plus `index.html`, `index.css` and `tailwind.config.ts`.
2. `supabase/` inside the web app if it exists — migrations tell you the real schema, RLS policies tell you what the client is allowed to do, `functions/` tells you what edge functions exist.
3. After Step 3, the mobile template's own `CLAUDE.md` and its existing folder conventions.

**Follow the mobile template's conventions rather than inventing your own.** Use its router, its component library, its styling system, its auth pattern, its data-fetching pattern. You are fitting the web app into the template, not fitting the template around the web app.

## Step 1 — Find the web app and work out what this app is called

From the workspace root, locate the Lovable project — the sibling folder with a `package.json`, a Vite config, and `src/integrations/supabase/client.ts`:

```bash
set -euo pipefail
for d in */; do
  if [ -f "$d/package.json" ] && [ -f "$d/src/integrations/supabase/client.ts" ]; then
    echo "=== candidate: $d"
    grep -i -m1 "<title>" "$d/index.html" 2>/dev/null || true
    grep -i -m1 'og:title' "$d/index.html" 2>/dev/null || true
    grep -m1 '"name"' "$d/package.json" 2>/dev/null || true
    head -3 "$d/README.md" 2>/dev/null || true
  fi
done
```

Now derive the **display name**. Ranked by reliability:

1. The brand text or logo alt text in the app shell — the header, nav, or auth screen. This is what users actually see, so it wins.
2. `<title>` in `index.html`, and the `og:title` meta tag.
3. The `README.md` heading.
4. `package.json` → `name`. **Least reliable.** Lovable leaves this as its scaffold default (`vite_react_shadcn_ts` or similar) far more often than not. If it looks like a template artifact rather than a product name, discard it — do not name the app after it.

Cross-check at least two sources. Then derive:

- **Display name** — as a human writes it, e.g. `Split Ledger`
- **Slug** — lowercase, hyphenated, no spaces, e.g. `split-ledger`

Print both, plus which sources you used and any that disagreed, before continuing. If the sources conflict badly and you genuinely cannot tell what the product is called, **that** is worth stopping to ask me about — but only then.

## Step 2 — Capture the Supabase credentials

Still from the workspace root:

```bash
set -euo pipefail
WEB="<the web app folder from Step 1>"

if [ -f "$WEB/.env" ]; then
  echo "Found $WEB/.env — Supabase keys present:"
  grep -E 'SUPABASE' "$WEB/.env" | sed -E 's/=(.{8}).*/=\1…(hidden)/' || true
else
  echo "No $WEB/.env — falling back to the hard-coded client"
fi

[ -f "$WEB/src/integrations/supabase/types.ts" ] && echo "OK: generated DB types found"
[ -d "$WEB/supabase/migrations" ] && echo "OK: migrations found" || echo "note: no local migrations"
[ -d "$WEB/supabase/functions" ] && ls "$WEB/supabase/functions" || echo "note: no edge functions"
```

Read the actual values and hold them for Step 5:

- **Supabase URL** — `VITE_SUPABASE_URL`
- **Anon / publishable key** — `VITE_SUPABASE_ANON_KEY` or `VITE_SUPABASE_PUBLISHABLE_KEY` (Lovable uses both names depending on export age)
- **Project ID** — `VITE_SUPABASE_PROJECT_ID` if present

If `.env` is missing or incomplete, read them from `src/integrations/supabase/client.ts`, where Lovable often hard-codes them.

If you cannot find a URL and a key from either source, **stop and tell me** — do not invent placeholders and carry on.

Never print the full key in your output. Masked, as above, is fine.

## Step 3 — Download the mobile app template

From the workspace root, using the slug from Step 1:

```bash
set -euo pipefail
[ ! -e "<slug>-mobile" ] || { echo "ERROR: a folder named '<slug>-mobile' already exists here"; exit 1; }

# Resolve the latest mobile template release (public repo, via GitHub's redirect)
TAG=$(curl -fsSLI -o /dev/null -w '%{url_effective}' https://github.com/mosayic-io/mobile-app/releases/latest | sed 's#.*/tag/##')
[ -n "$TAG" ] || { echo "ERROR: could not resolve the latest mobile-app release"; exit 1; }
echo "Mobile template release: $TAG"

# Shallow-clone the tagged release (no zip tooling needed — works in Git Bash
# on Windows too), then drop the template's history so this is a fresh start
git -c advice.detachedHead=false clone -q --depth 1 --branch "$TAG" https://github.com/mosayic-io/mobile-app "<slug>-mobile"
rm -rf "<slug>-mobile/.git"
( cd "<slug>-mobile" && { [ -f .env.example ] && mv .env.example .env || true; } && git init -q && git add -A && git commit -q -m "Initial commit" )
echo "Mobile app ready at <slug>-mobile"
```

Substitute the literal slug into these commands — don't rely on shell variables surviving between tool calls.

## Step 4 — Install the mobile app's dependencies

```bash
set -euo pipefail
( cd "<slug>-mobile" && npm install )
```

If `npm install` fails, show me the error and stop.

## Step 5 — Wire the Supabase credentials into the mobile app

The mobile app talks to **the same Supabase project as the web app**. Two places need the credentials.

**5a — `.env`**

Open `<slug>-mobile/.env` first and use whatever Supabase variable names the template already expects. If it defines none, use:

```
EXPO_PUBLIC_SUPABASE_URL=<url from Step 2>
EXPO_PUBLIC_SUPABASE_ANON_KEY=<anon/publishable key from Step 2>
```

Only variables prefixed `EXPO_PUBLIC_` reach the client bundle. Confirm `.env` is listed in `.gitignore`.

**5b — `eas.json`**

Add the same two variables to the `env` block of **every** build profile so cloud builds aren't missing them. Merge into the existing profiles — do not replace the file:

```json
{
  "build": {
    "development": {
      "env": {
        "EXPO_PUBLIC_SUPABASE_URL": "…",
        "EXPO_PUBLIC_SUPABASE_ANON_KEY": "…"
      }
    },
    "preview":    { "env": { "EXPO_PUBLIC_SUPABASE_URL": "…", "EXPO_PUBLIC_SUPABASE_ANON_KEY": "…" } },
    "production": { "env": { "EXPO_PUBLIC_SUPABASE_URL": "…", "EXPO_PUBLIC_SUPABASE_ANON_KEY": "…" } }
  }
}
```

If `eas.json` doesn't exist in the template, say so in your final report and skip 5b — don't create one.

**5c — Configure the client for React Native**

Point the template's Supabase client at those variables, and make sure it uses `AsyncStorage` for session persistence, `autoRefreshToken: true`, `persistSession: true`, and `detectSessionInUrl: false` (that last one is web-only and breaks on native).

**5d — Verify nothing sensitive leaked**

```bash
set -euo pipefail
cd "<slug>-mobile"
! grep -ril "service_role" . --exclude-dir=node_modules --exclude-dir=.git || { echo "ERROR: service_role key found in the mobile app"; exit 1; }
echo "OK: no server-side secrets in the mobile app"
cd ..
```

## Step 6 — Auth: email and password only

**This is a constraint, not a preference. Read it carefully.**

The Supabase project is managed by Lovable. I have **no access to the Supabase dashboard** — I cannot enable auth providers, I cannot edit email templates, and I **cannot add anything to the redirect URL allow-list**. Build auth so that it needs none of those things.

**Remove Google and Apple sign-in from the template entirely:**

- Delete the Google and Apple sign-in buttons and their handlers from the auth screens.
- Remove `@react-native-google-signin/google-signin`, `expo-auth-session`, `expo-apple-authentication` and any other provider-only packages from `package.json`, then re-run `npm install`.
- Remove the matching entries from `app.json` — plugins, `usesAppleSignIn`, `ios.entitlements` for Apple, any Google `iosUrlScheme` / `googleServicesFile`.
- Remove `EXPO_PUBLIC_GOOGLE_*` / Apple-related variables from `.env`, `.env.example`, and `eas.json`.
- Remove every `signInWithOAuth` and `signInWithIdToken` path and its redirect handling.

**Build only these flows:**

- **Sign in** — `signInWithPassword`. No redirect is involved; this works with zero dashboard configuration.
- **Sign up** — `signUp`. Check the web app first to see whether email confirmation is enabled (does its signup screen show a "check your email" state, or does it get a session immediately?) and mirror that behaviour. If confirmation is on, the emailed link will open the **web** app, not the mobile app — that's expected and fine. After confirming in the browser the user returns to the app and signs in normally. Say so plainly in the UI.
- **Password reset** — do not attempt an in-app deep-link reset. Send the user to the web app's reset page, or call `resetPasswordForEmail` with **no** `redirectTo` so it falls back to the configured Site URL. Then they sign in on mobile with the new password.
- **Session persistence and refresh** — AsyncStorage plus an `AppState` listener that calls `startAutoRefresh` / `stopAutoRefresh`. No redirects, works fine.

**Never pass a custom scheme** (`myapp://…`) to `redirectTo` or `emailRedirectTo`. It isn't on the allow-list, it can't be added, and Supabase will reject it or silently fall back.

Then confirm the removal is clean:

```bash
set -euo pipefail
cd "<slug>-mobile"
grep -rin "google-signin\|GoogleSignin\|apple-authentication\|AppleAuthentication\|signInWithOAuth\|signInWithIdToken\|redirectTo\|emailRedirectTo" . \
  --exclude-dir=node_modules --exclude-dir=.git || echo "OK: clean"
cd ..
```

Any surviving `redirectTo` / `emailRedirectTo` must be justified in your final report or removed.

## Step 7 — Set the app identity

In `<slug>-mobile/app.json`, set only:

- `expo.name` → the display name from Step 1
- `expo.slug` → the slug from Step 1
- `expo.scheme` → the slug (for general deep linking; it is deliberately **not** load-bearing for auth)

Leave bundle identifiers, package names, EAS project IDs, and everything else untouched.

## Step 8 — Write the port plan before porting

Produce a plan and show it to me as part of your final report:

1. **Route inventory** — every route in the web app's `src/App.tsx`, and the mobile screen each one becomes.
2. **Data inventory** — every Supabase table, view, RPC, storage bucket, and edge function the web app touches, with exact names.
3. **Design tokens** — the colour/radius/spacing variables from `index.css` and `tailwind.config.ts`, translated into the template's theme file.
4. **Anything you're dropping** — web-only routes with no mobile value, and why.

## Step 9 — Port the app

Build a real, working mobile app — not a shell, not TODO stubs. Every screen wired to real data.

**Data layer — copy, don't rewrite**

- Copy `src/integrations/supabase/types.ts` from the web app **verbatim** into the mobile app.
- Keep every table name, column name, filter, `.select()` string, RPC name, and bucket name **byte-identical** to the web app. A typo here means silently empty screens.
- Port the queries and mutations as they are. If the web app uses `@tanstack/react-query`, keep it — it works unchanged in React Native.
- Edge functions are called the same way: `supabase.functions.invoke(...)`. Same names, same payloads.
- Respect the existing RLS policies. If a query works on web for a given user, it must work on mobile for that same user.

**Translation map**

| Web (Lovable) | Mobile (Expo) |
|---|---|
| React Router `<Routes>` / `<Route>` | the template's router (Expo Router file routes, or React Navigation — whichever it ships with) |
| `useNavigate()`, `useParams()` | `useRouter()`, `useLocalSearchParams()` (or the template's equivalents) |
| `div`, `span`, `p`, `button` | `View`, `Text`, `Pressable` / the template's `Button` |
| `onClick` | `onPress` |
| `img` | `expo-image` |
| Tailwind classes + shadcn/ui | the template's component library and styling system; port the CSS variables into its theme |
| `localStorage` / `sessionStorage` | `AsyncStorage` (async — await it) |
| `window`, `document`, `navigator` | RN equivalents; there is no DOM |
| `sonner` / toast | the template's toast |
| `react-hook-form` + `zod` | unchanged, works natively |
| Long scrolling lists | `FlatList` / `FlashList`, not `.map()` inside a `ScrollView` |
| Modals / dialogs | native modals or bottom sheets |
| Hover states | pressed / active states |
| Sidebar or top nav | tabs or a drawer |
| Wide data tables | stacked cards |

**Mobile-native quality bar**

- Safe-area insets respected on every screen.
- `KeyboardAvoidingView` on every form.
- Loading, empty, and error states on every data-backed screen.
- Pull-to-refresh on list screens.
- Optimistic updates where the web app has them.
- Nothing hard-codes a phone width — it must work on a small Android phone and a tablet.

**Visual identity**

Match the web app's colours, typography scale, corner radii, spacing rhythm, and iconography. It should be recognisably the same product, laid out for a thumb rather than a mouse.

## Step 10 — Verify

```bash
set -euo pipefail
cd "<slug>-mobile"
npx tsc --noEmit
npm run lint --if-present
cd ..
```

Both must pass clean. Fix what you broke; don't silence errors with `any` or `@ts-ignore`.

Then commit inside the mobile folder:

```bash
set -euo pipefail
( cd "<slug>-mobile" && git add -A && git commit -q -m "Port web app to Expo" )
```

Do **not** run `eas build`, `supabase start`, or `gh auth login`.

## Step 11 — Write `CLAUDE.md` and `PORT.md`

Create `CLAUDE.md` at the workspace root, substituting the real names:

````markdown
# <Display Name>

This workspace holds the two projects that make up the <Display Name> app:

- **`<web app folder>/`** — the React web app exported from Lovable. Source of truth for
  product behaviour, schema usage, and visual identity. Lovable manages the Supabase project.
- **`<slug>-mobile/`** — the React Native (Expo) mobile app, ported from the web app.
  It connects to the **same Supabase project** with the same anon key.

Both apps share one backend. Table, column, RPC, bucket, and edge-function names must stay
identical across the two — changing one without the other breaks the other client.

## Auth constraints

The Supabase project is managed by Lovable Cloud. There is **no Supabase dashboard access**:
auth providers cannot be enabled, email templates cannot be edited, and the redirect URL
allow-list cannot be changed.

Because of that, the mobile app uses **email/password only**. No Google, no Apple, no magic
links, no in-app deep-link password reset. Never pass a custom `myapp://` scheme to
`redirectTo` or `emailRedirectTo` — it is not on the allow-list and cannot be added.
Emailed confirmation and reset links land on the **web** app by design.

Each folder is its own git repository with its own `CLAUDE.md` — open the one you're
working in for project-specific guidance.
````

Create `PORT.md` at the workspace root with:

- a route-by-route table: web route → mobile screen → status (done / partial / dropped)
- every Supabase table, RPC, bucket, and edge function the mobile app touches
- anything intentionally left out, and why
- anything I need to do myself before running the app

## Step 12 — Report back

```bash
ls -la
```

Then tell me, briefly:

- what you decided the app is called, and which sources you inferred that from,
- that the mobile folder was created and `npm install` completed cleanly,
- that the Supabase URL and anon key were copied into `.env` and `eas.json`,
- that Google and Apple sign-in are fully removed and no `redirectTo` remains,
- how many web routes you ported and how many you dropped,
- that `tsc` passes,
- anything you need from me before I run the app,
- and remind me to head back to the Kealy Studio lesson for the next step.

It's long on purpose — every step is there so the AI can't wander off and invent its own version of your app.

The prompt is the easy part

Getting a ported app onto the App Store is where most people stall — signing, store listings, payments, real infrastructure. That's the bit Kealy Studio walks you through, step by step.

The converter is free to use and provided as it is — see the terms of service.