explico 0.3.0 → 0.5.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 (151) hide show
  1. package/CHANGELOG.md +344 -1
  2. package/README.md +117 -3
  3. package/dist/compiler/astro.d.ts +38 -0
  4. package/dist/compiler/astro.js +258 -0
  5. package/dist/compiler/cli.d.ts +13 -0
  6. package/dist/compiler/cli.js +32 -12
  7. package/dist/compiler/collections.d.ts +3 -2
  8. package/dist/compiler/collections.js +28 -1
  9. package/dist/compiler/course-config.d.ts +86 -4
  10. package/dist/compiler/course-config.js +39 -0
  11. package/dist/compiler/exports/standalone-html.d.ts +34 -0
  12. package/dist/compiler/exports/standalone-html.js +803 -0
  13. package/dist/compiler/lesson-derivation.d.ts +1 -1
  14. package/dist/compiler/lesson-derivation.js +2 -2
  15. package/dist/compiler/manifest.d.ts +112 -7
  16. package/dist/compiler/manifest.js +205 -40
  17. package/dist/compiler/resolution-report.d.ts +4 -0
  18. package/dist/reference/base-abbreviations.d.mts +30 -0
  19. package/dist/reference/base-abbreviations.mjs +60 -0
  20. package/dist/reference/base-operators.d.mts +15 -0
  21. package/dist/reference/base-operators.mjs +40 -0
  22. package/dist/reference/base-roman-labels.d.mts +35 -0
  23. package/dist/reference/base-roman-labels.mjs +43 -0
  24. package/dist/reference/consistency.d.ts +8 -2
  25. package/dist/reference/consistency.js +50 -3
  26. package/dist/reference/diagrams-validate.d.ts +21 -0
  27. package/dist/reference/diagrams-validate.js +13 -0
  28. package/dist/reference/equations-validate.d.ts +8 -3
  29. package/dist/reference/equations-validate.js +1 -1
  30. package/dist/reference/equations.d.mts +66 -35
  31. package/dist/reference/equations.mjs +142 -40
  32. package/dist/reference/gate-abbreviations.d.ts +17 -0
  33. package/dist/reference/gate-abbreviations.js +64 -0
  34. package/dist/reference/gate-math.d.ts +28 -5
  35. package/dist/reference/gate-math.js +5 -2
  36. package/dist/reference/gate-prose-notation.d.ts +60 -0
  37. package/dist/reference/gate-prose-notation.js +125 -0
  38. package/dist/reference/keyed-float-validate.d.ts +14 -8
  39. package/dist/reference/keyed-float-validate.js +1 -1
  40. package/dist/reference/keyed-jsx-floats.d.mts +24 -21
  41. package/dist/reference/keyed-jsx-floats.mjs +90 -41
  42. package/dist/reference/knowledge-checks-validate.d.ts +35 -0
  43. package/dist/reference/knowledge-checks-validate.js +78 -0
  44. package/dist/reference/knowledge-checks.d.mts +25 -0
  45. package/dist/reference/knowledge-checks.mjs +45 -0
  46. package/dist/reference/lab-math-scope.d.ts +9 -1
  47. package/dist/reference/lab-math-scope.js +32 -4
  48. package/dist/reference/math-glyphs.d.mts +30 -0
  49. package/dist/reference/math-glyphs.mjs +367 -0
  50. package/dist/reference/mdx-attributes.d.mts +13 -0
  51. package/dist/reference/mdx-attributes.mjs +38 -0
  52. package/dist/reference/mixed-content.d.ts +17 -0
  53. package/dist/reference/mixed-content.js +29 -0
  54. package/dist/reference/page-notation.d.ts +52 -0
  55. package/dist/reference/page-notation.js +171 -0
  56. package/dist/reference/raw-floats-validate.d.ts +18 -0
  57. package/dist/reference/raw-floats-validate.js +53 -0
  58. package/dist/reference/raw-floats.d.mts +18 -0
  59. package/dist/reference/raw-floats.mjs +129 -0
  60. package/dist/reference/rehype-heading-anchors.d.mts +1 -1
  61. package/dist/reference/rehype-heading-anchors.mjs +107 -1
  62. package/dist/reference/rehype-table-header-labels.d.mts +26 -0
  63. package/dist/reference/rehype-table-header-labels.mjs +152 -0
  64. package/dist/reference/remark-abbreviations.d.mts +7 -0
  65. package/dist/reference/remark-abbreviations.mjs +174 -0
  66. package/dist/reference/remark-memo.d.mts +16 -0
  67. package/dist/reference/remark-memo.mjs +339 -0
  68. package/dist/reference/remark-notation.d.mts +102 -5
  69. package/dist/reference/remark-notation.mjs +506 -50
  70. package/dist/reference/remark-short-note.d.mts +4 -0
  71. package/dist/reference/remark-short-note.mjs +237 -0
  72. package/dist/reference/types.d.ts +16 -1
  73. package/dist/reference/units-vocab.js +4 -0
  74. package/docs/VERSIONING.md +33 -4
  75. package/docs/authoring/README.md +12 -0
  76. package/docs/authoring/agents.md +17 -2
  77. package/docs/authoring/components.md +135 -0
  78. package/docs/authoring/migrating.md +154 -7
  79. package/docs/authoring/notation.md +568 -8
  80. package/docs/authoring/structure.md +251 -0
  81. package/docs/authoring/visualizations.md +36 -3
  82. package/package.json +5 -2
  83. package/src/compiler/astro.ts +332 -0
  84. package/src/compiler/cli.ts +33 -10
  85. package/src/compiler/collections.ts +45 -4
  86. package/src/compiler/course-config.ts +131 -3
  87. package/src/compiler/exports/standalone-html.ts +963 -0
  88. package/src/compiler/lesson-derivation.ts +2 -2
  89. package/src/compiler/manifest.ts +367 -49
  90. package/src/components/HoverPanel.astro +45 -4
  91. package/src/components/abbreviation/AbbreviationGlossary.astro +70 -0
  92. package/src/components/abbreviation/AbbreviationLayer.astro +127 -0
  93. package/src/components/assessments/AssessmentRunner.tsx +52 -17
  94. package/src/components/assessments/AssessmentSet.astro +98 -14
  95. package/src/components/assessments/RenderedContent.tsx +55 -0
  96. package/src/components/assessments/assessment-randomization.ts +16 -0
  97. package/src/components/diagram/Diagram.astro +444 -83
  98. package/src/components/disclosure/hover-preview.ts +202 -0
  99. package/src/components/disclosure/persisted-disclosure.ts +66 -0
  100. package/src/components/examples/CompactExamples.astro +2 -0
  101. package/src/components/figure/Figure.astro +279 -10
  102. package/src/components/memo/Memo.astro +453 -0
  103. package/src/components/memo/ShortNote.astro +223 -0
  104. package/src/components/notation/NotationFormula.astro +18 -1
  105. package/src/components/notation/NotationLayer.astro +116 -8
  106. package/src/components/notation/render-notation-math.ts +39 -2
  107. package/src/components/notation/safe-math-to-react.ts +50 -0
  108. package/src/components/reference/FloatRefLayer.astro +233 -15
  109. package/src/components/starlight/CollapsibleSections.astro +461 -0
  110. package/src/components/starlight/DiagramEnhancer.astro +385 -0
  111. package/src/components/starlight/HeadingAnchorEnhancer.astro +5 -17
  112. package/src/components/starlight/KeyedFloatEnhancer.astro +81 -3
  113. package/src/components/starlight/LayoutHeader.astro +50 -0
  114. package/src/components/starlight/LayoutSidebar.astro +62 -1
  115. package/src/components/starlight/LayoutSocialIcons.astro +41 -2
  116. package/src/components/starlight/LessonExportActions.astro +436 -0
  117. package/src/components/starlight/LessonFooter.astro +38 -2
  118. package/src/components/starlight/section-parts.ts +31 -0
  119. package/src/components/table/Table.astro +296 -9
  120. package/src/components/table/TableOverflowExpander.astro +489 -0
  121. package/src/content-config.ts +20 -0
  122. package/src/reference/base-abbreviations.mjs +60 -0
  123. package/src/reference/base-operators.mjs +40 -0
  124. package/src/reference/base-roman-labels.mjs +43 -0
  125. package/src/reference/consistency.ts +73 -2
  126. package/src/reference/diagrams-validate.ts +40 -0
  127. package/src/reference/equations-validate.ts +6 -4
  128. package/src/reference/equations.mjs +142 -40
  129. package/src/reference/gate-abbreviations.ts +128 -0
  130. package/src/reference/gate-math.ts +27 -7
  131. package/src/reference/gate-prose-notation.ts +224 -0
  132. package/src/reference/keyed-float-validate.ts +12 -9
  133. package/src/reference/keyed-jsx-floats.mjs +90 -41
  134. package/src/reference/knowledge-checks-validate.ts +130 -0
  135. package/src/reference/knowledge-checks.mjs +45 -0
  136. package/src/reference/lab-math-scope.ts +45 -3
  137. package/src/reference/math-glyphs.mjs +367 -0
  138. package/src/reference/mdx-attributes.mjs +38 -0
  139. package/src/reference/mixed-content.ts +69 -0
  140. package/src/reference/page-notation.ts +252 -0
  141. package/src/reference/raw-floats-validate.ts +103 -0
  142. package/src/reference/raw-floats.mjs +129 -0
  143. package/src/reference/rehype-heading-anchors.mjs +107 -1
  144. package/src/reference/rehype-table-header-labels.mjs +152 -0
  145. package/src/reference/remark-abbreviations.mjs +174 -0
  146. package/src/reference/remark-memo.mjs +339 -0
  147. package/src/reference/remark-notation.mjs +506 -50
  148. package/src/reference/remark-short-note.mjs +237 -0
  149. package/src/reference/types.ts +16 -0
  150. package/src/reference/units-vocab.ts +4 -0
  151. package/src/styles/global.css +1125 -50
@@ -84,6 +84,6 @@ type SidebarGroup = {
84
84
  * merged track order (ties broken by slug), then the static Reference group.
85
85
  * Replaces seven `autogenerate` blocks that read an authored `sidebar.order`.
86
86
  */
87
- declare function buildSidebar(tracks: readonly TrackLike[], lessonSlugs: readonly string[], sections: readonly SidebarSection[]): SidebarGroup[];
87
+ declare function buildSidebar(tracks: readonly TrackLike[], lessonSlugs: readonly string[], parts: readonly SidebarSection[]): SidebarGroup[];
88
88
 
89
89
  export { type SidebarGroup, type SidebarSection, buildSidebar, citedSourceIds, deriveRequires, deriveSources, isLessonSlug, lessonIdFromSlug, mergeTrackOrder, parseCheckList };
@@ -59,12 +59,12 @@ function mergeTrackOrder(tracks) {
59
59
  }
60
60
  return ordered;
61
61
  }
62
- function buildSidebar(tracks, lessonSlugs, sections) {
62
+ function buildSidebar(tracks, lessonSlugs, parts) {
63
63
  const rank = new Map(
64
64
  mergeTrackOrder(tracks).map((lessonId, index) => [lessonId, index])
65
65
  );
66
66
  const positionOf = (slug) => rank.get(lessonIdFromSlug(slug)) ?? Number.POSITIVE_INFINITY;
67
- const groups = sections.map(({ dir, label }) => ({
67
+ const groups = parts.map(({ dir, label }) => ({
68
68
  label,
69
69
  items: lessonSlugs.filter((slug) => slug.startsWith(`${dir}/`)).sort(
70
70
  (left, right) => positionOf(left) - positionOf(right) || left.localeCompare(right)
@@ -3,10 +3,14 @@ import { ConsistencyDiagnostic } from '../reference/consistency.js';
3
3
  import { EquationLabelRecord, EquationDiagnostic } from '../reference/equations-validate.js';
4
4
  import { TableLabelRecord, TableDiagnostic } from '../reference/tables-validate.js';
5
5
  import { FigureLabelRecord, FigureDiagnostic } from '../reference/figures-validate.js';
6
+ import { DiagramLabelRecord, DiagramDiagnostic } from '../reference/diagrams-validate.js';
7
+ import { KnowledgeCheckLabelRecord, KnowledgeCheckDiagnostic } from '../reference/knowledge-checks-validate.js';
8
+ import { RawFloatDiagnostic } from '../reference/raw-floats-validate.js';
6
9
  import { MathGateDiagnostic } from '../reference/gate-math.js';
10
+ import { ContentSegment } from '../reference/mixed-content.js';
7
11
  import { GlyphScopeEntry } from '../reference/gloss.js';
8
12
  import { NotationDefinitionRecord, NotationPageBundle, NotationBacklink, NotationDiagnostic, EditorialStatus, NotationGlossEntry } from '../reference/types.js';
9
- import { CourseConfig } from './course-config.js';
13
+ import { NormalizedLessonExports, CourseConfig } from './course-config.js';
10
14
  import { KnowledgeVersionDiagnostic } from './knowledge-version.js';
11
15
  import { SidebarGroup } from './lesson-derivation.js';
12
16
  import './collections.js';
@@ -51,14 +55,43 @@ import '../reference/keyed-float-validate.js';
51
55
  * `lessons[].equations`, and `diagnostics.equations`.
52
56
  * v4 (F-tables-figures): `tables`/`figures` (same shape as `equations`, for
53
57
  * `<Table>`/`<Figure>`), `lessons[].tables`/`lessons[].figures`, and
54
- * `diagnostics.tables`/`diagnostics.figures`.
58
+ * `diagnostics.tables`/`diagnostics.figures`/`diagnostics.diagrams`.
55
59
  * v5 (about): `about` (engine identity from `explico`'s `package.json`, the
56
60
  * course's reader-facing `about` block, and the knowledge version resolved
57
61
  * against `content/CHANGELOG.md`) and `diagnostics.knowledge`.
58
62
  * v6 (chapter numbering): `lessons[].chapter` — the lesson's 1-based course
59
63
  * ordinal, the chapter in every equation/table/figure number (`(2.1)`).
64
+ * v7 (part numbering): `lessons[].part` (the sidebar group's 1-based ordinal,
65
+ * now the number before the dot), `lessons[].chapter` redefined as the
66
+ * lesson's ordinal *within its part*, and `lessons[].numbering` — the
67
+ * per-kind offsets that make `n` run continuously across a part.
68
+ * v8 (assessment math): `assessmentContent` — every assessment item's
69
+ * `prompt` / `explanation`, pre-split into text/math segments and resolved
70
+ * against the scope of the lesson that embeds it, so `AssessmentSet.astro`
71
+ * can render them through the same KaTeX pipeline as lesson body math instead
72
+ * of showing raw text.
73
+ * v9 (diagram identity): `diagrams` (the independent `<Diagram>` number and
74
+ * cross-page lookup), `lessons[].diagrams`, `lessons[].numbering.diagrams`,
75
+ * and `diagnostics.diagrams`.
76
+ * v10 (knowledge-check identity): `checks` (part-wide numbering and cross-page
77
+ * lookup), `lessons[].knowledgeChecks`, `lessons[].numbering.checks`, and
78
+ * `diagnostics.checks`.
79
+ * v11 (lesson exports): normalized standalone HTML and print build/UI settings.
80
+ * v12 (abbreviation hover cards): `manifest.abbreviations` — the
81
+ * `abbreviations` content collection (N14), flattened to plain
82
+ * `{key, abbr, expansion, aliases, domain?}` records sorted by key, the way
83
+ * `manifest.notation.raw` already carries notation frontmatter for
84
+ * `remarkNotation`. `remarkAbbreviations` reads it to wrap the first
85
+ * occurrence of a known abbreviation in a lesson's prose with a hover/click
86
+ * card.
87
+ * v13 (raw-float diagnostics, additive): `diagnostics.rawFloats` — a raw
88
+ * Markdown table, a raw HTML `<table>`, or an Explico `<Diagram>` that is
89
+ * unkeyed or nested outside `<Table>`/`<Figure>` never reaches keyed-float
90
+ * validation and can never get a stable number or `[[…]]` reference; this
91
+ * flags it (warning-only — the manifest shape a client already reads is
92
+ * unchanged).
60
93
  */
61
- declare const MANIFEST_SCHEMA_VERSION = 6;
94
+ declare const MANIFEST_SCHEMA_VERSION = 13;
62
95
  declare const MANIFEST_PATH: string;
63
96
  interface ManifestCitation {
64
97
  readonly id: string;
@@ -108,11 +141,31 @@ interface ManifestLesson {
108
141
  /** Docs-loader slug, e.g. `bonds/price-yield-relationship`. */
109
142
  readonly slug: string;
110
143
  /**
111
- * The lesson's 1-based ordinal in the course (merged track order, then any
112
- * unranked lessons by slug). It is the chapter in every equation, table, and
113
- * figure number on the page — `(2.1)` is the first equation of chapter 2.
144
+ * The 1-based ordinal of the **part** this lesson sits in — its sidebar
145
+ * group's position in `courseConfig.parts`. It is the number before the dot
146
+ * in every equation, table, figure, diagram, and knowledge-check number on
147
+ * the page: `(2.1)` is the first equation of part 2. See
148
+ * `docs/authoring/structure.md`.
149
+ */
150
+ readonly part: number;
151
+ /**
152
+ * The lesson's 1-based ordinal **within its part** (merged track order, then
153
+ * any unranked lessons by slug) — its chapter number. This orders the
154
+ * sidebar and the reading path; it does not appear in a float's number.
114
155
  */
115
156
  readonly chapter: number;
157
+ /**
158
+ * How many floats of each kind the earlier lessons of this part already
159
+ * used. The lesson's own floats continue from here, so a part's sequence
160
+ * runs unbroken across its chapters instead of restarting on every page.
161
+ */
162
+ readonly numbering: {
163
+ readonly equations: number;
164
+ readonly tables: number;
165
+ readonly figures: number;
166
+ readonly diagrams: number;
167
+ readonly checks: number;
168
+ };
116
169
  readonly file: string;
117
170
  readonly title: string;
118
171
  readonly description: string;
@@ -122,6 +175,11 @@ interface ManifestLesson {
122
175
  readonly requires: readonly string[];
123
176
  readonly assumptions: readonly string[];
124
177
  readonly assessments: readonly string[];
178
+ /** Numbered knowledge checks from `assessments`, in `.checks.yml` order. */
179
+ readonly knowledgeChecks: readonly {
180
+ readonly key: string;
181
+ readonly number: string;
182
+ }[];
125
183
  readonly sources: readonly string[];
126
184
  readonly citations: readonly ManifestCitation[];
127
185
  /** Competency prerequisite edges this lesson introduces. */
@@ -141,6 +199,11 @@ interface ManifestLesson {
141
199
  readonly key: string;
142
200
  readonly number: string;
143
201
  }[];
202
+ /** Keyed `<Diagram>` elements in appearance order. */
203
+ readonly diagrams: readonly {
204
+ readonly key: string;
205
+ readonly number: string;
206
+ }[];
144
207
  readonly notation: {
145
208
  readonly bindings: readonly {
146
209
  readonly key: string;
@@ -181,8 +244,18 @@ interface ManifestAbout {
181
244
  readonly disclaimer: string;
182
245
  readonly changelogHref?: string;
183
246
  }
247
+ /** One assessment item's pre-rendered `prompt` / `explanation` (v8). */
248
+ interface ManifestAssessmentItemContent {
249
+ readonly lessonId: string;
250
+ readonly assessmentId: string;
251
+ readonly itemId: string;
252
+ readonly promptSegments: readonly ContentSegment[];
253
+ readonly explanationSegments: readonly ContentSegment[];
254
+ }
184
255
  interface Manifest {
185
256
  readonly schemaVersion: number;
257
+ /** Normalized build/UI settings for opt-in per-lesson exports. */
258
+ readonly lessonExports: NormalizedLessonExports;
186
259
  /** Reader-facing identity + versions (the `[?]` About panel). */
187
260
  readonly about: ManifestAbout;
188
261
  readonly notation: {
@@ -196,6 +269,8 @@ interface Manifest {
196
269
  readonly competencies: readonly Record<string, unknown>[];
197
270
  readonly sources: readonly Record<string, unknown>[];
198
271
  readonly tracks: readonly Record<string, unknown>[];
272
+ /** Abbreviations table (N14), for `remarkAbbreviations`. Ordered by key. */
273
+ readonly abbreviations: readonly Record<string, unknown>[];
199
274
  readonly sidebar: readonly SidebarGroup[];
200
275
  readonly lessons: readonly ManifestLesson[];
201
276
  /** Full competency prerequisite graph. */
@@ -216,6 +291,26 @@ interface Manifest {
216
291
  readonly labels: readonly FigureLabelRecord[];
217
292
  readonly numbersBySlug: Record<string, Record<string, string>>;
218
293
  };
294
+ /** Keyed-float identity for `<Diagram>` — independent of figure numbering. */
295
+ readonly diagrams: {
296
+ readonly labels: readonly DiagramLabelRecord[];
297
+ readonly numbersBySlug: Record<string, Record<string, string>>;
298
+ };
299
+ /** Knowledge-check identity, derived from each lesson's `.checks.yml`. */
300
+ readonly checks: {
301
+ readonly labels: readonly KnowledgeCheckLabelRecord[];
302
+ readonly numbersBySlug: Record<string, Record<string, string>>;
303
+ };
304
+ /**
305
+ * Every assessment item's `prompt` / `explanation`, pre-split into
306
+ * text/math segments (v8). `AssessmentSet.astro` filters this by
307
+ * `lessonId` + `assessmentId` and renders each `math` segment's `latex`
308
+ * through `renderNotationMath` before handing the result to
309
+ * `AssessmentRunner`. An assessment embedded in more than one lesson (via
310
+ * more than one `checks.yml`) appears once per lesson here, since each
311
+ * lesson's scope can resolve its glyphs differently.
312
+ */
313
+ readonly assessmentContent: readonly ManifestAssessmentItemContent[];
219
314
  readonly diagnostics: {
220
315
  readonly curriculum: readonly CurriculumIssue[];
221
316
  readonly notation: readonly NotationDiagnostic[];
@@ -231,6 +326,16 @@ interface Manifest {
231
326
  readonly tables: readonly TableDiagnostic[];
232
327
  /** `<Figure>` identity checks (F-tables-figures) — same codes, `fig-` prefixed. */
233
328
  readonly figures: readonly FigureDiagnostic[];
329
+ /** `<Diagram>` identity checks — same codes, `dia-` prefixed. */
330
+ readonly diagrams: readonly DiagramDiagnostic[];
331
+ /** Knowledge-check identity and `[[check-…]]` reference checks. */
332
+ readonly checks: readonly KnowledgeCheckDiagnostic[];
333
+ /**
334
+ * A raw Markdown table, a raw HTML `<table>`, or an Explico `<Diagram>`
335
+ * that is unkeyed or nested outside `<Table>`/`<Figure>` — invisible to
336
+ * keyed-float validation above. Warnings only.
337
+ */
338
+ readonly rawFloats: readonly RawFloatDiagnostic[];
234
339
  };
235
340
  readonly hashes: {
236
341
  /** `sha256` of every content source file, keyed by repo-relative path. */
@@ -259,4 +364,4 @@ declare function readManifestSync(): Manifest;
259
364
  */
260
365
  declare function loadManifest(courseConfig?: CourseConfig): Promise<Manifest>;
261
366
 
262
- export { MANIFEST_PATH, MANIFEST_SCHEMA_VERSION, type Manifest, type ManifestAbout, type ManifestCitation, type ManifestLesson, type ManifestLessonNotation, type ManifestNotationResolution, type PrereqEdge, compileManifest, loadManifest, readManifestSync, serializeManifest, writeManifest };
367
+ export { MANIFEST_PATH, MANIFEST_SCHEMA_VERSION, type Manifest, type ManifestAbout, type ManifestAssessmentItemContent, type ManifestCitation, type ManifestLesson, type ManifestLessonNotation, type ManifestNotationResolution, type PrereqEdge, compileManifest, loadManifest, readManifestSync, serializeManifest, writeManifest };
@@ -16,6 +16,8 @@ import {
16
16
  runConsistencyChecks
17
17
  } from "../reference/consistency.js";
18
18
  import { validateNotationAlignment } from "../reference/curriculum-alignment.js";
19
+ import { countDisplayEquations } from "../reference/equations.mjs";
20
+ import { countKeyedJsxFloats } from "../reference/keyed-jsx-floats.mjs";
19
21
  import {
20
22
  validateEquations
21
23
  } from "../reference/equations-validate.js";
@@ -26,16 +28,39 @@ import {
26
28
  validateFigures
27
29
  } from "../reference/figures-validate.js";
28
30
  import {
29
- gateContentMath
31
+ validateDiagrams
32
+ } from "../reference/diagrams-validate.js";
33
+ import {
34
+ validateKnowledgeChecks
35
+ } from "../reference/knowledge-checks-validate.js";
36
+ import {
37
+ validateRawFloats
38
+ } from "../reference/raw-floats-validate.js";
39
+ import {
40
+ gateContentMath,
41
+ pageScope
30
42
  } from "../reference/gate-math.js";
43
+ import {
44
+ gateProseNotation,
45
+ gateProseNotationInAssessments
46
+ } from "../reference/gate-prose-notation.js";
47
+ import { gateAbbreviations } from "../reference/gate-abbreviations.js";
48
+ import {
49
+ splitMixedContent
50
+ } from "../reference/mixed-content.js";
31
51
  import { loadLintIgnore } from "../reference/lint-ignore.js";
32
52
  import { buildNotationRegistry } from "../reference/registry.js";
33
53
  import { formulaGlyphScope } from "../reference/gloss.js";
34
54
  import { resolveReferenceId } from "../reference/references.js";
55
+ import {
56
+ coursePartsOf,
57
+ normalizeLessonExports
58
+ } from "./course-config.js";
35
59
  import {
36
60
  resolveKnowledgeVersion
37
61
  } from "./knowledge-version.js";
38
62
  import {
63
+ loadAbbreviationRegistryInput,
39
64
  loadCurriculumCatalog,
40
65
  loadNotationDefinitions,
41
66
  loadNotationRegistryInput,
@@ -48,7 +73,7 @@ import {
48
73
  isLessonSlug,
49
74
  lessonIdFromSlug
50
75
  } from "./lesson-derivation.js";
51
- const MANIFEST_SCHEMA_VERSION = 6;
76
+ const MANIFEST_SCHEMA_VERSION = 13;
52
77
  const REPOSITORY_ROOT = process.cwd();
53
78
  const CONTENT_ROOT = join(REPOSITORY_ROOT, "content");
54
79
  const MANIFEST_PATH = join(REPOSITORY_ROOT, "build", "manifest.json");
@@ -181,9 +206,10 @@ async function compileManifest(courseConfig) {
181
206
  "course.config.ts is missing a complete `about` block (required since explico 0.3.0): `about: { version, authors: [{ name }], disclaimer, materialName?, summary?, changelogHref? }`. See docs/authoring/migrating.md (\xA7 0.2.x \u2192 0.3.0) in the installed package."
182
207
  );
183
208
  }
184
- const [catalog, notationInput] = await Promise.all([
209
+ const [catalog, notationInput, abbreviationsInput] = await Promise.all([
185
210
  loadCurriculumCatalog(),
186
- loadNotationRegistryInput()
211
+ loadNotationRegistryInput(),
212
+ loadAbbreviationRegistryInput()
187
213
  ]);
188
214
  const registry = buildNotationRegistry(notationInput);
189
215
  const cardsByKey = new Map(
@@ -199,16 +225,53 @@ async function compileManifest(courseConfig) {
199
225
  const bundlesByLesson = new Map(
200
226
  registry.bundles.map((bundle) => [bundle.lessonId, bundle])
201
227
  );
228
+ const assessmentsInput = catalog.assessments;
202
229
  const curriculumIssues = validateCurriculum(catalog);
203
230
  const alignmentIssues = validateNotationAlignment(registry, catalog);
204
- const mathIssues = gateContentMath({
205
- notationInput,
206
- assessments: catalog.assessments,
207
- lessonAssessments: catalog.lessons.map((lesson) => ({
208
- lessonId: lesson.id,
209
- assessmentIds: lesson.assessments
210
- }))
211
- });
231
+ const mathIssues = [
232
+ ...gateContentMath({
233
+ notationInput,
234
+ assessments: assessmentsInput,
235
+ lessonAssessments: catalog.lessons.map((lesson) => ({
236
+ lessonId: lesson.id,
237
+ assessmentIds: lesson.assessments
238
+ }))
239
+ }),
240
+ ...gateProseNotation(notationInput.lessons),
241
+ ...gateProseNotationInAssessments(assessmentsInput),
242
+ ...gateAbbreviations({
243
+ notationInput,
244
+ abbreviations: abbreviationsInput,
245
+ courseAbbreviations: courseConfig.abbreviations
246
+ })
247
+ ];
248
+ const notationLessonById = new Map(
249
+ notationInput.lessons.map((lesson) => [lesson.lessonId, lesson])
250
+ );
251
+ const assessmentInputById = new Map(
252
+ assessmentsInput.map((assessment) => [assessment.id, assessment])
253
+ );
254
+ const assessmentContent = [];
255
+ for (const lesson of catalog.lessons) {
256
+ if (lesson.assessments.length === 0) continue;
257
+ const lessonInput = notationLessonById.get(lesson.id);
258
+ if (!lessonInput) continue;
259
+ const scope = pageScope(lessonInput, cardsByKey);
260
+ for (const assessmentId of lesson.assessments) {
261
+ const assessment = assessmentInputById.get(assessmentId);
262
+ if (!assessment?.items) continue;
263
+ for (const item of assessment.items) {
264
+ if (!item.id) continue;
265
+ assessmentContent.push({
266
+ lessonId: lesson.id,
267
+ assessmentId,
268
+ itemId: item.id,
269
+ promptSegments: splitMixedContent(item.prompt ?? "", scope),
270
+ explanationSegments: splitMixedContent(item.explanation ?? "", scope)
271
+ });
272
+ }
273
+ }
274
+ }
212
275
  const prerequisitesById = new Map(
213
276
  catalog.competencies.map((competency) => [
214
277
  competency.id,
@@ -231,44 +294,114 @@ async function compileManifest(courseConfig) {
231
294
  const sidebar = buildSidebar(
232
295
  catalog.tracks.map((track) => ({ id: track.id, lessons: track.lessons })),
233
296
  lessonSlugs,
234
- courseConfig.sections
297
+ coursePartsOf(courseConfig)
298
+ );
299
+ const bodyByLessonId = new Map(
300
+ notationInput.lessons.map((lesson) => [lesson.lessonId, lesson.body])
235
301
  );
236
- const chapterByLessonId = /* @__PURE__ */ new Map();
237
- let chapterCursor = 0;
302
+ const placementByLessonId = /* @__PURE__ */ new Map();
303
+ let partCursor = 0;
238
304
  for (const group of sidebar) {
239
- for (const item of group.items) {
240
- if (typeof item !== "string") continue;
241
- chapterByLessonId.set(lessonIdFromSlug(item), ++chapterCursor);
305
+ const lessonIds = group.items.filter((item) => typeof item === "string").map(lessonIdFromSlug);
306
+ if (lessonIds.length === 0) continue;
307
+ partCursor += 1;
308
+ const used = {
309
+ equations: 0,
310
+ tables: 0,
311
+ figures: 0,
312
+ diagrams: 0,
313
+ checks: 0
314
+ };
315
+ let chapterCursor = 0;
316
+ for (const lessonId of lessonIds) {
317
+ const body = bodyByLessonId.get(lessonId) ?? "";
318
+ placementByLessonId.set(lessonId, {
319
+ part: partCursor,
320
+ chapter: ++chapterCursor,
321
+ numbering: { ...used }
322
+ });
323
+ used.equations += countDisplayEquations(body);
324
+ used.tables += countKeyedJsxFloats(body, { tagName: "Table" });
325
+ used.figures += countKeyedJsxFloats(body, { tagName: "Figure" });
326
+ used.diagrams += countKeyedJsxFloats(body, { tagName: "Diagram" });
327
+ used.checks += lessonMetaById.get(lessonId)?.assessments.length ?? 0;
242
328
  }
243
329
  }
244
- const keyedFloatLessonInputs = notationInput.lessons.map((lesson) => ({
245
- lessonId: lesson.lessonId,
246
- slug: lesson.lessonId.replace(/\./g, "/"),
247
- file: lesson.source.file,
248
- body: lesson.body,
249
- chapter: chapterByLessonId.get(lesson.lessonId)
250
- }));
251
- const equationValidation = validateEquations(keyedFloatLessonInputs);
330
+ const floatInputs = (kind) => notationInput.lessons.map((lesson) => {
331
+ const placement = placementByLessonId.get(lesson.lessonId);
332
+ return {
333
+ lessonId: lesson.lessonId,
334
+ slug: lesson.lessonId.replace(/\./g, "/"),
335
+ file: lesson.source.file,
336
+ body: lesson.body,
337
+ ...placement ? {
338
+ numbering: {
339
+ prefix: placement.part,
340
+ offset: placement.numbering[kind]
341
+ }
342
+ } : {}
343
+ };
344
+ });
345
+ const equationValidation = validateEquations(floatInputs("equations"));
252
346
  const equationsBySlug = new Map(
253
347
  Object.entries(equationValidation.numbersBySlug).map(([slug, table]) => [
254
348
  slug,
255
349
  Object.entries(table).map(([key, number]) => ({ key, number }))
256
350
  ])
257
351
  );
258
- const tableValidation = validateTables(keyedFloatLessonInputs);
352
+ const tableValidation = validateTables(floatInputs("tables"));
259
353
  const tablesBySlug = new Map(
260
354
  Object.entries(tableValidation.numbersBySlug).map(([slug, table]) => [
261
355
  slug,
262
356
  Object.entries(table).map(([key, number]) => ({ key, number }))
263
357
  ])
264
358
  );
265
- const figureValidation = validateFigures(keyedFloatLessonInputs);
359
+ const figureValidation = validateFigures(floatInputs("figures"));
266
360
  const figuresBySlug = new Map(
267
361
  Object.entries(figureValidation.numbersBySlug).map(([slug, table]) => [
268
362
  slug,
269
363
  Object.entries(table).map(([key, number]) => ({ key, number }))
270
364
  ])
271
365
  );
366
+ const diagramValidation = validateDiagrams(floatInputs("diagrams"));
367
+ const diagramsBySlug = new Map(
368
+ Object.entries(diagramValidation.numbersBySlug).map(([slug, table]) => [
369
+ slug,
370
+ Object.entries(table).map(([key, number]) => ({ key, number }))
371
+ ])
372
+ );
373
+ const checkValidation = validateKnowledgeChecks(
374
+ notationInput.lessons.map((lesson) => {
375
+ const placement = placementByLessonId.get(lesson.lessonId);
376
+ return {
377
+ lessonId: lesson.lessonId,
378
+ slug: lesson.lessonId.replace(/\./g, "/"),
379
+ file: lesson.source.file,
380
+ body: lesson.body,
381
+ assessmentIds: lessonMetaById.get(lesson.lessonId)?.assessments ?? [],
382
+ ...placement ? {
383
+ numbering: {
384
+ prefix: placement.part,
385
+ offset: placement.numbering.checks
386
+ }
387
+ } : {}
388
+ };
389
+ })
390
+ );
391
+ const checksBySlug = new Map(
392
+ Object.entries(checkValidation.numbersBySlug).map(([slug, table]) => [
393
+ slug,
394
+ Object.entries(table).map(([key, number]) => ({ key, number }))
395
+ ])
396
+ );
397
+ const rawFloatDiagnostics = validateRawFloats(
398
+ notationInput.lessons.map((lesson) => ({
399
+ lessonId: lesson.lessonId,
400
+ slug: lesson.lessonId.replace(/\./g, "/"),
401
+ file: lesson.source.file,
402
+ body: lesson.body
403
+ }))
404
+ );
272
405
  const lessons = [];
273
406
  for (const notationLesson of notationInput.lessons) {
274
407
  const id = notationLesson.lessonId;
@@ -282,10 +415,18 @@ async function compileManifest(courseConfig) {
282
415
  lessons.push({
283
416
  id,
284
417
  slug: id.replace(/\./g, "/"),
285
- // 0 = the lesson is not placed in any configured section; a curriculum
418
+ // 0 = the lesson is not placed in any configured part; a curriculum
286
419
  // diagnostic covers that separately, and both numbering paths then fall
287
420
  // back to the legacy `##`-section scheme.
288
- chapter: chapterByLessonId.get(id) ?? 0,
421
+ part: placementByLessonId.get(id)?.part ?? 0,
422
+ chapter: placementByLessonId.get(id)?.chapter ?? 0,
423
+ numbering: placementByLessonId.get(id)?.numbering ?? {
424
+ equations: 0,
425
+ tables: 0,
426
+ figures: 0,
427
+ diagrams: 0,
428
+ checks: 0
429
+ },
289
430
  file: notationLesson.source.file,
290
431
  title: frontmatter.title,
291
432
  description: frontmatter.description,
@@ -294,11 +435,13 @@ async function compileManifest(courseConfig) {
294
435
  requires: [...meta.requires],
295
436
  assumptions: [...meta.assumptions],
296
437
  assessments: [...meta.assessments],
438
+ knowledgeChecks: checksBySlug.get(id.replace(/\./g, "/")) ?? [],
297
439
  sources: deriveSources(notationLesson.body),
298
440
  citations: lessonCitations(notationLesson.body),
299
441
  equations: equationsBySlug.get(id.replace(/\./g, "/")) ?? [],
300
442
  tables: tablesBySlug.get(id.replace(/\./g, "/")) ?? [],
301
443
  figures: figuresBySlug.get(id.replace(/\./g, "/")) ?? [],
444
+ diagrams: diagramsBySlug.get(id.replace(/\./g, "/")) ?? [],
302
445
  prereqEdges: sortEdges(
303
446
  teaches.flatMap(
304
447
  (to) => (prerequisitesById.get(to) ?? []).filter((from) => !teaches.includes(from)).map((from) => ({ from, to }))
@@ -324,7 +467,8 @@ async function compileManifest(courseConfig) {
324
467
  catalog,
325
468
  lintIgnore: loadLintIgnore(),
326
469
  conventions: courseConfig.conventions,
327
- ...courseConfig.operators === void 0 ? {} : { operators: courseConfig.operators }
470
+ ...courseConfig.operators === void 0 ? {} : { operators: courseConfig.operators },
471
+ ...courseConfig.romanLabels === void 0 ? {} : { romanLabels: courseConfig.romanLabels }
328
472
  });
329
473
  const rawNotation = [...loadNotationDefinitions()].sort(
330
474
  (a, b) => String(a.key).localeCompare(String(b.key))
@@ -332,6 +476,13 @@ async function compileManifest(courseConfig) {
332
476
  const competencies = catalog.competencies.map((entry) => ({ ...entry })).sort(byId);
333
477
  const sources = [...loadSourceRecords()].sort(byId);
334
478
  const tracks = catalog.tracks.map((entry) => ({ ...entry })).sort(byId);
479
+ const abbreviations = abbreviationsInput.map((entry) => ({
480
+ key: entry.key,
481
+ abbr: entry.abbr,
482
+ expansion: entry.expansion,
483
+ aliases: entry.aliases,
484
+ ...entry.domain === void 0 ? {} : { domain: entry.domain }
485
+ })).sort((a, b) => a.key.localeCompare(b.key));
335
486
  const hashes = contentHashes();
336
487
  const knowledgeChangelogPath = join(CONTENT_ROOT, "CHANGELOG.md");
337
488
  const knowledge = resolveKnowledgeVersion({
@@ -365,6 +516,7 @@ async function compileManifest(courseConfig) {
365
516
  };
366
517
  return {
367
518
  schemaVersion: MANIFEST_SCHEMA_VERSION,
519
+ lessonExports: normalizeLessonExports(courseConfig),
368
520
  about,
369
521
  notation: {
370
522
  definitions: registry.definitions,
@@ -376,6 +528,7 @@ async function compileManifest(courseConfig) {
376
528
  competencies,
377
529
  sources,
378
530
  tracks,
531
+ abbreviations,
379
532
  sidebar,
380
533
  lessons,
381
534
  prereqEdges,
@@ -391,6 +544,15 @@ async function compileManifest(courseConfig) {
391
544
  labels: figureValidation.labels,
392
545
  numbersBySlug: figureValidation.numbersBySlug
393
546
  },
547
+ diagrams: {
548
+ labels: diagramValidation.labels,
549
+ numbersBySlug: diagramValidation.numbersBySlug
550
+ },
551
+ checks: {
552
+ labels: checkValidation.labels,
553
+ numbersBySlug: checkValidation.numbersBySlug
554
+ },
555
+ assessmentContent,
394
556
  diagnostics: {
395
557
  curriculum: curriculumIssues,
396
558
  notation: registry.diagnostics,
@@ -400,7 +562,10 @@ async function compileManifest(courseConfig) {
400
562
  equations: equationValidation.diagnostics,
401
563
  tables: tableValidation.diagnostics,
402
564
  figures: figureValidation.diagnostics,
403
- knowledge: knowledge.diagnostics
565
+ diagrams: diagramValidation.diagnostics,
566
+ checks: checkValidation.diagnostics,
567
+ knowledge: knowledge.diagnostics,
568
+ rawFloats: rawFloatDiagnostics
404
569
  },
405
570
  hashes
406
571
  };
@@ -437,15 +602,15 @@ function readManifestSync() {
437
602
  return JSON.parse(readFileSync(MANIFEST_PATH, "utf8"));
438
603
  }
439
604
  async function loadManifest(courseConfig) {
440
- if (existsSync(MANIFEST_PATH)) return readManifestSync();
441
- if (!courseConfig) {
442
- throw new Error(
443
- `${relative(REPOSITORY_ROOT, MANIFEST_PATH)} not found. Run \`pnpm validate:content\` first.`
444
- );
605
+ if (courseConfig) {
606
+ const manifest = await compileManifest(courseConfig);
607
+ writeManifest(manifest);
608
+ return manifest;
445
609
  }
446
- const manifest = await compileManifest(courseConfig);
447
- writeManifest(manifest);
448
- return manifest;
610
+ if (existsSync(MANIFEST_PATH)) return readManifestSync();
611
+ throw new Error(
612
+ `${relative(REPOSITORY_ROOT, MANIFEST_PATH)} not found. Run \`pnpm validate:content\` first.`
613
+ );
449
614
  }
450
615
  export {
451
616
  MANIFEST_PATH,
@@ -9,7 +9,11 @@ import '../reference/equations-validate.js';
9
9
  import '../reference/tables-validate.js';
10
10
  import '../reference/keyed-float-validate.js';
11
11
  import '../reference/figures-validate.js';
12
+ import '../reference/diagrams-validate.js';
13
+ import '../reference/knowledge-checks-validate.js';
14
+ import '../reference/raw-floats-validate.js';
12
15
  import '../reference/gate-math.js';
16
+ import '../reference/mixed-content.js';
13
17
  import '../reference/gloss.js';
14
18
  import './knowledge-version.js';
15
19
  import './lesson-derivation.js';
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The abbreviations a course treats as common, given its `course.config.ts`
3
+ * `abbreviations` block: the shipped baseline, plus `allow`, minus `deny`.
4
+ *
5
+ * @param {{ allow?: readonly string[], deny?: readonly string[] } | undefined} abbreviations
6
+ * @returns {Set<string>}
7
+ */
8
+ export function effectiveAbbreviationAllowlist(abbreviations: {
9
+ allow?: readonly string[];
10
+ deny?: readonly string[];
11
+ } | undefined): Set<string>;
12
+ /** True when `token` needs no abbreviations-table entry for this course. */
13
+ export function isBaseAbbreviation(token: any, abbreviations: any): boolean;
14
+ /**
15
+ * The base abbreviation baseline: acronyms and dotted abbreviations every
16
+ * reader is assumed to know, so a lesson never has to introduce them with an
17
+ * abbreviations-table entry. The `\operatorname{…}` / `\mathrm{…}` twins are
18
+ * `base-operators.mjs` (rule N8) / `base-roman-labels.mjs` (rule N10); this is
19
+ * the plain-prose equivalent for rule N14.
20
+ *
21
+ * Kept intentionally small, same rationale as `BASE_ROMAN_LABELS`: a
22
+ * course-specific acronym (`OLS`, `SSR`, `MSE`) is exactly the thing a reader
23
+ * benefits from having spelled out, so it belongs in the course's own
24
+ * `content/abbreviations/*.md` table, not here. This baseline only holds
25
+ * abbreviations so common in ordinary English/academic writing that spelling
26
+ * them out on every course would be noise, not help. A course adds a real
27
+ * one via `abbreviations.allow` in `course.config.ts`, the same shape as
28
+ * `operators.allow` / `romanLabels.allow`.
29
+ */
30
+ export const BASE_ABBREVIATIONS: Readonly<Set<string>>;