explico 0.4.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 (143) hide show
  1. package/CHANGELOG.md +273 -1
  2. package/README.md +103 -8
  3. package/dist/compiler/astro.d.ts +9 -1
  4. package/dist/compiler/astro.js +182 -3
  5. package/dist/compiler/cli.d.ts +13 -0
  6. package/dist/compiler/cli.js +28 -4
  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 +58 -1
  10. package/dist/compiler/course-config.js +28 -1
  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/manifest.d.ts +90 -6
  14. package/dist/compiler/manifest.js +150 -17
  15. package/dist/compiler/resolution-report.d.ts +4 -0
  16. package/dist/reference/base-abbreviations.d.mts +30 -0
  17. package/dist/reference/base-abbreviations.mjs +60 -0
  18. package/dist/reference/base-operators.d.mts +15 -0
  19. package/dist/reference/base-operators.mjs +40 -0
  20. package/dist/reference/base-roman-labels.d.mts +35 -0
  21. package/dist/reference/base-roman-labels.mjs +43 -0
  22. package/dist/reference/consistency.d.ts +8 -2
  23. package/dist/reference/consistency.js +50 -3
  24. package/dist/reference/diagrams-validate.d.ts +21 -0
  25. package/dist/reference/diagrams-validate.js +13 -0
  26. package/dist/reference/equations.d.mts +10 -15
  27. package/dist/reference/equations.mjs +67 -14
  28. package/dist/reference/gate-abbreviations.d.ts +17 -0
  29. package/dist/reference/gate-abbreviations.js +64 -0
  30. package/dist/reference/gate-math.d.ts +28 -5
  31. package/dist/reference/gate-math.js +5 -2
  32. package/dist/reference/gate-prose-notation.d.ts +60 -0
  33. package/dist/reference/gate-prose-notation.js +125 -0
  34. package/dist/reference/keyed-float-validate.d.ts +5 -5
  35. package/dist/reference/keyed-jsx-floats.d.mts +7 -12
  36. package/dist/reference/keyed-jsx-floats.mjs +50 -25
  37. package/dist/reference/knowledge-checks-validate.d.ts +35 -0
  38. package/dist/reference/knowledge-checks-validate.js +78 -0
  39. package/dist/reference/knowledge-checks.d.mts +25 -0
  40. package/dist/reference/knowledge-checks.mjs +45 -0
  41. package/dist/reference/lab-math-scope.d.ts +9 -1
  42. package/dist/reference/lab-math-scope.js +32 -4
  43. package/dist/reference/math-glyphs.d.mts +30 -0
  44. package/dist/reference/math-glyphs.mjs +367 -0
  45. package/dist/reference/mdx-attributes.d.mts +13 -0
  46. package/dist/reference/mdx-attributes.mjs +38 -0
  47. package/dist/reference/mixed-content.d.ts +17 -0
  48. package/dist/reference/mixed-content.js +29 -0
  49. package/dist/reference/page-notation.d.ts +52 -0
  50. package/dist/reference/page-notation.js +171 -0
  51. package/dist/reference/raw-floats-validate.d.ts +18 -0
  52. package/dist/reference/raw-floats-validate.js +53 -0
  53. package/dist/reference/raw-floats.d.mts +18 -0
  54. package/dist/reference/raw-floats.mjs +129 -0
  55. package/dist/reference/rehype-heading-anchors.d.mts +1 -1
  56. package/dist/reference/rehype-heading-anchors.mjs +107 -1
  57. package/dist/reference/rehype-table-header-labels.d.mts +26 -0
  58. package/dist/reference/rehype-table-header-labels.mjs +152 -0
  59. package/dist/reference/remark-abbreviations.d.mts +7 -0
  60. package/dist/reference/remark-abbreviations.mjs +174 -0
  61. package/dist/reference/remark-memo.d.mts +16 -0
  62. package/dist/reference/remark-memo.mjs +339 -0
  63. package/dist/reference/remark-notation.d.mts +57 -9
  64. package/dist/reference/remark-notation.mjs +354 -42
  65. package/dist/reference/remark-short-note.d.mts +4 -0
  66. package/dist/reference/remark-short-note.mjs +237 -0
  67. package/dist/reference/types.d.ts +16 -1
  68. package/dist/reference/units-vocab.js +4 -0
  69. package/docs/VERSIONING.md +33 -4
  70. package/docs/authoring/README.md +8 -0
  71. package/docs/authoring/agents.md +17 -2
  72. package/docs/authoring/components.md +135 -0
  73. package/docs/authoring/migrating.md +92 -7
  74. package/docs/authoring/notation.md +560 -1
  75. package/docs/authoring/structure.md +120 -8
  76. package/docs/authoring/visualizations.md +36 -3
  77. package/package.json +5 -2
  78. package/src/compiler/astro.ts +208 -3
  79. package/src/compiler/cli.ts +29 -2
  80. package/src/compiler/collections.ts +45 -4
  81. package/src/compiler/course-config.ts +92 -0
  82. package/src/compiler/exports/standalone-html.ts +963 -0
  83. package/src/compiler/manifest.ts +268 -22
  84. package/src/components/HoverPanel.astro +45 -4
  85. package/src/components/abbreviation/AbbreviationGlossary.astro +70 -0
  86. package/src/components/abbreviation/AbbreviationLayer.astro +127 -0
  87. package/src/components/assessments/AssessmentRunner.tsx +52 -17
  88. package/src/components/assessments/AssessmentSet.astro +98 -14
  89. package/src/components/assessments/RenderedContent.tsx +55 -0
  90. package/src/components/assessments/assessment-randomization.ts +16 -0
  91. package/src/components/diagram/Diagram.astro +444 -83
  92. package/src/components/disclosure/hover-preview.ts +202 -0
  93. package/src/components/disclosure/persisted-disclosure.ts +66 -0
  94. package/src/components/examples/CompactExamples.astro +2 -0
  95. package/src/components/figure/Figure.astro +279 -10
  96. package/src/components/memo/Memo.astro +453 -0
  97. package/src/components/memo/ShortNote.astro +223 -0
  98. package/src/components/notation/NotationFormula.astro +18 -1
  99. package/src/components/notation/NotationLayer.astro +105 -8
  100. package/src/components/notation/render-notation-math.ts +39 -2
  101. package/src/components/notation/safe-math-to-react.ts +50 -0
  102. package/src/components/reference/FloatRefLayer.astro +233 -15
  103. package/src/components/starlight/CollapsibleSections.astro +461 -0
  104. package/src/components/starlight/DiagramEnhancer.astro +385 -0
  105. package/src/components/starlight/HeadingAnchorEnhancer.astro +5 -17
  106. package/src/components/starlight/KeyedFloatEnhancer.astro +81 -3
  107. package/src/components/starlight/LayoutHeader.astro +50 -0
  108. package/src/components/starlight/LayoutSocialIcons.astro +41 -2
  109. package/src/components/starlight/LessonExportActions.astro +436 -0
  110. package/src/components/starlight/LessonFooter.astro +27 -2
  111. package/src/components/starlight/section-parts.ts +31 -0
  112. package/src/components/table/Table.astro +293 -7
  113. package/src/components/table/TableOverflowExpander.astro +489 -0
  114. package/src/content-config.ts +20 -0
  115. package/src/reference/base-abbreviations.mjs +60 -0
  116. package/src/reference/base-operators.mjs +40 -0
  117. package/src/reference/base-roman-labels.mjs +43 -0
  118. package/src/reference/consistency.ts +73 -2
  119. package/src/reference/diagrams-validate.ts +40 -0
  120. package/src/reference/equations.mjs +67 -14
  121. package/src/reference/gate-abbreviations.ts +128 -0
  122. package/src/reference/gate-math.ts +27 -7
  123. package/src/reference/gate-prose-notation.ts +224 -0
  124. package/src/reference/keyed-float-validate.ts +5 -5
  125. package/src/reference/keyed-jsx-floats.mjs +50 -25
  126. package/src/reference/knowledge-checks-validate.ts +130 -0
  127. package/src/reference/knowledge-checks.mjs +45 -0
  128. package/src/reference/lab-math-scope.ts +45 -3
  129. package/src/reference/math-glyphs.mjs +367 -0
  130. package/src/reference/mdx-attributes.mjs +38 -0
  131. package/src/reference/mixed-content.ts +69 -0
  132. package/src/reference/page-notation.ts +252 -0
  133. package/src/reference/raw-floats-validate.ts +103 -0
  134. package/src/reference/raw-floats.mjs +129 -0
  135. package/src/reference/rehype-heading-anchors.mjs +107 -1
  136. package/src/reference/rehype-table-header-labels.mjs +152 -0
  137. package/src/reference/remark-abbreviations.mjs +174 -0
  138. package/src/reference/remark-memo.mjs +339 -0
  139. package/src/reference/remark-notation.mjs +354 -42
  140. package/src/reference/remark-short-note.mjs +237 -0
  141. package/src/reference/types.ts +16 -0
  142. package/src/reference/units-vocab.ts +4 -0
  143. package/src/styles/global.css +996 -50
@@ -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,7 +55,7 @@ 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`.
@@ -61,8 +65,33 @@ import '../reference/keyed-float-validate.js';
61
65
  * now the number before the dot), `lessons[].chapter` redefined as the
62
66
  * lesson's ordinal *within its part*, and `lessons[].numbering` — the
63
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).
64
93
  */
65
- declare const MANIFEST_SCHEMA_VERSION = 7;
94
+ declare const MANIFEST_SCHEMA_VERSION = 13;
66
95
  declare const MANIFEST_PATH: string;
67
96
  interface ManifestCitation {
68
97
  readonly id: string;
@@ -114,8 +143,9 @@ interface ManifestLesson {
114
143
  /**
115
144
  * The 1-based ordinal of the **part** this lesson sits in — its sidebar
116
145
  * group's position in `courseConfig.parts`. It is the number before the dot
117
- * in every equation, table, and figure number on the page: `(2.1)` is the
118
- * first equation of part 2. See `docs/authoring/structure.md`.
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`.
119
149
  */
120
150
  readonly part: number;
121
151
  /**
@@ -133,6 +163,8 @@ interface ManifestLesson {
133
163
  readonly equations: number;
134
164
  readonly tables: number;
135
165
  readonly figures: number;
166
+ readonly diagrams: number;
167
+ readonly checks: number;
136
168
  };
137
169
  readonly file: string;
138
170
  readonly title: string;
@@ -143,6 +175,11 @@ interface ManifestLesson {
143
175
  readonly requires: readonly string[];
144
176
  readonly assumptions: readonly string[];
145
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
+ }[];
146
183
  readonly sources: readonly string[];
147
184
  readonly citations: readonly ManifestCitation[];
148
185
  /** Competency prerequisite edges this lesson introduces. */
@@ -162,6 +199,11 @@ interface ManifestLesson {
162
199
  readonly key: string;
163
200
  readonly number: string;
164
201
  }[];
202
+ /** Keyed `<Diagram>` elements in appearance order. */
203
+ readonly diagrams: readonly {
204
+ readonly key: string;
205
+ readonly number: string;
206
+ }[];
165
207
  readonly notation: {
166
208
  readonly bindings: readonly {
167
209
  readonly key: string;
@@ -202,8 +244,18 @@ interface ManifestAbout {
202
244
  readonly disclaimer: string;
203
245
  readonly changelogHref?: string;
204
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
+ }
205
255
  interface Manifest {
206
256
  readonly schemaVersion: number;
257
+ /** Normalized build/UI settings for opt-in per-lesson exports. */
258
+ readonly lessonExports: NormalizedLessonExports;
207
259
  /** Reader-facing identity + versions (the `[?]` About panel). */
208
260
  readonly about: ManifestAbout;
209
261
  readonly notation: {
@@ -217,6 +269,8 @@ interface Manifest {
217
269
  readonly competencies: readonly Record<string, unknown>[];
218
270
  readonly sources: readonly Record<string, unknown>[];
219
271
  readonly tracks: readonly Record<string, unknown>[];
272
+ /** Abbreviations table (N14), for `remarkAbbreviations`. Ordered by key. */
273
+ readonly abbreviations: readonly Record<string, unknown>[];
220
274
  readonly sidebar: readonly SidebarGroup[];
221
275
  readonly lessons: readonly ManifestLesson[];
222
276
  /** Full competency prerequisite graph. */
@@ -237,6 +291,26 @@ interface Manifest {
237
291
  readonly labels: readonly FigureLabelRecord[];
238
292
  readonly numbersBySlug: Record<string, Record<string, string>>;
239
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[];
240
314
  readonly diagnostics: {
241
315
  readonly curriculum: readonly CurriculumIssue[];
242
316
  readonly notation: readonly NotationDiagnostic[];
@@ -252,6 +326,16 @@ interface Manifest {
252
326
  readonly tables: readonly TableDiagnostic[];
253
327
  /** `<Figure>` identity checks (F-tables-figures) — same codes, `fig-` prefixed. */
254
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[];
255
339
  };
256
340
  readonly hashes: {
257
341
  /** `sha256` of every content source file, keyed by repo-relative path. */
@@ -280,4 +364,4 @@ declare function readManifestSync(): Manifest;
280
364
  */
281
365
  declare function loadManifest(courseConfig?: CourseConfig): Promise<Manifest>;
282
366
 
283
- 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 };
@@ -28,17 +28,39 @@ import {
28
28
  validateFigures
29
29
  } from "../reference/figures-validate.js";
30
30
  import {
31
- 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
32
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";
33
51
  import { loadLintIgnore } from "../reference/lint-ignore.js";
34
52
  import { buildNotationRegistry } from "../reference/registry.js";
35
53
  import { formulaGlyphScope } from "../reference/gloss.js";
36
54
  import { resolveReferenceId } from "../reference/references.js";
37
- import { coursePartsOf } from "./course-config.js";
55
+ import {
56
+ coursePartsOf,
57
+ normalizeLessonExports
58
+ } from "./course-config.js";
38
59
  import {
39
60
  resolveKnowledgeVersion
40
61
  } from "./knowledge-version.js";
41
62
  import {
63
+ loadAbbreviationRegistryInput,
42
64
  loadCurriculumCatalog,
43
65
  loadNotationDefinitions,
44
66
  loadNotationRegistryInput,
@@ -51,7 +73,7 @@ import {
51
73
  isLessonSlug,
52
74
  lessonIdFromSlug
53
75
  } from "./lesson-derivation.js";
54
- const MANIFEST_SCHEMA_VERSION = 7;
76
+ const MANIFEST_SCHEMA_VERSION = 13;
55
77
  const REPOSITORY_ROOT = process.cwd();
56
78
  const CONTENT_ROOT = join(REPOSITORY_ROOT, "content");
57
79
  const MANIFEST_PATH = join(REPOSITORY_ROOT, "build", "manifest.json");
@@ -184,9 +206,10 @@ async function compileManifest(courseConfig) {
184
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."
185
207
  );
186
208
  }
187
- const [catalog, notationInput] = await Promise.all([
209
+ const [catalog, notationInput, abbreviationsInput] = await Promise.all([
188
210
  loadCurriculumCatalog(),
189
- loadNotationRegistryInput()
211
+ loadNotationRegistryInput(),
212
+ loadAbbreviationRegistryInput()
190
213
  ]);
191
214
  const registry = buildNotationRegistry(notationInput);
192
215
  const cardsByKey = new Map(
@@ -202,16 +225,53 @@ async function compileManifest(courseConfig) {
202
225
  const bundlesByLesson = new Map(
203
226
  registry.bundles.map((bundle) => [bundle.lessonId, bundle])
204
227
  );
228
+ const assessmentsInput = catalog.assessments;
205
229
  const curriculumIssues = validateCurriculum(catalog);
206
230
  const alignmentIssues = validateNotationAlignment(registry, catalog);
207
- const mathIssues = gateContentMath({
208
- notationInput,
209
- assessments: catalog.assessments,
210
- lessonAssessments: catalog.lessons.map((lesson) => ({
211
- lessonId: lesson.id,
212
- assessmentIds: lesson.assessments
213
- }))
214
- });
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
+ }
215
275
  const prerequisitesById = new Map(
216
276
  catalog.competencies.map((competency) => [
217
277
  competency.id,
@@ -245,7 +305,13 @@ async function compileManifest(courseConfig) {
245
305
  const lessonIds = group.items.filter((item) => typeof item === "string").map(lessonIdFromSlug);
246
306
  if (lessonIds.length === 0) continue;
247
307
  partCursor += 1;
248
- const used = { equations: 0, tables: 0, figures: 0 };
308
+ const used = {
309
+ equations: 0,
310
+ tables: 0,
311
+ figures: 0,
312
+ diagrams: 0,
313
+ checks: 0
314
+ };
249
315
  let chapterCursor = 0;
250
316
  for (const lessonId of lessonIds) {
251
317
  const body = bodyByLessonId.get(lessonId) ?? "";
@@ -257,6 +323,8 @@ async function compileManifest(courseConfig) {
257
323
  used.equations += countDisplayEquations(body);
258
324
  used.tables += countKeyedJsxFloats(body, { tagName: "Table" });
259
325
  used.figures += countKeyedJsxFloats(body, { tagName: "Figure" });
326
+ used.diagrams += countKeyedJsxFloats(body, { tagName: "Diagram" });
327
+ used.checks += lessonMetaById.get(lessonId)?.assessments.length ?? 0;
260
328
  }
261
329
  }
262
330
  const floatInputs = (kind) => notationInput.lessons.map((lesson) => {
@@ -295,6 +363,45 @@ async function compileManifest(courseConfig) {
295
363
  Object.entries(table).map(([key, number]) => ({ key, number }))
296
364
  ])
297
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
+ );
298
405
  const lessons = [];
299
406
  for (const notationLesson of notationInput.lessons) {
300
407
  const id = notationLesson.lessonId;
@@ -316,7 +423,9 @@ async function compileManifest(courseConfig) {
316
423
  numbering: placementByLessonId.get(id)?.numbering ?? {
317
424
  equations: 0,
318
425
  tables: 0,
319
- figures: 0
426
+ figures: 0,
427
+ diagrams: 0,
428
+ checks: 0
320
429
  },
321
430
  file: notationLesson.source.file,
322
431
  title: frontmatter.title,
@@ -326,11 +435,13 @@ async function compileManifest(courseConfig) {
326
435
  requires: [...meta.requires],
327
436
  assumptions: [...meta.assumptions],
328
437
  assessments: [...meta.assessments],
438
+ knowledgeChecks: checksBySlug.get(id.replace(/\./g, "/")) ?? [],
329
439
  sources: deriveSources(notationLesson.body),
330
440
  citations: lessonCitations(notationLesson.body),
331
441
  equations: equationsBySlug.get(id.replace(/\./g, "/")) ?? [],
332
442
  tables: tablesBySlug.get(id.replace(/\./g, "/")) ?? [],
333
443
  figures: figuresBySlug.get(id.replace(/\./g, "/")) ?? [],
444
+ diagrams: diagramsBySlug.get(id.replace(/\./g, "/")) ?? [],
334
445
  prereqEdges: sortEdges(
335
446
  teaches.flatMap(
336
447
  (to) => (prerequisitesById.get(to) ?? []).filter((from) => !teaches.includes(from)).map((from) => ({ from, to }))
@@ -356,7 +467,8 @@ async function compileManifest(courseConfig) {
356
467
  catalog,
357
468
  lintIgnore: loadLintIgnore(),
358
469
  conventions: courseConfig.conventions,
359
- ...courseConfig.operators === void 0 ? {} : { operators: courseConfig.operators }
470
+ ...courseConfig.operators === void 0 ? {} : { operators: courseConfig.operators },
471
+ ...courseConfig.romanLabels === void 0 ? {} : { romanLabels: courseConfig.romanLabels }
360
472
  });
361
473
  const rawNotation = [...loadNotationDefinitions()].sort(
362
474
  (a, b) => String(a.key).localeCompare(String(b.key))
@@ -364,6 +476,13 @@ async function compileManifest(courseConfig) {
364
476
  const competencies = catalog.competencies.map((entry) => ({ ...entry })).sort(byId);
365
477
  const sources = [...loadSourceRecords()].sort(byId);
366
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));
367
486
  const hashes = contentHashes();
368
487
  const knowledgeChangelogPath = join(CONTENT_ROOT, "CHANGELOG.md");
369
488
  const knowledge = resolveKnowledgeVersion({
@@ -397,6 +516,7 @@ async function compileManifest(courseConfig) {
397
516
  };
398
517
  return {
399
518
  schemaVersion: MANIFEST_SCHEMA_VERSION,
519
+ lessonExports: normalizeLessonExports(courseConfig),
400
520
  about,
401
521
  notation: {
402
522
  definitions: registry.definitions,
@@ -408,6 +528,7 @@ async function compileManifest(courseConfig) {
408
528
  competencies,
409
529
  sources,
410
530
  tracks,
531
+ abbreviations,
411
532
  sidebar,
412
533
  lessons,
413
534
  prereqEdges,
@@ -423,6 +544,15 @@ async function compileManifest(courseConfig) {
423
544
  labels: figureValidation.labels,
424
545
  numbersBySlug: figureValidation.numbersBySlug
425
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,
426
556
  diagnostics: {
427
557
  curriculum: curriculumIssues,
428
558
  notation: registry.diagnostics,
@@ -432,7 +562,10 @@ async function compileManifest(courseConfig) {
432
562
  equations: equationValidation.diagnostics,
433
563
  tables: tableValidation.diagnostics,
434
564
  figures: figureValidation.diagnostics,
435
- knowledge: knowledge.diagnostics
565
+ diagrams: diagramValidation.diagnostics,
566
+ checks: checkValidation.diagnostics,
567
+ knowledge: knowledge.diagnostics,
568
+ rawFloats: rawFloatDiagnostics
436
569
  },
437
570
  hashes
438
571
  };
@@ -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>>;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The base abbreviation baseline: acronyms and dotted abbreviations every
3
+ * reader is assumed to know, so a lesson never has to introduce them with an
4
+ * abbreviations-table entry. The `\operatorname{…}` / `\mathrm{…}` twins are
5
+ * `base-operators.mjs` (rule N8) / `base-roman-labels.mjs` (rule N10); this is
6
+ * the plain-prose equivalent for rule N14.
7
+ *
8
+ * Kept intentionally small, same rationale as `BASE_ROMAN_LABELS`: a
9
+ * course-specific acronym (`OLS`, `SSR`, `MSE`) is exactly the thing a reader
10
+ * benefits from having spelled out, so it belongs in the course's own
11
+ * `content/abbreviations/*.md` table, not here. This baseline only holds
12
+ * abbreviations so common in ordinary English/academic writing that spelling
13
+ * them out on every course would be noise, not help. A course adds a real
14
+ * one via `abbreviations.allow` in `course.config.ts`, the same shape as
15
+ * `operators.allow` / `romanLabels.allow`.
16
+ */
17
+ export const BASE_ABBREVIATIONS = Object.freeze(
18
+ new Set([
19
+ // dotted Latin/English abbreviations
20
+ 'e.g.',
21
+ 'i.e.',
22
+ 'etc.',
23
+ 'cf.',
24
+ 'vs.',
25
+ 'a.m.',
26
+ 'p.m.',
27
+ 'a.k.a.',
28
+ 'et al.',
29
+ // generic acronyms with no domain-specific meaning in this engine
30
+ 'AI',
31
+ 'ID',
32
+ 'OK',
33
+ 'URL',
34
+ 'FAQ',
35
+ 'US',
36
+ 'UK',
37
+ 'EU',
38
+ ]),
39
+ );
40
+
41
+ /**
42
+ * The abbreviations a course treats as common, given its `course.config.ts`
43
+ * `abbreviations` block: the shipped baseline, plus `allow`, minus `deny`.
44
+ *
45
+ * @param {{ allow?: readonly string[], deny?: readonly string[] } | undefined} abbreviations
46
+ * @returns {Set<string>}
47
+ */
48
+ export function effectiveAbbreviationAllowlist(abbreviations) {
49
+ const allow = abbreviations?.allow ?? [];
50
+ const deny = new Set(abbreviations?.deny ?? []);
51
+ const effective = new Set(BASE_ABBREVIATIONS);
52
+ for (const name of allow) effective.add(name);
53
+ for (const name of deny) effective.delete(name);
54
+ return effective;
55
+ }
56
+
57
+ /** True when `token` needs no abbreviations-table entry for this course. */
58
+ export function isBaseAbbreviation(token, abbreviations) {
59
+ return effectiveAbbreviationAllowlist(abbreviations).has(token);
60
+ }
@@ -36,3 +36,18 @@ export function isBaseOperator(name: any, operators: any): boolean;
36
36
  * Matching is case-sensitive.
37
37
  */
38
38
  export const BASE_OPERATORS: Readonly<Set<string>>;
39
+ /**
40
+ * The subset of {@link BASE_OPERATORS} that must never appear with a bare
41
+ * (unparenthesized) argument: every trigonometric, inverse-trigonometric, and
42
+ * hyperbolic function, plus the exponential and logarithms (N13). `\sin x`
43
+ * leaves how far the argument extends ambiguous the moment anything follows
44
+ * it (`\sin x + 1` — is the `+1` inside or outside the sine?); `\sin(x)`
45
+ * never does, even for a single-symbol argument. Kept separate from
46
+ * `BASE_OPERATORS`: this is a formatting rule layered on top of the "does it
47
+ * need a notation card" question that set answers, so an operator can be
48
+ * universal (N8) and still owe a parenthesized argument. Enforced by
49
+ * `resolveMathGlyphs` in `math-glyphs.mjs`, which every rendered-math context
50
+ * (lesson body, notation `formula`, assessments, `Diagram` nodes, lab math)
51
+ * already calls.
52
+ */
53
+ export const PARENTHESIZED_FUNCTIONS: Readonly<Set<string>>;
@@ -81,6 +81,46 @@ export const BASE_OPERATORS = Object.freeze(
81
81
  ]),
82
82
  );
83
83
 
84
+ /**
85
+ * The subset of {@link BASE_OPERATORS} that must never appear with a bare
86
+ * (unparenthesized) argument: every trigonometric, inverse-trigonometric, and
87
+ * hyperbolic function, plus the exponential and logarithms (N13). `\sin x`
88
+ * leaves how far the argument extends ambiguous the moment anything follows
89
+ * it (`\sin x + 1` — is the `+1` inside or outside the sine?); `\sin(x)`
90
+ * never does, even for a single-symbol argument. Kept separate from
91
+ * `BASE_OPERATORS`: this is a formatting rule layered on top of the "does it
92
+ * need a notation card" question that set answers, so an operator can be
93
+ * universal (N8) and still owe a parenthesized argument. Enforced by
94
+ * `resolveMathGlyphs` in `math-glyphs.mjs`, which every rendered-math context
95
+ * (lesson body, notation `formula`, assessments, `Diagram` nodes, lab math)
96
+ * already calls.
97
+ */
98
+ export const PARENTHESIZED_FUNCTIONS = Object.freeze(
99
+ new Set([
100
+ // exponential / logarithms
101
+ 'exp',
102
+ 'log',
103
+ 'ln',
104
+ 'lg',
105
+ // trigonometric
106
+ 'sin',
107
+ 'cos',
108
+ 'tan',
109
+ 'cot',
110
+ 'sec',
111
+ 'csc',
112
+ // inverse trigonometric
113
+ 'arcsin',
114
+ 'arccos',
115
+ 'arctan',
116
+ // hyperbolic
117
+ 'sinh',
118
+ 'cosh',
119
+ 'tanh',
120
+ 'coth',
121
+ ]),
122
+ );
123
+
84
124
  /**
85
125
  * The operators a course treats as common, given its `course.config.ts`
86
126
  * `operators` block: the shipped baseline, plus `allow`, minus `deny`.
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The roman labels a course treats as common, given its `course.config.ts`
3
+ * `romanLabels` block: the shipped baseline, plus `allow`, minus `deny`.
4
+ *
5
+ * @param {{ allow?: readonly string[], deny?: readonly string[] } | undefined} romanLabels
6
+ * @returns {Set<string>}
7
+ */
8
+ export function effectiveRomanLabelAllowlist(romanLabels: {
9
+ allow?: readonly string[];
10
+ deny?: readonly string[];
11
+ } | undefined): Set<string>;
12
+ /** True when `name` needs no notation entry for this course. */
13
+ export function isBaseRomanLabel(name: any, romanLabels: any): boolean;
14
+ /**
15
+ * The base roman-label baseline: `\mathrm{…}` multi-letter labels every
16
+ * reader is assumed to know, so a lesson never has to introduce them with a
17
+ * notation entry. The `\operatorname{…}` counterpart is `base-operators.mjs`
18
+ * (rule N8); this is its `\mathrm{…}` twin (rule N10).
19
+ *
20
+ * Rationale: the completeness gate has no opinion about `\mathrm{…}` bodies —
21
+ * they are a typographic label to KaTeX, not a bindable symbol, and N3
22
+ * requires the format without asking whether the label is ever explained.
23
+ * The `roman-label-explained` consistency check fills that gap: a label a
24
+ * lesson uses is either on this list (or the course's additions to it) or it
25
+ * carries a notation entry the reader can hover.
26
+ *
27
+ * Kept intentionally small — unlike `sin`/`exp`, an upright label is almost
28
+ * always a course-specific named quantity (`KE`, `PV`, `Var`-as-a-label) that
29
+ * genuinely benefits from a hover card, so the shipped baseline holds only
30
+ * the qualifiers that function like an operator's extremum subscript
31
+ * (`x_{\mathrm{opt}}`) rather than naming a quantity of their own. A course
32
+ * adds a real one via `romanLabels.allow` in `course.config.ts`, the same
33
+ * shape as `operators.allow`.
34
+ */
35
+ export const BASE_ROMAN_LABELS: Readonly<Set<string>>;