explico 0.12.0 → 0.13.1

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 +1137 -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 +707 -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 +321 -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
@@ -7,6 +7,7 @@ import {
7
7
  parseFrontmatter
8
8
  } from "@astrojs/markdown-remark";
9
9
  import rehypeKatex from "rehype-katex";
10
+ import remarkDirective from "remark-directive";
10
11
  import remarkMath from "remark-math";
11
12
  import remarkMdx from "remark-mdx";
12
13
  import { createNotationKatexOptions } from "../reference/katex-options.mjs";
@@ -17,12 +18,14 @@ import remarkNotation, {
17
18
  createRemarkNotationOptions
18
19
  } from "../reference/remark-notation.mjs";
19
20
  import remarkShortNote from "../reference/remark-short-note.mjs";
21
+ import { readRenderDiagnostics } from "./render-diagnostics.js";
20
22
  import { isLessonSlug, lessonIdFromSlug } from "./lesson-derivation.js";
21
23
  import { isCourseRuleId, ownerOf, ELEMENTS } from "../rules/catalog.js";
22
24
  import {
23
25
  renderRulesInEffect,
24
26
  rulesInEffect
25
27
  } from "../rules/in-effect.js";
28
+ import { RESERVED_KEY_PREFIXES, reservedKeyPrefix } from "../reference/references.js";
26
29
  const REPOSITORY_ROOT = process.cwd();
27
30
  const CONTENT_ROOT = join(REPOSITORY_ROOT, "content");
28
31
  const DOCS_ROOT = join(CONTENT_ROOT, "docs");
@@ -47,13 +50,26 @@ function ownerFields(code) {
47
50
  function withOwner(diagnostic) {
48
51
  return { ...diagnostic, ...ownerFields(diagnostic.code) };
49
52
  }
53
+ function renderedCliDiagnostic(diagnostic) {
54
+ return {
55
+ code: `render:${diagnostic.code}`,
56
+ ...ownerFields(diagnostic.code),
57
+ severity: diagnosticSeverity(diagnostic.severity),
58
+ message: diagnostic.message,
59
+ file: diagnostic.file
60
+ };
61
+ }
50
62
  function mentions(message, lessonId, file) {
51
63
  return message.includes(lessonId) || message.includes(file) || message.includes(file.replace(/^content\/docs\//, ""));
52
64
  }
53
- function lessonDiagnostics(manifest, lesson) {
65
+ function lessonDiagnostics(manifest, lesson, rendered = readRenderDiagnostics()) {
54
66
  const out = [];
55
67
  const { file, id } = lesson;
56
68
  const assessments = new Set(lesson.assessments);
69
+ const bundle = new Set(lesson.notation.definitionIds);
70
+ const entryFiles = new Set(
71
+ manifest.notation.definitions.filter((definition) => definition.scope.kind === "shared" && bundle.has(definition.id)).map((definition) => definition.source.file)
72
+ );
57
73
  const withFile = (value) => value === void 0 ? {} : { file: value };
58
74
  for (const issue of manifest.diagnostics.curriculum) {
59
75
  if (!mentions(issue.message, id, file)) continue;
@@ -69,7 +85,7 @@ function lessonDiagnostics(manifest, lesson) {
69
85
  ]) {
70
86
  const [group, entry] = diagnostic;
71
87
  const entryFile = entry.source?.file;
72
- if (entry.lessonId !== id && entryFile !== file && !mentions(entry.message, id, file)) {
88
+ if (entry.lessonId !== id && entryFile !== file && !(entryFile !== void 0 && entryFiles.has(entryFile)) && !mentions(entry.message, id, file)) {
73
89
  continue;
74
90
  }
75
91
  out.push({
@@ -80,7 +96,7 @@ function lessonDiagnostics(manifest, lesson) {
80
96
  });
81
97
  }
82
98
  for (const diagnostic of manifest.diagnostics.math) {
83
- const forLesson = diagnostic.file === file || diagnostic.context === "assessment" && [...assessments].some(
99
+ const forLesson = diagnostic.file === file || entryFiles.has(diagnostic.file) || diagnostic.context === "assessment" && [...assessments].some(
84
100
  (assessmentId) => diagnostic.file.startsWith(`${assessmentId} `)
85
101
  );
86
102
  if (!forLesson) continue;
@@ -133,6 +149,10 @@ function lessonDiagnostics(manifest, lesson) {
133
149
  ...withFile(diagnostic.file)
134
150
  });
135
151
  }
152
+ for (const diagnostic of rendered) {
153
+ if (diagnostic.file !== file && diagnostic.lessonId !== id) continue;
154
+ out.push(renderedCliDiagnostic(diagnostic));
155
+ }
136
156
  return out.map(withOwner).sort(
137
157
  (a, b) => a.code.localeCompare(b.code) || a.message.localeCompare(b.message)
138
158
  );
@@ -148,6 +168,11 @@ function processorFor(manifest) {
148
168
  // as the real Astro/MDX build, so `remarkNotation` sees the same tree
149
169
  // shape here that it does there (D7/F-tables-figures parity).
150
170
  remarkMdx,
171
+ // Starlight parses `:::note` asides as directives in the real build
172
+ // (on every page, not only its own `src/content/docs/`); without this
173
+ // an aside's body read as top-level lesson flow here, so its equation
174
+ // took a number the build does not give it.
175
+ remarkDirective,
151
176
  remarkMath,
152
177
  remarkShortNote,
153
178
  remarkMemo,
@@ -171,7 +196,7 @@ function processorFor(manifest) {
171
196
  return processorPromise;
172
197
  }
173
198
  function stripEsm(body) {
174
- return body.replace(/^(?:import|export)\s[^\n]*\n?/gm, "");
199
+ return body.replace(/^(?:import|export)\s[^\n]*/gm, "");
175
200
  }
176
201
  function renderErrorCode(message) {
177
202
  if (/Unresolved notation/i.test(message)) return "render:notation";
@@ -195,21 +220,50 @@ async function renderLesson(manifest, input) {
195
220
  relative(DOCS_ROOT, input.path).replace(/\\/g, "/")
196
221
  );
197
222
  try {
198
- await processor.render(stripEsm(input.body), {
199
- frontmatter: { ...input.frontmatter, lessonId },
200
- fileURL: pathToFileURL(input.path)
201
- });
223
+ await withoutParseLogs(
224
+ () => processor.render(stripEsm(input.body), {
225
+ frontmatter: { ...input.frontmatter, lessonId },
226
+ fileURL: pathToFileURL(input.path)
227
+ })
228
+ );
202
229
  return { rendered: true, diagnostics: [] };
203
230
  } catch (error) {
204
231
  const message = error instanceof Error ? error.message : String(error);
232
+ const { line, column } = error ?? {};
205
233
  return {
206
234
  rendered: false,
207
235
  diagnostics: [
208
- { code: renderErrorCode(message), severity: "error", message }
236
+ {
237
+ code: renderErrorCode(message),
238
+ severity: "error",
239
+ message,
240
+ ...typeof line === "number" ? { line } : {},
241
+ ...typeof column === "number" ? { column } : {}
242
+ }
209
243
  ]
210
244
  };
211
245
  }
212
246
  }
247
+ let quietRenders = 0;
248
+ let consoleError;
249
+ async function withoutParseLogs(work) {
250
+ if (quietRenders++ === 0) {
251
+ const original = console.error;
252
+ consoleError = original;
253
+ console.error = (...args) => {
254
+ if (args[0] instanceof Error && args[0].message.startsWith("Failed to parse Markdown file")) return;
255
+ original(...args);
256
+ };
257
+ }
258
+ try {
259
+ return await work();
260
+ } finally {
261
+ if (--quietRenders === 0 && consoleError !== void 0) {
262
+ console.error = consoleError;
263
+ consoleError = void 0;
264
+ }
265
+ }
266
+ }
213
267
  function estimateTokens(text) {
214
268
  return Math.ceil(text.length / 4);
215
269
  }
@@ -272,6 +326,13 @@ function lessonRaw(lesson) {
272
326
  body: parsed.content.replace(/^\n/, "")
273
327
  };
274
328
  }
329
+ function lessonSource(text) {
330
+ const parsed = parseFrontmatter(text, { frontmatter: "empty-with-spaces" });
331
+ return {
332
+ frontmatter: parsed.frontmatter ?? {},
333
+ body: parsed.content
334
+ };
335
+ }
275
336
  const DOMAIN_DIRS_BY_SECTION = {
276
337
  foundations: [".", "rates", "valuation"],
277
338
  bonds: ["bonds", "rates"],
@@ -317,9 +378,7 @@ function domainSignaturesForSection(section) {
317
378
  signatures: signaturesInFile(path)
318
379
  })).filter((entry) => entry.signatures.length > 0);
319
380
  }
320
- function contentStatus(manifest) {
321
- const draftLessons = manifest.lessons.filter((lesson) => lesson.status === "draft").map((lesson) => lesson.id).sort();
322
- const draftNotation = manifest.notation.definitions.filter((definition) => definition.status === "draft").map((definition) => definition.id).sort();
381
+ function contentStatus(manifest, rendered = readRenderDiagnostics()) {
323
382
  const orphans = manifest.notation.diagnostics.filter((diagnostic) => diagnostic.code === "unused-definition").map((diagnostic) => ({
324
383
  code: `notation:${diagnostic.code}`,
325
384
  ...ownerFields(diagnostic.code),
@@ -343,7 +402,8 @@ function contentStatus(manifest) {
343
402
  ...manifest.diagnostics.consistency.map((d) => d.severity),
344
403
  ...manifest.diagnostics.knowledge.map((d) => d.severity),
345
404
  ...manifest.diagnostics.config.map((d) => d.severity),
346
- ...manifest.diagnostics.waivers.map((d) => d.severity)
405
+ ...manifest.diagnostics.waivers.map((d) => d.severity),
406
+ ...rendered.map((d) => diagnosticSeverity(d.severity))
347
407
  ];
348
408
  const keyedEquations = manifest.lessons.reduce(
349
409
  (count, lesson) => count + lesson.equations.length,
@@ -383,7 +443,6 @@ function contentStatus(manifest) {
383
443
  keyedDiagrams,
384
444
  keyedExamples
385
445
  },
386
- draftDebt: { lessons: draftLessons, notation: draftNotation },
387
446
  orphans,
388
447
  diagnostics: {
389
448
  errors: allDiagnostics.filter((severity) => severity === "error").length,
@@ -405,7 +464,7 @@ const SEVERITY_RANK = {
405
464
  warning: 1,
406
465
  error: 2
407
466
  };
408
- function contentDiagnostics(manifest, minimumSeverity = "info") {
467
+ function contentDiagnostics(manifest, minimumSeverity = "info", rendered = readRenderDiagnostics()) {
409
468
  const lessonFileByHint = /* @__PURE__ */ new Map();
410
469
  for (const lesson of manifest.lessons) {
411
470
  lessonFileByHint.set(lesson.id, lesson.file);
@@ -439,6 +498,10 @@ function contentDiagnostics(manifest, minimumSeverity = "info") {
439
498
  });
440
499
  }
441
500
  }
501
+ for (const diagnostic of rendered) {
502
+ const cli = renderedCliDiagnostic(diagnostic);
503
+ if (SEVERITY_RANK[cli.severity] >= SEVERITY_RANK[minimumSeverity]) diagnostics.push(cli);
504
+ }
442
505
  diagnostics.sort(
443
506
  (left, right) => (left.file ?? "").localeCompare(right.file ?? "") || (left.line ?? 0) - (right.line ?? 0) || (left.column ?? 0) - (right.column ?? 0) || left.code.localeCompare(right.code) || left.message.localeCompare(right.message)
444
507
  );
@@ -448,8 +511,8 @@ function contentDiagnostics(manifest, minimumSeverity = "info") {
448
511
  diagnostics
449
512
  };
450
513
  }
451
- function contentWarnings(manifest) {
452
- const warnings = contentDiagnostics(manifest, "warning").diagnostics.filter(
514
+ function contentWarnings(manifest, rendered = readRenderDiagnostics()) {
515
+ const warnings = contentDiagnostics(manifest, "warning", rendered).diagnostics.filter(
453
516
  (diagnostic) => diagnostic.severity === "warning"
454
517
  );
455
518
  return { count: warnings.length, warnings };
@@ -515,7 +578,6 @@ function contentContext(manifest, rawId) {
515
578
  file: lesson.file,
516
579
  title: lesson.title,
517
580
  description: lesson.description,
518
- status: lesson.status,
519
581
  teaches: lesson.teaches,
520
582
  requires: lesson.requires,
521
583
  assumptions: lesson.assumptions,
@@ -561,7 +623,9 @@ function contentContext(manifest, rawId) {
561
623
  }
562
624
  async function checkOneLesson(manifest, lesson) {
563
625
  const manifestDiagnostics = lessonDiagnostics(manifest, lesson);
564
- const { frontmatter, body } = lessonRaw(lesson);
626
+ const { frontmatter, body } = lessonSource(
627
+ readFileSync(lessonAbsolutePath(lesson), "utf8")
628
+ );
565
629
  const render = await renderLesson(manifest, {
566
630
  body,
567
631
  frontmatter,
@@ -611,17 +675,18 @@ ${untracked}`;
611
675
  }
612
676
  async function contentCheck(manifest, options) {
613
677
  let targets;
678
+ const named = [...options.lesson ? [options.lesson] : [], ...options.lessons ?? []];
614
679
  if (options.changed) {
615
680
  const ids = changedLessonIds();
616
681
  targets = manifest.lessons.filter((lesson) => ids.includes(lesson.id));
617
682
  } else {
618
- if (!options.lesson) {
683
+ if (named.length === 0) {
619
684
  throw new ContentCliError(
620
685
  "usage",
621
686
  "content check needs a <lesson> id or --changed."
622
687
  );
623
688
  }
624
- targets = [findLesson(manifest, options.lesson)];
689
+ targets = named.map((id) => findLesson(manifest, id));
625
690
  }
626
691
  const lessons = await Promise.all(
627
692
  targets.map((lesson) => checkOneLesson(manifest, lesson))
@@ -655,7 +720,6 @@ function scaffoldLesson(rawId) {
655
720
  const contents = `---
656
721
  title: ${title}
657
722
  description: One sentence on what the reader can do after this lesson.
658
- editorialStatus: draft
659
723
  teaches: []
660
724
  assumptions: []
661
725
  ---
@@ -698,6 +762,13 @@ function scaffoldTerm(rawKey) {
698
762
  `A notation key is lower-case, dot/hyphen separated, got ${JSON.stringify(rawKey)}.`
699
763
  );
700
764
  }
765
+ const reserved = reservedKeyPrefix(key);
766
+ if (reserved !== void 0) {
767
+ throw new ContentCliError(
768
+ "invalid-id",
769
+ `A notation key may not start with ${RESERVED_KEY_PREFIXES.join(", ")}: [[${key}]] would be read as a reference to that kind of float. Got ${JSON.stringify(rawKey)}.`
770
+ );
771
+ }
701
772
  const relPath = `content/notation/${key}.md`;
702
773
  if (existsSync(join(REPOSITORY_ROOT, relPath))) {
703
774
  throw new ContentCliError(
@@ -715,13 +786,12 @@ sources: []
715
786
  seeAlso: []
716
787
  alignment:
717
788
  kind: general
718
- rationale: Draft scaffold \u2014 set a real alignment before review.
719
- editorialStatus: draft
789
+ rationale: Draft scaffold \u2014 set a real alignment.
720
790
  aiAssisted: true
721
791
  ---
722
792
 
723
793
  Draft definition for [[${key}]]. Replace this body and the placeholder
724
- frontmatter before moving the entry past \`draft\`.
794
+ frontmatter.
725
795
  `;
726
796
  return { path: relPath, contents, companions: [] };
727
797
  }
@@ -737,6 +807,7 @@ export {
737
807
  estimateTokens,
738
808
  findLesson,
739
809
  lessonDiagnostics,
810
+ lessonSource,
740
811
  normalizeLessonId,
741
812
  parseRulesFilter,
742
813
  renderLesson,
@@ -1,5 +1,6 @@
1
1
  import { CurriculumCatalog } from '../curriculum/validation.js';
2
2
  import { NotationLessonInput, NotationReferenceInput, AbbreviationDefinitionInput, NotationRegistryInput, LocalNotationDefinitionInput, NotationSuspension } from '../reference/types.js';
3
+ import { ContentRecord } from '../reference/gate-retired-keys.js';
3
4
 
4
5
  /**
5
6
  * The single path from content files to typed data.
@@ -83,6 +84,13 @@ declare function loadNotationRegistryInput(): Promise<NotationRegistryInput>;
83
84
  * Returned synchronously because the remark transform reads the loader inline.
84
85
  */
85
86
  declare function loadNotationDefinitions(): Record<string, unknown>[];
87
+ /**
88
+ * Every content file's top-level fields and text: the frontmatter of a page,
89
+ * a notation entry or an abbreviation, and the object of a competency,
90
+ * assessment, source or track. The typed loaders above drop keys they do not
91
+ * know; the retired-key gate (`reference/gate-retired-keys.ts`) needs them.
92
+ */
93
+ declare function loadContentRecords(): ContentRecord[];
86
94
  /** Parsed `src/content/sources/*.json` records for `remark-citation`. */
87
95
  declare function loadSourceRecords(): Record<string, unknown>[];
88
96
  /**
@@ -124,4 +132,4 @@ declare function notationSourceLocatorGaps(): {
124
132
  total: number;
125
133
  };
126
134
 
127
- export { type NonChapterPage, type NotationSpec, type PartOverviewPage, extractNotationReferences, loadAbbreviationRegistryInput, loadCurriculumCatalog, loadLessonChecks, loadNonChapterPages, loadNotationDefinitions, loadNotationRegistryInput, loadNotationSpecs, loadPartOverviews, loadSourceRecords, notationSourceLocatorGaps, notationSuspensions };
135
+ export { type NonChapterPage, type NotationSpec, type PartOverviewPage, extractNotationReferences, loadAbbreviationRegistryInput, loadContentRecords, loadCurriculumCatalog, loadLessonChecks, loadNonChapterPages, loadNotationDefinitions, loadNotationRegistryInput, loadNotationSpecs, loadPartOverviews, loadSourceRecords, notationSourceLocatorGaps, notationSuspensions };
@@ -13,6 +13,8 @@ import { parseMemoRef } from "../reference/remark-memo.mjs";
13
13
  import { parseSectionRef } from "../reference/sections.mjs";
14
14
  import { resolveLabel } from "../reference/label.js";
15
15
  import { resolveGlosses } from "../reference/gloss.js";
16
+ import { indexEntryFormula } from "../reference/derived-notation.mjs";
17
+ import { isIndexEntryLatex } from "./content-schemas.js";
16
18
  import {
17
19
  deriveRequires,
18
20
  deriveSources,
@@ -100,12 +102,6 @@ function boolean(value, label) {
100
102
  }
101
103
  return value;
102
104
  }
103
- function status(value, label) {
104
- if (value !== "draft" && value !== "in-review" && value !== "reviewed") {
105
- throw new TypeError(`${label} must be draft, in-review, or reviewed`);
106
- }
107
- return value;
108
- }
109
105
  function alignment(value, label) {
110
106
  const data = record(value, label);
111
107
  if (data.kind === "general") {
@@ -225,7 +221,6 @@ function lessonEntries(prerequisitesById) {
225
221
  const teaches = data.teaches ?? [];
226
222
  lessons.push({
227
223
  id: lessonIdFromSlug(slug),
228
- status: data.editorialStatus ?? "draft",
229
224
  requires: deriveRequires(teaches, prerequisitesById),
230
225
  teaches,
231
226
  assessments: lessonCheckIds(path),
@@ -280,6 +275,18 @@ function glossEntries(raw, ownerKey, at) {
280
275
  }
281
276
  return glosses;
282
277
  }
278
+ function indexFields(data, at, key, latex, formula) {
279
+ const reads = optionalString(data.reads, `${at}.reads`);
280
+ const range = optionalString(data.range, `${at}.range`);
281
+ const fixedIndex = data.fixedIndex === void 0 ? false : boolean(data.fixedIndex, `${at}.fixedIndex`);
282
+ const template = formula !== void 0 && isIndexEntryLatex(latex) ? formula : void 0;
283
+ return {
284
+ ...reads === void 0 ? {} : { reads },
285
+ ...range === void 0 ? {} : { range },
286
+ ...fixedIndex ? { fixedIndex: true } : {},
287
+ ...template === void 0 ? {} : { formula: indexEntryFormula(template, key), formulaTemplate: template }
288
+ };
289
+ }
283
290
  function localDefinition(raw, file, index) {
284
291
  const at = `${file} notation.local[${index}]`;
285
292
  const data = record(raw, at);
@@ -288,7 +295,6 @@ function localDefinition(raw, file, index) {
288
295
  const meaning = meaningText(data.meaning, `${at}.meaning`);
289
296
  const formula = optionalString(data.formula, `${at}.formula`);
290
297
  const units = optionalString(data.units, `${at}.units`);
291
- const definitionText = [meaning, formula].filter((value) => value !== void 0).join("\n");
292
298
  return {
293
299
  key,
294
300
  notation: latex,
@@ -297,11 +303,15 @@ function localDefinition(raw, file, index) {
297
303
  sources: sourceIds(data.sources, `${at}.sources`),
298
304
  seeAlso: strings(data.seeAlso, `${at}.seeAlso`),
299
305
  alignment: data.alignment === void 0 ? { kind: "general", rationale: "Lesson-local symbol." } : alignment(data.alignment, `${at}.alignment`),
300
- references: extractNotationReferences(definitionText, file, "definition"),
306
+ references: [
307
+ ...extractNotationReferences(meaning, file, "definition"),
308
+ ...formula === void 0 ? [] : extractNotationReferences(formula, file, "formula")
309
+ ],
301
310
  glosses: glossEntries(data.glosses, key, `${at}.glosses`),
302
311
  source: { file },
303
312
  ...formula === void 0 ? {} : { formula },
304
- ...units === void 0 ? {} : { units }
313
+ ...units === void 0 ? {} : { units },
314
+ ...indexFields(data, at, key, latex, formula)
305
315
  };
306
316
  }
307
317
  function sharedNotationEntries() {
@@ -325,15 +335,16 @@ function sharedNotationEntries() {
325
335
  domain: string(data.domain, `${file} domain`),
326
336
  sources: sourceIds(data.sources, `${file} sources`),
327
337
  seeAlso: strings(data.seeAlso, `${file} seeAlso`),
328
- alignment: alignment(data.alignment, `${file} alignment`),
329
- status: status(data.editorialStatus, `${file} editorialStatus`),
330
- aiAssisted: boolean(data.aiAssisted, `${file} aiAssisted`),
338
+ // The collection schema's defaults (`compiler/content-schemas.ts`).
339
+ alignment: data.alignment === void 0 ? { kind: "general", rationale: "General notation." } : alignment(data.alignment, `${file} alignment`),
340
+ aiAssisted: data.aiAssisted === void 0 ? false : boolean(data.aiAssisted, `${file} aiAssisted`),
331
341
  body,
332
342
  references: extractNotationReferences(body, file, void 0),
333
343
  glosses: glossEntries(data.glosses, key, `${file} glosses`),
334
344
  source: { file },
335
345
  ...formula === void 0 ? {} : { formula },
336
- ...units === void 0 ? {} : { units }
346
+ ...units === void 0 ? {} : { units },
347
+ ...indexFields(data, file, key, string(data.latex, `${file} latex`), formula)
337
348
  };
338
349
  });
339
350
  }
@@ -405,7 +416,6 @@ function notationLessonEntries() {
405
416
  );
406
417
  lessons.push({
407
418
  lessonId: lessonIdFromSlug(slug),
408
- status: status(data.editorialStatus, `${file} editorialStatus`),
409
419
  localDefinitions,
410
420
  suspended: notationSuspensions(notation.suspend, localDefinitions, file),
411
421
  references: extractNotationReferences(body, file, void 0),
@@ -432,7 +442,6 @@ function loadNonChapterPages() {
432
442
  bodyLine,
433
443
  notation: {
434
444
  lessonId: id,
435
- status: status(data.editorialStatus ?? "draft", `${file} editorialStatus`),
436
445
  localDefinitions: [],
437
446
  suspended: [],
438
447
  references: extractNotationReferences(body, file, void 0),
@@ -455,6 +464,35 @@ function loadNotationDefinitions() {
455
464
  (path) => parseDocument(path).data
456
465
  );
457
466
  }
467
+ function loadContentRecords() {
468
+ const below = (directory, extensions) => {
469
+ const path = join(CONTENT_ROOT, directory);
470
+ return existsSync(path) ? filesBelow(path, extensions) : [];
471
+ };
472
+ const records = [];
473
+ for (const directory of ["docs", "notation", "abbreviations"]) {
474
+ for (const path of below(directory, [".md", ".mdx"])) {
475
+ const text = readFileSync(path, "utf8");
476
+ records.push({
477
+ file: relative(REPOSITORY_ROOT, path),
478
+ data: parseFrontmatter(text).frontmatter ?? {},
479
+ text
480
+ });
481
+ }
482
+ }
483
+ for (const directory of ["competencies", "assessments", "sources", "tracks"]) {
484
+ for (const path of below(directory, [".json"])) {
485
+ const text = readFileSync(path, "utf8");
486
+ const data = JSON.parse(text);
487
+ records.push({
488
+ file: relative(REPOSITORY_ROOT, path),
489
+ data: data !== null && typeof data === "object" && !Array.isArray(data) ? data : {},
490
+ text
491
+ });
492
+ }
493
+ }
494
+ return records;
495
+ }
458
496
  function loadSourceRecords() {
459
497
  return jsonEntries(
460
498
  join(CONTENT_ROOT, "sources")
@@ -536,6 +574,7 @@ function notationSourceLocatorGaps() {
536
574
  export {
537
575
  extractNotationReferences,
538
576
  loadAbbreviationRegistryInput,
577
+ loadContentRecords,
539
578
  loadCurriculumCatalog,
540
579
  loadLessonChecks,
541
580
  loadNonChapterPages,
@@ -0,0 +1,31 @@
1
+ import { CurriculumIssue } from '../curriculum/validation.js';
2
+ import { ContentRecord } from '../reference/gate-retired-keys.js';
3
+ import { CONTENT_SCHEMAS } from './content-schemas.js';
4
+ import 'astro/zod';
5
+
6
+ /**
7
+ * The content-schema check (invariant `content-schema`). Every content file
8
+ * must satisfy its collection's schema — the same zod schema `astro build`
9
+ * loads it with (`content-schemas.ts`) — so `pnpm validate:content` and
10
+ * `pnpm content check` reject what the build would, at the key's line,
11
+ * instead of passing it and leaving `astro build` to fail with
12
+ * `InvalidContentEntryDataError`.
13
+ */
14
+
15
+ type ContentCollection = keyof typeof CONTENT_SCHEMAS;
16
+ /**
17
+ * The collection a repository-relative content file belongs to, or `null`
18
+ * for one Astro's loaders skip (`src/content-config.ts`): a docs page whose
19
+ * name starts with `_`, a notation or abbreviation file that is not `.md`.
20
+ */
21
+ declare function collectionOf(file: string): ContentCollection | null;
22
+ /**
23
+ * The 1-based line of the value at `path` in a YAML or JSON `text`: each name
24
+ * is searched for after the previous one, and an array index skips that many
25
+ * `- ` items of a YAML list. Falls back to the deepest line found.
26
+ */
27
+ declare function lineOfPath(text: string, path: readonly (string | number)[]): number | undefined;
28
+ /** One `content-schema` issue per schema problem in each record, in record order. */
29
+ declare function validateContentSchemas(records: readonly ContentRecord[]): CurriculumIssue[];
30
+
31
+ export { type ContentCollection, collectionOf, lineOfPath, validateContentSchemas };
@@ -0,0 +1,82 @@
1
+ import { keyLine } from "../reference/gate-retired-keys.js";
2
+ import { CONTENT_SCHEMAS } from "./content-schemas.js";
3
+ function collectionOf(file) {
4
+ const parts = file.replace(/\\/g, "/").split("/");
5
+ if (parts[0] !== "content" || parts.length < 3) return null;
6
+ const directory = parts[1] ?? "";
7
+ const name = parts[parts.length - 1] ?? "";
8
+ if (!Object.hasOwn(CONTENT_SCHEMAS, directory)) return null;
9
+ const collection = directory;
10
+ if (collection === "docs") {
11
+ return /\.mdx?$/.test(name) && !name.startsWith("_") ? collection : null;
12
+ }
13
+ if (collection === "notation" || collection === "abbreviations") {
14
+ return name.endsWith(".md") ? collection : null;
15
+ }
16
+ return name.endsWith(".json") ? collection : null;
17
+ }
18
+ function lineOfPath(text, path) {
19
+ const first = path[0];
20
+ if (typeof first !== "string") return void 0;
21
+ const start = keyLine(text, first);
22
+ if (start === void 0) return void 0;
23
+ const lines = text.split("\n");
24
+ let at = start - 1;
25
+ for (const segment of path.slice(1)) {
26
+ if (typeof segment === "number") {
27
+ let seen = -1;
28
+ let found2 = -1;
29
+ for (let index = at + 1; index < lines.length; index += 1) {
30
+ if (/^\s*-\s/.test(lines[index] ?? "")) seen += 1;
31
+ if (seen === segment) {
32
+ found2 = index;
33
+ break;
34
+ }
35
+ }
36
+ if (found2 === -1) break;
37
+ at = found2;
38
+ continue;
39
+ }
40
+ const name = segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
41
+ const pattern = new RegExp(`^[\\s-]*(?:"${name}"|${name})[ \\t]*:`);
42
+ let found = -1;
43
+ for (let index = at; index < lines.length; index += 1) {
44
+ if (pattern.test(lines[index] ?? "")) {
45
+ found = index;
46
+ break;
47
+ }
48
+ }
49
+ if (found === -1) break;
50
+ at = found;
51
+ }
52
+ return at + 1;
53
+ }
54
+ function validateContentSchemas(records) {
55
+ const issues = [];
56
+ for (const { file, data, text } of records) {
57
+ const collection = collectionOf(file);
58
+ if (!collection) continue;
59
+ const result = CONTENT_SCHEMAS[collection].safeParse(data);
60
+ if (result.success) continue;
61
+ for (const problem of result.error.issues) {
62
+ const path = problem.path.filter(
63
+ (segment) => typeof segment === "string" || typeof segment === "number"
64
+ );
65
+ const line = lineOfPath(text, path);
66
+ const at = line === void 0 ? file : `${file}:${line}`;
67
+ const field = path.length > 0 ? `${path.join(".")}: ` : "";
68
+ issues.push({
69
+ kind: "content-schema",
70
+ message: `${at}: ${field}${problem.message} (the ${collection} collection's schema, which astro build applies too)`,
71
+ file,
72
+ ...line === void 0 ? {} : { line }
73
+ });
74
+ }
75
+ }
76
+ return issues;
77
+ }
78
+ export {
79
+ collectionOf,
80
+ lineOfPath,
81
+ validateContentSchemas
82
+ };