Rajveer Singh✕
Client Project · Under NDA

Enterprise
Learning
Platform

In active developmentAngular 22 · zonelessSQLite in the browserClient name withheld
Introduction

A learning management system built for a public-sector education client, covering the full course lifecycle — a specialist authors a course, publishes it, a learner finds it, enrols, is approved, works through it, passes a real assessment and walks away with a transcript entry and a certificate.

The benchmark was the depth of a tier-one enterprise HCM learning suite. Courses split into offerings, offerings into sections and activities, each carrying completion rules, sequencing and prerequisites; access is governed by rule-based groups; and five roles see five different products built on the same data.

I own the front-end architecture and the data layer. The entire system runs in the browser today — a real relational database compiled to WebAssembly — behind repository interfaces, so the client's backend can replace it later without a single screen being rewritten.

Home springboard — greeting, tabbed quick actions and a grid of app tiles
The home springboard — glass tiles, gliding tabs and quick actions. Client branding, names and data have been removed from every screen on this page.
20K
Lines of Source
38
Database Tables
35
Components
21
Routes
5
User Roles

The Whole
Course Lifecycle

01
Course Authoring
Reusable course definitions with generated catalogue numbers, syllabus, skills granted on completion, renewal rules and a draft → active publish flow with an audit trail.
02
Offerings & Delivery
Each course ships as one or more offerings — self-paced, instructor-led or blended — with its own capacity, waitlist, coordinator, location and publish window.
03
Activities Builder
Sections of video, PDF, SCORM, web-link, assessment and survey activities, each with a completion rule, a minimum score, sequencing and prerequisites.
04
Catalogue Access
Per course and per offering: open, restricted or hidden, refined by access groups built from rules over department, job, location or named people.
05
Learner Catalogue
Search with delivery-mode and skill filters, a featured strip, and course pages with syllabus, outcomes and prerequisites ticked off as they complete.
06
Enrolment Engine
One service resolves every enrolment into immediate, manager-approval or waitlisted — and promotes the next waitlisted learner whenever a seat is released.
07
Manager Approvals
A team area with an approvals inbox (approve or reject with a reason), direct reports' learning status, and assigning a course to someone on the team.
08
Activity Player
A focused two-pane player with a sticky syllabus rail. Activities unlock in sequence or behind prerequisites, and progress is recorded per attempt.
09
Real Assessments
Single- and multi-answer questions graded against a pass threshold, with retries, followed by a Kirkpatrick level-one evaluation after completion.
10
History & Certificates
A transcript grouped by year with completion date, score and expiry, plus a printable certificate generated for every completed course.
11
Notifications
Event-driven in-app notifications for requests, approvals, completions and waitlist promotions, surfaced through an unread badge in the app bar.
12
Screen Studio
An administrator workspace that renames, hides, reorders and adds fields and columns on any authoring screen — and sets what each role may see or edit.
Course catalogue admin table with numbers, delivery types, suppliers and status
Course catalogue — columns are configuration, not code.
Offering editor showing sections and activities with completion rules
Activities builder — sections, rules, pass marks and sequencing.

One Door
Into Every Course

Enrolment

Every path into a course — self-service, manager approval, a full offering with a waitlist — goes through one service. Screens never decide a status themselves, which is what keeps seat counts, approval tasks and notifications consistent no matter where the learner clicked.

enrollment.service.ts
// enrollment.service.ts — one entry point decides how a learner gets a seat.
const mode: EnrollMode = offering.selfEnroll ?? course.selfEnroll;
if (mode === "OFF") return reject("Self-enrolment is not available.");

const seatFull =
  offering.capacityMax != null &&
  assignments.seatCount(offeringId) >= offering.capacityMax;

if (mode === "REQUESTED" && manager) {
  status = "REQUESTED";      // → approval task routed to the learner's manager
} else if (seatFull) {
  if (!offering.waitlistAllowed) return reject("Offering is full.");
  status = "WAITLISTED";     // → auto-promoted the moment a seat frees
} else {
  status = "NOT_STARTED";    // → straight into My Learning
}
assignment lifecycle
REQUESTED ──approve──▶ NOT_STARTED ──first activity──▶ IN_PROGRESS ──all required done──▶ COMPLETED
   └─reject──▶ REJECTED                                                                  │
WAITLISTED ──seat frees──▶ NOT_STARTED                                          renewal ─▶ EXPIRED
any active state ──learner / manager──▶ WITHDRAWN
Learner catalogue with search, delivery and skill filters, featured and all courses
The learner side — a catalogue filtered to exactly what this person is allowed to see.

Hard Problems
I Solved

Challenge 01

A Relational Database With No Server

The Problem: The product had to be demonstrable end to end — real enrolments, approvals, scores, transcripts — long before the client's backend existed. Mock JSON would have meant building every screen twice, and throwing the data logic away when the real API arrived.

My Solution: SQLite compiled to WebAssembly, running in the tab. On boot it restores the last snapshot from IndexedDB, runs ordered, append-only migrations, and seeds if empty; every write schedules a debounced snapshot, with a pagehide flush as a safety net. Seventeen repositories are the only code allowed to write SQL, and screens depend on their interfaces through injection tokens — so moving to an HTTP backend is a change of provider bindings, not a rewrite.

Challenge 02

Screens the Client Can Reshape Without a Release

The Problem: Enterprise customers always want one more field — and a different field for each role. Hard-coding forms means a code change, a schema migration and a deployment for every request.

My Solution: Forms and tables render from metadata. Shipped manifests are the defaults and the database is the truth: an administrator renames, hides, reorders and adds fields in Screen Studio, and sets per-role view and edit rules. New fields claim one of twenty reserved columns on each core table, then spill into a JSON column — no migration. Identity fields are locked so they can be renamed but never removed, which keeps the configuration from breaking the product.

Challenge 03

Completion Rules That Can't Disagree With Each Other

The Problem: Completing a course touches everything: the completion date, a rolled-up score, skills on the learner's profile, an expiry from the renewal rule, an audit row, notifications. If three screens can each mark something complete, they will eventually disagree.

My Solution: One recompute() in the progress service is the only place a status becomes COMPLETED, and it does all of the above in one pass. Activities unlock through sequencing and prerequisite checks, assessments grade against a pass threshold, and ownership is enforced on the row itself — the player refuses an assignment that isn't yours, because a route guard alone isn't enough.

Challenge 04

A Glass Interface That Survives Its Own Animations

The Problem: The design called for frosted-glass tiles and a gliding tab pill. Live backdrop blur visibly pops whenever an ancestor is transformed or faded — which is exactly what a tab transition does — and switching panels caused the page to jump as heights changed.

My Solution: The glass is built from layered translucent fills, hairline borders, inner highlights and a specular sheen over a soft page wash — no live blur at all. All tab panels stay mounted in a single grid cell, so the container is always the tallest panel and never jumps; switching is an opacity crossfade with a small glide, while one pill slides to the active tab's measured box. Every colour, size and spacing value is a token, and layout uses logical properties throughout, so the interface is ready for right-to-left languages.

Catalogue overview charts — courses by status, by learning type, and added over time
Catalogue insights under the admin tables, themed from the same token set as the UI.

Five Roles,
One Codebase

Access

Behaviour is gated on capabilities, never on role names. Each role maps to a set of capabilities, routes match on them, and a person can hold several roles at once — so adding a role is a data change rather than a search through templates.

LearnerManagerInstructorLearning SpecialistScreen Administrator

Architecture
& Stack

Framework
Angular 22 — standalone, zoneless, signals
OnPush everywhere, new control flow, no NgModules
Components
Angular Material 22 (M3), Angular CDK
Material tokens re-pointed at the brand design tokens
Database
SQLite via sql.js (WebAssembly)
38 tables, append-only migrations, seeded demo data
Persistence
IndexedDB snapshots via idb
Debounced writes with a page-hide flush
Data access
Repository pattern behind injection tokens
Backend swap is a provider change, not a rewrite
Configuration
Metadata-driven forms & tables
Role-filtered layouts, reserve columns, locked fields
Charts
ApexCharts behind a thin wrapper
Validated categorical palette from the token set
Design system
SCSS tokens, logical properties
No raw colours in components, RTL-ready
Icons
345-icon brand set, build-time processed
Recoloured to currentColor, typed name manifest
Testing
Vitest via the Angular CLI, jsdom
Tests and production build kept green per hand-off
Role
Front-end architecture, data layer & UI engineering
Author
Rajveer Singh
Client
Public-sector education — name withheld under NDA
Type
Enterprise learning management system
Frontend
Angular 22, Angular Material, SCSS, ApexCharts
Data
SQLite (sql.js / WebAssembly), IndexedDB
Delivered
Authoring & administration · the self-paced learner lifecycle
Next
Instructor-led sessions, learning paths & certifications, SCORM / xAPI runtime
Status
In active development — 2026