Skip to content

Add local student dashboard + Code Lab runner#156

Open
NischaySabharwal1 wants to merge 2 commits into
rohitg00:mainfrom
NischaySabharwal1:main
Open

Add local student dashboard + Code Lab runner#156
NischaySabharwal1 wants to merge 2 commits into
rohitg00:mainfrom
NischaySabharwal1:main

Conversation

@NischaySabharwal1
Copy link
Copy Markdown

What this PR does

Kind of change

  • New lesson
  • Fix to an existing lesson
  • Translation
  • New output (prompt, skill, agent, MCP server)
  • Docs / website / tooling

Checklist

  • Code runs without errors with the listed dependencies
  • No comments in code files (docs explain, code is self-explanatory)
  • Built from scratch first, then shown with a framework (for new lessons)
  • Lesson folder matches LESSON_TEMPLATE.md structure
  • ROADMAP.md row for the lesson is a markdown link ([Name](phases/...)), not bare text
  • One lesson per commit (atomic per-lesson rule)
  • Tested locally / code output matches what docs/en.md claims

Phase / lesson

Notes for reviewer

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 24, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a local-first dev server and local frontend pages: new dashboard and phase pages, updated lesson UI with a Code Lab using local APIs (/api/lesson-meta, /api/run, /api/test, /api/list, /api/rubric, /api/review), and progress driven by window.AIFSProgress; includes styles and documentation for local development.

Changes

Local-First Student Dashboard

Layer / File(s) Summary
Local Development Server with Content & Execution APIs
site/local-server.mjs
Node.js server serving site files and /content/*, exposing health, lesson-meta, list, rubric, review (stub), run, and test endpoints with path sanitization, body limits, temp-copy/apply edits, process execution, timeouts, and CORS.
Dashboard Page with KPI and Phase Card Grid
site/dashboard.html, site/dashboard.js
New dashboard page and script: theme toggle, KPIs (total/percent complete), per-phase cards with progress, Continue/Reset buttons, and rendering computed from global PHASES and window.AIFSProgress.
Phase Page with Sidebar, Header, and Lessons Grid
site/phase.html, site/phase.js
New phase page and script: sidebar of phases, breadcrumb/header with progress pill, lesson tiles with “Open” links and mark/unmark buttons wired to window.AIFSProgress.
Lesson Page: Local Content, Topbar, and Code Lab
site/lesson.html
Lesson loading now reads from /content/<path>/docs/en.md and quiz JSON, injects a sticky lesson topbar with completion toggle, adds renderCodeLab(path) to fetch /api/lesson-meta, edit/run/test/rubric/review flows, and replaces GitHub-based file listings with local listings.
Progress Tracking Model with AIFSProgress
site/app.js
computeStats(), renderPhases(), and modal lesson rendering derive completion from window.AIFSProgress.isLessonComplete() (using extracted lessonPath) rather than static status === 'complete', and lesson links use lesson.html?path=... when available.
Navigation Links and Local-First Header Mode
site/index.html, site/lesson.html, site/header.js
Adds “Dashboard” links, removes third-party inline Vercel scripts in favor of local deferred scripts, and header.js detects local origins to skip GitHub API star fetches in local-first mode.
CSS for Dashboard, Phase, Code Lab, and Lesson Topbar
site/style.css
New styles for Code Lab layout/editor/output, dashboard and phase grids/KPIs/phase cards, fixed responsive sidebar, lesson tiles with done state, and sticky lesson topbar.
README and Frontend Architecture Docs
README.md, site/FRONTEND.md
Documents offline startup (node site/local-server.mjs), local URLs (/index.html, /dashboard.html), and adds FRONTEND.md describing dashboard/phase/lesson pages, local-server endpoints, Code Lab workflows, and local progress storage.

🎯 4 (Complex) | ⏱️ ~75 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description is a template with unchecked checkboxes and empty sections. While it doesn't contain substantive information about the changes, the commit message and PR title provide context that relates to the changeset. Fill in the template fields with a concrete summary of changes: describe what was added (local dashboard, Code Lab runner, local server), which files were modified, and any relevant notes for reviewers.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: adding a local student dashboard and Code Lab runner, which are the primary features introduced across multiple new files and modifications.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
site/app.js (2)

61-64: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Phase completion is still derived from static metadata, not user progress.

computeStats() and renderPhases() still rely on PHASES[*].status for phase completion/status class, which can contradict user progress-derived lesson counts and show misleading completion UI.

Suggested fix
-    var completePhases = 0;
-    for (var p = 0; p < PHASES.length; p++) {
-      if (PHASES[p].status === 'complete') completePhases++;
-    }
+    var completePhases = 0;
+    for (var p = 0; p < PHASES.length; p++) {
+      var phaseDone = 0;
+      var phaseTotal = PHASES[p].lessons.length;
+      for (var q = 0; q < phaseTotal; q++) {
+        var lesson = PHASES[p].lessons[q];
+        if (hasProgress && lesson.url) {
+          var phaseLp = window.AIFSProgress.extractPath(lesson.url);
+          if (phaseLp && window.AIFSProgress.isLessonComplete(phaseLp)) phaseDone++;
+        }
+      }
+      if (phaseTotal > 0 && phaseDone === phaseTotal) completePhases++;
+    }
-      var statusClass = p.status.replace(/ /g, '-');
+      var statusClass = done === total ? 'complete' : (done > 0 ? 'in-progress' : 'planned');

Also applies to: 122-122

🤖 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 `@site/app.js` around lines 61 - 64, computeStats() and renderPhases() are
still using PHASES[p].status to determine completed phases (see the
completePhases loop), causing UI to disagree with user progress; change them to
derive phase status from actual user progress/lesson completion counts instead
of static PHASES[].status. Specifically, in computeStats() replace the loop that
counts completePhases from PHASES with logic that inspects the user's
progress/lessonsCompleted data for each phase (e.g., map each phase id to number
of completed lessons and consider a phase complete when its completedLessons ===
totalLessons), store that computed status/counts on the stats object, and then
update renderPhases() to read the phase status/counts from the computed stats
(not PHASES[p].status) so the UI classes reflect real progress. Ensure you
update any references to completePhases, PHASES[p].status, and phase completion
checks to use the new computed values.

233-233: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use encoded lesson paths consistently in modal links.

At Line 233, lessonPath is interpolated unencoded, while Line 224 encodes it. Keep both encoded to avoid query-string breakage for edge-case paths.

Suggested fix
-        actionHtml = '<a href="lesson.html?path=' + lessonPath + '" class="modal-lesson-read">' + (userComplete ? 'Review' : 'Read') + '</a>';
+        actionHtml = '<a href="lesson.html?path=' + encodeURIComponent(lessonPath) + '" class="modal-lesson-read">' + (userComplete ? 'Review' : 'Read') + '</a>';
🤖 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 `@site/app.js` at line 233, The modal link is using an unencoded lessonPath
when building actionHtml, causing inconsistent encoding versus the earlier
encoded usage; update the construction of actionHtml to use the same encoded
value (e.g., the encoded lessonPath variable or call
encodeURIComponent(lessonPath)) so both places consistently encode query
parameters (refer to actionHtml and lessonPath where the link is built).
🧹 Nitpick comments (1)
site/lesson.html (1)

2113-2134: ⚡ Quick win

Test endpoint only receives the currently selected file's edits.

When a user edits multiple files across different selections, testCurrent only sends the currently visible file in the edits object. If tests depend on changes across multiple files, they will fail unexpectedly because other modified files retain their original content in the temp copy.

Consider tracking all modified files across the session:

Proposed enhancement: Track all edits
             var originalByFile = {};
+            var editedByFile = {};

             function loadFile(filePath) {
               setOut('loading', 'Fetching ' + filePath + '…');
+              // Save current edits before switching
+              var prevFile = sel.value;
+              if (prevFile && ed.value !== originalByFile[prevFile]) {
+                editedByFile[prevFile] = ed.value;
+              }
               fetch(fileUrl(filePath))
                 .then(function (r) { if (!r.ok) throw new Error('file-missing'); return r.text(); })
                 .then(function (txt) {
                   if (typeof originalByFile[filePath] !== 'string') originalByFile[filePath] = txt;
-                  ed.value = txt;
+                  ed.value = editedByFile[filePath] || txt;
                   setOut('', '');
                 })
                 // ...

             function testCurrent() {
               var filePath = sel.value;
               setOut('testing', 'Running tests…');
-              var edits = {};
-              edits[filePath] = ed.value;
+              // Include all modified files
+              editedByFile[filePath] = ed.value;
+              var edits = Object.assign({}, editedByFile);
🤖 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 `@site/lesson.html` around lines 2113 - 2134, testCurrent currently builds a
local edits map containing only the active file (edits[filePath] = ed.value) so
tests miss changes made to other files; persist a session-wide edits map (e.g.,
modifiedEdits) and update it whenever the editor content or selection changes
(handlers in the file-selection/change logic should set modifiedEdits[filePath]
= ed.value or delete if unchanged), then have testCurrent send the full
modifiedEdits instead of a single-file map in its POST body; ensure any
save/reset logic also updates modifiedEdits so the server receives all modified
files for testing.
🤖 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 `@site/dashboard.js`:
- Around line 123-132: The reset handler currently calls
window.AIFSProgress.reset() then renderKpis(PHASES) and renderPhaseCards(PHASES)
but does not update the Continue CTA target; modify the click handler for
resetBtn to call the full render() (or explicitly recompute/update the Continue
button target) after reset so the `#continueBtn` state/target is recalculated;
update the resetBtn listener that references window.AIFSProgress.reset(),
renderKpis, and renderPhaseCards to invoke render() (or the function that builds
the Continue CTA) as the final step.

In `@site/FRONTEND.md`:
- Around line 13-18: The doc snippet contradicts its preamble: it says "From the
repo root" but then runs "cd ai-engineering-from-scratch", which will fail for
users already at root; in the code block that contains "cd
ai-engineering-from-scratch" and "node site/local-server.mjs" remove the "cd
ai-engineering-from-scratch" line (or alternatively change the preamble to "From
the parent directory") so the sequence is consistent and simply runs "node
site/local-server.mjs" from the repo root (refer to the command lines "cd
ai-engineering-from-scratch" and "node site/local-server.mjs" in the snippet).

In `@site/header.js`:
- Around line 47-50: The local-host detection in header.js using variables host
and proto only checks for '127.0.0.1' and 'localhost' so add '0.0.0.0' and the
IPv6 loopback '::1' to the condition that calls paint(0); update the if that
currently reads (proto === 'file:' || host === '127.0.0.1' || host ===
'localhost') to also test host === '0.0.0.0' and host === '::1' (and if your
code may encounter bracketed IPv6 addresses, normalize host by trimming
surrounding brackets before comparison) so local-first behavior covers common
local dev hosts.

In `@site/local-server.mjs`:
- Around line 135-138: ensureTmpDir currently creates temp directories that are
never removed; update the code to remove those dirs after use by changing the
callers (/api/run and /api/test handlers) to wrap their execution in try/finally
and call a safe removal (e.g. await fsp.rm(tmpDir, { recursive: true, force:
true }) or fs.promises.rm) in the finally block; ensure the tmpDir returned from
ensureTmpDir is passed to the handlers' finally, and handle any removal errors
silently/log them so cleanup won't throw and will always run.

In `@site/phase.js`:
- Line 118: The code in phase.js uses escapeAttr(lessonPath) but escapeAttr is
defined only in lesson.html, causing a ReferenceError; fix by extracting the
escapeAttr implementation into a shared utility module (e.g., utils/escape.js)
and import or include that before phase.js so escapeAttr is available to
phase.html, or alternatively add an equivalent local helper function named
escapeAttr at the top of phase.js and use it for the line that constructs the
toggle button (the html += '<button ... data-path="' + escapeAttr(lessonPath) +
'">...' expression); ensure the helper matches the original escaping semantics
to avoid XSS regressions.

---

Outside diff comments:
In `@site/app.js`:
- Around line 61-64: computeStats() and renderPhases() are still using
PHASES[p].status to determine completed phases (see the completePhases loop),
causing UI to disagree with user progress; change them to derive phase status
from actual user progress/lesson completion counts instead of static
PHASES[].status. Specifically, in computeStats() replace the loop that counts
completePhases from PHASES with logic that inspects the user's
progress/lessonsCompleted data for each phase (e.g., map each phase id to number
of completed lessons and consider a phase complete when its completedLessons ===
totalLessons), store that computed status/counts on the stats object, and then
update renderPhases() to read the phase status/counts from the computed stats
(not PHASES[p].status) so the UI classes reflect real progress. Ensure you
update any references to completePhases, PHASES[p].status, and phase completion
checks to use the new computed values.
- Line 233: The modal link is using an unencoded lessonPath when building
actionHtml, causing inconsistent encoding versus the earlier encoded usage;
update the construction of actionHtml to use the same encoded value (e.g., the
encoded lessonPath variable or call encodeURIComponent(lessonPath)) so both
places consistently encode query parameters (refer to actionHtml and lessonPath
where the link is built).

---

Nitpick comments:
In `@site/lesson.html`:
- Around line 2113-2134: testCurrent currently builds a local edits map
containing only the active file (edits[filePath] = ed.value) so tests miss
changes made to other files; persist a session-wide edits map (e.g.,
modifiedEdits) and update it whenever the editor content or selection changes
(handlers in the file-selection/change logic should set modifiedEdits[filePath]
= ed.value or delete if unchanged), then have testCurrent send the full
modifiedEdits instead of a single-file map in its POST body; ensure any
save/reset logic also updates modifiedEdits so the server receives all modified
files for testing.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 073cb36d-4633-4f15-a18e-901c5335b25e

📥 Commits

Reviewing files that changed from the base of the PR and between 4415dc8 and 6a5e052.

📒 Files selected for processing (12)
  • README.md
  • site/FRONTEND.md
  • site/app.js
  • site/dashboard.html
  • site/dashboard.js
  • site/header.js
  • site/index.html
  • site/lesson.html
  • site/local-server.mjs
  • site/phase.html
  • site/phase.js
  • site/style.css

Comment thread site/dashboard.js
Comment on lines +123 to +132
var resetBtn = document.getElementById('resetBtn');
if (resetBtn) {
resetBtn.addEventListener('click', function () {
if (!window.AIFSProgress) return;
var ok = window.confirm('Clear all your local progress (quiz answers and completed lessons)? This cannot be undone.');
if (!ok) return;
window.AIFSProgress.reset();
renderKpis(PHASES);
renderPhaseCards(PHASES);
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reset flow leaves the Continue button target stale.

After window.AIFSProgress.reset(), KPIs/cards rerender, but #continueBtn is not recomputed. Re-run full render() (or recompute target) so CTA matches new state.

Suggested fix
-        window.AIFSProgress.reset();
-        renderKpis(PHASES);
-        renderPhaseCards(PHASES);
+        window.AIFSProgress.reset();
+        render();
🤖 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 `@site/dashboard.js` around lines 123 - 132, The reset handler currently calls
window.AIFSProgress.reset() then renderKpis(PHASES) and renderPhaseCards(PHASES)
but does not update the Continue CTA target; modify the click handler for
resetBtn to call the full render() (or explicitly recompute/update the Continue
button target) after reset so the `#continueBtn` state/target is recalculated;
update the resetBtn listener that references window.AIFSProgress.reset(),
renderKpis, and renderPhaseCards to invoke render() (or the function that builds
the Continue CTA) as the final step.

Comment thread site/FRONTEND.md
Comment on lines +13 to +18
From the repo root:

```bash
cd ai-engineering-from-scratch
node site/local-server.mjs
```
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Quick-start command conflicts with stated working directory.

Line 13 says “From the repo root,” but Line 16 then cds into the repo again, which fails for users already at root.

Suggested doc fix
 ## Quick start
 
 From the repo root:
 
 ```bash
-cd ai-engineering-from-scratch
 node site/local-server.mjs
</details>

<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

```suggestion
From the repo root:

🤖 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 `@site/FRONTEND.md` around lines 13 - 18, The doc snippet contradicts its
preamble: it says "From the repo root" but then runs "cd
ai-engineering-from-scratch", which will fail for users already at root; in the
code block that contains "cd ai-engineering-from-scratch" and "node
site/local-server.mjs" remove the "cd ai-engineering-from-scratch" line (or
alternatively change the preamble to "From the parent directory") so the
sequence is consistent and simply runs "node site/local-server.mjs" from the
repo root (refer to the command lines "cd ai-engineering-from-scratch" and "node
site/local-server.mjs" in the snippet).

Comment thread site/header.js
Comment on lines +47 to +50
var host = String(window.location && window.location.hostname || '');
var proto = String(window.location && window.location.protocol || '');
if (proto === 'file:' || host === '127.0.0.1' || host === 'localhost') {
paint(0);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Local-host detection should include 0.0.0.0 and ::1.

Current guard misses common local dev hosts, so “local-first” can still make network calls in those setups.

Suggested fix
-      if (proto === 'file:' || host === '127.0.0.1' || host === 'localhost') {
+      if (
+        proto === 'file:' ||
+        host === '127.0.0.1' ||
+        host === 'localhost' ||
+        host === '0.0.0.0' ||
+        host === '::1'
+      ) {
📝 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
var host = String(window.location && window.location.hostname || '');
var proto = String(window.location && window.location.protocol || '');
if (proto === 'file:' || host === '127.0.0.1' || host === 'localhost') {
paint(0);
var host = String(window.location && window.location.hostname || '');
var proto = String(window.location && window.location.protocol || '');
if (
proto === 'file:' ||
host === '127.0.0.1' ||
host === 'localhost' ||
host === '0.0.0.0' ||
host === '::1'
) {
paint(0);
🤖 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 `@site/header.js` around lines 47 - 50, The local-host detection in header.js
using variables host and proto only checks for '127.0.0.1' and 'localhost' so
add '0.0.0.0' and the IPv6 loopback '::1' to the condition that calls paint(0);
update the if that currently reads (proto === 'file:' || host === '127.0.0.1' ||
host === 'localhost') to also test host === '0.0.0.0' and host === '::1' (and if
your code may encounter bracketed IPv6 addresses, normalize host by trimming
surrounding brackets before comparison) so local-first behavior covers common
local dev hosts.

Comment thread site/local-server.mjs
Comment on lines +135 to +138
async function ensureTmpDir(prefix) {
const dir = await fsp.mkdtemp(path.join(os.tmpdir(), prefix));
return dir;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Temporary directories are never cleaned up, causing disk space leaks.

ensureTmpDir creates directories that are used by /api/run and /api/test but never deleted. Over repeated usage, this will accumulate orphaned temp directories in the system's temp folder.

Proposed fix: Add cleanup after execution
+async function removeDirRecursive(dir) {
+  try {
+    await fsp.rm(dir, { recursive: true, force: true });
+  } catch {}
+}
+
 async function ensureTmpDir(prefix) {
   const dir = await fsp.mkdtemp(path.join(os.tmpdir(), prefix));
   return dir;
 }

Then in /api/run and /api/test handlers, wrap the execution in a try/finally:

     const tmp = await ensureTmpDir('aifs-run-');
+    try {
       const dstLesson = path.join(tmp, 'lesson');
       // ... existing code ...
       return sendJson(res, 200, { ... });
+    } finally {
+      removeDirRecursive(tmp);
+    }
🤖 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 `@site/local-server.mjs` around lines 135 - 138, ensureTmpDir currently creates
temp directories that are never removed; update the code to remove those dirs
after use by changing the callers (/api/run and /api/test handlers) to wrap
their execution in try/finally and call a safe removal (e.g. await
fsp.rm(tmpDir, { recursive: true, force: true }) or fs.promises.rm) in the
finally block; ensure the tmpDir returned from ensureTmpDir is passed to the
handlers' finally, and handle any removal errors silently/log them so cleanup
won't throw and will always run.

Comment thread site/phase.js
html += '<div class="lesson-tile-actions">';
if (lessonPath) html += '<a class="btn btn-primary" href="lesson.html?path=' + encodeURIComponent(lessonPath) + '">Open<\/a>';
if (hasProgress && lessonPath) {
html += '<button class="btn lesson-toggle" type="button" data-path="' + escapeAttr(lessonPath) + '">' + (userComplete ? 'Unmark' : 'Mark done') + '<\/button>';
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find all escapeAttr definitions/usages with context.
rg -n -C3 '\bescapeAttr\b'

# Confirm which scripts are loaded by phase.html and their order.
rg -n -C2 '<script .*src=' site/phase.html

Repository: rohitg00/ai-engineering-from-scratch

Length of output: 3735


Fix escapeAttr usage on the phase page
site/phase.js calls escapeAttr(lessonPath) at line 118, but escapeAttr is defined only in site/lesson.html; the scripts loaded by site/phase.html (data.js, progress.js, header.js, cmdpalette.js, then phase.js) don’t define it. This can throw a ReferenceError when rendering the lesson toggle—move escapeAttr to a shared utility loaded by phase.html, or add an equivalent local helper to phase.js.

🤖 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 `@site/phase.js` at line 118, The code in phase.js uses escapeAttr(lessonPath)
but escapeAttr is defined only in lesson.html, causing a ReferenceError; fix by
extracting the escapeAttr implementation into a shared utility module (e.g.,
utils/escape.js) and import or include that before phase.js so escapeAttr is
available to phase.html, or alternatively add an equivalent local helper
function named escapeAttr at the top of phase.js and use it for the line that
constructs the toggle button (the html += '<button ... data-path="' +
escapeAttr(lessonPath) + '">...' expression); ensure the helper matches the
original escaping semantics to avoid XSS regressions.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
site/app.js (2)

45-69: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use a single completion source for phase metrics and styling.

completeLessons / per-phase done now come from AIFSProgress, but completePhases and the row status badge still come from static PHASES[*].status. A fresh profile can therefore show 0 / N lessons completed while phases still render as complete. Derive phase completion and styling from the same per-lesson progress data to keep the dashboard coherent.

Also applies to: 105-128

🤖 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 `@site/app.js` around lines 45 - 69, computeStats currently mixes sources: it
counts completeLessons from AIFSProgress but computes completePhases and UI
badges from PHASES[*].status, causing inconsistent displays. Change computeStats
(and the row badge rendering logic referenced around the 105-128 area) so
completePhases is derived by iterating each PHASES[p].lessons and treating a
phase as complete only when every lesson's URL path returns true from
window.AIFSProgress.isLessonComplete(after using
window.AIFSProgress.extractPath) (fall back to PHASES status only if
AIFSProgress is unavailable), and use that same per-lesson result to decide
per-phase "done" styling/badges instead of reading PHASES[p].status directly.

224-237: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Keep the static lesson status as the fallback.

The unconditional else statusClass = 'planned' also runs when window.AIFSProgress is unavailable, so the modal loses the curriculum's built-in status metadata and every unfinished lesson looks planned. Only override l.status when local progress is actually available.

Suggested fix
       var statusClass = l.status.replace(/ /g, '-');
-      if (userComplete) statusClass = 'complete';
-      else statusClass = 'planned';
+      if (hasProgress) {
+        statusClass = userComplete ? 'complete' : 'planned';
+      }
🤖 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 `@site/app.js` around lines 224 - 237, The code unconditionally overwrites the
lesson's original status with 'planned' even when no local progress exists;
change the logic in the loop around hasProgress/userComplete/statusClass so that
you initialize statusClass from l.status (statusClass = l.status.replace(/ /g,
'-')), then only override it to 'complete' when userComplete is true and only
set it to 'planned' when hasProgress is true and userComplete is false (i.e., if
(!userComplete && hasProgress) statusClass = 'planned'); ensure you reference
the existing variables userComplete, hasProgress, statusClass and l.status when
making this conditional change.
🤖 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 `@README.md`:
- Around line 136-137: Remove the duplicated "Option C — find your level
*(recommended)*." heading by keeping only the updated line that reads "Inside
Claude, Cursor, Codex, OpenClaw, Hermes, or any agent with the curriculum skills
installed" and deleting the earlier/duplicate line that mentions "SkillKit
installed"; ensure the README's Getting started flow contains the single
corrected Option C heading.

---

Outside diff comments:
In `@site/app.js`:
- Around line 45-69: computeStats currently mixes sources: it counts
completeLessons from AIFSProgress but computes completePhases and UI badges from
PHASES[*].status, causing inconsistent displays. Change computeStats (and the
row badge rendering logic referenced around the 105-128 area) so completePhases
is derived by iterating each PHASES[p].lessons and treating a phase as complete
only when every lesson's URL path returns true from
window.AIFSProgress.isLessonComplete(after using
window.AIFSProgress.extractPath) (fall back to PHASES status only if
AIFSProgress is unavailable), and use that same per-lesson result to decide
per-phase "done" styling/badges instead of reading PHASES[p].status directly.
- Around line 224-237: The code unconditionally overwrites the lesson's original
status with 'planned' even when no local progress exists; change the logic in
the loop around hasProgress/userComplete/statusClass so that you initialize
statusClass from l.status (statusClass = l.status.replace(/ /g, '-')), then only
override it to 'complete' when userComplete is true and only set it to 'planned'
when hasProgress is true and userComplete is false (i.e., if (!userComplete &&
hasProgress) statusClass = 'planned'); ensure you reference the existing
variables userComplete, hasProgress, statusClass and l.status when making this
conditional change.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bb48cf26-26df-4251-ae41-9e47f81704c1

📥 Commits

Reviewing files that changed from the base of the PR and between 6a5e052 and d57b164.

📒 Files selected for processing (5)
  • README.md
  • site/app.js
  • site/index.html
  • site/lesson.html
  • site/style.css

Comment thread README.md
Comment on lines +136 to 137
**Option C — find your level *(recommended)*.** Skip ahead intelligently. Inside Claude, Cursor, Codex, OpenClaw, Hermes, or any agent with SkillKit installed:
**Option C — find your level *(recommended)*.** Skip ahead intelligently. Inside Claude, Cursor, Codex, OpenClaw, Hermes, or any agent with the curriculum skills installed:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove duplicated Option C heading line.

Line 136 and Line 137 both define the same “Option C” heading; this renders duplicate content in the Getting started flow. Keep only one line (prefer the updated “curriculum skills installed” wording).

🤖 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 `@README.md` around lines 136 - 137, Remove the duplicated "Option C — find
your level *(recommended)*." heading by keeping only the updated line that reads
"Inside Claude, Cursor, Codex, OpenClaw, Hermes, or any agent with the
curriculum skills installed" and deleting the earlier/duplicate line that
mentions "SkillKit installed"; ensure the README's Getting started flow contains
the single corrected Option C heading.

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.

1 participant