explico 0.1.1 → 0.3.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 (94) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/README.md +46 -5
  3. package/dist/compiler/cli.d.ts +17 -0
  4. package/dist/compiler/cli.js +41 -3
  5. package/dist/compiler/collections.js +31 -1
  6. package/dist/compiler/course-config.d.ts +52 -1
  7. package/dist/compiler/knowledge-version.d.ts +58 -0
  8. package/dist/compiler/knowledge-version.js +80 -0
  9. package/dist/compiler/manifest.d.ts +87 -3
  10. package/dist/compiler/manifest.js +133 -20
  11. package/dist/compiler/resolution-report.d.ts +5 -0
  12. package/dist/reference/base-operators.d.mts +38 -0
  13. package/dist/reference/base-operators.mjs +103 -0
  14. package/dist/reference/consistency.d.ts +7 -2
  15. package/dist/reference/consistency.js +142 -2
  16. package/dist/reference/diagram-layout.d.ts +63 -0
  17. package/dist/reference/diagram-layout.js +122 -0
  18. package/dist/reference/equations-validate.d.ts +5 -0
  19. package/dist/reference/equations-validate.js +1 -1
  20. package/dist/reference/equations.d.mts +76 -18
  21. package/dist/reference/equations.mjs +118 -41
  22. package/dist/reference/figures-validate.d.ts +21 -0
  23. package/dist/reference/figures-validate.js +13 -0
  24. package/dist/reference/gate-math.d.ts +5 -4
  25. package/dist/reference/gate-math.js +73 -2
  26. package/dist/reference/gloss.d.ts +87 -0
  27. package/dist/reference/gloss.js +59 -0
  28. package/dist/reference/keyed-float-validate.d.ts +67 -0
  29. package/dist/reference/keyed-float-validate.js +110 -0
  30. package/dist/reference/keyed-jsx-floats.d.mts +56 -0
  31. package/dist/reference/keyed-jsx-floats.mjs +167 -0
  32. package/dist/reference/math-glyphs.d.mts +18 -0
  33. package/dist/reference/math-glyphs.mjs +92 -0
  34. package/dist/reference/registry.js +25 -0
  35. package/dist/reference/rehype-heading-anchors.d.mts +1 -0
  36. package/dist/reference/rehype-heading-anchors.mjs +144 -0
  37. package/dist/reference/remark-notation.d.mts +12 -0
  38. package/dist/reference/remark-notation.mjs +228 -32
  39. package/dist/reference/tables-validate.d.ts +21 -0
  40. package/dist/reference/tables-validate.js +13 -0
  41. package/dist/reference/types.d.ts +25 -2
  42. package/dist/reference/units-vocab.js +19 -1
  43. package/docs/VERSIONING.md +66 -0
  44. package/docs/authoring/README.md +96 -0
  45. package/docs/authoring/agents.md +63 -0
  46. package/docs/authoring/migrating.md +107 -0
  47. package/docs/authoring/notation.md +296 -0
  48. package/docs/authoring/versioning.md +116 -0
  49. package/docs/authoring/visualizations.md +195 -0
  50. package/package.json +20 -5
  51. package/src/compiler/cli.ts +49 -0
  52. package/src/compiler/collections.ts +52 -3
  53. package/src/compiler/course-config.ts +54 -0
  54. package/src/compiler/knowledge-version.ts +157 -0
  55. package/src/compiler/manifest.ts +270 -17
  56. package/src/components/CurriculumMap.astro +27 -0
  57. package/src/components/HoverPanel.astro +42 -0
  58. package/src/components/citation/CitationLayer.astro +1 -0
  59. package/src/components/diagram/Diagram.astro +317 -0
  60. package/src/components/examples/CompactExamples.astro +1 -1
  61. package/src/components/figure/Figure.astro +104 -0
  62. package/src/components/hover-panel.ts +71 -5
  63. package/src/components/notation/NotationFormula.astro +154 -0
  64. package/src/components/notation/NotationGlossary.astro +60 -0
  65. package/src/components/notation/NotationLayer.astro +248 -103
  66. package/src/components/notation/render-notation-math.ts +66 -0
  67. package/src/components/popup-stack.ts +345 -0
  68. package/src/components/reference/FloatRefLayer.astro +671 -0
  69. package/src/components/starlight/AboutPanel.astro +255 -0
  70. package/src/components/starlight/HeadingAnchorEnhancer.astro +131 -0
  71. package/src/components/starlight/KeyedFloatEnhancer.astro +137 -0
  72. package/src/components/starlight/LayoutSocialIcons.astro +12 -0
  73. package/src/components/starlight/LessonFooter.astro +8 -2
  74. package/src/components/table/Table.astro +104 -0
  75. package/src/content-config.ts +38 -0
  76. package/src/reference/base-operators.mjs +103 -0
  77. package/src/reference/consistency.ts +236 -2
  78. package/src/reference/diagram-layout.ts +246 -0
  79. package/src/reference/equations-validate.ts +6 -1
  80. package/src/reference/equations.mjs +118 -41
  81. package/src/reference/figures-validate.ts +40 -0
  82. package/src/reference/gate-math.ts +104 -7
  83. package/src/reference/gloss.ts +153 -0
  84. package/src/reference/keyed-float-validate.ts +207 -0
  85. package/src/reference/keyed-jsx-floats.mjs +167 -0
  86. package/src/reference/math-glyphs.mjs +92 -0
  87. package/src/reference/registry.ts +43 -0
  88. package/src/reference/rehype-heading-anchors.mjs +144 -0
  89. package/src/reference/remark-notation.mjs +228 -32
  90. package/src/reference/tables-validate.ts +40 -0
  91. package/src/reference/types.ts +26 -1
  92. package/src/reference/units-vocab.ts +18 -0
  93. package/src/styles/global.css +193 -3
  94. package/src/components/starlight/EquationEnhancer.astro +0 -72
@@ -1,12 +1,17 @@
1
1
  import { CurriculumIssue } from '../curriculum/validation.js';
2
2
  import { ConsistencyDiagnostic } from '../reference/consistency.js';
3
3
  import { EquationLabelRecord, EquationDiagnostic } from '../reference/equations-validate.js';
4
+ import { TableLabelRecord, TableDiagnostic } from '../reference/tables-validate.js';
5
+ import { FigureLabelRecord, FigureDiagnostic } from '../reference/figures-validate.js';
4
6
  import { MathGateDiagnostic } from '../reference/gate-math.js';
5
- import { NotationDefinitionRecord, NotationPageBundle, NotationBacklink, NotationDiagnostic, EditorialStatus } from '../reference/types.js';
7
+ import { GlyphScopeEntry } from '../reference/gloss.js';
8
+ import { NotationDefinitionRecord, NotationPageBundle, NotationBacklink, NotationDiagnostic, EditorialStatus, NotationGlossEntry } from '../reference/types.js';
6
9
  import { CourseConfig } from './course-config.js';
10
+ import { KnowledgeVersionDiagnostic } from './knowledge-version.js';
7
11
  import { SidebarGroup } from './lesson-derivation.js';
8
12
  import './collections.js';
9
13
  import '../reference/lint-ignore.js';
14
+ import '../reference/keyed-float-validate.js';
10
15
 
11
16
  /**
12
17
  * The one compiler manifest (Phase D5).
@@ -44,8 +49,16 @@ import '../reference/lint-ignore.js';
44
49
  * `diagnostics.consistency` (Tier-3 corpus checks).
45
50
  * v3 (D7): `equations` (labelled display equations + cross-page number table),
46
51
  * `lessons[].equations`, and `diagnostics.equations`.
52
+ * v4 (F-tables-figures): `tables`/`figures` (same shape as `equations`, for
53
+ * `<Table>`/`<Figure>`), `lessons[].tables`/`lessons[].figures`, and
54
+ * `diagnostics.tables`/`diagnostics.figures`.
55
+ * v5 (about): `about` (engine identity from `explico`'s `package.json`, the
56
+ * course's reader-facing `about` block, and the knowledge version resolved
57
+ * against `content/CHANGELOG.md`) and `diagnostics.knowledge`.
58
+ * v6 (chapter numbering): `lessons[].chapter` — the lesson's 1-based course
59
+ * ordinal, the chapter in every equation/table/figure number (`(2.1)`).
47
60
  */
48
- declare const MANIFEST_SCHEMA_VERSION = 3;
61
+ declare const MANIFEST_SCHEMA_VERSION = 6;
49
62
  declare const MANIFEST_PATH: string;
50
63
  interface ManifestCitation {
51
64
  readonly id: string;
@@ -81,11 +94,25 @@ interface ManifestLessonNotation {
81
94
  readonly seeAlso: readonly string[];
82
95
  readonly units?: string;
83
96
  readonly formula?: string;
97
+ /** Symbols the formula names that have no card of their own (D15). */
98
+ readonly glosses: readonly NotationGlossEntry[];
99
+ /**
100
+ * Entry-scoped glyph table for `formula` — the entry, its glosses, and any
101
+ * card the formula names. Precomputed here because the whole card set lives
102
+ * in the compiler, not in the page component.
103
+ */
104
+ readonly formulaScope: readonly GlyphScopeEntry[];
84
105
  }
85
106
  interface ManifestLesson {
86
107
  readonly id: string;
87
108
  /** Docs-loader slug, e.g. `bonds/price-yield-relationship`. */
88
109
  readonly slug: string;
110
+ /**
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.
114
+ */
115
+ readonly chapter: number;
89
116
  readonly file: string;
90
117
  readonly title: string;
91
118
  readonly description: string;
@@ -104,6 +131,16 @@ interface ManifestLesson {
104
131
  readonly key: string;
105
132
  readonly number: string;
106
133
  }[];
134
+ /** Keyed `<Table>` elements in appearance order. */
135
+ readonly tables: readonly {
136
+ readonly key: string;
137
+ readonly number: string;
138
+ }[];
139
+ /** Keyed `<Figure>` elements in appearance order. */
140
+ readonly figures: readonly {
141
+ readonly key: string;
142
+ readonly number: string;
143
+ }[];
107
144
  readonly notation: {
108
145
  readonly bindings: readonly {
109
146
  readonly key: string;
@@ -115,8 +152,39 @@ interface ManifestLesson {
115
152
  readonly resolution: readonly ManifestNotationResolution[];
116
153
  };
117
154
  }
155
+ /** The reader-facing "about this material" bundle — the `[?]` panel's data. */
156
+ interface ManifestAbout {
157
+ /** `explico`'s own identity, from its `package.json` (the version's golden source). */
158
+ readonly engine: {
159
+ readonly name: string;
160
+ readonly version: string;
161
+ readonly homepage?: string;
162
+ };
163
+ readonly material: {
164
+ readonly name: string;
165
+ readonly summary: string;
166
+ };
167
+ /** The course's knowledge version — semver over the content, engine-independent. */
168
+ readonly knowledge: {
169
+ readonly version: string;
170
+ /** Date of the newest `content/CHANGELOG.md` entry, if it has one. */
171
+ readonly date?: string;
172
+ /** Bullet notes from that entry. */
173
+ readonly notes: readonly string[];
174
+ /** Content fingerprint at compile time — ties the shown version to a build. */
175
+ readonly contentTree: string;
176
+ };
177
+ readonly authors: readonly {
178
+ readonly name: string;
179
+ readonly contact?: string;
180
+ }[];
181
+ readonly disclaimer: string;
182
+ readonly changelogHref?: string;
183
+ }
118
184
  interface Manifest {
119
185
  readonly schemaVersion: number;
186
+ /** Reader-facing identity + versions (the `[?]` About panel). */
187
+ readonly about: ManifestAbout;
120
188
  readonly notation: {
121
189
  readonly definitions: readonly NotationDefinitionRecord[];
122
190
  readonly bundles: readonly NotationPageBundle[];
@@ -138,6 +206,16 @@ interface Manifest {
138
206
  /** `slug -> { eqKey: number }` — passed to `remarkNotation` for `[[slug#eq:key]]`. */
139
207
  readonly numbersBySlug: Record<string, Record<string, string>>;
140
208
  };
209
+ /** Keyed-float identity for `<Table>` — same shape as `equations`. */
210
+ readonly tables: {
211
+ readonly labels: readonly TableLabelRecord[];
212
+ readonly numbersBySlug: Record<string, Record<string, string>>;
213
+ };
214
+ /** Keyed-float identity for `<Figure>` — same shape as `equations`. */
215
+ readonly figures: {
216
+ readonly labels: readonly FigureLabelRecord[];
217
+ readonly numbersBySlug: Record<string, Record<string, string>>;
218
+ };
141
219
  readonly diagnostics: {
142
220
  readonly curriculum: readonly CurriculumIssue[];
143
221
  readonly notation: readonly NotationDiagnostic[];
@@ -145,8 +223,14 @@ interface Manifest {
145
223
  readonly math: readonly MathGateDiagnostic[];
146
224
  /** Tier-3 corpus consistency checks (D6). Warnings only. */
147
225
  readonly consistency: readonly ConsistencyDiagnostic[];
226
+ /** Knowledge-version checks (C3): `about.version` vs `content/CHANGELOG.md`. Warnings only. */
227
+ readonly knowledge: readonly KnowledgeVersionDiagnostic[];
148
228
  /** Equation-identity checks (D7): `eq-duplicate-key`, `eq-ref-resolves` (error); `eq-key-unused` (warning). */
149
229
  readonly equations: readonly EquationDiagnostic[];
230
+ /** `<Table>` identity checks (F-tables-figures) — same codes, `tab-` prefixed. */
231
+ readonly tables: readonly TableDiagnostic[];
232
+ /** `<Figure>` identity checks (F-tables-figures) — same codes, `fig-` prefixed. */
233
+ readonly figures: readonly FigureDiagnostic[];
150
234
  };
151
235
  readonly hashes: {
152
236
  /** `sha256` of every content source file, keyed by repo-relative path. */
@@ -175,4 +259,4 @@ declare function readManifestSync(): Manifest;
175
259
  */
176
260
  declare function loadManifest(courseConfig?: CourseConfig): Promise<Manifest>;
177
261
 
178
- export { MANIFEST_PATH, MANIFEST_SCHEMA_VERSION, type Manifest, type ManifestCitation, type ManifestLesson, type ManifestLessonNotation, type ManifestNotationResolution, type PrereqEdge, compileManifest, loadManifest, readManifestSync, serializeManifest, writeManifest };
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 };
@@ -6,6 +6,7 @@ import {
6
6
  readdirSync,
7
7
  writeFileSync
8
8
  } from "node:fs";
9
+ import { createRequire } from "node:module";
9
10
  import { dirname, join, relative } from "node:path";
10
11
  import { parseFrontmatter } from "@astrojs/markdown-remark";
11
12
  import {
@@ -18,12 +19,22 @@ import { validateNotationAlignment } from "../reference/curriculum-alignment.js"
18
19
  import {
19
20
  validateEquations
20
21
  } from "../reference/equations-validate.js";
22
+ import {
23
+ validateTables
24
+ } from "../reference/tables-validate.js";
25
+ import {
26
+ validateFigures
27
+ } from "../reference/figures-validate.js";
21
28
  import {
22
29
  gateContentMath
23
30
  } from "../reference/gate-math.js";
24
31
  import { loadLintIgnore } from "../reference/lint-ignore.js";
25
32
  import { buildNotationRegistry } from "../reference/registry.js";
33
+ import { formulaGlyphScope } from "../reference/gloss.js";
26
34
  import { resolveReferenceId } from "../reference/references.js";
35
+ import {
36
+ resolveKnowledgeVersion
37
+ } from "./knowledge-version.js";
27
38
  import {
28
39
  loadCurriculumCatalog,
29
40
  loadNotationDefinitions,
@@ -34,12 +45,22 @@ import {
34
45
  import {
35
46
  buildSidebar,
36
47
  deriveSources,
37
- isLessonSlug
48
+ isLessonSlug,
49
+ lessonIdFromSlug
38
50
  } from "./lesson-derivation.js";
39
- const MANIFEST_SCHEMA_VERSION = 3;
51
+ const MANIFEST_SCHEMA_VERSION = 6;
40
52
  const REPOSITORY_ROOT = process.cwd();
41
53
  const CONTENT_ROOT = join(REPOSITORY_ROOT, "content");
42
54
  const MANIFEST_PATH = join(REPOSITORY_ROOT, "build", "manifest.json");
55
+ let enginePackage;
56
+ function readEnginePackage() {
57
+ if (!enginePackage) {
58
+ enginePackage = createRequire(import.meta.url)(
59
+ "explico/package.json"
60
+ );
61
+ }
62
+ return enginePackage;
63
+ }
43
64
  const CITE_PATTERN = /\[@([a-z0-9][a-z0-9.-]*)(?:;[ \t\r\n]*([^\]]*))?\]/g;
44
65
  function lessonCitations(body) {
45
66
  const withoutCode = body.replace(/```[\s\S]*?```/g, "").replace(/`[^`\n]*`/g, "");
@@ -97,7 +118,7 @@ function lessonNotationResolution(lesson, definitionsById, availableDefinitionId
97
118
  };
98
119
  });
99
120
  }
100
- function lessonNotationBundle(bundle, definitionsById, resolution) {
121
+ function lessonNotationBundle(bundle, definitionsById, resolution, cardsByKey) {
101
122
  const definitions = [];
102
123
  for (const definitionId of bundle?.definitionIds ?? []) {
103
124
  const definition = definitionsById.get(definitionId);
@@ -111,6 +132,14 @@ function lessonNotationBundle(bundle, definitionsById, resolution) {
111
132
  summary: definition.summary,
112
133
  editorialStatus: definition.status,
113
134
  seeAlso: [...definition.seeAlso],
135
+ glosses: [...definition.glosses],
136
+ formulaScope: formulaGlyphScope({
137
+ key: definition.key,
138
+ notation: definition.notation,
139
+ glosses: definition.glosses,
140
+ cardsByKey,
141
+ ...definition.formula === void 0 ? {} : { formula: definition.formula }
142
+ }),
114
143
  ...definition.units === void 0 ? {} : { units: definition.units },
115
144
  ...definition.formula === void 0 ? {} : { formula: definition.formula }
116
145
  });
@@ -147,11 +176,22 @@ function lessonFrontmatterById() {
147
176
  return result;
148
177
  }
149
178
  async function compileManifest(courseConfig) {
179
+ if (courseConfig.about === void 0 || courseConfig.about.version === void 0 || courseConfig.about.authors === void 0 || courseConfig.about.disclaimer === void 0) {
180
+ throw new Error(
181
+ "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
+ );
183
+ }
150
184
  const [catalog, notationInput] = await Promise.all([
151
185
  loadCurriculumCatalog(),
152
186
  loadNotationRegistryInput()
153
187
  ]);
154
188
  const registry = buildNotationRegistry(notationInput);
189
+ const cardsByKey = new Map(
190
+ notationInput.sharedDefinitions.map((definition) => [
191
+ definition.key,
192
+ { key: definition.key, notation: definition.notation }
193
+ ])
194
+ );
155
195
  const definitionsById = new Map(
156
196
  registry.definitions.map((definition) => [definition.id, definition])
157
197
  );
@@ -187,20 +227,48 @@ async function compileManifest(courseConfig) {
187
227
  catalog.lessons.map((lesson) => [lesson.id, lesson])
188
228
  );
189
229
  const frontmatterById = lessonFrontmatterById();
190
- const equationValidation = validateEquations(
191
- notationInput.lessons.map((lesson) => ({
192
- lessonId: lesson.lessonId,
193
- slug: lesson.lessonId.replace(/\./g, "/"),
194
- file: lesson.source.file,
195
- body: lesson.body
196
- }))
230
+ const lessonSlugs = [...notationInput.lessons].map((lesson) => lesson.lessonId.replace(/\./g, "/")).sort();
231
+ const sidebar = buildSidebar(
232
+ catalog.tracks.map((track) => ({ id: track.id, lessons: track.lessons })),
233
+ lessonSlugs,
234
+ courseConfig.sections
197
235
  );
236
+ const chapterByLessonId = /* @__PURE__ */ new Map();
237
+ let chapterCursor = 0;
238
+ for (const group of sidebar) {
239
+ for (const item of group.items) {
240
+ if (typeof item !== "string") continue;
241
+ chapterByLessonId.set(lessonIdFromSlug(item), ++chapterCursor);
242
+ }
243
+ }
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);
198
252
  const equationsBySlug = new Map(
199
253
  Object.entries(equationValidation.numbersBySlug).map(([slug, table]) => [
200
254
  slug,
201
255
  Object.entries(table).map(([key, number]) => ({ key, number }))
202
256
  ])
203
257
  );
258
+ const tableValidation = validateTables(keyedFloatLessonInputs);
259
+ const tablesBySlug = new Map(
260
+ Object.entries(tableValidation.numbersBySlug).map(([slug, table]) => [
261
+ slug,
262
+ Object.entries(table).map(([key, number]) => ({ key, number }))
263
+ ])
264
+ );
265
+ const figureValidation = validateFigures(keyedFloatLessonInputs);
266
+ const figuresBySlug = new Map(
267
+ Object.entries(figureValidation.numbersBySlug).map(([slug, table]) => [
268
+ slug,
269
+ Object.entries(table).map(([key, number]) => ({ key, number }))
270
+ ])
271
+ );
204
272
  const lessons = [];
205
273
  for (const notationLesson of notationInput.lessons) {
206
274
  const id = notationLesson.lessonId;
@@ -214,6 +282,10 @@ async function compileManifest(courseConfig) {
214
282
  lessons.push({
215
283
  id,
216
284
  slug: id.replace(/\./g, "/"),
285
+ // 0 = the lesson is not placed in any configured section; a curriculum
286
+ // diagnostic covers that separately, and both numbering paths then fall
287
+ // back to the legacy `##`-section scheme.
288
+ chapter: chapterByLessonId.get(id) ?? 0,
217
289
  file: notationLesson.source.file,
218
290
  title: frontmatter.title,
219
291
  description: frontmatter.description,
@@ -225,6 +297,8 @@ async function compileManifest(courseConfig) {
225
297
  sources: deriveSources(notationLesson.body),
226
298
  citations: lessonCitations(notationLesson.body),
227
299
  equations: equationsBySlug.get(id.replace(/\./g, "/")) ?? [],
300
+ tables: tablesBySlug.get(id.replace(/\./g, "/")) ?? [],
301
+ figures: figuresBySlug.get(id.replace(/\./g, "/")) ?? [],
228
302
  prereqEdges: sortEdges(
229
303
  teaches.flatMap(
230
304
  (to) => (prerequisitesById.get(to) ?? []).filter((from) => !teaches.includes(from)).map((from) => ({ from, to }))
@@ -237,7 +311,8 @@ async function compileManifest(courseConfig) {
237
311
  notationLesson,
238
312
  definitionsById,
239
313
  availableDefinitionIds
240
- )
314
+ ),
315
+ cardsByKey
241
316
  )
242
317
  });
243
318
  }
@@ -248,7 +323,8 @@ async function compileManifest(courseConfig) {
248
323
  specs: loadNotationSpecs(),
249
324
  catalog,
250
325
  lintIgnore: loadLintIgnore(),
251
- conventions: courseConfig.conventions
326
+ conventions: courseConfig.conventions,
327
+ ...courseConfig.operators === void 0 ? {} : { operators: courseConfig.operators }
252
328
  });
253
329
  const rawNotation = [...loadNotationDefinitions()].sort(
254
330
  (a, b) => String(a.key).localeCompare(String(b.key))
@@ -256,14 +332,40 @@ async function compileManifest(courseConfig) {
256
332
  const competencies = catalog.competencies.map((entry) => ({ ...entry })).sort(byId);
257
333
  const sources = [...loadSourceRecords()].sort(byId);
258
334
  const tracks = catalog.tracks.map((entry) => ({ ...entry })).sort(byId);
259
- const lessonSlugs = [...notationInput.lessons].map((lesson) => lesson.lessonId.replace(/\./g, "/")).sort();
260
- const sidebar = buildSidebar(
261
- catalog.tracks.map((track) => ({ id: track.id, lessons: track.lessons })),
262
- lessonSlugs,
263
- courseConfig.sections
264
- );
335
+ const hashes = contentHashes();
336
+ const knowledgeChangelogPath = join(CONTENT_ROOT, "CHANGELOG.md");
337
+ const knowledge = resolveKnowledgeVersion({
338
+ changelogText: existsSync(knowledgeChangelogPath) ? readFileSync(knowledgeChangelogPath, "utf8") : void 0,
339
+ declaredVersion: courseConfig.about.version,
340
+ file: relative(REPOSITORY_ROOT, knowledgeChangelogPath).replace(/\\/g, "/")
341
+ });
342
+ const enginePkg = readEnginePackage();
343
+ const about = {
344
+ engine: {
345
+ name: enginePkg.name,
346
+ version: enginePkg.version,
347
+ ...enginePkg.homepage === void 0 ? {} : { homepage: enginePkg.homepage }
348
+ },
349
+ material: {
350
+ name: courseConfig.about.materialName ?? courseConfig.title,
351
+ summary: courseConfig.about.summary ?? courseConfig.description
352
+ },
353
+ knowledge: {
354
+ version: knowledge.version,
355
+ ...knowledge.latest?.date === void 0 ? {} : { date: knowledge.latest.date },
356
+ notes: knowledge.latest?.notes ?? [],
357
+ contentTree: hashes.contentTree
358
+ },
359
+ authors: courseConfig.about.authors.map((author) => ({
360
+ name: author.name,
361
+ ...author.contact === void 0 ? {} : { contact: author.contact }
362
+ })),
363
+ disclaimer: courseConfig.about.disclaimer,
364
+ ...courseConfig.about.changelogHref === void 0 ? {} : { changelogHref: courseConfig.about.changelogHref }
365
+ };
265
366
  return {
266
367
  schemaVersion: MANIFEST_SCHEMA_VERSION,
368
+ about,
267
369
  notation: {
268
370
  definitions: registry.definitions,
269
371
  bundles: registry.bundles,
@@ -281,15 +383,26 @@ async function compileManifest(courseConfig) {
281
383
  labels: equationValidation.labels,
282
384
  numbersBySlug: equationValidation.numbersBySlug
283
385
  },
386
+ tables: {
387
+ labels: tableValidation.labels,
388
+ numbersBySlug: tableValidation.numbersBySlug
389
+ },
390
+ figures: {
391
+ labels: figureValidation.labels,
392
+ numbersBySlug: figureValidation.numbersBySlug
393
+ },
284
394
  diagnostics: {
285
395
  curriculum: curriculumIssues,
286
396
  notation: registry.diagnostics,
287
397
  alignment: alignmentIssues,
288
398
  math: mathIssues,
289
399
  consistency: consistencyIssues,
290
- equations: equationValidation.diagnostics
400
+ equations: equationValidation.diagnostics,
401
+ tables: tableValidation.diagnostics,
402
+ figures: figureValidation.diagnostics,
403
+ knowledge: knowledge.diagnostics
291
404
  },
292
- hashes: contentHashes()
405
+ hashes
293
406
  };
294
407
  }
295
408
  function byId(a, b) {
@@ -6,7 +6,12 @@ import './collections.js';
6
6
  import '../reference/types.js';
7
7
  import '../reference/lint-ignore.js';
8
8
  import '../reference/equations-validate.js';
9
+ import '../reference/tables-validate.js';
10
+ import '../reference/keyed-float-validate.js';
11
+ import '../reference/figures-validate.js';
9
12
  import '../reference/gate-math.js';
13
+ import '../reference/gloss.js';
14
+ import './knowledge-version.js';
10
15
  import './lesson-derivation.js';
11
16
 
12
17
  /**
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The operators a course treats as common, given its `course.config.ts`
3
+ * `operators` block: the shipped baseline, plus `allow`, minus `deny`.
4
+ *
5
+ * @param {{ allow?: readonly string[], deny?: readonly string[] } | undefined} operators
6
+ * @returns {Set<string>}
7
+ */
8
+ export function effectiveOperatorAllowlist(operators: {
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 isBaseOperator(name: any, operators: any): boolean;
14
+ /**
15
+ * The base operator library: the named mathematical operators every reader is
16
+ * assumed to know, so a lesson never has to introduce them with a notation
17
+ * entry.
18
+ *
19
+ * Rationale (parallel to `base-library.mjs`): the completeness gate has no
20
+ * opinion about operators — KaTeX parses `\exp`, `\int`, `\sin`, … as operator
21
+ * nodes, and `\operatorname{Var}` as an `operatorname` node, none of which the
22
+ * glyph resolver treats as a bindable symbol. The `operator-explained`
23
+ * consistency check (rule N8) fills that gap: an operator a lesson uses is
24
+ * either on this list (or the course's additions to it) or it carries a
25
+ * notation entry the reader can hover. This set is the "everyone knows these"
26
+ * baseline — trig, the exponential and logarithms, limits, extrema, and the
27
+ * big operators — kept greppable and small like the base library.
28
+ *
29
+ * A domain operator (`Var`, `Cov`, `Corr`, `sgn`, `erf`, `Tr`, `argmax`, …) is
30
+ * written `\operatorname{…}` and is NOT here: it earns a card, a
31
+ * `notation.local` entry, or an explicit line in `course.config.ts`
32
+ * (`operators.allow`).
33
+ *
34
+ * Entries are the KaTeX command without its backslash (`exp`, not `\exp`) for
35
+ * `op` nodes, and the verbatim body for `\operatorname{…}` nodes (`Var`).
36
+ * Matching is case-sensitive.
37
+ */
38
+ export const BASE_OPERATORS: Readonly<Set<string>>;
@@ -0,0 +1,103 @@
1
+ /**
2
+ * The base operator library: the named mathematical operators every reader is
3
+ * assumed to know, so a lesson never has to introduce them with a notation
4
+ * entry.
5
+ *
6
+ * Rationale (parallel to `base-library.mjs`): the completeness gate has no
7
+ * opinion about operators — KaTeX parses `\exp`, `\int`, `\sin`, … as operator
8
+ * nodes, and `\operatorname{Var}` as an `operatorname` node, none of which the
9
+ * glyph resolver treats as a bindable symbol. The `operator-explained`
10
+ * consistency check (rule N8) fills that gap: an operator a lesson uses is
11
+ * either on this list (or the course's additions to it) or it carries a
12
+ * notation entry the reader can hover. This set is the "everyone knows these"
13
+ * baseline — trig, the exponential and logarithms, limits, extrema, and the
14
+ * big operators — kept greppable and small like the base library.
15
+ *
16
+ * A domain operator (`Var`, `Cov`, `Corr`, `sgn`, `erf`, `Tr`, `argmax`, …) is
17
+ * written `\operatorname{…}` and is NOT here: it earns a card, a
18
+ * `notation.local` entry, or an explicit line in `course.config.ts`
19
+ * (`operators.allow`).
20
+ *
21
+ * Entries are the KaTeX command without its backslash (`exp`, not `\exp`) for
22
+ * `op` nodes, and the verbatim body for `\operatorname{…}` nodes (`Var`).
23
+ * Matching is case-sensitive.
24
+ */
25
+ export const BASE_OPERATORS = Object.freeze(
26
+ new Set([
27
+ // exponential / logarithms
28
+ 'exp',
29
+ 'log',
30
+ 'ln',
31
+ 'lg',
32
+ // trigonometric
33
+ 'sin',
34
+ 'cos',
35
+ 'tan',
36
+ 'cot',
37
+ 'sec',
38
+ 'csc',
39
+ // inverse trigonometric
40
+ 'arcsin',
41
+ 'arccos',
42
+ 'arctan',
43
+ // hyperbolic
44
+ 'sinh',
45
+ 'cosh',
46
+ 'tanh',
47
+ 'coth',
48
+ // limits
49
+ 'lim',
50
+ 'limsup',
51
+ 'liminf',
52
+ 'injlim',
53
+ 'projlim',
54
+ 'varlimsup',
55
+ 'varliminf',
56
+ 'varinjlim',
57
+ 'varprojlim',
58
+ // extrema
59
+ 'max',
60
+ 'min',
61
+ 'sup',
62
+ 'inf',
63
+ // linear algebra / number theory / misc
64
+ 'arg',
65
+ 'deg',
66
+ 'det',
67
+ 'dim',
68
+ 'gcd',
69
+ 'hom',
70
+ 'ker',
71
+ 'Pr',
72
+ 'mod',
73
+ // big operators
74
+ 'sum',
75
+ 'prod',
76
+ 'coprod',
77
+ 'int',
78
+ 'iint',
79
+ 'iiint',
80
+ 'oint',
81
+ ]),
82
+ );
83
+
84
+ /**
85
+ * The operators a course treats as common, given its `course.config.ts`
86
+ * `operators` block: the shipped baseline, plus `allow`, minus `deny`.
87
+ *
88
+ * @param {{ allow?: readonly string[], deny?: readonly string[] } | undefined} operators
89
+ * @returns {Set<string>}
90
+ */
91
+ export function effectiveOperatorAllowlist(operators) {
92
+ const allow = operators?.allow ?? [];
93
+ const deny = new Set(operators?.deny ?? []);
94
+ const effective = new Set(BASE_OPERATORS);
95
+ for (const name of allow) effective.add(name);
96
+ for (const name of deny) effective.delete(name);
97
+ return effective;
98
+ }
99
+
100
+ /** True when `name` needs no notation entry for this course. */
101
+ export function isBaseOperator(name, operators) {
102
+ return effectiveOperatorAllowlist(operators).has(name);
103
+ }
@@ -1,4 +1,4 @@
1
- import { CourseConventions } from '../compiler/course-config.js';
1
+ import { CourseConventions, CourseOperators } from '../compiler/course-config.js';
2
2
  import { CurriculumCatalog } from '../curriculum/validation.js';
3
3
  import { NotationSpec } from '../compiler/collections.js';
4
4
  import { LintIgnore } from './lint-ignore.js';
@@ -25,12 +25,15 @@ import { NotationRegistry, NotationRegistryInput } from './types.js';
25
25
  * (f) weak-local a `notation.local` symbol used only in its
26
26
  * own defining equation and prose
27
27
  * (g) notation-source-locator every notation `sources` entry has a locator
28
+ * (h) operator-explained a named operator a lesson uses is on the
29
+ * shipped/course operator allowlist or
30
+ * carries a notation entry (rule N8)
28
31
  *
29
32
  * Check (d) — the committed per-lesson resolution report — lives in
30
33
  * `src/content/resolution-report.ts`.
31
34
  */
32
35
 
33
- type ConsistencyCode = 'glyph-unique-in-corpus' | 'notation-units' | 'units-vocab' | 'convention-single-definition' | 'numerals-tagged' | 'weak-local' | 'notation-source-locator';
36
+ type ConsistencyCode = 'glyph-unique-in-corpus' | 'notation-units' | 'units-vocab' | 'convention-single-definition' | 'numerals-tagged' | 'weak-local' | 'notation-source-locator' | 'gloss-wants-promoting' | 'card-wants-demoting' | 'gloss-name-shape' | 'operator-explained';
34
37
  interface ConsistencyDiagnostic {
35
38
  readonly code: ConsistencyCode;
36
39
  /** Tier 3 — every finding here is a warning until content is brought clean. */
@@ -51,6 +54,8 @@ interface ConsistencyInput {
51
54
  readonly lintIgnore: LintIgnore;
52
55
  /** Convention keys this course anchors on (from `content/course.config.ts`). */
53
56
  readonly conventions: CourseConventions;
57
+ /** This course's edits to the named-operator baseline (rule N8). */
58
+ readonly operators?: CourseOperators;
54
59
  }
55
60
  /**
56
61
  * Run every consistency check. The result is sorted deterministically (code,