explico 0.7.0 → 0.11.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 (236) hide show
  1. package/CHANGELOG.md +1409 -1
  2. package/README.md +60 -15
  3. package/dist/compiler/astro.d.ts +6 -1
  4. package/dist/compiler/cli.d.ts +54 -3
  5. package/dist/compiler/cli.js +80 -6
  6. package/dist/compiler/collections.d.ts +35 -2
  7. package/dist/compiler/collections.js +84 -8
  8. package/dist/compiler/course-config.d.ts +3 -194
  9. package/dist/compiler/course-config.js +78 -5
  10. package/dist/compiler/exports/standalone-html.d.ts +6 -1
  11. package/dist/compiler/knowledge-version.js +1 -1
  12. package/dist/compiler/lesson-derivation.d.ts +27 -10
  13. package/dist/compiler/lesson-derivation.js +23 -8
  14. package/dist/compiler/logging.d.ts +14 -1
  15. package/dist/compiler/logging.js +6 -2
  16. package/dist/compiler/manifest.d.ts +204 -31
  17. package/dist/compiler/manifest.js +270 -55
  18. package/dist/compiler/resolution-report.d.ts +6 -1
  19. package/dist/course-config-Dsnri9Tk.d.ts +464 -0
  20. package/dist/reference/base-accents.d.mts +26 -0
  21. package/dist/reference/base-accents.mjs +31 -0
  22. package/dist/reference/base-roman-labels.d.mts +2 -2
  23. package/dist/reference/base-roman-labels.mjs +2 -2
  24. package/dist/reference/consistency.d.ts +14 -2
  25. package/dist/reference/consistency.js +108 -4
  26. package/dist/reference/delimiter-size.d.ts +21 -11
  27. package/dist/reference/delimiter-size.js +15 -39
  28. package/dist/reference/diagrams-validate.d.ts +7 -2
  29. package/dist/reference/diagrams-validate.js +9 -1
  30. package/dist/reference/equations-validate.d.ts +8 -5
  31. package/dist/reference/equations-validate.js +1 -0
  32. package/dist/reference/equations.d.mts +69 -34
  33. package/dist/reference/equations.mjs +90 -44
  34. package/dist/reference/examples-validate.d.ts +31 -0
  35. package/dist/reference/examples-validate.js +48 -0
  36. package/dist/reference/figures-validate.d.ts +1 -1
  37. package/dist/reference/figures-validate.js +4 -1
  38. package/dist/reference/folds-validate.d.ts +33 -0
  39. package/dist/reference/folds-validate.js +32 -0
  40. package/dist/reference/gate-abbreviations.d.ts +3 -1
  41. package/dist/reference/gate-abbreviations.js +3 -2
  42. package/dist/reference/gate-course-rules.d.ts +32 -0
  43. package/dist/reference/gate-course-rules.js +85 -0
  44. package/dist/reference/gate-heading-length.d.ts +5 -2
  45. package/dist/reference/gate-heading-length.js +47 -23
  46. package/dist/reference/gate-heading-structure.d.ts +41 -0
  47. package/dist/reference/gate-heading-structure.js +113 -0
  48. package/dist/reference/gate-math-punctuation.d.ts +71 -0
  49. package/dist/reference/gate-math-punctuation.js +251 -0
  50. package/dist/reference/gate-math.d.ts +44 -6
  51. package/dist/reference/gate-math.js +63 -10
  52. package/dist/reference/gate-part-pages.d.ts +50 -0
  53. package/dist/reference/gate-part-pages.js +118 -0
  54. package/dist/reference/gate-prose-notation.d.ts +32 -11
  55. package/dist/reference/gate-prose-notation.js +30 -8
  56. package/dist/reference/katex-delimiters.d.mts +15 -0
  57. package/dist/reference/katex-delimiters.mjs +138 -0
  58. package/dist/reference/katex-options.mjs +4 -0
  59. package/dist/reference/keyed-float-validate.d.ts +27 -10
  60. package/dist/reference/keyed-float-validate.js +15 -1
  61. package/dist/reference/keyed-jsx-floats.d.mts +11 -5
  62. package/dist/reference/keyed-jsx-floats.mjs +73 -20
  63. package/dist/reference/knowledge-checks-validate.d.ts +1 -1
  64. package/dist/reference/knowledge-checks.d.mts +3 -1
  65. package/dist/reference/knowledge-checks.mjs +9 -4
  66. package/dist/reference/lab-math-scope.d.ts +1 -0
  67. package/dist/reference/lab-math-slot.d.ts +34 -0
  68. package/dist/reference/lab-math-slot.js +6 -0
  69. package/dist/reference/math-glyphs.d.mts +80 -4
  70. package/dist/reference/math-glyphs.mjs +588 -39
  71. package/dist/reference/page-notation.d.ts +6 -1
  72. package/dist/reference/raw-floats-validate.js +1 -1
  73. package/dist/reference/raw-floats.mjs +1 -1
  74. package/dist/reference/rehype-heading-anchors.d.mts +14 -0
  75. package/dist/reference/rehype-heading-anchors.mjs +116 -17
  76. package/dist/reference/rehype-table-header-labels.d.mts +6 -1
  77. package/dist/reference/rehype-table-header-labels.mjs +21 -3
  78. package/dist/reference/remark-asides.d.mts +3 -0
  79. package/dist/reference/remark-asides.mjs +180 -0
  80. package/dist/reference/remark-memo.d.mts +3 -1
  81. package/dist/reference/remark-memo.mjs +42 -11
  82. package/dist/reference/remark-notation.d.mts +60 -10
  83. package/dist/reference/remark-notation.mjs +387 -36
  84. package/dist/reference/render-lab-math.d.ts +4 -26
  85. package/dist/reference/render-lab-math.js +5 -5
  86. package/dist/reference/sections-validate.d.ts +27 -0
  87. package/dist/reference/sections-validate.js +41 -0
  88. package/dist/reference/sections.d.mts +97 -0
  89. package/dist/reference/sections.mjs +306 -0
  90. package/dist/reference/tables-validate.d.ts +1 -1
  91. package/dist/reference/types.d.ts +24 -1
  92. package/dist/reference/unit-words.d.ts +37 -0
  93. package/dist/reference/unit-words.js +170 -0
  94. package/dist/rules/catalog.d.ts +176 -0
  95. package/dist/rules/catalog.js +983 -0
  96. package/dist/rules/course-rules.d.ts +115 -0
  97. package/dist/rules/course-rules.js +292 -0
  98. package/dist/rules/course-rules.schema.json +138 -0
  99. package/dist/rules/in-effect.d.ts +53 -0
  100. package/dist/rules/in-effect.js +142 -0
  101. package/dist/rules/rules.schema.json +1072 -0
  102. package/dist/rules/settings.d.ts +3 -0
  103. package/dist/rules/settings.js +290 -0
  104. package/dist/rules/waivers.d.ts +53 -0
  105. package/dist/rules/waivers.js +132 -0
  106. package/dist/session/PreferenceStore.d.ts +5 -0
  107. package/dist/session/PreferenceStore.js +9 -0
  108. package/docs/VERSIONING.md +2 -2
  109. package/docs/authoring/README.md +232 -40
  110. package/docs/authoring/agents.md +55 -9
  111. package/docs/authoring/components.md +178 -22
  112. package/docs/authoring/migrating.md +1324 -2
  113. package/docs/authoring/notation.md +616 -344
  114. package/docs/authoring/prose.md +551 -0
  115. package/docs/authoring/retired.md +35 -0
  116. package/docs/authoring/structure-rules.md +422 -40
  117. package/docs/authoring/structure.md +445 -62
  118. package/docs/authoring/versioning.md +20 -28
  119. package/docs/authoring/visualizations.md +142 -20
  120. package/docs/katex-bracket-scaling.md +191 -0
  121. package/package.json +3 -2
  122. package/src/compiler/cli.ts +139 -6
  123. package/src/compiler/collections.ts +144 -10
  124. package/src/compiler/course-config.ts +273 -34
  125. package/src/compiler/knowledge-version.ts +1 -1
  126. package/src/compiler/lesson-derivation.ts +54 -15
  127. package/src/compiler/logging.ts +21 -4
  128. package/src/compiler/manifest.ts +573 -104
  129. package/src/components/BlockHeading.astro +113 -0
  130. package/src/components/CourseContents.astro +221 -0
  131. package/src/components/CurriculumMap.astro +5 -6
  132. package/src/components/FloatIndex.astro +200 -0
  133. package/src/components/HoverPanel.astro +431 -79
  134. package/src/components/PartBridge.astro +27 -0
  135. package/src/components/PartOverview.astro +401 -0
  136. package/src/components/PartScope.astro +27 -0
  137. package/src/components/abbreviation/AbbreviationLayer.astro +49 -51
  138. package/src/components/assessments/AssessmentRunner.tsx +8 -5
  139. package/src/components/assessments/AssessmentSet.astro +23 -26
  140. package/src/components/block-heading.ts +146 -0
  141. package/src/components/card-arrow.ts +243 -0
  142. package/src/components/card-drag.ts +631 -0
  143. package/src/components/card-jump.ts +45 -0
  144. package/src/components/card-manager.ts +1589 -0
  145. package/src/components/card-memory.ts +177 -0
  146. package/src/components/card-placement.ts +504 -0
  147. package/src/components/card-side.ts +336 -0
  148. package/src/components/card-travel.ts +197 -0
  149. package/src/components/citation/CitationLayer.astro +77 -76
  150. package/src/components/diagram/Diagram.astro +161 -316
  151. package/src/components/disclosure/course-disclosure.ts +24 -0
  152. package/src/components/disclosure/hover-preview.ts +122 -42
  153. package/src/components/disclosure/persisted-disclosure.ts +3 -3
  154. package/src/components/disclosure/print-as-shown.ts +252 -0
  155. package/src/components/examples/CompactExample.astro +3 -1
  156. package/src/components/examples/CompactExamples.astro +101 -94
  157. package/src/components/figure/Figure.astro +174 -274
  158. package/src/components/figure/FloatCaption.astro +87 -0
  159. package/src/components/figure/figure-state.ts +58 -0
  160. package/src/components/figure/figure-tools.ts +87 -0
  161. package/src/components/float-name.ts +101 -0
  162. package/src/components/fold/Fold.astro +125 -0
  163. package/src/components/fold/fold-id.ts +40 -0
  164. package/src/components/memo/Memo.astro +162 -231
  165. package/src/components/memo/ShortNote.astro +6 -6
  166. package/src/components/notation/NotationFormula.astro +5 -1
  167. package/src/components/notation/NotationGlossary.astro +24 -14
  168. package/src/components/notation/NotationLayer.astro +169 -162
  169. package/src/components/notation/NotationMath.astro +10 -1
  170. package/src/components/notation/render-notation-math.ts +111 -10
  171. package/src/components/reference/FloatRefLayer.astro +485 -423
  172. package/src/components/starlight/AboutPanel.astro +22 -6
  173. package/src/components/starlight/CollapsibleSections.astro +207 -93
  174. package/src/components/starlight/DiagramEnhancer.astro +15 -138
  175. package/src/components/starlight/HeadingAnchorEnhancer.astro +193 -1
  176. package/src/components/starlight/KeyedFloatEnhancer.astro +100 -9
  177. package/src/components/starlight/LayoutHeader.astro +298 -19
  178. package/src/components/starlight/LayoutPageSidebar.astro +99 -12
  179. package/src/components/starlight/LayoutPageTitle.astro +120 -0
  180. package/src/components/starlight/LayoutSidebar.astro +229 -11
  181. package/src/components/starlight/LessonExportActions.astro +53 -72
  182. package/src/components/starlight/LessonFooter.astro +26 -4
  183. package/src/components/starlight/section-parts.ts +15 -2
  184. package/src/components/starlight/section-summary.ts +181 -0
  185. package/src/components/table/Table.astro +95 -76
  186. package/src/components/table/TableOverflowExpander.astro +65 -28
  187. package/src/content-config.ts +40 -4
  188. package/src/reference/base-accents.mjs +31 -0
  189. package/src/reference/base-roman-labels.mjs +2 -2
  190. package/src/reference/consistency.ts +176 -5
  191. package/src/reference/delimiter-size.ts +40 -61
  192. package/src/reference/diagrams-validate.ts +17 -1
  193. package/src/reference/equations-validate.ts +10 -5
  194. package/src/reference/equations.mjs +90 -44
  195. package/src/reference/examples-validate.ts +97 -0
  196. package/src/reference/figures-validate.ts +6 -1
  197. package/src/reference/folds-validate.ts +65 -0
  198. package/src/reference/gate-abbreviations.ts +3 -2
  199. package/src/reference/gate-course-rules.ts +134 -0
  200. package/src/reference/gate-heading-length.ts +68 -37
  201. package/src/reference/gate-heading-structure.ts +193 -0
  202. package/src/reference/gate-math-punctuation.ts +381 -0
  203. package/src/reference/gate-math.ts +119 -12
  204. package/src/reference/gate-part-pages.ts +217 -0
  205. package/src/reference/gate-prose-notation.ts +74 -13
  206. package/src/reference/katex-delimiters.mjs +138 -0
  207. package/src/reference/katex-options.mjs +4 -0
  208. package/src/reference/keyed-float-validate.ts +45 -11
  209. package/src/reference/keyed-jsx-floats.mjs +73 -20
  210. package/src/reference/knowledge-checks-validate.ts +1 -1
  211. package/src/reference/knowledge-checks.mjs +9 -4
  212. package/src/reference/lab-math-slot.ts +36 -0
  213. package/src/reference/math-glyphs.mjs +588 -39
  214. package/src/reference/raw-floats-validate.ts +1 -1
  215. package/src/reference/raw-floats.mjs +1 -1
  216. package/src/reference/rehype-heading-anchors.mjs +116 -17
  217. package/src/reference/rehype-table-header-labels.mjs +21 -3
  218. package/src/reference/remark-asides.mjs +180 -0
  219. package/src/reference/remark-memo.mjs +42 -11
  220. package/src/reference/remark-notation.mjs +387 -36
  221. package/src/reference/render-lab-math.ts +17 -32
  222. package/src/reference/sections-validate.ts +81 -0
  223. package/src/reference/sections.mjs +306 -0
  224. package/src/reference/tables-validate.ts +2 -1
  225. package/src/reference/types.ts +24 -0
  226. package/src/reference/unit-words.ts +233 -0
  227. package/src/rules/catalog.ts +1298 -0
  228. package/src/rules/course-rules.ts +456 -0
  229. package/src/rules/in-effect.ts +235 -0
  230. package/src/rules/settings.ts +448 -0
  231. package/src/rules/waivers.ts +249 -0
  232. package/src/session/PreferenceStore.ts +15 -0
  233. package/src/styles/global.css +1471 -163
  234. package/src/styles/surfaces.css +182 -0
  235. package/src/components/hover-panel.ts +0 -339
  236. package/src/components/popup-stack.ts +0 -345
package/README.md CHANGED
@@ -1,3 +1,8 @@
1
+ <picture>
2
+ <source media="(prefers-color-scheme: dark)" srcset="playground/src/assets/explico-mark-dark.svg">
3
+ <img src="playground/src/assets/explico-mark.svg" alt="" width="88" align="right">
4
+ </picture>
5
+
1
6
  # explico
2
7
 
3
8
  A build-time engine for **prerequisite-aware, notation-rigorous course
@@ -23,6 +28,13 @@ What it gives a course:
23
28
  - **Equation identity** — `\label{eq:key}` gives a display equation a stable
24
29
  key, an appearance-order number, an anchor, and cross-lesson `[[eq-key]]`
25
30
  references.
31
+ - **Outline numbering** — sections number `part.chapter.section` like a book
32
+ (`§ 1.1.2`), floats `part.chapter.n` (`Figure 2.2.1`); `[[sec-…]]` cites a
33
+ section, a heading's chain icon copies its link, and a typed number form
34
+ (`#sec-1.1.2`) lands on the named anchor. A knowledge check and a set of
35
+ worked examples are numbered sections of their own: the author writes the
36
+ topic, the engine the kind's badge and the number (`[[ex-…]]`,
37
+ `[[check-…]]`).
26
38
  - **A KaTeX trust boundary** — build-time HTML + MathML; the trust callback
27
39
  accepts exactly one validated `data-notation-key`. No runtime math, no CDN.
28
40
  - **A deterministic JSON manifest** — the MCP/LMS contract: lessons, notation,
@@ -78,8 +90,10 @@ import remarkNotation, {
78
90
  } from 'explico/reference/remark-notation.mjs';
79
91
  import remarkShortNote from 'explico/reference/remark-short-note.mjs';
80
92
  import remarkCitation from 'explico/reference/remark-citation.mjs';
93
+ import remarkAsides from 'explico/reference/remark-asides.mjs';
81
94
  import { createNotationKatexOptions } from 'explico/reference/katex-options.mjs';
82
95
  import rehypeFailKatexErrors from 'explico/reference/rehype-fail-katex-errors.mjs';
96
+ import rehypeHeadingAnchors from 'explico/reference/rehype-heading-anchors.mjs';
83
97
  import { courseConfig } from './content/course.config.ts';
84
98
 
85
99
  const manifest = await loadManifest(courseConfig);
@@ -93,14 +107,26 @@ const manifest = await loadManifest(courseConfig);
93
107
  // // the build when a catalogued lesson has equations/tables/figures but no
94
108
  // // numbering or cross-page entry, instead of silently numbering it by
95
109
  // // `##` section while references to it read the part scheme.
96
- // [remarkNotation, createRemarkNotationOptions(manifest, { strict: true })],
97
- // [remarkCitation, { sources: manifest.sources }],
110
+ // // `base` is the Astro `base` of a subpath deployment (e.g. a GitHub
111
+ // // Pages project site); the links these plugins write 404 without it.
112
+ // [remarkNotation, createRemarkNotationOptions(manifest, { strict: true, base })],
113
+ // // The generated References section's first state, from the course's
114
+ // // `disclosure` settings block (`CourseConfig.disclosure`).
115
+ // [remarkCitation, {
116
+ // sources: manifest.sources,
117
+ // defaultOpen: courseConfig.disclosure?.references?.defaultOpen === true,
118
+ // }],
119
+ // // Last: renders `:::note` / `:::tip` / `:::caution` / `:::danger` as
120
+ // // Starlight's `<Aside>` (Starlight's own pass skips `content/docs/`).
121
+ // remarkAsides,
98
122
  // ]
99
123
  // … integrations: [explicoContentRefresh(courseConfig, manifest),
100
124
  // starlight({ customCss: ['explico/styles/global.css'],
101
125
  // components: { Footer: 'explico/components/starlight/LessonFooter.astro',
102
126
  // SocialIcons: 'explico/components/starlight/LayoutSocialIcons.astro', … } })
103
- // … rehypePlugins: [[rehypeKatex, createNotationKatexOptions()], rehypeFailKatexErrors]
127
+ // … rehypePlugins: [[rehypeKatex, createNotationKatexOptions()], rehypeFailKatexErrors,
128
+ // // Heading collapse defaults: the same `disclosure` block.
129
+ // [rehypeHeadingAnchors, { defaultCollapsible: courseConfig.disclosure }]]
104
130
  ```
105
131
 
106
132
  `loadManifest(courseConfig)` always recompiles current content before Astro
@@ -109,18 +135,18 @@ and restarts the development server when any file under `content/` is added,
109
135
  changed, or removed, keeping Markdown transforms and UI components on the same
110
136
  model without an editor-specific task.
111
137
 
112
- A memo is a substantial optional derivation: it behaves like a collapsible,
113
- numbered subsection without adding a heading to the page outline. Cite it from
114
- earlier prose with `[[memo-id]]`, then place the block wherever it best fits.
115
- The citation, the block's own number, and its chain icon link to the same URL
116
- fragment:
138
+ A memo is a substantial optional derivation: it folds like a `######`
139
+ section without adding a heading to the page outline. Cite it from earlier
140
+ prose with `[[memo-id]]` — a superscript letter — then place the block
141
+ wherever it best fits. The citation and the chain icon on its bar link to the
142
+ same URL fragment:
117
143
 
118
144
  ```mdx
119
145
  The omitted calculation is recorded in [[memo-full-check]].
120
146
 
121
147
  Several paragraphs may intervene.
122
148
 
123
- <Memo id="full-check" title="Full check" description="The omitted algebra and evidence.">
149
+ <Memo id="full-check" short="Full check" caption="The omitted algebra and evidence">
124
150
 
125
151
  The longer derivation can contain paragraphs, equations, tables, and figures.
126
152
 
@@ -140,9 +166,24 @@ Differentiate the polynomial, set the result to zero, and solve.
140
166
  </ShortNote>. But symmetry is quicker.
141
167
  ```
142
168
 
143
- Both components require literal `id`, `title`, and `description` strings.
144
- Memos start collapsed and persist reader state unless `rememberState={false}`;
145
- short notes always start collapsed.
169
+ A memo requires literal `id` and `caption` strings (`short` is optional); a
170
+ short note requires `id`, `title` and `description`. Memos start as the
171
+ course's `disclosure.memos` says (closed by default) and persist reader state
172
+ unless `rememberState={false}`; short notes always start collapsed.
173
+
174
+ A figure or a diagram never folds by itself. To let a reader fold one away
175
+ where it stands — or any stretch of content — import
176
+ `explico/components/fold/Fold.astro` and wrap it in `<Fold>`: no heading,
177
+ number or letter, only a bar with the section chevron. The bar is its `label`
178
+ or, without one, the numbers and short captions of the floats inside
179
+ ("Diagram 2.1.1 Steps of the derivation"). Folds start as the course's
180
+ `disclosure.folds` says (closed by default), or as `defaultOpen` says:
181
+
182
+ ```mdx
183
+ <Fold>
184
+ <Diagram id="steps" caption="…" short="Steps of the derivation" nodes={…} edges={…} />
185
+ </Fold>
186
+ ```
146
187
 
147
188
  ### Lesson exports
148
189
 
@@ -176,9 +217,13 @@ viewer’s system color scheme.
176
217
 
177
218
  The **Print / save as PDF** action calls the browser's native print dialog; it
178
219
  does not download or build a `.pdf` file itself. The print layout removes site
179
- chrome, expands disclosures and complete data surfaces without changing saved
180
- reader state, hides controls that have no paper equivalent while retaining their
181
- current visual result, and keeps linked text clickable with canonical destinations.
220
+ chrome and prints every disclosure as the reader left it on screen: an open
221
+ section, figure, memo, or question prints in full, and a closed one prints its
222
+ title and a "(content collapsed — full text: …)" note with its permalink.
223
+ Complete data surfaces (every table row, every worked example of an open set)
224
+ still print in full. It does all this without changing saved reader state,
225
+ hides controls that have no paper equivalent while retaining their current
226
+ visual result, and keeps linked text clickable with canonical destinations.
182
227
  Paper size, margins, browser-added headers/footers, final filename, and the
183
228
  choice to save as PDF remain browser and reader settings.
184
229
 
@@ -1,6 +1,8 @@
1
1
  import { AstroConfig, AstroIntegration } from 'astro';
2
- import { CourseConfig } from './course-config.js';
2
+ import { C as CourseConfig } from '../course-config-Dsnri9Tk.js';
3
3
  import { Manifest } from './manifest.js';
4
+ import '../rules/catalog.js';
5
+ import '../rules/course-rules.js';
4
6
  import '../curriculum/validation.js';
5
7
  import '../reference/consistency.js';
6
8
  import './collections.js';
@@ -11,11 +13,14 @@ import '../reference/tables-validate.js';
11
13
  import '../reference/keyed-float-validate.js';
12
14
  import '../reference/figures-validate.js';
13
15
  import '../reference/diagrams-validate.js';
16
+ import '../reference/examples-validate.js';
17
+ import '../reference/folds-validate.js';
14
18
  import '../reference/knowledge-checks-validate.js';
15
19
  import '../reference/raw-floats-validate.js';
16
20
  import '../reference/gate-math.js';
17
21
  import '../reference/mixed-content.js';
18
22
  import '../reference/gloss.js';
23
+ import '../rules/waivers.js';
19
24
  import './knowledge-version.js';
20
25
  import './lesson-derivation.js';
21
26
 
@@ -1,8 +1,12 @@
1
1
  import { DiagnosticSeverity } from '../reference/types.js';
2
+ import { EquationLink } from '../reference/gate-math.js';
2
3
  import { Manifest, ManifestLesson } from './manifest.js';
4
+ import { RulesFilter, RuleInEffect } from '../rules/in-effect.js';
3
5
  import '../curriculum/validation.js';
4
6
  import '../reference/consistency.js';
5
- import './course-config.js';
7
+ import '../course-config-Dsnri9Tk.js';
8
+ import '../rules/catalog.js';
9
+ import '../rules/course-rules.js';
6
10
  import './collections.js';
7
11
  import '../reference/lint-ignore.js';
8
12
  import '../reference/equations-validate.js';
@@ -10,11 +14,13 @@ import '../reference/tables-validate.js';
10
14
  import '../reference/keyed-float-validate.js';
11
15
  import '../reference/figures-validate.js';
12
16
  import '../reference/diagrams-validate.js';
17
+ import '../reference/examples-validate.js';
18
+ import '../reference/folds-validate.js';
13
19
  import '../reference/knowledge-checks-validate.js';
14
20
  import '../reference/raw-floats-validate.js';
15
- import '../reference/gate-math.js';
16
21
  import '../reference/mixed-content.js';
17
22
  import '../reference/gloss.js';
23
+ import '../rules/waivers.js';
18
24
  import './knowledge-version.js';
19
25
  import './lesson-derivation.js';
20
26
 
@@ -56,11 +62,24 @@ declare class ContentCliError extends Error {
56
62
  /** One normalized diagnostic. `code` is `<source>:<kind>` and always stable. */
57
63
  interface CliDiagnostic {
58
64
  readonly code: string;
65
+ /**
66
+ * The catalog entry that owns the code — a rule ID (`N9`) or an invariant
67
+ * slug (`keyed-floats`). Absent only for a code the catalog does not know,
68
+ * which `tests/rules/catalog.test.ts` treats as a bug.
69
+ */
70
+ readonly rule?: string;
59
71
  readonly severity: DiagnosticSeverity;
60
72
  readonly message: string;
61
73
  readonly file?: string;
62
74
  readonly line?: number;
63
75
  readonly column?: number;
76
+ /**
77
+ * The keyed equation the finding sits in, when the math carries a
78
+ * `\label{eq:…}` — `{ key, href }`, the href being the anchor on the
79
+ * rendered page. A size-only finding inside a long `aligned` block is
80
+ * otherwise anonymous.
81
+ */
82
+ readonly equation?: EquationLink;
64
83
  }
65
84
  /**
66
85
  * Every manifest diagnostic that names this lesson (by id or file), normalized
@@ -116,6 +135,7 @@ interface StatusReport {
116
135
  readonly keyedTables: number;
117
136
  readonly keyedFigures: number;
118
137
  readonly keyedDiagrams: number;
138
+ readonly keyedExamples: number;
119
139
  };
120
140
  readonly draftDebt: {
121
141
  readonly lessons: readonly string[];
@@ -143,6 +163,23 @@ interface WarningsReport {
143
163
  declare function contentDiagnostics(manifest: Manifest, minimumSeverity?: DiagnosticThreshold): DiagnosticsReport;
144
164
  /** Every warning exactly; errors and informational advice are excluded. */
145
165
  declare function contentWarnings(manifest: Manifest): WarningsReport;
166
+ interface RulesReport {
167
+ readonly filter: RulesFilter;
168
+ readonly count: number;
169
+ readonly rules: readonly RuleInEffect[];
170
+ /** The same selection as the page `validate:content` writes. */
171
+ readonly markdown: string;
172
+ }
173
+ /**
174
+ * Parse `--element a,b` / `--enforcement script` into a filter, throwing a
175
+ * usage error on a value outside the vocabulary.
176
+ */
177
+ declare function parseRulesFilter(input: {
178
+ readonly element?: string;
179
+ readonly enforcement?: string;
180
+ }): RulesFilter;
181
+ /** The rules this course has in effect, optionally narrowed. */
182
+ declare function contentRules(manifest: Manifest, filter?: RulesFilter): RulesReport;
146
183
  interface ContextReport {
147
184
  readonly lesson: {
148
185
  readonly id: string;
@@ -189,9 +226,23 @@ interface ContextReport {
189
226
  key: string;
190
227
  number: string;
191
228
  }[];
229
+ /** Worked-example sets: `short` is the title `[[ex-key]]` names. */
230
+ readonly examples: readonly {
231
+ key: string;
232
+ number: string;
233
+ short?: string;
234
+ }[];
192
235
  readonly knowledgeChecks: readonly {
193
236
  key: string;
194
237
  number: string;
238
+ title?: string;
239
+ }[];
240
+ /** The top-level headings: `slug` is what `[[sec-slug]]` names. */
241
+ readonly sections: readonly {
242
+ slug: string;
243
+ text: string;
244
+ level: number;
245
+ number?: string;
195
246
  }[];
196
247
  readonly sources: readonly {
197
248
  id: string;
@@ -235,4 +286,4 @@ interface Scaffold {
235
286
  declare function scaffoldLesson(rawId: string): Scaffold;
236
287
  declare function scaffoldTerm(rawKey: string): Scaffold;
237
288
 
238
- export { type CheckLessonResult, type CheckReport, type CliDiagnostic, type ContentCliCode, ContentCliError, type ContextReport, type DiagnosticThreshold, type DiagnosticsReport, type DomainFileSignatures, type RenderResult, type Scaffold, type StatusReport, type TokenCounts, type WarningsReport, changedLessonIds, contentCheck, contentContext, contentDiagnostics, contentStatus, contentWarnings, estimateTokens, findLesson, lessonDiagnostics, normalizeLessonId, renderLesson, scaffoldLesson, scaffoldTerm };
289
+ export { type CheckLessonResult, type CheckReport, type CliDiagnostic, type ContentCliCode, ContentCliError, type ContextReport, type DiagnosticThreshold, type DiagnosticsReport, type DomainFileSignatures, type RenderResult, type RulesReport, type Scaffold, type StatusReport, type TokenCounts, type WarningsReport, changedLessonIds, contentCheck, contentContext, contentDiagnostics, contentRules, contentStatus, contentWarnings, estimateTokens, findLesson, lessonDiagnostics, normalizeLessonId, parseRulesFilter, renderLesson, scaffoldLesson, scaffoldTerm };
@@ -18,6 +18,11 @@ import remarkNotation, {
18
18
  } from "../reference/remark-notation.mjs";
19
19
  import remarkShortNote from "../reference/remark-short-note.mjs";
20
20
  import { isLessonSlug, lessonIdFromSlug } from "./lesson-derivation.js";
21
+ import { isCourseRuleId, ownerOf, ELEMENTS } from "../rules/catalog.js";
22
+ import {
23
+ renderRulesInEffect,
24
+ rulesInEffect
25
+ } from "../rules/in-effect.js";
21
26
  const REPOSITORY_ROOT = process.cwd();
22
27
  const CONTENT_ROOT = join(REPOSITORY_ROOT, "content");
23
28
  const DOCS_ROOT = join(CONTENT_ROOT, "docs");
@@ -33,6 +38,15 @@ class ContentCliError extends Error {
33
38
  function diagnosticSeverity(value) {
34
39
  return value === "info" || value === "warning" ? value : "error";
35
40
  }
41
+ function ownerFields(code) {
42
+ const bare = code.slice(code.indexOf(":") + 1);
43
+ const owner = ownerOf(bare);
44
+ if (owner !== void 0) return { rule: owner.id };
45
+ return isCourseRuleId(bare) ? { rule: bare } : {};
46
+ }
47
+ function withOwner(diagnostic) {
48
+ return { ...diagnostic, ...ownerFields(diagnostic.code) };
49
+ }
36
50
  function mentions(message, lessonId, file) {
37
51
  return message.includes(lessonId) || message.includes(file) || message.includes(file.replace(/^content\/docs\//, ""));
38
52
  }
@@ -71,12 +85,13 @@ function lessonDiagnostics(manifest, lesson) {
71
85
  );
72
86
  if (!forLesson) continue;
73
87
  out.push({
74
- code: `math:${diagnostic.code ?? diagnostic.context}`,
88
+ code: `math:${diagnostic.code}`,
75
89
  severity: diagnostic.severity,
76
90
  message: diagnostic.message,
77
91
  file: diagnostic.file,
78
92
  ...diagnostic.line === void 0 ? {} : { line: diagnostic.line },
79
- ...diagnostic.column === void 0 ? {} : { column: diagnostic.column }
93
+ ...diagnostic.column === void 0 ? {} : { column: diagnostic.column },
94
+ ...diagnostic.equation === void 0 ? {} : { equation: diagnostic.equation }
80
95
  });
81
96
  }
82
97
  for (const diagnostic of manifest.diagnostics.equations) {
@@ -95,6 +110,8 @@ function lessonDiagnostics(manifest, lesson) {
95
110
  ...manifest.diagnostics.figures,
96
111
  ...manifest.diagnostics.diagrams,
97
112
  ...manifest.diagnostics.checks,
113
+ ...manifest.diagnostics.examples,
114
+ ...manifest.diagnostics.folds,
98
115
  ...manifest.diagnostics.rawFloats
99
116
  ]) {
100
117
  if (diagnostic.lessonId !== id && diagnostic.file !== file && !mentions(diagnostic.message, id, file)) {
@@ -116,7 +133,7 @@ function lessonDiagnostics(manifest, lesson) {
116
133
  ...withFile(diagnostic.file)
117
134
  });
118
135
  }
119
- return out.sort(
136
+ return out.map(withOwner).sort(
120
137
  (a, b) => a.code.localeCompare(b.code) || a.message.localeCompare(b.message)
121
138
  );
122
139
  }
@@ -305,6 +322,7 @@ function contentStatus(manifest) {
305
322
  const draftNotation = manifest.notation.definitions.filter((definition) => definition.status === "draft").map((definition) => definition.id).sort();
306
323
  const orphans = manifest.notation.diagnostics.filter((diagnostic) => diagnostic.code === "unused-definition").map((diagnostic) => ({
307
324
  code: `notation:${diagnostic.code}`,
325
+ ...ownerFields(diagnostic.code),
308
326
  severity: diagnosticSeverity(diagnostic.severity),
309
327
  message: diagnostic.message,
310
328
  ...diagnostic.source?.file === void 0 ? {} : { file: diagnostic.source.file }
@@ -319,9 +337,13 @@ function contentStatus(manifest) {
319
337
  ...manifest.diagnostics.figures.map((d) => d.severity),
320
338
  ...manifest.diagnostics.diagrams.map((d) => d.severity),
321
339
  ...manifest.diagnostics.checks.map((d) => d.severity),
340
+ ...manifest.diagnostics.examples.map((d) => d.severity),
341
+ ...manifest.diagnostics.folds.map((d) => d.severity),
322
342
  ...manifest.diagnostics.rawFloats.map((d) => d.severity),
323
343
  ...manifest.diagnostics.consistency.map((d) => d.severity),
324
- ...manifest.diagnostics.knowledge.map((d) => d.severity)
344
+ ...manifest.diagnostics.knowledge.map((d) => d.severity),
345
+ ...manifest.diagnostics.config.map((d) => d.severity),
346
+ ...manifest.diagnostics.waivers.map((d) => d.severity)
325
347
  ];
326
348
  const keyedEquations = manifest.lessons.reduce(
327
349
  (count, lesson) => count + lesson.equations.length,
@@ -339,6 +361,10 @@ function contentStatus(manifest) {
339
361
  (count, lesson) => count + lesson.diagrams.length,
340
362
  0
341
363
  );
364
+ const keyedExamples = manifest.lessons.reduce(
365
+ (count, lesson) => count + lesson.examples.length,
366
+ 0
367
+ );
342
368
  const assessments = new Set(
343
369
  manifest.lessons.flatMap((lesson) => lesson.assessments)
344
370
  ).size;
@@ -354,7 +380,8 @@ function contentStatus(manifest) {
354
380
  keyedEquations,
355
381
  keyedTables,
356
382
  keyedFigures,
357
- keyedDiagrams
383
+ keyedDiagrams,
384
+ keyedExamples
358
385
  },
359
386
  draftDebt: { lessons: draftLessons, notation: draftNotation },
360
387
  orphans,
@@ -365,6 +392,11 @@ function contentStatus(manifest) {
365
392
  }
366
393
  };
367
394
  }
395
+ function equationLink(value) {
396
+ if (value === null || typeof value !== "object") return void 0;
397
+ const { key, href } = value;
398
+ return typeof key === "string" && typeof href === "string" ? { key, href } : void 0;
399
+ }
368
400
  function numericLocation(value) {
369
401
  return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : void 0;
370
402
  }
@@ -394,13 +426,16 @@ function contentDiagnostics(manifest, minimumSeverity = "info") {
394
426
  const line = numericLocation(diagnostic.line) ?? numericLocation(source?.line);
395
427
  const column = numericLocation(diagnostic.column) ?? numericLocation(source?.column);
396
428
  const detail = typeof diagnostic.code === "string" ? diagnostic.code : typeof diagnostic.kind === "string" ? diagnostic.kind : "diagnostic";
429
+ const equation = equationLink(diagnostic.equation);
397
430
  diagnostics.push({
398
431
  code: `${group}:${detail}`,
432
+ ...ownerFields(detail),
399
433
  severity,
400
434
  message: String(diagnostic.message ?? detail),
401
435
  ...file === void 0 ? {} : { file },
402
436
  ...line === void 0 ? {} : { line },
403
- ...column === void 0 ? {} : { column }
437
+ ...column === void 0 ? {} : { column },
438
+ ...equation === void 0 ? {} : { equation }
404
439
  });
405
440
  }
406
441
  }
@@ -419,6 +454,41 @@ function contentWarnings(manifest) {
419
454
  );
420
455
  return { count: warnings.length, warnings };
421
456
  }
457
+ function parseRulesFilter(input) {
458
+ const filter = {};
459
+ if (input.element !== void 0) {
460
+ const elements = input.element.split(",").map((e) => e.trim()).filter(Boolean);
461
+ for (const element of elements) {
462
+ if (!ELEMENTS.includes(element)) {
463
+ throw new ContentCliError(
464
+ "usage",
465
+ `--element ${element}: not an element. One of: ${ELEMENTS.join(", ")}.`
466
+ );
467
+ }
468
+ }
469
+ filter.elements = elements;
470
+ }
471
+ if (input.enforcement !== void 0) {
472
+ const kinds = ["script", "partial", "guide", "locked"];
473
+ if (!kinds.includes(input.enforcement)) {
474
+ throw new ContentCliError(
475
+ "usage",
476
+ `--enforcement ${input.enforcement}: one of ${kinds.join(", ")}.`
477
+ );
478
+ }
479
+ filter.enforcement = input.enforcement;
480
+ }
481
+ return filter;
482
+ }
483
+ function contentRules(manifest, filter = {}) {
484
+ const rules = rulesInEffect(manifest, filter);
485
+ return {
486
+ filter,
487
+ count: rules.length,
488
+ rules,
489
+ markdown: renderRulesInEffect(manifest, filter)
490
+ };
491
+ }
422
492
  function contentContext(manifest, rawId) {
423
493
  const lesson = findLesson(manifest, rawId);
424
494
  const { frontmatter, body } = lessonRaw(lesson);
@@ -466,7 +536,9 @@ function contentContext(manifest, rawId) {
466
536
  tables: lesson.tables.map((table) => ({ ...table })),
467
537
  figures: lesson.figures.map((figure) => ({ ...figure })),
468
538
  diagrams: lesson.diagrams.map((diagram) => ({ ...diagram })),
539
+ examples: lesson.examples.map((example) => ({ ...example })),
469
540
  knowledgeChecks: lesson.knowledgeChecks.map((check) => ({ ...check })),
541
+ sections: lesson.headings.map((heading) => ({ ...heading })),
470
542
  sources: lesson.sources.map((id) => {
471
543
  const record = sourceById.get(id);
472
544
  return {
@@ -659,12 +731,14 @@ export {
659
731
  contentCheck,
660
732
  contentContext,
661
733
  contentDiagnostics,
734
+ contentRules,
662
735
  contentStatus,
663
736
  contentWarnings,
664
737
  estimateTokens,
665
738
  findLesson,
666
739
  lessonDiagnostics,
667
740
  normalizeLessonId,
741
+ parseRulesFilter,
668
742
  renderLesson,
669
743
  scaffoldLesson,
670
744
  scaffoldTerm
@@ -1,5 +1,5 @@
1
1
  import { CurriculumCatalog } from '../curriculum/validation.js';
2
- import { NotationReferenceInput, AbbreviationDefinitionInput, NotationRegistryInput } from '../reference/types.js';
2
+ import { NotationReferenceInput, AbbreviationDefinitionInput, NotationRegistryInput, LocalNotationDefinitionInput, NotationSuspension } from '../reference/types.js';
3
3
 
4
4
  /**
5
5
  * The single path from content files to typed data.
@@ -19,8 +19,41 @@ import { NotationReferenceInput, AbbreviationDefinitionInput, NotationRegistryIn
19
19
  * ignoring code fences and inline code, sorted by position.
20
20
  */
21
21
  declare function extractNotationReferences(text: string, file: string, kind: NotationReferenceInput['kind'] | undefined): NotationReferenceInput[];
22
+ /**
23
+ * One part's **overview page** — `content/docs/<dir>/index.mdx`, the page the
24
+ * part's own directory URL resolves to. `isLessonSlug` already excludes any
25
+ * `<dir>/index`, so this page is not a chapter: it carries no competencies, no
26
+ * knowledge check, and no float numbers, and nothing in the curriculum graph
27
+ * changes when a course adds one.
28
+ */
29
+ interface PartOverviewPage {
30
+ readonly dir: string;
31
+ readonly slug: string;
32
+ readonly file: string;
33
+ readonly title: string;
34
+ readonly description?: string;
35
+ /** MDX after the frontmatter — what the S2 gate (`gate-part-pages.ts`) reads. */
36
+ readonly body: string;
37
+ /** 1-based file line `body` starts on, for diagnostic locations. */
38
+ readonly bodyLine: number;
39
+ }
40
+ /**
41
+ * The overview page of every part that has one, keyed by the part's directory.
42
+ * A part without one is simply absent — overview pages are opt-in, one part at
43
+ * a time.
44
+ */
45
+ declare function loadPartOverviews(parts: readonly {
46
+ readonly dir: string;
47
+ }[]): PartOverviewPage[];
22
48
  declare function loadCurriculumCatalog(): Promise<CurriculumCatalog>;
23
49
  declare function loadAbbreviationRegistryInput(): Promise<readonly AbbreviationDefinitionInput[]>;
50
+ /**
51
+ * A lesson's `notation.suspend` (rule N22): decorations that are not
52
+ * operators on this page. A name outside `DECORATION_NAMES`, a decoration
53
+ * suspended twice, and a `notation.local` operator entry for a suspended
54
+ * decoration (the page would contradict itself) each fail the load.
55
+ */
56
+ declare function notationSuspensions(raw: unknown, localDefinitions: readonly LocalNotationDefinitionInput[], file: string): NotationSuspension[];
24
57
  declare function loadNotationRegistryInput(): Promise<NotationRegistryInput>;
25
58
  /**
26
59
  * Notation frontmatter objects for `remark-notation`'s `definitions` option.
@@ -68,4 +101,4 @@ declare function notationSourceLocatorGaps(): {
68
101
  total: number;
69
102
  };
70
103
 
71
- export { type NotationSpec, extractNotationReferences, loadAbbreviationRegistryInput, loadCurriculumCatalog, loadLessonChecks, loadNotationDefinitions, loadNotationRegistryInput, loadNotationSpecs, loadSourceRecords, notationSourceLocatorGaps };
104
+ export { type NotationSpec, type PartOverviewPage, extractNotationReferences, loadAbbreviationRegistryInput, loadCurriculumCatalog, loadLessonChecks, loadNotationDefinitions, loadNotationRegistryInput, loadNotationSpecs, loadPartOverviews, loadSourceRecords, notationSourceLocatorGaps, notationSuspensions };