Skip to content

Feat/interactive lessons - #1246

Open
jeromehardaway wants to merge 11 commits into
masterfrom
feat/interactive-lessons
Open

Feat/interactive lessons#1246
jeromehardaway wants to merge 11 commits into
masterfrom
feat/interactive-lessons

Conversation

@jeromehardaway

Copy link
Copy Markdown
Contributor

Description

Type of Change

  • Bug fix (non-braking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Code refactoring
  • Performance improvement
  • Test addition or update
  • Build/CI update

Related Issues

Closes #

Changes Made

Screenshots/Videos

Before

After

Testing

Test Configuration

  • OS:
  • Browser:
  • Node version:

Test Steps

Checklist

Code Quality

  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have commented my code, particulary in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Testing

  • I have tested on desktop
  • I have tested on mobile
  • I have tested on tablet
  • I have tested on multiple browseers (Chrome, Firefox, Safari)
  • All tests pass: npm test
  • TypeScript compiles: npm run typecheck
  • Linting passes: npm run lit

Accessibility

  • I have tested with a screen reader
  • All images have alt text
  • Color contrast meets WCAG AA standards
  • Keyboard navigation works
  • Focus indicators are visible

Responsive Design

  • Works on mobile (375px width)
  • Works on tablet (768px width)
  • Works on desktop (1440px width)
  • No horizontal scrolling on mobile

Performance

  • Images are optimized
  • No unnecessary re-renders
  • Bundle size impact is reasonable
  • Page load time is acceptable

Security

  • No sensitive data in code
  • API keys are in environment variables
  • Input validation is implemented
  • SQL injection prevention is in place (if applicable)
  • XSS prevention is in place

Documentation

  • README updated (if needed)
  • API documentation updated (if needed)
  • Component props documented (if new component)
  • CHANGELOG updated (if applicable)

Additional Notes

For Reviewers

Focus areas:

Questions:


Checklist for Reviewers

  • Code follows project conventions
  • Changes are well-tested
  • Documentation is clear and complete
  • No obvious security issues
  • Performance impact is acceptable

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).
@vercel

vercel Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
vets-who-code-app Ready Ready Preview, Comment Jul 18, 2026 7:40pm

Request Review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 /learn catalog 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;

@jeromehardaway jeromehardaway self-assigned this Jul 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants