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
@@ -0,0 +1,299 @@
1
+ import { stripEquationLabels } from "./equations.mjs";
2
+ import { resolveMathGlyphs, stripNotationMarkers } from "./math-glyphs.mjs";
3
+ import {
4
+ equationLinkFor,
5
+ mathSpans,
6
+ pageScope,
7
+ suspendedOperators
8
+ } from "./gate-math.js";
9
+ import { formulaGlyphScope } from "./gloss.js";
10
+ import { indexOfEntry } from "./math-glyphs.mjs";
11
+ const INDEX_FIX = {
12
+ body: (token, subscript) => `Give the indexed symbol its own entry (latex: ${token}), a family entry whose index runs (latex: x_k), or an index entry for the subscript (latex: "{}_${subscript}", label and meaning), introduced with [[key]] or in notation.local`,
13
+ assessment: (token, subscript) => `Give the indexed symbol its own entry (latex: ${token}), a family entry whose index runs (latex: x_k), or an index entry for the subscript (latex: "{}_${subscript}"), introduced in the lesson's prose with [[key]] or in its notation.local`,
14
+ formula: (token, subscript) => `In a formula, wrap the symbol in \\explain{key}{${token}} with an entry for the indexed symbol, declare it under glosses: (latex: ${token}), or wrap the subscript in an index entry's key (\\explain{key}{${subscript}})`
15
+ };
16
+ const DISPLAY_OPEN = /\$\$\s*$/;
17
+ const RANGE_RELATION = /(?:=|<|>|\\(?:in|le|leq|ge|geq|lt|gt|dots|ldots|cdots|to|subseteq|subset)(?![A-Za-z]))/;
18
+ function quadClauses(mathLine) {
19
+ return mathLine.split(/\\quad(?![A-Za-z])/).slice(1).map((clause) => stripNotationMarkers(clause));
20
+ }
21
+ function statesRange(clause, letter) {
22
+ const named = new RegExp(`(?<![A-Za-z\\\\])${letter}(?![A-Za-z])`);
23
+ return named.test(clause) && RANGE_RELATION.test(clause);
24
+ }
25
+ function mathLineAt(latex, offset) {
26
+ const before = latex.slice(0, offset).split(/\\\\/);
27
+ const after = latex.slice(offset).split(/\\\\/);
28
+ return `${before[before.length - 1] ?? ""}${after[0] ?? ""}`;
29
+ }
30
+ function message(lessonId, code, details, place = "body") {
31
+ switch (code) {
32
+ case "index-explained": {
33
+ const index = details.index ?? "";
34
+ const subscript = index.length === 1 ? index : `{${index}}`;
35
+ const what = details.base === "false" ? `has no card, and neither does its base (${details.parts})` : `has a subscript no card explains (${details.parts})`;
36
+ return `${lessonId}: ${JSON.stringify(details.token)} ${what}. ${INDEX_FIX[place](details.token ?? "", subscript)}; a letter in a subscript is an index, so a base-library i, e or d there needs a card (rule N23).`;
37
+ }
38
+ case "index-swallowed":
39
+ return `${lessonId}: \\explain{${details.key}}{${details.token}} wraps an indexed symbol in ${details.key}, whose latex (${details.notation}) has no index, so its card cannot explain ${JSON.stringify(details.index)}. Drop the wrap and give the subscript an index entry (latex: "{}_${details.index}"), or wrap it in an entry for the indexed symbol (rule N23).`;
40
+ case "index-range":
41
+ return `${lessonId}: the running index ${details.letter} (first in ${JSON.stringify(details.token)}) has no stated set. Give ${details.where} a range (range: "1, \\\\dots, n"), state it after a \\quad on the first display line that uses it (\\quad ${details.letter} = 1, \\dots, n), or mark the card fixedIndex: true if ${details.letter} is a label there, not a running index (rule N23).`;
42
+ case "derived-key-taken":
43
+ return `${lessonId}: ${JSON.stringify(details.token)} composes the derived card ${details.key}, but an authored entry has that key. Rename the entry: a key of the form <symbol>.sub.<index> is the build's (rule N23).`;
44
+ }
45
+ }
46
+ function gateIndices(lessons, entries, extras = {}) {
47
+ const diagnostics = [];
48
+ const derivedByLesson = /* @__PURE__ */ new Map();
49
+ for (const lesson of lessons) {
50
+ const file = lesson.source.file;
51
+ const bodyOffset = (lesson.bodyLine ?? 1) - 1;
52
+ const scope = pageScope(lesson, entries);
53
+ const local = new Map(
54
+ lesson.localDefinitions.map((definition) => [definition.key, definition])
55
+ );
56
+ const factsOf = (key) => key === null ? void 0 : local.get(key) ?? entries.get(key);
57
+ const suspended = suspendedOperators(lesson);
58
+ const derived = /* @__PURE__ */ new Map();
59
+ const reported = /* @__PURE__ */ new Set();
60
+ const occurrences = [];
61
+ const spans = mathSpans(lesson.body).sort((left, right) => left.offset - right.offset);
62
+ for (const span of spans) {
63
+ const latex = stripEquationLabels(span.latex);
64
+ let result;
65
+ try {
66
+ result = resolveMathGlyphs(latex, scope, { suspended, derive: true });
67
+ } catch {
68
+ continue;
69
+ }
70
+ const line = span.line + bodyOffset;
71
+ const equation = equationLinkFor(lesson.lessonId, span.latex);
72
+ const at = {
73
+ context: "body",
74
+ severity: "error",
75
+ file,
76
+ line,
77
+ ...equation === void 0 ? {} : { equation }
78
+ };
79
+ const display = DISPLAY_OPEN.test(lesson.body.slice(0, span.offset));
80
+ for (const use of result.derived) {
81
+ if (!derived.has(use.key)) derived.set(use.key, use);
82
+ if (factsOf(use.key) !== void 0 && !reported.has(`taken:${use.key}`)) {
83
+ reported.add(`taken:${use.key}`);
84
+ diagnostics.push({
85
+ ...at,
86
+ code: "derived-key-taken",
87
+ token: use.key,
88
+ message: message(lesson.lessonId, "derived-key-taken", {
89
+ key: use.key,
90
+ token: use.key
91
+ })
92
+ });
93
+ }
94
+ }
95
+ for (const item of result.indices) {
96
+ pushFindings(item, at, lesson.lessonId, "body", factsOf, reported, diagnostics);
97
+ for (const running of item.running) {
98
+ if (running.key === null && running.cover === null) continue;
99
+ occurrences.push({
100
+ letter: running.letter,
101
+ line,
102
+ display,
103
+ mathLine: mathLineAt(latex, running.start),
104
+ token: item.token,
105
+ ownKey: running.key,
106
+ coverKey: running.cover
107
+ });
108
+ }
109
+ }
110
+ }
111
+ const byLetter = /* @__PURE__ */ new Map();
112
+ for (const occurrence of occurrences) {
113
+ const list = byLetter.get(occurrence.letter) ?? [];
114
+ list.push(occurrence);
115
+ byLetter.set(occurrence.letter, list);
116
+ }
117
+ for (const [letter, list] of byLetter) {
118
+ const declared = list.some(
119
+ (occurrence) => [occurrence.ownKey, occurrence.coverKey].some((key) => {
120
+ const facts = factsOf(key);
121
+ return facts?.range !== void 0 || facts?.fixedIndex === true;
122
+ })
123
+ );
124
+ if (declared) continue;
125
+ const firstDisplay = list.find((occurrence) => occurrence.display);
126
+ if (firstDisplay && quadClauses(firstDisplay.mathLine).some((clause) => statesRange(clause, letter))) {
127
+ continue;
128
+ }
129
+ const [first] = list;
130
+ if (!first) continue;
131
+ const card = first.ownKey ?? first.coverKey;
132
+ diagnostics.push({
133
+ context: "body",
134
+ severity: "error",
135
+ code: "index-range",
136
+ file,
137
+ line: first.line,
138
+ token: letter,
139
+ message: message(lesson.lessonId, "index-range", {
140
+ letter,
141
+ token: first.token,
142
+ where: card === null ? `the card of ${letter}` : `the card ${card}`
143
+ })
144
+ });
145
+ }
146
+ if (derived.size > 0) derivedByLesson.set(lesson.lessonId, [...derived.values()]);
147
+ for (const definition of lesson.localDefinitions) {
148
+ if (!definition.formula || indexOfEntry(definition.notation) !== void 0) continue;
149
+ checkFormula(
150
+ definition.formula,
151
+ dedupeRows([...scope, ...definition.glosses]),
152
+ `${lesson.lessonId}: notation.local ${definition.key} formula`,
153
+ file,
154
+ factsOf,
155
+ reported,
156
+ diagnostics
157
+ );
158
+ }
159
+ }
160
+ const cards = new Map(entries);
161
+ for (const entry of entries.values()) {
162
+ if (!entry.formula || indexOfEntry(entry.notation) !== void 0) continue;
163
+ checkFormula(
164
+ entry.formula,
165
+ formulaGlyphScope({
166
+ key: entry.key,
167
+ notation: entry.notation,
168
+ formula: entry.formula,
169
+ glosses: entry.glosses ?? [],
170
+ cardsByKey: cards
171
+ }),
172
+ `notation ${entry.key} formula`,
173
+ entry.source?.file ?? `content/notation/${entry.key}.md`,
174
+ (key) => key === null ? void 0 : entries.get(key),
175
+ /* @__PURE__ */ new Set(),
176
+ diagnostics
177
+ );
178
+ }
179
+ const lessonById = new Map(lessons.map((lesson) => [lesson.lessonId, lesson]));
180
+ const assessmentById = new Map(
181
+ (extras.assessments ?? []).map((assessment) => [assessment.id, assessment])
182
+ );
183
+ for (const link of extras.lessonAssessments ?? []) {
184
+ const lesson = lessonById.get(link.lessonId);
185
+ if (!lesson) continue;
186
+ const scope = pageScope(lesson, entries);
187
+ const local = new Map(
188
+ lesson.localDefinitions.map((definition) => [definition.key, definition])
189
+ );
190
+ const factsOf = (key) => key === null ? void 0 : local.get(key) ?? entries.get(key);
191
+ for (const assessmentId of link.assessmentIds) {
192
+ const assessment = assessmentById.get(assessmentId);
193
+ const reported = /* @__PURE__ */ new Set();
194
+ for (const item of assessment?.items ?? []) {
195
+ for (const field of [
196
+ item.prompt,
197
+ item.explanation,
198
+ ...(item.options ?? []).map((option) => option.label)
199
+ ]) {
200
+ if (typeof field !== "string") continue;
201
+ for (const span of mathSpans(field)) {
202
+ let result;
203
+ try {
204
+ result = resolveMathGlyphs(span.latex, scope, { derive: true });
205
+ } catch {
206
+ continue;
207
+ }
208
+ const at = {
209
+ context: "assessment",
210
+ severity: "error",
211
+ file: `${assessmentId} (${item.id ?? "?"})`,
212
+ line: span.line
213
+ };
214
+ for (const indexed of result.indices) {
215
+ pushFindings(
216
+ indexed,
217
+ at,
218
+ `${link.lessonId}: assessment ${assessmentId}`,
219
+ "assessment",
220
+ factsOf,
221
+ reported,
222
+ diagnostics
223
+ );
224
+ }
225
+ }
226
+ }
227
+ }
228
+ }
229
+ }
230
+ return { diagnostics, derivedByLesson };
231
+ }
232
+ function pushFindings(item, at, owner, place, factsOf, reported, diagnostics) {
233
+ const token = item.token.replace(/\s+/g, "");
234
+ if (item.how === "swallowed" && !reported.has(`swallowed:${token}`)) {
235
+ reported.add(`swallowed:${token}`);
236
+ diagnostics.push({
237
+ ...at,
238
+ code: "index-swallowed",
239
+ token: item.token,
240
+ message: message(owner, "index-swallowed", {
241
+ key: item.key ?? "",
242
+ token: item.token,
243
+ notation: factsOf(item.key)?.notation ?? "",
244
+ index: item.index
245
+ })
246
+ });
247
+ } else if (item.how === "composed" && !item.explained && !reported.has(`explained:${token}`)) {
248
+ reported.add(`explained:${token}`);
249
+ diagnostics.push({
250
+ ...at,
251
+ code: "index-explained",
252
+ token: item.token,
253
+ message: message(
254
+ owner,
255
+ "index-explained",
256
+ {
257
+ token: item.token,
258
+ index: item.index.replace(/^\{([\s\S]*)\}$/, "$1"),
259
+ parts: item.unexplained.map((part) => JSON.stringify(part)).join(", "),
260
+ base: String(item.baseExplained)
261
+ },
262
+ place
263
+ )
264
+ });
265
+ }
266
+ }
267
+ function checkFormula(formula, scope, owner, file, factsOf, reported, diagnostics) {
268
+ let result;
269
+ try {
270
+ result = resolveMathGlyphs(formula, [...scope]);
271
+ } catch {
272
+ return;
273
+ }
274
+ const at = { context: "formula", severity: "error", file };
275
+ for (const item of result.indices) {
276
+ pushFindings(item, at, owner, "formula", factsOf, reported, diagnostics);
277
+ }
278
+ }
279
+ function dedupeRows(rows) {
280
+ const seen = /* @__PURE__ */ new Set();
281
+ return rows.filter((row) => !seen.has(row.key) && Boolean(seen.add(row.key)));
282
+ }
283
+ function indexFindings(indices) {
284
+ return indices.flatMap((item) => {
285
+ if (item.how === "swallowed") {
286
+ return [
287
+ `${item.token}: \\explain{${item.key ?? ""}}{\u2026} names an entry whose latex has no index, so its card cannot explain ${JSON.stringify(item.index)}; give the subscript an index entry ({}_${item.index.length === 1 ? item.index : `{${item.index}}`}) and drop the wrap, or name an entry for the indexed symbol`
288
+ ];
289
+ }
290
+ if (item.how !== "composed" || item.explained) return [];
291
+ return [
292
+ `${item.token}: no card explains ${item.unexplained.map((part) => JSON.stringify(part)).join(", ")}; give the indexed symbol its own entry, a family entry (x_k), or an index entry for the subscript ({}_0) in the page's notation`
293
+ ];
294
+ });
295
+ }
296
+ export {
297
+ gateIndices,
298
+ indexFindings
299
+ };
@@ -58,6 +58,10 @@ interface AssessmentItemLike {
58
58
  readonly id?: string;
59
59
  readonly prompt?: string;
60
60
  readonly explanation?: string;
61
+ /** A choice item's options; a label's math renders like the prompt's. */
62
+ readonly options?: readonly {
63
+ readonly label?: string;
64
+ }[];
61
65
  }
62
66
  interface AssessmentLike {
63
67
  readonly id: string;
@@ -74,12 +78,27 @@ interface MathSpan {
74
78
  /** Index of `latex` in the text it was cut from. */
75
79
  readonly offset: number;
76
80
  }
77
- /** Every `$$…$$` then `$…$` span in `body`, outside code, with its position. */
81
+ /**
82
+ * Every `$$…$$` then `$…$` span in `body`, outside code and quoted JSX props
83
+ * (plain text that never reaches KaTeX; invariant `prop-text`), with its
84
+ * position.
85
+ */
78
86
  declare function mathSpans(body: string): MathSpan[];
87
+ /**
88
+ * One row of a page's glyph table. `fixedIndex` (rule N23) tells the
89
+ * resolver that an entry's index is a fixed label, so it is no family.
90
+ */
79
91
  type ScopeEntry = {
80
92
  key: string;
81
93
  notation: string;
94
+ fixedIndex?: true;
82
95
  };
96
+ /** A definition as a glyph-table row, `fixedIndex` kept. */
97
+ declare function scopeEntry(definition: {
98
+ readonly key: string;
99
+ readonly notation: string;
100
+ readonly fixedIndex?: true;
101
+ }): ScopeEntry;
83
102
  /**
84
103
  * A lesson's flat glyph table: its own `notation.local` entries plus every
85
104
  * shared card it introduces with `[[key]]` / `\explain{key}`. Exported so a
@@ -96,6 +115,17 @@ declare function pageScope(lesson: NotationLessonInput, sharedByKey: ReadonlyMap
96
115
  declare function equationLinkFor(lessonId: string, latex: string): EquationLink | undefined;
97
116
  /** Decorations a lesson suspends (`notation.suspend`, rule N22), by name. */
98
117
  declare function suspendedOperators(lesson: NotationLessonInput): string[];
118
+ /**
119
+ * The `$…$` pairs remark-math reads as math that hold an amount of money
120
+ * instead: `$5 to $6`, `$5–$10`, `$5, and $x`. remark-math pairs any two
121
+ * single `$` on a line, left to right; `mathSpans` follows Pandoc (no space
122
+ * before a closing `$`, no digit after it) and skips them, so such a pair was
123
+ * neither checked as math nor shown as money — the page printed "5 to 6", or
124
+ * the build failed on the letters of "and". A pair is money when its content
125
+ * starts with a digit and either ends in a space or runs straight into a
126
+ * digit after the closing `$`.
127
+ */
128
+ declare function currencyDollars(body: string): MathSpan[];
99
129
  /**
100
130
  * Resolve every rendered-math context against the page glyph table. Returns one
101
131
  * diagnostic per unresolved token; an empty array means the gate passed.
@@ -106,4 +136,4 @@ declare function gateContentMath(input: {
106
136
  readonly lessonAssessments: readonly LessonAssessmentLink[];
107
137
  }): MathGateDiagnostic[];
108
138
 
109
- export { type EquationLink, type MathGateDiagnostic, type MathSpan, type ScopeEntry, equationLinkFor, gateContentMath, mathSpans, pageScope, suspendedOperators };
139
+ export { type AssessmentItemLike, type AssessmentLike, type EquationLink, type LessonAssessmentLink, type MathGateDiagnostic, type MathSpan, type ScopeEntry, currencyDollars, equationLinkFor, gateContentMath, mathSpans, pageScope, scopeEntry, suspendedOperators };
@@ -6,6 +6,7 @@ import {
6
6
  } from "./equations.mjs";
7
7
  import { lintDelimiterSizes } from "./delimiter-size.js";
8
8
  import { formulaGlyphScope, unknownFormulaScopeKeys } from "./gloss.js";
9
+ import { blankPropStrings } from "./gate-prop-text.js";
9
10
  const SEVERITY = {
10
11
  body: "error",
11
12
  formula: "error",
@@ -27,7 +28,7 @@ function lineOf(text, index) {
27
28
  return text.slice(0, index).split("\n").length;
28
29
  }
29
30
  function mathSpans(body) {
30
- const searchable = stripCode(body);
31
+ const searchable = stripCode(blankPropStrings(body));
31
32
  const spans = [];
32
33
  for (const pattern of [DISPLAY_MATH, INLINE_MATH]) {
33
34
  pattern.lastIndex = 0;
@@ -47,11 +48,15 @@ function mathSpans(body) {
47
48
  }
48
49
  return spans;
49
50
  }
50
- function pageScope(lesson, sharedByKey) {
51
- const local = lesson.localDefinitions.map((definition) => ({
51
+ function scopeEntry(definition) {
52
+ return {
52
53
  key: definition.key,
53
- notation: definition.notation
54
- }));
54
+ notation: definition.notation,
55
+ ...definition.fixedIndex ? { fixedIndex: true } : {}
56
+ };
57
+ }
58
+ function pageScope(lesson, sharedByKey) {
59
+ const local = lesson.localDefinitions.map(scopeEntry);
55
60
  const localKeys = new Set(local.map((entry) => entry.key));
56
61
  const referenced = /* @__PURE__ */ new Set();
57
62
  for (const pattern of [TERM_KEY, EXPLAIN_KEY]) {
@@ -61,7 +66,7 @@ function pageScope(lesson, sharedByKey) {
61
66
  for (const key of referenced) {
62
67
  if (localKeys.has(key)) continue;
63
68
  const shared = sharedByKey.get(key);
64
- if (shared) scope.push({ key: shared.key, notation: shared.notation });
69
+ if (shared) scope.push(scopeEntry(shared));
65
70
  }
66
71
  return scope;
67
72
  }
@@ -77,7 +82,7 @@ function equationLinkFor(lessonId, latex) {
77
82
  function suspendedOperators(lesson) {
78
83
  return (lesson.suspended ?? []).map((item) => item.operator);
79
84
  }
80
- function resolveInto(latex, scope, context, file, line, column, fix, out, equation, suspended = []) {
85
+ function resolveInto(latex, scope, context, file, line, column, fix, out, equation, suspended = [], derive = false) {
81
86
  const at = equation === void 0 ? {} : { equation };
82
87
  for (const diagnostic of lintDelimiterSizes(latex)) {
83
88
  const locatedLine = line === void 0 ? void 0 : line + diagnostic.line - 1;
@@ -96,7 +101,8 @@ function resolveInto(latex, scope, context, file, line, column, fix, out, equati
96
101
  let result;
97
102
  try {
98
103
  result = resolveMathGlyphs(stripEquationLabels(latex), [...scope], {
99
- suspended
104
+ suspended,
105
+ derive
100
106
  });
101
107
  } catch (error) {
102
108
  out.push({
@@ -127,6 +133,7 @@ function resolveInto(latex, scope, context, file, line, column, fix, out, equati
127
133
  continue;
128
134
  }
129
135
  const fontHint = "font" in item && typeof item.font === "string" ? ` ${token} is a different symbol from the plain letter: give it its own entry (latex: ${token}) or wrap it in \\explain{key}{${token}}.` : "";
136
+ const lenderHint = "lenders" in item && Array.isArray(item.lenders) && item.lenders.length > 0 ? ` ${item.lenders.join(", ")} ${item.lenders.length === 1 ? "has" : "have"} ${token} with a subscript, and an indexed entry explains its own instances only, never a bare ${token} (rule N4). If ${token} is a different object, give it its own entry (latex: ${token}); if it is shorthand for that entry, wrap it in \\explain{${item.lenders[0]}}{${token}}.` : "";
130
137
  out.push({
131
138
  context,
132
139
  code: "unresolved-glyph",
@@ -135,10 +142,34 @@ function resolveInto(latex, scope, context, file, line, column, fix, out, equati
135
142
  line,
136
143
  token,
137
144
  ...at,
138
- message: `Unresolved notation ${JSON.stringify(token)} in ${context} math.${fontHint} ${fix}`
145
+ message: `Unresolved notation ${JSON.stringify(token)} in ${context} math.${fontHint}${lenderHint} ${fix}`
139
146
  });
140
147
  }
141
148
  }
149
+ function currencyDollars(body) {
150
+ const searchable = stripCode(blankPropStrings(body)).replace(
151
+ DISPLAY_MATH,
152
+ (block) => block.replace(/[^\n]/g, " ")
153
+ );
154
+ const spans = [];
155
+ let lineStart = 0;
156
+ searchable.split("\n").forEach((text, index) => {
157
+ const dollars = [];
158
+ for (let at = 0; at < text.length; at += 1) {
159
+ if (text[at] !== "$" || text[at - 1] === "\\" || text[at - 1] === "$" || text[at + 1] === "$") continue;
160
+ dollars.push(at);
161
+ }
162
+ for (let pair = 0; pair + 1 < dollars.length; pair += 2) {
163
+ const open = dollars[pair];
164
+ const close = dollars[pair + 1];
165
+ const latex = text.slice(open + 1, close);
166
+ if (!/^\d/.test(latex) || !(/\s$/.test(latex) || /^\d/.test(text.slice(close + 1)))) continue;
167
+ spans.push({ latex, line: index + 1, column: open + 2, offset: lineStart + open + 1 });
168
+ }
169
+ lineStart += text.length + 1;
170
+ });
171
+ return spans;
172
+ }
142
173
  const BODY_FIX = "Introduce the symbol with [[key]], define it in notation.local, or wrap it in \\explain{key}{latex}.";
143
174
  function dedupeScope(scope) {
144
175
  const seen = /* @__PURE__ */ new Set();
@@ -215,9 +246,24 @@ function gateContentMath(input) {
215
246
  BODY_FIX,
216
247
  diagnostics,
217
248
  equationLinkFor(lesson.lessonId, span.latex),
218
- suspended
249
+ suspended,
250
+ // Derived cards (rule N23) are lesson-body math, like the render path.
251
+ true
219
252
  );
220
253
  }
254
+ for (const span of currencyDollars(lesson.body)) {
255
+ const shown = span.latex.replace(/\s+$/, "");
256
+ diagnostics.push({
257
+ context: "body",
258
+ code: "currency-dollar",
259
+ severity: SEVERITY.body,
260
+ file,
261
+ line: span.line + bodyOffset,
262
+ column: span.column,
263
+ token: `$${span.latex}$`,
264
+ message: `"$${span.latex}$" reads as math: remark-math pairs any two dollars on a line, so the page shows "${shown}" without them. Write a currency dollar as \\$ (\\$5 to \\$6), or the amount in words.`
265
+ });
266
+ }
221
267
  for (const definition of lesson.localDefinitions) {
222
268
  if (!definition.formula) continue;
223
269
  resolveInto(
@@ -272,7 +318,11 @@ function gateContentMath(input) {
272
318
  const assessment = assessmentById.get(assessmentId);
273
319
  if (!assessment?.items) continue;
274
320
  for (const item of assessment.items) {
275
- for (const field of [item.prompt, item.explanation]) {
321
+ for (const field of [
322
+ item.prompt,
323
+ item.explanation,
324
+ ...(item.options ?? []).map((option) => option.label)
325
+ ]) {
276
326
  if (typeof field !== "string") continue;
277
327
  for (const span of mathSpans(field)) {
278
328
  resolveInto(
@@ -283,7 +333,11 @@ function gateContentMath(input) {
283
333
  span.line,
284
334
  span.column,
285
335
  ASSESSMENT_FIX,
286
- diagnostics
336
+ diagnostics,
337
+ void 0,
338
+ [],
339
+ // Page math, rendered with derived cards (rule N23).
340
+ true
287
341
  );
288
342
  }
289
343
  }
@@ -293,9 +347,11 @@ function gateContentMath(input) {
293
347
  return diagnostics;
294
348
  }
295
349
  export {
350
+ currencyDollars,
296
351
  equationLinkFor,
297
352
  gateContentMath,
298
353
  mathSpans,
299
354
  pageScope,
355
+ scopeEntry,
300
356
  suspendedOperators
301
357
  };
@@ -0,0 +1,17 @@
1
+ import { MathGateDiagnostic } from './gate-math.js';
2
+ import { NotationLessonInput } from './types.js';
3
+
4
+ /**
5
+ * The MDX-expression gate (invariant `prop-text`, with the prop gate). MDX
6
+ * reads `{…}` in a lesson's text as JavaScript: `The set {a} has one element` renders nothing
7
+ * and fails the build with "a is not defined", in a compiled chunk, with no
8
+ * file or line. `validate:content` and `content check` parse no expression, so
9
+ * the build was the first to notice. This gate finds a brace pair in the prose
10
+ * (code, math, JSX tags, comments, references and import/export lines blanked;
11
+ * a directive's attributes, `:::note{icon=""}`, kept out) and says what to
12
+ * write instead. See docs/authoring/components.md#braces-in-prose.
13
+ */
14
+
15
+ declare function gateMdxExpressions(lessons: readonly NotationLessonInput[]): MathGateDiagnostic[];
16
+
17
+ export { gateMdxExpressions };
@@ -0,0 +1,29 @@
1
+ import { lessonLine, proseOnly } from "./gate-prose-notation.js";
2
+ const EXPORT_LINE = /^[ \t]*export\b[^\n]*$/gm;
3
+ const DIRECTIVE = /:{1,3}[A-Za-z][\w-]*(?:\[[^\]\n]*\])?\{[^{}\n]*\}/g;
4
+ function gateMdxExpressions(lessons) {
5
+ const blank = (match) => match.replace(/[^\n]/g, " ");
6
+ const diagnostics = [];
7
+ for (const lesson of lessons) {
8
+ if (!lesson.body.includes("{")) continue;
9
+ const prose = proseOnly(lesson.body).replace(EXPORT_LINE, blank).replace(DIRECTIVE, blank);
10
+ for (const match of prose.matchAll(/\{([^{}\n]*)\}/g)) {
11
+ const index = match.index ?? 0;
12
+ if (lesson.body[index - 1] === "\\") continue;
13
+ const inner = lesson.body.slice(index + 1, index + match[0].length - 1);
14
+ diagnostics.push({
15
+ context: "prose",
16
+ code: "mdx-expression",
17
+ severity: "error",
18
+ file: lesson.source.file,
19
+ line: lessonLine(lesson, index),
20
+ token: `{${inner}}`,
21
+ message: `{${inner}} in the prose is a JavaScript expression to MDX, and the build fails on it ("${inner.trim() || "\u2026"} is not defined"). Write \\{${inner}\\} for braces in the text, or $\\{${inner}\\}$ for a set.`
22
+ });
23
+ }
24
+ }
25
+ return diagnostics;
26
+ }
27
+ export {
28
+ gateMdxExpressions
29
+ };
@@ -0,0 +1,39 @@
1
+ import { MathGateDiagnostic } from './gate-math.js';
2
+ import './types.js';
3
+
4
+ /**
5
+ * The non-chapter page gate (rule S10). A page that is not a chapter — the
6
+ * landing page, a part's overview page, any other page outside a part
7
+ * directory — explains its symbols like a chapter does, but numbers nothing:
8
+ *
9
+ * - no numbered block: `<Table>`, `<Figure>`, `<Diagram>`, `<CompactExamples>`,
10
+ * `<AssessmentSet>`, and no lab (a component imported from a `labs/` module,
11
+ * or any hydrated `client:*` island) — code `non-chapter-float`;
12
+ * - no `\label{eq:…}` on a display equation — the same code;
13
+ * - no `notation.local` / `notation.suspend`: the page recalls symbols and
14
+ * defines none — code `non-chapter-local-notation`;
15
+ * - no `$…$` or `[[…]]` in the plain-text strings a page shows outside its
16
+ * Markdown body (`hero.title`, `hero.tagline`, a `<PartOverview chain>`
17
+ * step), where they would print literally — code `non-chapter-plain-text`.
18
+ *
19
+ * A float needs a chapter number, which such a page does not have; linking
20
+ * the chapter's float (`[[lesson#tab-key]]`) shows it in the reference card
21
+ * instead. The rule is locked: turning it off would not give the page a
22
+ * number, so each finding is an error with no waiver. See
23
+ * docs/authoring/structure-rules.md#s10.
24
+ */
25
+
26
+ /** One non-chapter page, as the manifest compiler loads it. */
27
+ interface NonChapterPageInput {
28
+ readonly file: string;
29
+ readonly data: Readonly<Record<string, unknown>>;
30
+ readonly body: string;
31
+ readonly bodyLine: number;
32
+ }
33
+ /**
34
+ * Check every non-chapter page for what rule S10 keeps off it. Returns one
35
+ * `MathGateDiagnostic` (context `'structure'`, always an error) per finding.
36
+ */
37
+ declare function gateNonChapterPages(pages: readonly NonChapterPageInput[]): MathGateDiagnostic[];
38
+
39
+ export { type NonChapterPageInput, gateNonChapterPages };