explico 0.12.0 → 0.13.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 (279) hide show
  1. package/CHANGELOG.md +1122 -1
  2. package/README.md +91 -13
  3. package/dist/compiler/astro.d.ts +4 -3
  4. package/dist/compiler/astro.js +45 -15
  5. package/dist/compiler/cli.d.ts +26 -12
  6. package/dist/compiler/cli.js +96 -25
  7. package/dist/compiler/collections.d.ts +9 -1
  8. package/dist/compiler/collections.js +55 -16
  9. package/dist/compiler/content-schema.d.ts +31 -0
  10. package/dist/compiler/content-schema.js +82 -0
  11. package/dist/compiler/content-schemas.d.ts +422 -0
  12. package/dist/compiler/content-schemas.js +229 -0
  13. package/dist/compiler/course-config.d.ts +1 -1
  14. package/dist/compiler/course-config.js +30 -2
  15. package/dist/compiler/exports/combined-html.d.ts +97 -0
  16. package/dist/compiler/exports/combined-html.js +492 -0
  17. package/dist/compiler/exports/download-index.d.ts +9 -0
  18. package/dist/compiler/exports/download-index.js +33 -0
  19. package/dist/compiler/exports/export-chapters.d.ts +98 -0
  20. package/dist/compiler/exports/export-chapters.js +164 -0
  21. package/dist/compiler/exports/export-pages.d.ts +48 -0
  22. package/dist/compiler/exports/export-pages.js +22 -0
  23. package/dist/compiler/exports/html-tree.d.ts +22 -0
  24. package/dist/compiler/exports/html-tree.js +47 -0
  25. package/dist/compiler/exports/latex/capture.d.ts +106 -0
  26. package/dist/compiler/exports/latex/capture.js +435 -0
  27. package/dist/compiler/exports/latex/compile.d.ts +73 -0
  28. package/dist/compiler/exports/latex/compile.js +115 -0
  29. package/dist/compiler/exports/latex/document.d.ts +102 -0
  30. package/dist/compiler/exports/latex/document.js +349 -0
  31. package/dist/compiler/exports/latex/explico.cls +415 -0
  32. package/dist/compiler/exports/latex/numbering.d.ts +18 -0
  33. package/dist/compiler/exports/latex/numbering.js +57 -0
  34. package/dist/compiler/exports/latex/serialize.d.ts +112 -0
  35. package/dist/compiler/exports/latex/serialize.js +606 -0
  36. package/dist/compiler/exports/latex-export.d.ts +44 -0
  37. package/dist/compiler/exports/latex-export.js +169 -0
  38. package/dist/compiler/exports/markdown/serialize.d.ts +51 -0
  39. package/dist/compiler/exports/markdown/serialize.js +527 -0
  40. package/dist/compiler/exports/markdown-export.d.ts +39 -0
  41. package/dist/compiler/exports/markdown-export.js +240 -0
  42. package/dist/compiler/exports/standalone-html.d.ts +14 -5
  43. package/dist/compiler/exports/standalone-html.js +23 -51
  44. package/dist/compiler/exports/static-tree.d.ts +253 -0
  45. package/dist/compiler/exports/static-tree.js +625 -0
  46. package/dist/compiler/exports/zip.d.ts +16 -0
  47. package/dist/compiler/exports/zip.js +72 -0
  48. package/dist/compiler/lesson-derivation.d.ts +8 -2
  49. package/dist/compiler/lesson-derivation.js +6 -7
  50. package/dist/compiler/manifest.d.ts +87 -6
  51. package/dist/compiler/manifest.js +174 -31
  52. package/dist/compiler/render-diagnostics.d.ts +65 -0
  53. package/dist/compiler/render-diagnostics.js +56 -0
  54. package/dist/compiler/resolution-report.d.ts +2 -1
  55. package/dist/{course-config-Dsnri9Tk.d.ts → course-config-rRtME5UV.d.ts} +56 -13
  56. package/dist/curriculum/validation.d.ts +9 -3
  57. package/dist/curriculum/validation.js +35 -17
  58. package/dist/reference/base-abbreviations.d.mts +2 -2
  59. package/dist/reference/base-abbreviations.mjs +2 -2
  60. package/dist/reference/base-operators.d.mts +2 -2
  61. package/dist/reference/base-operators.mjs +2 -2
  62. package/dist/reference/base-roman-labels.d.mts +4 -3
  63. package/dist/reference/base-roman-labels.mjs +4 -3
  64. package/dist/reference/citation-format.d.mts +35 -0
  65. package/dist/reference/citation-format.mjs +74 -0
  66. package/dist/reference/consistency.d.ts +5 -9
  67. package/dist/reference/consistency.js +34 -38
  68. package/dist/reference/curriculum-alignment.js +3 -24
  69. package/dist/reference/derived-entries.d.ts +45 -0
  70. package/dist/reference/derived-entries.js +50 -0
  71. package/dist/reference/derived-notation.d.mts +125 -0
  72. package/dist/reference/derived-notation.mjs +121 -0
  73. package/dist/reference/diagram-fit.d.ts +45 -0
  74. package/dist/reference/diagram-fit.js +54 -0
  75. package/dist/reference/diagram-layout.d.ts +59 -10
  76. package/dist/reference/diagram-layout.js +85 -24
  77. package/dist/reference/diagrams-validate.d.ts +1 -1
  78. package/dist/reference/equations-validate.d.ts +1 -1
  79. package/dist/reference/equations-validate.js +26 -1
  80. package/dist/reference/equations.d.mts +27 -0
  81. package/dist/reference/equations.mjs +120 -22
  82. package/dist/reference/examples-validate.d.ts +1 -1
  83. package/dist/reference/figure-fit.d.ts +70 -0
  84. package/dist/reference/figure-fit.js +60 -0
  85. package/dist/reference/figure-pictures.d.ts +27 -0
  86. package/dist/reference/figure-pictures.js +49 -0
  87. package/dist/reference/figures-validate.d.ts +1 -1
  88. package/dist/reference/gate-abbreviations.d.ts +1 -1
  89. package/dist/reference/gate-abbreviations.js +1 -1
  90. package/dist/reference/gate-course-rules.d.ts +4 -3
  91. package/dist/reference/gate-course-rules.js +9 -4
  92. package/dist/reference/gate-heading-length.d.ts +2 -0
  93. package/dist/reference/gate-heading-length.js +7 -5
  94. package/dist/reference/gate-heading-structure.js +26 -0
  95. package/dist/reference/gate-indices.d.ts +35 -0
  96. package/dist/reference/gate-indices.js +299 -0
  97. package/dist/reference/gate-math.d.ts +32 -2
  98. package/dist/reference/gate-math.js +68 -12
  99. package/dist/reference/gate-mdx-expression.d.ts +17 -0
  100. package/dist/reference/gate-mdx-expression.js +29 -0
  101. package/dist/reference/gate-part-pages.d.ts +1 -1
  102. package/dist/reference/gate-prop-text.d.ts +28 -0
  103. package/dist/reference/gate-prop-text.js +106 -0
  104. package/dist/reference/gate-prose-notation.d.ts +3 -1
  105. package/dist/reference/gate-prose-notation.js +4 -2
  106. package/dist/reference/gate-retired-keys.d.ts +35 -0
  107. package/dist/reference/gate-retired-keys.js +58 -0
  108. package/dist/reference/gloss.d.ts +2 -0
  109. package/dist/reference/katex-measure.d.ts +17 -0
  110. package/dist/reference/katex-measure.js +223 -0
  111. package/dist/reference/keyed-float-validate.js +12 -0
  112. package/dist/reference/keyed-jsx-floats.d.mts +2 -2
  113. package/dist/reference/keyed-jsx-floats.mjs +11 -14
  114. package/dist/reference/lab-math-scope.d.ts +1 -0
  115. package/dist/reference/lab-math-scope.js +13 -2
  116. package/dist/reference/math-glyphs.d.mts +94 -2
  117. package/dist/reference/math-glyphs.mjs +851 -46
  118. package/dist/reference/mixed-content.d.ts +8 -3
  119. package/dist/reference/mixed-content.js +4 -2
  120. package/dist/reference/page-notation.d.ts +34 -4
  121. package/dist/reference/page-notation.js +54 -2
  122. package/dist/reference/references.d.ts +11 -1
  123. package/dist/reference/references.js +6 -0
  124. package/dist/reference/registry.js +104 -10
  125. package/dist/reference/rehype-fail-katex-errors.d.mts +9 -0
  126. package/dist/reference/rehype-fail-katex-errors.mjs +19 -0
  127. package/dist/reference/rehype-heading-anchors.mjs +11 -1
  128. package/dist/reference/remark-citation.mjs +0 -0
  129. package/dist/reference/remark-notation.mjs +84 -20
  130. package/dist/reference/render-lab-math.d.ts +2 -0
  131. package/dist/reference/render-lab-math.js +17 -5
  132. package/dist/reference/sections-validate.d.ts +28 -3
  133. package/dist/reference/sections-validate.js +31 -4
  134. package/dist/reference/sections.d.mts +24 -6
  135. package/dist/reference/sections.mjs +92 -17
  136. package/dist/reference/tables-validate.d.ts +1 -1
  137. package/dist/reference/tables-validate.js +24 -1
  138. package/dist/reference/text-measure.d.ts +10 -0
  139. package/dist/reference/text-measure.js +41 -0
  140. package/dist/reference/text-widths.d.ts +10 -0
  141. package/dist/reference/text-widths.js +1238 -0
  142. package/dist/reference/types.d.ts +64 -8
  143. package/dist/rules/catalog.js +191 -30
  144. package/dist/rules/in-effect.d.ts +1 -1
  145. package/dist/rules/rules.schema.json +79 -24
  146. package/dist/rules/settings.d.ts +1 -1
  147. package/dist/rules/settings.js +7 -0
  148. package/dist/session/PreferenceStore.d.ts +18 -1
  149. package/dist/session/PreferenceStore.js +29 -1
  150. package/docs/authoring/README.md +32 -21
  151. package/docs/authoring/agents.md +38 -7
  152. package/docs/authoring/components.md +111 -6
  153. package/docs/authoring/migrating.md +663 -0
  154. package/docs/authoring/notation.md +281 -29
  155. package/docs/authoring/retired.md +1 -0
  156. package/docs/authoring/structure-rules.md +10 -41
  157. package/docs/authoring/structure.md +53 -2
  158. package/docs/authoring/visualizations.md +144 -18
  159. package/package.json +12 -7
  160. package/src/compiler/astro.ts +57 -17
  161. package/src/compiler/cli.ts +143 -35
  162. package/src/compiler/collections.ts +92 -19
  163. package/src/compiler/content-schema.ts +108 -0
  164. package/src/compiler/content-schemas.ts +391 -0
  165. package/src/compiler/course-config.ts +86 -15
  166. package/src/compiler/exports/combined-html.ts +669 -0
  167. package/src/compiler/exports/download-index.ts +36 -0
  168. package/src/compiler/exports/export-chapters.ts +233 -0
  169. package/src/compiler/exports/export-pages.ts +44 -0
  170. package/src/compiler/exports/html-tree.ts +62 -0
  171. package/src/compiler/exports/latex/capture.ts +651 -0
  172. package/src/compiler/exports/latex/compile.ts +168 -0
  173. package/src/compiler/exports/latex/document.ts +479 -0
  174. package/src/compiler/exports/latex/explico.cls +415 -0
  175. package/src/compiler/exports/latex/numbering.ts +74 -0
  176. package/src/compiler/exports/latex/serialize.ts +787 -0
  177. package/src/compiler/exports/latex/texlive-packages.txt +76 -0
  178. package/src/compiler/exports/latex-export.ts +255 -0
  179. package/src/compiler/exports/markdown/serialize.ts +632 -0
  180. package/src/compiler/exports/markdown-export.ts +329 -0
  181. package/src/compiler/exports/standalone-html.ts +39 -70
  182. package/src/compiler/exports/static-tree.ts +944 -0
  183. package/src/compiler/exports/zip.ts +88 -0
  184. package/src/compiler/lesson-derivation.ts +13 -8
  185. package/src/compiler/manifest.ts +299 -28
  186. package/src/compiler/render-diagnostics.ts +111 -0
  187. package/src/components/HoverPanel.astro +204 -6
  188. package/src/components/PartOverview.astro +12 -9
  189. package/src/components/abbreviation/AbbreviationLayer.astro +21 -12
  190. package/src/components/assessments/AssessmentRunner.tsx +7 -1
  191. package/src/components/assessments/AssessmentSet.astro +14 -1
  192. package/src/components/block-heading.ts +38 -0
  193. package/src/components/card-arrow.ts +18 -4
  194. package/src/components/card-carry.ts +406 -0
  195. package/src/components/card-drag.ts +1 -30
  196. package/src/components/card-fetch.ts +87 -0
  197. package/src/components/card-manager.ts +1222 -38
  198. package/src/components/card-memory.ts +5 -1
  199. package/src/components/card-placement.ts +211 -13
  200. package/src/components/card-side.ts +281 -31
  201. package/src/components/citation/CitationLayer.astro +45 -40
  202. package/src/components/diagram/Diagram.astro +123 -44
  203. package/src/components/figure/Figure.astro +11 -0
  204. package/src/components/figure/FloatCaption.astro +4 -17
  205. package/src/components/float-toolbar.ts +274 -0
  206. package/src/components/memo/Memo.astro +21 -10
  207. package/src/components/memo/ShortNote.astro +4 -0
  208. package/src/components/notation/NotationGlossary.astro +22 -17
  209. package/src/components/notation/NotationLayer.astro +300 -79
  210. package/src/components/notation/NotationMath.astro +8 -1
  211. package/src/components/notation/render-notation-math.ts +51 -5
  212. package/src/components/readable-text.ts +25 -0
  213. package/src/components/reference/FloatRefLayer.astro +162 -89
  214. package/src/components/starlight/HeadingAnchorEnhancer.astro +13 -8
  215. package/src/components/starlight/KeyedFloatEnhancer.astro +28 -9
  216. package/src/components/starlight/LayoutPageSidebar.astro +17 -3
  217. package/src/components/starlight/LayoutSocialIcons.astro +79 -29
  218. package/src/components/starlight/LessonExportActions.astro +384 -310
  219. package/src/components/starlight/LessonFooter.astro +47 -9
  220. package/src/components/starlight/PrintController.astro +28 -0
  221. package/src/components/starlight/TextSizeControl.astro +137 -0
  222. package/src/components/starlight/print-controller.ts +380 -0
  223. package/src/components/starlight/section-summary.ts +13 -5
  224. package/src/components/table/Table.astro +13 -21
  225. package/src/components/table/table-truncation.ts +15 -0
  226. package/src/content-config.ts +20 -312
  227. package/src/curriculum/validation.ts +72 -24
  228. package/src/reference/base-abbreviations.mjs +2 -2
  229. package/src/reference/base-operators.mjs +2 -2
  230. package/src/reference/base-roman-labels.mjs +4 -3
  231. package/src/reference/citation-format.mjs +74 -0
  232. package/src/reference/consistency.ts +47 -61
  233. package/src/reference/curriculum-alignment.ts +4 -41
  234. package/src/reference/derived-entries.ts +68 -0
  235. package/src/reference/derived-notation.mjs +121 -0
  236. package/src/reference/diagram-fit.ts +104 -0
  237. package/src/reference/diagram-layout.ts +182 -51
  238. package/src/reference/diagrams-validate.ts +2 -1
  239. package/src/reference/equations-validate.ts +41 -2
  240. package/src/reference/equations.mjs +120 -22
  241. package/src/reference/examples-validate.ts +1 -0
  242. package/src/reference/figure-fit.ts +139 -0
  243. package/src/reference/figure-pictures.ts +96 -0
  244. package/src/reference/figures-validate.ts +2 -1
  245. package/src/reference/gate-abbreviations.ts +1 -1
  246. package/src/reference/gate-course-rules.ts +22 -7
  247. package/src/reference/gate-heading-length.ts +25 -7
  248. package/src/reference/gate-heading-structure.ts +40 -1
  249. package/src/reference/gate-indices.ts +470 -0
  250. package/src/reference/gate-math.ts +102 -13
  251. package/src/reference/gate-mdx-expression.ts +45 -0
  252. package/src/reference/gate-prop-text.ts +145 -0
  253. package/src/reference/gate-prose-notation.ts +19 -5
  254. package/src/reference/gate-retired-keys.ts +101 -0
  255. package/src/reference/gloss.ts +2 -0
  256. package/src/reference/katex-measure.ts +384 -0
  257. package/src/reference/keyed-float-validate.ts +22 -0
  258. package/src/reference/keyed-jsx-floats.mjs +11 -14
  259. package/src/reference/lab-math-scope.ts +15 -1
  260. package/src/reference/math-glyphs.mjs +851 -46
  261. package/src/reference/mixed-content.ts +11 -2
  262. package/src/reference/page-notation.ts +100 -3
  263. package/src/reference/references.ts +14 -0
  264. package/src/reference/registry.ts +154 -8
  265. package/src/reference/rehype-fail-katex-errors.mjs +19 -0
  266. package/src/reference/rehype-heading-anchors.mjs +11 -1
  267. package/src/reference/remark-citation.mjs +0 -0
  268. package/src/reference/remark-notation.mjs +84 -20
  269. package/src/reference/render-lab-math.ts +29 -5
  270. package/src/reference/sections-validate.ts +67 -3
  271. package/src/reference/sections.mjs +92 -17
  272. package/src/reference/tables-validate.ts +40 -2
  273. package/src/reference/text-measure.ts +63 -0
  274. package/src/reference/text-widths.ts +133 -0
  275. package/src/reference/types.ts +75 -8
  276. package/src/rules/catalog.ts +205 -29
  277. package/src/rules/settings.ts +10 -0
  278. package/src/session/PreferenceStore.ts +53 -0
  279. package/src/styles/global.css +291 -75
@@ -45,8 +45,11 @@ import {
45
45
  } from "../reference/lab-katex-validate.js";
46
46
  import {
47
47
  gateContentMath,
48
- pageScope
48
+ pageScope,
49
+ scopeEntry
49
50
  } from "../reference/gate-math.js";
51
+ import { gateIndices } from "../reference/gate-indices.js";
52
+ import { derivedNotationEntries } from "../reference/derived-entries.js";
50
53
  import {
51
54
  gateProseNotation,
52
55
  gateProseNotationInAssessments
@@ -61,12 +64,17 @@ import { gateHeadingLength } from "../reference/gate-heading-length.js";
61
64
  import { gateHeadingStructure } from "../reference/gate-heading-structure.js";
62
65
  import { gatePartPages } from "../reference/gate-part-pages.js";
63
66
  import { gateNonChapterPages } from "../reference/gate-non-chapter-pages.js";
67
+ import { findRetiredKeys, findUnsupportedKeys } from "../reference/gate-retired-keys.js";
68
+ import { findMarkupInProps } from "../reference/gate-prop-text.js";
69
+ import { gateMdxExpressions } from "../reference/gate-mdx-expression.js";
70
+ import { CITATION_BRACKET, bracketCitations, withoutLinkCitations } from "../reference/citation-format.mjs";
71
+ import { validateContentSchemas } from "./content-schema.js";
64
72
  import {
65
73
  scanSectionHeadings,
66
74
  scanTitledBlocks
67
75
  } from "../reference/sections.mjs";
68
76
  import { knowledgeCheckAnchorId } from "../reference/knowledge-checks.mjs";
69
- import { validateSectionRefs } from "../reference/sections-validate.js";
77
+ import { validateHeadingAnchors, validateSectionRefs } from "../reference/sections-validate.js";
70
78
  import {
71
79
  splitMixedContent
72
80
  } from "../reference/mixed-content.js";
@@ -77,6 +85,7 @@ import { resolveReferenceId } from "../reference/references.js";
77
85
  import {
78
86
  coursePartsOf,
79
87
  normalizeBlockLabels,
88
+ normalizeCards,
80
89
  normalizeLessonExports,
81
90
  normalizeDisclosure,
82
91
  normalizeNumbering
@@ -97,6 +106,7 @@ import {
97
106
  } from "./knowledge-version.js";
98
107
  import {
99
108
  loadAbbreviationRegistryInput,
109
+ loadContentRecords,
100
110
  loadCurriculumCatalog,
101
111
  loadNotationDefinitions,
102
112
  loadNotationRegistryInput,
@@ -111,7 +121,7 @@ import {
111
121
  isLessonSlug,
112
122
  lessonIdFromSlug
113
123
  } from "./lesson-derivation.js";
114
- const MANIFEST_SCHEMA_VERSION = 25;
124
+ const MANIFEST_SCHEMA_VERSION = 31;
115
125
  const REPOSITORY_ROOT = process.cwd();
116
126
  const CONTENT_ROOT = join(REPOSITORY_ROOT, "content");
117
127
  const MANIFEST_PATH = join(REPOSITORY_ROOT, "build", "manifest.json");
@@ -124,18 +134,19 @@ function readEnginePackage() {
124
134
  }
125
135
  return enginePackage;
126
136
  }
127
- const CITE_PATTERN = /\[@([a-z0-9][a-z0-9.-]*)(?:;[ \t\r\n]*([^\]]*))?\]/g;
128
137
  function lessonCitations(body) {
129
138
  const withoutCode = body.replace(/```[\s\S]*?```/g, "").replace(/`[^`\n]*`/g, "");
130
139
  const seen = /* @__PURE__ */ new Set();
131
140
  const citations = [];
132
- for (const match of withoutCode.matchAll(CITE_PATTERN)) {
133
- const id = (match[1] ?? "").trim();
134
- const locator = (match[2] ?? "").replace(/\s+/g, " ").trim() || void 0;
135
- const dedupeKey = `${id} ${locator ?? ""}`;
136
- if (seen.has(dedupeKey)) continue;
137
- seen.add(dedupeKey);
138
- citations.push(locator === void 0 ? { id } : { id, locator });
141
+ for (const match of withoutLinkCitations(withoutCode).matchAll(CITATION_BRACKET)) {
142
+ for (const citation of bracketCitations(match[1] ?? "", match[2])) {
143
+ const id = citation.id;
144
+ const locator = citation.locator || void 0;
145
+ const dedupeKey = `${id} ${locator ?? ""}`;
146
+ if (seen.has(dedupeKey)) continue;
147
+ seen.add(dedupeKey);
148
+ citations.push(locator === void 0 ? { id } : { id, locator });
149
+ }
139
150
  }
140
151
  return citations;
141
152
  }
@@ -175,17 +186,21 @@ function lessonNotationResolution(lesson, definitionsById, availableDefinitionId
175
186
  key: reference.key,
176
187
  scope: definition ? definition.kind === "shared" ? "shared" : "local" : "unresolved",
177
188
  definitionId: definitionId ?? null,
178
- kind: reference.kind,
189
+ // A page's own references are prose or math; a definition's formula
190
+ // reference is reported as the definition reference it always was.
191
+ kind: reference.kind === "formula" ? "definition" : reference.kind,
179
192
  line: reference.source.line ?? null,
180
193
  column: reference.source.column ?? null
181
194
  };
182
195
  });
183
196
  }
184
- function lessonNotationBundle(bundle, definitionsById, resolution, cardsByKey) {
197
+ function lessonNotationBundle(bundle, definitionsById, resolution, cardsByKey, derivedUses = []) {
185
198
  const definitions = [];
199
+ const records = [];
186
200
  for (const definitionId of bundle?.definitionIds ?? []) {
187
201
  const definition = definitionsById.get(definitionId);
188
202
  if (!definition) continue;
203
+ records.push(definition);
189
204
  definitions.push({
190
205
  id: definition.id,
191
206
  key: definition.key,
@@ -193,7 +208,6 @@ function lessonNotationBundle(bundle, definitionsById, resolution, cardsByKey) {
193
208
  notation: definition.notation,
194
209
  label: definition.label,
195
210
  summary: definition.summary,
196
- editorialStatus: definition.status,
197
211
  seeAlso: [...definition.seeAlso],
198
212
  glosses: [...definition.glosses],
199
213
  formulaScope: formulaGlyphScope({
@@ -204,13 +218,19 @@ function lessonNotationBundle(bundle, definitionsById, resolution, cardsByKey) {
204
218
  ...definition.formula === void 0 ? {} : { formula: definition.formula }
205
219
  }),
206
220
  ...definition.units === void 0 ? {} : { units: definition.units },
207
- ...definition.formula === void 0 ? {} : { formula: definition.formula }
221
+ ...definition.formula === void 0 ? {} : { formula: definition.formula },
222
+ ...definition.range === void 0 ? {} : { range: definition.range }
208
223
  });
209
224
  }
210
225
  return {
211
226
  bindings: [...bundle?.bindings ?? []].map((binding) => ({ ...binding })),
212
227
  definitionIds: [...bundle?.definitionIds ?? []],
213
228
  definitions,
229
+ derived: derivedNotationEntries(
230
+ derivedUses,
231
+ new Map(records.map((record) => [record.key, record])),
232
+ cardsByKey
233
+ ),
214
234
  resolution: [...resolution]
215
235
  };
216
236
  }
@@ -277,7 +297,7 @@ async function compileManifest(courseConfig) {
277
297
  const cardsByKey = new Map(
278
298
  notationInput.sharedDefinitions.map((definition) => [
279
299
  definition.key,
280
- { key: definition.key, notation: definition.notation }
300
+ scopeEntry(definition)
281
301
  ])
282
302
  );
283
303
  const definitionsById = new Map(
@@ -288,13 +308,44 @@ async function compileManifest(courseConfig) {
288
308
  registry.bundles.map((bundle) => [bundle.lessonId, bundle])
289
309
  );
290
310
  const assessmentsInput = catalog.assessments;
291
- const curriculumIssues = validateCurriculum(catalog);
311
+ const contentRecords = loadContentRecords();
312
+ const curriculumIssues = [
313
+ ...validateCurriculum(catalog),
314
+ ...validateContentSchemas(contentRecords),
315
+ ...findRetiredKeys(contentRecords),
316
+ ...findUnsupportedKeys(contentRecords),
317
+ ...findMarkupInProps(contentRecords)
318
+ ];
292
319
  const alignmentIssues = validateNotationAlignment(registry, catalog);
293
320
  const mathEndAbbreviations = {
294
321
  allow: rules.strings("N12", "allow"),
295
322
  deny: rules.strings("N12", "deny")
296
323
  };
324
+ const sharedLabels = new Map(
325
+ notationInput.sharedDefinitions.map((definition) => [definition.key, definition.label])
326
+ );
327
+ const localLabelsByLesson = new Map(
328
+ notationInput.lessons.map((lesson) => [
329
+ lesson.lessonId,
330
+ new Map(lesson.localDefinitions.map((definition) => [definition.key, definition.label]))
331
+ ])
332
+ );
333
+ const termLabel = (lessonId, key) => localLabelsByLesson.get(lessonId)?.get(key) ?? sharedLabels.get(key);
334
+ const indexGate = gateIndices(
335
+ withPages.lessons,
336
+ new Map(
337
+ notationInput.sharedDefinitions.map((definition) => [definition.key, definition])
338
+ ),
339
+ {
340
+ assessments: assessmentsInput,
341
+ lessonAssessments: catalog.lessons.map((lesson) => ({
342
+ lessonId: lesson.id,
343
+ assessmentIds: lesson.assessments
344
+ }))
345
+ }
346
+ );
297
347
  const mathIssues = [
348
+ ...indexGate.diagnostics,
298
349
  ...gateContentMath({
299
350
  notationInput: withPages,
300
351
  assessments: assessmentsInput,
@@ -303,6 +354,7 @@ async function compileManifest(courseConfig) {
303
354
  assessmentIds: lesson.assessments
304
355
  }))
305
356
  }),
357
+ ...gateMdxExpressions(withPages.lessons),
306
358
  ...rules.isEnabled("N9") ? [
307
359
  ...gateProseNotation(withPages.lessons, {
308
360
  allowSpelledUnits: rules.strings("N9", "allow")
@@ -321,7 +373,8 @@ async function compileManifest(courseConfig) {
321
373
  courseAbbreviations: rules.settings("N14")
322
374
  }) : [],
323
375
  ...rules.isEnabled("S1") ? gateHeadingLength(notationInput.lessons, {
324
- maxLength: rules.number("S1", "maxLength")
376
+ maxLength: rules.number("S1", "maxLength"),
377
+ labelFor: termLabel
325
378
  }) : [],
326
379
  ...gateHeadingStructure(notationInput.lessons, {
327
380
  levels: rules.isEnabled("S6"),
@@ -341,22 +394,46 @@ async function compileManifest(courseConfig) {
341
394
  assessmentsInput.map((assessment) => [assessment.id, assessment])
342
395
  );
343
396
  const assessmentContent = [];
397
+ const assessmentDerived = /* @__PURE__ */ new Map();
344
398
  for (const lesson of catalog.lessons) {
345
399
  if (lesson.assessments.length === 0) continue;
346
400
  const lessonInput = notationLessonById.get(lesson.id);
347
401
  if (!lessonInput) continue;
348
402
  const scope = pageScope(lessonInput, cardsByKey);
403
+ const derived = /* @__PURE__ */ new Map();
404
+ assessmentDerived.set(lesson.id, derived);
405
+ const page = { derive: true, derived };
349
406
  for (const assessmentId of lesson.assessments) {
350
407
  const assessment = assessmentInputById.get(assessmentId);
351
408
  if (!assessment?.items) continue;
352
409
  for (const item of assessment.items) {
353
410
  if (!item.id) continue;
411
+ const numeric = item.type === "numeric" && typeof item.answer?.value === "number" && typeof item.answer.tolerance === "number";
354
412
  assessmentContent.push({
355
413
  lessonId: lesson.id,
356
414
  assessmentId,
357
415
  itemId: item.id,
358
- promptSegments: splitMixedContent(item.prompt ?? "", scope),
359
- explanationSegments: splitMixedContent(item.explanation ?? "", scope)
416
+ promptSegments: splitMixedContent(item.prompt ?? "", scope, page),
417
+ explanationSegments: splitMixedContent(item.explanation ?? "", scope, page),
418
+ type: item.type === "numeric" ? "numeric" : "single-choice",
419
+ ...item.options ? {
420
+ options: item.options.flatMap(
421
+ (option) => option.id ? [
422
+ {
423
+ id: option.id,
424
+ label: option.label ?? "",
425
+ labelSegments: splitMixedContent(option.label ?? "", scope, page)
426
+ }
427
+ ] : []
428
+ )
429
+ } : {},
430
+ ...item.correctOptionId ? { correctOptionId: item.correctOptionId } : {},
431
+ ...numeric ? {
432
+ answer: {
433
+ value: item.answer.value,
434
+ tolerance: item.answer.tolerance
435
+ }
436
+ } : {}
360
437
  });
361
438
  }
362
439
  }
@@ -380,9 +457,31 @@ async function compileManifest(courseConfig) {
380
457
  );
381
458
  const frontmatterById = lessonFrontmatterById();
382
459
  const lessonSlugs = [...notationInput.lessons].map((lesson) => lesson.lessonId.replace(/\./g, "/")).sort();
383
- const configuredParts = coursePartsOf(courseConfig);
384
- const overviewPages = loadPartOverviews(configuredParts);
460
+ const listedParts = coursePartsOf(courseConfig);
461
+ const overviewPages = loadPartOverviews(listedParts);
385
462
  const overviewByDir = new Map(overviewPages.map((page) => [page.dir, page]));
463
+ const hasChapter = (dir) => lessonSlugs.some((slug) => slug.startsWith(`${dir}/`));
464
+ const configuredParts = listedParts.filter(
465
+ (part) => hasChapter(part.dir) || overviewByDir.has(part.dir)
466
+ );
467
+ for (const part of listedParts) {
468
+ if (configuredParts.includes(part)) continue;
469
+ curriculumIssues.push({
470
+ kind: "part-empty",
471
+ severity: "warning",
472
+ 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
+ });
474
+ }
475
+ const partDirs = new Set(listedParts.map((part) => part.dir));
476
+ for (const lesson of notationInput.lessons) {
477
+ const dir = lesson.lessonId.split(".")[0] ?? "";
478
+ if (partDirs.has(dir)) continue;
479
+ curriculumIssues.push({
480
+ kind: "lesson-outside-parts",
481
+ message: `${lesson.source.file} is in content/docs/${dir}/, which no part in course.settings.json \`parts\` lists, so it has no part, no chapter number and no place in the sidebar. Add { "dir": "${dir}", "label": "\u2026" } to \`parts\`, or move the lesson into a part's directory.`,
482
+ file: lesson.source.file
483
+ });
484
+ }
386
485
  if (rules.isEnabled("S2")) {
387
486
  mathIssues.push(
388
487
  ...gatePartPages(
@@ -423,10 +522,10 @@ async function compileManifest(courseConfig) {
423
522
  const placementByLessonId = /* @__PURE__ */ new Map();
424
523
  const chapterScoped = numberingSettings.floats === "chapter";
425
524
  let partCursor = 0;
426
- for (const group of sidebar) {
525
+ for (const group of sidebar.slice(0, configuredParts.length)) {
526
+ partCursor += 1;
427
527
  const lessonIds = group.items.filter((item) => typeof item === "string").map(lessonIdFromSlug);
428
528
  if (lessonIds.length === 0) continue;
429
- partCursor += 1;
430
529
  const used = {
431
530
  equations: 0,
432
531
  tables: 0,
@@ -467,7 +566,8 @@ async function compileManifest(courseConfig) {
467
566
  lesson.lessonId,
468
567
  scanSectionHeadings(lesson.body, {
469
568
  ...placement ? { prefix: `${placement.part}.${placement.chapter}` } : {},
470
- depth: numberingSettings.sections.depth
569
+ depth: numberingSettings.sections.depth,
570
+ labelFor: (key) => termLabel(lesson.lessonId, key)
471
571
  })
472
572
  ];
473
573
  })
@@ -482,7 +582,8 @@ async function compileManifest(courseConfig) {
482
582
  headingSlugs: (headingsByLessonId.get(lesson.lessonId) ?? []).map(
483
583
  (heading) => heading.slug
484
584
  )
485
- }))
585
+ })),
586
+ nonChapterPages.map((page) => page.slug)
486
587
  )
487
588
  );
488
589
  const manifestParts = configuredParts.map((part, index) => {
@@ -584,6 +685,45 @@ async function compileManifest(courseConfig) {
584
685
  })
585
686
  ])
586
687
  );
688
+ const engineAnchorsFor = (lessonId, body) => {
689
+ const slug = lessonId.replace(/\./g, "/");
690
+ const anchors = /* @__PURE__ */ new Map();
691
+ const keyed = (floats, prefix, owner) => {
692
+ for (const float of floats) anchors.set(`${prefix}-${float.key}`, owner(float.key));
693
+ };
694
+ keyed(equationsBySlug.get(slug) ?? [], "eq", (key) => `the equation labelled eq:${key}`);
695
+ keyed(tablesBySlug.get(slug) ?? [], "tab", (key) => `<Table id="${key}">`);
696
+ keyed(figuresBySlug.get(slug) ?? [], "fig", (key) => `<Figure id="${key}">`);
697
+ keyed(diagramsBySlug.get(slug) ?? [], "dia", (key) => `<Diagram id="${key}">`);
698
+ keyed(examplesBySlug.get(slug) ?? [], "ex", (key) => `<CompactExamples id="${key}">`);
699
+ for (const check of checksBySlug.get(slug) ?? []) {
700
+ anchors.set(knowledgeCheckAnchorId(check.key), `the knowledge check ${check.key}`);
701
+ }
702
+ for (const [name, prefix] of [
703
+ ["Memo", "memo"],
704
+ ["ShortNote", "short-note"]
705
+ ]) {
706
+ const tag = new RegExp(`<${name}\\b[^>]*?\\bid=["']([^"']+)["']`, "g");
707
+ for (const match of body.matchAll(tag)) {
708
+ anchors.set(`${prefix}-${match[1]}`, `<${name} id="${match[1]}">`);
709
+ }
710
+ }
711
+ if (deriveSources(body).length > 0) {
712
+ anchors.set("references", "the References section generated from the page\u2019s citations");
713
+ }
714
+ return anchors;
715
+ };
716
+ mathIssues.push(
717
+ ...validateHeadingAnchors(
718
+ notationInput.lessons.map((lesson) => ({
719
+ file: lesson.source.file,
720
+ body: lesson.body,
721
+ ...lesson.bodyLine === void 0 ? {} : { bodyLine: lesson.bodyLine },
722
+ headings: headingsByLessonId.get(lesson.lessonId) ?? [],
723
+ anchors: engineAnchorsFor(lesson.lessonId, lesson.body)
724
+ }))
725
+ )
726
+ );
587
727
  const titledBlocksFor = (lessonId) => {
588
728
  const slug = lessonId.replace(/\./g, "/");
589
729
  const body = bodyByLessonId.get(lessonId) ?? "";
@@ -656,7 +796,6 @@ async function compileManifest(courseConfig) {
656
796
  title: frontmatter.title,
657
797
  description: frontmatter.description,
658
798
  ...frontmatter.studyMinutes === void 0 ? {} : { studyMinutes: frontmatter.studyMinutes },
659
- status: meta.status,
660
799
  teaches: [...teaches],
661
800
  requires: [...meta.requires],
662
801
  assumptions: [...meta.assumptions],
@@ -683,7 +822,11 @@ async function compileManifest(courseConfig) {
683
822
  definitionsById,
684
823
  availableDefinitionIds
685
824
  ),
686
- cardsByKey
825
+ cardsByKey,
826
+ [
827
+ ...indexGate.derivedByLesson.get(id) ?? [],
828
+ ...assessmentDerived.get(id)?.values() ?? []
829
+ ]
687
830
  )
688
831
  });
689
832
  }
@@ -700,16 +843,15 @@ async function compileManifest(courseConfig) {
700
843
  definitionsById,
701
844
  availableDefinitionIds
702
845
  ),
703
- cardsByKey
846
+ cardsByKey,
847
+ indexGate.derivedByLesson.get(page.id)
704
848
  )
705
849
  })).sort((a, b) => a.id.localeCompare(b.id));
706
850
  const consistencyIssues = runConsistencyChecks({
707
851
  registry,
708
852
  notationInput: withPages,
709
853
  specs: loadNotationSpecs(),
710
- catalog,
711
854
  lintIgnore: loadLintIgnore(),
712
- conventions: courseConfig.conventions,
713
855
  operators: rules.settings("N8"),
714
856
  romanLabels: rules.settings("N10"),
715
857
  accents: rules.settings("N22")
@@ -764,6 +906,7 @@ async function compileManifest(courseConfig) {
764
906
  disclosure: normalizeDisclosure(courseConfig),
765
907
  numbering: numberingSettings,
766
908
  blockLabels,
909
+ cards: normalizeCards(courseConfig),
767
910
  about,
768
911
  notation: {
769
912
  definitions: registry.definitions,
@@ -0,0 +1,65 @@
1
+ import { DiagnosticSeverity } from '../reference/types.js';
2
+ import { Manifest } from './manifest.js';
3
+ import '../curriculum/validation.js';
4
+ import '../reference/consistency.js';
5
+ import '../course-config-rRtME5UV.js';
6
+ import '../rules/catalog.js';
7
+ import '../rules/course-rules.js';
8
+ import './collections.js';
9
+ import '../reference/gate-retired-keys.js';
10
+ import '../reference/lint-ignore.js';
11
+ import '../reference/equations-validate.js';
12
+ import '../reference/tables-validate.js';
13
+ import '../reference/keyed-float-validate.js';
14
+ import '../reference/figures-validate.js';
15
+ import '../reference/diagrams-validate.js';
16
+ import '../reference/examples-validate.js';
17
+ import '../reference/folds-validate.js';
18
+ import '../reference/knowledge-checks-validate.js';
19
+ import '../reference/raw-floats-validate.js';
20
+ import '../reference/lab-katex-validate.js';
21
+ import '../reference/gate-math.js';
22
+ import '../reference/mixed-content.js';
23
+ import '../reference/gloss.js';
24
+ import '../rules/waivers.js';
25
+ import './knowledge-version.js';
26
+ import './lesson-derivation.js';
27
+
28
+ /**
29
+ * Diagnostics only a rendered page can give, kept from one `astro build` for
30
+ * `content warnings`.
31
+ *
32
+ * Most checks read the content tree, so the manifest carries them and every
33
+ * `content` command recomputes them. A few need the page as Astro renders it:
34
+ * a diagram drawn by a wrapper component lays out to a width only its render
35
+ * knows (rule V8, `reference/diagram-fit.ts`). `compiler/astro.ts` reports
36
+ * those as the build finishes and writes them to
37
+ * `build/render-diagnostics.json`, replacing the previous build's; the
38
+ * `content` CLI lists them beside the manifest's under the `render:` source.
39
+ * They are as current as the last build: run `pnpm build` (or `astro build`)
40
+ * after changing a diagram to refresh them.
41
+ */
42
+
43
+ declare const RENDER_DIAGNOSTICS_PATH: string;
44
+ interface RenderDiagnostic {
45
+ readonly code: string;
46
+ readonly severity: DiagnosticSeverity;
47
+ readonly message: string;
48
+ /** The lesson's source file, e.g. `content/docs/motion/range.mdx`; else the built page. */
49
+ readonly file: string;
50
+ readonly lessonId?: string;
51
+ /** The float the finding is about, e.g. a diagram's `id`. */
52
+ readonly key?: string;
53
+ }
54
+ /**
55
+ * The render diagnostics of one built page. A diagram that appears on more
56
+ * than one page (a lesson and its standalone export) is reported once per
57
+ * lesson by {@link writeRenderDiagnostics}.
58
+ */
59
+ declare function pageRenderDiagnostics(manifest: Pick<Manifest, 'lessons'>, page: string, html: string): RenderDiagnostic[];
60
+ /** Replace the last build's render diagnostics with these, deduplicated and sorted. */
61
+ declare function writeRenderDiagnostics(diagnostics: readonly RenderDiagnostic[], path?: string): readonly RenderDiagnostic[];
62
+ /** What the last build reported; `[]` before the first build. */
63
+ declare function readRenderDiagnostics(path?: string): readonly RenderDiagnostic[];
64
+
65
+ export { RENDER_DIAGNOSTICS_PATH, type RenderDiagnostic, pageRenderDiagnostics, readRenderDiagnostics, writeRenderDiagnostics };
@@ -0,0 +1,56 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import {
4
+ diagramTooWideMessage,
5
+ oversizedDiagrams
6
+ } from "../reference/diagram-fit.js";
7
+ const RENDER_DIAGNOSTICS_PATH = join(
8
+ process.cwd(),
9
+ "build",
10
+ "render-diagnostics.json"
11
+ );
12
+ function slugOfPage(page) {
13
+ return page.split(/[\\/]/u).join("/").replace(/(^|\/)index\.html$/u, "").replace(/\.html$/u, "");
14
+ }
15
+ function pageRenderDiagnostics(manifest, page, html) {
16
+ const oversized = oversizedDiagrams(html);
17
+ if (oversized.length === 0) return [];
18
+ const slug = slugOfPage(page);
19
+ const lesson = manifest.lessons.find((candidate) => candidate.slug === slug);
20
+ return oversized.map((diagram) => ({
21
+ code: diagram.code,
22
+ severity: "warning",
23
+ message: diagramTooWideMessage(diagram.key, diagram.width, diagram.limit),
24
+ file: lesson?.file ?? page,
25
+ ...lesson ? { lessonId: lesson.id } : {},
26
+ key: diagram.key
27
+ }));
28
+ }
29
+ function writeRenderDiagnostics(diagnostics, path = RENDER_DIAGNOSTICS_PATH) {
30
+ const unique = /* @__PURE__ */ new Map();
31
+ for (const diagnostic of diagnostics) {
32
+ unique.set(`${diagnostic.file}\0${diagnostic.code}\0${diagnostic.key ?? diagnostic.message}`, diagnostic);
33
+ }
34
+ const sorted = [...unique.values()].sort(
35
+ (a, b) => a.file.localeCompare(b.file) || a.code.localeCompare(b.code) || (a.key ?? "").localeCompare(b.key ?? "")
36
+ );
37
+ mkdirSync(dirname(path), { recursive: true });
38
+ writeFileSync(path, `${JSON.stringify({ diagnostics: sorted }, null, 2)}
39
+ `);
40
+ return sorted;
41
+ }
42
+ function readRenderDiagnostics(path = RENDER_DIAGNOSTICS_PATH) {
43
+ if (!existsSync(path)) return [];
44
+ try {
45
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
46
+ return Array.isArray(parsed.diagnostics) ? parsed.diagnostics : [];
47
+ } catch {
48
+ return [];
49
+ }
50
+ }
51
+ export {
52
+ RENDER_DIAGNOSTICS_PATH,
53
+ pageRenderDiagnostics,
54
+ readRenderDiagnostics,
55
+ writeRenderDiagnostics
56
+ };
@@ -1,11 +1,12 @@
1
1
  import { Manifest } from './manifest.js';
2
2
  import '../curriculum/validation.js';
3
3
  import '../reference/consistency.js';
4
- import '../course-config-Dsnri9Tk.js';
4
+ import '../course-config-rRtME5UV.js';
5
5
  import '../rules/catalog.js';
6
6
  import '../rules/course-rules.js';
7
7
  import './collections.js';
8
8
  import '../reference/types.js';
9
+ import '../reference/gate-retired-keys.js';
9
10
  import '../reference/lint-ignore.js';
10
11
  import '../reference/equations-validate.js';
11
12
  import '../reference/tables-validate.js';
@@ -138,12 +138,6 @@ interface CoursePart {
138
138
  * `##` heading inside a lesson. Removed in the next MINOR release.
139
139
  */
140
140
  type CourseSection = CoursePart;
141
- interface CourseConventions {
142
- /** Notation key a lesson's stated sign convention must resolve to. */
143
- readonly signConventionKey: string;
144
- /** Competency id that also counts as declaring the cash-flow perspective. */
145
- readonly cashFlowPerspectiveCompetency: string;
146
- }
147
141
  /**
148
142
  * A course's edits to the shipped named-operator baseline (`BASE_OPERATORS`),
149
143
  * consumed by the `operator-explained` consistency check (rule N8). An operator
@@ -367,6 +361,27 @@ type BlockLabels = 'icon' | 'icon-word';
367
361
  declare const BLOCK_LABELS_DEFAULT: BlockLabels;
368
362
  /** Validate `CourseConfig.blockLabels` and apply its default. Throws on a bad value. */
369
363
  declare function normalizeBlockLabels(config: CourseConfig): BlockLabels;
364
+ /**
365
+ * How a course's explanation cards start for a reader who has not chosen
366
+ * yet (`cards` in the course settings). Every field is optional;
367
+ * {@link normalizeCards} fills in the defaults.
368
+ */
369
+ interface CourseCards {
370
+ /**
371
+ * Whether "Carry cards" starts on (rule K1, `card-carry.ts`): a kept card
372
+ * stays on screen as the page scrolls and comes along to every page of the
373
+ * course. A reader's own choice always wins. Default `false`.
374
+ */
375
+ readonly carry?: boolean;
376
+ }
377
+ /** {@link CourseCards} with every default applied. */
378
+ interface NormalizedCards {
379
+ readonly carry: boolean;
380
+ }
381
+ /** The engine's defaults: a reader turns carrying on (D8). */
382
+ declare const CARDS_DEFAULTS: NormalizedCards;
383
+ /** Validate `CourseConfig.cards` and apply its defaults. Throws on a bad value. */
384
+ declare function normalizeCards(config: CourseConfig): NormalizedCards;
370
385
  /**
371
386
  * Rule S2's word ranges (`rules.S2.bridgeMinWords` … in the course settings),
372
387
  * in the shape `gate-part-pages.ts` takes. Built by `partPageLimits` in
@@ -382,15 +397,33 @@ interface NormalizedPartPages {
382
397
  readonly maxWords: number;
383
398
  };
384
399
  }
385
- /** Optional, build-time lesson artifacts exposed to readers. */
400
+ /**
401
+ * Optional, build-time downloads and print actions: each chapter, each part and
402
+ * the course download; every page prints.
403
+ */
386
404
  interface LessonExportsConfig {
387
- /** Emit one self-contained, offline-capable HTML file per lesson. */
405
+ /** Emit one self-contained, offline-capable HTML file per page. */
388
406
  readonly standaloneHtml?: boolean;
389
- /** Offer the browser's native print dialog with explico's lesson print layout. */
407
+ /** Offer the browser's native print dialog with explico's print layout. */
390
408
  readonly print?: boolean;
409
+ /**
410
+ * Emit each chapter's LaTeX source — a zipped folder with explico's class,
411
+ * the chapter and its figures — and the PDF compiled from that folder.
412
+ */
413
+ readonly latex?: boolean;
414
+ /**
415
+ * Paper size of the LaTeX documents: `letter` (default, LaTeX's own) or
416
+ * `a4`. A plain `string` because the settings arrive as JSON.
417
+ */
418
+ readonly latexPaper?: string;
419
+ /**
420
+ * Emit each chapter, each part and the course as Markdown — GitHub's
421
+ * flavour with `$…$` math, its figures as PNG — zipped with its assets.
422
+ */
423
+ readonly markdown?: boolean;
391
424
  /**
392
425
  * Published course root used for links that intentionally leave the exported
393
- * lesson. Required at export time when such a link is present.
426
+ * page. Required at export time when such a link is present.
394
427
  */
395
428
  readonly canonicalBaseUrl?: string;
396
429
  }
@@ -398,6 +431,9 @@ interface LessonExportsConfig {
398
431
  interface NormalizedLessonExports {
399
432
  readonly standaloneHtml: boolean;
400
433
  readonly print: boolean;
434
+ readonly latex: boolean;
435
+ readonly latexPaper: 'letter' | 'a4';
436
+ readonly markdown: boolean;
401
437
  readonly canonicalBaseUrl?: string;
402
438
  }
403
439
  interface CourseConfig {
@@ -415,7 +451,12 @@ interface CourseConfig {
415
451
  readonly sections?: readonly CoursePart[];
416
452
  /** Namespaces used by competency / notation `domain` fields. */
417
453
  readonly domains: readonly string[];
418
- readonly conventions: CourseConventions;
454
+ /**
455
+ * @deprecated No longer read: rule S5, the only reader, was retired. A
456
+ * course that still sets it gets a `config-deprecated-key` warning; delete
457
+ * the block. Removed from the type in the next MINOR release.
458
+ */
459
+ readonly conventions?: unknown;
419
460
  /** Reader-facing identity + knowledge version (the `[?]` About panel). */
420
461
  readonly about: CourseAbout;
421
462
  /**
@@ -438,12 +479,14 @@ interface CourseConfig {
438
479
  readonly romanLabels?: CourseRomanLabels;
439
480
  /** @deprecated Moved to `rules.N14` (same shape). Honoured for now; removed in a later MINOR. */
440
481
  readonly abbreviations?: CourseAbbreviations;
441
- /** Per-lesson export actions and downloadable artifacts. Disabled when omitted. */
482
+ /** Per-page export actions and downloadable artifacts. Disabled when omitted. */
442
483
  readonly lessonExports?: LessonExportsConfig;
443
484
  /** Every collapse default: headings, checks, memos, floats, tables. */
444
485
  readonly disclosure?: CourseDisclosure;
445
486
  /** Outline and float numbering. The defaults apply when omitted. */
446
487
  readonly numbering?: CourseNumbering;
488
+ /** How explanation cards start for a reader: "Carry cards" on or off. */
489
+ readonly cards?: CourseCards;
447
490
  /**
448
491
  * How a check's and an example set's heading names its kind: `icon-word`
449
492
  * (default) or `icon` ({@link BlockLabels}). A plain `string` because the
@@ -461,4 +504,4 @@ declare function normalizeLessonExports(config: CourseConfig): NormalizedLessonE
461
504
  */
462
505
  declare function coursePartsOf(config: CourseConfig): readonly CoursePart[];
463
506
 
464
- export { type CourseSection as A, type BlockLabels as B, type CourseConfig as C, DISCLOSURE_DEFAULTS as D, coursePartsOf as E, normalizeBlockLabels as F, normalizeDisclosure as G, normalizeLessonExports as H, normalizeNumbering as I, type LessonExportsConfig as L, type ManifestRules as M, type NormalizedPartPages as N, type ResolvedRules as R, type CourseAbbreviations as a, type NormalizedLessonExports as b, type NormalizedDisclosure as c, type NormalizedNumbering as d, type ConfigDiagnostic as e, type CourseConventions as f, type CourseOperators as g, type CourseRomanLabels as h, type CourseAccents as i, type CourseRulesConfig as j, type RuleSettingsInput as k, catalogIds as l, disabledCodes as m, dropDisabled as n, rulesJsonSchema as o, partPageLimits as p, rulesSnapshot as q, resolveRules as r, BLOCK_LABELS_DEFAULT as s, type CourseAbout as t, type CourseAuthor as u, type CourseDisclosure as v, type CourseHeadingDisclosure as w, type CourseNumbering as x, type CourseOpenDefault as y, type CoursePart as z };
507
+ export { type CourseOpenDefault as A, type BlockLabels as B, type CourseConfig as C, type CoursePart as D, type CourseSection as E, DISCLOSURE_DEFAULTS as F, coursePartsOf as G, normalizeBlockLabels as H, normalizeCards as I, normalizeDisclosure as J, normalizeLessonExports as K, type LessonExportsConfig as L, type ManifestRules as M, type NormalizedLessonExports as N, normalizeNumbering as O, type ResolvedRules as R, type NormalizedDisclosure as a, type NormalizedNumbering as b, type NormalizedCards as c, type ConfigDiagnostic as d, type CourseOperators as e, type CourseRomanLabels as f, type CourseAccents as g, type CourseAbbreviations as h, type NormalizedPartPages as i, type CourseRulesConfig as j, type RuleSettingsInput as k, catalogIds as l, disabledCodes as m, dropDisabled as n, rulesJsonSchema as o, partPageLimits as p, rulesSnapshot as q, resolveRules as r, BLOCK_LABELS_DEFAULT as s, CARDS_DEFAULTS as t, type CourseAbout as u, type CourseAuthor as v, type CourseCards as w, type CourseDisclosure as x, type CourseHeadingDisclosure as y, type CourseNumbering as z };