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
@@ -10,7 +10,6 @@ interface CompetencyDefinition {
10
10
  }
11
11
  interface LessonDefinition {
12
12
  readonly id: string;
13
- readonly status: 'draft' | 'in-review' | 'reviewed';
14
13
  readonly requires: readonly string[];
15
14
  readonly teaches: readonly string[];
16
15
  readonly assessments: readonly string[];
@@ -23,6 +22,8 @@ interface AssessmentItemBase {
23
22
  readonly id: string;
24
23
  readonly competencyId: string;
25
24
  readonly evidenceKind: 'direct' | 'transfer';
25
+ readonly prompt?: string;
26
+ readonly explanation?: string;
26
27
  }
27
28
  interface NumericAssessmentItem extends AssessmentItemBase {
28
29
  readonly type: 'numeric';
@@ -45,7 +46,6 @@ interface AssessmentDefinition {
45
46
  }
46
47
  interface SourceDefinition {
47
48
  readonly id: string;
48
- readonly editorialStatus: 'draft' | 'in-review' | 'reviewed';
49
49
  }
50
50
  interface TrackDefinition {
51
51
  readonly id: string;
@@ -59,8 +59,14 @@ interface CurriculumCatalog {
59
59
  readonly tracks: readonly TrackDefinition[];
60
60
  }
61
61
  interface CurriculumIssue {
62
- readonly kind: 'invalid-id' | 'duplicate-id' | 'unknown-reference' | 'duplicate-reference' | 'self-prerequisite' | 'cycle' | 'lesson-order' | 'track-order' | 'assessment-coverage' | 'invalid-assessment' | 'review-state' | 'teaches-support';
62
+ readonly kind: 'invalid-id' | 'duplicate-id' | 'unknown-reference' | 'duplicate-reference' | 'self-prerequisite' | 'cycle' | 'lesson-order' | 'track-order' | 'assessment-coverage' | 'invalid-assessment' | 'missing-assumptions' | 'teaches-support' | 'retired-key' | 'content-schema' | 'part-empty' | 'lesson-outside-parts' | 'unsupported-key' | 'ignored-key' | 'markup-in-prop' | 'citation-in-link';
63
63
  readonly message: string;
64
+ /**
65
+ * The content file the issue is about, when it is about one file rather
66
+ * than the graph (`retired-key`, `content-schema`), and the 1-based line in it.
67
+ */
68
+ readonly file?: string;
69
+ readonly line?: number;
64
70
  /**
65
71
  * `error` (default) blocks the build; `warning` is reported but non-blocking.
66
72
  * New checks enter as warnings and are promoted once they run clean on real
@@ -1,3 +1,5 @@
1
+ import { citationsInLinks, citedIds, withoutLinkCitations } from "../reference/citation-format.mjs";
2
+ const CHECK_TEXT_REFERENCE = /\[\[[^\]\n]*\]\]|\[@[^\]\n]*\]/;
1
3
  const VALID_ID = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;
2
4
  function buildIndex(kind, entries, issues) {
3
5
  const index = /* @__PURE__ */ new Map();
@@ -23,13 +25,7 @@ function buildIndex(kind, entries, issues) {
23
25
  function citedSourceIds(body) {
24
26
  if (!body) return /* @__PURE__ */ new Set();
25
27
  const withoutCode = body.replace(/```[\s\S]*?```/g, "").replace(/`[^`\n]*`/g, "");
26
- const ids = /* @__PURE__ */ new Set();
27
- for (const match of withoutCode.matchAll(
28
- /\[@([a-z0-9][a-z0-9.-]*)(?:;[^\]]*)?\]/g
29
- )) {
30
- ids.add((match[1] ?? "").trim());
31
- }
32
- return ids;
28
+ return citedIds(withoutLinkCitations(withoutCode));
33
29
  }
34
30
  function findDuplicates(values) {
35
31
  const seen = /* @__PURE__ */ new Set();
@@ -173,18 +169,22 @@ function validateCurriculum(catalog) {
173
169
  }
174
170
  }
175
171
  const cited = citedSourceIds(lesson.body);
172
+ if (lesson.body !== void 0) {
173
+ const blank = (code) => code.replace(/[^\n]/g, " ");
174
+ const prose = lesson.body.replace(/```[\s\S]*?```/g, blank).replace(/`[^`\n]*`/g, blank);
175
+ for (const { text } of citationsInLinks(prose)) {
176
+ issues.push({
177
+ kind: "citation-in-link",
178
+ message: `${lesson.id} puts the citation ${text} inside a link's text, where it prints as typed: a link cannot hold the citation's own link. Cite after the link: [the words](https://\u2026) ${text}.`
179
+ });
180
+ }
181
+ }
176
182
  for (const sourceId of lesson.sources) {
177
- const source = sources.get(sourceId);
178
- if (!source) {
183
+ if (!sources.has(sourceId)) {
179
184
  issues.push({
180
185
  kind: "unknown-reference",
181
186
  message: `${lesson.id} references unknown source ${sourceId}`
182
187
  });
183
- } else if (lesson.status === "reviewed" && source.editorialStatus !== "reviewed") {
184
- issues.push({
185
- kind: "review-state",
186
- message: `${lesson.id} is reviewed but source ${sourceId} is ${source.editorialStatus}`
187
- });
188
188
  }
189
189
  if (lesson.body !== void 0 && !cited.has(sourceId)) {
190
190
  issues.push({
@@ -203,7 +203,7 @@ function validateCurriculum(catalog) {
203
203
  }
204
204
  if (lesson.teaches.length > 0 && lesson.assumptions.length === 0) {
205
205
  issues.push({
206
- kind: "review-state",
206
+ kind: "missing-assumptions",
207
207
  message: `${lesson.id} teaches quantitative content but declares no assumptions`
208
208
  });
209
209
  }
@@ -232,10 +232,28 @@ function validateCurriculum(catalog) {
232
232
  message: `${assessment.id}/${item.id} has unknown correct option ${item.correctOptionId}`
233
233
  });
234
234
  }
235
- } else if (!Number.isFinite(item.answer.value) || !Number.isFinite(item.answer.tolerance) || item.answer.tolerance <= 0) {
235
+ } else if (!Number.isFinite(item.answer.value)) {
236
+ issues.push({
237
+ kind: "invalid-assessment",
238
+ message: `${assessment.id}/${item.id} answer.value must be a number`
239
+ });
240
+ } else if (!Number.isFinite(item.answer.tolerance) || item.answer.tolerance <= 0) {
241
+ issues.push({
242
+ kind: "invalid-assessment",
243
+ message: `${assessment.id}/${item.id} answer.tolerance must be a number greater than 0, not ${String(item.answer.tolerance)}: an answer within it of answer.value is correct. For an exact whole-number answer, use a tolerance below one half, such as 0.4.`
244
+ });
245
+ }
246
+ const texts = [
247
+ ["prompt", item.prompt],
248
+ ["explanation", item.explanation],
249
+ ...item.type === "single-choice" ? item.options.map((option) => [`option ${option.id}`, option.label]) : []
250
+ ];
251
+ for (const [field, text] of texts) {
252
+ const found = text === void 0 ? null : CHECK_TEXT_REFERENCE.exec(text);
253
+ if (!found) continue;
236
254
  issues.push({
237
255
  kind: "invalid-assessment",
238
- message: `${assessment.id}/${item.id} has an invalid numeric answer or tolerance`
256
+ message: `${assessment.id}/${item.id} ${field} contains ${found[0]}, which a knowledge check shows exactly as written: its text renders math, not references or citations. Name the equation, section or source in words, or cite it in the lesson text the check follows.`
239
257
  });
240
258
  }
241
259
  const evidence = evidenceByCompetency.get(item.competencyId) ?? {
@@ -24,7 +24,7 @@ export function isBaseAbbreviation(token: any, abbreviations: any): boolean;
24
24
  * `content/abbreviations/*.md` table, not here. This baseline only holds
25
25
  * abbreviations so common in ordinary English/academic writing that spelling
26
26
  * them out on every course would be noise, not help. A course adds a real
27
- * one via `abbreviations.allow` in `course.config.ts`, the same shape as
28
- * `operators.allow` / `romanLabels.allow`.
27
+ * one via `rules.N14.allow` in its course config, the same shape as
28
+ * `rules.N8.allow` / `rules.N10.allow`.
29
29
  */
30
30
  export const BASE_ABBREVIATIONS: Readonly<Set<string>>;
@@ -11,8 +11,8 @@
11
11
  * `content/abbreviations/*.md` table, not here. This baseline only holds
12
12
  * abbreviations so common in ordinary English/academic writing that spelling
13
13
  * them out on every course would be noise, not help. A course adds a real
14
- * one via `abbreviations.allow` in `course.config.ts`, the same shape as
15
- * `operators.allow` / `romanLabels.allow`.
14
+ * one via `rules.N14.allow` in its course config, the same shape as
15
+ * `rules.N8.allow` / `rules.N10.allow`.
16
16
  */
17
17
  export const BASE_ABBREVIATIONS = Object.freeze(
18
18
  new Set([
@@ -34,8 +34,8 @@ export function isBaseOperator(name: any, operators: any): boolean;
34
34
  *
35
35
  * A domain operator (`Var`, `Cov`, `Corr`, `sgn`, `erf`, `Tr`, `argmax`, …) is
36
36
  * written `\operatorname{…}` and is NOT here: it earns a card, a
37
- * `notation.local` entry, or an explicit line in `course.config.ts`
38
- * (`operators.allow`).
37
+ * `notation.local` entry, or an explicit line in the course config
38
+ * (`rules.N8.allow`).
39
39
  *
40
40
  * Entries are the KaTeX command without its backslash (`exp`, not `\exp`) for
41
41
  * `op` nodes, and the verbatim body for `\operatorname{…}` nodes (`Var`).
@@ -21,8 +21,8 @@
21
21
  *
22
22
  * A domain operator (`Var`, `Cov`, `Corr`, `sgn`, `erf`, `Tr`, `argmax`, …) is
23
23
  * written `\operatorname{…}` and is NOT here: it earns a card, a
24
- * `notation.local` entry, or an explicit line in `course.config.ts`
25
- * (`operators.allow`).
24
+ * `notation.local` entry, or an explicit line in the course config
25
+ * (`rules.N8.allow`).
26
26
  *
27
27
  * Entries are the KaTeX command without its backslash (`exp`, not `\exp`) for
28
28
  * `op` nodes, and the verbatim body for `\operatorname{…}` nodes (`Var`).
@@ -28,8 +28,9 @@ export function isBaseRomanLabel(name: any, romanLabels: any): boolean;
28
28
  * always a course-specific named quantity (`KE`, `PV`, `Var`-as-a-label) that
29
29
  * genuinely benefits from a hover card, so the shipped baseline holds only
30
30
  * the qualifiers that function like an operator's extremum subscript
31
- * (`x_{\mathrm{opt}}`) rather than naming a quantity of their own. A course
32
- * adds a real one via `romanLabels.allow` in `course.config.ts`, the same
33
- * shape as `operators.allow`.
31
+ * (`x_{\mathrm{opt}}`) rather than naming a quantity of their own. That
32
+ * clears rule N10 only: the indexed symbol still needs a card (rule N23).
33
+ * A course adds a real one via `rules.N10.allow` in its course config, the
34
+ * same shape as `rules.N8.allow`.
34
35
  */
35
36
  export const BASE_ROMAN_LABELS: Readonly<Set<string>>;
@@ -15,9 +15,10 @@
15
15
  * always a course-specific named quantity (`KE`, `PV`, `Var`-as-a-label) that
16
16
  * genuinely benefits from a hover card, so the shipped baseline holds only
17
17
  * the qualifiers that function like an operator's extremum subscript
18
- * (`x_{\mathrm{opt}}`) rather than naming a quantity of their own. A course
19
- * adds a real one via `romanLabels.allow` in `course.config.ts`, the same
20
- * shape as `operators.allow`.
18
+ * (`x_{\mathrm{opt}}`) rather than naming a quantity of their own. That
19
+ * clears rule N10 only: the indexed symbol still needs a card (rule N23).
20
+ * A course adds a real one via `rules.N10.allow` in its course config, the
21
+ * same shape as `rules.N8.allow`.
21
22
  */
22
23
  export const BASE_ROMAN_LABELS = Object.freeze(new Set(['opt']));
23
24
 
@@ -44,6 +44,35 @@ export function referenceLabelFromText(referenceText: string, locator: string |
44
44
  * @returns {string}
45
45
  */
46
46
  export function formatSourceLabel(record: Record<string, unknown>): string;
47
+ /**
48
+ * The citations one bracket holds, in order, as `{ id, locator }`. A
49
+ * `;`-separated part that is only `@id` starts another citation (Pandoc's
50
+ * `[@a; @b]`); any other part belongs to the locator of the citation before
51
+ * it, so a locator may itself contain `;` (`[@a; §1.2; eq. 3]`).
52
+ *
53
+ * @param {string} firstId
54
+ * @param {string | undefined} rest
55
+ * @returns {{ id: string, locator: string }[]}
56
+ */
57
+ export function bracketCitations(firstId: string, rest: string | undefined): {
58
+ id: string;
59
+ locator: string;
60
+ }[];
61
+ /** Every source id a text cites, `[@a; @b]` included. */
62
+ export function citedIds(text: any): Set<any>;
63
+ /**
64
+ * Each citation written inside a link's text, as `{ index, text }`: the
65
+ * offset of its `[@` in `text`, and the citation as written.
66
+ */
67
+ export function citationsInLinks(text: any): {
68
+ index: number;
69
+ text: string;
70
+ }[];
71
+ /**
72
+ * `text` with every citation inside a link's text blanked, offsets kept: what
73
+ * the page cites, for a scan that counts a lesson's sources.
74
+ */
75
+ export function withoutLinkCitations(text: any): string;
47
76
  /**
48
77
  * Pure helpers for turning a source record plus a per-citation locator into
49
78
  * the strings shown in the inline panel and the end-of-page reference list.
@@ -53,3 +82,9 @@ export function formatSourceLabel(record: Record<string, unknown>): string;
53
82
  * file system, or Astro APIs.
54
83
  */
55
84
  export const SOURCE_ID_PATTERN: RegExp;
85
+ /**
86
+ * One `[@…]` bracket: `[@id]`, `[@id; locator]`, or several citations as
87
+ * Pandoc writes them, `[@a; @b; p. 7]`. Group 1 is the first id, group 2 the
88
+ * rest after the first `;`, read by {@link bracketCitations}.
89
+ */
90
+ export const CITATION_BRACKET: RegExp;
@@ -131,3 +131,77 @@ export function formatSourceLabel(record) {
131
131
  .filter(Boolean)
132
132
  .join(', ');
133
133
  }
134
+
135
+ /**
136
+ * One `[@…]` bracket: `[@id]`, `[@id; locator]`, or several citations as
137
+ * Pandoc writes them, `[@a; @b; p. 7]`. Group 1 is the first id, group 2 the
138
+ * rest after the first `;`, read by {@link bracketCitations}.
139
+ */
140
+ export const CITATION_BRACKET = /\[@([a-z0-9][a-z0-9.-]*)(?:;[ \t\r\n]*([^\]]*))?\]/g;
141
+
142
+ /**
143
+ * The citations one bracket holds, in order, as `{ id, locator }`. A
144
+ * `;`-separated part that is only `@id` starts another citation (Pandoc's
145
+ * `[@a; @b]`); any other part belongs to the locator of the citation before
146
+ * it, so a locator may itself contain `;` (`[@a; §1.2; eq. 3]`).
147
+ *
148
+ * @param {string} firstId
149
+ * @param {string | undefined} rest
150
+ * @returns {{ id: string, locator: string }[]}
151
+ */
152
+ export function bracketCitations(firstId, rest) {
153
+ const citations = [{ id: String(firstId).trim(), parts: [] }];
154
+ for (const part of String(rest ?? '').split(';')) {
155
+ const next = /^\s*@([a-z0-9][a-z0-9.-]*)\s*$/.exec(part);
156
+ if (next) citations.push({ id: next[1], parts: [] });
157
+ else if (part.trim()) citations[citations.length - 1].parts.push(part);
158
+ }
159
+ return citations.map(({ id, parts }) => ({
160
+ id,
161
+ locator: parts.join(';').replace(/\s+/g, ' ').trim(),
162
+ }));
163
+ }
164
+
165
+ /** Every source id a text cites, `[@a; @b]` included. */
166
+ export function citedIds(text) {
167
+ const ids = new Set();
168
+ for (const match of String(text).matchAll(CITATION_BRACKET)) {
169
+ for (const { id } of bracketCitations(match[1] ?? '', match[2])) ids.add(id);
170
+ }
171
+ return ids;
172
+ }
173
+
174
+ /**
175
+ * A Markdown link whose text holds a citation, `[as measured in [@id]](url)`:
176
+ * remark-citation leaves link text alone (a link cannot hold the marker's
177
+ * link), so the citation would print as typed. Group 1 is the link text.
178
+ */
179
+ const LINK_WITH_CITATION = /\[((?:[^[\]\n]|\[[^[\]\n]*\])*?\[@[^\]\n]*\](?:[^[\]\n]|\[[^[\]\n]*\])*)\]\(/g;
180
+
181
+ /**
182
+ * Each citation written inside a link's text, as `{ index, text }`: the
183
+ * offset of its `[@` in `text`, and the citation as written.
184
+ */
185
+ export function citationsInLinks(text) {
186
+ const found = [];
187
+ for (const link of String(text).matchAll(LINK_WITH_CITATION)) {
188
+ const start = (link.index ?? 0) + 1;
189
+ for (const citation of (link[1] ?? '').matchAll(/\[@[^\]\n]*\]/g)) {
190
+ found.push({ index: start + (citation.index ?? 0), text: citation[0] });
191
+ }
192
+ }
193
+ return found;
194
+ }
195
+
196
+ /**
197
+ * `text` with every citation inside a link's text blanked, offsets kept: what
198
+ * the page cites, for a scan that counts a lesson's sources.
199
+ */
200
+ export function withoutLinkCitations(text) {
201
+ let out = String(text);
202
+ for (const { index, text: citation } of citationsInLinks(out)) {
203
+ out = out.slice(0, index) + citation.replace(/[^\n]/g, ' ') + out.slice(index + citation.length);
204
+ }
205
+ return out;
206
+ }
207
+
@@ -1,10 +1,11 @@
1
- import { f as CourseConventions, g as CourseOperators, h as CourseRomanLabels, i as CourseAccents } from '../course-config-Dsnri9Tk.js';
2
- import { CurriculumCatalog } from '../curriculum/validation.js';
1
+ import { e as CourseOperators, f as CourseRomanLabels, g as CourseAccents } from '../course-config-rRtME5UV.js';
3
2
  import { NotationSpec } from '../compiler/collections.js';
4
3
  import { LintIgnore } from './lint-ignore.js';
5
4
  import { NotationRegistry, NotationRegistryInput } from './types.js';
6
5
  import '../rules/catalog.js';
7
6
  import '../rules/course-rules.js';
7
+ import '../curriculum/validation.js';
8
+ import './gate-retired-keys.js';
8
9
 
9
10
  /**
10
11
  * Corpus consistency checks (Step D6) — the Tier 1–2 checks from the cleanup
@@ -20,9 +21,7 @@ import '../rules/course-rules.js';
20
21
  * (a) glyph-unique-in-corpus one rendered glyph → one meaning corpus-wide
21
22
  * (b) notation-units / units-vocab every entry has units or `dimensionless`,
22
23
  * unit strings drawn from a controlled vocab
23
- * (c) convention-single-definition a sign / cash-flow-perspective convention a
24
- * lesson uses resolves to one declaration
25
- * (f) weak-local a `notation.local` symbol used only in its
24
+ * (f) weak-local a `notation.local` symbol used only in its
26
25
  * own defining equation and prose
27
26
  * (g) notation-source-locator every notation `sources` entry has a locator
28
27
  * (h) operator-explained a named operator a lesson uses is on the
@@ -45,7 +44,7 @@ import '../rules/course-rules.js';
45
44
  * `src/content/resolution-report.ts`.
46
45
  */
47
46
 
48
- type ConsistencyCode = 'glyph-unique-in-corpus' | 'notation-units' | 'units-vocab' | 'convention-single-definition' | 'weak-local' | 'notation-source-locator' | 'gloss-wants-promoting' | 'card-wants-demoting' | 'gloss-name-shape' | 'operator-explained' | 'roman-label-explained' | 'accent-explained' | 'operator-suspended' | 'operator-suspension-reason' | 'operator-shadowed';
47
+ type ConsistencyCode = 'glyph-unique-in-corpus' | 'notation-units' | 'units-vocab' | 'weak-local' | 'notation-source-locator' | 'gloss-wants-promoting' | 'card-wants-demoting' | 'gloss-name-shape' | 'operator-explained' | 'roman-label-explained' | 'accent-explained' | 'accent-swallowed' | 'operator-suspended' | 'operator-suspension-reason' | 'operator-shadowed';
49
48
  interface ConsistencyDiagnostic {
50
49
  readonly code: ConsistencyCode;
51
50
  /** Tier 3 — repairable violations are warnings; optional advice is info. */
@@ -62,10 +61,7 @@ interface ConsistencyInput {
62
61
  readonly registry: NotationRegistry;
63
62
  readonly notationInput: NotationRegistryInput;
64
63
  readonly specs: readonly NotationSpec[];
65
- readonly catalog: CurriculumCatalog;
66
64
  readonly lintIgnore: LintIgnore;
67
- /** Convention keys this course anchors on (from `content/course.config.ts`). */
68
- readonly conventions: CourseConventions;
69
65
  /** This course's edits to the named-operator baseline (rule N8). */
70
66
  readonly operators?: CourseOperators;
71
67
  /** This course's edits to the roman-label baseline (rule N10). */
@@ -2,7 +2,8 @@ import { effectiveAccentAllowlist } from "./base-accents.mjs";
2
2
  import { effectiveOperatorAllowlist } from "./base-operators.mjs";
3
3
  import { effectiveRomanLabelAllowlist } from "./base-roman-labels.mjs";
4
4
  import { stripEquationLabels } from "./equations.mjs";
5
- import { pageScope } from "./gate-math.js";
5
+ import { pageScope, scopeEntry } from "./gate-math.js";
6
+ import { isIndexEntryLatex } from "../compiler/content-schemas.js";
6
7
  import {
7
8
  collectDecorations,
8
9
  collectOperators,
@@ -12,12 +13,12 @@ import {
12
13
  resolveMathGlyphs
13
14
  } from "./math-glyphs.mjs";
14
15
  import { offVocabularyTokens } from "./units-vocab.js";
15
- const CONVENTION_RE = /\bsign convention\b|\bcash-flow perspective\b|\bholder(?:'s)?(?:\s+\w+){0,2}\s+perspective\b|\bfrom (?:a|one|the|its)(?:\s+\w+){0,3}\s+perspective\b|\breverses? every sign\b|\bpositive (?:means|amounts?)\b/i;
16
+ import { blankPropStrings } from "./gate-prop-text.js";
16
17
  function stripCode(text) {
17
18
  return text.replace(/```[\s\S]*?```/g, (block) => block.replace(/[^\n]/g, " ")).replace(/(^|[^`])`[^`\n]*`/g, (match) => match.replace(/[^\n]/g, " "));
18
19
  }
19
20
  function mathSpans(body) {
20
- const searchable = stripCode(body);
21
+ const searchable = stripCode(blankPropStrings(body));
21
22
  const spans = [];
22
23
  for (const match of searchable.matchAll(/\$\$([\s\S]+?)\$\$/g)) {
23
24
  spans.push({ latex: stripEquationLabels(match[1] ?? "") });
@@ -79,6 +80,7 @@ function checkGlyphUniqueness(input, out) {
79
80
  }
80
81
  function checkNotationUnits(input, out) {
81
82
  for (const spec of input.specs) {
83
+ if (isIndexEntryLatex(spec.latex)) continue;
82
84
  if (spec.units === void 0 && !spec.dimensionless) {
83
85
  if (input.lintIgnore.matches("notation-units", spec.key)) continue;
84
86
  out.push({
@@ -105,35 +107,6 @@ function checkNotationUnits(input, out) {
105
107
  });
106
108
  }
107
109
  }
108
- function checkConventionSingleDefinition(input, out) {
109
- const lessonById = new Map(
110
- input.catalog.lessons.map((lesson) => [lesson.id, lesson])
111
- );
112
- for (const lesson of input.notationInput.lessons) {
113
- const body = lesson.body;
114
- if (!CONVENTION_RE.test(body)) continue;
115
- const meta = lessonById.get(lesson.lessonId);
116
- const { signConventionKey, cashFlowPerspectiveCompetency } = input.conventions;
117
- const anchoredByCompetency = [
118
- ...meta?.teaches ?? [],
119
- ...meta?.requires ?? []
120
- ].includes(cashFlowPerspectiveCompetency);
121
- const anchoredByTerm = new RegExp(
122
- `\\[\\[\\s*${signConventionKey}\\s*\\]\\]`
123
- ).test(body);
124
- if (anchoredByCompetency || anchoredByTerm) continue;
125
- if (input.lintIgnore.matches("convention-single-definition", lesson.lessonId)) {
126
- continue;
127
- }
128
- out.push({
129
- code: "convention-single-definition",
130
- severity: "warning",
131
- lessonId: lesson.lessonId,
132
- file: lesson.source.file,
133
- message: `${lesson.lessonId} states a sign / cash-flow-perspective convention in prose but does not resolve it to a single declaration ([[${signConventionKey}]] or ${cashFlowPerspectiveCompetency})`
134
- });
135
- }
136
- }
137
110
  function checkWeakLocal(input, out) {
138
111
  for (const lesson of input.notationInput.lessons) {
139
112
  const spans = mathSpans(lesson.body).map((span) => span.latex);
@@ -295,7 +268,7 @@ function checkOperatorExplained(input, out) {
295
268
  lessonId: lesson.lessonId,
296
269
  file: lesson.source.file,
297
270
  glyph: operator.name,
298
- message: `${lesson.lessonId}: operator ${JSON.stringify(display)} is neither on the course operator allowlist nor explained by a notation entry; give it a card or notation.local entry (latex ${JSON.stringify(display)}) introduced with [[key]], or add ${JSON.stringify(operator.name)} to operators.allow in course.config.ts`
271
+ message: `${lesson.lessonId}: operator ${JSON.stringify(display)} is neither on the course operator allowlist nor explained by a notation entry; give it a card or notation.local entry (latex ${JSON.stringify(display)}) introduced with [[key]], or add ${JSON.stringify(operator.name)} to rules.N8.allow in the course config (course.config.ts, or the course.settings.json it reads)`
299
272
  });
300
273
  }
301
274
  }
@@ -339,7 +312,7 @@ function checkRomanLabelExplained(input, out) {
339
312
  lessonId: lesson.lessonId,
340
313
  file: lesson.source.file,
341
314
  glyph: label.name,
342
- message: `${lesson.lessonId}: roman label ${JSON.stringify(`\\mathrm{${label.name}}`)} is neither on the course roman-label allowlist nor explained by a notation entry; give it a card or notation.local entry (latex ${JSON.stringify(`\\mathrm{${label.name}}`)}) introduced with [[key]], or add ${JSON.stringify(label.name)} to romanLabels.allow in course.config.ts`
315
+ message: `${lesson.lessonId}: roman label ${JSON.stringify(`\\mathrm{${label.name}}`)} is neither on the course roman-label allowlist nor explained by a notation entry; give it a card or notation.local entry (latex ${JSON.stringify(`\\mathrm{${label.name}}`)}) introduced with [[key]], or add ${JSON.stringify(label.name)} to rules.N10.allow in the course config (course.config.ts, or the course.settings.json it reads)`
343
316
  });
344
317
  }
345
318
  }
@@ -349,7 +322,7 @@ function sharedScopeByKey(input) {
349
322
  return new Map(
350
323
  input.notationInput.sharedDefinitions.map((definition) => [
351
324
  definition.key,
352
- { key: definition.key, notation: definition.notation }
325
+ scopeEntry(definition)
353
326
  ])
354
327
  );
355
328
  }
@@ -363,11 +336,35 @@ function checkAccentExplained(input, out) {
363
336
  for (const span of mathSpans(lesson.body)) {
364
337
  let decorations;
365
338
  try {
366
- ({ decorations } = resolveMathGlyphs(span.latex, scope, { suspended }));
339
+ ({ decorations } = resolveMathGlyphs(span.latex, scope, {
340
+ suspended,
341
+ derive: true
342
+ }));
367
343
  } catch {
368
344
  continue;
369
345
  }
370
346
  for (const decoration of decorations) {
347
+ if (decoration.wholeForm !== null && decoration.wholeFormExplicit) {
348
+ const notation = scope.find((entry) => entry.key === decoration.wholeForm)?.notation ?? "";
349
+ const carries = collectDecorations(notation).some(
350
+ (item) => item.name === decoration.name
351
+ );
352
+ const id = `swallowed:${decoration.token}`;
353
+ if (!carries && !reported.has(id)) {
354
+ reported.add(id);
355
+ if (!input.lintIgnore.matches("accent-swallowed", decoration.name)) {
356
+ out.push({
357
+ code: "accent-swallowed",
358
+ severity: "warning",
359
+ lessonId: lesson.lessonId,
360
+ file: lesson.source.file,
361
+ glyph: decoration.name,
362
+ message: `${lesson.lessonId}: \\explain{${decoration.wholeForm}}{${decoration.token}} wraps the decoration ${JSON.stringify(decoration.name)} in ${decoration.wholeForm}, whose latex (${notation}) has none, so its card cannot explain it; wrap only the symbol under it and give the decoration an operator entry (latex ${JSON.stringify(operatorEntryLatex(decoration.name))}), or name an entry for the decorated form (latex ${JSON.stringify(decoration.token)})`
363
+ });
364
+ }
365
+ }
366
+ continue;
367
+ }
371
368
  if (decoration.wholeForm !== null || decoration.operator !== null) continue;
372
369
  if (suspended.includes(decoration.name)) continue;
373
370
  if (allowlist.has(decoration.name)) continue;
@@ -436,7 +433,6 @@ function checkOperatorSuspension(input, out) {
436
433
  const CHECKS = [
437
434
  checkGlyphUniqueness,
438
435
  checkNotationUnits,
439
- checkConventionSingleDefinition,
440
436
  checkWeakLocal,
441
437
  checkNotationSourceLocator,
442
438
  checkGlossWantsPromoting,
@@ -459,7 +455,6 @@ function consistencyCounts(diagnostics) {
459
455
  "glyph-unique-in-corpus": 0,
460
456
  "notation-units": 0,
461
457
  "units-vocab": 0,
462
- "convention-single-definition": 0,
463
458
  "weak-local": 0,
464
459
  "notation-source-locator": 0,
465
460
  "gloss-wants-promoting": 0,
@@ -468,6 +463,7 @@ function consistencyCounts(diagnostics) {
468
463
  "operator-explained": 0,
469
464
  "roman-label-explained": 0,
470
465
  "accent-explained": 0,
466
+ "accent-swallowed": 0,
471
467
  "operator-suspended": 0,
472
468
  "operator-suspension-reason": 0,
473
469
  "operator-shadowed": 0
@@ -2,13 +2,10 @@ import { compareDiagnostics } from "./references.js";
2
2
  function validateNotationAlignment(registry, catalog) {
3
3
  const diagnostics = [];
4
4
  const competencies = new Map(
5
- catalog.competencies.map((entry) => [
6
- entry.id,
7
- entry
8
- ])
5
+ catalog.competencies.map((entry) => [entry.id, entry])
9
6
  );
10
7
  const lessons = new Map(catalog.lessons.map((entry) => [entry.id, entry]));
11
- const sources = new Map(catalog.sources.map((entry) => [entry.id, entry]));
8
+ const sources = new Set(catalog.sources.map((entry) => entry.id));
12
9
  const trackPositions = buildTrackPositions(catalog, diagnostics);
13
10
  for (const definition of registry.definitions) {
14
11
  validateSources(definition, sources, diagnostics);
@@ -60,15 +57,6 @@ function validateNotationAlignment(registry, catalog) {
60
57
  trackPositions,
61
58
  diagnostics
62
59
  );
63
- if (definition.status === "reviewed" && (lesson.status !== "reviewed" || competency?.editorialStatus !== "reviewed")) {
64
- diagnostics.push(
65
- error(
66
- "alignment-review-state",
67
- `${definition.id} is reviewed while its owning lesson or competency is not reviewed`,
68
- definition
69
- )
70
- );
71
- }
72
60
  }
73
61
  return diagnostics.sort(compareDiagnostics);
74
62
  }
@@ -129,8 +117,7 @@ function validateIntroductionOrder(definition, introductionLessonId, registry, t
129
117
  }
130
118
  function validateSources(definition, sources, diagnostics) {
131
119
  for (const sourceId of definition.sources) {
132
- const source = sources.get(sourceId);
133
- if (!source) {
120
+ if (!sources.has(sourceId)) {
134
121
  diagnostics.push(
135
122
  error(
136
123
  "alignment-unavailable",
@@ -138,14 +125,6 @@ function validateSources(definition, sources, diagnostics) {
138
125
  definition
139
126
  )
140
127
  );
141
- } else if (definition.status === "reviewed" && source.editorialStatus !== "reviewed") {
142
- diagnostics.push(
143
- error(
144
- "alignment-review-state",
145
- `${definition.id} is reviewed while source ${sourceId} is ${source.editorialStatus}`,
146
- definition
147
- )
148
- );
149
128
  }
150
129
  }
151
130
  }
@@ -0,0 +1,45 @@
1
+ import { ManifestDerivedNotation } from '../compiler/manifest.js';
2
+ import { GlyphScopeEntry } from './gloss.js';
3
+ import { DerivedUse, NotationDefinitionRecord } from './types.js';
4
+ import '../curriculum/validation.js';
5
+ import './consistency.js';
6
+ import '../course-config-rRtME5UV.js';
7
+ import '../rules/catalog.js';
8
+ import '../rules/course-rules.js';
9
+ import '../compiler/collections.js';
10
+ import './gate-retired-keys.js';
11
+ import './lint-ignore.js';
12
+ import './equations-validate.js';
13
+ import './tables-validate.js';
14
+ import './keyed-float-validate.js';
15
+ import './figures-validate.js';
16
+ import './diagrams-validate.js';
17
+ import './examples-validate.js';
18
+ import './folds-validate.js';
19
+ import './knowledge-checks-validate.js';
20
+ import './raw-floats-validate.js';
21
+ import './lab-katex-validate.js';
22
+ import './gate-math.js';
23
+ import './mixed-content.js';
24
+ import '../rules/waivers.js';
25
+ import '../compiler/knowledge-version.js';
26
+ import '../compiler/lesson-derivation.js';
27
+
28
+ /**
29
+ * Derived cards (rule N23) as the page payload carries them: each derived
30
+ * card a page's math uses, composed from its base and index entries by
31
+ * `derived-notation.mjs`, with the glyph table its formula resolves against.
32
+ * The manifest builds the ones lesson-body and assessment math use; the page
33
+ * payload (`page-notation.ts`) builds the ones a lab or a diagram registers
34
+ * while it renders, the same way.
35
+ */
36
+
37
+ /**
38
+ * The derived cards for `uses`, sorted by key. `records` holds the page's
39
+ * entries by key, local ones winning; a use whose base or index entry is not
40
+ * there is skipped. A card's formula resolves against the derived symbol,
41
+ * the index entry's glosses, the base, and the cards the formula names.
42
+ */
43
+ declare function derivedNotationEntries(uses: readonly DerivedUse[], records: ReadonlyMap<string, NotationDefinitionRecord>, cardsByKey: ReadonlyMap<string, GlyphScopeEntry>): ManifestDerivedNotation[];
44
+
45
+ export { derivedNotationEntries };