Skip to content

fix(content): replace hand-rolled MDX renderer with marked#28

Merged
projectamazonph merged 1 commit into
mainfrom
claude/whats-next-dk7dvf
Jul 16, 2026
Merged

fix(content): replace hand-rolled MDX renderer with marked#28
projectamazonph merged 1 commit into
mainfrom
claude/whats-next-dk7dvf

Conversation

@projectamazonph

@projectamazonph projectamazonph commented Jul 16, 2026

Copy link
Copy Markdown
Owner

Summary

Resolves the second P0 in docs/CONTENT-AUDIT-2026-07-16.md: the lesson renderer couldn't display tables, ordered lists, nested lists, links, or images used throughout content/curriculum/modules/ (28 files use tables, 23 use ordered lists, one has 3-level nested lists in a decision flowchart). Multi-line blockquote callouts (the 🎯 Analogy / 📌 Key Takeaway pattern used throughout) also rendered as several broken adjacent <blockquote> tags instead of one block with its nested list intact.

  • Replaces the regex-based hand-rolled parser in src/lib/mdx.ts with marked (CommonMark + GFM, zero runtime deps) behind a custom renderer.
  • Raw HTML (block and inline) is escaped rather than passed through, and link/image URLs are restricted to http(s), mailto, and relative/anchor paths — everything else drops to its text content — preserving the "no raw HTML" security posture of the original renderer.
  • Adds CSS for table/th/td/a/img, none of which existed before since the old renderer never emitted them.
  • Rewrites src/lib/__tests__/mdx.test.ts to cover tables, ordered/nested lists, links (safe + unsafe schemes), images (safe + unsafe schemes), multi-line blockquote callouts, and raw-HTML escaping.

Video embeds remain out of scope, per the audit's own note not to start the video program until a lesson can render and track a video correctly — no current lesson content uses them.

Test plan

  • pnpm tsc --noEmit — zero errors
  • pnpm lint — zero errors (same 6 pre-existing warnings)
  • pnpm test — 215/215 passing, including 26 rewritten MDX tests
  • pnpm build — production build succeeds
  • Verified live: seeded local Postgres, ran the real curriculum import, started the dev server, and loaded three real lessons through a browser — confirmed multi-line callouts, nested lists, and tables render correctly against the design system
  • Ran all 31 real lesson files through the renderer programmatically — no leftover unrendered markdown syntax

🤖 Generated with Claude Code


Generated by Claude Code

Summary by CodeRabbit

  • New Features

    • Improved lesson content rendering, including ordered lists, tables, links, images, blockquotes, and horizontal rules.
    • Added responsive styling for lesson images and improved table and link presentation.
  • Security

    • Unsafe links and images are safely filtered, while supported links retain appropriate security protections.
  • Bug Fixes

    • Improved HTML escaping to prevent unsafe raw markup from being rendered.

Resolves the second P0 in docs/CONTENT-AUDIT-2026-07-16.md: the lesson
renderer couldn't display tables, ordered lists, nested lists, links,
or images used throughout content/curriculum/modules/ (28 files use
tables, 23 use ordered lists, one has 3-level nested lists in a
decision flowchart). Multi-line blockquote callouts also rendered as
several broken adjacent <blockquote> tags instead of one block with
its nested list intact.

Swaps the regex-based parser for `marked` (CommonMark + GFM, zero
runtime deps) instead of continuing to patch the hand-rolled one by
hand. Raw HTML (block and inline) is escaped rather than passed
through, and link/image URLs are restricted to http(s), mailto, and
relative/anchor paths — everything else is dropped to its text
content — preserving the "no raw HTML" security posture of the
original renderer.

Added CSS for table/th/td/a/img, none of which existed before since
the old renderer never emitted them. Verified against a running
instance (seeded Postgres + real curriculum import) that lessons with
tables, multi-line callouts, and nested flowcharts render correctly.

Video embeds remain out of scope, per the audit's own note not to
start the video program until a lesson can render and track a video
correctly — no current lesson content uses them.
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The lesson Markdown renderer now delegates parsing to marked, escapes raw HTML, filters unsafe URLs, and supports richer Markdown structures. Tests cover the new output and sanitization behavior, while CSS styles rendered links, images, and tables.

Changes

Lesson rendering

Layer / File(s) Summary
Marked renderer and URL safety
package.json, src/lib/mdx.ts
Adds marked, replaces custom parsing with configured token rendering, escapes raw HTML, restricts link and image URLs, and keeps the synchronous renderLesson API.
Rendered content validation and styling
src/lib/__tests__/mdx.test.ts, src/app/(dashboard)/courses/[courseSlug]/lessons/[lessonSlug]/lesson.module.css
Expands coverage for lists, tables, blockquotes, links, images, escaping, and formatting, while styling rendered lesson content.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: swapping the custom MDX renderer for marked.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/whats-next-dk7dvf

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.


Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/__tests__/mdx.test.ts`:
- Around line 6-33: Move the `mdx.test.ts` suite from the `__tests__` directory
to sit alongside `mdx.ts` in `src/lib`, preserving all existing tests and
behavior.
- Around line 127-132: Remove the emoji literals from the comment and input
fixture in the multi-line blockquote test, while preserving the callout text
structure and test behavior. Update the related reference in the test
description/comment as needed without changing the parsing scenario.

In `@src/lib/mdx.ts`:
- Around line 2-13: Replace the em dashes in the module-level comments of the
MDX renderer with permitted punctuation, preserving the existing descriptions
and meaning.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3864431e-5899-4e66-a74a-e7725accc27d

📥 Commits

Reviewing files that changed from the base of the PR and between f33f32b and 010f284.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • package.json
  • src/app/(dashboard)/courses/[courseSlug]/lessons/[lessonSlug]/lesson.module.css
  • src/lib/__tests__/mdx.test.ts
  • src/lib/mdx.ts

Comment on lines +6 to +33
expect(renderLesson('Hello world').trim()).toBe('<p>Hello world</p>');
});

it('renders multiple paragraphs separated by blank lines', () => {
const input = 'First paragraph.\n\nSecond paragraph.';
const result = renderLesson(input);
const result = renderLesson('First paragraph.\n\nSecond paragraph.');
expect(result).toContain('<p>First paragraph.</p>');
expect(result).toContain('<p>Second paragraph.</p>');
});

it('renders headings h1 through h4', () => {
expect(renderLesson('# Title')).toBe('<h1>Title</h1>');
expect(renderLesson('## Section')).toBe('<h2>Section</h2>');
expect(renderLesson('### Subsection')).toBe('<h3>Subsection</h3>');
expect(renderLesson('#### Detail')).toBe('<h4>Detail</h4>');
expect(renderLesson('# Title').trim()).toBe('<h1>Title</h1>');
expect(renderLesson('## Section').trim()).toBe('<h2>Section</h2>');
expect(renderLesson('### Subsection').trim()).toBe('<h3>Subsection</h3>');
expect(renderLesson('#### Detail').trim()).toBe('<h4>Detail</h4>');
});

it('renders bold text', () => {
expect(renderLesson('This is **bold** text')).toBe('<p>This is <strong>bold</strong> text</p>');
expect(renderLesson('This is **bold** text')).toContain('This is <strong>bold</strong> text');
});

it('renders italic text', () => {
expect(renderLesson('This is *italic* text')).toBe('<p>This is <em>italic</em> text</p>');
expect(renderLesson('This is *italic* text')).toContain('This is <em>italic</em> text');
});

it('renders inline code', () => {
expect(renderLesson('Use the `renderLesson` function')).toBe('<p>Use the <code>renderLesson</code> function</p>');
expect(renderLesson('Use the `renderLesson` function')).toContain(
'Use the <code>renderLesson</code> function'
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Co-locate this suite with mdx.ts.

Move src/lib/__tests__/mdx.test.ts to src/lib/mdx.test.ts so the tests sit beside the module they cover. As per coding guidelines, **/*.test.{ts,tsx} tests must be placed next to the code they test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/__tests__/mdx.test.ts` around lines 6 - 33, Move the `mdx.test.ts`
suite from the `__tests__` directory to sit alongside `mdx.ts` in `src/lib`,
preserving all existing tests and behavior.

Source: Coding guidelines

Comment on lines +127 to +132
it('renders multi-line blockquote callouts with a nested list as one block', () => {
// Matches the "🎯 Analogy" / "📌 Key Takeaway" callout pattern used throughout lessons.
const input =
'> 🎯 **Analogy**: Three ways to pay for a taxi:\n' +
'> - **Fixed Bids** = flat-rate ride share\n' +
'> - **Dynamic** = surge pricing';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove emoji literals from the test source.

The changed comment and fixture contain emoji characters, which the repository prohibits in code.

Proposed fix
-    // Matches the "🎯 Analogy" / "📌 Key Takeaway" callout pattern used throughout lessons.
+    // Covers callout patterns used throughout lessons.
...
-      '> 🎯 **Analogy**: Three ways to pay for a taxi:\n' +
+      '> \u{1F3AF} **Analogy**: Three ways to pay for a taxi:\n' +

As per coding guidelines, **/*.{ts,tsx,js,jsx} must not use emojis in code.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('renders multi-line blockquote callouts with a nested list as one block', () => {
// Matches the "🎯 Analogy" / "📌 Key Takeaway" callout pattern used throughout lessons.
const input =
'> 🎯 **Analogy**: Three ways to pay for a taxi:\n' +
'> - **Fixed Bids** = flat-rate ride share\n' +
'> - **Dynamic** = surge pricing';
it('renders multi-line blockquote callouts with a nested list as one block', () => {
// Covers callout patterns used throughout lessons.
const input =
'> \u{1F3AF} **Analogy**: Three ways to pay for a taxi:\n' +
'> - **Fixed Bids** = flat-rate ride share\n' +
'> - **Dynamic** = surge pricing';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/__tests__/mdx.test.ts` around lines 127 - 132, Remove the emoji
literals from the comment and input fixture in the multi-line blockquote test,
while preserving the callout text structure and test behavior. Update the
related reference in the test description/comment as needed without changing the
parsing scenario.

Source: Coding guidelines

Comment thread src/lib/mdx.ts
Comment on lines +2 to +13
* MDX (markdown subset) -> HTML renderer for lesson content.
*
* Handles the markdown subset we use in lessons:
* - Headings (# ## ### ####)
* - Paragraphs
* - Bold/italic (**text**, *text*)
* - Inline code (`code`) and code blocks (```)
* - Bullet lists (- item)
* - Blockquotes (> text)
* - Horizontal rules (---)
* Backed by `marked` (CommonMark + GFM) so lessons can use tables, ordered
* and nested lists, links, and images, none of which the hand-rolled
* predecessor of this module supported.
*
* No JSX components — pure markdown. For interactive components
* (e.g., drag-drop simulations) we'll need a real MDX setup, but the
* current AMPH content is markdown-only.
* No JSX/component support — pure markdown. Interactive components would
* need a real MDX compile step; current AMPH content is markdown-only.
*
* Security: all output is HTML-escaped. No raw HTML passes through.
* Security: raw HTML (block or inline) is escaped rather than passed
* through, and link/image URLs are restricted to http(s), mailto, and
* relative/anchor paths — everything else is dropped to its text content.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Replace em dashes in source comments.

Lines 8 and 13 introduce em dashes, which are prohibited in TypeScript source.

Proposed fix
- * No JSX/component support — pure markdown. Interactive components would
+ * No JSX/component support. This is pure markdown. Interactive components would
...
- * relative/anchor paths — everything else is dropped to its text content.
+ * relative/anchor paths. Everything else is dropped to its text content.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
* MDX (markdown subset) -> HTML renderer for lesson content.
*
* Handles the markdown subset we use in lessons:
* - Headings (# ## ### ####)
* - Paragraphs
* - Bold/italic (**text**, *text*)
* - Inline code (`code`) and code blocks (```)
* - Bullet lists (- item)
* - Blockquotes (> text)
* - Horizontal rules (---)
* Backed by `marked` (CommonMark + GFM) so lessons can use tables, ordered
* and nested lists, links, and images, none of which the hand-rolled
* predecessor of this module supported.
*
* No JSX components pure markdown. For interactive components
* (e.g., drag-drop simulations) we'll need a real MDX setup, but the
* current AMPH content is markdown-only.
* No JSX/component support pure markdown. Interactive components would
* need a real MDX compile step; current AMPH content is markdown-only.
*
* Security: all output is HTML-escaped. No raw HTML passes through.
* Security: raw HTML (block or inline) is escaped rather than passed
* through, and link/image URLs are restricted to http(s), mailto, and
* relative/anchor paths everything else is dropped to its text content.
* MDX (markdown subset) -> HTML renderer for lesson content.
*
* Backed by `marked` (CommonMark + GFM) so lessons can use tables, ordered
* and nested lists, links, and images, none of which the hand-rolled
* predecessor of this module supported.
*
* No JSX/component support. This is pure markdown. Interactive components would
* need a real MDX compile step; current AMPH content is markdown-only.
*
* Security: raw HTML (block or inline) is escaped rather than passed
* through, and link/image URLs are restricted to http(s), mailto, and
* relative/anchor paths. Everything else is dropped to its text content.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/mdx.ts` around lines 2 - 13, Replace the em dashes in the
module-level comments of the MDX renderer with permitted punctuation, preserving
the existing descriptions and meaning.

Source: Coding guidelines

@projectamazonph
projectamazonph merged commit 83caa5f into main Jul 16, 2026
5 checks passed
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