Architecture
Overview
Vigil is a prop firm compliance SaaS for traders. Users define their trading rules, connect their broker platforms, and Vigil uses AI to audit each trade against those rules. The app is built as a Next.js 16 static export deployed on Cloudflare Pages, with Supabase (PostgreSQL) as the database and auth layer. Server-side logic runs as Cloudflare Pages Functions (file-based routing under functions/api/). AI audits use OpenRouter (Kimi k2.5 model) for both single-agent and multi-agent analysis.
The product serves three user tiers: anonymous visitors (1 free audit/day), free authenticated users (3 audits/month), and Pro subscribers (50 audits/month via Stripe). Revenue comes from Stripe subscriptions with a Pro+ tier that unlocks multi-agent audits.
Stack
| Layer | Technology | Version |
|---|---|---|
| Framework | Next.js | 16.2.1 |
| Runtime | Cloudflare Pages Functions | Workers runtime |
| Language | TypeScript | ^5 |
| UI Library | React | 19.2.4 |
| Styling | Tailwind CSS v4 | ^4 |
| UI Components | shadcn/ui (CVA + Radix via Base UI) | ^4.1.0 |
| Animation | Framer Motion | ^12.38.0 |
| Charts | Recharts | ^3.8.0 |
| Database | Supabase (PostgreSQL) | Hosted |
| Auth | Supabase Auth (PKCE flow) | @supabase/ssr ^0.9.0 |
| Payments | Stripe | ^20.4.1 |
| AI | OpenRouter (Kimi k2.5) | REST API |
| Email (transactional) | Resend | REST API |
| Email (newsletter) | Beehiiv | REST API |
| Bot Protection | Cloudflare Turnstile | Widget + server verify |
| Analytics | PostHog | ^1.363.1 |
| Validation | Zod | ^4.3.6 |
| Testing | Vitest + Testing Library | ^4.1.0 |
| Build | Webpack (via NEXT_WEBPACK=1 flag) | — |
| Deploy | Wrangler | ^4.76.0 |
Directory Structure
vigil/
src/
app/ # Next.js App Router pages (static export)
[firm]/[questionSlug]/ # Dynamic firm FAQ pages (pSEO)
about/ # About + methodology pages
auth/callback/ # Supabase OAuth callback handler
best/[category]/ # "Best prop firm for X" pSEO pages
calculators/ # Cost calculators per firm
changelog/ # Public changelog
compare/ # Firm comparison tool (pSEO)
corrections/ # Public corrections page
dashboard/ # Authenticated dashboard area
audit/ # AI trade audit interface
connections/ # Broker connections management
import/ # CSV trade import
onboarding/ # New user onboarding wizard
settings/ # Account settings + billing
strategy/ # Strategy analysis page
guides/ # Educational guides
import/[platform]/ # Platform-specific import pages
learn/ # Glossary / learn pages (pSEO)
login/ # Auth login page
onboarding/ # Pre-auth onboarding flow
pricing/ # Pricing page
quiz/ # Interactive quiz funnel
r/ # Shared results viewer
reviews/ # Firm review pages (pSEO)
rules/ # Firm rules reference (pSEO)
setup/ # Platform setup guides
stats/ # Public stats page
strategies/ # Strategy-firm combo pages (pSEO)
switch-from/ # Competitor switch pages
tools/ # Free tools (strategy analyzer, simulators)
trust/ # Trust/safety score pages (pSEO)
components/ # React components
announcements/ # Announcement banner
audit/ # Audit UI (client, results, widget)
auth/ # Auth provider (Supabase session)
broker/ # Broker connection UI components
dashboard/ # Dashboard panels and widgets
home/ # Homepage sections
import/ # CSV import UI
quiz/ # Quiz funnel components
seo/ # JSON-LD schema components
simulator/ # Trailing drawdown simulator
strategy/ # Strategy detail views
strategy-analyzer/ # Chart screenshot analyzer
trust/ # Trust score animations
ui/ # shadcn/ui primitives (button, card, etc.)
lib/ # Shared business logic
audit/ # Audit cost tracking, schema, screenshot validation
broker/ # Broker auto-sync system
adapters/ # Per-broker adapter implementations
credentials.ts # AES-256-GCM encryption/decryption
oauth-state.ts # HMAC-signed OAuth state parameter
registry.ts # Adapter registry (maps broker_type to adapter)
sync.ts # Sync orchestrator (fetch, dedup, insert)
types.ts # Domain types (BrokerAdapter, BrokerConnection, etc.)
data/ # Static data (firms, glossary, strategies, instruments)
email/ # Email template builder
export/ # CSV export generation
import/ # CSV parser + Supabase import
quiz/ # Quiz scoring + trader types
rules/ # Rule quality scoring (A-F grading)
simulator/ # Drawdown simulation engine
storage/ # Screenshot storage helpers
posthog.ts # PostHog analytics client
referral.ts # Referral link handling
streak.ts # Streak calculation logic
stripe.ts # Client-side Stripe checkout
supabase.ts # Supabase browser client singleton
utm.ts # UTM parameter capture
functions/
api/ # Cloudflare Pages Functions (server-side)
_auth.ts # Shared Supabase JWT verification
_journal-core.ts # Shared journal business logic (NLP, screenshots, logging)
audit.ts # Single-agent AI audit (POST)
audit-multi.ts # Multi-agent AI audit (POST, Pro+ only)
broker/ # Broker CRUD + sync endpoints
callback.ts # OAuth callback handler (GET)
connect.ts # Create broker connection (POST)
disconnect.ts # Remove broker connection (POST)
status.ts # List user's connections (GET)
sync.ts # Manual sync trigger (POST)
cron/ # Scheduled tasks (triggered by GitHub Actions)
cleanup-screenshots.ts # Delete screenshots > 90 days
reset-audits.ts # Reset free audit quotas monthly
sync-brokers.ts # Auto-sync active broker connections
weekly-digest.ts # Weekly compliance email to Pro users
delete-account.ts # GDPR account deletion (POST)
discord/webhook.ts # Discord journal bot webhook
email-capture.ts # Email lead capture + Beehiiv (POST)
email/inbound.ts # SendGrid inbound email journal
health.ts # Health check (GET)
journal/ # Journal platform linking
link.ts # Generate/check link codes (POST/GET)
settings.ts # Journal preferences (GET/PATCH)
og-image.ts # Dynamic OG image SVG (GET)
rules/grade.ts # Rule quality grading (POST)
send-email.ts # Transactional email via Resend (POST)
strategy-analyze.ts # Strategy extraction from screenshots (POST)
strategy/
generate-pine.ts # Pine Script v5 generator (POST)
import-backtest.ts # Backtest CSV import (POST)
stripe/ # Stripe billing
checkout.ts # Create checkout session (POST)
portal.ts # Create billing portal session (POST)
webhook.ts # Stripe event handler (POST)
telegram/webhook.ts # Telegram journal bot webhook
whatsapp/webhook.ts # WhatsApp journal bot webhook
supabase/
config.toml # Supabase local dev config
migrations/ # 15 SQL migration files (see DATABASE_SCHEMA.md)
scripts/ # Utility scripts
check-supabase-usage.ts # Check Supabase usage metrics
generate-icons.ts # Generate favicon variants
generate-og-image.ts # Generate static OG image
google-indexing.ts # Google Indexing API batch submit
llm-accuracy-test.ts # LLM response accuracy testing
ping-search-engines.sh # Ping Google/Bing with sitemap
setup-uptime.sh # Configure Uptime Robot monitors
public/ # Static assets (favicons, firm logos, llms.txt, robots.txt)
docs/ # Project documentation + specs
tests/ # Test filesKey Flows
- 1.Client submits trade data + optional screenshot(s) + Turnstile token to
POST /api/audit - 2.Server verifies Turnstile token, validates trade data, checks monthly AI budget ($50 cap)
- 3.Server enforces per-user quota: anonymous (1/day via IP), free (3/month via
free_audits_remaining), active subscriber (50/month) - 4.Server builds a compliance audit prompt from: user's trading rules, prop firm rules, trade data, and screenshot instructions
- 5.Server calls OpenRouter (Kimi k2.5) with the prompt + optional Vision images
- 6.Server validates the JSON response against the audit schema (verdicts, compliance score, recommendation)
- 7.On validation failure, retries once with a correction prompt appended to the conversation
- 8.Server estimates cost from token usage, records quota usage, and returns the audit result
- 1.Same initial flow as single agent, but requires JWT auth +
subscription_plan = 'pro_plus' - 2.Runs 3 specialist agents in parallel: Technical Analyst (with screenshots), Risk Manager (data only), Rules Compliance Officer (data only)
- 3.Feeds all 3 reports into a 4th Debate Synthesizer agent that resolves contradictions and produces a unified audit
- 4.Returns the unified result with individual agent reports attached (~$0.12/audit vs $0.03 single-agent)
- 1.User connects a broker via
POST /api/broker/connect-- credentials are AES-256-GCM encrypted before storage - 2.For OAuth brokers (Tradovate, cTrader), returns a redirect URL with HMAC-signed state; callback at
GET /api/broker/callbackexchanges the code - 3.For credential-based brokers (MetaTrader via MetaAPI, NinjaTrader, etc.), adapter.connect() verifies immediately
- 4.On successful connection, an initial sync is triggered fire-and-forget
- 5.A GitHub Actions cron triggers
POST /api/cron/sync-brokersevery 15 minutes - 6.The cron endpoint queries active connections due for sync (oldest-first, batch of 10)
- 7.For each connection: decrypt credentials, call adapter.fetchTrades(since), deduplicate via external_id index + composite fallback, insert new trades with
source_type = 'broker_sync' - 8.On success: update
last_sync_at, resetconsecutive_errors, count total trades atomically - 9.On failure: increment
consecutive_errors; after 5 consecutive failures, status is set toerror
- 1.User generates a link code via
POST /api/journal/link(VGL-XXXX format, 10-minute TTL) - 2.User sends the code to a bot on Telegram, Discord, WhatsApp, or via email
- 3.Platform webhook (
/api/{platform}/webhook) receives the message, callsvalidateLinkCode()to bind the platform ID to the user's profile - 4.Once linked, natural language trade messages are parsed by AI (
parseTradeMessage()via OpenRouter) - 5.Parsed trades are inserted via
logTrade()which also upsertsdaily_tracking - 6.Screenshots can be sent and are processed via Vision AI (
parseTradeScreenshot()) - 7.Users can request daily summaries, weekly digests, and pre-trade checks via commands
- 1.Client calls
POST /api/stripe/checkout(authenticated) to create a Checkout Session - 2.Stripe redirects user to hosted checkout; on success, redirects back to
/dashboard - 3.
POST /api/stripe/webhookreceives events, verifies HMAC-SHA256 signature using Web Crypto API - 4.
checkout.session.completed-- linksstripe_customer_idto user's profile, setssubscription_status = 'active' - 5.
customer.subscription.updated-- syncs plan name, status, period end date - 6.
customer.subscription.deleted-- sets status tocancelled - 7.
invoice.payment_failed-- sets status topast_due - 8.
invoice.paid-- confirmsactivestatus - 9.Billing portal access via
POST /api/stripe/portalusing the storedstripe_customer_id
Configuration
| File | Purpose |
|---|---|
next.config.ts | Static export (output: 'export'), unoptimized images |
wrangler.jsonc | Cloudflare Pages config (project: tradingaudit, nodejs_compat flag) |
tsconfig.json | TypeScript config, @/* path alias to ./src/* |
postcss.config.mjs | PostCSS with Tailwind v4 |
components.json | shadcn/ui component config |
vitest.config.ts | Vitest test runner config |
open-next.config.ts | OpenNext/Cloudflare adapter config |
supabase/config.toml | Supabase local development config |
Environment Variables
| Variable | Layer | Purpose |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL | Client | Supabase project URL |
NEXT_PUBLIC_SUPABASE_ANON_KEY | Client | Supabase anonymous key (RLS-enforced) |
SUPABASE_SERVICE_ROLE_KEY | Server | Supabase admin key (bypasses RLS) |
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY | Client | Stripe public key |
NEXT_PUBLIC_STRIPE_PRICE_ID_PRO | Client | Stripe Pro plan price ID |
STRIPE_SECRET_KEY | Server | Stripe API secret |
STRIPE_WEBHOOK_SECRET | Server | Stripe webhook signing secret |
OPENROUTER_API_KEY | Server | OpenRouter API key for AI audits |
OPENROUTER_BASE_URL | Server | OpenRouter base URL (default: https://openrouter.ai/api/v1) |
OPENROUTER_MODEL | Server | AI model (default: moonshot/kimi-k2.5) |
NEXT_PUBLIC_TURNSTILE_SITE_KEY | Client | Cloudflare Turnstile site key |
TURNSTILE_SECRET_KEY | Server | Cloudflare Turnstile secret |
RESEND_API_KEY | Server | Resend email API key |
BEEHIIV_API_KEY | Server | Beehiiv newsletter API key |
BEEHIIV_PUBLICATION_ID | Server | Beehiiv publication ID |
NEXT_PUBLIC_POSTHOG_KEY | Client | PostHog project API key |
NEXT_PUBLIC_POSTHOG_HOST | Client | PostHog ingest URL |
CRON_SECRET | Server | Bearer token for cron endpoints |
BROKER_ENCRYPTION_KEY | Server | 256-bit hex key for AES-256-GCM credential encryption |
TELEGRAM_BOT_TOKEN | Server | Telegram Bot API token |
DISCORD_PUBLIC_KEY | Server | Discord application public key |
DISCORD_BOT_TOKEN | Server | Discord bot token |
WHATSAPP_TOKEN | Server | WhatsApp Cloud API token |
WHATSAPP_PHONE_NUMBER_ID | Server | WhatsApp phone number ID |
Deployment
Build command: NEXT_WEBPACK=1 next build (outputs to out/)
Deploy command: wrangler pages deploy out --project-name tradingaudit
Combined: npm run cf:deploy
The build produces a fully static HTML/JS/CSS export in out/. Cloudflare Pages serves the static files and routes /api/* requests to the Pages Functions in functions/api/.
Cron jobs (screenshot cleanup, audit reset, broker sync, weekly digest) are triggered by GitHub Actions on a schedule, calling the respective /api/cron/* endpoints with a Bearer CRON_SECRET authorization header.
Key Design Decisions
- 1.Static export + Pages Functions -- No server-side rendering. All pages are statically generated at build time. Server logic runs only in Cloudflare Pages Functions, keeping the critical path fast and the hosting cost near zero.
- 1.Raw fetch against PostgREST -- Pages Functions cannot use the Supabase JS client (it requires Node.js). All database operations use raw
fetch()against Supabase's PostgREST API with the service role key.
- 1.Adapter pattern for brokers -- Each broker integration implements the
BrokerAdapterinterface. Adding a new broker requires only creating an adapter file and registering it in the registry. The sync orchestrator is broker-agnostic.
- 1.AES-256-GCM credential encryption -- Broker credentials are encrypted client-side before storage. The encryption key is a server-side secret (
BROKER_ENCRYPTION_KEY). Credentials are never stored in plaintext.
- 1.Graceful degradation everywhere -- Every server function falls back safely when optional services (Supabase, Turnstile, Resend, etc.) are not configured. This allows development and testing without all services running.
- 1.Multi-platform journal -- The
_journal-core.tsmodule contains all journal business logic. Platform webhooks (Telegram, Discord, WhatsApp, Email) are thin adapters that delegate to the core module, preventing logic duplication.