Courses & Library: Faculty Panel¶
The faculty panel groups everything a trainer teaches under a single Courses sidebar entry with two sub-routes: My Courses (/courses) and Library (/courses/library). It replaces the old parallel "Courses" and "Lessons" nav entries and fixes the terminology so "Module" means a syllabus chapter everywhere, not a single lecture.
How it works¶
Mental model¶
Course ← the thing a learner enrolls in
└── Module ← syllabus chapter (stored as lesson.unit_name, a string)
└── Lesson ← single atom: slide deck, reading, video, quiz, TipTap doc
Orthogonal to it, the Library is a pool of reusable Lessons not bound to any course.
Rules:
- A Lesson belongs to exactly one
(course, module)pair, or lives in the Library unattached. - A Module is a soft grouping with no DB entity: just
lesson.unit_namevalues grouped client-side. Renaming a module updatesunit_nameon all its lessons. - Moving a Lesson between Library and a Course is a first-class UI action backed by the
linkToCourse/unlinkFromCourseendpoints. - "Lesson" is the same word for every org type. "Module" resolves via the
tp('module')terminology key: "Chapter" for K-12, "Module" for college and corporate.
Creating a course¶
A trainer clicks Courses, lands on /courses (empty-state CTA "Create your first course"), and opens the create wizard: title, branch, semester, then add modules by name. They land on /courses/:id/edit with one group per module. Inside a module, Add lesson reveals an inline TipTap editor so content is authored without leaving the course view. Publish is one modal with visibility and targeting.
Reusing a Library lesson¶
From Library, a row menu's Add to course… opens a course + module picker and calls linkToCourse. From the course editor, Add from Library opens a lesson picker that calls the same endpoint. A Library row shows attached-to chips for each course it is linked into.
Edge cases¶
- First visit to
/coursesshows two CTAs: Create your first course (primary) and Browse Library (secondary). - An orphan standalone lesson from before this change shows up in Library automatically; no data migration needed.
- A bookmarked
/lessonsURL redirects to/courses/libraryfor trainers; learners keep/lessonsunchanged. - Deleting a module with lessons prompts "Delete 10 lessons or move them to Library?", defaulting to move.
Routes¶
/courses:CoursesPage(shared). Trainers see tabsMy Courses | Curated | External | Library; learners see the first three (no Library). Source tabs switch content inline; Library navigates to/courses/library.?tab=<id>is honored for deep links./courses/library:LessonsPage→TeacherLessonsViewwith Library active./courses/new:CreateCoursePage./courses/:courseId/edit:CourseEditPage. Unit → Module terminology applied./courses/browse:CourseBrowserPage, a secondary trainer browse route./lessons: learners getStudentLessonsView; trainers auto-redirect to/courses/library./lessons/new,/lessons/:id/edit,/lessons/:id/analytics: trainer authoring sub-pages, reachable from Library cards.
Endpoints¶
| Method | Endpoint | Purpose |
|---|---|---|
| GET/POST | /courses |
List/Create courses |
| GET/PATCH/DELETE | /courses/{id} |
Course CRUD |
| POST | /courses/{id}/publish |
Publish with targeting |
| POST | /courses/{id}/lessons |
Add lesson to course |
| GET/PATCH | /courses/lessons/{id} |
Nested lesson CRUD |
| POST/PATCH/DELETE | /courses/lessons/{id}/blocks |
TipTap block CRUD |
| GET/POST | /lessons |
Standalone (Library) lesson CRUD |
| POST | /lessons/{id}/publish |
Publish standalone |
| POST | /lessons/{id}/link-to-course |
Attach lesson to a course |
| POST | /lessons/{id}/unlink-from-course |
Detach lesson to Library |
| GET | /lessons/feed |
Learner lesson feed |
Where it lives¶
Routes: /courses, /courses/library, /courses/:id/edit.
apps/web/src/pages/teacher/TeacherCoursesPage/: tab shell + MyCoursesTab / LibraryTab / AddToCourseModalapps/web/src/pages/teacher/CourseEditPage/: inline editor, Add from Library, module renameapps/web/src/hooks/useTerminology.ts:moduleterminology key