explico 0.12.0 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. package/CHANGELOG.md +1137 -1
  2. package/README.md +91 -13
  3. package/dist/compiler/astro.d.ts +4 -3
  4. package/dist/compiler/astro.js +45 -15
  5. package/dist/compiler/cli.d.ts +26 -12
  6. package/dist/compiler/cli.js +96 -25
  7. package/dist/compiler/collections.d.ts +9 -1
  8. package/dist/compiler/collections.js +55 -16
  9. package/dist/compiler/content-schema.d.ts +31 -0
  10. package/dist/compiler/content-schema.js +82 -0
  11. package/dist/compiler/content-schemas.d.ts +422 -0
  12. package/dist/compiler/content-schemas.js +229 -0
  13. package/dist/compiler/course-config.d.ts +1 -1
  14. package/dist/compiler/course-config.js +30 -2
  15. package/dist/compiler/exports/combined-html.d.ts +97 -0
  16. package/dist/compiler/exports/combined-html.js +492 -0
  17. package/dist/compiler/exports/download-index.d.ts +9 -0
  18. package/dist/compiler/exports/download-index.js +33 -0
  19. package/dist/compiler/exports/export-chapters.d.ts +98 -0
  20. package/dist/compiler/exports/export-chapters.js +164 -0
  21. package/dist/compiler/exports/export-pages.d.ts +48 -0
  22. package/dist/compiler/exports/export-pages.js +22 -0
  23. package/dist/compiler/exports/html-tree.d.ts +22 -0
  24. package/dist/compiler/exports/html-tree.js +47 -0
  25. package/dist/compiler/exports/latex/capture.d.ts +106 -0
  26. package/dist/compiler/exports/latex/capture.js +435 -0
  27. package/dist/compiler/exports/latex/compile.d.ts +73 -0
  28. package/dist/compiler/exports/latex/compile.js +115 -0
  29. package/dist/compiler/exports/latex/document.d.ts +102 -0
  30. package/dist/compiler/exports/latex/document.js +349 -0
  31. package/dist/compiler/exports/latex/explico.cls +415 -0
  32. package/dist/compiler/exports/latex/numbering.d.ts +18 -0
  33. package/dist/compiler/exports/latex/numbering.js +57 -0
  34. package/dist/compiler/exports/latex/serialize.d.ts +112 -0
  35. package/dist/compiler/exports/latex/serialize.js +606 -0
  36. package/dist/compiler/exports/latex-export.d.ts +44 -0
  37. package/dist/compiler/exports/latex-export.js +169 -0
  38. package/dist/compiler/exports/markdown/serialize.d.ts +51 -0
  39. package/dist/compiler/exports/markdown/serialize.js +527 -0
  40. package/dist/compiler/exports/markdown-export.d.ts +39 -0
  41. package/dist/compiler/exports/markdown-export.js +240 -0
  42. package/dist/compiler/exports/standalone-html.d.ts +14 -5
  43. package/dist/compiler/exports/standalone-html.js +23 -51
  44. package/dist/compiler/exports/static-tree.d.ts +253 -0
  45. package/dist/compiler/exports/static-tree.js +625 -0
  46. package/dist/compiler/exports/zip.d.ts +16 -0
  47. package/dist/compiler/exports/zip.js +72 -0
  48. package/dist/compiler/lesson-derivation.d.ts +8 -2
  49. package/dist/compiler/lesson-derivation.js +6 -7
  50. package/dist/compiler/manifest.d.ts +87 -6
  51. package/dist/compiler/manifest.js +174 -31
  52. package/dist/compiler/render-diagnostics.d.ts +65 -0
  53. package/dist/compiler/render-diagnostics.js +56 -0
  54. package/dist/compiler/resolution-report.d.ts +2 -1
  55. package/dist/{course-config-Dsnri9Tk.d.ts → course-config-rRtME5UV.d.ts} +56 -13
  56. package/dist/curriculum/validation.d.ts +9 -3
  57. package/dist/curriculum/validation.js +35 -17
  58. package/dist/reference/base-abbreviations.d.mts +2 -2
  59. package/dist/reference/base-abbreviations.mjs +2 -2
  60. package/dist/reference/base-operators.d.mts +2 -2
  61. package/dist/reference/base-operators.mjs +2 -2
  62. package/dist/reference/base-roman-labels.d.mts +4 -3
  63. package/dist/reference/base-roman-labels.mjs +4 -3
  64. package/dist/reference/citation-format.d.mts +35 -0
  65. package/dist/reference/citation-format.mjs +74 -0
  66. package/dist/reference/consistency.d.ts +5 -9
  67. package/dist/reference/consistency.js +34 -38
  68. package/dist/reference/curriculum-alignment.js +3 -24
  69. package/dist/reference/derived-entries.d.ts +45 -0
  70. package/dist/reference/derived-entries.js +50 -0
  71. package/dist/reference/derived-notation.d.mts +125 -0
  72. package/dist/reference/derived-notation.mjs +121 -0
  73. package/dist/reference/diagram-fit.d.ts +45 -0
  74. package/dist/reference/diagram-fit.js +54 -0
  75. package/dist/reference/diagram-layout.d.ts +59 -10
  76. package/dist/reference/diagram-layout.js +85 -24
  77. package/dist/reference/diagrams-validate.d.ts +1 -1
  78. package/dist/reference/equations-validate.d.ts +1 -1
  79. package/dist/reference/equations-validate.js +26 -1
  80. package/dist/reference/equations.d.mts +27 -0
  81. package/dist/reference/equations.mjs +120 -22
  82. package/dist/reference/examples-validate.d.ts +1 -1
  83. package/dist/reference/figure-fit.d.ts +70 -0
  84. package/dist/reference/figure-fit.js +60 -0
  85. package/dist/reference/figure-pictures.d.ts +27 -0
  86. package/dist/reference/figure-pictures.js +49 -0
  87. package/dist/reference/figures-validate.d.ts +1 -1
  88. package/dist/reference/gate-abbreviations.d.ts +1 -1
  89. package/dist/reference/gate-abbreviations.js +1 -1
  90. package/dist/reference/gate-course-rules.d.ts +4 -3
  91. package/dist/reference/gate-course-rules.js +9 -4
  92. package/dist/reference/gate-heading-length.d.ts +2 -0
  93. package/dist/reference/gate-heading-length.js +7 -5
  94. package/dist/reference/gate-heading-structure.js +26 -0
  95. package/dist/reference/gate-indices.d.ts +35 -0
  96. package/dist/reference/gate-indices.js +299 -0
  97. package/dist/reference/gate-math.d.ts +32 -2
  98. package/dist/reference/gate-math.js +68 -12
  99. package/dist/reference/gate-mdx-expression.d.ts +17 -0
  100. package/dist/reference/gate-mdx-expression.js +29 -0
  101. package/dist/reference/gate-part-pages.d.ts +1 -1
  102. package/dist/reference/gate-prop-text.d.ts +28 -0
  103. package/dist/reference/gate-prop-text.js +106 -0
  104. package/dist/reference/gate-prose-notation.d.ts +3 -1
  105. package/dist/reference/gate-prose-notation.js +4 -2
  106. package/dist/reference/gate-retired-keys.d.ts +35 -0
  107. package/dist/reference/gate-retired-keys.js +58 -0
  108. package/dist/reference/gloss.d.ts +2 -0
  109. package/dist/reference/katex-measure.d.ts +17 -0
  110. package/dist/reference/katex-measure.js +223 -0
  111. package/dist/reference/keyed-float-validate.js +12 -0
  112. package/dist/reference/keyed-jsx-floats.d.mts +2 -2
  113. package/dist/reference/keyed-jsx-floats.mjs +11 -14
  114. package/dist/reference/lab-math-scope.d.ts +1 -0
  115. package/dist/reference/lab-math-scope.js +13 -2
  116. package/dist/reference/math-glyphs.d.mts +94 -2
  117. package/dist/reference/math-glyphs.mjs +851 -46
  118. package/dist/reference/mixed-content.d.ts +8 -3
  119. package/dist/reference/mixed-content.js +4 -2
  120. package/dist/reference/page-notation.d.ts +34 -4
  121. package/dist/reference/page-notation.js +54 -2
  122. package/dist/reference/references.d.ts +11 -1
  123. package/dist/reference/references.js +6 -0
  124. package/dist/reference/registry.js +104 -10
  125. package/dist/reference/rehype-fail-katex-errors.d.mts +9 -0
  126. package/dist/reference/rehype-fail-katex-errors.mjs +19 -0
  127. package/dist/reference/rehype-heading-anchors.mjs +11 -1
  128. package/dist/reference/remark-citation.mjs +0 -0
  129. package/dist/reference/remark-notation.mjs +84 -20
  130. package/dist/reference/render-lab-math.d.ts +2 -0
  131. package/dist/reference/render-lab-math.js +17 -5
  132. package/dist/reference/sections-validate.d.ts +28 -3
  133. package/dist/reference/sections-validate.js +31 -4
  134. package/dist/reference/sections.d.mts +24 -6
  135. package/dist/reference/sections.mjs +92 -17
  136. package/dist/reference/tables-validate.d.ts +1 -1
  137. package/dist/reference/tables-validate.js +24 -1
  138. package/dist/reference/text-measure.d.ts +10 -0
  139. package/dist/reference/text-measure.js +41 -0
  140. package/dist/reference/text-widths.d.ts +10 -0
  141. package/dist/reference/text-widths.js +1238 -0
  142. package/dist/reference/types.d.ts +64 -8
  143. package/dist/rules/catalog.js +191 -30
  144. package/dist/rules/in-effect.d.ts +1 -1
  145. package/dist/rules/rules.schema.json +79 -24
  146. package/dist/rules/settings.d.ts +1 -1
  147. package/dist/rules/settings.js +7 -0
  148. package/dist/session/PreferenceStore.d.ts +18 -1
  149. package/dist/session/PreferenceStore.js +29 -1
  150. package/docs/authoring/README.md +32 -21
  151. package/docs/authoring/agents.md +38 -7
  152. package/docs/authoring/components.md +111 -6
  153. package/docs/authoring/migrating.md +707 -0
  154. package/docs/authoring/notation.md +281 -29
  155. package/docs/authoring/retired.md +1 -0
  156. package/docs/authoring/structure-rules.md +10 -41
  157. package/docs/authoring/structure.md +53 -2
  158. package/docs/authoring/visualizations.md +144 -18
  159. package/package.json +12 -7
  160. package/src/compiler/astro.ts +57 -17
  161. package/src/compiler/cli.ts +143 -35
  162. package/src/compiler/collections.ts +92 -19
  163. package/src/compiler/content-schema.ts +108 -0
  164. package/src/compiler/content-schemas.ts +391 -0
  165. package/src/compiler/course-config.ts +86 -15
  166. package/src/compiler/exports/combined-html.ts +669 -0
  167. package/src/compiler/exports/download-index.ts +36 -0
  168. package/src/compiler/exports/export-chapters.ts +233 -0
  169. package/src/compiler/exports/export-pages.ts +44 -0
  170. package/src/compiler/exports/html-tree.ts +62 -0
  171. package/src/compiler/exports/latex/capture.ts +651 -0
  172. package/src/compiler/exports/latex/compile.ts +168 -0
  173. package/src/compiler/exports/latex/document.ts +479 -0
  174. package/src/compiler/exports/latex/explico.cls +415 -0
  175. package/src/compiler/exports/latex/numbering.ts +74 -0
  176. package/src/compiler/exports/latex/serialize.ts +787 -0
  177. package/src/compiler/exports/latex/texlive-packages.txt +76 -0
  178. package/src/compiler/exports/latex-export.ts +255 -0
  179. package/src/compiler/exports/markdown/serialize.ts +632 -0
  180. package/src/compiler/exports/markdown-export.ts +329 -0
  181. package/src/compiler/exports/standalone-html.ts +39 -70
  182. package/src/compiler/exports/static-tree.ts +944 -0
  183. package/src/compiler/exports/zip.ts +88 -0
  184. package/src/compiler/lesson-derivation.ts +13 -8
  185. package/src/compiler/manifest.ts +299 -28
  186. package/src/compiler/render-diagnostics.ts +111 -0
  187. package/src/components/HoverPanel.astro +204 -6
  188. package/src/components/PartOverview.astro +12 -9
  189. package/src/components/abbreviation/AbbreviationLayer.astro +21 -12
  190. package/src/components/assessments/AssessmentRunner.tsx +7 -1
  191. package/src/components/assessments/AssessmentSet.astro +14 -1
  192. package/src/components/block-heading.ts +38 -0
  193. package/src/components/card-arrow.ts +18 -4
  194. package/src/components/card-carry.ts +406 -0
  195. package/src/components/card-drag.ts +1 -30
  196. package/src/components/card-fetch.ts +87 -0
  197. package/src/components/card-manager.ts +1222 -38
  198. package/src/components/card-memory.ts +5 -1
  199. package/src/components/card-placement.ts +211 -13
  200. package/src/components/card-side.ts +281 -31
  201. package/src/components/citation/CitationLayer.astro +45 -40
  202. package/src/components/diagram/Diagram.astro +123 -44
  203. package/src/components/figure/Figure.astro +11 -0
  204. package/src/components/figure/FloatCaption.astro +4 -17
  205. package/src/components/float-toolbar.ts +274 -0
  206. package/src/components/memo/Memo.astro +21 -10
  207. package/src/components/memo/ShortNote.astro +4 -0
  208. package/src/components/notation/NotationGlossary.astro +22 -17
  209. package/src/components/notation/NotationLayer.astro +321 -79
  210. package/src/components/notation/NotationMath.astro +8 -1
  211. package/src/components/notation/render-notation-math.ts +51 -5
  212. package/src/components/readable-text.ts +25 -0
  213. package/src/components/reference/FloatRefLayer.astro +162 -89
  214. package/src/components/starlight/HeadingAnchorEnhancer.astro +13 -8
  215. package/src/components/starlight/KeyedFloatEnhancer.astro +28 -9
  216. package/src/components/starlight/LayoutPageSidebar.astro +17 -3
  217. package/src/components/starlight/LayoutSocialIcons.astro +79 -29
  218. package/src/components/starlight/LessonExportActions.astro +384 -310
  219. package/src/components/starlight/LessonFooter.astro +47 -9
  220. package/src/components/starlight/PrintController.astro +28 -0
  221. package/src/components/starlight/TextSizeControl.astro +137 -0
  222. package/src/components/starlight/print-controller.ts +380 -0
  223. package/src/components/starlight/section-summary.ts +13 -5
  224. package/src/components/table/Table.astro +13 -21
  225. package/src/components/table/table-truncation.ts +15 -0
  226. package/src/content-config.ts +20 -312
  227. package/src/curriculum/validation.ts +72 -24
  228. package/src/reference/base-abbreviations.mjs +2 -2
  229. package/src/reference/base-operators.mjs +2 -2
  230. package/src/reference/base-roman-labels.mjs +4 -3
  231. package/src/reference/citation-format.mjs +74 -0
  232. package/src/reference/consistency.ts +47 -61
  233. package/src/reference/curriculum-alignment.ts +4 -41
  234. package/src/reference/derived-entries.ts +68 -0
  235. package/src/reference/derived-notation.mjs +121 -0
  236. package/src/reference/diagram-fit.ts +104 -0
  237. package/src/reference/diagram-layout.ts +182 -51
  238. package/src/reference/diagrams-validate.ts +2 -1
  239. package/src/reference/equations-validate.ts +41 -2
  240. package/src/reference/equations.mjs +120 -22
  241. package/src/reference/examples-validate.ts +1 -0
  242. package/src/reference/figure-fit.ts +139 -0
  243. package/src/reference/figure-pictures.ts +96 -0
  244. package/src/reference/figures-validate.ts +2 -1
  245. package/src/reference/gate-abbreviations.ts +1 -1
  246. package/src/reference/gate-course-rules.ts +22 -7
  247. package/src/reference/gate-heading-length.ts +25 -7
  248. package/src/reference/gate-heading-structure.ts +40 -1
  249. package/src/reference/gate-indices.ts +470 -0
  250. package/src/reference/gate-math.ts +102 -13
  251. package/src/reference/gate-mdx-expression.ts +45 -0
  252. package/src/reference/gate-prop-text.ts +145 -0
  253. package/src/reference/gate-prose-notation.ts +19 -5
  254. package/src/reference/gate-retired-keys.ts +101 -0
  255. package/src/reference/gloss.ts +2 -0
  256. package/src/reference/katex-measure.ts +384 -0
  257. package/src/reference/keyed-float-validate.ts +22 -0
  258. package/src/reference/keyed-jsx-floats.mjs +11 -14
  259. package/src/reference/lab-math-scope.ts +15 -1
  260. package/src/reference/math-glyphs.mjs +851 -46
  261. package/src/reference/mixed-content.ts +11 -2
  262. package/src/reference/page-notation.ts +100 -3
  263. package/src/reference/references.ts +14 -0
  264. package/src/reference/registry.ts +154 -8
  265. package/src/reference/rehype-fail-katex-errors.mjs +19 -0
  266. package/src/reference/rehype-heading-anchors.mjs +11 -1
  267. package/src/reference/remark-citation.mjs +0 -0
  268. package/src/reference/remark-notation.mjs +84 -20
  269. package/src/reference/render-lab-math.ts +29 -5
  270. package/src/reference/sections-validate.ts +67 -3
  271. package/src/reference/sections.mjs +92 -17
  272. package/src/reference/tables-validate.ts +40 -2
  273. package/src/reference/text-measure.ts +63 -0
  274. package/src/reference/text-widths.ts +133 -0
  275. package/src/reference/types.ts +75 -8
  276. package/src/rules/catalog.ts +205 -29
  277. package/src/rules/settings.ts +10 -0
  278. package/src/session/PreferenceStore.ts +53 -0
  279. package/src/styles/global.css +291 -75
@@ -1,4 +1,3 @@
1
- type EditorialStatus = 'draft' | 'in-review' | 'reviewed';
2
1
  interface SourceSpan {
3
2
  readonly file: string;
4
3
  readonly line?: number;
@@ -14,7 +13,13 @@ type NotationAlignment = {
14
13
  readonly kind: 'general';
15
14
  readonly rationale: string;
16
15
  };
17
- type NotationReferenceKind = 'prose' | 'math' | 'definition';
16
+ /**
17
+ * Where a reference was written: in prose, in math, in a definition's meaning
18
+ * or body, or in its `formula`. A formula's references bring their cards onto
19
+ * the page but are no dependency: two formulas may name each other
20
+ * (γ = 1 − α, α = 1 − γ).
21
+ */
22
+ type NotationReferenceKind = 'prose' | 'math' | 'definition' | 'formula';
18
23
  /**
19
24
  * A gloss resolved by the loader (D15). Declared on a card, keyed
20
25
  * `<card key>.<slug(name)>`, and deliberately absent from the registry: a
@@ -42,10 +47,17 @@ interface SharedNotationDefinitionInput {
42
47
  readonly domain: string;
43
48
  readonly formula?: string;
44
49
  readonly units?: string;
50
+ /** Rule N23: an index entry's title for what it indexes, `{base} …`. */
51
+ readonly reads?: string;
52
+ /** Rule N23: the set a running index letter runs over. */
53
+ readonly range?: string;
54
+ /** Rule N23: as an index, a fixed label rather than a running position. */
55
+ readonly fixedIndex?: true;
56
+ /** Rule N23: an index entry's `formula` as written, `{base}` unbound. */
57
+ readonly formulaTemplate?: string;
45
58
  readonly sources: readonly string[];
46
59
  readonly seeAlso: readonly string[];
47
60
  readonly alignment: NotationAlignment;
48
- readonly status: EditorialStatus;
49
61
  readonly aiAssisted: boolean;
50
62
  readonly body: string;
51
63
  /** References harvested from the Markdown body. */
@@ -61,6 +73,14 @@ interface LocalNotationDefinitionInput {
61
73
  readonly summary: string;
62
74
  readonly formula?: string;
63
75
  readonly units?: string;
76
+ /** Rule N23: an index entry's title for what it indexes, `{base} …`. */
77
+ readonly reads?: string;
78
+ /** Rule N23: the set a running index letter runs over. */
79
+ readonly range?: string;
80
+ /** Rule N23: as an index, a fixed label rather than a running position. */
81
+ readonly fixedIndex?: true;
82
+ /** Rule N23: an index entry's `formula` as written, `{base}` unbound. */
83
+ readonly formulaTemplate?: string;
64
84
  readonly sources: readonly string[];
65
85
  readonly seeAlso: readonly string[];
66
86
  readonly alignment: NotationAlignment;
@@ -83,7 +103,6 @@ interface NotationSuspension {
83
103
  }
84
104
  interface NotationLessonInput {
85
105
  readonly lessonId: string;
86
- readonly status: EditorialStatus;
87
106
  readonly localDefinitions: readonly LocalNotationDefinitionInput[];
88
107
  /**
89
108
  * Decorations this page suspends (`notation.suspend`, rule N22). Optional
@@ -122,7 +141,7 @@ interface AbbreviationDefinitionInput {
122
141
  readonly domain?: string;
123
142
  readonly source: SourceSpan;
124
143
  }
125
- type NotationDiagnosticCode = 'invalid-key' | 'duplicate-page' | 'duplicate-definition' | 'conflicting-definition' | 'undefined-reference' | 'reference-cycle' | 'unused-definition' | 'alignment-unknown-competency' | 'alignment-unknown-lesson' | 'alignment-introduction' | 'alignment-unavailable' | 'alignment-review-state' | 'gloss-collides-with-card';
144
+ type NotationDiagnosticCode = 'invalid-key' | 'duplicate-page' | 'duplicate-definition' | 'conflicting-definition' | 'undefined-reference' | 'reference-cycle' | 'unused-definition' | 'local-shadows-shared' | 'malformed-reference' | 'alignment-unknown-competency' | 'alignment-unknown-lesson' | 'alignment-introduction' | 'alignment-unavailable' | 'gloss-collides-with-card';
126
145
  type DiagnosticSeverity = 'error' | 'warning' | 'info';
127
146
  interface NotationDiagnostic {
128
147
  readonly code: NotationDiagnosticCode;
@@ -159,9 +178,16 @@ interface NotationDefinitionRecordBase {
159
178
  readonly scope: NotationDefinitionScope;
160
179
  readonly notation: string;
161
180
  readonly label: string;
181
+ /** Rule N23: an index entry's title for what it indexes, `{base} …`. */
182
+ readonly reads?: string;
183
+ /** Rule N23: the set a running index letter runs over. */
184
+ readonly range?: string;
185
+ /** Rule N23: as an index, a fixed label rather than a running position. */
186
+ readonly fixedIndex?: true;
187
+ /** Rule N23: an index entry's `formula` as written, `{base}` unbound. */
188
+ readonly formulaTemplate?: string;
162
189
  readonly seeAlso: readonly string[];
163
190
  readonly alignment: NotationAlignment;
164
- readonly status: EditorialStatus;
165
191
  readonly source: SourceSpan;
166
192
  readonly resolvedReferences: readonly ResolvedNotationReference[];
167
193
  }
@@ -191,7 +217,6 @@ interface NotationBinding {
191
217
  }
192
218
  interface NotationPageBundle {
193
219
  readonly lessonId: string;
194
- readonly status: EditorialStatus;
195
220
  readonly source: SourceSpan;
196
221
  readonly bindings: readonly NotationBinding[];
197
222
  readonly definitionIds: readonly string[];
@@ -208,5 +233,36 @@ interface NotationRegistry {
208
233
  readonly backlinks: readonly NotationBacklink[];
209
234
  readonly diagnostics: readonly NotationDiagnostic[];
210
235
  }
236
+ /** A derived card a math source uses (rule N23): `key` is `<base>.sub.<index>`. */
237
+ interface DerivedUse {
238
+ readonly key: string;
239
+ readonly base: string;
240
+ readonly index: string;
241
+ }
242
+ /**
243
+ * How one symbol with a subscript is explained (rule N23), as
244
+ * `resolveMathGlyphs` reports it in `indices`.
245
+ */
246
+ interface IndexedSymbol {
247
+ readonly token: string;
248
+ readonly start: number;
249
+ readonly end: number;
250
+ readonly base: string;
251
+ readonly index: string;
252
+ readonly how: 'whole-form' | 'family' | 'derived' | 'inside-form' | 'explicit' | 'swallowed' | 'inside-wrap' | 'composed' | 'unresolved';
253
+ readonly key: string | null;
254
+ readonly explained: boolean;
255
+ readonly baseExplained: boolean;
256
+ /** The parts no card explains: the base first, when it has none, then each slot. */
257
+ readonly unexplained: readonly string[];
258
+ /** Running index letters (`i`, `k`, `t`) in its slots, for the index-set check. */
259
+ readonly running: readonly {
260
+ readonly letter: string;
261
+ readonly start: number;
262
+ readonly end: number;
263
+ readonly key: string | null;
264
+ readonly cover: string | null;
265
+ }[];
266
+ }
211
267
 
212
- export type { AbbreviationDefinitionInput, DiagnosticSeverity, EditorialStatus, LocalNotationDefinitionInput, LocalNotationDefinitionRecord, NotationAlignment, NotationBacklink, NotationBinding, NotationDefinitionRecord, NotationDefinitionScope, NotationDiagnostic, NotationDiagnosticCode, NotationGlossEntry, NotationLessonInput, NotationPageBundle, NotationReferenceInput, NotationReferenceKind, NotationRegistry, NotationRegistryInput, NotationSuspension, ResolvedNotationReference, SharedNotationDefinitionInput, SharedNotationDefinitionRecord, SourceSpan };
268
+ export type { AbbreviationDefinitionInput, DerivedUse, DiagnosticSeverity, IndexedSymbol, LocalNotationDefinitionInput, LocalNotationDefinitionRecord, NotationAlignment, NotationBacklink, NotationBinding, NotationDefinitionRecord, NotationDefinitionScope, NotationDiagnostic, NotationDiagnosticCode, NotationGlossEntry, NotationLessonInput, NotationPageBundle, NotationReferenceInput, NotationReferenceKind, NotationRegistry, NotationRegistryInput, NotationSuspension, ResolvedNotationReference, SharedNotationDefinitionInput, SharedNotationDefinitionRecord, SourceSpan };
@@ -20,6 +20,7 @@ const GATE_MATH = { module: "src/reference/gate-math.ts", fn: "resolveInto" };
20
20
  const GLYPHS = { module: "src/reference/math-glyphs.mjs", fn: "resolveMathGlyphs" };
21
21
  const REGISTRY = { module: "src/reference/registry.ts", fn: "buildNotationRegistry" };
22
22
  const KEYED_FLOATS = { module: "src/reference/keyed-float-validate.ts", fn: "validateKeyedFloats" };
23
+ const INDICES = { module: "src/reference/gate-indices.ts", fn: "gateIndices" };
23
24
  function consistency(fn) {
24
25
  return { module: "src/reference/consistency.ts", fn };
25
26
  }
@@ -35,10 +36,28 @@ function keyedFloatChecks() {
35
36
  check(`${prefix}-duplicate-key`, "error", false, emitter),
36
37
  check(`${prefix}-ref-resolves`, "error", false, emitter)
37
38
  );
39
+ if (prefix === "eq") {
40
+ out.push(
41
+ check("eq-label-repeated", "error", false, emitter),
42
+ // LaTeX's `\eqref{eq:key}` in prose, which MDX cannot parse.
43
+ check("eq-latex-ref", "error", false, emitter)
44
+ );
45
+ }
38
46
  if (prefix === "tab" || prefix === "fig" || prefix === "dia" || prefix === "ex") {
39
47
  out.push(check(`${prefix}-key-unused`, "info", false, emitter));
40
48
  out.push(check(`${prefix}-removed-prop`, "error", false, emitter));
41
49
  }
50
+ if (prefix !== "eq" && prefix !== "check") {
51
+ out.push(check(`${prefix}-unread-prop`, "warning", false, emitter));
52
+ }
53
+ if (prefix === "tab") {
54
+ out.push(
55
+ check("tab-several-tables", "error", false, {
56
+ module: "src/reference/tables-validate.ts",
57
+ fn: "validateTables"
58
+ })
59
+ );
60
+ }
42
61
  }
43
62
  out.push(
44
63
  check("ex-missing-prop", "error", false, {
@@ -50,6 +69,10 @@ function keyedFloatChecks() {
50
69
  check("sec-ref-resolves", "error", true, {
51
70
  module: "src/reference/sections-validate.ts",
52
71
  fn: "validateSectionRefs"
72
+ }),
73
+ check("heading-anchor-collision", "error", true, {
74
+ module: "src/reference/sections-validate.ts",
75
+ fn: "validateHeadingAnchors"
53
76
  })
54
77
  );
55
78
  out.push(
@@ -161,7 +184,7 @@ const RULES = [
161
184
  title: "A physical quantity, unit, or math operator is typeset in `$\u2026$`, never as plain text \u2014 in prose, a table cell, an assessment `prompt` / `explanation`, or a caption that itself lives outside math.",
162
185
  elements: ["prose", "assessment", "table"],
163
186
  doc: "notation.md#n9",
164
- summary: 'Never write `45 m/s`, `t (s)`, `vx` or `KE/m` as plain text, and never spell a stated quantity\'s unit out: "4 seconds", "2 meters per second squared", "30 degrees", "$x = 3$ meters" are `$4\\ \\text{s}$`, `$2\\ \\text{m/s}^2$`, `$30^{\\circ}$`, `$x = 3\\ \\text{m}$`. A unit inside math is `\\text{}`-wrapped, short form, in the same span as its number or symbol. A unit named on its own ("measured in meters") stays a word; a sentence that defines a unit is waived. JSX prop strings (`caption="\u2026"`) and assessment choice labels render no math, so N9 does not apply there.',
187
+ summary: 'Never write `45 m/s`, `t (s)`, `vx` or `KE/m` as plain text, and never spell a stated quantity\'s unit out: "4 seconds", "2 meters per second squared", "30 degrees", "$x = 3$ meters" are `$4\\ \\text{s}$`, `$2\\ \\text{m/s}^2$`, `$30^{\\circ}$`, `$x = 3\\ \\text{m}$`. A unit inside math is `\\text{}`-wrapped, short form, in the same span as its number or symbol. A unit named on its own ("measured in meters") stays a word; a sentence that defines a unit is waived. JSX prop strings (`caption="\u2026"`) render no math, so N9 does not apply there; nor does it apply to an assessment choice label, which may still hold math (`$30^{\\circ}$`).',
165
188
  checks: [
166
189
  check(
167
190
  "prose-notation",
@@ -314,7 +337,7 @@ const RULES = [
314
337
  title: "A notation entry states its `units`, or declares itself `dimensionless`, using the units vocabulary.",
315
338
  elements: ["notation"],
316
339
  doc: "notation.md#n19",
317
- summary: "Every notation entry has `units:` from `units-vocab.ts` (`m/s`, `s`, `kg`, \u2026) or `dimensionless: true`. A token outside the vocabulary is a warning.",
340
+ summary: 'Every notation entry has `units:` from `units-vocab.ts` (`m/s`, `s`, `kg`, \u2026) or `dimensionless: true`, except an index entry (`latex: "{}_0"`, N23), which takes neither. A token outside the vocabulary is a warning.',
318
341
  checks: [
319
342
  check("notation-units", "warning", false, consistency("checkNotationUnits")),
320
343
  check("units-vocab", "warning", false, consistency("checkNotationUnits"))
@@ -339,7 +362,7 @@ const RULES = [
339
362
  title: "A gloss is named by a short noun phrase, not described by a sentence.",
340
363
  elements: ["notation"],
341
364
  doc: "notation.md#n21",
342
- summary: "A gloss `label` names the symbol in a few words (\u201Claunch angle\u201D); a clause, sentence punctuation, or a definition belongs in a full notation entry instead.",
365
+ summary: "A gloss `name` names the symbol in a few words (\u201Claunch angle\u201D); a clause, sentence punctuation, or a definition belongs in a full notation entry instead.",
343
366
  checks: [check("gloss-name-shape", "warning", false, consistency("checkGlossNameShape"))]
344
367
  },
345
368
  {
@@ -352,6 +375,7 @@ const RULES = [
352
375
  summary: "A decoration (`\\hat`, `\\bar`, `\\overline`, `'`, `^*`, `^\\dagger`, \u2026) is on the course\u2019s `allow` list (shipped empty), explained by an operator entry (`latex: \\hat{}`), or the decorated form has its own entry (`latex: \\hat{\\beta}`), which wins for that form. A page may suspend a decoration in `notation.suspend`, with a `reason`; every decorated form of it there then needs its own entry.",
353
376
  checks: [
354
377
  check("accent-explained", "warning", false, consistency("checkAccentExplained")),
378
+ check("accent-swallowed", "warning", false, consistency("checkAccentExplained")),
355
379
  check("suspended-operator-unresolved", "error", true, GATE_MATH),
356
380
  check("operator-suspension-reason", "warning", false, consistency("checkOperatorSuspension")),
357
381
  check("operator-suspended", "info", false, consistency("checkOperatorSuspension")),
@@ -359,6 +383,22 @@ const RULES = [
359
383
  ],
360
384
  settings: baselineEdits("Decorations")
361
385
  },
386
+ {
387
+ kind: "rule",
388
+ id: "N23",
389
+ short: "An index is part of the symbol: whole form, family or index entry",
390
+ title: "An index is part of the symbol: a subscript is explained by the indexed symbol\u2019s entry, a family entry, or an index entry.",
391
+ elements: ["math", "notation"],
392
+ doc: "notation.md#n23",
393
+ summary: 'A subscript on a symbol is explained by the indexed symbol\u2019s own entry (`latex: v_0`), a family entry whose index runs (`latex: x_k` explains `x_1`), or an index entry for the subscript (`latex: "{}_0"`, which composes a derived card); a card on the base alone does not explain it, and arithmetic on an index letter with its own card does (`X_{t-1}`). A running index \u2014 a lowercase letter not marked `fixedIndex` \u2014 states its set, as `range` on its card or after a `\\quad` on the first display line that uses it. Never wrap an indexed symbol in `\\explain` with a key whose `latex` has no index.',
394
+ checks: [
395
+ check("index-explained", "error", true, INDICES),
396
+ check("index-swallowed", "error", true, INDICES),
397
+ check("index-range", "error", true, INDICES),
398
+ check("derived-key-taken", "error", true, INDICES)
399
+ ],
400
+ unchecked: "whether a `\\quad` clause states the set correctly; whether a lowercase index is really running; lab math whose glyph table is a copy of the one `labMathScope` returned."
401
+ },
362
402
  // ---- V — visualizations -----------------------------------------------------
363
403
  {
364
404
  kind: "rule",
@@ -387,11 +427,15 @@ const RULES = [
387
427
  title: "Interactive figures: a hydrated island and the `renderLabMath` seam for equations.",
388
428
  elements: ["plot"],
389
429
  doc: "visualizations.md#v3",
390
- summary: "An interactive figure is an island under `content/labs/` inside a `<Figure>`, renders its equations through `renderLabMath` (`{ display: false }` for a legend, an axis title or a label; a `{ math }` run for the caption clause) and never KaTeX directly, and labels its controls; the figure\u2019s caption is its name, so the lab writes no accessible name or text alternative of its own.",
430
+ summary: "An interactive figure is an island under `content/labs/` inside a `<Figure>`, renders its equations through `renderLabMath` (`{ display: false }` for a legend, an axis title or a label; a `{ math }` run for the caption clause) and never KaTeX directly, and labels its controls; the figure\u2019s caption is its name, so the lab writes no accessible name or text alternative of its own. Its default picture is drawn at build time too and handed to the island as children, so the built page has it.",
391
431
  checks: [
392
432
  check("lab-katex-direct", "error", false, {
393
433
  module: "src/reference/lab-katex-validate.ts",
394
434
  fn: "validateLabKatexImports"
435
+ }),
436
+ check("lab-picture-undrawn", "warning", false, {
437
+ module: "src/reference/figure-pictures.ts",
438
+ fn: "undrawnFigurePictures"
395
439
  })
396
440
  ],
397
441
  unchecked: "that a lab labels its controls and writes no name of its own, and math a lab draws without importing `katex` \u2014 the build\u2019s rendered check (`figure-symbols.ts`) covers the page it renders."
@@ -442,6 +486,53 @@ const RULES = [
442
486
  }
443
487
  ]
444
488
  },
489
+ {
490
+ kind: "rule",
491
+ id: "V7",
492
+ short: "A figure\u2019s picture fits on one screen",
493
+ title: "A figure\u2019s picture takes at most `maxPercent` of the screen height.",
494
+ elements: ["plot"],
495
+ doc: "visualizations.md#v7",
496
+ summary: "The default slot of a `<Figure>` \u2014 plot, key and tables \u2014 stays within `maxPercent` of the viewport height, so the reader sees the picture and its caption line together. A square or tall plot gets a fixed width instead of the full column.",
497
+ checks: [
498
+ check("figure-too-tall", "warning", false, {
499
+ module: "src/reference/figure-fit.ts",
500
+ fn: "checkFigureFit"
501
+ })
502
+ ],
503
+ unchecked: "any page or viewport the course\u2019s Playwright suite does not open \u2014 the check runs in the browser, not in the build.",
504
+ settings: [
505
+ {
506
+ key: "maxPercent",
507
+ title: "Largest picture height (% of screen)",
508
+ description: "How much of the viewport height a figure\u2019s picture may take.",
509
+ schema: { type: "integer", minimum: 10, default: 80 }
510
+ }
511
+ ]
512
+ },
513
+ {
514
+ kind: "rule",
515
+ id: "V8",
516
+ short: "A diagram fits the lesson column",
517
+ title: "A diagram is no wider than the lesson column.",
518
+ elements: ["diagram"],
519
+ doc: "visualizations.md#v8",
520
+ summary: 'A `<Diagram>` lays out no wider than `columnWidth`, the lesson column at a 1366px-wide window, so the reader sees the whole graph without scrolling it. Narrow it with `direction="TB"`, a shorter label or a node `width` (its label wraps inside it); `warnAboveWidth={false}` marks a diagram meant to scroll.',
521
+ checks: [
522
+ check("dia-too-wide", "warning", false, {
523
+ module: "src/reference/diagram-fit.ts",
524
+ fn: "oversizedDiagrams"
525
+ })
526
+ ],
527
+ settings: [
528
+ {
529
+ key: "columnWidth",
530
+ title: "Column width (px)",
531
+ description: "The widest a diagram may lay out: the lesson column at the window width the course is read at most (707px at 1366px).",
532
+ schema: { type: "integer", minimum: 200, default: 707 }
533
+ }
534
+ ]
535
+ },
445
536
  // ---- C — versioning ---------------------------------------------------------
446
537
  {
447
538
  kind: "rule",
@@ -592,23 +683,6 @@ const RULES = [
592
683
  })
593
684
  ]
594
685
  },
595
- {
596
- kind: "rule",
597
- id: "S5",
598
- short: "A sign / perspective convention is declared once and cited elsewhere",
599
- title: "A sign or perspective convention is declared once and cited everywhere else.",
600
- elements: ["prose"],
601
- doc: "structure-rules.md#s5",
602
- summary: "Prose that states a sign / cash-flow-perspective convention resolves it to the single declaration \u2014 the `conventions.signConventionKey` notation entry (`[[key]]`) or the `conventions.cashFlowPerspectiveCompetency` competency \u2014 rather than restating it.",
603
- checks: [
604
- check(
605
- "convention-single-definition",
606
- "warning",
607
- false,
608
- consistency("checkConventionSingleDefinition")
609
- )
610
- ]
611
- },
612
686
  {
613
687
  kind: "rule",
614
688
  id: "S6",
@@ -621,6 +695,12 @@ const RULES = [
621
695
  check("heading-skip", "error", true, {
622
696
  module: "src/reference/gate-heading-structure.ts",
623
697
  fn: "gateHeadingStructure"
698
+ }),
699
+ // A knowledge check or a worked-example set, which draws an `<h2>`,
700
+ // inside a memo or a short note.
701
+ check("heading-nested", "error", true, {
702
+ module: "src/reference/gate-heading-structure.ts",
703
+ fn: "gateHeadingStructure"
624
704
  })
625
705
  ]
626
706
  },
@@ -817,6 +897,13 @@ const RETIRED = [
817
897
  date: "2026-09-26",
818
898
  into: "C3",
819
899
  why: "The release procedure and the invariant it keeps are one rule, and `knowledge-changelog-missing` (owned by C3) was already the check that fired on a half-finished release."
900
+ },
901
+ {
902
+ id: "S5",
903
+ title: "A sign or perspective convention is declared once and cited everywhere else.",
904
+ date: "2026-10-07",
905
+ into: "N4",
906
+ why: "Its check knew one finance course\u2019s phrasing and one key per course, and never fired in any course; the `conventions` setting it read is gone. A convention is a definition, so it is now N4\u2019s: defined once on its notation entry, whose `meaning` names the other choice and whose body says how to convert."
820
907
  }
821
908
  ];
822
909
  const INVARIANTS = [
@@ -826,7 +913,7 @@ const INVARIANTS = [
826
913
  title: "Notation keys are well-formed, unique, defined, and acyclic.",
827
914
  elements: ["notation", "math"],
828
915
  doc: "components.md#reference-systems",
829
- summary: "A `[[key]]` or `\\explain{key}{\u2026}` names an existing entry; a key is kebab-case; a shared entry is defined once; formula references never form a cycle.",
916
+ summary: "A `[[key]]` or `\\explain{key}{\u2026}` names an existing entry; a key is kebab-case; a shared entry is defined once; references in meanings and bodies never form a cycle (formulas may name each other).",
830
917
  checks: [
831
918
  check("undefined-reference", "error", false, REGISTRY),
832
919
  check("invalid-key", "error", false, REGISTRY, GLYPHS),
@@ -841,7 +928,11 @@ const INVARIANTS = [
841
928
  fn: "reportUnknownScopeKeys"
842
929
  }),
843
930
  check("malformed-explicit-binding", "error", true, GLYPHS),
844
- check("unused-definition", "info", false, REGISTRY)
931
+ check("unused-definition", "info", false, REGISTRY),
932
+ // A page-local entry under a shared key: two meanings for one key.
933
+ check("local-shadows-shared", "warning", false, REGISTRY),
934
+ // A `[[` that never closes, before the render fails on it.
935
+ check("malformed-reference", "error", false, REGISTRY)
845
936
  ]
846
937
  },
847
938
  {
@@ -860,7 +951,12 @@ const INVARIANTS = [
860
951
  check("ambiguous-span", "error", true, GLYPHS),
861
952
  check("ambiguous-base-fallback", "error", true, GLYPHS),
862
953
  check("ambiguous-canonical-notation", "error", false, GLYPHS),
863
- check("math-compile-error", "error", true, GATE_MATH)
954
+ check("math-compile-error", "error", true, GATE_MATH),
955
+ // `$5 to $6`: money remark-math reads as one math span.
956
+ check("currency-dollar", "error", true, {
957
+ module: "src/reference/gate-math.ts",
958
+ fn: "gateContentMath"
959
+ })
864
960
  ]
865
961
  },
866
962
  {
@@ -878,7 +974,7 @@ const INVARIANTS = [
878
974
  title: "The course settings name real rules and current keys.",
879
975
  elements: ["changelog"],
880
976
  doc: "README.md#amending",
881
- summary: "A `rules.<ID>` block names a live rule; a deprecated top-level key (`operators`, `romanLabels`, `abbreviations`) still works but warns until it is moved under `rules`.",
977
+ summary: "A `rules.<ID>` block names a live rule; a deprecated top-level key (`operators`, `romanLabels`, `abbreviations`) still works but warns until it is moved under `rules`; `conventions` is ignored and warns until it is deleted.",
882
978
  checks: [
883
979
  check("config-deprecated-key", "warning", false, {
884
980
  module: "src/rules/settings.ts",
@@ -922,8 +1018,10 @@ const INVARIANTS = [
922
1018
  "track-order",
923
1019
  "assessment-coverage",
924
1020
  "invalid-assessment",
925
- "review-state",
926
- "teaches-support"
1021
+ "missing-assumptions",
1022
+ "teaches-support",
1023
+ // `[text [@id]](url)`: a citation the page prints as typed.
1024
+ "citation-in-link"
927
1025
  ].map(
928
1026
  (code) => check(code, "error", false, {
929
1027
  module: "src/curriculum/validation.ts",
@@ -934,14 +1032,77 @@ const INVARIANTS = [
934
1032
  "alignment-unknown-competency",
935
1033
  "alignment-unknown-lesson",
936
1034
  "alignment-introduction",
937
- "alignment-unavailable",
938
- "alignment-review-state"
1035
+ "alignment-unavailable"
939
1036
  ].map(
940
1037
  (code) => check(code, "error", false, {
941
1038
  module: "src/reference/curriculum-alignment.ts",
942
1039
  fn: "validateNotationAlignment"
943
1040
  })
944
- )
1041
+ ),
1042
+ // A course's parts and the lessons in them (`compileManifest`): a
1043
+ // lesson in a directory no part lists, and a part with nothing in it yet.
1044
+ check("lesson-outside-parts", "error", false, {
1045
+ module: "src/compiler/manifest.ts",
1046
+ fn: "compileManifest"
1047
+ }),
1048
+ check("part-empty", "warning", false, {
1049
+ module: "src/compiler/manifest.ts",
1050
+ fn: "compileManifest"
1051
+ })
1052
+ ]
1053
+ },
1054
+ {
1055
+ kind: "invariant",
1056
+ id: "content-schema",
1057
+ title: "Every content file satisfies its collection\u2019s schema.",
1058
+ elements: ["notation", "assessment", "source", "changelog"],
1059
+ doc: "structure.md#content-schemas",
1060
+ summary: "A page\u2019s explico fields, a notation entry, an abbreviation, a competency, an assessment, a source and a track match the schema `astro build` loads them with (`compiler/content-schemas.ts`); `validate:content` reports the field and its line.",
1061
+ checks: [
1062
+ check("content-schema", "error", false, {
1063
+ module: "src/compiler/content-schema.ts",
1064
+ fn: "validateContentSchemas"
1065
+ })
1066
+ ]
1067
+ },
1068
+ {
1069
+ kind: "invariant",
1070
+ id: "retired-keys",
1071
+ title: "No content file sets a key the engine has retired or cannot honour.",
1072
+ elements: ["changelog"],
1073
+ doc: "structure.md#retired-keys",
1074
+ summary: "A frontmatter or JSON key explico no longer reads (`editorialStatus`) fails the build at every file that still sets it; delete the line. A lesson\u2019s `draft: true` fails it too (the sidebar would name a page the build drops), and its `sidebar.order` warns (chapters follow the tracks).",
1075
+ checks: [
1076
+ check("retired-key", "error", false, {
1077
+ module: "src/reference/gate-retired-keys.ts",
1078
+ fn: "findRetiredKeys"
1079
+ }),
1080
+ check("unsupported-key", "error", false, {
1081
+ module: "src/reference/gate-retired-keys.ts",
1082
+ fn: "findUnsupportedKeys"
1083
+ }),
1084
+ check("ignored-key", "warning", false, {
1085
+ module: "src/reference/gate-retired-keys.ts",
1086
+ fn: "findUnsupportedKeys"
1087
+ })
1088
+ ]
1089
+ },
1090
+ {
1091
+ kind: "invariant",
1092
+ id: "prop-text",
1093
+ title: "A component\u2019s quoted prop holds plain text, and prose holds no `{\u2026}` expression.",
1094
+ elements: ["math", "plot", "diagram", "table"],
1095
+ doc: "components.md#props-are-plain-text",
1096
+ summary: 'A quoted prop (`caption="\u2026"`, `short="\u2026"`, `title="\u2026"`, `label="\u2026"`) reaches the page as plain text: no KaTeX, reference or citation runs in it. `$\u2026$`, `[[\u2026]]` or `[@\u2026]` in one fails the build, since it would print as typed; write the symbol in Unicode or words, and reference or cite from the prose next to the component. MDX reads `{\u2026}` in prose as JavaScript (`{a}` fails the build, "a is not defined"): write `\\{a\\}`, or `$\\{a\\}$` for a set.',
1097
+ checks: [
1098
+ check("markup-in-prop", "error", false, {
1099
+ module: "src/reference/gate-prop-text.ts",
1100
+ fn: "findMarkupInProps"
1101
+ }),
1102
+ check("mdx-expression", "error", false, {
1103
+ module: "src/reference/gate-mdx-expression.ts",
1104
+ fn: "gateMdxExpressions"
1105
+ })
945
1106
  ]
946
1107
  }
947
1108
  ];
@@ -1,5 +1,5 @@
1
1
  import { Element, Enforcement } from './catalog.js';
2
- import { M as ManifestRules } from '../course-config-Dsnri9Tk.js';
2
+ import { M as ManifestRules } from '../course-config-rRtME5UV.js';
3
3
  import './course-rules.js';
4
4
 
5
5
  /** The slice of a manifest the page needs. */