Skip to content

Commit c1e1405

Browse files
willwearingclaude
andauthored
fix: render markdown in lesson instruction and worked example text (#113)
Course YAML supports markdown in instructionText and workedExampleText per the schema docs, but the lesson UI was rendering it as plain text. Added a MarkdownText component using react-markdown + remark-gfm and replaced the plain <p> renders in lesson-flow. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent b906c54 commit c1e1405

4 files changed

Lines changed: 227 additions & 5 deletions

File tree

apps/web/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@
2727
"posthog-node": "^5.28.0",
2828
"react": "19.2.3",
2929
"react-dom": "19.2.3",
30+
"react-markdown": "^10.1.0",
3031
"recharts": "^3.8.0",
32+
"remark-gfm": "^4.0.1",
3133
"shadcn": "^4.0.2",
3234
"tailwind-merge": "^3.5.0",
3335
"tw-animate-css": "^1.4.0"

apps/web/src/components/app/lesson-flow.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { useAudioPlayer } from "@/lib/hooks/use-audio-player";
1111
import { useLessonAudio } from "@/lib/hooks/use-lesson-audio";
1212
import { trackLessonComplete, trackLessonStarted, trackLessonPracticeAnswered, trackLessonAudioPlayed, trackLessonAbandoned } from "@/lib/posthog/events";
1313
import { LessonRichContent } from "@/components/app/lesson-rich-content";
14+
import { MarkdownText } from "@/components/app/markdown-text";
1415
import type { Problem, ProblemAnswer, RichContentBlock } from "@/lib/types";
1516

1617
interface KnowledgePoint {
@@ -222,7 +223,7 @@ export function LessonFlow({ orgSlug, courseId, token, lesson, continueHref }: L
222223
<BookOpen className="h-4 w-4" />
223224
Instruction
224225
</div>
225-
<p className="text-foreground leading-relaxed whitespace-pre-line">{kp.instructionText}</p>
226+
<MarkdownText>{kp.instructionText}</MarkdownText>
226227
<LessonRichContent blocks={instructionContent} />
227228

228229
{/* Audio playback */}
@@ -271,7 +272,7 @@ export function LessonFlow({ orgSlug, courseId, token, lesson, continueHref }: L
271272
{phase === "worked-example" && kp.workedExampleText && (
272273
<div className="rounded-lg border border-border bg-muted/20 p-6 space-y-2">
273274
<p className="text-sm font-medium text-muted-foreground">Worked Example</p>
274-
<p className="text-foreground leading-relaxed whitespace-pre-line">{kp.workedExampleText}</p>
275+
<MarkdownText>{kp.workedExampleText}</MarkdownText>
275276
<LessonRichContent blocks={workedExampleContent} />
276277
</div>
277278
)}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"use client";
2+
3+
import ReactMarkdown from "react-markdown";
4+
import remarkGfm from "remark-gfm";
5+
6+
interface MarkdownTextProps {
7+
children: string;
8+
className?: string;
9+
}
10+
11+
/**
12+
* Renders markdown text with Tailwind-friendly styling.
13+
* Used for lesson instruction and worked example fields, which
14+
* support markdown per the course schema.
15+
*/
16+
export function MarkdownText({ children, className }: MarkdownTextProps) {
17+
return (
18+
<div
19+
className={
20+
className ??
21+
"text-foreground leading-relaxed space-y-3 [&_strong]:font-semibold [&_em]:italic [&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_code]:py-0.5 [&_code]:text-sm [&_ul]:list-disc [&_ul]:pl-6 [&_ol]:list-decimal [&_ol]:pl-6 [&_li]:my-1 [&_a]:text-primary [&_a]:underline"
22+
}
23+
>
24+
<ReactMarkdown remarkPlugins={[remarkGfm]}>{children}</ReactMarkdown>
25+
</div>
26+
);
27+
}

0 commit comments

Comments
 (0)