Skip to main content

Mo - Technical Architecture Document

1. Architecture Overview

Hybrid Architecture Strategy

┌─────────────────────────────────────────────────────────────────┐
│ FRONTEND │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Phase 1: Next.js 15 Phase 2+: Flutter │
│ ├── Web App (PWA) ├── iOS App Store │
│ ├── Landing Page (SEO) ├── Google Play Store │
│ ├── Marketing Pages ├── macOS Desktop │
│ └── Mobile Web └── Windows Desktop │
│ │
│ │ │ │
│ └──────────┬─────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Shared REST API │ │
│ └─────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────┼───────────────────────────────────────┐
│ ▼ │
│ BACKEND │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Next.js API Routes + Edge Functions │
│ ├── REST API (Flutter-ready) │
│ ├── Authentication (Clerk) │
│ ├── AI Integration (Vercel AI SDK + Claude) │
│ ├── Payments (Stripe) │
│ └── Real-time (WebSockets/Polling) │
│ │
│ │ │
│ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ PostgreSQL │ │ Redis │ │ Vercel Blob │ │
│ │ (Neon) │ │ (Cache) │ │ (Storage) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘

Platform Coverage Timeline

PhasePlatformTechnologyTimeline
Phase 1Web (all browsers)Next.js PWAMonths 1-3
Phase 1Mobile WebNext.js PWA (installable)Months 1-3
Phase 2iOS App StoreFlutterMonths 4-6
Phase 2Google Play StoreFlutterMonths 4-6
Phase 3macOS DesktopFlutterMonths 6+
Phase 3Windows DesktopFlutterMonths 6+

2. Tech Stack

Core Stack

LayerTechnologyVersionPurpose
FrameworkNext.js15.xFull-stack React framework
LanguageTypeScript5.xType safety
RuntimeNode.js20.x LTSServer runtime
StylingTailwind CSS4.xUtility-first CSS
Componentsshadcn/uiLatestAccessible UI primitives
DatabasePostgreSQL16.xPrimary data store
DB HostNeonServerlessServerless PostgreSQL
ORMDrizzleLatestType-safe queries
AuthClerkLatestAuthentication & users
PaymentsStripeLatestSubscriptions
AIVercel AI SDK + ClaudeLatestCoach AI
HostingVercelProDeployment & edge

Enhanced UI/UX Stack

CategoryTechnologyPurpose
AnimationsFramer MotionPage transitions, micro-interactions
3D GraphicsThree.js + React Three Fiber3D coach avatars
3D ScenesSplineVisual 3D scene design
Gestures@use-gesture/reactSwipe, drag, pinch
ChartsRechartsAnimated progress charts
Celebrationscanvas-confettiPR & milestone celebrations
Lottielottie-reactComplex vector animations
IconsLucide ReactConsistent iconography
SoundHowler.jsOptional audio feedback

Infrastructure Stack

ServiceTechnologyPurpose
HostingVercelEdge deployment
DatabaseNeonServerless PostgreSQL
CacheUpstash RedisSession, rate limiting
Blob StorageVercel BlobImages, avatars
CDNVercel Edge NetworkGlobal asset delivery
MonitoringVercel AnalyticsPerformance tracking
Error TrackingSentryError monitoring
EmailResendTransactional emails

Development Stack

ToolPurpose
Package Managerpnpm
LintingESLint + Prettier
TestingVitest + Playwright
Git HooksHusky + lint-staged
CI/CDGitHub Actions + Vercel
API DocsSwagger/OpenAPI

3. Database Schema

Entity Relationship Diagram

┌─────────────────────────────────────────────────────────────────┐
│ USERS │
├─────────────────────────────────────────────────────────────────┤
│ id UUID PRIMARY KEY │
│ clerk_id VARCHAR UNIQUE NOT NULL │
│ email VARCHAR UNIQUE NOT NULL │
│ full_name VARCHAR │
│ avatar_url VARCHAR │
│ height_cm INTEGER │
│ current_weight DECIMAL │
│ goal_weight DECIMAL │
│ experience ENUM (beginner/intermediate/advanced/returning) │
│ fitness_goal ENUM (lose_fat/build_muscle/recomp/strength) │
│ units ENUM (imperial/metric) DEFAULT imperial │
│ theme ENUM (dark/light/system) DEFAULT dark │
│ subscription ENUM (free/pro) DEFAULT free │
│ onboarded_at TIMESTAMP │
│ created_at TIMESTAMP DEFAULT NOW() │
│ updated_at TIMESTAMP │
└─────────────────────────────────────────────────────────────────┘

│ 1:many

┌─────────────────────────────────────────────────────────────────┐
│ USER_COACHES │
├─────────────────────────────────────────────────────────────────┤
│ id UUID PRIMARY KEY │
│ user_id UUID REFERENCES users(id) │
│ coach_id UUID REFERENCES coaches(id) │
│ is_primary BOOLEAN DEFAULT false │
│ context ENUM (all/workouts/rest/nutrition/motivation) │
│ created_at TIMESTAMP DEFAULT NOW() │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ COACHES │
├─────────────────────────────────────────────────────────────────┤
│ id UUID PRIMARY KEY │
│ name VARCHAR NOT NULL (Sam, Max, Sage, etc.) │
│ slug VARCHAR UNIQUE NOT NULL │
│ gender ENUM (male/female/neutral) │
│ role VARCHAR (Buddy, Drill Sergeant, etc.) │
│ personality TEXT │
│ voice_example VARCHAR │
│ avatar_url VARCHAR │
│ avatar_3d_url VARCHAR │
│ system_prompt TEXT NOT NULL │
│ is_active BOOLEAN DEFAULT true │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ USER_PROGRAMS │
├─────────────────────────────────────────────────────────────────┤
│ id UUID PRIMARY KEY │
│ user_id UUID REFERENCES users(id) │
│ program_id UUID REFERENCES programs(id) │
│ current_phase INTEGER DEFAULT 1 │
│ current_week INTEGER DEFAULT 1 │
│ status ENUM (active/paused/completed) │
│ started_at TIMESTAMP DEFAULT NOW() │
│ completed_at TIMESTAMP │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ PROGRAMS │
├─────────────────────────────────────────────────────────────────┤
│ id UUID PRIMARY KEY │
│ name VARCHAR NOT NULL │
│ slug VARCHAR UNIQUE NOT NULL │
│ description TEXT │
│ duration_weeks INTEGER │
│ experience ENUM (beginner/intermediate/advanced/all) │
│ goal ENUM (lose_fat/build_muscle/recomp/strength) │
│ is_active BOOLEAN DEFAULT true │
└─────────────────────────────────────────────────────────────────┘

│ 1:many

┌─────────────────────────────────────────────────────────────────┐
│ PROGRAM_PHASES │
├─────────────────────────────────────────────────────────────────┤
│ id UUID PRIMARY KEY │
│ program_id UUID REFERENCES programs(id) │
│ phase_number INTEGER NOT NULL │
│ name VARCHAR (Foundation, Hypertrophy, etc.) │
│ weeks INTEGER │
│ description TEXT │
│ focus VARCHAR │
└─────────────────────────────────────────────────────────────────┘

│ 1:many

┌─────────────────────────────────────────────────────────────────┐
│ PROGRAM_DAYS │
├─────────────────────────────────────────────────────────────────┤
│ id UUID PRIMARY KEY │
│ phase_id UUID REFERENCES program_phases(id) │
│ day_number INTEGER (1-7) │
│ name VARCHAR (Push A, Pull B, Legs A, etc.) │
│ workout_type ENUM (push/pull/legs/upper/lower/full/rest) │
│ is_rest_day BOOLEAN DEFAULT false │
└─────────────────────────────────────────────────────────────────┘

│ 1:many

┌─────────────────────────────────────────────────────────────────┐
│ PROGRAM_EXERCISES │
├─────────────────────────────────────────────────────────────────┤
│ id UUID PRIMARY KEY │
│ program_day_id UUID REFERENCES program_days(id) │
│ exercise_id UUID REFERENCES exercises(id) │
│ order INTEGER NOT NULL │
│ sets INTEGER NOT NULL │
│ reps_min INTEGER │
│ reps_max INTEGER │
│ rpe_target DECIMAL │
│ rest_seconds INTEGER │
│ notes TEXT │
│ is_warmup BOOLEAN DEFAULT false │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ EXERCISES │
├─────────────────────────────────────────────────────────────────┤
│ id UUID PRIMARY KEY │
│ name VARCHAR NOT NULL │
│ slug VARCHAR UNIQUE NOT NULL │
│ category ENUM (compound/isolation/cardio/mobility) │
│ equipment VARCHAR[] (barbell, dumbbell, cable, etc.) │
│ primary_muscles VARCHAR[] (chest, back, shoulders, etc.) │
│ secondary_muscles VARCHAR[] │
│ instructions TEXT │
│ tips TEXT[] │
│ common_mistakes TEXT[] │
│ demo_url VARCHAR │
│ video_url VARCHAR │
│ is_active BOOLEAN DEFAULT true │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ WORKOUTS │
├─────────────────────────────────────────────────────────────────┤
│ id UUID PRIMARY KEY │
│ user_id UUID REFERENCES users(id) │
│ program_day_id UUID REFERENCES program_days(id) │
│ date DATE NOT NULL │
│ status ENUM (planned/in_progress/completed/skipped) │
│ started_at TIMESTAMP │
│ completed_at TIMESTAMP │
│ duration_min INTEGER │
│ coach_greeting TEXT │
│ coach_summary TEXT │
│ notes TEXT │
│ mood_before INTEGER (1-5) │
│ mood_after INTEGER (1-5) │
│ created_at TIMESTAMP DEFAULT NOW() │
└─────────────────────────────────────────────────────────────────┘

│ 1:many

┌─────────────────────────────────────────────────────────────────┐
│ WORKOUT_SETS │
├─────────────────────────────────────────────────────────────────┤
│ id UUID PRIMARY KEY │
│ workout_id UUID REFERENCES workouts(id) │
│ exercise_id UUID REFERENCES exercises(id) │
│ set_number INTEGER NOT NULL │
│ weight DECIMAL │
│ reps INTEGER │
│ rpe DECIMAL │
│ is_warmup BOOLEAN DEFAULT false │
│ is_pr BOOLEAN DEFAULT false │
│ notes TEXT │
│ completed_at TIMESTAMP │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ WEIGHT_ENTRIES │
├─────────────────────────────────────────────────────────────────┤
│ id UUID PRIMARY KEY │
│ user_id UUID REFERENCES users(id) │
│ weight DECIMAL NOT NULL │
│ date DATE NOT NULL │
│ notes TEXT │
│ created_at TIMESTAMP DEFAULT NOW() │
│ UNIQUE(user_id, date) │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ PERSONAL_RECORDS │
├─────────────────────────────────────────────────────────────────┤
│ id UUID PRIMARY KEY │
│ user_id UUID REFERENCES users(id) │
│ exercise_id UUID REFERENCES exercises(id) │
│ weight DECIMAL NOT NULL │
│ reps INTEGER NOT NULL │
│ estimated_1rm DECIMAL │
│ achieved_at TIMESTAMP NOT NULL │
│ workout_set_id UUID REFERENCES workout_sets(id) │
│ created_at TIMESTAMP DEFAULT NOW() │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ CHAT_MESSAGES │
├─────────────────────────────────────────────────────────────────┤
│ id UUID PRIMARY KEY │
│ user_id UUID REFERENCES users(id) │
│ coach_id UUID REFERENCES coaches(id) │
│ role ENUM (user/assistant) │
│ content TEXT NOT NULL │
│ context JSONB (workout data, stats, etc.) │
│ created_at TIMESTAMP DEFAULT NOW() │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ STREAKS │
├─────────────────────────────────────────────────────────────────┤
│ id UUID PRIMARY KEY │
│ user_id UUID REFERENCES users(id) UNIQUE │
│ current_streak INTEGER DEFAULT 0 │
│ longest_streak INTEGER DEFAULT 0 │
│ last_workout DATE │
│ updated_at TIMESTAMP │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ SUBSCRIPTIONS │
├─────────────────────────────────────────────────────────────────┤
│ id UUID PRIMARY KEY │
│ user_id UUID REFERENCES users(id) UNIQUE │
│ stripe_customer_id VARCHAR │
│ stripe_subscription_id VARCHAR │
│ plan ENUM (free/pro) │
│ status ENUM (active/canceled/past_due) │
│ current_period_start TIMESTAMP │
│ current_period_end TIMESTAMP │
│ created_at TIMESTAMP DEFAULT NOW() │
└─────────────────────────────────────────────────────────────────┘

Indexes

-- Performance indexes
CREATE INDEX idx_workouts_user_date ON workouts(user_id, date);
CREATE INDEX idx_workout_sets_workout ON workout_sets(workout_id);
CREATE INDEX idx_weight_entries_user_date ON weight_entries(user_id, date);
CREATE INDEX idx_personal_records_user_exercise ON personal_records(user_id, exercise_id);
CREATE INDEX idx_chat_messages_user ON chat_messages(user_id, created_at DESC);
CREATE INDEX idx_user_coaches_user ON user_coaches(user_id);

4. API Design

API Principles

  • RESTful design (Flutter-compatible)
  • JSON responses
  • JWT authentication via Clerk
  • Consistent error format
  • Pagination on list endpoints
  • Rate limiting

Base URL

Production: https://api.mo.fitness/v1
Development: http://localhost:3000/api/v1

Authentication

All authenticated endpoints require:

Header: Authorization: Bearer <clerk_jwt_token>

API Endpoints

Auth (Handled by Clerk)

POST   /auth/signup          - Create account
POST /auth/login - Sign in
POST /auth/logout - Sign out
POST /auth/forgot-password - Request reset
POST /auth/reset-password - Reset password
GET /auth/oauth/google - Google OAuth
GET /auth/oauth/apple - Apple OAuth

Users

GET    /users/me                    - Get current user profile
PATCH /users/me - Update profile
DELETE /users/me - Delete account
GET /users/me/coaches - Get selected coaches
PUT /users/me/coaches - Update coach selections
GET /users/me/settings - Get user settings
PATCH /users/me/settings - Update settings
POST /users/me/onboarding - Complete onboarding

Programs

GET    /programs                    - List all programs
GET /programs/:slug - Get program details
POST /programs/:slug/enroll - Enroll in program
GET /programs/current - Get user's current program
PATCH /programs/current - Update progress (phase/week)

Workouts

GET    /workouts/today              - Get today's workout
GET /workouts/upcoming - Get upcoming workouts
GET /workouts/:id - Get workout details
POST /workouts - Start a workout
PATCH /workouts/:id - Update workout (complete, skip)
DELETE /workouts/:id - Delete workout
GET /workouts/history - Workout history (paginated)

Workout Sets

POST   /workouts/:id/sets           - Log a set
PATCH /workouts/:id/sets/:setId - Update a set
DELETE /workouts/:id/sets/:setId - Delete a set

Exercises

GET    /exercises                   - List all exercises
GET /exercises/:slug - Get exercise details
GET /exercises/search - Search exercises
GET /exercises/by-muscle/:muscle - Get by muscle group

Weight Tracking

GET    /weight                      - Get weight history
POST /weight - Log weight
PATCH /weight/:id - Update entry
DELETE /weight/:id - Delete entry
GET /weight/stats - Weekly averages, trends

Progress & Stats

GET    /progress/overview           - Dashboard stats
GET /progress/prs - Personal records
GET /progress/streaks - Streak data
GET /progress/charts/weight - Weight chart data
GET /progress/charts/strength - Strength chart data
GET /progress/charts/volume - Volume chart data

Coach (AI)

POST   /coach/chat                  - Send message (streaming)
GET /coach/chat/history - Chat history
DELETE /coach/chat/history - Clear chat
GET /coach/greeting - Get contextual greeting
GET /coach/tip - Get workout tip

Subscriptions

GET    /subscriptions/current       - Get current subscription
POST /subscriptions/checkout - Create Stripe checkout
POST /subscriptions/portal - Get Stripe billing portal
POST /subscriptions/cancel - Cancel subscription

Webhooks

POST   /webhooks/stripe             - Stripe events
POST /webhooks/clerk - Clerk events

Response Format

Success Response

{
"success": true,
"data": { ... },
"meta": {
"page": 1,
"limit": 20,
"total": 100
}
}

Error Response

{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid weight value",
"details": [
{ "field": "weight", "message": "Must be a positive number" }
]
}
}

Rate Limiting

EndpointLimit
Auth endpoints10/min
Coach chat30/min
General API100/min
Webhooks1000/min

5. Project Structure

mo/
├── apps/
│ ├── web/ # Next.js Application
│ │ ├── app/
│ │ │ ├── (auth)/ # Auth routes
│ │ │ │ ├── login/
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── signup/
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── forgot-password/
│ │ │ │ │ └── page.tsx
│ │ │ │ └── layout.tsx
│ │ │ │
│ │ │ ├── (marketing)/ # Public pages
│ │ │ │ ├── page.tsx # Landing (MoHome)
│ │ │ │ ├── pricing/
│ │ │ │ ├── about/
│ │ │ │ ├── privacy/
│ │ │ │ ├── terms/
│ │ │ │ └── layout.tsx
│ │ │ │
│ │ │ ├── (app)/ # Authenticated app
│ │ │ │ ├── dashboard/ # MoBoard
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── workout/ # MoLog
│ │ │ │ │ ├── page.tsx
│ │ │ │ │ ├── [id]/
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ └── active/
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── exercises/ # MoGuide
│ │ │ │ │ ├── page.tsx
│ │ │ │ │ └── [slug]/
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── coach/ # MoCoach
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── progress/ # MoStats
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── body/ # MoBody
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── program/ # MoPhase
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── settings/ # MoSettings
│ │ │ │ │ ├── page.tsx
│ │ │ │ │ ├── profile/
│ │ │ │ │ ├── coaches/ # MoVoice
│ │ │ │ │ ├── subscription/
│ │ │ │ │ └── notifications/
│ │ │ │ └── layout.tsx
│ │ │ │
│ │ │ ├── onboarding/ # MoStart
│ │ │ │ ├── page.tsx
│ │ │ │ ├── goals/
│ │ │ │ ├── experience/
│ │ │ │ ├── stats/
│ │ │ │ ├── quiz/
│ │ │ │ ├── coaches/
│ │ │ │ └── program/
│ │ │ │
│ │ │ ├── api/ # API Routes
│ │ │ │ └── v1/
│ │ │ │ ├── users/
│ │ │ │ ├── workouts/
│ │ │ │ ├── exercises/
│ │ │ │ ├── programs/
│ │ │ │ ├── weight/
│ │ │ │ ├── progress/
│ │ │ │ ├── coach/
│ │ │ │ ├── subscriptions/
│ │ │ │ └── webhooks/
│ │ │ │
│ │ │ ├── layout.tsx # Root layout
│ │ │ ├── loading.tsx # Global loading
│ │ │ ├── error.tsx # Global error
│ │ │ ├── not-found.tsx # 404 page
│ │ │ └── globals.css # Global styles
│ │ │
│ │ ├── components/
│ │ │ ├── ui/ # shadcn/ui components
│ │ │ │ ├── button.tsx
│ │ │ │ ├── card.tsx
│ │ │ │ ├── input.tsx
│ │ │ │ ├── dialog.tsx
│ │ │ │ └── ...
│ │ │ │
│ │ │ ├── mo/ # Mo-specific components
│ │ │ │ ├── MoBoard/
│ │ │ │ │ ├── DashboardCard.tsx
│ │ │ │ │ ├── TodayWorkout.tsx
│ │ │ │ │ ├── StreakCounter.tsx
│ │ │ │ │ └── CoachGreeting.tsx
│ │ │ │ ├── MoLog/
│ │ │ │ │ ├── WorkoutTimer.tsx
│ │ │ │ │ ├── SetLogger.tsx
│ │ │ │ │ ├── ExerciseCard.tsx
│ │ │ │ │ └── RestTimer.tsx
│ │ │ │ ├── MoCoach/
│ │ │ │ │ ├── ChatBubble.tsx
│ │ │ │ │ ├── CoachAvatar.tsx
│ │ │ │ │ ├── CoachAvatar3D.tsx
│ │ │ │ │ └── TypingIndicator.tsx
│ │ │ │ ├── MoStats/
│ │ │ │ │ ├── WeightChart.tsx
│ │ │ │ │ ├── StrengthChart.tsx
│ │ │ │ │ └── PRBadge.tsx
│ │ │ │ ├── MoBody/
│ │ │ │ │ ├── WeightInput.tsx
│ │ │ │ │ └── WeightTrend.tsx
│ │ │ │ └── shared/
│ │ │ │ ├── CoachSelector.tsx
│ │ │ │ ├── BottomNav.tsx
│ │ │ │ ├── Header.tsx
│ │ │ │ └── MoTagline.tsx
│ │ │ │
│ │ │ ├── animations/ # Animation components
│ │ │ │ ├── PageTransition.tsx
│ │ │ │ ├── Confetti.tsx
│ │ │ │ ├── PRCelebration.tsx
│ │ │ │ └── StreakFire.tsx
│ │ │ │
│ │ │ └── 3d/ # Three.js components
│ │ │ ├── CoachModel.tsx
│ │ │ ├── CoachScene.tsx
│ │ │ └── Canvas.tsx
│ │ │
│ │ ├── lib/
│ │ │ ├── db/
│ │ │ │ ├── schema.ts # Drizzle schema
│ │ │ │ ├── index.ts # DB client
│ │ │ │ ├── queries/
│ │ │ │ │ ├── users.ts
│ │ │ │ │ ├── workouts.ts
│ │ │ │ │ ├── exercises.ts
│ │ │ │ │ └── ...
│ │ │ │ └── migrations/
│ │ │ │
│ │ │ ├── coaches/ # Centralized AI Coach System
│ │ │ │ ├── index.ts # Main exports
│ │ │ │ ├── types.ts # Coach types & interfaces
│ │ │ │ ├── profiles.ts # Coach personality profiles
│ │ │ │ ├── constants.ts # Model configs, rate limits
│ │ │ │ ├── tasks.ts # Task configurations
│ │ │ │ ├── builder.ts # Prompt composition
│ │ │ │ └── examples/ # Few-shot learning examples
│ │ │ │ ├── index.ts # Example registry
│ │ │ │ ├── schema.ts # Example types
│ │ │ │ ├── max/ # Max's curated examples
│ │ │ │ ├── nova/ # Nova's curated examples
│ │ │ │ └── sage/ # Sage's curated examples
│ │ │ │
│ │ │ ├── ai/
│ │ │ │ ├── chat.ts # Chat utilities
│ │ │ │ └── context.ts # Context builders
│ │ │ │
│ │ │ ├── auth/
│ │ │ │ └── clerk.ts # Clerk utilities
│ │ │ │
│ │ │ ├── stripe/
│ │ │ │ ├── client.ts
│ │ │ │ └── webhooks.ts
│ │ │ │
│ │ │ ├── utils/
│ │ │ │ ├── cn.ts # Class name utility
│ │ │ │ ├── format.ts # Formatters
│ │ │ │ ├── calculate.ts # Fitness calculations
│ │ │ │ └── date.ts # Date utilities
│ │ │ │
│ │ │ └── constants/
│ │ │ ├── coaches.ts # Coach definitions
│ │ │ ├── exercises.ts # Exercise data
│ │ │ ├── taglines.ts # Mo taglines
│ │ │ └── config.ts # App config
│ │ │
│ │ ├── hooks/
│ │ │ ├── useWorkout.ts
│ │ │ ├── useTimer.ts
│ │ │ ├── useOffline.ts
│ │ │ ├── useHaptic.ts
│ │ │ └── useCoach.ts
│ │ │
│ │ ├── stores/ # Zustand stores
│ │ │ ├── workout-store.ts
│ │ │ └── offline-store.ts
│ │ │
│ │ ├── types/
│ │ │ ├── api.ts
│ │ │ ├── database.ts
│ │ │ └── coach.ts
│ │ │
│ │ ├── public/
│ │ │ ├── coaches/ # Coach avatars
│ │ │ ├── exercises/ # Exercise demos
│ │ │ ├── sounds/ # Audio files
│ │ │ ├── animations/ # Lottie files
│ │ │ ├── manifest.json # PWA manifest
│ │ │ └── sw.js # Service worker
│ │ │
│ │ ├── next.config.js
│ │ ├── tailwind.config.ts
│ │ ├── drizzle.config.ts
│ │ ├── package.json
│ │ └── tsconfig.json
│ │
│ └── mobile/ # Flutter (Phase 2)
│ ├── lib/
│ ├── ios/
│ ├── android/
│ └── pubspec.yaml

├── packages/
│ └── shared/ # Shared utilities
│ ├── api-types.ts # API type definitions
│ ├── validation.ts # Shared validation schemas
│ └── constants.ts # Shared constants

├── infrastructure/
│ ├── database/
│ │ └── seed.sql # Seed data
│ └── docker-compose.yml # Local development

├── docs/
│ ├── 01-brand-and-vision.md
│ ├── 02-product-definition.md
│ ├── 03-technical-architecture.md # This document
│ └── ...

├── .github/
│ └── workflows/
│ ├── ci.yml
│ └── deploy.yml

├── package.json # Workspace root
├── pnpm-workspace.yaml
└── README.md

6. AI Coach System Architecture

Overview

The AI coach system is centralized in /lib/coaches/ for consistency across all features. This architecture ensures coach personalities are maintained uniformly whether in chat, plan feedback, check-ins, or greetings.

Coach Categories

CategoryCoachesPurpose
Domain ExpertsMax, Nova, SageSpecialized knowledge (training, data, recovery)
Personality CoachesSam, Ace, Chip, RexDistinct communication styles
AdaptiveMoAll-in-one coach that adapts to user needs

Coach Profiles

Each coach has a centralized profile in lib/coaches/profiles.ts:

interface CoachProfile {
slug: CoachSlug;
name: string;
role: string;
category: 'domain-expert' | 'personality' | 'adaptive';
expertise?: string[];
personality: {
tone: string[];
traits: string[];
avoids: string[];
};
systemPromptCore: string;
responseStyle: {
length: 'brief' | 'moderate' | 'detailed';
formality: 'casual' | 'balanced' | 'formal';
useEmoji: boolean;
useData: boolean;
};
}

Task-Based Configuration

Different features use different coaches configured in lib/coaches/tasks.ts:

TaskActive CoachesDescription
plan-feedbackMax, Nova, SageDomain experts for plan creation
chatAll coachesUser's selected coaches
greetingPrimary coachContextual greetings
check-inContext-appropriateAutomated check-ins

Prompt Composition

The builder pattern in lib/coaches/builder.ts composes prompts:

// For plan feedback
const { systemPrompt, userPrompt } = buildPlanFeedbackPrompt(
coachSlug,
phase,
context,
examples,
{ userName: 'John' }
);

// For chat
const { systemPrompt, userPrompt } = buildChatPrompt(
coachSlug,
message,
context,
examples,
{ userName: 'John', isGroupChat: false }
);

Few-Shot Learning

Curated examples in lib/coaches/examples/ improve response quality:

interface CoachExample {
id: string;
phase?: PlanFeedbackPhase;
input?: string;
output: string;
demonstrates: string[];
}

Examples are retrieved based on coach, task, and phase:

const examples = getExamplesForPhase('max', 'plan-feedback', 'goal_selection');

Constants

Centralized in lib/coaches/constants.ts:

// Model configuration
export const DEFAULT_MODEL = 'claude-sonnet-4-20250514';

// Token limits by use case
export const MAX_TOKENS_BRIEF = 150; // Quick feedback
export const MAX_TOKENS_MODERATE = 500; // Standard chat
export const MAX_TOKENS_DETAILED = 1000; // Detailed analysis

// Rate limiting
export const RATE_LIMIT_REQUESTS = 30;
export const RATE_LIMIT_WINDOW_MS = 60000;

Integration Points

FeatureFileIntegration
Plan Feedbackapp/api/v1/plans/feedback/route.tsUses buildPlanFeedbackPrompt()
Coach Chatapp/api/v1/coach/chat/route.tsUses buildChatPrompt()
Greetingsapp/api/v1/coach/greeting/route.tsUses getCoachProfile()
Check-inslib/coach-check-ins.tsUses getCoachProfile()

7. Enhanced UI Features

Animation System

FeatureTechnologyImplementation
Page TransitionsFramer Motion + View Transitions APISmooth navigation between screens
Micro-interactionsFramer MotionButton press, toggle, input focus
Loading StatesSkeleton + ShimmerPerceived performance
Celebrationscanvas-confetti + LottiePR, streak milestones
3D AvatarsThree.js + React Three FiberCoach representations
ChartsRecharts + Framer MotionAnimated data visualization
Gestures@use-gesture/reactSwipe, drag, pinch

Animation Examples

// Page transition wrapper
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3, ease: "easeInOut" }}
>
{children}
</motion.div>

// Button with haptic feedback
<motion.button
whileTap={{ scale: 0.95 }}
onClick={() => {
haptic.impact('medium');
onPress();
}}
>
Log Set
</motion.button>

// PR celebration
<motion.div
initial={{ scale: 0, rotate: -180 }}
animate={{ scale: 1, rotate: 0 }}
transition={{ type: "spring", stiffness: 200 }}
>
<Confetti />
<CoachAvatar3D expression="excited" />
<h1>NEW PR!</h1>
</motion.div>

Visual Design Tokens

/* Dark theme (default) */
:root {
--mo-bg-primary: #0a0a0f;
--mo-bg-secondary: #14141f;
--mo-bg-card: #1a1a2e;
--mo-accent-primary: #00d4aa; /* Teal/Cyan */
--mo-accent-secondary: #7c3aed; /* Purple accent */
--mo-text-primary: #ffffff;
--mo-text-secondary: #a1a1aa;
--mo-border: #27272a;
--mo-success: #22c55e;
--mo-warning: #f59e0b;
--mo-error: #ef4444;
}

/* Light theme */
[data-theme="light"] {
--mo-bg-primary: #ffffff;
--mo-bg-secondary: #f4f4f5;
--mo-bg-card: #ffffff;
--mo-text-primary: #09090b;
--mo-text-secondary: #71717a;
--mo-border: #e4e4e7;
}

PWA Features

// manifest.json
{
"name": "Mo - Build Momentum",
"short_name": "Mo",
"description": "AI fitness coaching that matches your personality",
"start_url": "/dashboard",
"display": "standalone",
"background_color": "#0a0a0f",
"theme_color": "#00d4aa",
"icons": [
{ "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png" }
]
}

Offline Support

// Offline workout storage using Dexie.js
const db = new Dexie('MoOffline');
db.version(1).stores({
pendingWorkouts: '++id, date, status',
pendingSets: '++id, workoutId, exerciseId',
cachedExercises: 'id, slug'
});

// Background sync when online
if ('serviceWorker' in navigator && 'SyncManager' in window) {
navigator.serviceWorker.ready.then((registration) => {
registration.sync.register('sync-workouts');
});
}

8. Security

Authentication Flow

1. User signs up/in via Clerk
2. Clerk issues JWT token
3. All API requests include Bearer token
4. Server validates token with Clerk
5. User ID extracted from token for queries

Security Measures

MeasureImplementation
AuthenticationClerk (OAuth 2.0, JWT)
AuthorizationRow-level security in queries
Input ValidationZod schemas on all endpoints
Rate LimitingUpstash Redis
HTTPSEnforced via Vercel
CORSStrict origin policy
SQL InjectionDrizzle ORM parameterized queries
XSSReact auto-escaping + CSP headers
CSRFSameSite cookies
SecretsEnvironment variables (Vercel)

Data Privacy

  • All user data encrypted at rest (Neon)
  • HTTPS for all traffic
  • No sensitive data in logs
  • GDPR-compliant data export/delete
  • Privacy policy and terms required

9. Performance Targets

MetricTargetHow
First Contentful Paint< 1.5sSSR, optimized fonts
Largest Contentful Paint< 2.5sImage optimization, lazy loading
Time to Interactive< 3.5sCode splitting, minimal JS
Cumulative Layout Shift< 0.1Skeleton loaders, reserved space
Animation Frame Rate60fpsGPU acceleration, will-change
Lighthouse Score> 90All optimizations
Initial Bundle< 200kbTree shaking, dynamic imports
API Response< 200msEdge functions, caching

Optimization Strategies

// Dynamic imports for heavy components
const CoachAvatar3D = dynamic(
() => import('@/components/3d/CoachAvatar3D'),
{ loading: () => <AvatarSkeleton /> }
);

// Image optimization
<Image
src={exercise.demoUrl}
alt={exercise.name}
width={400}
height={300}
placeholder="blur"
loading="lazy"
/>

// API route edge config
export const runtime = 'edge';
export const preferredRegion = 'iad1';

10. Monitoring & Analytics

ToolPurpose
Vercel AnalyticsPerformance, Web Vitals
SentryError tracking, session replay
PostHogProduct analytics, feature flags
UpstashRate limit monitoring

Key Metrics to Track

  • Daily/Weekly Active Users
  • Workout completion rate
  • Coach chat usage
  • Feature adoption
  • Conversion (free → pro)
  • Churn rate
  • Performance metrics

11. Development Workflow

Git Workflow

main         - Production
├── staging - Pre-production testing
└── feature/* - Feature branches
└── PR → staging → main

CI/CD Pipeline

# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
test:
- Lint (ESLint + Prettier)
- Type check (TypeScript)
- Unit tests (Vitest)
- E2E tests (Playwright)
deploy:
- Preview deploy on PR
- Production deploy on main merge

Environment Variables

# .env.local
DATABASE_URL=
CLERK_SECRET_KEY=
CLERK_PUBLISHABLE_KEY=
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=
ANTHROPIC_API_KEY=
UPSTASH_REDIS_URL=
UPSTASH_REDIS_TOKEN=

Document Status

  • Architecture Overview
  • Tech Stack
  • Database Schema
  • API Design
  • Project Structure
  • AI Coach System Architecture
  • Enhanced UI Features
  • Security
  • Performance
  • Monitoring
  • Development Workflow

Phase 3: Technical Architecture - COMPLETE


Document created: December 2024 Version: 1.0 Status: Complete