This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Clementime is a multi-platform oral exam scheduler for universities with two independent implementations:
-
Web App (
/clementime-web): Rails 8.1.1 + React 19 + PostgreSQL- Status: Stable production deployment (maintenance mode)
- Deployment: Docker, Render.com
-
macOS App (
/clementime-mac): Swift 5.9+ + SwiftUI + CloudKit- Status: Active development
- Requirements: macOS 15.0+, Xcode 15+
Both platforms share the same core scheduling algorithm but operate completely independently with no shared data layer.
# Initial setup
cd clementime-web
bin/setup # Install all dependencies + setup DB
# Development
bin/dev # Start Rails API + React dev server
make dev # Alternative: Docker Compose dev environment
make dev-logs # View container logs
make dev-stop # Stop development environment
# Database
make db-setup # Create, migrate, seed
make db-migrate # Run migrations only
make db-reset # DESTRUCTIVE: Reset database
bundle exec rails console # Rails console
make console # Rails console (Docker)
# Testing & Linting
make test # Run test suite
make lint # RuboCop linting
make lint-fix # Auto-fix lint issues
# Build & Deploy
make build # Build Docker image
make push # Push to Docker Hub
make deploy-render # Deploy to Render (via git push)# Development
cd clementime-mac
open Clementime/Clementime.xcodeproj # Open in Xcode (recommended)
# Command-line builds
xcodebuild -scheme Clementime -configuration Debug # Debug build
xcodebuild -scheme Clementime -configuration Release # Release build
xcodebuild test -scheme Clementime # Run tests
# Linting
swiftlint # Lint Swift code (.swiftlint.yml config)# Version & Release (run from repo root)
./scripts/release.sh # Patch version bump + GitHub release
./scripts/release.sh minor # Minor version bump
./scripts/release.sh major # Major version bump
# The release script automatically:
# - Builds Docker image for web app
# - Builds macOS DMG installer
# - Creates GitHub release with changelog
# - Pushes to Docker HubBackend (/clementime-web/app):
- MVC Pattern: Rails controllers → services → models
- API Structure:
/api/admin/*(admin endpoints),/api/ta/*(TA endpoints) - Key Services:
ScheduleGenerator(697 lines): Core constraint-based scheduling algorithmCanvasRosterImporter: LMS roster importsSlackNotifier,SlackMatcher: Real-time Slack notificationsCloudflareR2Uploader: File storage
- Database: PostgreSQL (8 main tables: constraints, exam_slots, exam_slot_histories, sections, students, recordings, system_configs, users)
- Caching: Redis + Solid Cache/Queue/Cable (Rails 8 Omakase)
Frontend (/clementime-web/client):
- Framework: React 19.1.1 with Vite 7.1.11
- Styling: Tailwind CSS 3
- Routing: React Router 7.9.3
- HTTP: Axios for API calls
- State: React Context API
Clean Architecture with strict layer separation:
Presentation Layer (Views + ViewModels)
↓
Domain Layer (UseCases + Repositories + Entities + Services)
↓
Data Layer (Core Data + CloudKit + Repository Implementations)
Key Patterns:
- MVVM: ViewModels handle presentation logic, Views are pure SwiftUI
- Repository Pattern: Abstract data access via protocol-based repositories
- Dependency Injection: Manual DI container (
DependencyContainer.swift) - Use Cases: Single-responsibility application logic (e.g.,
GenerateScheduleUseCase,ShareCourseUseCase) - Entity Mapping: Core Data entities ↔ Domain entities via explicit mappers (
*Entity+Mapping.swift)
Core Data Stack (/clementime-mac/Clementime/Clementime/Data/CoreData):
- Local persistence with offline-first design
- CloudKit integration for automatic iCloud sync
- Mappers handle conversion between Core Data entities and domain models
Important Files:
DependencyContainer.swift: Wires up all dependencies (repositories, use cases, etc.)PersistenceController.swift: Core Data stack initializationCloudKitShareManager.swift: Course sharing via CloudKit
The core scheduling algorithm is identical logic in both platforms (ported from Rails to Swift):
Location:
- Web:
/clementime-web/app/services/schedule_generator.rb(697 lines) - macOS:
/clementime-mac/Clementime/Clementime/Core/Domain/UseCases/GenerateScheduleUseCase.swift
-
Cohorts: Students assigned to "odd" or "even" cohorts (or custom cohorts in macOS)
- Alternating week scheduling (Exam 1: odd week 1 + even week 2, etc.)
-
Constraint System (5 types):
time_before: Student must finish before specified timetime_after: Student must start after specified timeweek_preference: Lock student to odd/even weeks onlyspecific_date: Force exam on specific dateexclude_date: Prevent exam on specific date
-
Prioritization Strategy:
- Constrained students scheduled first (deterministic shuffle within groups)
- Order:
time_before→time_after→ date constraints → unconstrained - Prevents constraint violations and maximizes scheduling success
-
Scheduling Logic:
- Sequential slot assignment in priority order
- Time windows respect exam duration + buffer minutes
- Gap filling: Regeneration attempts to fill gaps in existing schedules
- Locked slots: Once "sent to students", slots are locked and never regenerated
-
Configuration (SystemConfig table for web, UserDefaults for macOS):
EXAM_DAY: e.g., "friday"EXAM_START_TIME: e.g., "13:30"EXAM_END_TIME: e.g., "14:50"EXAM_DURATION_MINUTES: e.g., 7EXAM_BUFFER_MINUTES: e.g., 1QUARTER_START_DATE: Start of termTOTAL_EXAMS: Max 5 for web, unlimited for macOSBALANCED_TA_SCHEDULING: Enable/disable balanced mode
Key tables in /clementime-web/db/schema.rb:
constraints: Time/date constraints per student per examexam_slots: Assigned exam times (withsent_to_studentlock flag)exam_slot_histories: Audit trail of slot changessections: Course sections (lecture/lab)students: Student roster (name, email, cohort)recordings: Audio recordings of examssystem_configs: Configuration key-value pairsusers: Admin/TA accounts
Entities in /clementime-mac/Clementime/Clementime/Clementime.xcdatamodeld:
CourseEntity: Top-level course containerExamSessionEntity: Exam configuration (date, time, duration)CohortEntity: Custom cohort definitions (unlimited, not just odd/even)SectionEntity: Course sectionsStudentEntity: Student roster with cohort assignmentExamSlotEntity: Scheduled exam timesExamSlotHistoryEntity: Audit trailConstraintEntity: Time/date constraintsRecordingEntity: Audio recordings (stored in iCloud)TAUserEntity: TA accounts with granular permissions (8 permission types)
CloudKit Sync: All entities sync automatically to iCloud when user is signed in.
| Aspect | Web App | macOS App |
|---|---|---|
| Language | Ruby 3.4.6 + JavaScript | Swift 5.9+ |
| Framework | Rails 8.1.1 + React 19 | SwiftUI |
| Database | PostgreSQL + Redis | Core Data + CloudKit |
| Offline Support | ❌ No | ✅ Full offline-first |
| Cohorts | 2 fixed (odd/even) | ∞ unlimited custom |
| Exam Limit | 5 max | ∞ unlimited |
| Permissions | Basic (admin/TA) | Granular (8 permission types) |
| Deployment | Docker, Render.com | Mac App Store (planned) |
| Integrations | Slack, Canvas LMS | iCloud, CloudKit Share |
- Rails 8 Omakase Stack: Uses Solid Cache/Queue/Cable (not Sidekiq/Memcached)
- Linting: RuboCop with
rubocop-rails-omakase(opinionated style) - Security: Brakeman scanner for vulnerabilities (runs in CI)
- CORS: Configured via
rack-corsgem - File Storage: Supports Google Drive OR Cloudflare R2 (S3-compatible)
- Authentication: BCrypt password hashing, JWT tokens for API
- Docker: Multi-stage builds for production deployment
- Clean Architecture: Strict separation of presentation/domain/data layers
- SwiftLint: Comprehensive rules in
.swiftlint.yml(30+ opt-in rules) - Async/Await: Modern concurrency throughout (no completion handlers)
- CloudKit Sharing: Courses can be shared with TAs via CloudKit Share
- Export/Import:
.clementimefiles for course backup/transfer - Audio Recording: AVFoundation for built-in recording, stored in iCloud
- PDF Export: Schedule export via SwiftUI rendering
.github/workflows/ci.yml:
- Brakeman security scan (Rails)
- RuboCop linting (Rails)
- ESLint + Prettier (React client + landing page)
.github/workflows/swift-lint.yml:
- SwiftLint checks for macOS app
.github/workflows/release.yml:
- Triggered by version tags (e.g.,
v25.2.0) - Builds Docker image for web app
- Builds macOS DMG installer
- Creates GitHub release with changelog
- Pushes to Docker Hub
# Run all tests
make test # Via Docker
bundle exec rails test # Direct (requires local setup)
# Test a single file
bundle exec rails test test/models/student_test.rb
# Test with coverage (if configured)
COVERAGE=true bundle exec rails test# Run all tests in Xcode
⌘U (Cmd+U)
# Command-line tests
xcodebuild test -scheme Clementime -destination 'platform=macOS'
# Test a specific test case
xcodebuild test -scheme Clementime -only-testing:ClementimeTests/ScheduleGeneratorTests- Routes:
/clementime-web/config/routes.rb - Schema:
/clementime-web/db/schema.rb - Environment:
/clementime-web/.env(create from.env.example) - Docker Compose:
/clementime-web/docker-compose.yml(dev),docker-compose.production.yml(prod) - Frontend Config:
/clementime-web/client/vite.config.js,tailwind.config.cjs
- Xcode Project:
/clementime-mac/Clementime/Clementime.xcodeproj - Core Data Model:
/clementime-mac/Clementime/Clementime/Clementime.xcdatamodeld - Entitlements:
/clementime-mac/Clementime/Clementime/Clementime.entitlements(iCloud permissions) - SwiftLint Config:
/clementime-mac/.swiftlint.yml - Info.plist:
/clementime-mac/Clementime/Clementime/Info.plist
- Main README:
/README.md(project overview) - Release Script:
/scripts/release.sh - Version File:
/VERSION(single source of truth for version number) - Deployment Docs:
/docs/DEPLOYMENT_GUIDE.md,/docs/QUICK_START.md
sis_user_id,email,full_name,section_code
student001,alice@fakeuni.edu,Alice Johnson,F25-PSYCH-10-01
student002,bob@fakeuni.edu,Bob Smith,F25-PSYCH-10-02Required columns:
sis_user_id: Student's unique ID from SISemail: Student's email addressfull_name: Student's full namesection_code: Section identifier matching your course
Example file: /docs/examples/roster-mac-example.csv
Student,SIS User ID,SIS Login ID,Section
"Johnson, Alice Marie",student001,alice.johnson@fakeuni.edu,F25-PSYCH-10-01
"Smith, Bob Thomas",student002,bob.smith@fakeuni.edu,F25-PSYCH-10-02Required columns:
Student: Full name in "Last, First Middle" formatSIS User ID: Student's unique ID from SISSIS Login ID: Student's email address (login)Section: Section identifier matching your course
Note: The web app accepts Canvas LMS gradebook export format. To obtain this file, go to your Canvas course → Grades → Export → Export Entire Gradebook. Upload the downloaded CSV directly - extra columns will be ignored. When combined with Slack member export, the system will merge student data with Slack profiles for notifications.
Example file: /docs/examples/roster-web-example.csv
For Slack integration, export workspace members from Slack admin panel:
Export from Slack:
- Slack workspace → Settings & administration → Workspace settings
- Import/Export Data → Export member list
- Download CSV
Format:
username,email,status,has-2fa,has-sso,userid,fullname,displayname,expiration-timestamp
alice_j,alice.johnson@fakeuni.edu,Member,0,1,UFAKE001ABC,"Alice Johnson",Alice,Merge logic: Students are matched with Slack members by email address. Matched students get Slack user IDs stored for direct message notifications about schedule changes.
Example file: /docs/examples/slack-members-example.csv
Quick Deploy to Render.com:
cd clementime-web
make deploy-render MSG="Deploy message" # Pushes to GitHub, Render auto-deploysDocker Deployment:
cd clementime-web
make build # Build image
make push # Push to Docker Hub
docker pull shawnschwartz/clementime:latest
docker-compose -f docker-compose.production.yml up -dEnvironment Variables Required:
DATABASE_URL: PostgreSQL connection stringREDIS_URL: Redis connection stringSECRET_KEY_BASE: Rails secret (generate withrails secret)SLACK_CLIENT_ID,SLACK_CLIENT_SECRET: Slack OAuthCANVAS_API_KEY,CANVAS_BASE_URL: Canvas LMS integrationR2_ACCESS_KEY_ID,R2_SECRET_ACCESS_KEY,R2_BUCKET: Cloudflare R2 storage
Local Build:
- Open
Clementime.xcodeprojin Xcode - Select "Any Mac" as build destination
- Product → Archive
- Distribute App → Developer ID (for distribution outside Mac App Store)
Automated Build (via release script):
./scripts/release.sh # Creates DMG in GitHub releaseCode Signing Setup: For automated builds via GitHub Actions, you need to configure code signing credentials. See the macOS Code Signing Guide for detailed instructions on obtaining:
- Developer ID certificates
- Notarization credentials
- GitHub Actions secrets
Web App: Production-ready, maintenance mode
- Used in production at universities
- Receives bug fixes and security updates
- No major new features planned
macOS App: Active development
- Core features implemented
- Planned features: Slack integration, Canvas LMS integration
- Target: Mac App Store distribution
Landing Page (/landing): Static marketing site (separate Node.js project)
- Database Changes: Always create migrations (
rails g migration) - API Changes: Update both controller AND client API service files
- Lint Before Commit: Run
make lint-fixto auto-fix style issues - Security: Run
bundle exec brakemanto check for vulnerabilities - Environment Variables: Add new vars to
.env.examplewith documentation
- Core Data Changes: Always create new model version (do NOT edit existing)
- Repository Pattern: Changes to data layer should go through repositories
- Dependency Injection: Register new dependencies in
DependencyContainer.swift - SwiftLint: Run
swiftlintbefore committing (enforced in CI) - CloudKit: Changes to synced entities require CloudKit schema updates
- Define route in
config/routes.rb - Create controller action in
app/controllers/api/admin/*_controller.rb - Extract business logic to service in
app/services/*_service.rb - Add client method in
client/src/services/*Service.js - Call from React component
- Define domain entity in
Core/Domain/Entities/ - Create repository protocol in
Core/Domain/Repositories/ - Implement repository in
Data/Repositories/ - Create use case in
Core/Domain/UseCases/ - Create ViewModel in
ViewModels/ - Create SwiftUI View in
Views/ - Register dependencies in
DependencyContainer.swift
- Web App: Don't use Sidekiq or Memcached (Rails 8 uses Solid Queue/Cache instead)
- Web App: CORS must be configured in
config/initializers/cors.rbfor React client - macOS App: Always use async/await, never
@MainActoron repository methods - macOS App: Core Data entities are NOT domain entities (use mappers)
- Both: The scheduling algorithm must remain identical between platforms
- Both: Cohort assignment logic differs (fixed odd/even vs unlimited custom)