explico 0.4.0 → 0.6.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 (150) hide show
  1. package/CHANGELOG.md +382 -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 +88 -1
  10. package/dist/compiler/course-config.js +35 -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 +98 -6
  14. package/dist/compiler/manifest.js +154 -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-heading-length.d.ts +12 -0
  31. package/dist/reference/gate-heading-length.js +79 -0
  32. package/dist/reference/gate-math.d.ts +32 -5
  33. package/dist/reference/gate-math.js +6 -2
  34. package/dist/reference/gate-prose-notation.d.ts +60 -0
  35. package/dist/reference/gate-prose-notation.js +125 -0
  36. package/dist/reference/keyed-float-validate.d.ts +5 -5
  37. package/dist/reference/keyed-jsx-floats.d.mts +7 -12
  38. package/dist/reference/keyed-jsx-floats.mjs +50 -25
  39. package/dist/reference/knowledge-checks-validate.d.ts +35 -0
  40. package/dist/reference/knowledge-checks-validate.js +78 -0
  41. package/dist/reference/knowledge-checks.d.mts +25 -0
  42. package/dist/reference/knowledge-checks.mjs +45 -0
  43. package/dist/reference/lab-math-scope.d.ts +9 -1
  44. package/dist/reference/lab-math-scope.js +32 -4
  45. package/dist/reference/math-glyphs.d.mts +42 -0
  46. package/dist/reference/math-glyphs.mjs +548 -40
  47. package/dist/reference/mdx-attributes.d.mts +13 -0
  48. package/dist/reference/mdx-attributes.mjs +38 -0
  49. package/dist/reference/mixed-content.d.ts +17 -0
  50. package/dist/reference/mixed-content.js +29 -0
  51. package/dist/reference/page-notation.d.ts +52 -0
  52. package/dist/reference/page-notation.js +171 -0
  53. package/dist/reference/raw-floats-validate.d.ts +18 -0
  54. package/dist/reference/raw-floats-validate.js +53 -0
  55. package/dist/reference/raw-floats.d.mts +18 -0
  56. package/dist/reference/raw-floats.mjs +129 -0
  57. package/dist/reference/rehype-heading-anchors.d.mts +1 -1
  58. package/dist/reference/rehype-heading-anchors.mjs +107 -1
  59. package/dist/reference/rehype-table-header-labels.d.mts +26 -0
  60. package/dist/reference/rehype-table-header-labels.mjs +152 -0
  61. package/dist/reference/remark-abbreviations.d.mts +7 -0
  62. package/dist/reference/remark-abbreviations.mjs +174 -0
  63. package/dist/reference/remark-citation.d.mts +5 -1
  64. package/dist/reference/remark-citation.mjs +0 -0
  65. package/dist/reference/remark-memo.d.mts +16 -0
  66. package/dist/reference/remark-memo.mjs +339 -0
  67. package/dist/reference/remark-notation.d.mts +57 -9
  68. package/dist/reference/remark-notation.mjs +354 -42
  69. package/dist/reference/remark-short-note.d.mts +4 -0
  70. package/dist/reference/remark-short-note.mjs +237 -0
  71. package/dist/reference/types.d.ts +16 -1
  72. package/dist/reference/units-vocab.js +4 -0
  73. package/docs/VERSIONING.md +53 -4
  74. package/docs/authoring/README.md +10 -0
  75. package/docs/authoring/agents.md +17 -2
  76. package/docs/authoring/components.md +138 -0
  77. package/docs/authoring/migrating.md +275 -7
  78. package/docs/authoring/notation.md +615 -1
  79. package/docs/authoring/structure-rules.md +69 -0
  80. package/docs/authoring/structure.md +120 -8
  81. package/docs/authoring/visualizations.md +36 -3
  82. package/package.json +5 -2
  83. package/src/compiler/astro.ts +208 -3
  84. package/src/compiler/cli.ts +29 -2
  85. package/src/compiler/collections.ts +45 -4
  86. package/src/compiler/course-config.ts +132 -0
  87. package/src/compiler/exports/standalone-html.ts +963 -0
  88. package/src/compiler/manifest.ts +281 -22
  89. package/src/components/HoverPanel.astro +45 -4
  90. package/src/components/abbreviation/AbbreviationGlossary.astro +70 -0
  91. package/src/components/abbreviation/AbbreviationLayer.astro +127 -0
  92. package/src/components/assessments/AssessmentRunner.tsx +52 -17
  93. package/src/components/assessments/AssessmentSet.astro +98 -14
  94. package/src/components/assessments/RenderedContent.tsx +55 -0
  95. package/src/components/assessments/assessment-randomization.ts +16 -0
  96. package/src/components/diagram/Diagram.astro +409 -103
  97. package/src/components/disclosure/hover-preview.ts +202 -0
  98. package/src/components/disclosure/persisted-disclosure.ts +66 -0
  99. package/src/components/examples/CompactExamples.astro +2 -0
  100. package/src/components/figure/Figure.astro +279 -10
  101. package/src/components/memo/Memo.astro +453 -0
  102. package/src/components/memo/ShortNote.astro +223 -0
  103. package/src/components/notation/NotationFormula.astro +18 -1
  104. package/src/components/notation/NotationLayer.astro +260 -83
  105. package/src/components/notation/render-notation-math.ts +39 -2
  106. package/src/components/notation/safe-math-to-react.ts +50 -0
  107. package/src/components/reference/FloatRefLayer.astro +233 -15
  108. package/src/components/starlight/CollapsibleSections.astro +461 -0
  109. package/src/components/starlight/DiagramEnhancer.astro +385 -0
  110. package/src/components/starlight/HeadingAnchorEnhancer.astro +5 -17
  111. package/src/components/starlight/KeyedFloatEnhancer.astro +81 -3
  112. package/src/components/starlight/LayoutHeader.astro +50 -0
  113. package/src/components/starlight/LayoutSocialIcons.astro +41 -2
  114. package/src/components/starlight/LessonExportActions.astro +436 -0
  115. package/src/components/starlight/LessonFooter.astro +30 -3
  116. package/src/components/starlight/section-parts.ts +31 -0
  117. package/src/components/table/Table.astro +293 -7
  118. package/src/components/table/TableOverflowExpander.astro +489 -0
  119. package/src/content-config.ts +36 -1
  120. package/src/reference/base-abbreviations.mjs +60 -0
  121. package/src/reference/base-operators.mjs +40 -0
  122. package/src/reference/base-roman-labels.mjs +43 -0
  123. package/src/reference/consistency.ts +73 -2
  124. package/src/reference/diagrams-validate.ts +40 -0
  125. package/src/reference/equations.mjs +67 -14
  126. package/src/reference/gate-abbreviations.ts +128 -0
  127. package/src/reference/gate-heading-length.ts +131 -0
  128. package/src/reference/gate-math.ts +32 -7
  129. package/src/reference/gate-prose-notation.ts +224 -0
  130. package/src/reference/keyed-float-validate.ts +5 -5
  131. package/src/reference/keyed-jsx-floats.mjs +50 -25
  132. package/src/reference/knowledge-checks-validate.ts +130 -0
  133. package/src/reference/knowledge-checks.mjs +45 -0
  134. package/src/reference/lab-math-scope.ts +45 -3
  135. package/src/reference/math-glyphs.mjs +548 -40
  136. package/src/reference/mdx-attributes.mjs +38 -0
  137. package/src/reference/mixed-content.ts +69 -0
  138. package/src/reference/page-notation.ts +252 -0
  139. package/src/reference/raw-floats-validate.ts +103 -0
  140. package/src/reference/raw-floats.mjs +129 -0
  141. package/src/reference/rehype-heading-anchors.mjs +107 -1
  142. package/src/reference/rehype-table-header-labels.mjs +152 -0
  143. package/src/reference/remark-abbreviations.mjs +174 -0
  144. package/src/reference/remark-citation.mjs +0 -0
  145. package/src/reference/remark-memo.mjs +339 -0
  146. package/src/reference/remark-notation.mjs +354 -42
  147. package/src/reference/remark-short-note.mjs +237 -0
  148. package/src/reference/types.ts +16 -0
  149. package/src/reference/units-vocab.ts +4 -0
  150. 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, NormalizedDisclosureDefaults, 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,39 @@ 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).
93
+ * v14 (disclosure defaults, additive): `disclosureDefaults` — the resolved
94
+ * initial open/closed state for the "Page notation" panel and the generated
95
+ * "References" section, from `CourseConfig.disclosureDefaults`. Both are
96
+ * engine-authored sections, not written by a lesson, so they get their own
97
+ * default independent of a course's `##` heading default; closed unless the
98
+ * course sets one.
64
99
  */
65
- declare const MANIFEST_SCHEMA_VERSION = 7;
100
+ declare const MANIFEST_SCHEMA_VERSION = 14;
66
101
  declare const MANIFEST_PATH: string;
67
102
  interface ManifestCitation {
68
103
  readonly id: string;
@@ -114,8 +149,9 @@ interface ManifestLesson {
114
149
  /**
115
150
  * The 1-based ordinal of the **part** this lesson sits in — its sidebar
116
151
  * 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`.
152
+ * in every equation, table, figure, diagram, and knowledge-check number on
153
+ * the page: `(2.1)` is the first equation of part 2. See
154
+ * `docs/authoring/structure.md`.
119
155
  */
120
156
  readonly part: number;
121
157
  /**
@@ -133,6 +169,8 @@ interface ManifestLesson {
133
169
  readonly equations: number;
134
170
  readonly tables: number;
135
171
  readonly figures: number;
172
+ readonly diagrams: number;
173
+ readonly checks: number;
136
174
  };
137
175
  readonly file: string;
138
176
  readonly title: string;
@@ -143,6 +181,11 @@ interface ManifestLesson {
143
181
  readonly requires: readonly string[];
144
182
  readonly assumptions: readonly string[];
145
183
  readonly assessments: readonly string[];
184
+ /** Numbered knowledge checks from `assessments`, in `.checks.yml` order. */
185
+ readonly knowledgeChecks: readonly {
186
+ readonly key: string;
187
+ readonly number: string;
188
+ }[];
146
189
  readonly sources: readonly string[];
147
190
  readonly citations: readonly ManifestCitation[];
148
191
  /** Competency prerequisite edges this lesson introduces. */
@@ -162,6 +205,11 @@ interface ManifestLesson {
162
205
  readonly key: string;
163
206
  readonly number: string;
164
207
  }[];
208
+ /** Keyed `<Diagram>` elements in appearance order. */
209
+ readonly diagrams: readonly {
210
+ readonly key: string;
211
+ readonly number: string;
212
+ }[];
165
213
  readonly notation: {
166
214
  readonly bindings: readonly {
167
215
  readonly key: string;
@@ -202,8 +250,20 @@ interface ManifestAbout {
202
250
  readonly disclaimer: string;
203
251
  readonly changelogHref?: string;
204
252
  }
253
+ /** One assessment item's pre-rendered `prompt` / `explanation` (v8). */
254
+ interface ManifestAssessmentItemContent {
255
+ readonly lessonId: string;
256
+ readonly assessmentId: string;
257
+ readonly itemId: string;
258
+ readonly promptSegments: readonly ContentSegment[];
259
+ readonly explanationSegments: readonly ContentSegment[];
260
+ }
205
261
  interface Manifest {
206
262
  readonly schemaVersion: number;
263
+ /** Normalized build/UI settings for opt-in per-lesson exports. */
264
+ readonly lessonExports: NormalizedLessonExports;
265
+ /** Initial open/closed state for "Page notation" and "References". */
266
+ readonly disclosureDefaults: NormalizedDisclosureDefaults;
207
267
  /** Reader-facing identity + versions (the `[?]` About panel). */
208
268
  readonly about: ManifestAbout;
209
269
  readonly notation: {
@@ -217,6 +277,8 @@ interface Manifest {
217
277
  readonly competencies: readonly Record<string, unknown>[];
218
278
  readonly sources: readonly Record<string, unknown>[];
219
279
  readonly tracks: readonly Record<string, unknown>[];
280
+ /** Abbreviations table (N14), for `remarkAbbreviations`. Ordered by key. */
281
+ readonly abbreviations: readonly Record<string, unknown>[];
220
282
  readonly sidebar: readonly SidebarGroup[];
221
283
  readonly lessons: readonly ManifestLesson[];
222
284
  /** Full competency prerequisite graph. */
@@ -237,6 +299,26 @@ interface Manifest {
237
299
  readonly labels: readonly FigureLabelRecord[];
238
300
  readonly numbersBySlug: Record<string, Record<string, string>>;
239
301
  };
302
+ /** Keyed-float identity for `<Diagram>` — independent of figure numbering. */
303
+ readonly diagrams: {
304
+ readonly labels: readonly DiagramLabelRecord[];
305
+ readonly numbersBySlug: Record<string, Record<string, string>>;
306
+ };
307
+ /** Knowledge-check identity, derived from each lesson's `.checks.yml`. */
308
+ readonly checks: {
309
+ readonly labels: readonly KnowledgeCheckLabelRecord[];
310
+ readonly numbersBySlug: Record<string, Record<string, string>>;
311
+ };
312
+ /**
313
+ * Every assessment item's `prompt` / `explanation`, pre-split into
314
+ * text/math segments (v8). `AssessmentSet.astro` filters this by
315
+ * `lessonId` + `assessmentId` and renders each `math` segment's `latex`
316
+ * through `renderNotationMath` before handing the result to
317
+ * `AssessmentRunner`. An assessment embedded in more than one lesson (via
318
+ * more than one `checks.yml`) appears once per lesson here, since each
319
+ * lesson's scope can resolve its glyphs differently.
320
+ */
321
+ readonly assessmentContent: readonly ManifestAssessmentItemContent[];
240
322
  readonly diagnostics: {
241
323
  readonly curriculum: readonly CurriculumIssue[];
242
324
  readonly notation: readonly NotationDiagnostic[];
@@ -252,6 +334,16 @@ interface Manifest {
252
334
  readonly tables: readonly TableDiagnostic[];
253
335
  /** `<Figure>` identity checks (F-tables-figures) — same codes, `fig-` prefixed. */
254
336
  readonly figures: readonly FigureDiagnostic[];
337
+ /** `<Diagram>` identity checks — same codes, `dia-` prefixed. */
338
+ readonly diagrams: readonly DiagramDiagnostic[];
339
+ /** Knowledge-check identity and `[[check-…]]` reference checks. */
340
+ readonly checks: readonly KnowledgeCheckDiagnostic[];
341
+ /**
342
+ * A raw Markdown table, a raw HTML `<table>`, or an Explico `<Diagram>`
343
+ * that is unkeyed or nested outside `<Table>`/`<Figure>` — invisible to
344
+ * keyed-float validation above. Warnings only.
345
+ */
346
+ readonly rawFloats: readonly RawFloatDiagnostic[];
255
347
  };
256
348
  readonly hashes: {
257
349
  /** `sha256` of every content source file, keyed by repo-relative path. */
@@ -280,4 +372,4 @@ declare function readManifestSync(): Manifest;
280
372
  */
281
373
  declare function loadManifest(courseConfig?: CourseConfig): Promise<Manifest>;
282
374
 
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 };
375
+ 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,41 @@ 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 { gateHeadingLength } from "../reference/gate-heading-length.js";
49
+ import {
50
+ splitMixedContent
51
+ } from "../reference/mixed-content.js";
33
52
  import { loadLintIgnore } from "../reference/lint-ignore.js";
34
53
  import { buildNotationRegistry } from "../reference/registry.js";
35
54
  import { formulaGlyphScope } from "../reference/gloss.js";
36
55
  import { resolveReferenceId } from "../reference/references.js";
37
- import { coursePartsOf } from "./course-config.js";
56
+ import {
57
+ coursePartsOf,
58
+ normalizeLessonExports,
59
+ normalizeDisclosureDefaults
60
+ } from "./course-config.js";
38
61
  import {
39
62
  resolveKnowledgeVersion
40
63
  } from "./knowledge-version.js";
41
64
  import {
65
+ loadAbbreviationRegistryInput,
42
66
  loadCurriculumCatalog,
43
67
  loadNotationDefinitions,
44
68
  loadNotationRegistryInput,
@@ -51,7 +75,7 @@ import {
51
75
  isLessonSlug,
52
76
  lessonIdFromSlug
53
77
  } from "./lesson-derivation.js";
54
- const MANIFEST_SCHEMA_VERSION = 7;
78
+ const MANIFEST_SCHEMA_VERSION = 14;
55
79
  const REPOSITORY_ROOT = process.cwd();
56
80
  const CONTENT_ROOT = join(REPOSITORY_ROOT, "content");
57
81
  const MANIFEST_PATH = join(REPOSITORY_ROOT, "build", "manifest.json");
@@ -184,9 +208,10 @@ async function compileManifest(courseConfig) {
184
208
  "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
209
  );
186
210
  }
187
- const [catalog, notationInput] = await Promise.all([
211
+ const [catalog, notationInput, abbreviationsInput] = await Promise.all([
188
212
  loadCurriculumCatalog(),
189
- loadNotationRegistryInput()
213
+ loadNotationRegistryInput(),
214
+ loadAbbreviationRegistryInput()
190
215
  ]);
191
216
  const registry = buildNotationRegistry(notationInput);
192
217
  const cardsByKey = new Map(
@@ -202,16 +227,54 @@ async function compileManifest(courseConfig) {
202
227
  const bundlesByLesson = new Map(
203
228
  registry.bundles.map((bundle) => [bundle.lessonId, bundle])
204
229
  );
230
+ const assessmentsInput = catalog.assessments;
205
231
  const curriculumIssues = validateCurriculum(catalog);
206
232
  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
- });
233
+ const mathIssues = [
234
+ ...gateContentMath({
235
+ notationInput,
236
+ assessments: assessmentsInput,
237
+ lessonAssessments: catalog.lessons.map((lesson) => ({
238
+ lessonId: lesson.id,
239
+ assessmentIds: lesson.assessments
240
+ }))
241
+ }),
242
+ ...gateProseNotation(notationInput.lessons),
243
+ ...gateProseNotationInAssessments(assessmentsInput),
244
+ ...gateAbbreviations({
245
+ notationInput,
246
+ abbreviations: abbreviationsInput,
247
+ courseAbbreviations: courseConfig.abbreviations
248
+ }),
249
+ ...gateHeadingLength(notationInput.lessons)
250
+ ];
251
+ const notationLessonById = new Map(
252
+ notationInput.lessons.map((lesson) => [lesson.lessonId, lesson])
253
+ );
254
+ const assessmentInputById = new Map(
255
+ assessmentsInput.map((assessment) => [assessment.id, assessment])
256
+ );
257
+ const assessmentContent = [];
258
+ for (const lesson of catalog.lessons) {
259
+ if (lesson.assessments.length === 0) continue;
260
+ const lessonInput = notationLessonById.get(lesson.id);
261
+ if (!lessonInput) continue;
262
+ const scope = pageScope(lessonInput, cardsByKey);
263
+ for (const assessmentId of lesson.assessments) {
264
+ const assessment = assessmentInputById.get(assessmentId);
265
+ if (!assessment?.items) continue;
266
+ for (const item of assessment.items) {
267
+ if (!item.id) continue;
268
+ assessmentContent.push({
269
+ lessonId: lesson.id,
270
+ assessmentId,
271
+ itemId: item.id,
272
+ promptSegments: splitMixedContent(item.prompt ?? "", scope),
273
+ explanationSegments: splitMixedContent(item.explanation ?? "", scope)
274
+ });
275
+ }
276
+ }
277
+ }
215
278
  const prerequisitesById = new Map(
216
279
  catalog.competencies.map((competency) => [
217
280
  competency.id,
@@ -245,7 +308,13 @@ async function compileManifest(courseConfig) {
245
308
  const lessonIds = group.items.filter((item) => typeof item === "string").map(lessonIdFromSlug);
246
309
  if (lessonIds.length === 0) continue;
247
310
  partCursor += 1;
248
- const used = { equations: 0, tables: 0, figures: 0 };
311
+ const used = {
312
+ equations: 0,
313
+ tables: 0,
314
+ figures: 0,
315
+ diagrams: 0,
316
+ checks: 0
317
+ };
249
318
  let chapterCursor = 0;
250
319
  for (const lessonId of lessonIds) {
251
320
  const body = bodyByLessonId.get(lessonId) ?? "";
@@ -257,6 +326,8 @@ async function compileManifest(courseConfig) {
257
326
  used.equations += countDisplayEquations(body);
258
327
  used.tables += countKeyedJsxFloats(body, { tagName: "Table" });
259
328
  used.figures += countKeyedJsxFloats(body, { tagName: "Figure" });
329
+ used.diagrams += countKeyedJsxFloats(body, { tagName: "Diagram" });
330
+ used.checks += lessonMetaById.get(lessonId)?.assessments.length ?? 0;
260
331
  }
261
332
  }
262
333
  const floatInputs = (kind) => notationInput.lessons.map((lesson) => {
@@ -295,6 +366,45 @@ async function compileManifest(courseConfig) {
295
366
  Object.entries(table).map(([key, number]) => ({ key, number }))
296
367
  ])
297
368
  );
369
+ const diagramValidation = validateDiagrams(floatInputs("diagrams"));
370
+ const diagramsBySlug = new Map(
371
+ Object.entries(diagramValidation.numbersBySlug).map(([slug, table]) => [
372
+ slug,
373
+ Object.entries(table).map(([key, number]) => ({ key, number }))
374
+ ])
375
+ );
376
+ const checkValidation = validateKnowledgeChecks(
377
+ notationInput.lessons.map((lesson) => {
378
+ const placement = placementByLessonId.get(lesson.lessonId);
379
+ return {
380
+ lessonId: lesson.lessonId,
381
+ slug: lesson.lessonId.replace(/\./g, "/"),
382
+ file: lesson.source.file,
383
+ body: lesson.body,
384
+ assessmentIds: lessonMetaById.get(lesson.lessonId)?.assessments ?? [],
385
+ ...placement ? {
386
+ numbering: {
387
+ prefix: placement.part,
388
+ offset: placement.numbering.checks
389
+ }
390
+ } : {}
391
+ };
392
+ })
393
+ );
394
+ const checksBySlug = new Map(
395
+ Object.entries(checkValidation.numbersBySlug).map(([slug, table]) => [
396
+ slug,
397
+ Object.entries(table).map(([key, number]) => ({ key, number }))
398
+ ])
399
+ );
400
+ const rawFloatDiagnostics = validateRawFloats(
401
+ notationInput.lessons.map((lesson) => ({
402
+ lessonId: lesson.lessonId,
403
+ slug: lesson.lessonId.replace(/\./g, "/"),
404
+ file: lesson.source.file,
405
+ body: lesson.body
406
+ }))
407
+ );
298
408
  const lessons = [];
299
409
  for (const notationLesson of notationInput.lessons) {
300
410
  const id = notationLesson.lessonId;
@@ -316,7 +426,9 @@ async function compileManifest(courseConfig) {
316
426
  numbering: placementByLessonId.get(id)?.numbering ?? {
317
427
  equations: 0,
318
428
  tables: 0,
319
- figures: 0
429
+ figures: 0,
430
+ diagrams: 0,
431
+ checks: 0
320
432
  },
321
433
  file: notationLesson.source.file,
322
434
  title: frontmatter.title,
@@ -326,11 +438,13 @@ async function compileManifest(courseConfig) {
326
438
  requires: [...meta.requires],
327
439
  assumptions: [...meta.assumptions],
328
440
  assessments: [...meta.assessments],
441
+ knowledgeChecks: checksBySlug.get(id.replace(/\./g, "/")) ?? [],
329
442
  sources: deriveSources(notationLesson.body),
330
443
  citations: lessonCitations(notationLesson.body),
331
444
  equations: equationsBySlug.get(id.replace(/\./g, "/")) ?? [],
332
445
  tables: tablesBySlug.get(id.replace(/\./g, "/")) ?? [],
333
446
  figures: figuresBySlug.get(id.replace(/\./g, "/")) ?? [],
447
+ diagrams: diagramsBySlug.get(id.replace(/\./g, "/")) ?? [],
334
448
  prereqEdges: sortEdges(
335
449
  teaches.flatMap(
336
450
  (to) => (prerequisitesById.get(to) ?? []).filter((from) => !teaches.includes(from)).map((from) => ({ from, to }))
@@ -356,7 +470,8 @@ async function compileManifest(courseConfig) {
356
470
  catalog,
357
471
  lintIgnore: loadLintIgnore(),
358
472
  conventions: courseConfig.conventions,
359
- ...courseConfig.operators === void 0 ? {} : { operators: courseConfig.operators }
473
+ ...courseConfig.operators === void 0 ? {} : { operators: courseConfig.operators },
474
+ ...courseConfig.romanLabels === void 0 ? {} : { romanLabels: courseConfig.romanLabels }
360
475
  });
361
476
  const rawNotation = [...loadNotationDefinitions()].sort(
362
477
  (a, b) => String(a.key).localeCompare(String(b.key))
@@ -364,6 +479,13 @@ async function compileManifest(courseConfig) {
364
479
  const competencies = catalog.competencies.map((entry) => ({ ...entry })).sort(byId);
365
480
  const sources = [...loadSourceRecords()].sort(byId);
366
481
  const tracks = catalog.tracks.map((entry) => ({ ...entry })).sort(byId);
482
+ const abbreviations = abbreviationsInput.map((entry) => ({
483
+ key: entry.key,
484
+ abbr: entry.abbr,
485
+ expansion: entry.expansion,
486
+ aliases: entry.aliases,
487
+ ...entry.domain === void 0 ? {} : { domain: entry.domain }
488
+ })).sort((a, b) => a.key.localeCompare(b.key));
367
489
  const hashes = contentHashes();
368
490
  const knowledgeChangelogPath = join(CONTENT_ROOT, "CHANGELOG.md");
369
491
  const knowledge = resolveKnowledgeVersion({
@@ -397,6 +519,8 @@ async function compileManifest(courseConfig) {
397
519
  };
398
520
  return {
399
521
  schemaVersion: MANIFEST_SCHEMA_VERSION,
522
+ lessonExports: normalizeLessonExports(courseConfig),
523
+ disclosureDefaults: normalizeDisclosureDefaults(courseConfig),
400
524
  about,
401
525
  notation: {
402
526
  definitions: registry.definitions,
@@ -408,6 +532,7 @@ async function compileManifest(courseConfig) {
408
532
  competencies,
409
533
  sources,
410
534
  tracks,
535
+ abbreviations,
411
536
  sidebar,
412
537
  lessons,
413
538
  prereqEdges,
@@ -423,6 +548,15 @@ async function compileManifest(courseConfig) {
423
548
  labels: figureValidation.labels,
424
549
  numbersBySlug: figureValidation.numbersBySlug
425
550
  },
551
+ diagrams: {
552
+ labels: diagramValidation.labels,
553
+ numbersBySlug: diagramValidation.numbersBySlug
554
+ },
555
+ checks: {
556
+ labels: checkValidation.labels,
557
+ numbersBySlug: checkValidation.numbersBySlug
558
+ },
559
+ assessmentContent,
426
560
  diagnostics: {
427
561
  curriculum: curriculumIssues,
428
562
  notation: registry.diagnostics,
@@ -432,7 +566,10 @@ async function compileManifest(courseConfig) {
432
566
  equations: equationValidation.diagnostics,
433
567
  tables: tableValidation.diagnostics,
434
568
  figures: figureValidation.diagnostics,
435
- knowledge: knowledge.diagnostics
569
+ diagrams: diagramValidation.diagnostics,
570
+ checks: checkValidation.diagnostics,
571
+ knowledge: knowledge.diagnostics,
572
+ rawFloats: rawFloatDiagnostics
436
573
  },
437
574
  hashes
438
575
  };
@@ -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`.