System Overview
Three Core Principles
Modular #-architecture — the system grows through bounded contexts. Alpha A = 15 active contexts. Vision (v2.0) = 25+ contexts. No cross-context
*.serviceimports — integration flows through the EventBus only.Federation-readiness without premature investment — URI-based actor identifiers (
acct:[email protected], Webfinger-style), event-sourcing of critical domains. ActivityPub bridge arrives in v2.0.Cost trajectory $30 → $500/month at 50 k MAU — Supabase + Expo + Cloudflare R2 + Centrifugo. No Convex / Firebase / Clerk without an architectural ADR.
System Diagram
mermaid
graph TB
subgraph Mobile["apps/mobile (Expo SDK 54 + RN 0.81)"]
MobileApp[React Native App]
end
subgraph Admin["apps/admin (Next.js 15 + shadcn/ui)"]
AdminPanel[Admin Panel]
end
subgraph API["apps/api (NestJS + Fastify)"]
direction TB
Identity[identity]
Interests[interests]
Reputation[reputation]
Social[social]
Posts[posts]
AI[ai-assistant]
Economy[economy]
Notifications[notifications]
Messaging[messaging]
Wiki[wiki]
Contribution[contribution]
Discovery[discovery]
Access[access-matrix]
InviteTree[invite-tree]
Shared[shared/]
end
subgraph Data["Data Layer"]
PG[(Supabase Postgres)]
R2[(Cloudflare R2)]
Centrifugo[Centrifugo WS]
end
subgraph AI_Ext["External AI"]
Anthropic[Anthropic Claude Sonnet 4.6]
end
Mobile -->|REST + JWT| API
Admin -->|REST + JWT| API
API -->|Prisma| PG
API -->|Presigned URLs| R2
API -->|Publish| Centrifugo
Mobile -->|Subscribe| Centrifugo
AI -->|Vercel AI SDK| AnthropicTech Stack
| Layer | Technology | Why |
|---|---|---|
| Mobile | Expo SDK 54 + RN 0.81 + New Architecture | One codebase, EAS OTA updates |
| Mobile UI | Tamagui v2.0.0-rc.0+ | Compiler-driven, design tokens first-class |
| Backend | NestJS + Fastify | Modules = bounded contexts |
| Database | Supabase Postgres + pgvector + RLS | 50 k MAU free tier, AI embeddings ready |
| Auth | Supabase Auth + custom invite flow | RLS integration |
| Real-time | Centrifugo (self-hosted) | Scalable, federation-friendly |
| AI | Vercel AI SDK + Anthropic Claude Sonnet 4.6 | Streaming, provider-agnostic |
| Storage | Cloudflare R2 + Supabase Storage | Zero egress cost for video |
| Web Admin | Next.js 15 + shadcn/ui + Ant Design | Owned code + CRUD factory |
| Monorepo | Turborepo + pnpm | Simplicity, remote cache |
| State | TanStack Query + Zustand + MMKV | Standard, offline-first |
| Push | Expo Push | 600 notif/sec, zero infra |
| Observability | PostHog + Sentry | Free tiers cover Alpha |
| Hosting | Fly.io (API + Centrifugo) + Vercel (admin) | $5/month start |
Monorepo Structure
regulus/
├── apps/
│ ├── api/ NestJS modular monolith
│ │ ├── prisma/ Schema + migrations (49 migrations)
│ │ └── src/
│ │ ├── modules/ One folder = one bounded context
│ │ └── shared/ Guards, EventBus, Prisma, etc.
│ ├── mobile/ Expo SDK 54 (iOS + Android)
│ │ └── src/
│ │ ├── screens/ Full-screen views
│ │ ├── components/ Local UI components
│ │ └── lib/ API client, auth, storage
│ ├── admin/ Next.js 15 admin panel
│ │ └── src/
│ │ ├── pages/ One file = one admin resource
│ │ ├── components/ Admin-specific components
│ │ └── lib/ Queries, auth, API client
│ └── docs/ VitePress documentation (this site)
├── packages/
│ ├── domain/ Shared types, Zod schemas, domain events
│ ├── ui/ Shared React Native components
│ ├── design-tokens/ Tamagui theme tokens
│ └── ai-prompts/ Anthropic prompt templates
└── docs/
├── architecture/ ADRs + bounded context map
├── adr/ Architecture Decision Records
└── setup/ Human setup guidesCross-Cutting Concerns
Reputation, Moderation, Governance, and Economy must integrate through events, not direct service calls:
typescript
// WRONG — cross-context service import
@Injectable()
class ProfileService {
constructor(private readonly reputationService: ReputationService) {}
}
// CORRECT — event-driven integration
@Injectable()
class ProfileService {
constructor(private readonly eventBus: EventBus) {}
async updateProfile(userId: string, data: UpdateProfileDto) {
await this.eventBus.publish(new ProfileUpdatedEvent({ userId, changes: data }));
}
}
// Reputation module listens
@OnEvent('profile.updated')
async handleProfileUpdated(event: ProfileUpdatedEvent) { /* ... */ }