explico 0.11.1 → 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 (298) hide show
  1. package/CHANGELOG.md +1292 -1
  2. package/README.md +91 -13
  3. package/dist/compiler/astro.d.ts +11 -4
  4. package/dist/compiler/astro.js +48 -15
  5. package/dist/compiler/cli.d.ts +27 -12
  6. package/dist/compiler/cli.js +96 -25
  7. package/dist/compiler/collections.d.ts +33 -2
  8. package/dist/compiler/collections.js +91 -19
  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 +15 -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 +115 -8
  51. package/dist/compiler/manifest.js +221 -39
  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 +3 -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 +8 -2
  61. package/dist/reference/base-operators.mjs +8 -10
  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/figure-symbols.d.ts +32 -0
  88. package/dist/reference/figure-symbols.js +55 -0
  89. package/dist/reference/figures-validate.d.ts +1 -1
  90. package/dist/reference/gate-abbreviations.d.ts +1 -1
  91. package/dist/reference/gate-abbreviations.js +1 -1
  92. package/dist/reference/gate-course-rules.d.ts +4 -3
  93. package/dist/reference/gate-course-rules.js +9 -4
  94. package/dist/reference/gate-heading-length.d.ts +2 -0
  95. package/dist/reference/gate-heading-length.js +7 -5
  96. package/dist/reference/gate-heading-structure.js +26 -0
  97. package/dist/reference/gate-indices.d.ts +35 -0
  98. package/dist/reference/gate-indices.js +299 -0
  99. package/dist/reference/gate-math.d.ts +32 -2
  100. package/dist/reference/gate-math.js +68 -12
  101. package/dist/reference/gate-mdx-expression.d.ts +17 -0
  102. package/dist/reference/gate-mdx-expression.js +29 -0
  103. package/dist/reference/gate-non-chapter-pages.d.ts +39 -0
  104. package/dist/reference/gate-non-chapter-pages.js +156 -0
  105. package/dist/reference/gate-part-pages.d.ts +4 -2
  106. package/dist/reference/gate-part-pages.js +2 -1
  107. package/dist/reference/gate-prop-text.d.ts +28 -0
  108. package/dist/reference/gate-prop-text.js +106 -0
  109. package/dist/reference/gate-prose-notation.d.ts +3 -1
  110. package/dist/reference/gate-prose-notation.js +4 -2
  111. package/dist/reference/gate-retired-keys.d.ts +35 -0
  112. package/dist/reference/gate-retired-keys.js +58 -0
  113. package/dist/reference/gloss.d.ts +2 -0
  114. package/dist/reference/katex-measure.d.ts +17 -0
  115. package/dist/reference/katex-measure.js +223 -0
  116. package/dist/reference/keyed-float-validate.js +12 -0
  117. package/dist/reference/keyed-jsx-floats.d.mts +2 -2
  118. package/dist/reference/keyed-jsx-floats.mjs +11 -14
  119. package/dist/reference/lab-katex-validate.d.ts +43 -0
  120. package/dist/reference/lab-katex-validate.js +96 -0
  121. package/dist/reference/lab-math-scope.d.ts +1 -0
  122. package/dist/reference/lab-math-scope.js +13 -2
  123. package/dist/reference/lint-ignore.js +1 -1
  124. package/dist/reference/math-glyphs.d.mts +94 -2
  125. package/dist/reference/math-glyphs.mjs +851 -46
  126. package/dist/reference/mixed-content.d.ts +8 -3
  127. package/dist/reference/mixed-content.js +4 -2
  128. package/dist/reference/page-notation.d.ts +39 -7
  129. package/dist/reference/page-notation.js +62 -10
  130. package/dist/reference/raw-floats.mjs +37 -13
  131. package/dist/reference/references.d.ts +11 -1
  132. package/dist/reference/references.js +6 -0
  133. package/dist/reference/registry.js +115 -11
  134. package/dist/reference/rehype-fail-katex-errors.d.mts +9 -0
  135. package/dist/reference/rehype-fail-katex-errors.mjs +19 -0
  136. package/dist/reference/rehype-heading-anchors.mjs +11 -1
  137. package/dist/reference/remark-citation.mjs +0 -0
  138. package/dist/reference/remark-notation.mjs +275 -23
  139. package/dist/reference/render-lab-math.d.ts +13 -2
  140. package/dist/reference/render-lab-math.js +18 -6
  141. package/dist/reference/sections-validate.d.ts +28 -3
  142. package/dist/reference/sections-validate.js +31 -4
  143. package/dist/reference/sections.d.mts +24 -6
  144. package/dist/reference/sections.mjs +92 -17
  145. package/dist/reference/tables-validate.d.ts +1 -1
  146. package/dist/reference/tables-validate.js +24 -1
  147. package/dist/reference/text-measure.d.ts +10 -0
  148. package/dist/reference/text-measure.js +41 -0
  149. package/dist/reference/text-widths.d.ts +10 -0
  150. package/dist/reference/text-widths.js +1238 -0
  151. package/dist/reference/types.d.ts +64 -8
  152. package/dist/rules/catalog.js +223 -32
  153. package/dist/rules/in-effect.d.ts +1 -1
  154. package/dist/rules/rules.schema.json +100 -27
  155. package/dist/rules/settings.d.ts +1 -1
  156. package/dist/rules/settings.js +7 -0
  157. package/dist/session/PreferenceStore.d.ts +18 -1
  158. package/dist/session/PreferenceStore.js +29 -1
  159. package/docs/authoring/README.md +33 -21
  160. package/docs/authoring/agents.md +38 -7
  161. package/docs/authoring/components.md +115 -10
  162. package/docs/authoring/migrating.md +878 -0
  163. package/docs/authoring/notation.md +285 -30
  164. package/docs/authoring/retired.md +1 -0
  165. package/docs/authoring/structure-rules.md +84 -45
  166. package/docs/authoring/structure.md +67 -2
  167. package/docs/authoring/visualizations.md +167 -25
  168. package/package.json +12 -7
  169. package/src/compiler/astro.ts +72 -21
  170. package/src/compiler/cli.ts +143 -35
  171. package/src/compiler/collections.ts +158 -27
  172. package/src/compiler/content-schema.ts +108 -0
  173. package/src/compiler/content-schemas.ts +391 -0
  174. package/src/compiler/course-config.ts +86 -15
  175. package/src/compiler/exports/combined-html.ts +669 -0
  176. package/src/compiler/exports/download-index.ts +36 -0
  177. package/src/compiler/exports/export-chapters.ts +233 -0
  178. package/src/compiler/exports/export-pages.ts +44 -0
  179. package/src/compiler/exports/html-tree.ts +62 -0
  180. package/src/compiler/exports/latex/capture.ts +651 -0
  181. package/src/compiler/exports/latex/compile.ts +168 -0
  182. package/src/compiler/exports/latex/document.ts +479 -0
  183. package/src/compiler/exports/latex/explico.cls +415 -0
  184. package/src/compiler/exports/latex/numbering.ts +74 -0
  185. package/src/compiler/exports/latex/serialize.ts +787 -0
  186. package/src/compiler/exports/latex/texlive-packages.txt +76 -0
  187. package/src/compiler/exports/latex-export.ts +255 -0
  188. package/src/compiler/exports/markdown/serialize.ts +632 -0
  189. package/src/compiler/exports/markdown-export.ts +329 -0
  190. package/src/compiler/exports/standalone-html.ts +39 -70
  191. package/src/compiler/exports/static-tree.ts +944 -0
  192. package/src/compiler/exports/zip.ts +88 -0
  193. package/src/compiler/lesson-derivation.ts +13 -8
  194. package/src/compiler/manifest.ts +387 -40
  195. package/src/compiler/render-diagnostics.ts +111 -0
  196. package/src/components/CourseContents.astro +6 -0
  197. package/src/components/HoverPanel.astro +204 -6
  198. package/src/components/PartOverview.astro +18 -9
  199. package/src/components/abbreviation/AbbreviationGlossary.astro +6 -2
  200. package/src/components/abbreviation/AbbreviationLayer.astro +21 -12
  201. package/src/components/assessments/AssessmentRunner.tsx +7 -1
  202. package/src/components/assessments/AssessmentSet.astro +14 -1
  203. package/src/components/block-heading.ts +38 -0
  204. package/src/components/card-arrow.ts +18 -4
  205. package/src/components/card-carry.ts +406 -0
  206. package/src/components/card-drag.ts +1 -30
  207. package/src/components/card-fetch.ts +87 -0
  208. package/src/components/card-manager.ts +1222 -38
  209. package/src/components/card-memory.ts +5 -1
  210. package/src/components/card-placement.ts +211 -13
  211. package/src/components/card-side.ts +281 -31
  212. package/src/components/citation/CitationLayer.astro +45 -40
  213. package/src/components/diagram/Diagram.astro +123 -44
  214. package/src/components/disclosure/print-as-shown.ts +42 -6
  215. package/src/components/figure/Figure.astro +17 -1
  216. package/src/components/figure/FloatCaption.astro +4 -17
  217. package/src/components/figure/figure-state.ts +26 -17
  218. package/src/components/float-toolbar.ts +274 -0
  219. package/src/components/memo/Memo.astro +21 -10
  220. package/src/components/memo/ShortNote.astro +4 -0
  221. package/src/components/notation/NotationGlossary.astro +22 -17
  222. package/src/components/notation/NotationLayer.astro +300 -79
  223. package/src/components/notation/NotationMath.astro +8 -1
  224. package/src/components/notation/render-notation-math.ts +51 -5
  225. package/src/components/readable-text.ts +25 -0
  226. package/src/components/reference/FloatRefLayer.astro +162 -89
  227. package/src/components/starlight/HeadingAnchorEnhancer.astro +13 -8
  228. package/src/components/starlight/KeyedFloatEnhancer.astro +28 -9
  229. package/src/components/starlight/LayoutPageSidebar.astro +17 -3
  230. package/src/components/starlight/LayoutSocialIcons.astro +79 -29
  231. package/src/components/starlight/LessonExportActions.astro +384 -310
  232. package/src/components/starlight/LessonFooter.astro +66 -8
  233. package/src/components/starlight/PrintController.astro +28 -0
  234. package/src/components/starlight/TextSizeControl.astro +137 -0
  235. package/src/components/starlight/print-controller.ts +380 -0
  236. package/src/components/starlight/section-summary.ts +13 -5
  237. package/src/components/table/Table.astro +14 -22
  238. package/src/components/table/table-truncation.ts +15 -0
  239. package/src/content-config.ts +20 -312
  240. package/src/curriculum/validation.ts +72 -24
  241. package/src/reference/base-abbreviations.mjs +2 -2
  242. package/src/reference/base-operators.mjs +8 -10
  243. package/src/reference/base-roman-labels.mjs +4 -3
  244. package/src/reference/citation-format.mjs +74 -0
  245. package/src/reference/consistency.ts +47 -61
  246. package/src/reference/curriculum-alignment.ts +4 -41
  247. package/src/reference/derived-entries.ts +68 -0
  248. package/src/reference/derived-notation.mjs +121 -0
  249. package/src/reference/diagram-fit.ts +104 -0
  250. package/src/reference/diagram-layout.ts +182 -51
  251. package/src/reference/diagrams-validate.ts +2 -1
  252. package/src/reference/equations-validate.ts +41 -2
  253. package/src/reference/equations.mjs +120 -22
  254. package/src/reference/examples-validate.ts +1 -0
  255. package/src/reference/figure-fit.ts +139 -0
  256. package/src/reference/figure-pictures.ts +96 -0
  257. package/src/reference/figure-symbols.ts +117 -0
  258. package/src/reference/figures-validate.ts +2 -1
  259. package/src/reference/gate-abbreviations.ts +1 -1
  260. package/src/reference/gate-course-rules.ts +22 -7
  261. package/src/reference/gate-heading-length.ts +25 -7
  262. package/src/reference/gate-heading-structure.ts +40 -1
  263. package/src/reference/gate-indices.ts +470 -0
  264. package/src/reference/gate-math.ts +102 -13
  265. package/src/reference/gate-mdx-expression.ts +45 -0
  266. package/src/reference/gate-non-chapter-pages.ts +241 -0
  267. package/src/reference/gate-part-pages.ts +1 -1
  268. package/src/reference/gate-prop-text.ts +145 -0
  269. package/src/reference/gate-prose-notation.ts +19 -5
  270. package/src/reference/gate-retired-keys.ts +101 -0
  271. package/src/reference/gloss.ts +2 -0
  272. package/src/reference/katex-measure.ts +384 -0
  273. package/src/reference/keyed-float-validate.ts +22 -0
  274. package/src/reference/keyed-jsx-floats.mjs +11 -14
  275. package/src/reference/lab-katex-validate.ts +156 -0
  276. package/src/reference/lab-math-scope.ts +15 -1
  277. package/src/reference/lint-ignore.ts +4 -1
  278. package/src/reference/math-glyphs.mjs +851 -46
  279. package/src/reference/mixed-content.ts +11 -2
  280. package/src/reference/page-notation.ts +114 -14
  281. package/src/reference/raw-floats.mjs +37 -13
  282. package/src/reference/references.ts +14 -0
  283. package/src/reference/registry.ts +170 -9
  284. package/src/reference/rehype-fail-katex-errors.mjs +19 -0
  285. package/src/reference/rehype-heading-anchors.mjs +11 -1
  286. package/src/reference/remark-citation.mjs +0 -0
  287. package/src/reference/remark-notation.mjs +275 -23
  288. package/src/reference/render-lab-math.ts +40 -5
  289. package/src/reference/sections-validate.ts +67 -3
  290. package/src/reference/sections.mjs +92 -17
  291. package/src/reference/tables-validate.ts +40 -2
  292. package/src/reference/text-measure.ts +63 -0
  293. package/src/reference/text-widths.ts +133 -0
  294. package/src/reference/types.ts +75 -8
  295. package/src/rules/catalog.ts +241 -31
  296. package/src/rules/settings.ts +10 -0
  297. package/src/session/PreferenceStore.ts +53 -0
  298. package/src/styles/global.css +322 -83
@@ -40,10 +40,16 @@ import {
40
40
  import {
41
41
  validateRawFloats
42
42
  } from "../reference/raw-floats-validate.js";
43
+ import {
44
+ validateLabKatexImports
45
+ } from "../reference/lab-katex-validate.js";
43
46
  import {
44
47
  gateContentMath,
45
- pageScope
48
+ pageScope,
49
+ scopeEntry
46
50
  } from "../reference/gate-math.js";
51
+ import { gateIndices } from "../reference/gate-indices.js";
52
+ import { derivedNotationEntries } from "../reference/derived-entries.js";
47
53
  import {
48
54
  gateProseNotation,
49
55
  gateProseNotationInAssessments
@@ -57,12 +63,18 @@ import { gateCourseRules } from "../reference/gate-course-rules.js";
57
63
  import { gateHeadingLength } from "../reference/gate-heading-length.js";
58
64
  import { gateHeadingStructure } from "../reference/gate-heading-structure.js";
59
65
  import { gatePartPages } from "../reference/gate-part-pages.js";
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";
60
72
  import {
61
73
  scanSectionHeadings,
62
74
  scanTitledBlocks
63
75
  } from "../reference/sections.mjs";
64
76
  import { knowledgeCheckAnchorId } from "../reference/knowledge-checks.mjs";
65
- import { validateSectionRefs } from "../reference/sections-validate.js";
77
+ import { validateHeadingAnchors, validateSectionRefs } from "../reference/sections-validate.js";
66
78
  import {
67
79
  splitMixedContent
68
80
  } from "../reference/mixed-content.js";
@@ -73,6 +85,7 @@ import { resolveReferenceId } from "../reference/references.js";
73
85
  import {
74
86
  coursePartsOf,
75
87
  normalizeBlockLabels,
88
+ normalizeCards,
76
89
  normalizeLessonExports,
77
90
  normalizeDisclosure,
78
91
  normalizeNumbering
@@ -93,9 +106,11 @@ import {
93
106
  } from "./knowledge-version.js";
94
107
  import {
95
108
  loadAbbreviationRegistryInput,
109
+ loadContentRecords,
96
110
  loadCurriculumCatalog,
97
111
  loadNotationDefinitions,
98
112
  loadNotationRegistryInput,
113
+ loadNonChapterPages,
99
114
  loadNotationSpecs,
100
115
  loadPartOverviews,
101
116
  loadSourceRecords
@@ -106,7 +121,7 @@ import {
106
121
  isLessonSlug,
107
122
  lessonIdFromSlug
108
123
  } from "./lesson-derivation.js";
109
- const MANIFEST_SCHEMA_VERSION = 24;
124
+ const MANIFEST_SCHEMA_VERSION = 31;
110
125
  const REPOSITORY_ROOT = process.cwd();
111
126
  const CONTENT_ROOT = join(REPOSITORY_ROOT, "content");
112
127
  const MANIFEST_PATH = join(REPOSITORY_ROOT, "build", "manifest.json");
@@ -119,18 +134,19 @@ function readEnginePackage() {
119
134
  }
120
135
  return enginePackage;
121
136
  }
122
- const CITE_PATTERN = /\[@([a-z0-9][a-z0-9.-]*)(?:;[ \t\r\n]*([^\]]*))?\]/g;
123
137
  function lessonCitations(body) {
124
138
  const withoutCode = body.replace(/```[\s\S]*?```/g, "").replace(/`[^`\n]*`/g, "");
125
139
  const seen = /* @__PURE__ */ new Set();
126
140
  const citations = [];
127
- for (const match of withoutCode.matchAll(CITE_PATTERN)) {
128
- const id = (match[1] ?? "").trim();
129
- const locator = (match[2] ?? "").replace(/\s+/g, " ").trim() || void 0;
130
- const dedupeKey = `${id} ${locator ?? ""}`;
131
- if (seen.has(dedupeKey)) continue;
132
- seen.add(dedupeKey);
133
- 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
+ }
134
150
  }
135
151
  return citations;
136
152
  }
@@ -170,17 +186,21 @@ function lessonNotationResolution(lesson, definitionsById, availableDefinitionId
170
186
  key: reference.key,
171
187
  scope: definition ? definition.kind === "shared" ? "shared" : "local" : "unresolved",
172
188
  definitionId: definitionId ?? null,
173
- 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,
174
192
  line: reference.source.line ?? null,
175
193
  column: reference.source.column ?? null
176
194
  };
177
195
  });
178
196
  }
179
- function lessonNotationBundle(bundle, definitionsById, resolution, cardsByKey) {
197
+ function lessonNotationBundle(bundle, definitionsById, resolution, cardsByKey, derivedUses = []) {
180
198
  const definitions = [];
199
+ const records = [];
181
200
  for (const definitionId of bundle?.definitionIds ?? []) {
182
201
  const definition = definitionsById.get(definitionId);
183
202
  if (!definition) continue;
203
+ records.push(definition);
184
204
  definitions.push({
185
205
  id: definition.id,
186
206
  key: definition.key,
@@ -188,7 +208,6 @@ function lessonNotationBundle(bundle, definitionsById, resolution, cardsByKey) {
188
208
  notation: definition.notation,
189
209
  label: definition.label,
190
210
  summary: definition.summary,
191
- editorialStatus: definition.status,
192
211
  seeAlso: [...definition.seeAlso],
193
212
  glosses: [...definition.glosses],
194
213
  formulaScope: formulaGlyphScope({
@@ -199,13 +218,19 @@ function lessonNotationBundle(bundle, definitionsById, resolution, cardsByKey) {
199
218
  ...definition.formula === void 0 ? {} : { formula: definition.formula }
200
219
  }),
201
220
  ...definition.units === void 0 ? {} : { units: definition.units },
202
- ...definition.formula === void 0 ? {} : { formula: definition.formula }
221
+ ...definition.formula === void 0 ? {} : { formula: definition.formula },
222
+ ...definition.range === void 0 ? {} : { range: definition.range }
203
223
  });
204
224
  }
205
225
  return {
206
226
  bindings: [...bundle?.bindings ?? []].map((binding) => ({ ...binding })),
207
227
  definitionIds: [...bundle?.definitionIds ?? []],
208
228
  definitions,
229
+ derived: derivedNotationEntries(
230
+ derivedUses,
231
+ new Map(records.map((record) => [record.key, record])),
232
+ cardsByKey
233
+ ),
209
234
  resolution: [...resolution]
210
235
  };
211
236
  }
@@ -260,11 +285,19 @@ async function compileManifest(courseConfig) {
260
285
  loadNotationRegistryInput(),
261
286
  loadAbbreviationRegistryInput()
262
287
  ]);
263
- const registry = buildNotationRegistry(notationInput);
288
+ const nonChapterPages = loadNonChapterPages();
289
+ const withPages = {
290
+ ...notationInput,
291
+ lessons: [
292
+ ...notationInput.lessons,
293
+ ...nonChapterPages.map((page) => page.notation)
294
+ ]
295
+ };
296
+ const registry = buildNotationRegistry(withPages);
264
297
  const cardsByKey = new Map(
265
298
  notationInput.sharedDefinitions.map((definition) => [
266
299
  definition.key,
267
- { key: definition.key, notation: definition.notation }
300
+ scopeEntry(definition)
268
301
  ])
269
302
  );
270
303
  const definitionsById = new Map(
@@ -275,23 +308,55 @@ async function compileManifest(courseConfig) {
275
308
  registry.bundles.map((bundle) => [bundle.lessonId, bundle])
276
309
  );
277
310
  const assessmentsInput = catalog.assessments;
278
- 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
+ ];
279
319
  const alignmentIssues = validateNotationAlignment(registry, catalog);
280
320
  const mathEndAbbreviations = {
281
321
  allow: rules.strings("N12", "allow"),
282
322
  deny: rules.strings("N12", "deny")
283
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
+ );
284
347
  const mathIssues = [
348
+ ...indexGate.diagnostics,
285
349
  ...gateContentMath({
286
- notationInput,
350
+ notationInput: withPages,
287
351
  assessments: assessmentsInput,
288
352
  lessonAssessments: catalog.lessons.map((lesson) => ({
289
353
  lessonId: lesson.id,
290
354
  assessmentIds: lesson.assessments
291
355
  }))
292
356
  }),
357
+ ...gateMdxExpressions(withPages.lessons),
293
358
  ...rules.isEnabled("N9") ? [
294
- ...gateProseNotation(notationInput.lessons, {
359
+ ...gateProseNotation(withPages.lessons, {
295
360
  allowSpelledUnits: rules.strings("N9", "allow")
296
361
  }),
297
362
  ...gateProseNotationInAssessments(assessmentsInput, {
@@ -299,16 +364,17 @@ async function compileManifest(courseConfig) {
299
364
  })
300
365
  ] : [],
301
366
  ...rules.isEnabled("N12") ? [
302
- ...gateMathPunctuation(notationInput.lessons, mathEndAbbreviations),
367
+ ...gateMathPunctuation(withPages.lessons, mathEndAbbreviations),
303
368
  ...gateMathPunctuationInAssessments(assessmentsInput, mathEndAbbreviations)
304
369
  ] : [],
305
370
  ...rules.isEnabled("N14") ? gateAbbreviations({
306
- notationInput,
371
+ notationInput: withPages,
307
372
  abbreviations: abbreviationsInput,
308
373
  courseAbbreviations: rules.settings("N14")
309
374
  }) : [],
310
375
  ...rules.isEnabled("S1") ? gateHeadingLength(notationInput.lessons, {
311
- maxLength: rules.number("S1", "maxLength")
376
+ maxLength: rules.number("S1", "maxLength"),
377
+ labelFor: termLabel
312
378
  }) : [],
313
379
  ...gateHeadingStructure(notationInput.lessons, {
314
380
  levels: rules.isEnabled("S6"),
@@ -317,7 +383,9 @@ async function compileManifest(courseConfig) {
317
383
  // The course's own rules (`courseRules`): one pass for every enabled `X`
318
384
  // rule that carries a `forbid` pattern. A disabled one, and a guide rule
319
385
  // with no pattern, do no work here.
320
- ...gateCourseRules(notationInput.lessons, rules.course)
386
+ ...gateCourseRules(withPages.lessons, rules.course),
387
+ // S10 is locked: a float on a non-chapter page has no number to render.
388
+ ...gateNonChapterPages(nonChapterPages)
321
389
  ];
322
390
  const notationLessonById = new Map(
323
391
  notationInput.lessons.map((lesson) => [lesson.lessonId, lesson])
@@ -326,22 +394,46 @@ async function compileManifest(courseConfig) {
326
394
  assessmentsInput.map((assessment) => [assessment.id, assessment])
327
395
  );
328
396
  const assessmentContent = [];
397
+ const assessmentDerived = /* @__PURE__ */ new Map();
329
398
  for (const lesson of catalog.lessons) {
330
399
  if (lesson.assessments.length === 0) continue;
331
400
  const lessonInput = notationLessonById.get(lesson.id);
332
401
  if (!lessonInput) continue;
333
402
  const scope = pageScope(lessonInput, cardsByKey);
403
+ const derived = /* @__PURE__ */ new Map();
404
+ assessmentDerived.set(lesson.id, derived);
405
+ const page = { derive: true, derived };
334
406
  for (const assessmentId of lesson.assessments) {
335
407
  const assessment = assessmentInputById.get(assessmentId);
336
408
  if (!assessment?.items) continue;
337
409
  for (const item of assessment.items) {
338
410
  if (!item.id) continue;
411
+ const numeric = item.type === "numeric" && typeof item.answer?.value === "number" && typeof item.answer.tolerance === "number";
339
412
  assessmentContent.push({
340
413
  lessonId: lesson.id,
341
414
  assessmentId,
342
415
  itemId: item.id,
343
- promptSegments: splitMixedContent(item.prompt ?? "", scope),
344
- 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
+ } : {}
345
437
  });
346
438
  }
347
439
  }
@@ -365,9 +457,31 @@ async function compileManifest(courseConfig) {
365
457
  );
366
458
  const frontmatterById = lessonFrontmatterById();
367
459
  const lessonSlugs = [...notationInput.lessons].map((lesson) => lesson.lessonId.replace(/\./g, "/")).sort();
368
- const configuredParts = coursePartsOf(courseConfig);
369
- const overviewPages = loadPartOverviews(configuredParts);
460
+ const listedParts = coursePartsOf(courseConfig);
461
+ const overviewPages = loadPartOverviews(listedParts);
370
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
+ }
371
485
  if (rules.isEnabled("S2")) {
372
486
  mathIssues.push(
373
487
  ...gatePartPages(
@@ -381,7 +495,7 @@ async function compileManifest(courseConfig) {
381
495
  }
382
496
  const waiverDiagnostics = [];
383
497
  const waiverRules = { courseRules: rules.course };
384
- const waivers = notationInput.lessons.flatMap((lesson) => {
498
+ const waivers = withPages.lessons.flatMap((lesson) => {
385
499
  const collected = collectWaivers(lesson, waiverRules);
386
500
  waiverDiagnostics.push(...collected.diagnostics);
387
501
  return collected.waivers;
@@ -408,10 +522,10 @@ async function compileManifest(courseConfig) {
408
522
  const placementByLessonId = /* @__PURE__ */ new Map();
409
523
  const chapterScoped = numberingSettings.floats === "chapter";
410
524
  let partCursor = 0;
411
- for (const group of sidebar) {
525
+ for (const group of sidebar.slice(0, configuredParts.length)) {
526
+ partCursor += 1;
412
527
  const lessonIds = group.items.filter((item) => typeof item === "string").map(lessonIdFromSlug);
413
528
  if (lessonIds.length === 0) continue;
414
- partCursor += 1;
415
529
  const used = {
416
530
  equations: 0,
417
531
  tables: 0,
@@ -452,7 +566,8 @@ async function compileManifest(courseConfig) {
452
566
  lesson.lessonId,
453
567
  scanSectionHeadings(lesson.body, {
454
568
  ...placement ? { prefix: `${placement.part}.${placement.chapter}` } : {},
455
- depth: numberingSettings.sections.depth
569
+ depth: numberingSettings.sections.depth,
570
+ labelFor: (key) => termLabel(lesson.lessonId, key)
456
571
  })
457
572
  ];
458
573
  })
@@ -467,7 +582,8 @@ async function compileManifest(courseConfig) {
467
582
  headingSlugs: (headingsByLessonId.get(lesson.lessonId) ?? []).map(
468
583
  (heading) => heading.slug
469
584
  )
470
- }))
585
+ })),
586
+ nonChapterPages.map((page) => page.slug)
471
587
  )
472
588
  );
473
589
  const manifestParts = configuredParts.map((part, index) => {
@@ -569,6 +685,45 @@ async function compileManifest(courseConfig) {
569
685
  })
570
686
  ])
571
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
+ );
572
727
  const titledBlocksFor = (lessonId) => {
573
728
  const slug = lessonId.replace(/\./g, "/");
574
729
  const body = bodyByLessonId.get(lessonId) ?? "";
@@ -593,6 +748,14 @@ async function compileManifest(courseConfig) {
593
748
  }
594
749
  );
595
750
  };
751
+ const labKatexDiagnostics = validateLabKatexImports(
752
+ notationInput.lessons.map((lesson) => ({
753
+ lessonId: lesson.lessonId,
754
+ file: lesson.source.file,
755
+ body: lesson.body
756
+ })),
757
+ REPOSITORY_ROOT
758
+ );
596
759
  const rawFloatDiagnostics = validateRawFloats(
597
760
  notationInput.lessons.map((lesson) => ({
598
761
  lessonId: lesson.lessonId,
@@ -633,7 +796,6 @@ async function compileManifest(courseConfig) {
633
796
  title: frontmatter.title,
634
797
  description: frontmatter.description,
635
798
  ...frontmatter.studyMinutes === void 0 ? {} : { studyMinutes: frontmatter.studyMinutes },
636
- status: meta.status,
637
799
  teaches: [...teaches],
638
800
  requires: [...meta.requires],
639
801
  assumptions: [...meta.assumptions],
@@ -660,18 +822,36 @@ async function compileManifest(courseConfig) {
660
822
  definitionsById,
661
823
  availableDefinitionIds
662
824
  ),
663
- cardsByKey
825
+ cardsByKey,
826
+ [
827
+ ...indexGate.derivedByLesson.get(id) ?? [],
828
+ ...assessmentDerived.get(id)?.values() ?? []
829
+ ]
664
830
  )
665
831
  });
666
832
  }
667
833
  lessons.sort((a, b) => a.id.localeCompare(b.id));
834
+ const pages = nonChapterPages.map((page) => ({
835
+ id: page.id,
836
+ slug: page.slug,
837
+ file: page.file,
838
+ notation: lessonNotationBundle(
839
+ bundlesByLesson.get(page.id),
840
+ definitionsById,
841
+ lessonNotationResolution(
842
+ page.notation,
843
+ definitionsById,
844
+ availableDefinitionIds
845
+ ),
846
+ cardsByKey,
847
+ indexGate.derivedByLesson.get(page.id)
848
+ )
849
+ })).sort((a, b) => a.id.localeCompare(b.id));
668
850
  const consistencyIssues = runConsistencyChecks({
669
851
  registry,
670
- notationInput,
852
+ notationInput: withPages,
671
853
  specs: loadNotationSpecs(),
672
- catalog,
673
854
  lintIgnore: loadLintIgnore(),
674
- conventions: courseConfig.conventions,
675
855
  operators: rules.settings("N8"),
676
856
  romanLabels: rules.settings("N10"),
677
857
  accents: rules.settings("N22")
@@ -726,6 +906,7 @@ async function compileManifest(courseConfig) {
726
906
  disclosure: normalizeDisclosure(courseConfig),
727
907
  numbering: numberingSettings,
728
908
  blockLabels,
909
+ cards: normalizeCards(courseConfig),
729
910
  about,
730
911
  notation: {
731
912
  definitions: registry.definitions,
@@ -741,6 +922,7 @@ async function compileManifest(courseConfig) {
741
922
  sidebar,
742
923
  parts: manifestParts,
743
924
  lessons,
925
+ pages,
744
926
  prereqEdges,
745
927
  equations: {
746
928
  labels: equationValidation.labels,
@@ -776,7 +958,7 @@ async function compileManifest(courseConfig) {
776
958
  consistency: dropDisabled(consistencyIssues, off),
777
959
  equations: dropDisabled(equationValidation.diagnostics, off),
778
960
  tables: dropDisabled(tableValidation.diagnostics, off),
779
- figures: dropDisabled(figureValidation.diagnostics, off),
961
+ figures: dropDisabled([...figureValidation.diagnostics, ...labKatexDiagnostics], off),
780
962
  diagrams: dropDisabled(diagramValidation.diagnostics, off),
781
963
  checks: dropDisabled(checkValidation.diagnostics, off),
782
964
  examples: dropDisabled(exampleValidation.diagnostics, off),
@@ -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';
@@ -16,6 +17,7 @@ import '../reference/examples-validate.js';
16
17
  import '../reference/folds-validate.js';
17
18
  import '../reference/knowledge-checks-validate.js';
18
19
  import '../reference/raw-floats-validate.js';
20
+ import '../reference/lab-katex-validate.js';
19
21
  import '../reference/gate-math.js';
20
22
  import '../reference/mixed-content.js';
21
23
  import '../reference/gloss.js';