Valcraven Docs
Mobile

Web/mobile parity

What the mobile app covers relative to the web app, and the copy-model behind the shared packages/core.

The mobile app is a full-featured client, not a thin companion. It mirrors the web template's core surfaces — auth, dashboard, settings, notifications, and AI chat — using native controls, while reusing the web app's schemas, types, and API client through @valcraven/core. This page maps what's covered and explains the shared-code model.

What the mobile app covers

The app was built out phase-by-phase; each phase maps to a screen (or set of screens) under apps/mobile/src/app/.

AreaScreensNotes
Navigation shell + themeRoot layout, app-headerLight/dark/system theme; tokens mirror the web app.
Full auth(auth)/sign-in, sign-up, forgot-password, two-factorEmail/password, OAuth, TOTP 2FA, and password reset — via @better-auth/expo.
Dashboard + items CRUD(app)/indexThe example resource, same as the web /app.
Settings(app)/settingsProfile, appearance, security, billing (read-only), data export, delete account, legal.
Notifications + native push(app)/notificationsList, mark-read, and native push via expo-notifications.
AI chat(app)/chatStreaming chat over the realtime Worker WebSocket, with voice.
Onboarding, legal, errors, deep linksOnboarding wizard, privacy-policy, termsPlus an ErrorBoundary wrapping the root layout and an offline banner.

Settings in detail

The settings screen (apps/mobile/src/app/(app)/settings.tsx) mirrors the web /settings page with native controls, section by section:

  • Profile — edit name, upload an avatar (picks from the photo library, uploads to /api/files, persists via /api/settings/avatar), and shows sign-in provider, email-verified badge, and "member since".
  • Appearance — light / dark / system theme picker.
  • Security — enable/disable two-factor authentication, including QR enrollment, manual key entry, and backup codes.
  • Billing — read-only plan badge; "Manage billing" opens the Stripe customer portal in the system browser (billing changes happen on the web).
  • Account — export all your data as JSON (shared via the native share sheet) and delete your account.
  • Legal — privacy policy and terms screens.

Where mobile intentionally defers to the web

A few flows are read-only or hand off to the browser on mobile — most notably billing: the app shows your current plan and opens the Stripe portal in the system browser rather than embedding checkout. The admin console is a web-only surface; the mobile app is an end-user client.

The shared-code model: packages/core

Both apps depend on the shared package as "@valcraven/core": "*". That "*" is a workspace dependency: npm resolves it to a local symlink into packages/core, so apps/web and apps/mobile import the same source at build time. Change a schema once and both platforms pick it up.

What packages/core exports (see packages/core/src/index.ts):

  • Zod schemas + inferred types for requests/responses (schemas/).
  • Shared chat logic — the model catalog, message building, and context trimming — so the web and mobile chat clients never drift.
  • HTTP primitives + ApiError, and the typed createApiClient.
  • Better Auth Session / User types (type-only).

The package has a hard constraint: nothing in it may import next/*, drizzle, better-sqlite3, react-dom, Node built-ins, or DOM globals — it must run inside React Native too. That rule is enforced at compile time by its tsconfig.json (no DOM lib, no @types/node).

It's a copy, not a published dependency

@valcraven/core is not a versioned package pulled from a registry. It lives inside your project's monorepo, and a project scaffolded from Valcraven gets its own copy of packages/core. The "*" link is intra-project only — it keeps your apps/web and apps/mobile in sync with each other. Across different apps built from the template, each project's packages/core evolves independently. That's why /valcraven-sync exists: it ports upstream Valcraven improvements (including changes under packages/core/) into a downstream project, rather than relying on a shared dependency bump.

On this page