Add a mobile (Expo) companion app

Goal

The user just imported a web app and the migration to the Replit pnpm_workspace stack has finished. Add a new Expo mobile artifact that mirrors the web app's data layer.

This task starts AFTER the migration task is merged. The migration may have landed in one of two shapes — see Step 1 below — so your first job is to detect which mode this is and mirror what the web app actually uses. The web artifact must stay untouched.

In scope

  • Detect the web artifact created by the migration task and read enough of it to understand the app's screens, state, and API usage.
  • Create a new Expo artifact via createArtifact({ artifactType: "expo", slug: "<web-slug>-mobile", previewPath: "/", title: "<Title> Mobile" }).
  • Translate the web app's screens / flows into a native mobile UX using the expo and mobile-ui skills. Aim for a mobile-native feel, not a phone-shaped copy of the website.
  • (Mode A) Reuse the existing API client (@workspace/api-client-react) — the same generated hooks the web app uses. (Mode B) Mirror the web app's actual data layer (exported fetch helper or third-party SDK).
  • Wire up setBaseUrl in app/_layout.tsx so requests target the deployed API.
  • Configure app.json with a sensible name, slug, and bundleIdentifier derived from the existing web app's name.
  • (Mode A) Additive backend / shared-lib changes when mobile parity needs them. It is OK to add new endpoints to artifacts/api-server/, new fields to lib/api-spec/openapi.yaml (and regenerate lib/api-client-react/), or a new column to lib/db/ schema for mobile-specific concerns (push tokens, device registration, mobile-only resources). Constraint: purely additive. Do not change or remove existing endpoints, fields, or columns the web app depends on; do not break the web app's existing contract. If the change cannot be additive, propose a follow-up task instead.

Out of scope

  • Do not modify the web artifact (artifacts/<web-slug>/). It is the user's product; leave it untouched.
  • Do not change existing backend behavior. Additive endpoints / fields are OK (see "In scope"); changing or removing existing ones is not.
  • New product features the web app doesn't have, beyond the mobile-specific plumbing called out above. The companion is a mirror, not an expansion.
  • Strict typecheck — use // @ts-ignore to keep moving. Functional + visual parity matters more than zero TS errors.

Pre-existing state (read, don't relitigate)

After the migration task merged, the workspace always contains:

  • artifacts/<web-slug>/ — the web artifact ported from the original import. Read this to understand screens, flows, and API usage.

In Mode A the workspace also contains:

  • artifacts/api-server/ — backend (Express + Drizzle).
  • lib/api-spec/openapi.yaml — API contract.
  • lib/api-client-react/src/generated/ — generated React Query hooks. Reuse these in the Expo app via @workspace/api-client-react.
  • lib/db/ — Drizzle schema + client.

In Mode B (frontend-only or legacy fetch) none or some of those lib/* and artifacts/api-server/ paths may be missing — work with what's actually there.

You are adding a sibling under artifacts/, nothing else.

User communication

Tell the user what's happening at each major phase. Keep it short and non-technical:

  • Before starting: "Looking at your web app to plan the mobile version..."
  • Before creating the artifact: "Setting up your mobile app scaffold..."
  • Before screen porting: "Building the mobile screens..."
  • Before wiring up the API: "Connecting the mobile app to your existing backend..."
  • Before mark_task_complete: "Running checks to make sure the mobile app builds and renders. This may take a minute."

Steps

  1. Detect what the migration produced

The migration recipes can land in one of two shapes. Before reading skills, in one parallel batch:

  • ls artifacts/ — confirm what artifact kinds exist.
  • cat artifacts/<web-slug>/package.json and search the web artifact for imports of @workspace/api-client-react.
  • ls lib/ and check for lib/api-spec/openapi.yaml + lib/api-client-react/.

This puts the companion in one of two modes:

  • Mode A — backend + codegen present. artifacts/api-server/ exists, lib/api-spec/openapi.yaml is populated, and the web artifact imports @workspace/api-client-react. The mobile app reuses the same generated hooks and the same backend.
  • Mode B — frontend-only or legacy fetch. Either artifacts/api-server/ does not exist (frontend-only import), OR it exists but the web app calls a hand-written fetch layer / third-party services directly without @workspace/api-client-react. In this mode:
    • Do not invent an artifacts/api-server/ if one doesn't exist.
    • Do not introduce @workspace/api-client-react if the web app isn't using it.
    • Mirror the web app's actual data layer: import the same fetch helper if one is exported, or call the same third-party services from the mobile app.
    • Skip every instruction below that references setBaseUrl, @workspace/api-client-react, or lib/api-spec/openapi.yaml — those are Mode A only.

Pick the mode now. The rest of this recipe assumes Mode A unless explicitly marked otherwise.

  1. Read the relevant skills first

Before doing anything else, read .local/skills/expo/SKILL.md and .local/skills/mobile-ui/SKILL.md. These contain the canonical patterns for Expo in this workspace (file-based routing, font loading, app.json requirements, mobile UX conventions). Also skim .local/skills/pnpm-workspace/SKILL.md for the workspace conventions you'll inherit.

  1. Inspect the existing web artifact (ONE parallel batch)
  • ls artifacts/ — confirm the web artifact's slug
  • cat artifacts/<web-slug>/package.json
  • ls artifacts/<web-slug>/src/ (or wherever the source lives)
  • Read the routing entry point — typically src/App.tsx / src/main.tsx or the routes module — to enumerate screens
  • Read 1-2 representative screens to see how they consume the data layer (Mode A: @workspace/api-client-react hooks; Mode B: fetch helper or third-party SDK)
  • Mode A: cat lib/api-spec/openapi.yaml (skim — confirms the endpoints you have)

After reading, write down: (a) the list of screens to port, (b) the auth pattern (if any), (c) the primary data hooks or fetch calls the web app uses.

  1. Create the Expo artifact
   await createArtifact({
     artifactType: "expo",
     slug: "<web-slug>-mobile",
     previewPath: "/",
     title: "<Web Title> Mobile",
   })

Then pnpm install at the workspace root. If install fails, fix the error and retry — do not skip.

  1. Configure app.json

Set name, slug, and bundleIdentifier (e.g. com.replit.<webslug>mobile) so Expo Launch can build. Keep the scaffold's expo-router plugin, expo-font plugin, typedRoutes: true, and the Replit origin — those are required, do not remove them.

  1. Wire up the data layer

Mode A only — skip in Mode B.

In artifacts/<web-slug>-mobile/app/_layout.tsx, at module level (before the component):

   import { setBaseUrl } from "@workspace/api-client-react";
   const domain = process.env.EXPO_PUBLIC_DOMAIN;
   if (domain) setBaseUrl(`https://${domain}`);

The Expo scaffold ships its own QueryClient — either created locally in app/_layout.tsx (new QueryClient()) or imported from @/lib/query-client in newer scaffolds. Use whichever the scaffold ships. @workspace/api-client-react does not export a queryClient; it only exports the generated API surface plus setBaseUrl and setAuthTokenGetter.

Mode B: If the web app exports a fetch helper (e.g. lib/api/client.ts), import it the same way. If the web app calls a third-party API (e.g. Supabase, Firebase) directly, configure the same SDK in the mobile app — do not route through a non-existent backend.

  1. Translate screens (one screen at a time, simplest first)

For each web screen identified in step 3, create the matching expo-router file under app/: - Top-level route → app/index.tsx - Tabs (if the web app has primary navigation) → app/(tabs)/<name>.tsx - Detail / modal screens → app/<name>.tsx

For each screen: - Reuse the same data layer the web app uses — Mode A: the @workspace/api-client-react hooks; Mode B: the web app's exported fetch helper or the third-party SDK. - Replace web layout primitives (div, button, Tailwind classes) with React Native equivalents (View, Pressable, StyleSheet.create()). - Replace router calls: useNavigate() / <Link> → router.push('/x') from expo-router. - Use design tokens from a constants/colors.ts file (create one if needed) — do not inline hex values. - Prefer mobile-native patterns (bottom tabs, swipe gestures, native pickers) over literal copies of desktop layouts.

  1. Auth (if the web app has login)

Mirror whatever auth stack the web app uses — do not pick a different one. Migrations from authenticated imports often rewrite the original auth (e.g. Lovable's supabase.auth.*, Base44's base44.auth.*) into Clerk, while other paths land on Replit Auth. Whatever is in artifacts/<web-slug>/ is what the mobile app must reuse, so the user's session is shared end-to-end and the same backend authorizes both clients.

To detect what's in use, in one parallel batch: - cat artifacts/<web-slug>/package.json — look for @clerk/clerk-react, @clerk/expo, or a Replit Auth helper. - Search the web artifact for sign-in / session calls (e.g. useUser, useAuth, SignIn, SignedIn, useSession, signInWithGoogle). - Check artifacts/api-server/ for the auth middleware (Clerk vs Replit Auth headers).

Then implement the matching mobile flow: - Clerk → follow .local/skills/clerk-auth/SKILL.md (Expo setup section). Use @clerk/expo (not @clerk/clerk-expo), wire <ClerkProvider> in app/_layout.tsx with the tokenCache from @clerk/expo/token-cache, and reuse the web app's publishable key. - Replit Auth → follow .local/skills/replit-auth/SKILL.md. - (Mode A only) Both auth paths require setAuthTokenGetter from @workspace/api-client-react so the generated API client attaches a bearer token to every request. Without it, sign-in succeeds but every protected backend call goes anonymous and 401s. Wire it in a layout that runs after the auth provider mounts (e.g. (home)/_layout.tsx); the canonical snippets live in the clerk-auth and replit-auth skills. Mode B: thread the token through whatever data layer the web app uses. - Other → ask the user before inventing a new flow.

Do not roll your own session handling. Mode A: do not duplicate the auth backend — both clients hit the same artifacts/api-server/.

  1. Verify the app runs

Restart the Expo workflow and check that Metro bundles without errors. If there are runtime errors, read the Metro output, fix, and restart. Do not run pnpm run build manually — the workflow handles env vars and build automatically.

  1. Pre-completion checklist
- [ ] All web screens have a mobile counterpart under `app/`?
- [ ] (Mode A) All API calls go through `@workspace/api-client-react` / (Mode B) the mobile app uses the same data-layer approach as the web app?
- [ ] `app.json` has a valid `name`, `slug`, `bundleIdentifier`?
- [ ] (Mode A) `setBaseUrl` is wired in `_layout.tsx`?
- [ ] Workflow restart bundles cleanly and the app renders on the Expo preview?
  1. Propose follow-up tasks

    Before completing, call proposeFollowUpTasks with the three tasks below in a single call. Now that the user has both a web app AND a mobile companion, suggest the natural next steps that turn the project into something they can show off and ship.

    Do not prescribe the exact createArtifact API in the task descriptions — the agent who picks up the follow-up will consult the create-artifact skill for the correct artifactType and other parameters. Just describe the artifact / outcome.

    1. "Make a promo video for your app" (category: next_steps) — a brand-new video artifact (Replit animation) alongside the web and mobile apps. The description must make clear this is a separate artifact, not a modification of either app and not just a screen recording — it's a short animated showcase built in Replit's video tooling that highlights the app's best features with motion graphics.
    2. "Create a pitch deck for your app" (category: next_steps) — a brand-new slides artifact alongside the web and mobile apps. The description must make clear this is a separate artifact presenting the app's value proposition, core features, and key user flows for demos and stakeholders.
    3. "Set up push notifications & deep linking" (category: next_steps) — extend the mobile app with Expo push notifications and deep links so users get re-engagement signals and shareable URLs back into specific screens. The description should call out that this builds on the mobile companion, not the web app, and requires additive backend support (token registration endpoint).
  2. Use the mark_task_complete tool, then STOP. Call mark_task_complete immediately. Do not send any further messages. Do not propose new features. Do not offer enhancements. Do not ask "what's next?". Do not propose an app icon, polish, or App Store work. The follow-up tasks proposed in step 11 are how the user engages with next steps; the user will inspect the running mobile app on the preview themselves. Yield immediately after mark_task_complete returns.

Note to the code reviewer

When reviewing the diff for this task:

  • HONOR Out of scope. Do NOT reject for items explicitly marked out of scope (e.g. typecheck errors, any types — explicitly permitted).
  • Do NOT reject for differences from the web app's exact layout. Mobile-native patterns are the goal; pixel-for-pixel parity is not.
  • Do NOT flag unused scaffold files (default components/ui/*, default assets/). Focus on files the agent actually wrote.
  • DO reject changes to artifacts/<web-slug>/ — the web artifact must stay untouched. Any diff there is a scope leak.
  • For artifacts/api-server/ and lib/*: additive changes are allowed (new endpoints, new fields, new columns) when mobile parity needs them. Reject only if the change modifies or removes something the web app already depends on, i.e. anything that breaks the existing web contract.
  • Apply only the criteria from ## Goal + ## In scope. Reject only for real regressions (mobile app fails to bundle, screens missing, breaking changes to existing backend behavior, web artifact touched).

Expo-specific pitfalls

  • Do not change app.json to app.config.ts. Static app.json is required — dynamic config breaks Expo Launch.
  • Native package versions — Expo has strict version requirements. If you pnpm add a native module, use npx expo install instead so it picks the SDK-compatible version.
  • expo-router is filesystem-based — every file under app/ is a route. Don't put helper components there; put them under components/.
  • Don't port web-only libraries. react-router-dom, wouter, framer-motion (sometimes), Tailwind classes, raw <div> — none of these work in React Native. Find the React Native equivalent.