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.
- package/CHANGELOG.md +102 -0
- package/README.md +46 -5
- package/dist/compiler/cli.d.ts +17 -0
- package/dist/compiler/cli.js +41 -3
- package/dist/compiler/collections.js +31 -1
- package/dist/compiler/course-config.d.ts +52 -1
- package/dist/compiler/knowledge-version.d.ts +58 -0
- package/dist/compiler/knowledge-version.js +80 -0
- package/dist/compiler/manifest.d.ts +87 -3
- package/dist/compiler/manifest.js +133 -20
- package/dist/compiler/resolution-report.d.ts +5 -0
- package/dist/reference/base-operators.d.mts +38 -0
- package/dist/reference/base-operators.mjs +103 -0
- package/dist/reference/consistency.d.ts +7 -2
- package/dist/reference/consistency.js +142 -2
- package/dist/reference/diagram-layout.d.ts +63 -0
- package/dist/reference/diagram-layout.js +122 -0
- package/dist/reference/equations-validate.d.ts +5 -0
- package/dist/reference/equations-validate.js +1 -1
- package/dist/reference/equations.d.mts +76 -18
- package/dist/reference/equations.mjs +118 -41
- package/dist/reference/figures-validate.d.ts +21 -0
- package/dist/reference/figures-validate.js +13 -0
- package/dist/reference/gate-math.d.ts +5 -4
- package/dist/reference/gate-math.js +73 -2
- package/dist/reference/gloss.d.ts +87 -0
- package/dist/reference/gloss.js +59 -0
- package/dist/reference/keyed-float-validate.d.ts +67 -0
- package/dist/reference/keyed-float-validate.js +110 -0
- package/dist/reference/keyed-jsx-floats.d.mts +56 -0
- package/dist/reference/keyed-jsx-floats.mjs +167 -0
- package/dist/reference/math-glyphs.d.mts +18 -0
- package/dist/reference/math-glyphs.mjs +92 -0
- package/dist/reference/registry.js +25 -0
- package/dist/reference/rehype-heading-anchors.d.mts +1 -0
- package/dist/reference/rehype-heading-anchors.mjs +144 -0
- package/dist/reference/remark-notation.d.mts +12 -0
- package/dist/reference/remark-notation.mjs +228 -32
- package/dist/reference/tables-validate.d.ts +21 -0
- package/dist/reference/tables-validate.js +13 -0
- package/dist/reference/types.d.ts +25 -2
- package/dist/reference/units-vocab.js +19 -1
- package/docs/VERSIONING.md +66 -0
- package/docs/authoring/README.md +96 -0
- package/docs/authoring/agents.md +63 -0
- package/docs/authoring/migrating.md +107 -0
- package/docs/authoring/notation.md +296 -0
- package/docs/authoring/versioning.md +116 -0
- package/docs/authoring/visualizations.md +195 -0
- package/package.json +20 -5
- package/src/compiler/cli.ts +49 -0
- package/src/compiler/collections.ts +52 -3
- package/src/compiler/course-config.ts +54 -0
- package/src/compiler/knowledge-version.ts +157 -0
- package/src/compiler/manifest.ts +270 -17
- package/src/components/CurriculumMap.astro +27 -0
- package/src/components/HoverPanel.astro +42 -0
- package/src/components/citation/CitationLayer.astro +1 -0
- package/src/components/diagram/Diagram.astro +317 -0
- package/src/components/examples/CompactExamples.astro +1 -1
- package/src/components/figure/Figure.astro +104 -0
- package/src/components/hover-panel.ts +71 -5
- package/src/components/notation/NotationFormula.astro +154 -0
- package/src/components/notation/NotationGlossary.astro +60 -0
- package/src/components/notation/NotationLayer.astro +248 -103
- package/src/components/notation/render-notation-math.ts +66 -0
- package/src/components/popup-stack.ts +345 -0
- package/src/components/reference/FloatRefLayer.astro +671 -0
- package/src/components/starlight/AboutPanel.astro +255 -0
- package/src/components/starlight/HeadingAnchorEnhancer.astro +131 -0
- package/src/components/starlight/KeyedFloatEnhancer.astro +137 -0
- package/src/components/starlight/LayoutSocialIcons.astro +12 -0
- package/src/components/starlight/LessonFooter.astro +8 -2
- package/src/components/table/Table.astro +104 -0
- package/src/content-config.ts +38 -0
- package/src/reference/base-operators.mjs +103 -0
- package/src/reference/consistency.ts +236 -2
- package/src/reference/diagram-layout.ts +246 -0
- package/src/reference/equations-validate.ts +6 -1
- package/src/reference/equations.mjs +118 -41
- package/src/reference/figures-validate.ts +40 -0
- package/src/reference/gate-math.ts +104 -7
- package/src/reference/gloss.ts +153 -0
- package/src/reference/keyed-float-validate.ts +207 -0
- package/src/reference/keyed-jsx-floats.mjs +167 -0
- package/src/reference/math-glyphs.mjs +92 -0
- package/src/reference/registry.ts +43 -0
- package/src/reference/rehype-heading-anchors.mjs +144 -0
- package/src/reference/remark-notation.mjs +228 -32
- package/src/reference/tables-validate.ts +40 -0
- package/src/reference/types.ts +26 -1
- package/src/reference/units-vocab.ts +18 -0
- package/src/styles/global.css +193 -3
- 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 {
|
|
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 =
|
|
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 =
|
|
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
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
courseConfig.
|
|
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
|
|
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,
|