Version: 15.2.0
Last Updated: June 2026
Purpose: Onboarding guide, cross-skilling reference, and architectural overview for contributors
- Overview
- Technology Stack
- Project Structure
- Core Architecture
- Service Layer
- UI Components
- Routing & Navigation
- Data Flow
- External Integrations
- Development Workflow
- Key Concepts
- Testing & Quality
- Deployment
- Contributing Guidelines
Tutors is an open-source learning experience platform that transforms structured course content into an intuitive, discoverable web application. This repository contains the Tutors Reader - a SvelteKit-based front-end application that presents educational content with rich features including analytics, real-time presence, and interactive learning objects.
- Course Rendering: Displays courses, topics, labs, talks, videos, and other learning objects
- Real-time Features: Live student presence tracking via PartyKit WebSockets
- Analytics: Learning event tracking via Supabase
- Authentication: GitHub OAuth integration via Auth.js
- Theming: Customizable themes with light/dark mode support
- Accessibility: Dyslexia-friendly fonts and accessible design patterns
- tutors-apps: Course generators and CLI tools
- tutors-reference-manual: Documentation
- tutors-reference-course: Example course
- SvelteKit 2.x: Full-stack framework with SSR/CSR support
- Svelte 5.x: Reactive UI framework with runes-based state management
- Vite 8.x: Build tool and dev server
- TypeScript 6.x: Type-safe JavaScript
- Tailwind CSS 4.x: Utility-first CSS framework
- Skeleton UI: Svelte component library
- Iconify: Icon library
- markdown-it: Markdown parser
- Shiki: Syntax highlighting
- PDF.js: PDF rendering
- Supabase: Database, authentication, and analytics backend
- Auth.js/SvelteKit: GitHub OAuth authentication
- PartyKit: Real-time WebSocket communication
- ESLint: Code linting
- Prettier: Code formatting
- svelte-check: Type checking for Svelte
tutors/
├── src/
│ ├── lib/ # Shared libraries and utilities
│ │ ├── services/ # Business logic layer
│ │ │ ├── course/ # Course data management
│ │ │ ├── community/ # Analytics & presence services
│ │ │ ├── connect/ # User connection & auth
│ │ │ ├── markdown/ # Markdown processing
│ │ │ └── themes/ # Theme & UI configuration
│ │ ├── ui/ # UI components
│ │ │ ├── components/ # Generic reusable components
│ │ │ ├── learning-objects/ # Learning object displays
│ │ │ │ ├── content/ # Content renderers (Lab, Video, etc.)
│ │ │ │ ├── layout/ # Layout components (Cards, Panels)
│ │ │ │ └── structure/ # Structural components
│ │ │ ├── navigators/ # Navigation components
│ │ │ └── time/ # Time tracking UI
│ │ └── runes.svelte.ts # Global reactive state
│ │
│ ├── routes/ # SvelteKit routing
│ │ ├── (auth)/ # Authentication routes
│ │ ├── (course-reader)/ # Course content routes
│ │ │ ├── course/ # Course home page
│ │ │ ├── topic/ # Topic pages
│ │ │ ├── lab/ # Interactive lab viewer
│ │ │ ├── talk/ # Presentation viewer
│ │ │ ├── video/ # Video player
│ │ │ ├── note/ # Note/PDF viewer
│ │ │ ├── wall/ # Content wall/gallery
│ │ │ ├── search/ # Course search
│ │ │ └── time/ # Analytics dashboard
│ │ ├── (home)/ # Landing page & course list
│ │ ├── (live)/ # Real-time presence views
│ │ │ ├── live/ # Live student tracking
│ │ │ └── catalogue/ # Course catalogue
│ │ ├── +layout.svelte # Root layout
│ │ └── +layout.server.ts # Server-side layout logic
│ │
│ ├── hooks.client.ts # Client-side hooks
│ └── app.css # Global styles
│
├── static/ # Static assets
│ ├── lib/ # Shared libraries (PDF.js worker)
│ ├── icons/ # Icon assets
│ └── *.woff2 # OpenDyslexic fonts
│
├── .env.example # Environment variable template
├── svelte.config.js # SvelteKit configuration
├── vite.config.ts # Vite build configuration
├── tsconfig.json # TypeScript configuration
├── tailwind.config.ts # Tailwind CSS configuration (if exists)
├── package.json # Dependencies and scripts
└── README.md # Setup instructions
src/lib/services/: Business logic layer, organized by domain (course, community, themes)src/lib/ui/: Presentational components, organized by functionsrc/routes/: File-based routing with grouped routes using(groupName)syntaxstatic/: Publicly accessible assets served as-is
The application follows a service-oriented architecture with clear separation of concerns:
┌─────────────────────────────────────────────────────────┐
│ SvelteKit Routes │
│ (Presentation) │
└──────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ UI Components │
│ (src/lib/ui/*) │
└──────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ Service Layer │
│ (src/lib/services/*) │
│ │
│ ┌──────────┐ ┌───────────┐ ┌────────┐ ┌──────────┐ │
│ │ Course │ │ Community │ │ Theme │ │ Connect │ │
│ │ Service │ │ Service │ │Service │ │ Service │ │
│ └──────────┘ └───────────┘ └────────┘ └──────────┘ │
└──────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ External Services & Data │
│ │
│ ┌──────────┐ ┌───────────┐ ┌────────────────────┐ │
│ │ Supabase │ │ PartyKit │ │ Course JSON (CDN) │ │
│ └──────────┘ └───────────┘ └────────────────────┘ │
└─────────────────────────────────────────────────────────┘
The application uses Svelte 5's runes-based reactivity for state management:
Global State (src/lib/runes.svelte.ts):
// Example pattern
export const currentCourse = rune<Course | null>(null);
export const currentLo = rune<Lo | null>(null);Service-Level State (within services):
// Services maintain their own reactive state
export const courseService: CourseService = {
courses: new Map<string, Course>(),
labs: new Map<string, LiveLab>()
// ...
};- Server-Side Rendering (SSR): Initial page loads fetch data server-side
- Client-Side Navigation: Subsequent navigation uses client-side fetching
- Caching: Services maintain in-memory caches of loaded courses/labs
- Progressive Enhancement: Works without JavaScript for basic content
The service layer encapsulates business logic and external service integrations. Each service follows a consistent pattern:
// types.ts - Type definitions
export interface XService {
// State
someData: Map<string, Data>;
// Methods
loadData(): Promise<void>;
processData(input: Input): Output;
}
// index.ts - Public exports
export { xService } from "./services/x.svelte";
export type { XService } from "./types";
// services/x.svelte.ts - Implementation
export const xService: XService = {
someData: new Map(),
async loadData() {
// Implementation
},
processData(input) {
// Implementation
}
};Purpose: Manages course data loading, caching, and navigation
Key Responsibilities:
- Fetch and parse course JSON from CDN/origin
- Cache loaded courses and learning objects
- Manage lab state with
LiveLabinstances - Handle course tree decoration and URL resolution
Key Files:
services/course.svelte.ts: Main course loading and caching logicservices/live-lab.ts: Interactive lab session managementservices/lo-tree.ts: Course tree traversal and decorationtypes.ts: TypeScript interfaces
Usage Example:
import { courseService } from "$lib/services/course";
const course = await courseService.readCourse(courseId, fetch);
const topic = await courseService.readTopic(courseId, topicId, fetch);Purpose: Real-time presence, analytics, and social features
Sub-services:
- Analytics Service: Tracks learning events to Supabase
- Presence Service: Manages course-specific real-time student presence
- Live Service: Platform-wide live activity monitoring
- Catalogue Service: Manages course catalogue and visit statistics
Key Technologies:
- Supabase for analytics storage
- PartyKit WebSockets for real-time communication
Key Files:
services/analytics.svelte.ts: Learning event trackingservices/presence.svelte.ts: Course presence trackingservices/live.svelte.ts: Global live activityutils/supabase-client.ts: Supabase client initialization
Purpose: Manages UI themes, icons, layouts, and display modes
Features:
- Light/dark mode toggle
- Multiple icon themes
- Card layout modes (expanded/compacted)
- Card styles (portrait/landscape/circular)
- Festive mode (snow animation)
Key Files:
services/themes.svelte.ts: Theme state and methodstypes.ts: Theme-related type definitions
Usage Example:
import { themeService } from "$lib/services/themes";
themeService.toggleDisplayMode(); // Light/dark toggle
themeService.setLayout("compacted");
const icon = themeService.getIcon("lab");Purpose: User authentication and session management
Features:
- GitHub OAuth integration
- Session persistence
- User profile management
- Course access control
Key Files:
services/connect.svelte.ts: Connection service implementationutils/allCourseAccess.ts: Course access utilities
Purpose: Markdown processing and rendering
Features:
- Markdown to HTML conversion
- Syntax highlighting with Shiki
- Code block enhancements (copy button)
- Custom markdown-it plugins
Key Files:
services/markdown.svelte.ts: Markdown processing logic
Components are organized by function and reusability:
src/lib/ui/
├── components/ # Generic, reusable components
├── learning-objects/ # Domain-specific LO components
│ ├── content/ # Content renderers (Lab, Video, Talk, etc.)
│ ├── layout/ # Layout components (Cards, Panels, Wall)
│ └── structure/ # Structural components
├── navigators/ # Navigation components
│ ├── buttons/ # Navigation buttons
│ ├── footers/ # Footer components
│ ├── titles/ # Title/header components
│ └── tutors-connect/ # User menu/profile
└── time/ # Time tracking & analytics UI
The platform supports various learning object types, each with dedicated renderers:
| Type | Component | Purpose |
|---|---|---|
| Course | Course.svelte |
Course home page |
| Topic | Topic.svelte |
Topic collection page |
| Lab | Lab.svelte |
Interactive step-by-step tutorials |
| Talk | TalkClient.svelte, TalkAdobe.svelte |
Presentation viewers |
| Video | Video.svelte |
Video player |
| Note | Note.svelte |
PDF/document viewer |
| Wall | Wall.svelte |
Gallery/grid view of content |
| PanelTalk | PanelTalk.svelte |
Panel-based presentations |
Cards (Cards.svelte, Card.svelte):
- Displays collections of learning objects as cards
- Supports multiple card styles (portrait/landscape/circular)
- Responsive grid layouts
Panels (Panels.svelte):
- Tabbed/panel interface for structured content
- Used in courses and topics
Units (Units.svelte):
- Displays sequential units/modules
All content components follow this pattern:
<script lang="ts">
import type { Lo, Course } from "@tutors/tutors-model-lib";
import { courseService } from "$lib/services/course";
interface Props {
lo: Lo;
course: Course;
}
let { lo, course }: Props = $props();
// Component logic
</script>
<!-- Template -->The application uses SvelteKit's file-based routing with route groups for organization:
routes/
├── (auth)/ # Authentication routes (no layout)
│ └── auth/
│ └── +page.svelte
│
├── (course-reader)/ # Course content routes
│ ├── +layout.svelte # Shared course layout
│ ├── course/[courseId]/
│ ├── topic/[courseId]/[topicId]/
│ ├── lab/[courseId]/[...labId]/
│ ├── talk/[courseId]/[talkId]/
│ ├── video/[courseId]/[videoId]/
│ ├── note/[courseId]/[noteId]/
│ └── ...
│
├── (home)/ # Public home routes
│ └── +page.svelte # Landing page
│
└── (live)/ # Real-time presence routes
├── live/
└── catalogue/
Routes use dynamic parameters for content navigation:
[courseId]: Course identifier (e.g.,reference-course)[topicId]: Topic identifier within a course[...labId]: Catch-all for lab steps (e.g.,lab-01/step-01)
Landing Page (/)
│
├─→ Course List → Course (/course/[courseId])
│ │
│ ├─→ Topic (/topic/[courseId]/[topicId])
│ │ │
│ │ └─→ Lab/Talk/Video/Note
│ │
│ └─→ Time Dashboard (/time/[courseId])
│
└─→ Live View (/live)
│
└─→ Catalogue (/catalogue)
1. User navigates to /course/[courseId]
│
▼
2. +page.ts calls courseService.readCourse()
│
▼
3. courseService fetches tutors.json from CDN
│
▼
4. Course tree decorated with URLs and metadata
│
▼
5. Course cached in courseService.courses Map
│
▼
6. currentCourse rune updated
│
▼
7. +page.svelte renders course with reactive data
1. User clicks on Lab card
│
▼
2. Navigate to /lab/[courseId]/[labId]
│
▼
3. +page.ts calls courseService.readLab()
│
▼
4. courseService creates LiveLab instance
│
▼
5. LiveLab fetches markdown files and converts to HTML
│
▼
6. Lab cached in courseService.labs Map
│
▼
7. +page.svelte renders Lab component
User views learning object
│
▼
analyticsService.reportPageLoad()
│
▼
presenceService.sendLoEvent()
│
├─→ Supabase (analytics storage)
│
└─→ PartyKit WebSocket (real-time broadcast)
│
▼
Other connected clients receive presence update
Purpose: Analytics storage and user data
Configuration:
PUBLIC_SUPABASE_URL="https://xxx.supabase.co"
PUBLIC_SUPABASE_ANON_KEY="xxx"Tables Used:
- Learning event logs
- User sessions
- Course catalogue
- Student visit records
Client Setup (src/lib/services/community/utils/supabase-client.ts):
import { createClient } from "@supabase/supabase-js";
export const supabase = createClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY);Purpose: Real-time WebSocket communication
Configuration:
PUBLIC_party_kit_main_room="https://tutors.partykit.dev"Connection Pattern:
import PartySocket from "partysocket";
const socket = new PartySocket({
host: PUBLIC_party_kit_main_room,
room: courseId
});
socket.addEventListener("message", (event) => {
// Handle real-time events
});Purpose: User authentication
Configuration:
PRIVATE_AUTH_GITHUB_ID="xxx"
PRIVATE_AUTH_GITHUB_SECRET="xxx"
PRIVATE_AUTH_SECRET="xxx"Flow:
- User clicks "Sign in with GitHub"
- Redirect to GitHub OAuth
- Callback to
/auth/callback - Session stored in cookies
- User data available in
$page.data.user
Purpose: Fetch course JSON and assets
URL Pattern:
https://[courseUrl]/tutors.json
https://[courseUrl]/[topicId]/[loId]/[file]
Protocol: Configurable via courseProtocol rune (http/https)
# Clone repository
git clone https://github.com/tutors-sdk/tutors.git
cd tutors
# Install dependencies
npm install
# Configure environment
cp .env.example .env
# Edit .env with your keys (or set PUBLIC_ANON_MODE=TRUE)
# Start dev server
npm run devnpm run dev
# Runs on http://localhost:3000Hot Module Replacement (HMR): Vite provides instant updates during development
To test with a local course:
- Generate a course using
tutors-gen-lib(from tutors-apps) - Serve the course locally or deploy to a static host
- Navigate to:
http://localhost:3000/course/[your-course-url]
{
"dev": "vite dev", // Start dev server
"build": "vite build", // Production build
"preview": "vite preview", // Preview production build
"check": "svelte-check", // Type checking
"check:watch": "svelte-check --watch",
"format": "prettier --write .", // Format code
"lint": "prettier --check . && eslint ." // Lint code
}Type Checking:
npm run checkLinting:
npm run lintFormatting:
npm run formatLearning objects are the atomic units of content in Tutors. Each has:
- Type: course, topic, lab, talk, video, note, etc.
- Route: URL path to access the object
- Metadata: Title, summary, icon, image
- Children: Nested learning objects (topics contain labs/talks/etc.)
Type Definition (from @tutors/tutors-model-lib):
interface Lo {
type: string;
title: string;
route: string;
summary?: string;
icon?: IconType;
img?: string;
los?: Lo[]; // Children
// ... more properties
}Courses are hierarchical:
Course
├── Topic 1
│ ├── Lab 1
│ ├── Talk 1
│ └── Video 1
├── Topic 2
│ ├── Lab 2
│ └── Note 1
└── Topic 3
└── ...
Tree Decoration (lo-tree.ts):
- Adds
routeproperty to each Lo - Builds lookup indexes (
topicIndex,labIndex, etc.) - Calculates
courseUrlfor asset fetching
Svelte 5 uses runes for fine-grained reactivity:
// Global state
export const currentCourse = rune<Course | null>(null);
// Usage in components
$effect(() => {
if (currentCourse.value) {
console.log("Course changed:", currentCourse.value.title);
}
});Key Runes:
$state: Reactive state$derived: Computed values$effect: Side effects$props: Component props
Services are singleton objects exported directly:
// services/x.svelte.ts
export const xService: XService = {
data: new Map(),
async loadData() {
// Shared state across entire app
}
};
// Import and use anywhere
import { xService } from "$lib/services/x";
await xService.loadData();When PUBLIC_ANON_MODE=TRUE:
- No authentication required
- Analytics disabled
- Presence features hidden
- Ideal for local development without backend setup
The project currently relies on:
- Type Safety: TypeScript with strict mode
- Linting: ESLint with Svelte plugin
- Type Checking:
svelte-checkfor Svelte component types - Manual Testing: Browser-based testing during development
For contributors looking to add tests:
- Unit Tests: Consider Vitest for service layer testing
- Component Tests: Svelte Testing Library
- E2E Tests: Playwright for critical user flows
- Visual Regression: Storybook + Chromatic (optional)
- TypeScript: All new code should be typed
- Prettier: Code must be formatted (run
npm run format) - ESLint: No linting errors (run
npm run lint) - Svelte Check: No type errors (run
npm run check)
npm run buildThis creates a production build in the build/ directory.
The project uses adapter-auto which supports:
- Netlify: Primary deployment target (adapter-netlify included)
- Vercel: Auto-detected
- Cloudflare Pages: Auto-detected
- Node.js: Fallback adapter
Required for full functionality:
# Authentication
PRIVATE_AUTH_GITHUB_ID="xxx"
PRIVATE_AUTH_GITHUB_SECRET="xxx"
PRIVATE_AUTH_SECRET="xxx"
# Analytics
PUBLIC_SUPABASE_URL="xxx"
PUBLIC_SUPABASE_ANON_KEY="xxx"
# Real-time presence
PUBLIC_party_kit_main_room="xxx"
# PDF viewer
PUBLIC_PDF_KEY="xxx"For anonymous mode (no backend):
PUBLIC_ANON_MODE=TRUEStatic assets in static/ are served from the root path:
static/favicon.png→/favicon.pngstatic/lib/pdf.worker.min.mjs→/lib/pdf.worker.min.mjs
Course JSON files are fetched from external URLs. Ensure:
- CORS headers allow requests from your domain
- Content is publicly accessible (or auth is handled)
- HTTPS is used in production
- Fork the repository
- Create a branch from
development(notmain) - Make your changes with clear, focused commits
- Test locally - verify your changes work as expected
- Submit a Pull Request to the
developmentbranch
# 1. Fork and clone
git clone https://github.com/YOUR_USERNAME/tutors.git
cd tutors
# 2. Create feature branch
git checkout -b feature/my-new-feature development
# 3. Make changes and commit
git add .
git commit -m "Add: Description of changes"
# 4. Push to your fork
git push origin feature/my-new-feature
# 5. Open PR to tutors-sdk/tutors:developmentFollow conventional commits:
Add: New feature or functionality
Update: Enhancement to existing feature
Fix: Bug fix
Refactor: Code restructuring without behavior change
Docs: Documentation changes
Style: Code style/formatting changes
Test: Adding or updating tests
- Indentation: 2 spaces
- Quotes: Double quotes for strings
- Semicolons: Not required (Prettier handles)
- Imports: Organized with
$lib/aliases
Good First Issues:
- Bug fixes (labeled
fix) - Documentation improvements
- UI/UX enhancements
- Accessibility improvements
Feature Contributions:
- Discuss in an issue first (labeled
feature) - Ensure alignment with project goals
- Include documentation updates
What to Avoid:
- Breaking changes without discussion
- Large refactors without prior approval
- Dependencies with incompatible licenses
- Code follows project style (run
npm run format) - No linting errors (
npm run lint) - Type checking passes (
npm run check) - Changes tested locally
- PR targets
developmentbranch - PR description explains what and why
- Related issue linked (if applicable)
- Issues: GitHub Issues
- Discussions: Use issue comments or discussions
- Documentation: Tutors Reference Manual
- Lo (Learning Object): Atomic unit of content (lab, talk, video, etc.)
- Course: Collection of topics and learning objects
- Topic: Grouping of related learning objects
- Lab: Step-by-step interactive tutorial
- Talk: Presentation/slide deck
- Wall: Gallery view of learning objects
- Tutors Connect: User authentication and session management
- Presence: Real-time tracking of online students
- Catalogue: Course directory and statistics
- Live Application: https://tutors.dev
- Documentation: Reference Manual
- Example Course: Reference Course
- Gallery: Course Gallery
- GitHub: tutors-sdk Organization
- 15.2.0 (Current): Latest stable release
- See CHANGELOG.md for detailed version history
This project is licensed under the MIT License. See LICENSE file for details.
Last Updated: June 2026
Maintainer: Tutors SDK Team
Contributors: See GitHub Contributors
For questions or clarifications about this architecture document, please open an issue on GitHub.