webhouse.appwebhouse.appdocs

Native iOS & Android app that edits any @webhouse/cms server. Capacitor shell, React 19, JWT auth, QR pairing, push notifications.

What it is

webhouse.app is a native mobile companion for the CMS. It edits content, browses media, runs chat, and receives push notifications — against any @webhouse/cms server you point it at. One TypeScript codebase compiles to iOS IPA and Android AAB via Capacitor 8.

It's a server-agnostic client: the app talks to /api/mobile/* endpoints via Bearer JWT and makes no assumptions about a specific bundle id, server version, or brand. You (or a whitelabel reseller) can repackage the same shell under a different name — cms-admin won't notice.

Stack

LayerChoice
ShellCapacitor 8 (native iOS + Android)
UIReact 19 + Vite 5 + Tailwind v3
Routingwouter (1.5 KB)
StateTanStack Query
Animationframer-motion 11
FormsReact Hook Form + Zod
QR scanningjsQR (via getUserMedia, no native plugin)
Biometric@capgo/capacitor-native-biometric
Push@capacitor/push-notifications + Firebase Cloud Messaging

Not React Native, not Expo. Native-feeling via Capacitor, native performance on each platform, one codebase to maintain.

What's shipped

  • Onboarding — user enters their own CMS server URL (BYO-server)
  • QR pairing login — live camera scanner reads a 5-minute pairing token from the desktop admin, exchanges for JWT
  • Email/password fallback/api/mobile/login for non-TOTP accounts
  • Biometric unlock — Face ID / Touch ID on 2nd launch, silent re-auth with stored JWT
  • Home screen — avatar, org dropdown, site list
  • Live preview — phone-safe preview for localhost dev servers via signed proxy (/api/mobile/preview-proxy)
  • Content editing — pages, posts, collections. Richtext, image upload with AI analysis, relation picker, array/object editors
  • Media browser — Photos-style thumbnail grid with AI analysis
  • AI chat — conversational CMS access as a floating action button
  • Push notifications — 6 topics, per-user topic preferences (build_failed, build_succeeded, agent_completed, curation_pending, link_check_failed, scheduled_publish)
  • Swipe-back navigation — left-edge swipe to go back (native iOS feel)
  • Settings — topic toggles, permission status, device management, sign out

Authentication

Bearer JWT in the Authorization header — never cookies. The token is minted by the same createToken(user) helper as the web admin, so the audit trail is unified. Tokens are stored in Capacitor Preferences (iOS Keychain / Android EncryptedSharedPreferences).

Two login paths:

  • QR pairing — scan a QR from the desktop admin, app calls POST /api/mobile/pair/exchange, gets a JWT back in one step. Works with TOTP-protected accounts because the desktop has already done the full 2FA flow.
  • Email/passwordPOST /api/mobile/login, returns JWT + user profile.

Both routes validate on the same session path as web — no parallel auth system to maintain.

API surface

Every mobile endpoint lives under /api/mobile/* and validates Bearer JWT via getMobileSession(req).

RoutePurpose
GET /api/mobile/pingServer identity check (no auth — onboarding)
POST /api/mobile/loginEmail/password → JWT
POST /api/mobile/pairIssue 5-min QR pairing token (desktop session)
POST /api/mobile/pair/exchangeExchange pairing token → JWT
GET /api/mobile/quick-pairOne-URL phone-safari pairing (auto-detects LAN IP)
GET /api/mobile/meAuthenticated user + sites + permissions
GET /api/mobile/preview-proxy?upstream=…&tok=…Signed proxy for localhost preview
POST /api/mobile/push/registerRegister FCM/APNs device token
POST /api/mobile/push/preferencesTopic opt-in/opt-out
POST /api/mobile/push/testTest push delivery
`GET\POST /api/mobile/content/*`Fetch/edit docs, resolve collections
POST /api/mobile/uploadsMedia upload with auto-analysis
POST /api/mobile/chat/*Chat history, memory, streaming

All responses are JSON. No HTML redirects, no cookies, CORS-permissive for capacitor://localhost, https://localhost, ionic://localhost.

LAN IP pairing (dev)

A subtle detail that saves a lot of time. During local development the desktop runs on https://localhost:3010. A phone on the same wifi can't reach localhost — it needs your Mac's LAN IP (e.g. 192.168.1.42).

/api/mobile/pair and /api/mobile/me auto-detect the Mac's first non-loopback IPv4 and rewrite server URLs in the response. The phone gets https://192.168.1.42:3010 without any manual configuration. /api/mobile/quick-pair bakes this into the emitted deep link so a single tap on the phone pairs both the LAN IP and the JWT.

Push notifications

Six topics configured out of the box, each with per-user opt-in/opt-out:

TopicDefaultFires when
build_failedONDeploy fails
build_succeededOFFDeploy succeeds
agent_completedONAgent run finishes
curation_pendingONNew item in curation queue
link_check_failedONBroken link detected
scheduled_publishONContent auto-published

Delivery via Firebase Cloud Messaging (iOS + Android). The device calls POST /api/mobile/push/register after the OS grants a token. Tokens are stored per-user in _data/device_tokens.json.

Build & run

From the repo root:

bash
pnpm install
pnpm webhouse.app:ios       # boots iOS sim, builds Vite, cap syncs, opens app
pnpm webhouse.app:android   # same for Android emulator

Fallback names if pnpm rejects dots in aliases:

bash
pnpm wha:ios
pnpm wha:android

Inside packages/cms-mobile/:

bash
pnpm build            # Vite → dist/
pnpm cap:sync         # sync native changes
pnpm dev              # local Vite dev server (web debugging)
pnpm typecheck        # TS validation
pnpm sim:login        # auto-login helper for iOS sim

Current status

Phase 3+ is shipped — content editing, media, chat, push, QR login, LAN pairing all live in the dev channel. Not yet in the App Store or Play Store. Phase 8 is the TestFlight beta + public release.

A blocker before App Store submission: NSAllowsArbitraryLoads in Info.plist must be removed (HTTPS-only). The preflight-release.sh script gates on this.

Tags:MobileAccess TokensArchitecture
Previous
Brand Voice
Next
Dashboard
JSON API →Edit on GitHub →