Version: 0.1.0-draft
Date: Jul 16, 2026
Author: CTO
Status: Planning
Repo: codecoradev/sinau-lms
Sinau LMS is a modern, lightweight, self-hostable open-source learning management system. Built with Rust (Axum) + SvelteKit 5 for optimal performance and developer experience.
Name: Sinau - Javanese (Krama) for "to learn".
Tagline: Learn without limits, platform without weight.
| Persona | Description | Core Needs |
|---|---|---|
| Content Creator | Teacher, instructor, content writer | Create courses, manage chapters, publish content |
| Student/Learner | Online learner | Access courses, track progress, self-paced learning |
| Admin | Platform manager | Manage users, orgs, analytics, configuration |
| Developer | Integrator / headless consumer | API access, webhooks, custom frontend |
LearnHouse is the most modern open-source LMS today, but uses Python (FastAPI) + Next.js (React). For an ecosystem already built on Rust + SvelteKit (Hompimpah), an LMS is needed that:
- Consistent stack - Rust backend, SvelteKit frontend
- Lightweight - runs on SQLite for development, PostgreSQL for production
- Not over-engineered - without unused features (code execution, podcasts, trails)
- Full control - custom domain logic, auth module, deployment patterns
| Field | Specification |
|---|---|
| Registration | OAuth-only (no email/password). Google, GitHub, Keycloak. |
| Login | OAuth2 Authorization Code flow with CSRF protection (state token). |
| Session | JWT (HS256), 7-day expiry, HttpOnly cookie. |
| Logout | Clear session cookie. |
| Profile | Email, name, avatar (from OAuth provider). |
| RBAC Roles | , , . Admin = full access, Teacher = create/manage courses, Student = consume content. |
| Admin Emails | ENV-based - emails matching auto-promoted to admin. |
User Model:
| Field | Specification |
|---|---|
| Single-tenancy first | One default org. Multi-org later. |
| Org Model | slug, name, logo, config JSON |
| Membership | User <-> Org via UserOrganization table |
Organization Model:
| Field | Specification |
|---|---|
| CRUD | Create, Read, Update, Delete courses |
| Published/Public | = visible to authenticated users. = visible without auth. |
| Thumbnail | Image upload, stored as file. |
| Clone | Duplicate course with all chapters + activities. |
| Meta Endpoint | GET single course with all chapters + activities (nested). |
| List | Paginated, filterable by org. |
Course Model:
| Field | Specification |
|---|---|
| Hierarchical | Chapter belongs to Course via join table. |
| Reorder | POST /chapters/reorder with ordered IDs. |
| Activities | Chapter has many activities (ordered). |
Chapter Model:
| Field | Specification |
|---|---|
| Activity Types | (TipTap JSON), , |
| Content Storage | TYPE_DYNAMIC -> column (TipTap JSON). Others -> . |
| Published | Activity-level published flag. |
| Lock Type | (free), , (future). |
Activity Model:
| Field | Specification |
|---|---|
| Editor | TipTap rich text editor (Svelte extension). See editor selection analysis below. |
| Block Types | Heading (1-6), Paragraph, Bullet List, Ordered List, Code Block, Blockquote, HR, Image, Table |
| Inline Types | Bold, Italic, Code, Link, Strikethrough |
| Storage Format | TipTap/ProseMirror JSON (LearnHouse-compatible). |
| Image Upload | Inline image upload within editor. |
Editor Selection Analysis:
Why TipTap over alternatives? Full comparison below.
| Editor | Based On | Svelte Support | Bundle Size | License | Collaboration | Verdict |
|---|---|---|---|---|---|---|
| TipTap | ProseMirror | Official extension | Small (tree-shakable) | MIT (core) | Yjs + Tiptap Cloud + Liveblocks | Selected |
| BlockNote | TipTap + ProseMirror | No (React-only) | Larger | MPL 2.0 | Yjs + Liveblocks | Not suitable (React-only) |
| Lexical | Custom (Meta) | Community/unofficial | Larger core | MIT | Yjs + Liveblocks (buggy) | Immature (< 1.0), collaboration issues |
| Slate | Custom | Community/unofficial | Medium | MIT | Yjs (no official) | No Svelte support, harder API |
| ProseMirror | - | No (Vanilla JS) | Small | MIT | Yjs (manual) | Too low-level, no Svelte bindings |
| Plate | Slate | No (React-only) | Medium | MIT | Hocuspocus (Yjs) | React-only |
Why TipTap:
- ProseMirror foundation - Battle-tested, proven at scale (used by GitBook, New York Times).
- Framework-agnostic - Official Svelte extension exists. Same core works across frameworks.
- Headless - Full control over UI. Build custom toolbars, menus, slash commands.
- Extension system - Add nodes, marks, plugins without forking. Override behavior of existing extensions.
- JSON storage - ProseMirror JSON format. Compatible with LearnHouse content. Easy to parse/validate server-side.
- Collaboration-ready - Yjs support built-in. Can add real-time co-editing later (Phase 2+) without switching editor.
- MIT license (core) - Open-source core is genuinely free. Pro features (AI, comments, etc.) are paid but not needed for MVP.
- Tree-shakable - Only ship extensions you use. Core bundle is smaller than Lexical, Slate, and Quill.
Alternatives considered and rejected:
- BlockNote - Great for Notion-like block editing, but React-only. No Svelte support. Deal-breaker.
- Lexical - Backed by Meta, active development. But: no pure decorations (DOM workarounds needed), collaboration has hardcoded root node (can't have multiple editors per doc), no official Svelte support, still pre-1.0.
- Slate - Powerful but React-only ecosystem. Steep learning curve. No Svelte bindings.
- ProseMirror - Too low-level. No Svelte bindings. Would need to build TipTap-equivalent on top of it anyway.
Editor Selection Analysis:
Why TipTap over alternatives?
| Editor | Based On | Svelte Support | Bundle Size | License | Collaboration | Verdict |
|---|---|---|---|---|---|---|
| TipTap | ProseMirror | Official extension | Small (tree-shakable) | MIT (core) | Yjs + Tiptap Cloud + Liveblocks | Selected |
| BlockNote | TipTap + ProseMirror | No (React-only) | Larger | MPL 2.0 | Yjs + Liveblocks | Rejected (React-only) |
| Lexical | Custom (Meta) | Community/unofficial | Larger core | MIT | Yjs + Liveblocks (buggy) | Rejected (pre-1.0, collab issues) |
| Slate | Custom | Community/unofficial | Medium | MIT | Yjs (no official) | Rejected (no Svelte, harder API) |
| ProseMirror | - | No (Vanilla JS) | Small | MIT | Yjs (manual) | Rejected (too low-level) |
| Plate | Slate | No (React-only) | Medium | MIT | Hocuspocus (Yjs) | Rejected (React-only) |
Why TipTap:
- ProseMirror foundation - Battle-tested at scale (GitBook, New York Times).
- Framework-agnostic - Official Svelte extension. Same core across frameworks.
- Headless - Full UI control. Custom toolbars, menus, slash commands.
- Extension system - Add/override nodes, marks, plugins without forking.
- JSON storage - ProseMirror JSON. LearnHouse-compatible. Server-side parseable.
- Collaboration-ready - Yjs support built-in. Real-time co-editing can be added later without switching.
- MIT license (core) - Genuinely free core. Pro features (AI, comments) are paid but not needed for MVP.
- Tree-shakable - Ship only extensions you use. Core smaller than Lexical, Slate, Quill.
Alternatives rejected:
- BlockNote - Great Notion-like editing but React-only. No Svelte = deal-breaker.
- Lexical - Meta-backed, active dev. But: no pure decorations, collab hardcodes root node (one editor per doc), no official Svelte support, pre-1.0.
- Slate - Powerful but React-only ecosystem. Steep learning curve.
- ProseMirror - Too low-level. No Svelte bindings. Would rebuild TipTap on top anyway.
| Field | Specification |
|---|---|
| Endpoint | POST /media/upload (multipart). |
| Storage | Pluggable: local filesystem OR S3-compatible (MinIO, AWS S3, Cloudflare R2). Toggle via env. |
| Supported Types | Image (png, jpg, webp, gif, svg), Document (pdf, doc), Video (mp4, webm). |
| Max Size | 10MB default (ENV configurable). |
Media Model:
| Field | Specification |
|---|---|
| Purpose | Headless API access (without OAuth/JWT session). |
| Generation | Admin/Teacher can create tokens. |
| Format | prefix + random 32 chars. |
| Auth | header. |
ApiToken Model:
| Field | Specification |
|---|---|
| Purpose | Notify external systems on events. |
| Events | , , , , , |
| Delivery | POST to configured URL with signed payload (HMAC-SHA256). |
| Retry | 3 retries with exponential backoff. |
Webhook Model:
| ID | Feature | Brief Spec |
|---|---|---|
| F-010 | Search | Full-text search via SQLite FTS5 / Postgres tsvector. |
| F-011 | Analytics | User progress per course, time spent, enrollment count. |
| F-012 | Folders | Group courses into named folders. |
| F-013 | User Groups | Named groups of users. Control access. |
| F-014 | Communities | Threaded discussion per course. Posts, replies, likes. |
| F-015 | Assignments | Tasks with due date, submission, grading. |
| F-016 | Certificates | Auto-generated PDF on course completion. |
| F-017 | Boards | Collaborative whiteboard per course. |
| ID | Feature | Brief Spec |
|---|---|---|
| F-018 | AI Features | RAG tutoring, embeddings. Requires pgvector. |
| F-019 | Payments | Stripe integration. Sell courses. |
| F-020 | SSO | SAML/OIDC via WorkOS or Keycloak advanced. |
| F-021 | Custom Domains | Per-org custom domain routing. |
| F-022 | Billing | Usage tracking, subscription plans. |
| Metric | Target |
|---|---|
| API response (read) | < 50ms p95 (single entity) |
| API response (list) | < 200ms p95 (20 items) |
| Page load (SSR) | < 500ms TTFB |
| Concurrent users | 100+ (SQLite), 1000+ (Postgres) |
| Requirement | Implementation |
|---|---|
| Authentication | JWT (HS256) HttpOnly cookie + CSRF state token |
| Authorization | RBAC per-route via Axum middleware |
| Input validation | sqlx compile-time checked SQL |
| File upload | Type whitelist, size limit, no executable files |
| CORS | Configurable origins (ENV) |
| Requirement | Implementation |
|---|---|
| Database backups | sqlite3 backup / pg_dump via cron |
| Error handling | Unified error type, structured JSON errors |
| Logging | tracing crate, structured JSON logs |
| Health check | GET /api/health - DB connectivity status |
- RESTful - Standard HTTP methods (GET, POST, PUT, DELETE)
- JSON - All request/response bodies in JSON
- Consistent errors -
- Pagination - pattern
- Auth - Cookie-based (JWT) OR Bearer token (API tokens)
- Prefix - All API routes under
- No trailing slash - Consistent
| Layer | Technology | Version |
|---|---|---|
| Runtime | Bun | latest |
| Frontend | SvelteKit | 5.x |
| UI Framework | Svelte 5 (runes) | 5.x |
| Styling | Tailwind CSS | 4.x |
| UI Components | shadcn-svelte | latest |
| Editor | TipTap | 2.x |
| Backend | Rust (Axum) | 0.8.x |
| DB Access | sqlx | 0.8.x |
| Database | SQLite (dev) / PostgreSQL (prod) | 16+ |
| Migration | sqlx-cli | 0.8.x |
| Auth | JWT (jsonwebtoken) | latest |
| HTTP Client | reqwest | latest |
| Logging | tracing | latest |
| Serialization | serde + serde_json | latest |
| Milestone | Scope | Target | Status |
|---|---|---|---|
| M1 - Planning | PRD, ERD, Roadmap, Repo setup | Jul 16, 2026 | In Progress |
| M2 - Foundation API | Skeleton, config, DB, auth, users, orgs, courses | Week 1-3 | Pending |
| M3 - Content API | Media upload, content CRUD, course clone | Week 3-5 | Pending |
| M4 - Access Control API | RBAC, orgs, API tokens, webhooks, folders, groups | Week 5-7 | Pending |
| M5 - Engagement API | Search, analytics, discussions, assignments, certs | Week 7-9 | Pending |
| M6 - Frontend | SvelteKit dashboard, editor, public view, settings | Week 9-12 | Pending |
| M7 - Production | Docker, deploy, CI, docs, release | Week 12-13 | Pending |