Mastery LS takes online learning to the next level by boosting maintainable content creation and focusing on learner mastery.
- Markdown for content creation sanity
- Content management using GitHub for version control
- AI powered for content generation, learner feedback, quiz generation
- Video and interactivity powered
- Project based mastery
- Content that leverages the online experience. This will include instruction video with interactive paths, textual instruction, and self paced learning.
- Markdown as the primary content format
- GitHub for version control and collaboration
- AI content generation for rapid content development
- Interactive video with branching paths
- Textual instruction with interactive elements
- Inline quizzes with support for multiple choice, multiple answer, and free text responses
- Self-paced learning modules
- Progress tracking and analytics
- Mobile-friendly design for learning on the go
- Offline access to learning materials
- AI-driven feedback for personalized learning
- Data privacy and security measures
- Scalability to accommodate large user bases
- Support for various content formats (videos, PDFs, images, etc.)
- Human Interaction between learning peers and mentors from around the globe in curated cohorts. Peers advance to serve as mentors for other learners.
- Peer review and feedback
- Mentor review and feedback
- Collaboration tools for peer projects
- Mastery Projects that demonstrate authentic learning in creative ways that provide a portfolio of the learner
- Tools for mastery projects
- System Integration that provides mastery reports, enrollments, analytics, and automated notification
- Download reports
- Automated notifications
- Metrics dashboard
- API access for reporting integrations
- API access for enrollments
- API access for analytics
- API access for notifications
- TouchSpeed: Keyboard assisted grading automation
- TouchSpeed: Keyboard assisted feedback generation
- Synchronize content to Canvas
MasteryLS is a web-based Learning System (LS) designed for content mastery and maintainable course creation, leveraging GitHub for content storage, Supabase for backend services, and Gemini AI for content generation and learner feedback.
The application is a Single Page Application (SPA) built with React and Vite. It interacts with several external services to provide a seamless learning experience:
- Frontend: React application served via Vite.
- Backend: Supabase (BaaS) for authentication, database, and real-time features.
- Content Storage: GitHub repositories store course content (Markdown, code), allowing for version control and community contribution.
- Integrations: Canvas LMS (via Supabase Edge Functions), Gemini AI (via Supabase Edge Functions).
- Framework: React 18+ (with React Router v6 for routing).
- Build Tool: Vite.
- Styling: TailwindCSS v4.
- Editor: Monaco Editor (for code editing).
- Markdown:
react-markdown,remark-gfm,rehype-rawfor rich content rendering. - Drag & Drop:
@dnd-kitfor interactive UI elements.
- Supabase:
- Auth: User management and authentication.
- Database: PostgreSQL for storing User profiles, Enrollments, Progress, and Roles.
- Edge Functions: Proxy for calling external APIs (Canvas, Gemini) to keep secrets secure.
- GitHub API: Used to fetch course content, templates, and manage user commits for projects.
- E2E/Component: Playwright.
- Coverage: Istanbul / NYC.
The application revolves around a few key entities (defined in src/model.ts):
- User: A registered learner or instructor. Uses Supabase Auth.
- Course (CatalogEntry): Represents a course. Metadata is stored in Supabase (
catalogtable), but the actual content is in a GitHub repository. - Enrollment: Links a
Userto aCourse. Tracks progress and user-specific settings. - Topic: A unit of learning within a course (Video, Instruction, Project, Exam).
- Role: Defines permissions (e.g.,
admin,editor) for a user on a specific object (Course) or globally. - LearningSession: A runtime state combining the current Course, Topic, and Enrollment.
src/index.html: Browser entry point.src/app.jsx: Main entry point, sets up the Router and global Contexts.src/service/: Containsservice.ts(singleton), which handles all data fetching and business logic (Supabase, GitHub, etc.).src/views/: Feature-based directory structure (e.g.,dashboard,classroom,courseCreation).src/components/: Reusable UI components.src/hooks/: Custom hooks, encompassing complex logic likeuseCourseOperations.
- Authentication: Users sign in via Supabase Auth.
- Roles for learnes, editors, and root administrators.
- Learning:
- User selects a course (Enrollment).
- Content is fetched from the associated GitHub repository.
- Progress is tracked in Supabase.
- Interactions use AI for grading and feedback.
- Discussions with AI for self directed learning.
- Editors:
- Instructors create a new course. The system uses the GitHub API to generate a new repository from a template (
csinstructiontemplate). - Edit markdown content (Monaco) and commit back to GitHub.
- Generate content with Gemini AI.
- Instructors create a new course. The system uses the GitHub API to generate a new repository from a template (
create your config.js file in the root of the project.
export default {
supabase: {
url: 'https://yyy.supabase.co',
key: 'xxx',
},
};A course definition is read from the course.json file found in the root of the repo. If there is not course.json file then the content of the instruction/modules.md file is analyzed to try and discover the course.
{
"title": "Rocket Science",
"schedule": "schedule/schedule.md",
"syllabus": "instruction/syllabus/syllabus.md",
"links": {
"canvas": "https://byu.instructure.com/courses/31151",
"chat": "https://discord.com/channels/748656649287368704"
},
"modules": [
{
"title": "Course info",
"topics": [
{ "title": "Home", "path": "README.md" },
{ "title": "Syllabus", "path": "instruction/syllabus/syllabus.md" },
{ "title": "Schedule", "path": "schedule/schedule.md" }
]
}
]
}In order for a GitHub repo to function as the source for a Mastery LS course it must have the following structure.
.
├── LICENSE
├── README.md
├── instruction
│ ├── modules.md
│ ├── topic1
│ │ └── topic1.md
│ ├── topic2
│ │ ├── topic2.md
│ │ └── topic2.gif
│ ├── topic3
│ │ ├── topic3.md
│ │ └── topic3.png
│ └── syllabus
│ └── syllabus.md
└── schedule
└── schedule.mdThe modules.md file creates the content structure for the course. It uses the headings levels to generate the interface for the instruction modules. The modules file allows you to reorganize the topic order in any desired structure. When Mastery LS loads a course it first parses modules.md to generate the table of contents. If a course author modifies the order of topics, and commits the change, then the modules.md file is overwritten.
You should only manually modify the contents of modules.md if you understand the structure of the file. Any additions to the file beyond the recognized structure will be lost when the file is altered by Mastery LS.
# Instructional modules
## Module1 title
- [Topic1](topic1/topic1.md)
- [Topic2](topic2/topic2.md)
## Module2 title
- [Topic3](topic3/topic3.md)
- [Topic4](topic4/topic4.md)I want to keep this really simple and so I am going to use:
- Vite
- Basic React library
- Tailwind CSS
- Supabase for database and authentication
This should make it so that I can host the whole thing as a static website and just host it on GitHub.
There are lots of markdown libraries out there such as marked, react-markdown, and remark. I went with react markdown and various remark plugins.
Followed these basic instructions for using with Vite.
I chose Gemini API because they have a free tier.
curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent" \
-H 'Content-Type: application/json' \
-H 'X-goog-api-key: GEMINI_API_KEY' \
-X POST \
-d '{
"contents": [
{
"parts": [
{
"text": "Explain how AI works in a few words"
}
]
}
]
}'Everyone can read the catalog
CREATE policy "User read all"
on "public"."catalog"
FOR ALL
TO public
using (
true
);
Let a user manage their own user record so that they can register, login, and update settings
CREATE policy "User manage self"
on "public"."user"
FOR ALL
TO public
using (
(auth.uid() = id)
)
with check (
(auth.uid() = id)
);
Let a user manage their own enrollment record so that they can join, drop, and update settings
CREATE policy "User manage self"
on "public"."enrollment"
FOR ALL
TO public
using (
(auth.uid() = "learnerId")
)
with check (
(auth.uid() = "learnerId")
);
CREATE POLICY "Allow users to insert their own progress"
ON "public"."progress"
FOR INSERT TO authenticated
WITH CHECK ((SELECT auth.uid()) = "userId");
CREATE POLICY "Allow users to read their own progress"
ON "public"."progress"
FOR SELECT TO authenticated
USING ((SELECT auth.uid()) = "userId");
Allow user to read their own roles
CREATE POLICY "User read self"
ON public.role
FOR SELECT
TO public
USING (
("user" = auth.uid())
);
Create a function that validates a user is root. You can then call this in RLS policies
CREATE OR REPLACE FUNCTION public.auth_is_root(uid uuid)
RETURNS boolean
LANGUAGE sql
STABLE
SECURITY DEFINER
SET search_path = public
AS $$
SELECT EXISTS (
SELECT 1
FROM public.role
WHERE "user" = uid
AND "right" = 'root'
);
$$;
GRANT EXECUTE ON FUNCTION public.auth_is_root(uuid) TO anon, authenticated;
Create a policy on each table that allows root to manage everything
CREATE POLICY "Root all access"
ON public.catalog
FOR ALL
TO public
USING (public.auth_is_root(auth.uid()))
WITH CHECK (public.auth_is_root(auth.uid()));
You can allow update for specific columns by restricting the update with a GRANT
DROP POLICY IF EXISTS "update-own-rows" ON public.role;
CREATE POLICY "update-own-rows"
ON public.role
FOR UPDATE
USING (user = auth.uid())
WITH CHECK (user = auth.uid());
REVOKE ALL PRIVILEGES ON public.role FROM authenticated;
GRANT SELECT ON public.role TO authenticated;
GRANT INSERT ON public.role TO authenticated;
GRANT DELETE ON public.role TO authenticated;
GRANT UPDATE (settings) ON public.role TO authenticated;




