Overview
Evaly is a test/exam management platform built on a modern, serverless architecture. The platform serves two primary user roles:- Organizers: Create and manage tests through the organizer dashboard
- Participants: Take tests through the participant interface
Tech Stack
- Frontend
- Backend
- Deployment
- Framework: TanStack Start (React 19 meta-framework)
- Routing: File-based routing with TanStack Router
- Runtime: Bun (not Node.js)
- Styling: Tailwind CSS v4 with custom design tokens
- UI Components: shadcn/ui built on Radix UI primitives
- Rich Text Editor: TipTap for question editing
- Build Tool: Vite bundler
Architecture Patterns
Route Organization
Evaly uses file-based routing with clear separation of concerns:- File naming:
s.$testId.index.tsx - Parameters:
$testId,$attemptId,$token
Data Flow Architecture
Backend Functions
All backend logic lives in Convex server functions within the
convex/ directory. Functions are organized by feature domain.Type-Safe Hooks
Frontend uses auto-generated Convex React hooks for type-safe data fetching and mutations.
Real-time Updates
Convex subscriptions provide automatic real-time updates without manual WebSocket management.
Component Architecture
Components are organized by purpose and reusability:Page Components
Page Components
components/pages/ - Full page components tied to specific routes. These are feature-complete views that compose multiple shared components.Shared Components
Shared Components
UI Primitives
UI Primitives
components/ui/ - Base UI primitives from shadcn/ui. These are generic, unstyled components like Button, Dialog, Input.Custom Hooks
Custom Hooks
hooks/ - Custom React hooks for business logic abstraction and state management.Authentication & Authorization
Authentication Flow
Evaly uses Convex Auth for secure, serverless authentication:- Multi-provider support (Google OAuth)
- Secure session management
- Protected routes with
<Authenticated>wrapper - Automatic token refresh
Permission System
Role-based access control is enforced through permission helpers inconvex/common/permissions.ts:
| Helper | Purpose |
|---|---|
requireAuth() | Ensures user is authenticated |
requireOrganizationMember() | Ensures user belongs to organization |
requireOrganizationOwner() | Ensures user is organization owner |
requireCanManageOrganization() | Ensures user is owner or admin |
Ownership Checking
All mutations verify ownership before allowing operations:Multi-Tenancy Architecture
Evaly implements organization-based multi-tenancy:- Each user can belong to multiple organizations
- Users have a
selectedOrganizationIdandselectedOrganizerId - All data is scoped to organizations
- Invitations use token-based system with 7-day expiry
- Email invitations sent via Plunk service
Organization Context
Every authenticated request operates within an organization context:- User authenticates and selects an organization
- All queries filter by
organizationId - Mutations verify organization membership
- Data is isolated between organizations
Real-Time Features
Evaly leverages Convex’s real-time capabilities for live collaboration:Test Presence Tracking
- Heartbeat System: Tracks active participants during tests
- Live Status Updates: Real-time participant status changes
- Automatic Cleanup: Stale presence records are cleaned up
Live Monitoring
Organizers can monitor tests in real-time:- Participant join/leave events
- Test submission notifications
- Section completion tracking
- Live attempt progress
Notification System
Insta-style notification batching for organizers:- Organization-level: Notifications created per organization
- Organizer preferences: Each organizer customizes notification types
- Read tracking: Per-organizer read status
- Smart batching: Events grouped within time windows
Error Handling Pattern
Simple Errors (Toast)
Rich Errors (Dialog with Actions)
Display Types
toast(default): Shows a toast notification with optional action buttondialog: Shows an alert dialog with title, message, and optional action buttonbanner: Falls back to toast (for now)none: Silent error, only logs to console
Errors with codes containing
LIMIT_EXCEEDED or LIMIT_REACHED automatically use dialog display.File Upload & Storage
Cloudflare R2 provides scalable file storage:Upload Flow
Storage Utilities
Helper functions inconvex/common/storage.ts:
keyToPublicUrl()- Convert R2 key to CDN URLpublicUrlToKey()- Extract key from CDN URL- Automatic CDN integration via
R2_CDN_URL
Scheduled Functions
Convex scheduler handles time-based operations:Test Scheduling
Job Management
- Job IDs stored in test records (
activationJobId,finishJobId) - Cancel existing jobs before scheduling new ones
- Prevents duplicate scheduled operations
Async Operations
Email sending scheduled asynchronously:Common Patterns
Soft Deletion
Most tables usedeletedAt field for soft deletion:
Order Management
Items withorder field maintain manual sorting:
- Update neighboring items when adding/removing
- Maintain sequence integrity
- Support drag-and-drop reordering
Question Duplication
Questions can be duplicated from libraries to tests:referenceId- Points to section or libraryoriginalReferenceId- Tracks source when duplicated- Enables question bank reusability
Testing Strategy
Testing infrastructure is configured using:- Test Runner: Vitest
- Component Testing: React Testing Library
- Test Files:
*.test.tsxor*.spec.tsx
Testing infrastructure is configured but no test files currently exist in the codebase.
Deployment Architecture
Frontend Deployment
- TypeScript path aliases:
@/*→./src/* - Environment:
.env.localfor Convex deployment - Platform: Cloudflare Workers (edge deployment)
Backend Deployment
- Fully managed by Convex
- Automatic scaling
- Built-in monitoring
- Zero-downtime deployments
Environment Variables
Frontend (.env.local)
Frontend (.env.local)
CONVEX_DEPLOYMENT- Convex deployment name
Backend (Convex Dashboard)
Backend (Convex Dashboard)
- Auth:
AUTH_GOOGLE_ID,AUTH_GOOGLE_SECRET,SITE_URL - Storage:
R2_ACCESS_KEY_ID,R2_SECRET_ACCESS_KEY,R2_BUCKET,R2_ENDPOINT,R2_CDN_URL,R2_TOKEN - Email:
PLUNK_SECRET_API_KEY,PLUNK_API_URL,PLUNK_FROM_EMAIL - AI:
GOOGLE_GENERATIVE_AI_API_KEY - Auth Tokens:
JWKS,JWT_PRIVATE_KEY(auto-generated)