explico 0.13.1 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (256) hide show
  1. package/CHANGELOG.md +990 -1
  2. package/README.md +200 -30
  3. package/dist/compiler/astro.d.ts +15 -2
  4. package/dist/compiler/astro.js +40 -11
  5. package/dist/compiler/cli-main.d.ts +100 -0
  6. package/dist/compiler/cli-main.js +433 -0
  7. package/dist/compiler/cli-reference.d.ts +50 -0
  8. package/dist/compiler/cli-reference.js +213 -0
  9. package/dist/compiler/cli-verbs.d.ts +45 -0
  10. package/dist/compiler/cli-verbs.js +224 -0
  11. package/dist/compiler/cli-verify.d.ts +51 -0
  12. package/dist/compiler/cli-verify.js +151 -0
  13. package/dist/compiler/cli.d.ts +408 -24
  14. package/dist/compiler/cli.js +1328 -8
  15. package/dist/compiler/collections.d.ts +88 -1
  16. package/dist/compiler/collections.js +170 -25
  17. package/dist/compiler/content-release.d.ts +49 -0
  18. package/dist/compiler/content-release.js +63 -0
  19. package/dist/compiler/content-schemas.d.ts +2 -2
  20. package/dist/compiler/course-config.d.ts +1 -1
  21. package/dist/compiler/course-config.js +105 -1
  22. package/dist/compiler/decks.d.ts +161 -0
  23. package/dist/compiler/decks.js +235 -0
  24. package/dist/compiler/exports/combined-html.d.ts +5 -1
  25. package/dist/compiler/exports/deck-fit.d.ts +55 -0
  26. package/dist/compiler/exports/deck-fit.js +90 -0
  27. package/dist/compiler/exports/export-chapters.d.ts +37 -6
  28. package/dist/compiler/exports/export-chapters.js +64 -5
  29. package/dist/compiler/exports/export-pages.d.ts +30 -3
  30. package/dist/compiler/exports/export-pages.js +24 -1
  31. package/dist/compiler/exports/latex/beamerthemeexplico.sty +124 -0
  32. package/dist/compiler/exports/latex/cache.d.ts +78 -0
  33. package/dist/compiler/exports/latex/cache.js +129 -0
  34. package/dist/compiler/exports/latex/capture.d.ts +10 -1
  35. package/dist/compiler/exports/latex/capture.js +3 -1
  36. package/dist/compiler/exports/latex/compile.d.ts +13 -2
  37. package/dist/compiler/exports/latex/compile.js +12 -1
  38. package/dist/compiler/exports/latex/deck.d.ts +92 -0
  39. package/dist/compiler/exports/latex/deck.js +349 -0
  40. package/dist/compiler/exports/latex/document.d.ts +51 -6
  41. package/dist/compiler/exports/latex/document.js +71 -12
  42. package/dist/compiler/exports/latex/explico-common.sty +198 -0
  43. package/dist/compiler/exports/latex/explico.cls +13 -183
  44. package/dist/compiler/exports/latex/serialize.d.ts +60 -4
  45. package/dist/compiler/exports/latex/serialize.js +4 -2
  46. package/dist/compiler/exports/latex-export.d.ts +24 -2
  47. package/dist/compiler/exports/latex-export.js +107 -26
  48. package/dist/compiler/exports/markdown/serialize.d.ts +24 -4
  49. package/dist/compiler/exports/markdown/serialize.js +85 -8
  50. package/dist/compiler/exports/markdown-export.d.ts +6 -1
  51. package/dist/compiler/exports/markdown-export.js +50 -11
  52. package/dist/compiler/exports/standalone-html.d.ts +10 -5
  53. package/dist/compiler/exports/standalone-html.js +3 -3
  54. package/dist/compiler/exports/static-tree.d.ts +38 -1
  55. package/dist/compiler/exports/static-tree.js +120 -4
  56. package/dist/compiler/knowledge-version.d.ts +26 -2
  57. package/dist/compiler/knowledge-version.js +27 -17
  58. package/dist/compiler/lesson-derivation.d.ts +143 -6
  59. package/dist/compiler/lesson-derivation.js +145 -13
  60. package/dist/compiler/manifest.d.ts +133 -4
  61. package/dist/compiler/manifest.js +364 -58
  62. package/dist/compiler/page-versions.d.ts +184 -0
  63. package/dist/compiler/page-versions.js +217 -0
  64. package/dist/compiler/render-diagnostics.d.ts +5 -1
  65. package/dist/compiler/resolution-report.d.ts +24 -6
  66. package/dist/compiler/resolution-report.js +22 -4
  67. package/dist/compiler/validate-content.d.ts +7 -0
  68. package/dist/compiler/validate-content.js +160 -0
  69. package/dist/{course-config-rRtME5UV.d.ts → course-config-BLtIdkei.d.ts} +164 -1
  70. package/dist/curriculum/competency-graph.d.ts +54 -0
  71. package/dist/curriculum/competency-graph.js +41 -0
  72. package/dist/reference/base-library.d.mts +8 -0
  73. package/dist/reference/base-library.mjs +19 -0
  74. package/dist/reference/consistency.d.ts +8 -4
  75. package/dist/reference/consistency.js +82 -26
  76. package/dist/reference/deck-remark.d.mts +123 -0
  77. package/dist/reference/deck-remark.mjs +600 -0
  78. package/dist/reference/deck-source.d.ts +107 -0
  79. package/dist/reference/deck-source.js +140 -0
  80. package/dist/reference/decks-validate.d.ts +133 -0
  81. package/dist/reference/decks-validate.js +359 -0
  82. package/dist/reference/derived-entries.d.ts +5 -1
  83. package/dist/reference/diagram-fit.d.ts +6 -2
  84. package/dist/reference/diagram-fit.js +5 -1
  85. package/dist/reference/figure-symbols.d.ts +23 -6
  86. package/dist/reference/figure-symbols.js +51 -15
  87. package/dist/reference/float-name.d.ts +43 -0
  88. package/dist/reference/float-name.js +58 -0
  89. package/dist/reference/folds-validate.d.ts +1 -1
  90. package/dist/reference/gate-abbreviations.d.ts +1 -1
  91. package/dist/reference/gate-indices.js +5 -4
  92. package/dist/reference/gate-math.d.ts +29 -2
  93. package/dist/reference/gate-math.js +99 -18
  94. package/dist/reference/gate-part-pages.d.ts +1 -1
  95. package/dist/reference/lab-katex-validate.d.ts +10 -6
  96. package/dist/reference/lab-katex-validate.js +17 -9
  97. package/dist/reference/lab-math-scope.js +29 -4
  98. package/dist/reference/math-glyphs.d.mts +25 -0
  99. package/dist/reference/math-glyphs.mjs +189 -25
  100. package/dist/reference/page-notation.d.ts +22 -3
  101. package/dist/reference/page-notation.js +32 -10
  102. package/dist/reference/references.d.ts +14 -1
  103. package/dist/reference/references.js +13 -0
  104. package/dist/reference/registry-scope.d.mts +33 -0
  105. package/dist/reference/registry-scope.mjs +34 -0
  106. package/dist/reference/registry.js +29 -10
  107. package/dist/reference/rehype-deck.d.mts +6 -0
  108. package/dist/reference/rehype-deck.mjs +286 -0
  109. package/dist/reference/rehype-heading-anchors.d.mts +20 -0
  110. package/dist/reference/rehype-heading-anchors.mjs +48 -6
  111. package/dist/reference/remark-notation.mjs +249 -40
  112. package/dist/reference/source-lines.d.mts +12 -0
  113. package/dist/reference/source-lines.mjs +65 -0
  114. package/dist/reference/types.d.ts +28 -0
  115. package/dist/rules/catalog.d.ts +21 -4
  116. package/dist/rules/catalog.js +237 -2
  117. package/dist/rules/course-rules.d.ts +5 -1
  118. package/dist/rules/course-rules.js +17 -3
  119. package/dist/rules/course-rules.schema.json +12 -0
  120. package/dist/rules/in-effect.d.ts +13 -3
  121. package/dist/rules/in-effect.js +34 -10
  122. package/dist/rules/rules.schema.json +280 -1
  123. package/dist/rules/settings.d.ts +1 -1
  124. package/dist/session/PreferenceStore.d.ts +6 -0
  125. package/dist/session/PreferenceStore.js +18 -1
  126. package/dist/session/deck-memory.d.ts +116 -0
  127. package/dist/session/deck-memory.js +143 -0
  128. package/dist/session/reading-memory.d.ts +96 -0
  129. package/dist/session/reading-memory.js +133 -0
  130. package/docs/VERSIONING.md +21 -18
  131. package/docs/authoring/README.md +31 -9
  132. package/docs/authoring/agents.md +72 -18
  133. package/docs/authoring/cli-reference.json +2172 -0
  134. package/docs/authoring/cli.md +887 -0
  135. package/docs/authoring/components.md +15 -0
  136. package/docs/authoring/deck.md +612 -0
  137. package/docs/authoring/migrating.md +1195 -16
  138. package/docs/authoring/notation.md +49 -6
  139. package/docs/authoring/structure.md +80 -1
  140. package/docs/authoring/versioning.md +56 -3
  141. package/docs/authoring/visualizations.md +7 -2
  142. package/docs/course-agent/AGENTS.md +40 -0
  143. package/docs/course-agent/agents/rules-reviewer.md +33 -0
  144. package/docs/course-agent/skills/draft-lesson/SKILL.md +38 -0
  145. package/docs/course-agent/skills/migrate-course/SKILL.md +32 -0
  146. package/docs/course-agent/skills/release-content/SKILL.md +26 -0
  147. package/docs/diagram-scaling.md +148 -0
  148. package/docs/privacy.md +59 -0
  149. package/package.json +8 -2
  150. package/src/compiler/astro.ts +60 -21
  151. package/src/compiler/cli-main.ts +557 -0
  152. package/src/compiler/cli-reference.ts +285 -0
  153. package/src/compiler/cli-verbs.ts +270 -0
  154. package/src/compiler/cli-verify.ts +241 -0
  155. package/src/compiler/cli.ts +2119 -170
  156. package/src/compiler/collections.ts +284 -25
  157. package/src/compiler/content-release.ts +91 -0
  158. package/src/compiler/course-config.ts +267 -0
  159. package/src/compiler/decks.ts +455 -0
  160. package/src/compiler/exports/deck-fit.ts +127 -0
  161. package/src/compiler/exports/export-chapters.ts +105 -6
  162. package/src/compiler/exports/export-pages.ts +46 -2
  163. package/src/compiler/exports/latex/beamerthemeexplico.sty +124 -0
  164. package/src/compiler/exports/latex/cache.ts +183 -0
  165. package/src/compiler/exports/latex/capture.ts +5 -2
  166. package/src/compiler/exports/latex/compile.ts +18 -0
  167. package/src/compiler/exports/latex/deck.ts +419 -0
  168. package/src/compiler/exports/latex/document.ts +117 -21
  169. package/src/compiler/exports/latex/explico-common.sty +198 -0
  170. package/src/compiler/exports/latex/explico.cls +13 -183
  171. package/src/compiler/exports/latex/serialize.ts +25 -21
  172. package/src/compiler/exports/latex/texlive-packages.txt +6 -2
  173. package/src/compiler/exports/latex-export.ts +187 -45
  174. package/src/compiler/exports/markdown/serialize.ts +118 -9
  175. package/src/compiler/exports/markdown-export.ts +56 -11
  176. package/src/compiler/exports/standalone-html.ts +8 -7
  177. package/src/compiler/exports/static-tree.ts +175 -3
  178. package/src/compiler/knowledge-version.ts +56 -21
  179. package/src/compiler/lesson-derivation.ts +282 -16
  180. package/src/compiler/manifest.ts +568 -63
  181. package/src/compiler/page-versions.ts +401 -0
  182. package/src/compiler/resolution-report.ts +38 -7
  183. package/src/compiler/validate-content.ts +215 -0
  184. package/src/components/CourseContents.astro +124 -6
  185. package/src/components/CurriculumMap.astro +49 -1
  186. package/src/components/FloatIndex.astro +1 -1
  187. package/src/components/PartOverview.astro +51 -5
  188. package/src/components/assessments/AssessmentSet.astro +41 -15
  189. package/src/components/block-heading.ts +15 -6
  190. package/src/components/card-carry.ts +7 -4
  191. package/src/components/card-manager.ts +14 -2
  192. package/src/components/card-memory.ts +13 -9
  193. package/src/components/deck/DeckEnhancer.astro +13 -0
  194. package/src/components/deck/deck-context.ts +104 -0
  195. package/src/components/deck/deck-controller.ts +1287 -0
  196. package/src/components/deck/deck-map.ts +91 -0
  197. package/src/components/deck-lane.ts +163 -0
  198. package/src/components/diagram/Diagram.astro +131 -86
  199. package/src/components/disclosure/persisted-disclosure.ts +9 -1
  200. package/src/components/figure/Figure.astro +7 -2
  201. package/src/components/figure/FloatCaption.astro +5 -3
  202. package/src/components/fold/Fold.astro +2 -2
  203. package/src/components/notation/NotationFormula.astro +81 -11
  204. package/src/components/notation/NotationGlossary.astro +132 -1
  205. package/src/components/notation/NotationLayer.astro +15 -7
  206. package/src/components/starlight/AboutPanel.astro +186 -3
  207. package/src/components/starlight/CollapsibleSections.astro +24 -3
  208. package/src/components/starlight/DiagramEnhancer.astro +5 -1
  209. package/src/components/starlight/HeadingAnchorEnhancer.astro +1 -1
  210. package/src/components/starlight/LayoutHeader.astro +17 -0
  211. package/src/components/starlight/LayoutPageTitle.astro +110 -0
  212. package/src/components/starlight/LayoutSidebar.astro +807 -21
  213. package/src/components/starlight/LayoutSocialIcons.astro +62 -6
  214. package/src/components/starlight/LessonExportActions.astro +27 -9
  215. package/src/components/starlight/LessonFooter.astro +59 -0
  216. package/src/components/starlight/ReadingMemoryNotice.astro +114 -0
  217. package/src/components/starlight/TextSizeControl.astro +24 -13
  218. package/src/components/starlight/page-marks.ts +139 -0
  219. package/src/components/starlight/section-summary.ts +1 -1
  220. package/src/components/starlight/sidebar-editions.ts +245 -0
  221. package/src/components/table/Table.astro +15 -7
  222. package/src/content-config.ts +87 -1
  223. package/src/curriculum/competency-graph.ts +95 -0
  224. package/src/reference/base-library.mjs +19 -0
  225. package/src/reference/consistency.ts +137 -39
  226. package/src/reference/deck-remark.mjs +600 -0
  227. package/src/reference/deck-source.ts +273 -0
  228. package/src/reference/decks-validate.ts +604 -0
  229. package/src/reference/diagram-fit.ts +18 -4
  230. package/src/reference/diagram-layout.ts +3 -1
  231. package/src/reference/figure-symbols.ts +108 -31
  232. package/src/reference/folds-validate.ts +1 -1
  233. package/src/reference/gate-indices.ts +5 -3
  234. package/src/reference/gate-math.ts +169 -25
  235. package/src/reference/lab-katex-validate.ts +30 -15
  236. package/src/reference/lab-math-scope.ts +34 -3
  237. package/src/reference/math-glyphs.mjs +189 -25
  238. package/src/reference/page-notation.ts +69 -15
  239. package/src/reference/references.ts +28 -0
  240. package/src/reference/registry-scope.mjs +34 -0
  241. package/src/reference/registry.ts +41 -11
  242. package/src/reference/rehype-deck.mjs +286 -0
  243. package/src/reference/rehype-heading-anchors.mjs +48 -6
  244. package/src/reference/remark-notation.mjs +249 -40
  245. package/src/reference/source-lines.mjs +65 -0
  246. package/src/reference/types.ts +28 -0
  247. package/src/rules/catalog.ts +274 -4
  248. package/src/rules/course-rules.ts +21 -1
  249. package/src/rules/in-effect.ts +58 -11
  250. package/src/session/PreferenceStore.ts +23 -0
  251. package/src/session/deck-memory.ts +263 -0
  252. package/src/session/reading-memory.ts +243 -0
  253. package/src/styles/deck.css +1150 -0
  254. package/src/styles/global.css +958 -27
  255. package/src/styles/zones.css +277 -0
  256. /package/src/{components → reference}/float-name.ts +0 -0
@@ -46,7 +46,9 @@ import {
46
46
  import {
47
47
  gateContentMath,
48
48
  pageScope,
49
- scopeEntry
49
+ scopeEntry,
50
+ scopeLocals,
51
+ suspendedOperators
50
52
  } from "../reference/gate-math.js";
51
53
  import { gateIndices } from "../reference/gate-indices.js";
52
54
  import { derivedNotationEntries } from "../reference/derived-entries.js";
@@ -81,14 +83,19 @@ import {
81
83
  import { loadLintIgnore } from "../reference/lint-ignore.js";
82
84
  import { buildNotationRegistry } from "../reference/registry.js";
83
85
  import { formulaGlyphScope } from "../reference/gloss.js";
84
- import { resolveReferenceId } from "../reference/references.js";
86
+ import { pageScopeLessonIds, resolvePageReferenceId } from "../reference/references.js";
85
87
  import {
86
88
  coursePartsOf,
87
89
  normalizeBlockLabels,
88
90
  normalizeCards,
91
+ normalizeSidebarMarks,
92
+ normalizeSidebarEditions,
93
+ normalizeReadingMemory,
94
+ normalizeAppearance,
89
95
  normalizeLessonExports,
90
96
  normalizeDisclosure,
91
- normalizeNumbering
97
+ normalizeNumbering,
98
+ normalizeDeck
92
99
  } from "./course-config.js";
93
100
  import {
94
101
  disabledCodes,
@@ -104,6 +111,14 @@ import {
104
111
  import {
105
112
  resolveKnowledgeVersion
106
113
  } from "./knowledge-version.js";
114
+ import {
115
+ deckStaleDiagnostics,
116
+ nextPageVersions,
117
+ PAGE_VERSIONS_FILE,
118
+ pageSourceDigest,
119
+ readPageVersions,
120
+ sinceOf
121
+ } from "./page-versions.js";
107
122
  import {
108
123
  loadAbbreviationRegistryInput,
109
124
  loadContentRecords,
@@ -113,15 +128,24 @@ import {
113
128
  loadNonChapterPages,
114
129
  loadNotationSpecs,
115
130
  loadPartOverviews,
116
- loadSourceRecords
131
+ loadSourceRecords,
132
+ loadDeckFiles,
133
+ loadDeckNotationPages,
134
+ loadLessonFiles
117
135
  } from "./collections.js";
136
+ import { buildDecks, chapterEquationLatex } from "./decks.js";
137
+ import { readDeckSource } from "../reference/deck-source.js";
138
+ import { coveredSections } from "../reference/decks-validate.js";
118
139
  import {
119
140
  buildSidebar,
120
141
  deriveSources,
121
142
  isLessonSlug,
122
- lessonIdFromSlug
143
+ lessonIdFromSlug,
144
+ mergeTrackOrder,
145
+ sidebarLessonSlug,
146
+ withPartDeckFrames
123
147
  } from "./lesson-derivation.js";
124
- const MANIFEST_SCHEMA_VERSION = 31;
148
+ const MANIFEST_SCHEMA_VERSION = 37;
125
149
  const REPOSITORY_ROOT = process.cwd();
126
150
  const CONTENT_ROOT = join(REPOSITORY_ROOT, "content");
127
151
  const MANIFEST_PATH = join(REPOSITORY_ROOT, "build", "manifest.json");
@@ -173,11 +197,11 @@ function contentHashes() {
173
197
  return { content, contentTree: `sha256:${tree.digest("hex")}` };
174
198
  }
175
199
  function lessonNotationResolution(lesson, definitionsById, availableDefinitionIds) {
176
- const scope = { kind: "page", lessonId: lesson.lessonId };
200
+ const scopes = pageScopeLessonIds(lesson);
177
201
  return lesson.references.map((reference) => {
178
- const definitionId = resolveReferenceId(
202
+ const definitionId = resolvePageReferenceId(
179
203
  reference.key,
180
- scope,
204
+ scopes,
181
205
  availableDefinitionIds
182
206
  );
183
207
  const definition = definitionId === void 0 ? void 0 : definitionsById.get(definitionId);
@@ -245,29 +269,16 @@ function studyMinutesRange(value) {
245
269
  return void 0;
246
270
  }
247
271
  function lessonFrontmatterById() {
248
- const base = join(CONTENT_ROOT, "docs");
249
272
  const result = /* @__PURE__ */ new Map();
250
- const walk = (directory) => {
251
- for (const entry of readdirSync(directory, { withFileTypes: true })) {
252
- const path = join(directory, entry.name);
253
- if (entry.isDirectory()) {
254
- walk(path);
255
- continue;
256
- }
257
- if (!entry.isFile() || !/\.mdx?$/.test(entry.name)) continue;
258
- const slug = relative(base, path).replace(/\\/g, "/");
259
- if (!isLessonSlug(slug)) continue;
260
- const id = slug.replace(/\.(md|mdx)$/, "").replace(/\//g, ".");
261
- const data = parseFrontmatter(readFileSync(path, "utf8")).frontmatter ?? {};
262
- const studyMinutes = studyMinutesRange(data.studyMinutes);
263
- result.set(id, {
264
- title: typeof data.title === "string" ? data.title : id,
265
- description: typeof data.description === "string" ? data.description : "",
266
- ...studyMinutes === void 0 ? {} : { studyMinutes }
267
- });
268
- }
269
- };
270
- walk(base);
273
+ for (const { path, lessonId: id } of loadLessonFiles()) {
274
+ const data = parseFrontmatter(readFileSync(path, "utf8")).frontmatter ?? {};
275
+ const studyMinutes = studyMinutesRange(data.studyMinutes);
276
+ result.set(id, {
277
+ title: typeof data.title === "string" ? data.title : id,
278
+ description: typeof data.description === "string" ? data.description : "",
279
+ ...studyMinutes === void 0 ? {} : { studyMinutes }
280
+ });
281
+ }
271
282
  return result;
272
283
  }
273
284
  async function compileManifest(courseConfig) {
@@ -280,17 +291,36 @@ async function compileManifest(courseConfig) {
280
291
  const off = disabledCodes(rules);
281
292
  const numberingSettings = normalizeNumbering(courseConfig);
282
293
  const blockLabels = normalizeBlockLabels(courseConfig);
294
+ const deckSettings = normalizeDeck(courseConfig);
283
295
  const [catalog, notationInput, abbreviationsInput] = await Promise.all([
284
296
  loadCurriculumCatalog(),
285
297
  loadNotationRegistryInput(),
286
298
  loadAbbreviationRegistryInput()
287
299
  ]);
288
300
  const nonChapterPages = loadNonChapterPages();
301
+ const deckFiles = loadDeckFiles();
302
+ const lessonDeckFiles = deckFiles.filter((deck) => deck.scope === "lesson");
303
+ const trackRank = new Map(
304
+ mergeTrackOrder(catalog.tracks.map((track) => ({ id: track.id, lessons: track.lessons }))).map(
305
+ (lessonId, index) => [lessonId, index]
306
+ )
307
+ );
308
+ const readingOrder = (lessonIds) => [...lessonIds].sort(
309
+ (left, right) => (trackRank.get(left) ?? Number.POSITIVE_INFINITY) - (trackRank.get(right) ?? Number.POSITIVE_INFINITY) || left.localeCompare(right)
310
+ );
311
+ const deckPages = loadDeckNotationPages(deckFiles, { order: readingOrder });
312
+ const deckLessonIds = new Set(
313
+ loadLessonFiles().filter((file) => file.edition === "deck").map((file) => file.lessonId)
314
+ );
315
+ const isChapter = (lessonId) => !deckLessonIds.has(lessonId);
316
+ const deckPageIds = new Set(deckPages.map((page) => page.notation.lessonId));
317
+ const chapterInputs = notationInput.lessons.filter((lesson) => isChapter(lesson.lessonId));
289
318
  const withPages = {
290
319
  ...notationInput,
291
320
  lessons: [
292
321
  ...notationInput.lessons,
293
- ...nonChapterPages.map((page) => page.notation)
322
+ ...nonChapterPages.map((page) => page.notation),
323
+ ...deckPages.map((page) => page.notation)
294
324
  ]
295
325
  };
296
326
  const registry = buildNotationRegistry(withPages);
@@ -325,9 +355,9 @@ async function compileManifest(courseConfig) {
325
355
  notationInput.sharedDefinitions.map((definition) => [definition.key, definition.label])
326
356
  );
327
357
  const localLabelsByLesson = new Map(
328
- notationInput.lessons.map((lesson) => [
358
+ withPages.lessons.map((lesson) => [
329
359
  lesson.lessonId,
330
- new Map(lesson.localDefinitions.map((definition) => [definition.key, definition.label]))
360
+ new Map(scopeLocals(lesson).map((definition) => [definition.key, definition.label]))
331
361
  ])
332
362
  );
333
363
  const termLabel = (lessonId, key) => localLabelsByLesson.get(lessonId)?.get(key) ?? sharedLabels.get(key);
@@ -372,18 +402,26 @@ async function compileManifest(courseConfig) {
372
402
  abbreviations: abbreviationsInput,
373
403
  courseAbbreviations: rules.settings("N14")
374
404
  }) : [],
375
- ...rules.isEnabled("S1") ? gateHeadingLength(notationInput.lessons, {
405
+ ...rules.isEnabled("S1") ? gateHeadingLength(chapterInputs, {
376
406
  maxLength: rules.number("S1", "maxLength"),
377
407
  labelFor: termLabel
378
408
  }) : [],
379
- ...gateHeadingStructure(notationInput.lessons, {
409
+ ...gateHeadingStructure(chapterInputs, {
380
410
  levels: rules.isEnabled("S6"),
381
411
  adjacency: rules.isEnabled("S7")
382
412
  }),
383
413
  // The course's own rules (`courseRules`): one pass for every enabled `X`
384
414
  // rule that carries a `forbid` pattern. A disabled one, and a guide rule
385
415
  // with no pattern, do no work here.
386
- ...gateCourseRules(withPages.lessons, rules.course),
416
+ // A course rule about one edition is checked on that edition's pages.
417
+ ...gateCourseRules(
418
+ withPages.lessons.filter((lesson) => isChapter(lesson.lessonId) && !deckPageIds.has(lesson.lessonId)),
419
+ rules.course.filter((rule) => rule.editions !== "deck")
420
+ ),
421
+ ...gateCourseRules(
422
+ withPages.lessons.filter((lesson) => !isChapter(lesson.lessonId) || deckPageIds.has(lesson.lessonId)),
423
+ rules.course.filter((rule) => rule.editions !== "chapter")
424
+ ),
387
425
  // S10 is locked: a float on a non-chapter page has no number to render.
388
426
  ...gateNonChapterPages(nonChapterPages)
389
427
  ];
@@ -472,6 +510,11 @@ async function compileManifest(courseConfig) {
472
510
  message: `course.settings.json \`parts\` lists "${part.dir}" (${JSON.stringify(part.label)}), but content/docs/${part.dir}/ has no chapter and no overview page yet, so the part is left out of the sidebar and the numbering until it has one.`
473
511
  });
474
512
  }
513
+ const configuredDirs = new Set(configuredParts.map((part) => part.dir));
514
+ const partDeckFiles = deckFiles.filter(
515
+ (deck) => deck.scope === "part" && configuredDirs.has(deck.partDir)
516
+ );
517
+ const partChapterNames = (dir) => lessonSlugs.filter((slug) => slug.startsWith(`${dir}/`) && isChapter(lessonIdFromSlug(slug))).map((slug) => slug.slice(dir.length + 1));
475
518
  const partDirs = new Set(listedParts.map((part) => part.dir));
476
519
  for (const lesson of notationInput.lessons) {
477
520
  const dir = lesson.lessonId.split(".")[0] ?? "";
@@ -493,16 +536,6 @@ async function compileManifest(courseConfig) {
493
536
  )
494
537
  );
495
538
  }
496
- const waiverDiagnostics = [];
497
- const waiverRules = { courseRules: rules.course };
498
- const waivers = withPages.lessons.flatMap((lesson) => {
499
- const collected = collectWaivers(lesson, waiverRules);
500
- waiverDiagnostics.push(...collected.diagnostics);
501
- return collected.waivers;
502
- });
503
- const waived = applyWaivers(mathIssues, waivers, waiverRules);
504
- mathIssues.splice(0, mathIssues.length, ...waived.kept);
505
- waiverDiagnostics.push(...waived.unused);
506
539
  const sidebar = buildSidebar(
507
540
  catalog.tracks.map((track) => ({ id: track.id, lessons: track.lessons })),
508
541
  lessonSlugs,
@@ -513,7 +546,31 @@ async function compileManifest(courseConfig) {
513
546
  {
514
547
  floatIndex: [".mdx", ".md"].some(
515
548
  (extension) => existsSync(join(CONTENT_ROOT, "docs", `figures-and-tables${extension}`))
516
- )
549
+ ),
550
+ // A deck beside its chapter is listed right under it.
551
+ decks: new Map(
552
+ lessonDeckFiles.filter((deck) => deck.chapterPath !== void 0 && deck.inPart).map((deck) => [deck.lessonSlug, deck.route])
553
+ ),
554
+ // A deck with no chapter is the lesson: its row carries the deck's glyph.
555
+ slidesFirst: new Set(
556
+ lessonDeckFiles.filter((deck) => deck.chapterPath === void 0 && deck.inPart).map((deck) => deck.lessonSlug)
557
+ ),
558
+ // A part's deck follows the part's Overview; a chapter it covers that
559
+ // has no deck of its own links into it, at its section's divider.
560
+ partDecks: new Map(
561
+ partDeckFiles.map((deck) => {
562
+ const { covered } = coveredSections(readDeckSource(deck.body), new Set(partChapterNames(deck.partDir)));
563
+ return [
564
+ deck.partDir,
565
+ {
566
+ route: deck.route,
567
+ covers: new Map(covered.map(({ name, position }) => [`${deck.partDir}/${name}`, position + 2]))
568
+ }
569
+ ];
570
+ })
571
+ ),
572
+ // Each edition's row names its lesson, which the sidebar's own row shows.
573
+ titleOf: (slug) => frontmatterById.get(lessonIdFromSlug(slug))?.title
517
574
  }
518
575
  );
519
576
  const bodyByLessonId = new Map(
@@ -524,7 +581,10 @@ async function compileManifest(courseConfig) {
524
581
  let partCursor = 0;
525
582
  for (const group of sidebar.slice(0, configuredParts.length)) {
526
583
  partCursor += 1;
527
- const lessonIds = group.items.filter((item) => typeof item === "string").map(lessonIdFromSlug);
584
+ const lessonIds = group.items.flatMap((item) => {
585
+ const slug = sidebarLessonSlug(item);
586
+ return slug === void 0 ? [] : [lessonIdFromSlug(slug)];
587
+ });
528
588
  if (lessonIds.length === 0) continue;
529
589
  const used = {
530
590
  equations: 0,
@@ -536,7 +596,7 @@ async function compileManifest(courseConfig) {
536
596
  };
537
597
  let chapterCursor = 0;
538
598
  for (const lessonId of lessonIds) {
539
- const body = bodyByLessonId.get(lessonId) ?? "";
599
+ const body = isChapter(lessonId) ? bodyByLessonId.get(lessonId) ?? "" : "";
540
600
  const chapter = ++chapterCursor;
541
601
  placementByLessonId.set(lessonId, {
542
602
  part: partCursor,
@@ -565,7 +625,8 @@ async function compileManifest(courseConfig) {
565
625
  return [
566
626
  lesson.lessonId,
567
627
  scanSectionHeadings(lesson.body, {
568
- ...placement ? { prefix: `${placement.part}.${placement.chapter}` } : {},
628
+ // A deck's headings are sections and frames, numbered as frames.
629
+ ...placement && isChapter(lesson.lessonId) ? { prefix: `${placement.part}.${placement.chapter}` } : {},
569
630
  depth: numberingSettings.sections.depth,
570
631
  labelFor: (key) => termLabel(lesson.lessonId, key)
571
632
  })
@@ -615,7 +676,7 @@ async function compileManifest(courseConfig) {
615
676
  } : {}
616
677
  };
617
678
  });
618
- const floatInputs = (kind) => notationInput.lessons.map((lesson) => {
679
+ const floatInputs = (kind) => chapterInputs.map((lesson) => {
619
680
  const placement = placementByLessonId.get(lesson.lessonId);
620
681
  return {
621
682
  lessonId: lesson.lessonId,
@@ -725,6 +786,7 @@ async function compileManifest(courseConfig) {
725
786
  )
726
787
  );
727
788
  const titledBlocksFor = (lessonId) => {
789
+ if (!isChapter(lessonId)) return [];
728
790
  const slug = lessonId.replace(/\./g, "/");
729
791
  const body = bodyByLessonId.get(lessonId) ?? "";
730
792
  const headings = headingsByLessonId.get(lessonId) ?? [];
@@ -749,7 +811,7 @@ async function compileManifest(courseConfig) {
749
811
  );
750
812
  };
751
813
  const labKatexDiagnostics = validateLabKatexImports(
752
- notationInput.lessons.map((lesson) => ({
814
+ [...notationInput.lessons, ...deckPages.map((page) => page.notation)].map((lesson) => ({
753
815
  lessonId: lesson.lessonId,
754
816
  file: lesson.source.file,
755
817
  body: lesson.body
@@ -757,13 +819,112 @@ async function compileManifest(courseConfig) {
757
819
  REPOSITORY_ROOT
758
820
  );
759
821
  const rawFloatDiagnostics = validateRawFloats(
760
- notationInput.lessons.map((lesson) => ({
822
+ [...notationInput.lessons, ...deckPages.map((page) => page.notation)].map((lesson) => ({
761
823
  lessonId: lesson.lessonId,
762
824
  slug: lesson.lessonId.replace(/\./g, "/"),
763
825
  file: lesson.source.file,
764
826
  body: lesson.body
765
827
  }))
766
828
  );
829
+ const pageInputById = new Map(withPages.lessons.map((lesson) => [lesson.lessonId, lesson]));
830
+ const chapterBodyById = new Map(chapterInputs.map((lesson) => [lesson.lessonId, lesson.body]));
831
+ const floatNumbers = (floats) => new Map((floats ?? []).map((float) => [float.key, float.number]));
832
+ const outcomeById = new Map(
833
+ catalog.competencies.flatMap((competency) => {
834
+ const outcome = competency.outcome;
835
+ return typeof outcome === "string" ? [[competency.id, outcome]] : [];
836
+ })
837
+ );
838
+ const chapterFloatsOf = (lessonId) => {
839
+ const slug = lessonId.replace(/\./g, "/");
840
+ const chapterBody = chapterBodyById.get(lessonId);
841
+ const latex = chapterBody === void 0 ? /* @__PURE__ */ new Map() : chapterEquationLatex(chapterBody);
842
+ return {
843
+ equations: new Map(
844
+ (equationsBySlug.get(slug) ?? []).map((equation) => [
845
+ equation.key,
846
+ { number: equation.number, ...latex.has(equation.key) ? { latex: latex.get(equation.key) } : {} }
847
+ ])
848
+ ),
849
+ figures: floatNumbers(figuresBySlug.get(slug)),
850
+ tables: floatNumbers(tablesBySlug.get(slug)),
851
+ diagrams: floatNumbers(diagramsBySlug.get(slug))
852
+ };
853
+ };
854
+ const outcomesOf = (lessonId) => (lessonMetaById.get(lessonId)?.teaches ?? []).flatMap((id) => {
855
+ const outcome = outcomeById.get(id);
856
+ return outcome ? [outcome] : [];
857
+ });
858
+ const deckParts = new Map(
859
+ partDeckFiles.map((deck) => {
860
+ const part = manifestParts.find((entry) => entry.dir === deck.partDir);
861
+ const lessonIds = part?.lessons ?? [];
862
+ const nameOf = (lessonId) => lessonId.replace(/\./g, "/").slice(deck.partDir.length + 1);
863
+ return [
864
+ deck.partDir,
865
+ {
866
+ label: part?.label ?? deck.partDir,
867
+ chapters: lessonIds.filter(isChapter).map((lessonId) => ({
868
+ name: nameOf(lessonId),
869
+ slug: lessonId.replace(/\./g, "/"),
870
+ teaches: lessonMetaById.get(lessonId)?.teaches ?? [],
871
+ outcomes: outcomesOf(lessonId),
872
+ floats: chapterFloatsOf(lessonId)
873
+ })),
874
+ slidesFirst: lessonIds.filter((lessonId) => !isChapter(lessonId)).map(nameOf)
875
+ }
876
+ ];
877
+ })
878
+ );
879
+ const deckModel = buildDecks({
880
+ decks: deckFiles,
881
+ settings: deckSettings,
882
+ rules: {
883
+ maxWords: rules.number("D2", "maxWords"),
884
+ maxBullets: rules.number("D2", "maxBullets"),
885
+ maxTitleLength: rules.number("D5", "maxLength"),
886
+ maxSteps: rules.number("D6", "maxSteps"),
887
+ maxNewSymbols: rules.number("D9", "maxNewSymbols")
888
+ },
889
+ lessons: new Map(
890
+ lessonDeckFiles.map((deck) => [
891
+ deck.lessonId,
892
+ {
893
+ title: frontmatterById.get(deck.lessonId)?.title ?? deck.lessonId,
894
+ teaches: lessonMetaById.get(deck.lessonId)?.teaches ?? [],
895
+ outcomes: outcomesOf(deck.lessonId),
896
+ ...deck.chapterPath === void 0 ? {} : { chapterFloats: chapterFloatsOf(deck.lessonId) }
897
+ }
898
+ ])
899
+ ),
900
+ parts: deckParts,
901
+ // As the title frame shows them (`titleFrameNode` in `deck-remark.mjs`).
902
+ course: {
903
+ name: courseConfig.about.materialName ?? courseConfig.title,
904
+ authors: courseConfig.about.authors.map((author) => author.name)
905
+ },
906
+ scopeOf: (pageId) => {
907
+ const page = pageInputById.get(pageId);
908
+ return page === void 0 ? [] : pageScope(page, cardsByKey);
909
+ },
910
+ suspendedOf: (pageId) => {
911
+ const page = pageInputById.get(pageId);
912
+ return page === void 0 ? [] : suspendedOperators(page);
913
+ },
914
+ labelFor: termLabel
915
+ });
916
+ mathIssues.push(...deckModel.diagnostics);
917
+ const sidebarWithFrames = withPartDeckFrames(sidebar, deckModel.decks);
918
+ const waiverDiagnostics = [];
919
+ const waiverRules = { courseRules: rules.course };
920
+ const waivers = withPages.lessons.flatMap((lesson) => {
921
+ const collected = collectWaivers(lesson, waiverRules);
922
+ waiverDiagnostics.push(...collected.diagnostics);
923
+ return collected.waivers;
924
+ });
925
+ const waived = applyWaivers(mathIssues, waivers, waiverRules);
926
+ mathIssues.splice(0, mathIssues.length, ...waived.kept);
927
+ waiverDiagnostics.push(...waived.unused);
767
928
  const lessons = [];
768
929
  for (const notationLesson of notationInput.lessons) {
769
930
  const id = notationLesson.lessonId;
@@ -777,6 +938,10 @@ async function compileManifest(courseConfig) {
777
938
  lessons.push({
778
939
  id,
779
940
  slug: id.replace(/\./g, "/"),
941
+ editions: [
942
+ ...isChapter(id) ? ["chapter"] : [],
943
+ ...lessonDeckFiles.some((deck) => deck.lessonId === id) ? ["deck"] : []
944
+ ],
780
945
  // 0 = the lesson is not placed in any configured part; a curriculum
781
946
  // diagnostic covers that separately, and both numbering paths then fall
782
947
  // back to the legacy `##`-section scheme.
@@ -876,6 +1041,107 @@ async function compileManifest(courseConfig) {
876
1041
  declaredVersion: courseConfig.about.version,
877
1042
  file: relative(REPOSITORY_ROOT, knowledgeChangelogPath).replace(/\\/g, "/")
878
1043
  });
1044
+ const trackPageVersions = !rules.disabled.includes("C5");
1045
+ const previousVersions = trackPageVersions ? readPageVersions(join(CONTENT_ROOT, "versions.json"), PAGE_VERSIONS_FILE) : { record: void 0, diagnostics: [] };
1046
+ const digestOf = (file) => hashes.content[file] ?? "missing";
1047
+ const presentFiles = (files) => files.filter((file) => hashes.content[file] !== void 0);
1048
+ const sourceFileById = /* @__PURE__ */ new Map();
1049
+ for (const record of contentRecords) {
1050
+ const file = record.file.replace(/\\/g, "/");
1051
+ if (file.startsWith("content/sources/") && typeof record.data.id === "string") {
1052
+ sourceFileById.set(record.data.id, file);
1053
+ }
1054
+ }
1055
+ const sharedEntryInputs = (definitionIds) => definitionIds.flatMap((definitionId) => {
1056
+ const definition = definitionsById.get(definitionId);
1057
+ return definition?.kind === "shared" ? [{ label: `notation ${definition.source.file}`, digest: digestOf(definition.source.file) }] : [];
1058
+ });
1059
+ const recalledFloatFacts = (recalledFrom) => Object.fromEntries(
1060
+ Object.entries(recalledFrom).map(([ref, chapter]) => {
1061
+ const [, prefix, key] = /^(eq|fig|tab|dia)-(.+)$/u.exec(ref) ?? [];
1062
+ const floats = prefix === "eq" ? equationsBySlug : prefix === "fig" ? figuresBySlug : prefix === "tab" ? tablesBySlug : diagramsBySlug;
1063
+ const float = floats.get(chapter)?.find((entry) => entry.key === key);
1064
+ const latex = prefix === "eq" ? chapterFloatsOf(chapter.replace(/\//g, ".")).equations.get(key ?? "")?.latex : void 0;
1065
+ return [ref, { chapter, ...float, ...latex === void 0 ? {} : { latex } }];
1066
+ })
1067
+ );
1068
+ const trackedPages = [
1069
+ ...lessons.map((lesson) => ({
1070
+ slug: lesson.slug,
1071
+ source: pageSourceDigest(
1072
+ [
1073
+ { label: `page ${lesson.file}`, digest: digestOf(lesson.file) },
1074
+ ...presentFiles([lesson.file.replace(/\.mdx?$/u, ".checks.yml")]).map((file) => ({
1075
+ label: `checks ${file}`,
1076
+ digest: digestOf(file)
1077
+ })),
1078
+ ...presentFiles(lesson.assessments.map((id) => `content/assessments/${id}.json`)).map((file) => ({
1079
+ label: `assessment ${file}`,
1080
+ digest: digestOf(file)
1081
+ })),
1082
+ ...sharedEntryInputs(lesson.notation.definitionIds),
1083
+ ...lesson.sources.map((id) => {
1084
+ const file = sourceFileById.get(id);
1085
+ return { label: `source ${id}`, digest: file === void 0 ? "missing" : digestOf(file) };
1086
+ })
1087
+ ],
1088
+ { part: lesson.part, chapter: lesson.chapter, numbering: lesson.numbering }
1089
+ )
1090
+ })),
1091
+ // A deck beside its chapter has a version of its own, by its route, and
1092
+ // so has a part's deck; a slides-first lesson's deck is the lesson, above.
1093
+ ...deckModel.decks.flatMap((deck) => {
1094
+ if (deck.chapter === void 0 && deck.part === void 0) return [];
1095
+ const page = deckPages.find((entry) => entry.deck.route === deck.slug);
1096
+ return [
1097
+ {
1098
+ slug: deck.slug,
1099
+ source: pageSourceDigest(
1100
+ [
1101
+ { label: `page ${deck.file}`, digest: digestOf(deck.file) },
1102
+ ...sharedEntryInputs(bundlesByLesson.get(deck.pageId)?.definitionIds ?? []),
1103
+ ...(page === void 0 ? [] : deriveSources(page.notation.body)).map((id) => {
1104
+ const file = sourceFileById.get(id);
1105
+ return { label: `source ${id}`, digest: file === void 0 ? "missing" : digestOf(file) };
1106
+ })
1107
+ ],
1108
+ // A part's deck shows each float it recalls as its chapter
1109
+ // numbers and captions it: those count as the page's too.
1110
+ deck.recalledFrom === void 0 ? {} : { recalls: recalledFloatFacts(deck.recalledFrom) }
1111
+ )
1112
+ }
1113
+ ];
1114
+ }),
1115
+ ...manifestParts.flatMap((part) => {
1116
+ if (!part.overview) return [];
1117
+ const slug = `${part.dir}/index`;
1118
+ const page = pages.find((entry) => entry.slug === slug);
1119
+ return [
1120
+ {
1121
+ slug,
1122
+ source: pageSourceDigest([
1123
+ { label: `page ${part.overview.file}`, digest: digestOf(part.overview.file) },
1124
+ ...sharedEntryInputs(page?.notation.definitionIds ?? [])
1125
+ ])
1126
+ }
1127
+ ];
1128
+ })
1129
+ ];
1130
+ const pageVersionsRecord = nextPageVersions({
1131
+ previous: previousVersions.record,
1132
+ version: knowledge.version,
1133
+ unreleased: knowledge.unreleased.length > 0,
1134
+ pages: trackedPages
1135
+ });
1136
+ const pageVersions = {
1137
+ enabled: trackPageVersions,
1138
+ file: PAGE_VERSIONS_FILE,
1139
+ record: pageVersionsRecord
1140
+ };
1141
+ const sinceOfPage = (slug) => {
1142
+ const entry = pageVersionsRecord.pages[slug];
1143
+ return trackPageVersions && entry !== void 0 ? sinceOf(entry) : knowledge.version;
1144
+ };
879
1145
  const enginePkg = readEnginePackage();
880
1146
  const about = {
881
1147
  engine: {
@@ -891,6 +1157,11 @@ async function compileManifest(courseConfig) {
891
1157
  version: knowledge.version,
892
1158
  ...knowledge.latest?.date === void 0 ? {} : { date: knowledge.latest.date },
893
1159
  notes: knowledge.latest?.notes ?? [],
1160
+ releases: knowledge.entries.map((entry) => ({
1161
+ version: entry.version,
1162
+ ...entry.date === void 0 ? {} : { date: entry.date }
1163
+ })),
1164
+ unreleased: knowledge.unreleased.length > 0,
894
1165
  contentTree: hashes.contentTree
895
1166
  },
896
1167
  authors: courseConfig.about.authors.map((author) => ({
@@ -900,6 +1171,32 @@ async function compileManifest(courseConfig) {
900
1171
  disclaimer: courseConfig.about.disclaimer,
901
1172
  ...courseConfig.about.changelogHref === void 0 ? {} : { changelogHref: courseConfig.about.changelogHref }
902
1173
  };
1174
+ const lessonsWithSince = lessons.map((lesson) => ({ ...lesson, since: sinceOfPage(lesson.slug) }));
1175
+ const decksWithSince = deckModel.decks.map((deck) => {
1176
+ const page = pageInputById.get(deck.pageId);
1177
+ return {
1178
+ ...deck,
1179
+ since: sinceOfPage(deck.slug),
1180
+ notation: lessonNotationBundle(
1181
+ bundlesByLesson.get(deck.pageId),
1182
+ definitionsById,
1183
+ page === void 0 ? [] : lessonNotationResolution(page, definitionsById, availableDefinitionIds),
1184
+ cardsByKey,
1185
+ // A deck shows its lessons' symbols: the derived cards (rule N23)
1186
+ // its chapters' bodies and checks use ship with it, as its bundle
1187
+ // holds the chapters' entries.
1188
+ [
1189
+ ...indexGate.derivedByLesson.get(deck.pageId) ?? [],
1190
+ ...(page?.scopeLessonIds ?? []).flatMap((lessonId) => [
1191
+ ...indexGate.derivedByLesson.get(lessonId) ?? [],
1192
+ ...assessmentDerived.get(lessonId)?.values() ?? []
1193
+ ])
1194
+ ].filter((use, index, all) => all.findIndex((other) => other.key === use.key) === index)
1195
+ )
1196
+ };
1197
+ });
1198
+ const chapterSince = new Map(lessonsWithSince.map((lesson) => [lesson.slug, lesson.since]));
1199
+ mathIssues.push(...deckStaleDiagnostics(decksWithSince, chapterSince));
903
1200
  return {
904
1201
  schemaVersion: MANIFEST_SCHEMA_VERSION,
905
1202
  lessonExports: normalizeLessonExports(courseConfig),
@@ -907,6 +1204,12 @@ async function compileManifest(courseConfig) {
907
1204
  numbering: numberingSettings,
908
1205
  blockLabels,
909
1206
  cards: normalizeCards(courseConfig),
1207
+ sidebarMarks: normalizeSidebarMarks(courseConfig),
1208
+ sidebarEditions: normalizeSidebarEditions(courseConfig),
1209
+ readingMemory: normalizeReadingMemory(courseConfig),
1210
+ appearance: normalizeAppearance(courseConfig),
1211
+ deck: deckSettings,
1212
+ decks: decksWithSince,
910
1213
  about,
911
1214
  notation: {
912
1215
  definitions: registry.definitions,
@@ -919,9 +1222,12 @@ async function compileManifest(courseConfig) {
919
1222
  sources,
920
1223
  tracks,
921
1224
  abbreviations,
922
- sidebar,
923
- parts: manifestParts,
924
- lessons,
1225
+ sidebar: sidebarWithFrames,
1226
+ parts: manifestParts.map(
1227
+ ({ overview, ...part }) => overview ? { ...part, overview: { ...overview, since: sinceOfPage(`${part.dir}/index`) } } : part
1228
+ ),
1229
+ lessons: lessonsWithSince,
1230
+ pageVersions,
925
1231
  pages,
926
1232
  prereqEdges,
927
1233
  equations: {
@@ -963,7 +1269,7 @@ async function compileManifest(courseConfig) {
963
1269
  checks: dropDisabled(checkValidation.diagnostics, off),
964
1270
  examples: dropDisabled(exampleValidation.diagnostics, off),
965
1271
  folds: dropDisabled(foldValidation.diagnostics, off),
966
- knowledge: dropDisabled(knowledge.diagnostics, off),
1272
+ knowledge: dropDisabled([...knowledge.diagnostics, ...previousVersions.diagnostics], off),
967
1273
  rawFloats: dropDisabled(rawFloatDiagnostics, off),
968
1274
  config: rules.diagnostics,
969
1275
  waivers: waiverDiagnostics