A shared platform for education businesses that teach live.
Product vision · Current status · Quickstart · Architecture · Documentation
LearnStack is being built as a white-label platform for multi-branch education businesses. A language school, a yoga studio and a music school share one codebase and database schema, while keeping their own content, branding and tenant boundaries. The product design supports a platform subdomain and optional custom domains; a business does not need to bring its own domain.
The endorsed direction adds an optional Course Marketplace alongside those sites: shared discovery, platform checkout, commission and institution payouts. Its pilot plan and direction ADR are Proposed; payment, seller operations and marketplace delivery are still ahead.
The difference between those businesses lives in tenant customization data. The platform vision defines the scope and the boundary between customization and capabilities that require platform code.
Under active development. The backend foundation and Education schema are implemented. The first working browser experience is the current milestone; browser sign-in, enrolment, payments and live teaching are still ahead. See current status before trying the local scaffold.
The product vision connects discovery, course content and live teaching in one place. The planned surfaces serve the people on each side of that experience:
| Surface | Who it serves | Intended experience |
|---|---|---|
| Public site | Visitors and prospective learners | Discover a school, browse its catalog and explore its content. |
| Admin Studio | Institution staff and instructors | Author content, manage people and organize teaching. |
| Learner portal | Enrolled learners | Work through lessons, track progress and join live sessions. |
| Optional Course Marketplace | Learners and participating institutions | Shared discovery and central checkout; endorsed target, architecture approval pending. |
The public catalog, course and lesson pages are implemented. Studio and learner portal remain scaffolds; the status below separates delivered work from remaining product and validation obligations.
Built for different ways of teaching. Content types and level taxonomies already have a validated customization model. The wider design covers page blocks, lesson items, rules, custom fields and notification templates; their delivery is tracked in the roadmap. Institutions and their branches follow the Tenant / Organization model.
Phase 01, Phase 02a and Phase 02d are complete; Phase 02b is in progress. Phase 02d, the two-tenant walking skeleton, merged through PR #28 on 2026-10-11. In Phase 02b, P02b-0 through P02b-5 are complete, P02b-1–5 merged through PRs #29–#33, and P02b-6's realm and identity implementation is merged through PR #34; BFF sessions and phase exit remain P02b-7–8, so browser sign-in is not implemented. Packet state lives in the Phase 02b document's Status block; the roadmap lists every phase.
| Area | Delivered now | Next milestone |
|---|---|---|
| Tenancy | Tenant provisioning, organizations, locales, typed settings/branding reads, host resolution and database isolation | User membership and permissions in Phase 03 |
| Customization | Content types, level taxonomies, exact-definition and generation-cached batched display readers, text-card metadata validation, create-only provisioning defaults and semantic built-in seed verification | Remaining authoring capabilities across Phases 04–08a |
| Audit | Classified write path and transactional durability for business changes | Operational hardening in Phase 11 |
| Education | Course and Lesson aggregates, translations, protected-content policy, scoped authoring commands, complete demo seeds and isolated public GET/HEAD reads | Full versioned curriculum in Phase 05 |
| API foundation | Error contracts, validation, tenancy, concurrency, observability, durable events/subscriber delivery, background jobs, the closed tenant bearer/AccountSite boundary and development realm reconciliation | BFF sessions and phase-exit enforcement in Phase 02b |
| Frontend | Native TLS ingress, live locale entry, trusted server caller, localized catalog/course/lesson pages, safe themes, accessibility controls/manual smoke and generated typed SDK | Full renderer/Studio and Playwright Test/axe in Phase 06 |
Four modules contain domain implementations: Tenancy, Customization, Audit and Education. Identity, Content and Media remain scaffolded.
The subsequent product milestones are CMS and media (04), the full course catalog (05), renderer and Studio (06), enrolment and progress (07), assessment and notifications (08a), scheduling (08b), live classroom (08c), and billing (09). The roadmap dependency map owns the order: 02d runs before 02b, despite the filenames.
Use Docker with Compose V2, Git, Make, Bash, Python 3, curl and OpenSSL (the TLS test fixtures require its CLI), plus:
| Tool | Repository requirement |
|---|---|
| .NET SDK | 10.0.112 with the roll-forward policy in backend/global.json |
| Node.js | >=22.23.1, as declared in frontend/package.json |
| pnpm | 9.12.3, pinned in frontend/package.json |
make hooks # optional: install the repository Git hooksContinue with the manual HTTPS prerequisites below, then run make demo. It
creates missing private configuration and restores dependencies without changing
Git hooks. make seed converges application data and the development identity
realm without starting the API or web. Direct .NET Seeder remains data-only.
The seed provisions demo-english and demo-yoga, their organizations and host
mappings, enabled locales, distinct branding, built-in and tenant-authored definitions,
and eight scoped courses, ten lessons and twenty-seven translations. It uses the
ordinary authoring pipeline and verifies completed acts before skipping them on rerun.
SeedData owns the complete inventory.
Both combined entries then await the same bounded development-realm reconciler.
Older private files must explicitly add KEYCLOAK_WEB_CLIENT_SECRET; existing
passwords/MFA and provider IDs are preserved. Prepare this secret before the
first make dev import, and retain it across reruns. Older files also need
explicit private permissions after verifying their ownership/type; Makefile
creation does not repair existing files. The
realm guide owns setup,
refusal, safe secret recovery and qualification details.
The demo prepares a missing private 32-byte hop secret in the ignored root .env.
Existing values and optional projections must agree; they are not reset.
Install mkcert using its official instructions. Run its trust step yourself, then create the local leaf certificate:
mkcert -install
mkdir -p .data/tls
mkcert -cert-file .data/tls/public.pem -key-file .data/tls/public-key.pem \
localhost demo-english.learnstack.local demo-yoga.learnstack.localManually map the two seeded names to 127.0.0.1 in your hosts file. Repository
commands never edit that file or install trust. Keep the CA private key local;
only the ignored leaf/key paths belong in .env.
make demoThis foreground command restores pinned dependencies, starts the selected default
stack, waits for PostgreSQL before migrations, retains both Keycloak realm gates,
converges the seed and builds/starts the production API and native HTTPS web.
Readiness includes actual seeded lesson content; nine seed-derived URLs are printed.
Ctrl+C joins its owned API/web processes and preserves local infrastructure/data.
Use make down to stop infrastructure explicitly. A second demo invocation or a
foreign listener is refused; no foreign process is killed. If a hard kill leaves
.data/demo.lock, confirm the earlier process tree has stopped before removing
that lock manually. Missing/stale configuration or TLS is refused without reset.
Local demo database validation accepts only Host, Port, Database,
Username and Password, with case-insensitive keys. Supported aliases are
Server for Host; User Name, Userid, User Id and Uid for Username;
and Pwd and Psw for Password. Single- or double-quoted values preserve
whitespace and semicolons; double the matching quote to escape it. Duplicate
canonical fields and unsupported keys are refused. Host must be localhost or
127.0.0.1; port defaults to 5432 and must resolve to 5432. Role, password and
database must match the root configuration. This is the closed local demo
grammar, not general Npgsql connection-string support;
demo-database.mjs owns validation.
For separate development/watch terminals, retain make public-api and
make public-web. The CI lifecycle uses an isolated checkout/project and disposable
configuration through make demo DEMO_MODE=ci. Install its locked managed browser
first with cd frontend && pnpm exec playwright install --with-deps chromium.
The CI mode requires a fresh checkout configuration and artifact directory; use
local make demo for an existing workstation.
Readiness is http://127.0.0.1:5080/healthz for the API and
https://localhost:3000/api/healthz for the web listener. pnpm dev/start also use
this launcher. Both processes read the same private root source; a web .env.local
projection is optional and must match. No shell evaluates the values. Ordinary
no-hop API startup remains supported outside this paired renderer recipe; supply
both ConnectionStrings:Default (learnstack_app) and
ConnectionStrings:OutboxDispatcher (learnstack_outbox_admin). The mandatory
API worker verifies dispatcher connectivity at startup.
Supply Authentication:Tenant:Issuer too: the canonical HTTPS tenant-realm URL,
with loopback HTTP allowed only in Development. The development configuration
defaults to http://localhost:8080/realms/learnstack; issuer configuration is
validated without contacting Keycloak at startup. Credential-free public reads
remain available when Keycloak is stopped. P02b-6 owns current realm issuance/
reconciliation qualification; browser sessions remain P02b-7.
Ingress, visitor budgets and public entry. The web bootstraps the captured host through the API, redirects using enabled/default locales and refuses unavailable scope. The server caller enforces no-store, deadline/body limits and the closed authenticated hop. See local setup and Compose documentation for troubleshooting.
| Command | Purpose |
|---|---|
make help |
List available tasks. |
make demo |
Build/start the seeded production sites; Ctrl+C preserves local data. |
make dev |
Start the default local infrastructure. |
make migrate |
Apply platform and module migration chains. |
make test |
Restore dependencies and run backend and frontend suites; Docker is required. |
make lint |
Check backend formatting and frontend lint rules. |
make down |
Stop infrastructure while preserving its data volumes. |
The service inventory and optional gated profile live in
infra/compose/README.md.
A modular monolith: one ASP.NET Core host, explicit module boundaries and a
PostgreSQL database with a DbContext per module. One Next.js application holds the
three product surfaces. See the
technical architecture and
cross-module contracts.
| Layer | Choice | Current scope |
|---|---|---|
| Backend | .NET 10 · ASP.NET Core · EF Core · MediatR | Foundation and four domain modules implemented |
| Database | PostgreSQL 18 | Migrations, tenant / organization RLS and integration proofs |
| Frontend | Next.js 15 · React 19 · TypeScript | App/UI scaffolds; generated SDK and injected transport implemented |
| Observability | OpenTelemetry · Serilog → OTLP | Cross-cutting backend instrumentation implemented |
| Identity | Keycloak | Tenant API bearer boundary, real issuance and safe development realm reconciliation implemented; browser sessions remain P02b-7 |
| Storage and search | SeaweedFS · PostgreSQL full-text search | Selected architecture; feature delivery belongs to Phases 04–05 |
| Live classroom | LiveKit through a provider adapter | Planned in Phase 08c, starting with Cloud and retaining a self-hosted path |
Infrastructure grows against explicit triggers. The running foundation uses
InProcessEventBus, InMemoryCacheService, ConfigurationSecretProvider and
NullEntitlementProvider. Adapter ownership and activation conditions live in
Infrastructure Stack Standards and
ADR-0035.
Deployment wiring is distinct from product readiness. Development and SaaS
have current backend wiring. Dedicated, SelfHostedOnline and
SelfHostedAirGapped are prepared seams pending
Phase 11.
LearnStack Hub is the separate operator
control plane. Its boundary is explicit: it stores tenant metadata, never tenant
content, and communication goes through named adapters
(ADR-0034).
- Isolation reaches the database. Tenant context, EF query filters and PostgreSQL
RLS work together. Isolation tests run as
learnstack_app, the same non-bypass role used by the API. Database standard. - Required business audit records commit with their changes. The business transaction owns durability; unclassified operations fail closed. Audit write path.
- Architecture claims have executable checks. Module boundaries, isolation conventions and documentation claims are checked against a named test catalogue.
- Verification includes failure cases. The suites exercise planted violations and mutations as well as successful paths. CI rejects skipped backend tests; the workflow defines the current checks.
backend/
src/ API, shared kernel, infrastructure and domain modules
tests/ Unit, architecture, contract and integration suites
analyzers/ LearnStack Roslyn analyzers
frontend/
apps/web/ Public catalog, course and lesson pages
Admin Studio and learner portal scaffolds
packages/ Shared config, UI and generated API SDK
infra/ Compose stack and service configuration
scripts/ Seed and verification helpers
docs/ Architecture, decisions, standards, roadmap and module specs
| Your question | Start here |
|---|---|
| What are we building, and for whom? | Platform vision · MVP scope |
| What works today, and what comes next? | Roadmap · Current phase |
| How do I contribute? | Contributing · Working agreement · Engineering standards |
| Why was a decision made? | ADR index |
| How are institutions isolated? | Tenant isolation · Tenancy module |
| How does customization work? | Customization model · Module spec |
| How are courses and lessons modeled? | Education module |
| How is the audit trail built? | Audit subsystem · Audit module |
| What belongs in the Hub? | Hub architecture · Deployment models |
| What does a term mean here? | Glossary |
Every architecture document — browse by topic
Strategy and shape · 01 Platform Vision · 02 Domain Model · 03 Module Boundaries · 04 Technical Architecture · 05 MVP Scope · 06 Extension Model · 11 Extension Points · 19 MVP Vertical Slice
Platform substrate · 09 Tenant Isolation · 10 Cross-Module Contracts · 15 Events and Outbox · 28 Platform Tenant + Organization · 31 Audit Subsystem · 32 Tenant Customization Model · 33 Cross-Cutting Concerns
Product surfaces · 12 Localization · 13 Identity and Authentication · 14 Frontend Architecture · 16 Media Pipeline · 17 Page Builder · 20 Search · 21 Feature Flags and Entitlements · 23 Data Protection (KVKK / GDPR)
Live classroom · 07 In-App Live Classroom · 08 Cost Model · 18 WebRTC Build vs Adopt
Hub, deployment and edge · 22 Custom Domains · 24 LearnStack Hub · 25 Deployment Models · 26 Hybrid License Model · 27 Custom Domain + TLS · 29 Dapr Integration · 30 API Gateway (APISIX)
| Directory | Purpose |
|---|---|
docs/architecture/ |
Product and system concepts; browse the complete index above. |
docs/decisions/ |
Architectural decisions, their context and consequences. |
docs/standards/ |
Engineering rules and their enforcement status. |
docs/roadmap/ |
Dependency-ordered phases, decision gates and delivery records. |
docs/modules/ |
Module contracts, permission matrices and audit coverage. |
Documentation is English; diagrams use Mermaid with a text fallback. Commits follow Conventional Commits. The glossary, ADRs, standards and roadmap each own their respective facts; other documents link to those sources. See Contributing, the documentation standard and review standard before opening a pull request.