Author a course
A course is config plus source snapshots. Students paste the course config URL to open it.
Scaffold quickly
If you are not sure what to put in the config files, use the extension’s skill and an AI agent to generate them.
- Open the folder that holds the source snapshots in the IDE (the command is unavailable with no folder open, and it is not on the Learn By Diff view).
- Open the Command Palette and run LearnByDiff: Install Generate Course Skill. The extension installs the course generator Skill in that folder and copies a pasteable
/generate-course-configprompt to the clipboard. - Paste the prompt into an agent chat, edit the placeholder paths as needed, and send it to generate the course config:
Snapshot path: ./path/to/chapter/code/root/folder
Target folder: /absolute/path/to/course-configYou can also skip the extension command and run this in the snapshot folder yourself:
npx skills add RJiazhen/learn-by-diff@generate-course-config -yThen run /generate-course-config.
You can also pass a GitHub repository URL; the agent will read the source and generate the config.
Course config files
Every course needs a course.jsonc and chapter files chapters/*.jsonc.
course.jsonc
Create course.jsonc in your course repository and paste:
{
"$schema": "https://raw.githubusercontent.com/RJiazhen/learn-by-diff/refs/heads/main/packages/protocol/schema.json#/$defs/course",
"id": "demo-course",
"title": "Demo course",
"source": {
"repository": "https://github.com/RJiazhen/learn-by-diff.git",
"root": "examples/demo-source",
},
"chaptersDir": "chapters",
}Fields in course.jsonc:
id(optional): Stable course id, also used as the student workspace folder name. Prefer a path-safe slug (letters, digits, hyphens). If omitted, the course folder name is used; if that folder is a git repository root,{repo-name}-learn.title(optional): Human-readable display name. Defaults toid.source.repository(optional): Where the source lives. Three forms:- Git repository URL, for example
https://github.com/org/repo.git. - Local path: absolute, or relative to the directory that contains
course.jsonc. - Omitted: source is the directory that contains
course.jsonc.
- Git repository URL, for example
source.root(optional): Path of the source tree insidesource.repository. Omitted means the repository root.chaptersDir(optional): Directory of chapter JSONC files, relative tocourse.jsonc. Defaults tochaptersnext tocourse.jsonc.retain(optional):.gitignore-style patterns for paths that stay when a chapter snapshot is applied. Matching uses gitignore rules from the learning folder root (node_moduleskeeps everynode_modulesdirectory). Omitted meansnode_modules.[]keeps no extra project paths. The workspace.gitignoredoes not control this. See Retained files.
chapters/*.jsonc
Next to course.jsonc, create a chapters directory, then create 00-start.jsonc and paste:
{
"$schema": "https://raw.githubusercontent.com/RJiazhen/learn-by-diff/refs/heads/main/packages/protocol/schema.json#/$defs/chapter",
"id": "start",
"title": "Start",
"fromDir": "start",
"toDir": "skeleton",
"entryFiles": ["src/main.js"],
"docs": "README.md",
}Chapter files go in chaptersDir (default chapters/ next to course.jsonc).
Each .jsonc file is one chapter. Course order matches filename order, so prefix names with numbers such as 00-start.jsonc, 01-skeleton.jsonc.
If id is empty, it is derived by stripping the extension and a leading numeric prefix: 00-start.jsonc → start. Prefixes like 00-, 01_, and 1. work. A course needs at least one chapter file, and chapter ids must be unique.
Fields in each chapter file:
id(optional): Stable chapter id used for progress, not for display. Omitted or empty → derived from the filename (00-start.jsonc→start).title(optional): Display name. Omitted or empty →id.fromDir(optional): Start snapshot directory, relative tosource.repository(or tosource.rootwhen that is set). Empty means an empty tree. No..or absolute paths.toDir(optional): Goal snapshot directory; same path rules asfromDir. Empty means an empty tree.entryFiles(optional): Files to highlight, relative to the chapter snapshot tree (toDir). Omitted means every file undertoDir.changedFiles(optional): From/to diffs (pathplusUadded,Mmodified,Ddeleted). Open Course uses this instead of comparing snapshots. Omitted means classify at runtime. An empty list means the chapter did not change. The generate-course-config skill fills this when scaffolding.docs(optional): Chapter documentation: anhttp(s)URL, or a file path in the snapshot tree (toDirfirst, thenfromDir), for exampleREADME.md. Omitted means no docs button.
Retained files
When you switch chapters, .gitignore-style retain patterns stay in the learning folder. Omitted retain keeps every node_modules. See Retained files.
Share a course
Follow these steps to share a finished course.
Push the course repository to Git
Publish the course repository (including the source) to Git.
Share the config location
If the course is on GitHub, you can share the course.jsonc file URL. The other person pastes it into Open Course.
For example:
https://github.com/RJiazhen/learn-by-diff/blob/main/packages/courses-config/demo-course/.course-config/course.jsoncIf course.jsonc is at the repository root, or at .course-config/course.jsonc under the root, you can share the git repository URL instead:
https://github.com/RJiazhen/learn-by-diff.gitIf course.jsonc lives in a subdirectory, append # and the path relative to the repository root (pointing at course.jsonc):
https://github.com/RJiazhen/learn-by-diff.git#packages/courses-config/demo-course/.course-config/course.jsoncOne-click share links
Or turn that address into a one-click link. Clicking it wakes the IDE and opens the course:
vscode://RuanJiazhen.learn-by-diff/open?url=<course.jsonc-or-git-repo-URL>
cursor://RuanJiazhen.learn-by-diff/open?url=<course.jsonc-or-git-repo-URL>Use vscode:// for VS Code and cursor:// for Cursor. The other person needs LearnByDiff installed; the OS may ask once for permission to open the protocol.
Example for https://github.com/RJiazhen/learn-by-diff.git:
vscode://RuanJiazhen.learn-by-diff/open?url=https://github.com/RJiazhen/learn-by-diff.git
cursor://RuanJiazhen.learn-by-diff/open?url=https://github.com/RJiazhen/learn-by-diff.git