Documentation
IntroductionArchitectureMonorepo & ToolingRunning Locally
Screen Flow MapUser JourneysPersonas & RolesWeb ScreensClient Portal
Convex OverviewData ModelFunctions ReferenceAuth & SessionsRoles & PermissionsJobs & Providers
Web Design SystemAdmin Console DesignPatterns & Conventions
Web AppiOS AppMarketing Website
Testing & HarnessCI Pipeline

Architecture

Architecture

One typed backend, multiple clients. The v2 platform is designed around a single Convex deployment that is the source of truth for schema, business rules, and realtime state, with thin reactive clients on top.

System shape

Clients
Web App
Next.js · :3200
staff app · customer portal · platform admin
iOS App
SwiftUI · iOS 26
customer companion, mock AppStore today
Marketing Website
Next.js · :3300
standalone — no backend dependency
Admin Console
Next.js · :3400
standalone — docs + flow map (this app)
live queries · mutations · actions
Convex client (roadmap)
packages/backend — Convex deployment
schema.ts
31 org-scoped tables + shared validators
lib/functions.ts
orgQuery / orgMutation — the tenancy seam
Feature modules
clients · pets · visits · orders · portal · …
crons — sweep 15m · reminders 30m
http — ICS / JSON feeds
providers — Stripe · Resend email
auth — PBKDF2 + sessions
authz — requirePolicy / hasPolicy
auditLogs on mutations
storage — uploads, signed URLs
seed — deterministic demo
Every query is a live subscription — realtime is the default, not a feature. Typed errors (UNAUTHENTICATED / FORBIDDEN / ORG_NOT_FOUND) map to client error screens.
imports enums + business rules
shared vocabulary
packages/domain
ROLE_RANK · VISIT_TRANSITIONS · POLICY_DEFAULTS · labels · zero deps
System architecture — one typed Convex backend, four clients, one shared domain vocabulary

The five load-bearing seams

The rewrite is organized around explicit seams — places where an implementation can be swapped without touching feature code.

1. Tenant isolation: orgQuery / orgMutation

Every org-scoped Convex function is built with the custom wrappers in packages/backend/convex/lib/functions.ts. They inject {sessionToken, orgSlug} args, resolve {user, org, membership} into context, and throw typed errors on failure. Feature code never filters by orgId manually — it reads ctx.org. This is the single place tenancy is enforced.

2. AuthZ: role hierarchy + policy board

A minimum-role hierarchy (owner > manager > supervisor > employee > volunteer > customer) combined with a per-org role-policy board: each sensitive action (e.g. orders.refund, visits.checkin_override) has a default minimum role that orgs can override in Settings → Roles. Enforcement happens server-side in requirePolicy; the UI's role gates are UX-only. See Roles & Permissions.

3. Identity: local credentials behind the Clerk seam

Today auth is a local PBKDF2 credential provider plus a sessions table issuing 14-day opaque bearer tokens. This sits exactly where Clerk will slot in for production — the session-resolution helper is the only thing that changes. See Auth & Sessions.

4. Visit lifecycle: one transition() function

Visits move through planned → due_in → in_house → overdue → completed (plus cancelled / no_show) via a single server-enforced state machine (VISIT_TRANSITIONS in the domain package). A 15-minute cron sweeps time-based transitions (due-in windows, overdue grace periods) using each org's configurable check-in/out windows.

5. Money: integer cents + provider interface

All money is integer cents end to end. Payments go through a provider interface: dev (instant simulated settle), manual (cash/desk), and stripe (production driver with SetupIntents, off-session PaymentIntents, and idempotency keys). fluidpay is reserved in the enums but has no driver yet.

Clients

  • apps/web — the product. All pages are client components using live Convex queries (useOrgQuery wrappers). Three surfaces in one app: the staff app under /o/[slug]/*, the customer portal at /o/[slug]/portal (renders without the staff shell), and the platform-admin landlord surface at /platform. See Web App.
  • apps/ios — SwiftUI customer companion. A single @Observable AppStore mirrors the Convex schema with mock data today; each intent method is annotated with the Convex mutation it maps to 1:1. See iOS App.
  • apps/website — standalone marketing site with zero backend dependencies. See Marketing Website.
  • apps/admin — this console. Standalone, no backend dependency; serves internal documentation and the screen-flow map.

Realtime as a default

Convex queries are live subscriptions, so realtime isn't a feature that was built — it's the default. The facility board (board.live), dashboard (dashboard.daily), and message threads update across sessions with no refresh; the Playwright harness explicitly verifies a second logged-in session sees board changes without reloading.

Platform (landlord) surface

Outside org scoping there is a small landlord layer: public platform.directory (marketing directory), platform.apply (tenant applications from the /apply wizard), and platform-owner-only application review that provisions a new org on approval. Platform-owner status = holding an active owner membership in any org.