Feat/interactive lessons - #1246
Open
jeromehardaway wants to merge 11 commits into
Open
Conversation
Assembles learner HTML/CSS/JS into a sandboxed iframe srcdoc with console + test harness. Console output and pass/fail flow back to the parent via postMessage. Opaque origin (allow-scripts, no allow-same-origin) keeps learner code out of the parent session. Messages are authenticated by frame identity and run id; pure string builders, unit-tested.
Lessons are authored as git-tracked TS data files (content, not user data). Adds html-tables, css-flexbox-nav, and js-render-table-rows with starter files, instructions, DOM/computed-style tests, and reference solutions. The loader handles module ordering and prev/next navigation.
Adds optional mode, name, height, and readOnly props and preloads the html/css/js Ace modes. All new props are optional, so existing callers are unaffected. The name prop fixes a hardcoded duplicate id when several editors render on one page.
The workspace wires the editor tabs, live preview iframe, console, and test results together with Run/Reset, auto-run on load, per-file localStorage persistence, and Back/Next navigation. Adds the /learn catalog and /learn/[slug] pages (SSG). Keyed by slug to remount cleanly between lessons. Toggle buttons use aria-pressed; the covered preview iframe is aria-hidden.
Adds an InteractiveProgress model (userId + lessonSlug, no FK to a Lesson row since lessons are git content) and an auth-gated /api/learn/progress endpoint that validates the slug and upserts completion. The migration applies via prisma migrate deploy on the next Vercel deploy.
Drives the real flow: the catalog lists lessons, starter code fails on auto-run, and the reference solution (seeded into localStorage the way saved work rehydrates) passes all tests.
The hero h1 relied on inheriting the parent's light color, but the design system's global h1 rule overrode it — rendering dark navy text on the navy hero (invisible). Sets tw-text-cream directly on the h1 and lightens the subtitle to tw-text-gray-100.
The interactive lessons are a members-only benefit, so /learn and /learn/[slug] now server-render with requireAuthSSR — signed-out visitors are redirected to /login. Updates the e2e spec to verify the redirect (the engine stays covered by unit tests).
Removes the authed-only My Cohort and Train dropdowns from the global top nav and puts their destinations behind the avatar — Learn, Reps, Assessment, J0d!e, Profile. Keeps the public nav lean and on one line, so the hide-social/cohort patch (#1245) is no longer needed.
Moving My Cohort/Train to the avatar cut the nav to 7 items, but the signed-in right block (avatar + name + social) still left ~90px too little for a single row. Hides only the '2026 Cohort Active' pill on desktop when signed in — it's redundant with the top-bar countdown, and the social links stay visible — plus nowrap. Verified one row on preview.
Covers adding a lesson (data-file template + authoring rules), the iframe-srcdoc sandbox architecture, and the sharp edges (body-fragment HTML, function declarations, getComputedStyle for CSS lessons, and light headings on dark backgrounds).
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a members-only “Interactive Lessons” experience under /learn, including a lesson catalog, per-lesson workspace with an iframe sandbox runner, and persisted completion tracking for signed-in users.
Changes:
- Introduces
/learncatalog and/learn/[slug]workspace pages gated via SSR auth redirects. - Implements the sandbox runtime (srcdoc builder + iframe harness), lesson content loader/types, and lesson UI (editor + preview + tests/console).
- Adds per-user lesson completion tracking (Prisma model +
/api/learn/progress) plus docs and basic test coverage (Vitest + Playwright auth-gate check).
Reviewed changes
Copilot reviewed 26 out of 26 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/e2e/interactive-lesson.spec.ts | E2E coverage for the /learn auth gate redirects. |
| src/pages/learn/index.tsx | Members-only catalog page listing lessons grouped by module. |
| src/pages/learn/[slug].tsx | Members-only lesson workspace page loading lesson + prev/next. |
| src/pages/api/learn/progress.ts | Auth-gated API for reading/updating completion state. |
| src/lib/lesson-sandbox/messages.ts | Defines parent↔iframe message contract + type guard. |
| src/lib/lesson-sandbox/harness.ts | In-iframe console shim + test runner codegen. |
| src/lib/lesson-sandbox/build-srcdoc.ts | Builds the iframe srcDoc document containing learner code + harness. |
| src/lib/lesson-sandbox/tests/build-srcdoc.test.ts | Unit tests for srcdoc assembly and escaping. |
| src/lib/interactive-lessons/types.ts | Core interactive lesson content types. |
| src/lib/interactive-lessons/index.ts | Loader helpers (slug list, grouping, prev/next). |
| src/lib/interactive-lessons/tests/loader.test.ts | Unit tests + basic content lint for lesson data. |
| src/layouts/headers/header.tsx | Header tweaks to keep nav layout stable when authenticated. |
| src/data/menu.ts | Removes signed-in-only items from top nav; documents new placement. |
| src/data/interactive-lessons/module-05/js-render-table-rows.ts | Adds a Module 5 interactive lesson definition. |
| src/data/interactive-lessons/module-04/html-tables.ts | Adds a Module 4 interactive lesson definition. |
| src/data/interactive-lessons/module-04/css-flexbox-nav.ts | Adds a Module 4 interactive lesson definition. |
| src/data/interactive-lessons/index.ts | Registers lesson data exports in the catalog. |
| src/containers/interactive-lesson/index.tsx | Implements the interactive lesson workspace UI + run/test flow + progress marking. |
| src/components/user-menu/index.tsx | Adds signed-in navigation links (including Learn) to avatar menu. |
| src/components/menu/main-menu/index.tsx | Prevents top nav wrapping (single-line menu). |
| src/components/lesson-test-results/index.tsx | Renders test runner status + per-test results. |
| src/components/lesson-console/index.tsx | Renders captured console output from the sandbox. |
| src/components/code-editor/index.tsx | Extends editor to support html/css/js modes + readOnly/name/height. |
| prisma/schema.prisma | Adds InteractiveProgress model and user relation. |
| prisma/migrations/20260718024251_add_interactive_progress/migration.sql | DB migration creating the InteractiveProgress table. |
| docs/INTERACTIVE_LESSONS.md | Internal documentation for authoring and architecture. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+47
to
+67
| export function isLessonMessage(data: unknown): data is LessonMessage { | ||
| if (typeof data !== "object" || data === null) return false; | ||
| const m = data as Record<string, unknown>; | ||
| if (m.source !== LESSON_MESSAGE_SOURCE) return false; | ||
| if (typeof m.runId !== "number") return false; | ||
| if (m.type === "console") { | ||
| return ( | ||
| (m.level === "log" || | ||
| m.level === "info" || | ||
| m.level === "warn" || | ||
| m.level === "error") && | ||
| Array.isArray(m.args) | ||
| ); | ||
| } | ||
| if (m.type === "tests") { | ||
| return ( | ||
| Array.isArray(m.results) && typeof m.passed === "number" && typeof m.total === "number" | ||
| ); | ||
| } | ||
| return false; | ||
| } |
Comment on lines
+38
to
+44
| async function handleGet(req: AuthenticatedRequest, res: NextApiResponse) { | ||
| const records = await prisma.interactiveProgress.findMany({ | ||
| where: { userId: req.user?.id, completed: true }, | ||
| select: { lessonSlug: true }, | ||
| }); | ||
| res.json({ completed: records.map((r) => r.lessonSlug) }); | ||
| } |
Comment on lines
+58
to
+60
| const isComplete = completed !== false; // defaults to marking complete | ||
| const userId = req.user?.id as string; | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Type of Change
Related Issues
Closes #
Changes Made
Screenshots/Videos
Before
After
Testing
Test Configuration
Test Steps
Checklist
Code Quality
Testing
npm testnpm run typechecknpm run litAccessibility
Responsive Design
Performance
Security
Documentation
Additional Notes
For Reviewers
Focus areas:
Questions:
Checklist for Reviewers