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
| Phase | Platform | Technology | Timeline |
|---|---|---|---|
| Phase 1 | Web (all browsers) | Next.js PWA | Months 1-3 |
| Phase 1 | Mobile Web | Next.js PWA (installable) | Months 1-3 |
| Phase 2 | iOS App Store | Flutter | Months 4-6 |
| Phase 2 | Google Play Store | Flutter | Months 4-6 |
| Phase 3 | macOS Desktop | Flutter | Months 6+ |
| Phase 3 | Windows Desktop | Flutter | Months 6+ |
2. Tech Stack
Core Stack
| Layer | Technology | Version | Purpose |
|---|---|---|---|
| Framework | Next.js | 15.x | Full-stack React framework |
| Language | TypeScript | 5.x | Type safety |
| Runtime | Node.js | 20.x LTS | Server runtime |
| Styling | Tailwind CSS | 4.x | Utility-first CSS |
| Components | shadcn/ui | Latest | Accessible UI primitives |
| Database | PostgreSQL | 16.x | Primary data store |
| DB Host | Neon | Serverless | Serverless PostgreSQL |
| ORM | Drizzle | Latest | Type-safe queries |
| Auth | Clerk | Latest | Authentication & users |
| Payments | Stripe | Latest | Subscriptions |
| AI | Vercel AI SDK + Claude | Latest | Coach AI |
| Hosting | Vercel | Pro | Deployment & edge |
Enhanced UI/UX Stack
| Category | Technology | Purpose |
|---|---|---|
| Animations | Framer Motion | Page transitions, micro-interactions |
| 3D Graphics | Three.js + React Three Fiber | 3D coach avatars |
| 3D Scenes | Spline | Visual 3D scene design |
| Gestures | @use-gesture/react | Swipe, drag, pinch |
| Charts | Recharts | Animated progress charts |
| Celebrations | canvas-confetti | PR & milestone celebrations |
| Lottie | lottie-react | Complex vector animations |
| Icons | Lucide React | Consistent iconography |
| Sound | Howler.js | Optional audio feedback |
Infrastructure Stack
| Service | Technology | Purpose |
|---|---|---|
| Hosting | Vercel | Edge deployment |
| Database | Neon | Serverless PostgreSQL |
| Cache | Upstash Redis | Session, rate limiting |
| Blob Storage | Vercel Blob | Images, avatars |
| CDN | Vercel Edge Network | Global asset delivery |
| Monitoring | Vercel Analytics | Performance tracking |
| Error Tracking | Sentry | Error monitoring |
| Resend | Transactional emails |
Development Stack
| Tool | Purpose |
|---|---|
| Package Manager | pnpm |
| Linting | ESLint + Prettier |
| Testing | Vitest + Playwright |
| Git Hooks | Husky + lint-staged |
| CI/CD | GitHub Actions + Vercel |
| API Docs | Swagger/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
| Endpoint | Limit |
|---|---|
| Auth endpoints | 10/min |
| Coach chat | 30/min |
| General API | 100/min |
| Webhooks | 1000/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
| Category | Coaches | Purpose |
|---|---|---|
| Domain Experts | Max, Nova, Sage | Specialized knowledge (training, data, recovery) |
| Personality Coaches | Sam, Ace, Chip, Rex | Distinct communication styles |
| Adaptive | Mo | All-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:
| Task | Active Coaches | Description |
|---|---|---|
plan-feedback | Max, Nova, Sage | Domain experts for plan creation |
chat | All coaches | User's selected coaches |
greeting | Primary coach | Contextual greetings |
check-in | Context-appropriate | Automated 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
| Feature | File | Integration |
|---|---|---|
| Plan Feedback | app/api/v1/plans/feedback/route.ts | Uses buildPlanFeedbackPrompt() |
| Coach Chat | app/api/v1/coach/chat/route.ts | Uses buildChatPrompt() |
| Greetings | app/api/v1/coach/greeting/route.ts | Uses getCoachProfile() |
| Check-ins | lib/coach-check-ins.ts | Uses getCoachProfile() |
7. Enhanced UI Features
Animation System
| Feature | Technology | Implementation |
|---|---|---|
| Page Transitions | Framer Motion + View Transitions API | Smooth navigation between screens |
| Micro-interactions | Framer Motion | Button press, toggle, input focus |
| Loading States | Skeleton + Shimmer | Perceived performance |
| Celebrations | canvas-confetti + Lottie | PR, streak milestones |
| 3D Avatars | Three.js + React Three Fiber | Coach representations |
| Charts | Recharts + Framer Motion | Animated data visualization |
| Gestures | @use-gesture/react | Swipe, 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
| Measure | Implementation |
|---|---|
| Authentication | Clerk (OAuth 2.0, JWT) |
| Authorization | Row-level security in queries |
| Input Validation | Zod schemas on all endpoints |
| Rate Limiting | Upstash Redis |
| HTTPS | Enforced via Vercel |
| CORS | Strict origin policy |
| SQL Injection | Drizzle ORM parameterized queries |
| XSS | React auto-escaping + CSP headers |
| CSRF | SameSite cookies |
| Secrets | Environment 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
| Metric | Target | How |
|---|---|---|
| First Contentful Paint | < 1.5s | SSR, optimized fonts |
| Largest Contentful Paint | < 2.5s | Image optimization, lazy loading |
| Time to Interactive | < 3.5s | Code splitting, minimal JS |
| Cumulative Layout Shift | < 0.1 | Skeleton loaders, reserved space |
| Animation Frame Rate | 60fps | GPU acceleration, will-change |
| Lighthouse Score | > 90 | All optimizations |
| Initial Bundle | < 200kb | Tree shaking, dynamic imports |
| API Response | < 200ms | Edge 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
| Tool | Purpose |
|---|---|
| Vercel Analytics | Performance, Web Vitals |
| Sentry | Error tracking, session replay |
| PostHog | Product analytics, feature flags |
| Upstash | Rate 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