explico 0.13.0 → 0.14.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 (239) hide show
  1. package/CHANGELOG.md +894 -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 +43 -0
  6. package/dist/compiler/cli-main.js +392 -0
  7. package/dist/compiler/cli.d.ts +424 -9
  8. package/dist/compiler/cli.js +1289 -8
  9. package/dist/compiler/collections.d.ts +88 -1
  10. package/dist/compiler/collections.js +170 -25
  11. package/dist/compiler/course-config.d.ts +1 -1
  12. package/dist/compiler/course-config.js +105 -1
  13. package/dist/compiler/decks.d.ts +161 -0
  14. package/dist/compiler/decks.js +235 -0
  15. package/dist/compiler/exports/combined-html.d.ts +5 -1
  16. package/dist/compiler/exports/deck-fit.d.ts +55 -0
  17. package/dist/compiler/exports/deck-fit.js +90 -0
  18. package/dist/compiler/exports/export-chapters.d.ts +37 -6
  19. package/dist/compiler/exports/export-chapters.js +64 -5
  20. package/dist/compiler/exports/export-pages.d.ts +30 -3
  21. package/dist/compiler/exports/export-pages.js +24 -1
  22. package/dist/compiler/exports/latex/beamerthemeexplico.sty +124 -0
  23. package/dist/compiler/exports/latex/cache.d.ts +78 -0
  24. package/dist/compiler/exports/latex/cache.js +129 -0
  25. package/dist/compiler/exports/latex/capture.d.ts +10 -1
  26. package/dist/compiler/exports/latex/capture.js +3 -1
  27. package/dist/compiler/exports/latex/compile.d.ts +13 -2
  28. package/dist/compiler/exports/latex/compile.js +12 -1
  29. package/dist/compiler/exports/latex/deck.d.ts +92 -0
  30. package/dist/compiler/exports/latex/deck.js +349 -0
  31. package/dist/compiler/exports/latex/document.d.ts +51 -6
  32. package/dist/compiler/exports/latex/document.js +71 -12
  33. package/dist/compiler/exports/latex/explico-common.sty +198 -0
  34. package/dist/compiler/exports/latex/explico.cls +13 -183
  35. package/dist/compiler/exports/latex/serialize.d.ts +60 -4
  36. package/dist/compiler/exports/latex/serialize.js +4 -2
  37. package/dist/compiler/exports/latex-export.d.ts +24 -2
  38. package/dist/compiler/exports/latex-export.js +107 -26
  39. package/dist/compiler/exports/markdown/serialize.d.ts +24 -4
  40. package/dist/compiler/exports/markdown/serialize.js +85 -8
  41. package/dist/compiler/exports/markdown-export.d.ts +6 -1
  42. package/dist/compiler/exports/markdown-export.js +50 -11
  43. package/dist/compiler/exports/standalone-html.d.ts +10 -5
  44. package/dist/compiler/exports/standalone-html.js +3 -3
  45. package/dist/compiler/exports/static-tree.d.ts +38 -1
  46. package/dist/compiler/exports/static-tree.js +120 -4
  47. package/dist/compiler/knowledge-version.d.ts +26 -2
  48. package/dist/compiler/knowledge-version.js +27 -17
  49. package/dist/compiler/lesson-derivation.d.ts +143 -6
  50. package/dist/compiler/lesson-derivation.js +145 -13
  51. package/dist/compiler/manifest.d.ts +133 -4
  52. package/dist/compiler/manifest.js +359 -58
  53. package/dist/compiler/page-versions.d.ts +160 -0
  54. package/dist/compiler/page-versions.js +198 -0
  55. package/dist/compiler/render-diagnostics.d.ts +5 -1
  56. package/dist/compiler/resolution-report.d.ts +24 -6
  57. package/dist/compiler/resolution-report.js +22 -4
  58. package/dist/compiler/validate-content.d.ts +7 -0
  59. package/dist/compiler/validate-content.js +160 -0
  60. package/dist/{course-config-rRtME5UV.d.ts → course-config-BLtIdkei.d.ts} +164 -1
  61. package/dist/curriculum/competency-graph.d.ts +54 -0
  62. package/dist/curriculum/competency-graph.js +41 -0
  63. package/dist/reference/base-library.d.mts +8 -0
  64. package/dist/reference/base-library.mjs +19 -0
  65. package/dist/reference/consistency.d.ts +8 -4
  66. package/dist/reference/consistency.js +76 -26
  67. package/dist/reference/deck-remark.d.mts +123 -0
  68. package/dist/reference/deck-remark.mjs +600 -0
  69. package/dist/reference/deck-source.d.ts +107 -0
  70. package/dist/reference/deck-source.js +140 -0
  71. package/dist/reference/decks-validate.d.ts +133 -0
  72. package/dist/reference/decks-validate.js +359 -0
  73. package/dist/reference/derived-entries.d.ts +5 -1
  74. package/dist/reference/diagram-fit.d.ts +1 -1
  75. package/dist/reference/figure-symbols.d.ts +23 -6
  76. package/dist/reference/figure-symbols.js +51 -15
  77. package/dist/reference/float-name.d.ts +43 -0
  78. package/dist/reference/float-name.js +58 -0
  79. package/dist/reference/folds-validate.d.ts +1 -1
  80. package/dist/reference/gate-abbreviations.d.ts +1 -1
  81. package/dist/reference/gate-indices.js +5 -4
  82. package/dist/reference/gate-math.d.ts +29 -2
  83. package/dist/reference/gate-math.js +99 -18
  84. package/dist/reference/gate-part-pages.d.ts +1 -1
  85. package/dist/reference/lab-katex-validate.d.ts +10 -6
  86. package/dist/reference/lab-katex-validate.js +17 -9
  87. package/dist/reference/lab-math-scope.js +29 -4
  88. package/dist/reference/math-glyphs.d.mts +25 -0
  89. package/dist/reference/math-glyphs.mjs +189 -25
  90. package/dist/reference/page-notation.d.ts +22 -3
  91. package/dist/reference/page-notation.js +32 -10
  92. package/dist/reference/references.d.ts +14 -1
  93. package/dist/reference/references.js +13 -0
  94. package/dist/reference/registry-scope.d.mts +33 -0
  95. package/dist/reference/registry-scope.mjs +34 -0
  96. package/dist/reference/registry.js +29 -10
  97. package/dist/reference/rehype-deck.d.mts +6 -0
  98. package/dist/reference/rehype-deck.mjs +286 -0
  99. package/dist/reference/rehype-heading-anchors.d.mts +20 -0
  100. package/dist/reference/rehype-heading-anchors.mjs +48 -6
  101. package/dist/reference/remark-notation.mjs +249 -40
  102. package/dist/reference/source-lines.d.mts +12 -0
  103. package/dist/reference/source-lines.mjs +65 -0
  104. package/dist/reference/types.d.ts +28 -0
  105. package/dist/rules/catalog.d.ts +21 -4
  106. package/dist/rules/catalog.js +221 -2
  107. package/dist/rules/course-rules.d.ts +5 -1
  108. package/dist/rules/course-rules.js +17 -3
  109. package/dist/rules/course-rules.schema.json +12 -0
  110. package/dist/rules/in-effect.d.ts +13 -3
  111. package/dist/rules/in-effect.js +30 -7
  112. package/dist/rules/rules.schema.json +259 -1
  113. package/dist/rules/settings.d.ts +1 -1
  114. package/dist/session/PreferenceStore.d.ts +6 -0
  115. package/dist/session/PreferenceStore.js +18 -1
  116. package/dist/session/deck-memory.d.ts +116 -0
  117. package/dist/session/deck-memory.js +143 -0
  118. package/dist/session/reading-memory.d.ts +96 -0
  119. package/dist/session/reading-memory.js +133 -0
  120. package/docs/VERSIONING.md +21 -18
  121. package/docs/authoring/README.md +29 -9
  122. package/docs/authoring/agents.md +63 -16
  123. package/docs/authoring/components.md +15 -0
  124. package/docs/authoring/deck.md +588 -0
  125. package/docs/authoring/migrating.md +1087 -14
  126. package/docs/authoring/notation.md +49 -6
  127. package/docs/authoring/structure.md +80 -1
  128. package/docs/authoring/versioning.md +54 -2
  129. package/docs/authoring/visualizations.md +7 -2
  130. package/docs/course-agent/AGENTS.md +37 -0
  131. package/docs/course-agent/agents/rules-reviewer.md +33 -0
  132. package/docs/course-agent/skills/draft-lesson/SKILL.md +37 -0
  133. package/docs/course-agent/skills/migrate-course/SKILL.md +31 -0
  134. package/docs/course-agent/skills/release-content/SKILL.md +24 -0
  135. package/docs/diagram-scaling.md +148 -0
  136. package/docs/privacy.md +59 -0
  137. package/package.json +8 -2
  138. package/src/compiler/astro.ts +60 -21
  139. package/src/compiler/cli-main.ts +469 -0
  140. package/src/compiler/cli.ts +1905 -16
  141. package/src/compiler/collections.ts +284 -25
  142. package/src/compiler/course-config.ts +267 -0
  143. package/src/compiler/decks.ts +455 -0
  144. package/src/compiler/exports/deck-fit.ts +127 -0
  145. package/src/compiler/exports/export-chapters.ts +105 -6
  146. package/src/compiler/exports/export-pages.ts +46 -2
  147. package/src/compiler/exports/latex/beamerthemeexplico.sty +124 -0
  148. package/src/compiler/exports/latex/cache.ts +183 -0
  149. package/src/compiler/exports/latex/capture.ts +5 -2
  150. package/src/compiler/exports/latex/compile.ts +18 -0
  151. package/src/compiler/exports/latex/deck.ts +419 -0
  152. package/src/compiler/exports/latex/document.ts +117 -21
  153. package/src/compiler/exports/latex/explico-common.sty +198 -0
  154. package/src/compiler/exports/latex/explico.cls +13 -183
  155. package/src/compiler/exports/latex/serialize.ts +25 -21
  156. package/src/compiler/exports/latex/texlive-packages.txt +6 -2
  157. package/src/compiler/exports/latex-export.ts +187 -45
  158. package/src/compiler/exports/markdown/serialize.ts +118 -9
  159. package/src/compiler/exports/markdown-export.ts +56 -11
  160. package/src/compiler/exports/standalone-html.ts +8 -7
  161. package/src/compiler/exports/static-tree.ts +175 -3
  162. package/src/compiler/knowledge-version.ts +56 -21
  163. package/src/compiler/lesson-derivation.ts +282 -16
  164. package/src/compiler/manifest.ts +558 -63
  165. package/src/compiler/page-versions.ts +359 -0
  166. package/src/compiler/resolution-report.ts +38 -7
  167. package/src/compiler/validate-content.ts +212 -0
  168. package/src/components/CourseContents.astro +124 -6
  169. package/src/components/CurriculumMap.astro +49 -1
  170. package/src/components/FloatIndex.astro +1 -1
  171. package/src/components/PartOverview.astro +51 -5
  172. package/src/components/assessments/AssessmentSet.astro +41 -15
  173. package/src/components/block-heading.ts +15 -6
  174. package/src/components/card-carry.ts +7 -4
  175. package/src/components/card-manager.ts +14 -2
  176. package/src/components/card-memory.ts +13 -9
  177. package/src/components/deck/DeckEnhancer.astro +13 -0
  178. package/src/components/deck/deck-context.ts +104 -0
  179. package/src/components/deck/deck-controller.ts +1287 -0
  180. package/src/components/deck/deck-map.ts +91 -0
  181. package/src/components/deck-lane.ts +163 -0
  182. package/src/components/diagram/Diagram.astro +131 -86
  183. package/src/components/disclosure/persisted-disclosure.ts +9 -1
  184. package/src/components/figure/Figure.astro +7 -2
  185. package/src/components/figure/FloatCaption.astro +5 -3
  186. package/src/components/fold/Fold.astro +2 -2
  187. package/src/components/notation/NotationFormula.astro +81 -11
  188. package/src/components/notation/NotationGlossary.astro +132 -1
  189. package/src/components/notation/NotationLayer.astro +29 -0
  190. package/src/components/starlight/AboutPanel.astro +186 -3
  191. package/src/components/starlight/CollapsibleSections.astro +24 -3
  192. package/src/components/starlight/DiagramEnhancer.astro +5 -1
  193. package/src/components/starlight/HeadingAnchorEnhancer.astro +1 -1
  194. package/src/components/starlight/LayoutHeader.astro +17 -0
  195. package/src/components/starlight/LayoutPageTitle.astro +110 -0
  196. package/src/components/starlight/LayoutSidebar.astro +807 -21
  197. package/src/components/starlight/LayoutSocialIcons.astro +62 -6
  198. package/src/components/starlight/LessonExportActions.astro +27 -9
  199. package/src/components/starlight/LessonFooter.astro +59 -0
  200. package/src/components/starlight/ReadingMemoryNotice.astro +114 -0
  201. package/src/components/starlight/TextSizeControl.astro +24 -13
  202. package/src/components/starlight/page-marks.ts +139 -0
  203. package/src/components/starlight/section-summary.ts +1 -1
  204. package/src/components/starlight/sidebar-editions.ts +245 -0
  205. package/src/components/table/Table.astro +15 -7
  206. package/src/content-config.ts +87 -1
  207. package/src/curriculum/competency-graph.ts +95 -0
  208. package/src/reference/base-library.mjs +19 -0
  209. package/src/reference/consistency.ts +126 -39
  210. package/src/reference/deck-remark.mjs +600 -0
  211. package/src/reference/deck-source.ts +273 -0
  212. package/src/reference/decks-validate.ts +604 -0
  213. package/src/reference/diagram-layout.ts +3 -1
  214. package/src/reference/figure-symbols.ts +108 -31
  215. package/src/reference/folds-validate.ts +1 -1
  216. package/src/reference/gate-indices.ts +5 -3
  217. package/src/reference/gate-math.ts +169 -25
  218. package/src/reference/lab-katex-validate.ts +30 -15
  219. package/src/reference/lab-math-scope.ts +34 -3
  220. package/src/reference/math-glyphs.mjs +189 -25
  221. package/src/reference/page-notation.ts +69 -15
  222. package/src/reference/references.ts +28 -0
  223. package/src/reference/registry-scope.mjs +34 -0
  224. package/src/reference/registry.ts +41 -11
  225. package/src/reference/rehype-deck.mjs +286 -0
  226. package/src/reference/rehype-heading-anchors.mjs +48 -6
  227. package/src/reference/remark-notation.mjs +249 -40
  228. package/src/reference/source-lines.mjs +65 -0
  229. package/src/reference/types.ts +28 -0
  230. package/src/rules/catalog.ts +257 -4
  231. package/src/rules/course-rules.ts +21 -1
  232. package/src/rules/in-effect.ts +54 -8
  233. package/src/session/PreferenceStore.ts +23 -0
  234. package/src/session/deck-memory.ts +263 -0
  235. package/src/session/reading-memory.ts +243 -0
  236. package/src/styles/deck.css +1150 -0
  237. package/src/styles/global.css +958 -27
  238. package/src/styles/zones.css +277 -0
  239. /package/src/{components → reference}/float-name.ts +0 -0
@@ -6,6 +6,7 @@ import { fileURLToPath } from "node:url";
6
6
  import { zip } from "./zip.js";
7
7
  import {
8
8
  consoleLogger,
9
+ deckBlocks,
9
10
  epochOf,
10
11
  figuresOf,
11
12
  imageFile,
@@ -14,57 +15,79 @@ import {
14
15
  readBuiltPage,
15
16
  strictMode
16
17
  } from "./export-chapters.js";
18
+ import { latexCacheFromEnvironment } from "./latex/cache.js";
17
19
  import { compileLatex, missingCharacters, findLatexEngine } from "./latex/compile.js";
18
- import { bookSource, chapterFolderName, chapterSource } from "./latex/document.js";
19
- import { courseFileStem } from "./export-pages.js";
20
+ import { deckNumberingProblems, deckOverflows, deckSource } from "./latex/deck.js";
21
+ import {
22
+ bookSince,
23
+ bookSource,
24
+ chapterFolderName,
25
+ chapterSource,
26
+ deckCheckItems
27
+ } from "./latex/document.js";
28
+ import { courseFileStem, deckFileStem, deckOwnerSlug, deckSaveName } from "./export-pages.js";
20
29
  import { auxLabels, numberingMismatches, websiteNumbers } from "./latex/numbering.js";
21
- async function pagePictures(outputRoot, base, page, captured) {
30
+ async function pagePictures(outputRoot, base, page, captures, drawnOn = () => page.route, folder = `figures/${page.route}`) {
22
31
  const pictures = /* @__PURE__ */ new Map();
23
32
  for (const figure of figuresOf(page.blocks)) {
24
- const capturedFigure = captured?.get(figure.anchor);
33
+ const capturedFigure = captures.get(drawnOn(figure.anchor))?.get(figure.anchor);
25
34
  if (capturedFigure) {
26
- pictures.set(figure.anchor, {
27
- path: `figures/${page.route}/${figure.anchor}.pdf`,
28
- content: capturedFigure.pdf
29
- });
35
+ pictures.set(figure.anchor, { path: `${folder}/${figure.anchor}.pdf`, content: capturedFigure.pdf });
30
36
  continue;
31
37
  }
32
38
  if (figure.picture.kind !== "image" || needsCapture(figure)) continue;
33
39
  const file = imageFile(outputRoot, base, page.route, figure.picture.src);
34
40
  pictures.set(figure.anchor, {
35
- path: `figures/${page.route}/${figure.anchor}${extname(file).toLowerCase()}`,
41
+ path: `${folder}/${figure.anchor}${extname(file).toLowerCase()}`,
36
42
  content: await readFile(file)
37
43
  });
38
44
  }
39
45
  return pictures;
40
46
  }
41
- async function mapWithLimit(items, limit, work) {
47
+ function slidesLesson(manifest, lesson) {
48
+ const byId = new Map(manifest.competencies.map((competency) => [competency.id, competency]));
49
+ const outcomes = lesson.teaches.flatMap((id) => {
50
+ const outcome = byId.get(id)?.outcome;
51
+ return typeof outcome === "string" && outcome.trim() !== "" ? [outcome.trim()] : [];
52
+ });
53
+ const base = manifest.lessonExports.canonicalBaseUrl;
54
+ return { lesson, outcomes, ...base ? { slidesUrl: `${base}${lesson.slug}/` } : {} };
55
+ }
56
+ async function mapWithLimit(items, limit, work, order = items.map((_item, index) => index)) {
42
57
  const results = new Array(items.length);
43
58
  let next = 0;
44
59
  const worker = async () => {
45
- while (next < items.length) {
46
- const index = next++;
60
+ while (next < order.length) {
61
+ const index = order[next++];
47
62
  results[index] = await work(items[index]);
48
63
  }
49
64
  };
50
- await Promise.all(Array.from({ length: Math.max(1, Math.min(limit, items.length)) }, worker));
65
+ await Promise.all(Array.from({ length: Math.max(1, Math.min(limit, order.length)) }, worker));
51
66
  return results;
52
67
  }
68
+ function startOrder(jobs) {
69
+ const rank = { course: 0, part: 1, chapter: 2, deck: 2 };
70
+ const size = jobs.map(
71
+ ({ files }) => files.reduce((sum, file) => sum + (typeof file.content === "string" ? Buffer.byteLength(file.content) : file.content.byteLength), 0)
72
+ );
73
+ return jobs.map((_job, index) => index).sort((a, b) => rank[jobs[a].kind] - rank[jobs[b].kind] || size[b] - size[a] || a - b);
74
+ }
53
75
  async function writeLatexExports(directory, manifest, options = {}) {
54
76
  if (!manifest.lessonExports.latex) return;
55
77
  const logger = options.logger ?? consoleLogger;
56
78
  const strict = strictMode();
57
79
  const outputRoot = fileURLToPath(directory);
58
- const { base, chapters, captures } = options.prepared ?? await prepareChapters(outputRoot, manifest, { logger, projectRoot: options.projectRoot });
80
+ const { base, chapters, decks, captures } = options.prepared ?? await prepareChapters(outputRoot, manifest, { logger, projectRoot: options.projectRoot });
59
81
  const engine = await findLatexEngine();
60
82
  if ("missing" in engine) {
61
83
  if (strict) throw new Error(`LaTeX export cannot build PDFs: ${engine.missing}`);
62
84
  logger.warn(`LaTeX export: writing the source zips only. ${engine.missing}`);
63
85
  }
64
86
  const jobs = [];
87
+ const againstWebsite = (website) => (aux) => numberingMismatches(website, auxLabels(aux));
65
88
  const pictures = /* @__PURE__ */ new Map();
66
89
  for (const { lesson, page } of chapters) {
67
- const chapterPictures = await pagePictures(outputRoot, base, page, captures.get(page.route));
90
+ const chapterPictures = await pagePictures(outputRoot, base, page, captures);
68
91
  pictures.set(lesson.id, chapterPictures);
69
92
  jobs.push({
70
93
  kind: "chapter",
@@ -72,9 +95,52 @@ async function writeLatexExports(directory, manifest, options = {}) {
72
95
  files: chapterSource({ manifest, lesson, page, documentRoutes: /* @__PURE__ */ new Set([page.route]), pictures: chapterPictures }),
73
96
  target: lesson.slug,
74
97
  folder: chapterFolderName(lesson),
75
- website: websiteNumbers(page, `${lesson.part}.${lesson.chapter}`)
98
+ numbering: againstWebsite(websiteNumbers(page, `${lesson.part}.${lesson.chapter}`)),
99
+ since: lesson.since
76
100
  });
77
101
  }
102
+ const lessonsById = new Map(manifest.lessons.map((lesson) => [lesson.id, lesson]));
103
+ for (const { deck, page, figureRoutes } of decks) {
104
+ if (deck.part === void 0 && !lessonsById.has(deck.lessonId ?? "")) continue;
105
+ const owner = deckOwnerSlug(deck, manifest);
106
+ const deckPictures = await pagePictures(
107
+ outputRoot,
108
+ base,
109
+ { route: page.route, blocks: deckBlocks(page) },
110
+ captures,
111
+ (anchor) => figureRoutes.get(anchor) ?? page.route,
112
+ "figures"
113
+ );
114
+ const { files, frameEnds } = deckSource({ manifest, deck, page, checkItems: deckCheckItems(manifest, deck), pictures: deckPictures });
115
+ const stem = deckFileStem(owner);
116
+ const numbering = (aux) => deckNumberingProblems(aux, page.frames.length);
117
+ const common = { kind: "deck", folder: `${deckSaveName(owner)}-latex`, numbering, since: deck.since };
118
+ jobs.push({
119
+ ...common,
120
+ name: stem,
121
+ files,
122
+ target: stem,
123
+ review: (log) => {
124
+ const over = deckOverflows(log, frameEnds);
125
+ if (over.length === 0) return;
126
+ logger.warn(
127
+ `LaTeX export: on paper, ${over.length === 1 ? "a slide" : `${over.length} slides`} of ${stem} run${over.length === 1 ? "s" : ""} over the bottom: ${over.map(({ frame, points }) => `slide ${frame} by ${points} pt`).join(", ")}. The web page fits them (rule D2), but the PDF cuts them off. Shorten the slide, or split it.`
128
+ );
129
+ }
130
+ });
131
+ if (manifest.deck.handoutPdf && manifest.deck.notes !== "off") {
132
+ const handout = files.find((file) => file.path === "handout.tex");
133
+ jobs.push({
134
+ ...common,
135
+ name: `${stem}-notes`,
136
+ files: files.flatMap(
137
+ (file) => file.path === "handout.tex" ? [] : file.path === "main.tex" ? [{ path: "main.tex", content: handout.content }] : [file]
138
+ ),
139
+ target: `${stem}-notes`,
140
+ noZip: true
141
+ });
142
+ }
143
+ }
78
144
  const byId = new Map(chapters.map((chapter) => [chapter.lesson.id, chapter]));
79
145
  const pageSlugs = new Set(manifest.pages.map((page) => page.slug));
80
146
  const parts = [];
@@ -83,9 +149,13 @@ async function writeLatexExports(directory, manifest, options = {}) {
83
149
  const chapter = byId.get(id);
84
150
  return chapter ? [{ ...chapter, pictures: pictures.get(id) }] : [];
85
151
  });
152
+ const slides = part.lessons.flatMap((id) => {
153
+ const lesson = lessonsById.get(id);
154
+ return lesson && !lesson.editions.includes("chapter") ? [slidesLesson(manifest, lesson)] : [];
155
+ });
86
156
  if (partChapters.length === 0) continue;
87
157
  const overview = pageSlugs.has(`${part.dir}/index`) ? (await readBuiltPage(outputRoot, `${part.dir}/index`)).page : void 0;
88
- parts.push({ part, ...overview ? { overview } : {}, chapters: partChapters });
158
+ parts.push({ part, ...overview ? { overview } : {}, chapters: partChapters, ...slides.length > 0 ? { slides } : {} });
89
159
  }
90
160
  const bookNumbers = (bookParts) => new Map(
91
161
  bookParts.flatMap(
@@ -106,7 +176,8 @@ async function writeLatexExports(directory, manifest, options = {}) {
106
176
  }),
107
177
  target: `part/${entry.part.dir}`,
108
178
  folder: `${entry.part.dir}-latex`,
109
- website: bookNumbers([entry])
179
+ numbering: againstWebsite(bookNumbers([entry])),
180
+ since: bookSince([entry])
110
181
  });
111
182
  }
112
183
  if (parts.length > 0) {
@@ -116,33 +187,40 @@ async function writeLatexExports(directory, manifest, options = {}) {
116
187
  files: bookSource({ manifest, parts, document: "course", title: course }),
117
188
  target: "course",
118
189
  folder: `${courseFileStem(manifest)}-latex`,
119
- website: bookNumbers(parts)
190
+ numbering: againstWebsite(bookNumbers(parts)),
191
+ since: bookSince(parts)
120
192
  });
121
193
  }
122
- const pending = jobs.map((job) => ({
194
+ const pending = jobs.filter((job) => !job.noZip).map((job) => ({
123
195
  target: join(outputRoot, "downloads", `${job.target}-latex.zip`),
124
196
  content: zip(job.files.map((file) => ({ path: `${job.folder}/${file.path}`, content: file.content })))
125
197
  }));
126
198
  const durations = [];
199
+ const cache = "missing" in engine ? void 0 : await latexCacheFromEnvironment(engine, logger);
200
+ let reused = 0;
127
201
  const started = performance.now();
128
202
  if (!("missing" in engine)) {
129
203
  const compiled = await mapWithLimit(jobs, availableParallelism(), async (job) => {
130
204
  const jobStarted = performance.now();
131
- const result = await compileLatex(job.files, engine, { epoch: epochOf(manifest), name: job.name });
132
- durations.push({ kind: job.kind, seconds: (performance.now() - jobStarted) / 1e3 });
133
- const problems = numberingMismatches(job.website, auxLabels(result.aux));
205
+ const options2 = { epoch: epochOf(manifest, job.since), name: job.name };
206
+ const compile = cache ? await cache.compile(job.files, options2) : { result: await compileLatex(job.files, engine, options2), reused: false };
207
+ const { result } = compile;
208
+ if (compile.reused) reused += 1;
209
+ else durations.push({ kind: job.kind, seconds: (performance.now() - jobStarted) / 1e3 });
210
+ const problems = job.numbering(result.aux);
134
211
  if (problems.length > 0) {
135
212
  throw new Error(`LaTeX numbers ${job.name} differently from the website:
136
213
  ${problems.join("\n ")}`);
137
214
  }
138
- const missing = job.kind === "chapter" ? missingCharacters(result.log) : [];
215
+ job.review?.(result.log);
216
+ const missing = (job.kind === "chapter" || job.kind === "deck") && !job.noZip ? missingCharacters(result.log) : [];
139
217
  if (missing.length > 0) {
140
218
  logger.warn(
141
219
  `LaTeX export: the PDF of ${job.name} leaves ${missing.length} character(s) blank, which its fonts cannot draw: ${missing.slice(0, 12).join(", ")}${missing.length > 12 ? ", \u2026" : ""}. The web page shows them, the PDF does not. Under LuaLaTeX, install a font that covers them on the build machine (explico.cls looks for CMU Serif, Noto Serif or DejaVu Serif for Greek, Noto CJK or Fandol for Chinese, Japanese and Korean, and Noto Color Emoji for emoji); pdfLaTeX sets none of them. Or write the word in Latin script, or a symbol as math ($\\alpha$).`
142
220
  );
143
221
  }
144
222
  return result.pdf;
145
- });
223
+ }, startOrder(jobs));
146
224
  jobs.forEach((job, index) => {
147
225
  pending.push({ target: join(outputRoot, "downloads", `${job.target}.pdf`), content: compiled[index] });
148
226
  });
@@ -158,12 +236,15 @@ async function writeLatexExports(directory, manifest, options = {}) {
158
236
  } finally {
159
237
  await Promise.all(temporary.map((file) => rm(file.temporary, { force: true })));
160
238
  }
239
+ const pruned = cache ? await cache.prune() : 0;
161
240
  const pdfs = pending.filter((file) => file.target.endsWith(".pdf")).length;
162
241
  const seconds = (kind) => durations.filter((entry) => entry.kind === kind).reduce((sum, entry) => sum + entry.seconds, 0);
163
242
  logger.info(
164
- `LaTeX export: ${chapters.length} chapter(s), ${parts.length} part(s)${parts.length > 0 ? " and the course" : ""}: ${jobs.length} source zip(s)` + (pdfs > 0 ? `, ${pdfs} PDF(s) with ${"name" in engine ? engine.name : ""} in ${wall.toFixed(1)} s on up to ${availableParallelism()} core(s) (compile time: chapters ${seconds("chapter").toFixed(1)} s, parts ${seconds("part").toFixed(1)} s, course ${seconds("course").toFixed(1)} s)` : "") + (captures.size > 0 ? `, figures from ${captures.size} page(s) drawn as vector PDF` : "") + "."
243
+ `LaTeX export: ${chapters.length} chapter(s), ${decks.length} deck(s), ${parts.length} part(s)${parts.length > 0 ? " and the course" : ""}: ${jobs.filter((job) => !job.noZip).length} source zip(s)` + (pdfs > 0 ? `, ${pdfs} PDF(s) with ${"name" in engine ? engine.name : ""} in ${wall.toFixed(1)} s on up to ${availableParallelism()} core(s) (` + (cache ? `${pdfs - reused} compiled, ${reused} reused from ${cache.directory}${pruned > 0 ? `, ${pruned} unused entr${pruned === 1 ? "y" : "ies"} deleted` : ""}; ` : "") + `compile time: chapters ${seconds("chapter").toFixed(1)} s, decks ${seconds("deck").toFixed(1)} s, parts ${seconds("part").toFixed(1)} s, course ${seconds("course").toFixed(1)} s)` : "") + (captures.size > 0 ? `, figures from ${captures.size} page(s) drawn as vector PDF` : "") + "."
165
244
  );
166
245
  }
167
246
  export {
247
+ mapWithLimit,
248
+ startOrder,
168
249
  writeLatexExports
169
250
  };
@@ -1,6 +1,6 @@
1
1
  import { RootContent } from 'mdast';
2
2
  import { CheckItem } from '../latex/serialize.js';
3
- import { StaticPage, DiagramGeometry } from '../static-tree.js';
3
+ import { StaticPage, StaticDeck, NotationEntry, DiagramGeometry } from '../static-tree.js';
4
4
  import '../../../reference/mixed-content.js';
5
5
  import '../../../reference/gate-math.js';
6
6
  import '../../../reference/types.js';
@@ -32,10 +32,13 @@ interface MarkdownChapterContext {
32
32
  readonly pictures: ReadonlyMap<string, string>;
33
33
  /** Answers of this page's checks, by assessment id, in authored order. */
34
34
  readonly checkItems: ReadonlyMap<string, readonly CheckItem[]>;
35
- /** Course title, version and the page's live URL, for the line under its title. */
35
+ /**
36
+ * The line under the title: the course, the version the page's content is
37
+ * at (`versionPhrase`, rule C5) and its live URL.
38
+ */
36
39
  readonly source?: {
37
40
  readonly course: string;
38
- readonly version?: string;
41
+ readonly since?: string;
39
42
  readonly url?: string;
40
43
  };
41
44
  }
@@ -45,7 +48,24 @@ declare function diagramEdges(geometry: DiagramGeometry): [number, number][];
45
48
  declare function serializeMarkdownPage(context: MarkdownChapterContext, options?: {
46
49
  readonly number?: string;
47
50
  }): string;
51
+ /** The slide sizes Marp's built-in themes have. */
52
+ type MarpSize = '16:9' | '4:3';
53
+ interface MarpDeckContext extends Omit<MarkdownChapterContext, 'page'> {
54
+ readonly deck: StaticDeck;
55
+ /** The symbols its frames use, by first use: its closing notation slide. */
56
+ readonly symbols: readonly NotationEntry[];
57
+ readonly size: MarpSize;
58
+ }
59
+ /**
60
+ * A deck in Marp's Markdown (decision D10): front matter that turns Marp on,
61
+ * one slide per frame between `---` lines, numbered as the deck is, each
62
+ * frame's notes and its check's answers as an HTML comment (Marp's speaker
63
+ * notes), and a closing slide of the symbols the frames use. A step shows
64
+ * its frame's final state, as the handout does. The file reads as plain
65
+ * Markdown too; Marp, its VS Code extension and Deckset present it.
66
+ */
67
+ declare function serializeMarpDeck(context: MarpDeckContext): string;
48
68
  /** Markdown of a few blocks of mdast, written the same way (for a download's `index.md`). */
49
69
  declare function serializeMarkdownNodes(children: RootContent[]): string;
50
70
 
51
- export { type MarkdownChapterContext, diagramEdges, serializeMarkdownNodes, serializeMarkdownPage };
71
+ export { type MarkdownChapterContext, type MarpDeckContext, type MarpSize, diagramEdges, serializeMarkdownNodes, serializeMarkdownPage, serializeMarpDeck };
@@ -1,7 +1,7 @@
1
1
  import { gfmTableToMarkdown } from "mdast-util-gfm-table";
2
2
  import { mathToMarkdown } from "mdast-util-math";
3
3
  import { toMarkdown } from "mdast-util-to-markdown";
4
- import { captionTitle } from "../../../components/float-name.js";
4
+ import { captionTitle } from "../../../reference/float-name.js";
5
5
  import { stripNotationMarkers } from "../../../reference/math-glyphs.mjs";
6
6
  const text = (value) => ({ type: "text", value });
7
7
  const html = (value) => ({ type: "html", value });
@@ -428,16 +428,19 @@ class MarkdownWriter {
428
428
  }
429
429
  ];
430
430
  }
431
- answers(checks) {
431
+ /**
432
+ * The answers to `checks`: under their own heading at a page's end, or
433
+ * bare (`inline`), as a deck's speaker notes give them.
434
+ */
435
+ answers(checks, inline = false) {
432
436
  if (checks.length === 0) return [];
433
437
  return [
434
- { type: "heading", depth: 2, children: [anchor("answers"), text("Answers to the knowledge checks")] },
438
+ ...inline ? [] : [{ type: "heading", depth: 2, children: [anchor("answers"), text("Answers to the knowledge checks")] }],
435
439
  ...checks.flatMap((check) => {
436
440
  const items = this.context.checkItems.get(check.assessmentId) ?? [];
441
+ const name = text(`Knowledge check ${check.number}`);
437
442
  return [
438
- paragraph([
439
- strong([link(`#${check.anchor}`, [text(`Knowledge check ${check.number}`)]), text(": "), ...this.inlines(check.title)])
440
- ]),
443
+ paragraph([strong([inline ? name : link(`#${check.anchor}`, [name]), text(": "), ...this.inlines(check.title)])]),
441
444
  {
442
445
  type: "list",
443
446
  ordered: true,
@@ -501,7 +504,7 @@ function serializeMarkdownPage(context, options = {}) {
501
504
  const sourceLine = source ? [
502
505
  text("From "),
503
506
  emphasis([text(source.course)]),
504
- ...source.version ? [text(`, version ${source.version}`)] : [],
507
+ ...source.since ? [text(`, ${source.since}`)] : [],
505
508
  text("."),
506
509
  ...source.url ? [text(" Interactive version: "), link(source.url, [text(source.url)]), text(".")] : []
507
510
  ] : [];
@@ -517,11 +520,85 @@ function serializeMarkdownPage(context, options = {}) {
517
520
  ...writer.answers(checksOf(page.blocks))
518
521
  ]);
519
522
  }
523
+ function serializeMarpDeck(context) {
524
+ const { deck, source } = context;
525
+ const page = {
526
+ slug: deck.slug,
527
+ route: deck.route,
528
+ title: deck.title,
529
+ blocks: [],
530
+ notation: deck.notation,
531
+ notationRemarks: []
532
+ };
533
+ const writer = new MarkdownWriter({ ...context, page });
534
+ const heading = (depth, children) => ({ type: "heading", depth, children });
535
+ const lead = { type: "html", value: "<!-- _class: lead -->" };
536
+ const notes = (blocks) => blocks.length === 0 ? [] : [{ type: "html", value: `<!--
537
+ ${serializeNodes(blocks).trim().replace(/--/gu, "- -")}
538
+ -->` }];
539
+ const slides = deck.frames.map((frame) => {
540
+ const title = writer.inlines(frame.title);
541
+ if (frame.kind === "title") {
542
+ return [
543
+ lead,
544
+ heading(1, title),
545
+ ...source ? [
546
+ paragraph([
547
+ emphasis([text(source.course)]),
548
+ ...source.since ? [text(`, ${source.since}`)] : [],
549
+ text("."),
550
+ ...source.url ? [text(" Interactive version: "), link(source.url, [text(source.url)]), text(".")] : []
551
+ ])
552
+ ] : []
553
+ ];
554
+ }
555
+ if (frame.kind === "section") return [lead, heading(1, title)];
556
+ const blocks = frame.segments.flatMap((segment) => segment.blocks);
557
+ const legend = frame.legend.filter((item) => item.tex);
558
+ return [
559
+ heading(2, title),
560
+ ...writer.blocks(blocks),
561
+ ...legend.length > 0 ? [
562
+ paragraph([
563
+ emphasis(
564
+ legend.flatMap((item, index) => [
565
+ ...index > 0 ? [text(" \xB7 ")] : [],
566
+ { type: "inlineMath", value: plainTex(item.tex) },
567
+ text(" "),
568
+ ...writer.inlines(item.label)
569
+ ])
570
+ )
571
+ ])
572
+ ] : [],
573
+ ...notes([...writer.blocks(frame.notes), ...writer.answers(checksOf(blocks), true)])
574
+ ];
575
+ });
576
+ if (context.symbols.length > 0) {
577
+ slides.push([heading(2, [text("Notation")]), ...writer.notation(context.symbols, []).slice(1)]);
578
+ }
579
+ const body = serializeNodes(
580
+ slides.flatMap((slide, index) => [...index > 0 ? [{ type: "thematicBreak" }] : [], ...slide])
581
+ );
582
+ const frontMatter = [
583
+ "---",
584
+ "marp: true",
585
+ `title: ${JSON.stringify(deck.title)}`,
586
+ `size: ${context.size}`,
587
+ "paginate: true",
588
+ "math: katex",
589
+ "---",
590
+ ""
591
+ ].join("\n");
592
+ const markdown = body.replace(/<a id="[^"]*"><\/a> ?/gu, "").replace(/(?<!!)\[([^\]]*)\]\(#[^)]*\)/gu, "$1").replace(/\n{3,}/gu, "\n\n");
593
+ return `${frontMatter}
594
+ ${markdown}`;
595
+ }
520
596
  function serializeMarkdownNodes(children) {
521
597
  return serializeNodes(children);
522
598
  }
523
599
  export {
524
600
  diagramEdges,
525
601
  serializeMarkdownNodes,
526
- serializeMarkdownPage
602
+ serializeMarkdownPage,
603
+ serializeMarpDeck
527
604
  };
@@ -2,7 +2,7 @@ import { Manifest } from '../manifest.js';
2
2
  import { ExportLogger, PreparedChapters } from './export-chapters.js';
3
3
  import '../../curriculum/validation.js';
4
4
  import '../../reference/consistency.js';
5
- import '../../course-config-rRtME5UV.js';
5
+ import '../../course-config-BLtIdkei.js';
6
6
  import '../../rules/catalog.js';
7
7
  import '../../rules/course-rules.js';
8
8
  import '../collections.js';
@@ -24,11 +24,16 @@ import '../../reference/mixed-content.js';
24
24
  import '../../reference/gloss.js';
25
25
  import '../../rules/waivers.js';
26
26
  import '../knowledge-version.js';
27
+ import '../page-versions.js';
28
+ import '../decks.js';
29
+ import '../../reference/decks-validate.js';
30
+ import '../../reference/deck-source.js';
27
31
  import '../lesson-derivation.js';
28
32
  import './static-tree.js';
29
33
  import './html-tree.js';
30
34
  import 'parse5';
31
35
  import './latex/capture.js';
36
+ import 'node:http';
32
37
 
33
38
  declare function writeMarkdownExports(directory: URL, manifest: Manifest, options?: {
34
39
  readonly logger?: ExportLogger;
@@ -1,9 +1,11 @@
1
1
  import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
2
2
  import { basename, dirname, extname, join } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
- import { courseFileStem } from "./export-pages.js";
4
+ import { newestSince, versionPhrase } from "../page-versions.js";
5
+ import { courseFileStem, deckFileStem, deckOwnerSlug, deckSaveName } from "./export-pages.js";
5
6
  import {
6
7
  consoleLogger,
8
+ deckBlocks,
7
9
  diagramsOf,
8
10
  figuresOf,
9
11
  imageExtension,
@@ -13,8 +15,9 @@ import {
13
15
  prepareChapters,
14
16
  readBuiltPage
15
17
  } from "./export-chapters.js";
16
- import { checkItems } from "./latex/document.js";
17
- import { serializeMarkdownNodes, serializeMarkdownPage } from "./markdown/serialize.js";
18
+ import { deckSymbols } from "./latex/deck.js";
19
+ import { checkItems, deckCheckItems } from "./latex/document.js";
20
+ import { serializeMarkdownNodes, serializeMarkdownPage, serializeMarpDeck } from "./markdown/serialize.js";
18
21
  import { zip } from "./zip.js";
19
22
  async function pagePictures(outputRoot, base, page, captured, assetDir) {
20
23
  const pictures = /* @__PURE__ */ new Map();
@@ -57,15 +60,19 @@ async function writeMarkdownExports(directory, manifest, options = {}) {
57
60
  if (!manifest.lessonExports.markdown) return;
58
61
  const logger = options.logger ?? consoleLogger;
59
62
  const outputRoot = fileURLToPath(directory);
60
- const { base, chapters, captures } = options.prepared ?? await prepareChapters(outputRoot, manifest, { logger, projectRoot: options.projectRoot });
63
+ const { base, chapters, decks, captures } = options.prepared ?? await prepareChapters(outputRoot, manifest, { logger, projectRoot: options.projectRoot });
61
64
  const canonicalBaseUrl = manifest.lessonExports.canonicalBaseUrl;
62
65
  const course = manifest.about.material.name;
63
- const version = manifest.about.knowledge.version;
64
- const source = (route) => ({
66
+ const current = manifest.about.knowledge.version;
67
+ const source = (route, since) => ({
65
68
  course,
66
- version,
69
+ since: versionPhrase(since, current),
67
70
  ...canonicalBaseUrl ? { url: `${canonicalBaseUrl}${route ? `${route}/` : ""}` } : {}
68
71
  });
72
+ const partSince = (entry) => newestSince([
73
+ ...entry.part.overview ? [entry.part.overview.since] : [],
74
+ ...entry.chapters.map(({ lesson }) => lesson.since)
75
+ ]);
69
76
  const chapterFiles = async (chapter, file, assetDir, documentFiles) => {
70
77
  const { lesson, page } = chapter;
71
78
  const pictures = await pagePictures(outputRoot, base, page, captures.get(page.route), assetDir);
@@ -76,7 +83,7 @@ async function writeMarkdownExports(directory, manifest, options = {}) {
76
83
  ...canonicalBaseUrl ? { canonicalBaseUrl } : {},
77
84
  pictures: new Map([...pictures].map(([key, picture]) => [key, picture.path])),
78
85
  checkItems: checkItems(manifest, lesson),
79
- source: source(page.route)
86
+ source: source(page.route, lesson.since)
80
87
  },
81
88
  { number: `${lesson.part}.${lesson.chapter}` }
82
89
  );
@@ -101,6 +108,38 @@ async function writeMarkdownExports(directory, manifest, options = {}) {
101
108
  await chapterFiles(chapter, "lesson.md", "assets", /* @__PURE__ */ new Map([[chapter.page.route, "lesson.md"]]))
102
109
  );
103
110
  }
111
+ for (const { deck, page, figureRoutes } of decks) {
112
+ if (deck.part === void 0 && !manifest.lessons.some((entry) => entry.id === deck.lessonId)) continue;
113
+ const owner = deckOwnerSlug(deck, manifest);
114
+ const captured = /* @__PURE__ */ new Map();
115
+ for (const [anchor, route] of figureRoutes) {
116
+ const figure = captures.get(route)?.get(anchor);
117
+ if (figure) captured.set(anchor, figure);
118
+ }
119
+ const blocks = [...deckBlocks(page), ...page.frames.flatMap((frame) => frame.notes)];
120
+ const pictures = await pagePictures(
121
+ outputRoot,
122
+ base,
123
+ { slug: page.slug, route: page.route, title: page.title, blocks, notation: [], notationRemarks: [] },
124
+ captured,
125
+ "assets"
126
+ );
127
+ const items = deckCheckItems(manifest, deck);
128
+ const markdown = serializeMarpDeck({
129
+ deck: page,
130
+ documentFiles: /* @__PURE__ */ new Map([[page.route, ""]]),
131
+ ...canonicalBaseUrl ? { canonicalBaseUrl } : {},
132
+ pictures: new Map([...pictures].map(([key, picture]) => [key, picture.path])),
133
+ checkItems: items,
134
+ source: source(page.route, deck.since),
135
+ symbols: deckSymbols({ deck: page, documentRoutes: /* @__PURE__ */ new Set([page.route]), pictures: /* @__PURE__ */ new Map(), checkItems: items }),
136
+ size: manifest.deck.aspectRatio === "4:3" ? "4:3" : "16:9"
137
+ });
138
+ pack(`${deckFileStem(owner)}-markdown.zip`, `${deckSaveName(owner)}-markdown`, [
139
+ { path: "slides.md", content: markdown },
140
+ ...[...pictures.values()].map((picture) => ({ path: picture.path, content: picture.content }))
141
+ ]);
142
+ }
104
143
  const byId = new Map(chapters.map((chapter) => [chapter.lesson.id, chapter]));
105
144
  const pageSlugs = new Set(manifest.pages.map((page) => page.slug));
106
145
  const parts = manifest.parts.map((part) => ({
@@ -121,7 +160,7 @@ async function writeMarkdownExports(directory, manifest, options = {}) {
121
160
  ...canonicalBaseUrl ? { canonicalBaseUrl } : {},
122
161
  pictures: new Map([...pictures].map(([key, picture]) => [key, picture.path])),
123
162
  checkItems: /* @__PURE__ */ new Map(),
124
- source: source(page.route)
163
+ source: source(page.route, partSince(entry))
125
164
  },
126
165
  { number: `Part ${entry.part.ordinal}:` }
127
166
  );
@@ -186,7 +225,7 @@ async function writeMarkdownExports(directory, manifest, options = {}) {
186
225
  {
187
226
  type: "paragraph",
188
227
  children: [
189
- { type: "text", value: `Content version ${version}.` },
228
+ { type: "text", value: `Content ${versionPhrase(newestSince(parts.map(partSince)), current)}.` },
190
229
  ...canonicalBaseUrl ? [
191
230
  { type: "text", value: " Interactive version: " },
192
231
  { type: "link", url: canonicalBaseUrl, children: [{ type: "text", value: canonicalBaseUrl }] },
@@ -232,7 +271,7 @@ async function writeMarkdownExports(directory, manifest, options = {}) {
232
271
  await Promise.all(pending.map((file) => rm(`${file.target}.tmp-${process.pid}`, { force: true })));
233
272
  }
234
273
  logger.info(
235
- `Markdown export: ${chapters.length} chapter(s), ${parts.length} part(s)` + (parts.length > 0 ? " and the course" : "") + "."
274
+ `Markdown export: ${chapters.length} chapter(s), ${decks.length} deck(s), ${parts.length} part(s)` + (parts.length > 0 ? " and the course" : "") + "."
236
275
  );
237
276
  }
238
277
  export {
@@ -1,7 +1,7 @@
1
1
  import { Manifest } from '../manifest.js';
2
2
  import '../../curriculum/validation.js';
3
3
  import '../../reference/consistency.js';
4
- import '../../course-config-rRtME5UV.js';
4
+ import '../../course-config-BLtIdkei.js';
5
5
  import '../../rules/catalog.js';
6
6
  import '../../rules/course-rules.js';
7
7
  import '../collections.js';
@@ -23,6 +23,10 @@ import '../../reference/mixed-content.js';
23
23
  import '../../reference/gloss.js';
24
24
  import '../../rules/waivers.js';
25
25
  import '../knowledge-version.js';
26
+ import '../page-versions.js';
27
+ import '../decks.js';
28
+ import '../../reference/decks-validate.js';
29
+ import '../../reference/deck-source.js';
26
30
  import '../lesson-derivation.js';
27
31
 
28
32
  /** Inputs for producing one standalone page from Astro's final HTML. */
@@ -39,10 +43,11 @@ declare function buildStandaloneLessonHtml(input: StandaloneLessonInput): Promis
39
43
  /** Astro's built HTML file of a docs slug. */
40
44
  declare function pageSourceFile(outputRoot: string, slug: string): Promise<string>;
41
45
  /**
42
- * Emit the opt-in standalone HTML artifact of every chapter
43
- * (`standalonePageSlugs`) from Astro's completed static output, at
44
- * `downloads/<docs slug>.html`. A page that is not a chapter is in its
45
- * part's and the course's files (`combined-html.ts`).
46
+ * Emit the opt-in standalone HTML artifact of every chapter and deck
47
+ * (`standaloneDownloads`) from Astro's completed static output, at
48
+ * `downloads/<docs slug>.html` and `downloads/<lesson>-slides.html`. A page
49
+ * that is not a chapter is in its part's and the course's files
50
+ * (`combined-html.ts`).
46
51
  */
47
52
  declare function writeStandaloneLessonExports(directory: URL, manifest: Manifest): Promise<void>;
48
53
 
@@ -10,7 +10,7 @@ import { Buffer } from "node:buffer";
10
10
  import { dirname, extname, join, relative, resolve, sep } from "node:path";
11
11
  import { fileURLToPath } from "node:url";
12
12
  import { parse, serialize } from "parse5";
13
- import { exportPageRoute, standalonePageSlugs } from "./export-pages.js";
13
+ import { exportPageRoute, standaloneDownloads } from "./export-pages.js";
14
14
  import {
15
15
  attribute,
16
16
  elements,
@@ -760,9 +760,9 @@ async function writeStandaloneLessonExports(directory, manifest) {
760
760
  if (!manifest.lessonExports.standaloneHtml) return;
761
761
  const outputRoot = fileURLToPath(directory);
762
762
  const pending = [];
763
- for (const slug of standalonePageSlugs(manifest)) {
763
+ for (const { slug, download } of standaloneDownloads(manifest)) {
764
764
  const sourceFile = await pageSourceFile(outputRoot, slug);
765
- const target = join(outputRoot, "downloads", `${slug}.html`);
765
+ const target = join(outputRoot, "downloads", `${download}.html`);
766
766
  pending.push({
767
767
  target,
768
768
  temporary: `${target}.tmp-${process.pid}`,