explico 0.2.0 → 0.4.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 +173 -0
- package/README.md +67 -7
- package/dist/compiler/astro.d.ts +30 -0
- package/dist/compiler/astro.js +79 -0
- package/dist/compiler/cli.d.ts +16 -0
- package/dist/compiler/cli.js +43 -9
- package/dist/compiler/collections.js +4 -1
- package/dist/compiler/course-config.d.ts +80 -4
- package/dist/compiler/course-config.js +12 -0
- package/dist/compiler/knowledge-version.d.ts +58 -0
- package/dist/compiler/knowledge-version.js +80 -0
- package/dist/compiler/lesson-derivation.d.ts +1 -1
- package/dist/compiler/lesson-derivation.js +2 -2
- package/dist/compiler/manifest.d.ts +98 -2
- package/dist/compiler/manifest.js +153 -24
- package/dist/compiler/resolution-report.d.ts +4 -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 +51 -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 +10 -0
- package/dist/reference/equations-validate.js +1 -1
- package/dist/reference/equations.d.mts +80 -13
- package/dist/reference/equations.mjs +109 -19
- package/dist/reference/figures-validate.d.ts +21 -0
- package/dist/reference/figures-validate.js +13 -0
- package/dist/reference/keyed-float-validate.d.ts +73 -0
- package/dist/reference/keyed-float-validate.js +110 -0
- package/dist/reference/keyed-jsx-floats.d.mts +64 -0
- package/dist/reference/keyed-jsx-floats.mjs +191 -0
- package/dist/reference/math-glyphs.d.mts +18 -0
- package/dist/reference/math-glyphs.mjs +92 -0
- package/dist/reference/remark-notation.d.mts +61 -0
- package/dist/reference/remark-notation.mjs +344 -10
- package/dist/reference/tables-validate.d.ts +21 -0
- package/dist/reference/tables-validate.js +13 -0
- package/dist/reference/units-vocab.js +19 -1
- package/docs/VERSIONING.md +66 -0
- package/docs/authoring/README.md +100 -0
- package/docs/authoring/agents.md +63 -0
- package/docs/authoring/migrating.md +169 -0
- package/docs/authoring/notation.md +297 -0
- package/docs/authoring/structure.md +139 -0
- package/docs/authoring/versioning.md +116 -0
- package/docs/authoring/visualizations.md +195 -0
- package/package.json +20 -5
- package/src/compiler/astro.ts +127 -0
- package/src/compiler/cli.ts +52 -7
- package/src/compiler/collections.ts +12 -3
- package/src/compiler/course-config.ts +93 -3
- package/src/compiler/knowledge-version.ts +157 -0
- package/src/compiler/lesson-derivation.ts +2 -2
- package/src/compiler/manifest.ts +320 -26
- package/src/components/CurriculumMap.astro +27 -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/notation/NotationLayer.astro +11 -0
- package/src/components/notation/render-notation-math.ts +13 -2
- package/src/components/reference/FloatRefLayer.astro +671 -0
- package/src/components/starlight/AboutPanel.astro +255 -0
- package/src/components/starlight/HeadingAnchorEnhancer.astro +3 -3
- package/src/components/starlight/KeyedFloatEnhancer.astro +137 -0
- package/src/components/starlight/LayoutSidebar.astro +62 -1
- package/src/components/starlight/LayoutSocialIcons.astro +12 -0
- package/src/components/starlight/LessonFooter.astro +15 -2
- package/src/components/table/Table.astro +105 -0
- package/src/reference/base-operators.mjs +103 -0
- package/src/reference/consistency.ts +83 -2
- package/src/reference/diagram-layout.ts +246 -0
- package/src/reference/equations-validate.ts +8 -1
- package/src/reference/equations.mjs +109 -19
- package/src/reference/figures-validate.ts +40 -0
- package/src/reference/keyed-float-validate.ts +210 -0
- package/src/reference/keyed-jsx-floats.mjs +191 -0
- package/src/reference/math-glyphs.mjs +92 -0
- package/src/reference/remark-notation.mjs +344 -10
- package/src/reference/tables-validate.ts +40 -0
- package/src/reference/units-vocab.ts +18 -0
- package/src/styles/global.css +244 -7
- package/src/components/starlight/EquationEnhancer.astro +0 -87
package/src/compiler/manifest.ts
CHANGED
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
readdirSync,
|
|
35
35
|
writeFileSync,
|
|
36
36
|
} from 'node:fs';
|
|
37
|
+
import { createRequire } from 'node:module';
|
|
37
38
|
import { dirname, join, relative } from 'node:path';
|
|
38
39
|
import { parseFrontmatter } from '@astrojs/markdown-remark';
|
|
39
40
|
|
|
@@ -46,11 +47,23 @@ import {
|
|
|
46
47
|
type ConsistencyDiagnostic,
|
|
47
48
|
} from '../reference/consistency';
|
|
48
49
|
import { validateNotationAlignment } from '../reference/curriculum-alignment';
|
|
50
|
+
import { countDisplayEquations } from '../reference/equations.mjs';
|
|
51
|
+
import { countKeyedJsxFloats } from '../reference/keyed-jsx-floats.mjs';
|
|
49
52
|
import {
|
|
50
53
|
validateEquations,
|
|
51
54
|
type EquationDiagnostic,
|
|
52
55
|
type EquationLabelRecord,
|
|
53
56
|
} from '../reference/equations-validate';
|
|
57
|
+
import {
|
|
58
|
+
validateTables,
|
|
59
|
+
type TableDiagnostic,
|
|
60
|
+
type TableLabelRecord,
|
|
61
|
+
} from '../reference/tables-validate';
|
|
62
|
+
import {
|
|
63
|
+
validateFigures,
|
|
64
|
+
type FigureDiagnostic,
|
|
65
|
+
type FigureLabelRecord,
|
|
66
|
+
} from '../reference/figures-validate';
|
|
54
67
|
import {
|
|
55
68
|
gateContentMath,
|
|
56
69
|
type MathGateDiagnostic,
|
|
@@ -69,7 +82,11 @@ import type {
|
|
|
69
82
|
NotationLessonInput,
|
|
70
83
|
NotationPageBundle,
|
|
71
84
|
} from '../reference/types';
|
|
72
|
-
import type
|
|
85
|
+
import { coursePartsOf, type CourseConfig } from './course-config';
|
|
86
|
+
import {
|
|
87
|
+
resolveKnowledgeVersion,
|
|
88
|
+
type KnowledgeVersionDiagnostic,
|
|
89
|
+
} from './knowledge-version';
|
|
73
90
|
import {
|
|
74
91
|
loadCurriculumCatalog,
|
|
75
92
|
loadNotationDefinitions,
|
|
@@ -81,6 +98,7 @@ import {
|
|
|
81
98
|
buildSidebar,
|
|
82
99
|
deriveSources,
|
|
83
100
|
isLessonSlug,
|
|
101
|
+
lessonIdFromSlug,
|
|
84
102
|
type SidebarGroup,
|
|
85
103
|
} from './lesson-derivation';
|
|
86
104
|
|
|
@@ -92,8 +110,20 @@ import {
|
|
|
92
110
|
* `diagnostics.consistency` (Tier-3 corpus checks).
|
|
93
111
|
* v3 (D7): `equations` (labelled display equations + cross-page number table),
|
|
94
112
|
* `lessons[].equations`, and `diagnostics.equations`.
|
|
113
|
+
* v4 (F-tables-figures): `tables`/`figures` (same shape as `equations`, for
|
|
114
|
+
* `<Table>`/`<Figure>`), `lessons[].tables`/`lessons[].figures`, and
|
|
115
|
+
* `diagnostics.tables`/`diagnostics.figures`.
|
|
116
|
+
* v5 (about): `about` (engine identity from `explico`'s `package.json`, the
|
|
117
|
+
* course's reader-facing `about` block, and the knowledge version resolved
|
|
118
|
+
* against `content/CHANGELOG.md`) and `diagnostics.knowledge`.
|
|
119
|
+
* v6 (chapter numbering): `lessons[].chapter` — the lesson's 1-based course
|
|
120
|
+
* ordinal, the chapter in every equation/table/figure number (`(2.1)`).
|
|
121
|
+
* v7 (part numbering): `lessons[].part` (the sidebar group's 1-based ordinal,
|
|
122
|
+
* now the number before the dot), `lessons[].chapter` redefined as the
|
|
123
|
+
* lesson's ordinal *within its part*, and `lessons[].numbering` — the
|
|
124
|
+
* per-kind offsets that make `n` run continuously across a part.
|
|
95
125
|
*/
|
|
96
|
-
export const MANIFEST_SCHEMA_VERSION =
|
|
126
|
+
export const MANIFEST_SCHEMA_VERSION = 7;
|
|
97
127
|
|
|
98
128
|
const REPOSITORY_ROOT = process.cwd();
|
|
99
129
|
// Content is the top-level `content/` folder; the build output is `build/`
|
|
@@ -101,6 +131,33 @@ const REPOSITORY_ROOT = process.cwd();
|
|
|
101
131
|
const CONTENT_ROOT = join(REPOSITORY_ROOT, 'content');
|
|
102
132
|
export const MANIFEST_PATH = join(REPOSITORY_ROOT, 'build', 'manifest.json');
|
|
103
133
|
|
|
134
|
+
interface EnginePackage {
|
|
135
|
+
readonly name: string;
|
|
136
|
+
readonly version: string;
|
|
137
|
+
readonly homepage?: string;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* `explico`'s own identity — the golden source for the engine version the About
|
|
142
|
+
* panel shows (the README points here, not a literal).
|
|
143
|
+
*
|
|
144
|
+
* Resolved as the bare specifier `explico/package.json` (a published `exports`
|
|
145
|
+
* entry, and a package self-reference inside this repo) through `createRequire`,
|
|
146
|
+
* so it survives being re-bundled by a course's Astro build — a relative
|
|
147
|
+
* `../../package.json` would follow the *bundle* location, not the source's.
|
|
148
|
+
* Lazy + cached: importing this module for `readManifestSync` must stay free of
|
|
149
|
+
* filesystem side effects; only `compileManifest` needs the value.
|
|
150
|
+
*/
|
|
151
|
+
let enginePackage: EnginePackage | undefined;
|
|
152
|
+
function readEnginePackage(): EnginePackage {
|
|
153
|
+
if (!enginePackage) {
|
|
154
|
+
enginePackage = createRequire(import.meta.url)(
|
|
155
|
+
'explico/package.json',
|
|
156
|
+
) as EnginePackage;
|
|
157
|
+
}
|
|
158
|
+
return enginePackage;
|
|
159
|
+
}
|
|
160
|
+
|
|
104
161
|
// --- shape ------------------------------------------------------------
|
|
105
162
|
|
|
106
163
|
export interface ManifestCitation {
|
|
@@ -154,6 +211,29 @@ export interface ManifestLesson {
|
|
|
154
211
|
readonly id: string;
|
|
155
212
|
/** Docs-loader slug, e.g. `bonds/price-yield-relationship`. */
|
|
156
213
|
readonly slug: string;
|
|
214
|
+
/**
|
|
215
|
+
* The 1-based ordinal of the **part** this lesson sits in — its sidebar
|
|
216
|
+
* group's position in `courseConfig.parts`. It is the number before the dot
|
|
217
|
+
* in every equation, table, and figure number on the page: `(2.1)` is the
|
|
218
|
+
* first equation of part 2. See `docs/authoring/structure.md`.
|
|
219
|
+
*/
|
|
220
|
+
readonly part: number;
|
|
221
|
+
/**
|
|
222
|
+
* The lesson's 1-based ordinal **within its part** (merged track order, then
|
|
223
|
+
* any unranked lessons by slug) — its chapter number. This orders the
|
|
224
|
+
* sidebar and the reading path; it does not appear in a float's number.
|
|
225
|
+
*/
|
|
226
|
+
readonly chapter: number;
|
|
227
|
+
/**
|
|
228
|
+
* How many floats of each kind the earlier lessons of this part already
|
|
229
|
+
* used. The lesson's own floats continue from here, so a part's sequence
|
|
230
|
+
* runs unbroken across its chapters instead of restarting on every page.
|
|
231
|
+
*/
|
|
232
|
+
readonly numbering: {
|
|
233
|
+
readonly equations: number;
|
|
234
|
+
readonly tables: number;
|
|
235
|
+
readonly figures: number;
|
|
236
|
+
};
|
|
157
237
|
readonly file: string;
|
|
158
238
|
readonly title: string;
|
|
159
239
|
readonly description: string;
|
|
@@ -172,6 +252,16 @@ export interface ManifestLesson {
|
|
|
172
252
|
readonly key: string;
|
|
173
253
|
readonly number: string;
|
|
174
254
|
}[];
|
|
255
|
+
/** Keyed `<Table>` elements in appearance order. */
|
|
256
|
+
readonly tables: readonly {
|
|
257
|
+
readonly key: string;
|
|
258
|
+
readonly number: string;
|
|
259
|
+
}[];
|
|
260
|
+
/** Keyed `<Figure>` elements in appearance order. */
|
|
261
|
+
readonly figures: readonly {
|
|
262
|
+
readonly key: string;
|
|
263
|
+
readonly number: string;
|
|
264
|
+
}[];
|
|
175
265
|
readonly notation: {
|
|
176
266
|
readonly bindings: readonly {
|
|
177
267
|
readonly key: string;
|
|
@@ -184,8 +274,37 @@ export interface ManifestLesson {
|
|
|
184
274
|
};
|
|
185
275
|
}
|
|
186
276
|
|
|
277
|
+
/** The reader-facing "about this material" bundle — the `[?]` panel's data. */
|
|
278
|
+
export interface ManifestAbout {
|
|
279
|
+
/** `explico`'s own identity, from its `package.json` (the version's golden source). */
|
|
280
|
+
readonly engine: {
|
|
281
|
+
readonly name: string;
|
|
282
|
+
readonly version: string;
|
|
283
|
+
readonly homepage?: string;
|
|
284
|
+
};
|
|
285
|
+
readonly material: { readonly name: string; readonly summary: string };
|
|
286
|
+
/** The course's knowledge version — semver over the content, engine-independent. */
|
|
287
|
+
readonly knowledge: {
|
|
288
|
+
readonly version: string;
|
|
289
|
+
/** Date of the newest `content/CHANGELOG.md` entry, if it has one. */
|
|
290
|
+
readonly date?: string;
|
|
291
|
+
/** Bullet notes from that entry. */
|
|
292
|
+
readonly notes: readonly string[];
|
|
293
|
+
/** Content fingerprint at compile time — ties the shown version to a build. */
|
|
294
|
+
readonly contentTree: string;
|
|
295
|
+
};
|
|
296
|
+
readonly authors: readonly {
|
|
297
|
+
readonly name: string;
|
|
298
|
+
readonly contact?: string;
|
|
299
|
+
}[];
|
|
300
|
+
readonly disclaimer: string;
|
|
301
|
+
readonly changelogHref?: string;
|
|
302
|
+
}
|
|
303
|
+
|
|
187
304
|
export interface Manifest {
|
|
188
305
|
readonly schemaVersion: number;
|
|
306
|
+
/** Reader-facing identity + versions (the `[?]` About panel). */
|
|
307
|
+
readonly about: ManifestAbout;
|
|
189
308
|
readonly notation: {
|
|
190
309
|
readonly definitions: readonly NotationDefinitionRecord[];
|
|
191
310
|
readonly bundles: readonly NotationPageBundle[];
|
|
@@ -207,6 +326,16 @@ export interface Manifest {
|
|
|
207
326
|
/** `slug -> { eqKey: number }` — passed to `remarkNotation` for `[[slug#eq:key]]`. */
|
|
208
327
|
readonly numbersBySlug: Record<string, Record<string, string>>;
|
|
209
328
|
};
|
|
329
|
+
/** Keyed-float identity for `<Table>` — same shape as `equations`. */
|
|
330
|
+
readonly tables: {
|
|
331
|
+
readonly labels: readonly TableLabelRecord[];
|
|
332
|
+
readonly numbersBySlug: Record<string, Record<string, string>>;
|
|
333
|
+
};
|
|
334
|
+
/** Keyed-float identity for `<Figure>` — same shape as `equations`. */
|
|
335
|
+
readonly figures: {
|
|
336
|
+
readonly labels: readonly FigureLabelRecord[];
|
|
337
|
+
readonly numbersBySlug: Record<string, Record<string, string>>;
|
|
338
|
+
};
|
|
210
339
|
readonly diagnostics: {
|
|
211
340
|
readonly curriculum: readonly CurriculumIssue[];
|
|
212
341
|
readonly notation: readonly NotationDiagnostic[];
|
|
@@ -214,8 +343,14 @@ export interface Manifest {
|
|
|
214
343
|
readonly math: readonly MathGateDiagnostic[];
|
|
215
344
|
/** Tier-3 corpus consistency checks (D6). Warnings only. */
|
|
216
345
|
readonly consistency: readonly ConsistencyDiagnostic[];
|
|
346
|
+
/** Knowledge-version checks (C3): `about.version` vs `content/CHANGELOG.md`. Warnings only. */
|
|
347
|
+
readonly knowledge: readonly KnowledgeVersionDiagnostic[];
|
|
217
348
|
/** Equation-identity checks (D7): `eq-duplicate-key`, `eq-ref-resolves` (error); `eq-key-unused` (warning). */
|
|
218
349
|
readonly equations: readonly EquationDiagnostic[];
|
|
350
|
+
/** `<Table>` identity checks (F-tables-figures) — same codes, `tab-` prefixed. */
|
|
351
|
+
readonly tables: readonly TableDiagnostic[];
|
|
352
|
+
/** `<Figure>` identity checks (F-tables-figures) — same codes, `fig-` prefixed. */
|
|
353
|
+
readonly figures: readonly FigureDiagnostic[];
|
|
219
354
|
};
|
|
220
355
|
readonly hashes: {
|
|
221
356
|
/** `sha256` of every content source file, keyed by repo-relative path. */
|
|
@@ -395,6 +530,22 @@ function lessonFrontmatterById(): Map<
|
|
|
395
530
|
export async function compileManifest(
|
|
396
531
|
courseConfig: CourseConfig,
|
|
397
532
|
): Promise<Manifest> {
|
|
533
|
+
// `about` became required in explico 0.3.0. A course upgrading from 0.2.x
|
|
534
|
+
// hits this before the raw `Cannot read properties of undefined` further
|
|
535
|
+
// down; the message names the fix and the guide.
|
|
536
|
+
if (
|
|
537
|
+
courseConfig.about === undefined ||
|
|
538
|
+
courseConfig.about.version === undefined ||
|
|
539
|
+
courseConfig.about.authors === undefined ||
|
|
540
|
+
courseConfig.about.disclaimer === undefined
|
|
541
|
+
) {
|
|
542
|
+
throw new Error(
|
|
543
|
+
"course.config.ts is missing a complete `about` block (required since explico 0.3.0): " +
|
|
544
|
+
"`about: { version, authors: [{ name }], disclaimer, materialName?, summary?, changelogHref? }`. " +
|
|
545
|
+
'See docs/authoring/migrating.md (§ 0.2.x → 0.3.0) in the installed package.',
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
|
|
398
549
|
const [catalog, notationInput] = await Promise.all([
|
|
399
550
|
loadCurriculumCatalog(),
|
|
400
551
|
loadNotationRegistryInput(),
|
|
@@ -452,20 +603,96 @@ export async function compileManifest(
|
|
|
452
603
|
);
|
|
453
604
|
const frontmatterById = lessonFrontmatterById();
|
|
454
605
|
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
606
|
+
const lessonSlugs = [...notationInput.lessons]
|
|
607
|
+
.map((lesson) => lesson.lessonId.replace(/\./g, '/'))
|
|
608
|
+
.sort();
|
|
609
|
+
const sidebar = buildSidebar(
|
|
610
|
+
catalog.tracks.map((track) => ({ id: track.id, lessons: track.lessons })),
|
|
611
|
+
lessonSlugs,
|
|
612
|
+
coursePartsOf(courseConfig),
|
|
462
613
|
);
|
|
614
|
+
|
|
615
|
+
// Part and chapter, read straight off the sidebar so there is one
|
|
616
|
+
// lesson-ordering source and not two: a part is a sidebar group (parts in
|
|
617
|
+
// config order), a chapter is a lesson's position inside its part (merged
|
|
618
|
+
// track order, then slug). The part is what prefixes every equation / table
|
|
619
|
+
// / figure number — `(2.1)` is the first equation of part 2 — while `n`
|
|
620
|
+
// continues across the part's chapters rather than restarting on each page,
|
|
621
|
+
// so every lesson also needs the running count that precedes it.
|
|
622
|
+
// See `docs/authoring/structure.md`.
|
|
623
|
+
const bodyByLessonId = new Map(
|
|
624
|
+
notationInput.lessons.map((lesson) => [lesson.lessonId, lesson.body]),
|
|
625
|
+
);
|
|
626
|
+
const placementByLessonId = new Map<
|
|
627
|
+
string,
|
|
628
|
+
{
|
|
629
|
+
part: number;
|
|
630
|
+
chapter: number;
|
|
631
|
+
numbering: { equations: number; tables: number; figures: number };
|
|
632
|
+
}
|
|
633
|
+
>();
|
|
634
|
+
let partCursor = 0;
|
|
635
|
+
for (const group of sidebar) {
|
|
636
|
+
const lessonIds = group.items
|
|
637
|
+
.filter((item): item is string => typeof item === 'string')
|
|
638
|
+
.map(lessonIdFromSlug);
|
|
639
|
+
if (lessonIds.length === 0) continue; // the static Reference group
|
|
640
|
+
partCursor += 1;
|
|
641
|
+
// Running totals for this part, advanced one chapter at a time.
|
|
642
|
+
const used = { equations: 0, tables: 0, figures: 0 };
|
|
643
|
+
let chapterCursor = 0;
|
|
644
|
+
for (const lessonId of lessonIds) {
|
|
645
|
+
const body = bodyByLessonId.get(lessonId) ?? '';
|
|
646
|
+
placementByLessonId.set(lessonId, {
|
|
647
|
+
part: partCursor,
|
|
648
|
+
chapter: ++chapterCursor,
|
|
649
|
+
numbering: { ...used },
|
|
650
|
+
});
|
|
651
|
+
used.equations += countDisplayEquations(body);
|
|
652
|
+
used.tables += countKeyedJsxFloats(body, { tagName: 'Table' });
|
|
653
|
+
used.figures += countKeyedJsxFloats(body, { tagName: 'Figure' });
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const floatInputs = (kind: 'equations' | 'tables' | 'figures') =>
|
|
658
|
+
notationInput.lessons.map((lesson) => {
|
|
659
|
+
const placement = placementByLessonId.get(lesson.lessonId);
|
|
660
|
+
return {
|
|
661
|
+
lessonId: lesson.lessonId,
|
|
662
|
+
slug: lesson.lessonId.replace(/\./g, '/'),
|
|
663
|
+
file: lesson.source.file,
|
|
664
|
+
body: lesson.body,
|
|
665
|
+
...(placement
|
|
666
|
+
? {
|
|
667
|
+
numbering: {
|
|
668
|
+
prefix: placement.part,
|
|
669
|
+
offset: placement.numbering[kind],
|
|
670
|
+
},
|
|
671
|
+
}
|
|
672
|
+
: {}),
|
|
673
|
+
};
|
|
674
|
+
});
|
|
675
|
+
const equationValidation = validateEquations(floatInputs('equations'));
|
|
463
676
|
const equationsBySlug = new Map(
|
|
464
677
|
Object.entries(equationValidation.numbersBySlug).map(([slug, table]) => [
|
|
465
678
|
slug,
|
|
466
679
|
Object.entries(table).map(([key, number]) => ({ key, number })),
|
|
467
680
|
]),
|
|
468
681
|
);
|
|
682
|
+
const tableValidation = validateTables(floatInputs('tables'));
|
|
683
|
+
const tablesBySlug = new Map(
|
|
684
|
+
Object.entries(tableValidation.numbersBySlug).map(([slug, table]) => [
|
|
685
|
+
slug,
|
|
686
|
+
Object.entries(table).map(([key, number]) => ({ key, number })),
|
|
687
|
+
]),
|
|
688
|
+
);
|
|
689
|
+
const figureValidation = validateFigures(floatInputs('figures'));
|
|
690
|
+
const figuresBySlug = new Map(
|
|
691
|
+
Object.entries(figureValidation.numbersBySlug).map(([slug, table]) => [
|
|
692
|
+
slug,
|
|
693
|
+
Object.entries(table).map(([key, number]) => ({ key, number })),
|
|
694
|
+
]),
|
|
695
|
+
);
|
|
469
696
|
|
|
470
697
|
const lessons: ManifestLesson[] = [];
|
|
471
698
|
for (const notationLesson of notationInput.lessons) {
|
|
@@ -480,6 +707,16 @@ export async function compileManifest(
|
|
|
480
707
|
lessons.push({
|
|
481
708
|
id,
|
|
482
709
|
slug: id.replace(/\./g, '/'),
|
|
710
|
+
// 0 = the lesson is not placed in any configured part; a curriculum
|
|
711
|
+
// diagnostic covers that separately, and both numbering paths then fall
|
|
712
|
+
// back to the legacy `##`-section scheme.
|
|
713
|
+
part: placementByLessonId.get(id)?.part ?? 0,
|
|
714
|
+
chapter: placementByLessonId.get(id)?.chapter ?? 0,
|
|
715
|
+
numbering: placementByLessonId.get(id)?.numbering ?? {
|
|
716
|
+
equations: 0,
|
|
717
|
+
tables: 0,
|
|
718
|
+
figures: 0,
|
|
719
|
+
},
|
|
483
720
|
file: notationLesson.source.file,
|
|
484
721
|
title: frontmatter.title,
|
|
485
722
|
description: frontmatter.description,
|
|
@@ -491,6 +728,8 @@ export async function compileManifest(
|
|
|
491
728
|
sources: deriveSources(notationLesson.body),
|
|
492
729
|
citations: lessonCitations(notationLesson.body),
|
|
493
730
|
equations: equationsBySlug.get(id.replace(/\./g, '/')) ?? [],
|
|
731
|
+
tables: tablesBySlug.get(id.replace(/\./g, '/')) ?? [],
|
|
732
|
+
figures: figuresBySlug.get(id.replace(/\./g, '/')) ?? [],
|
|
494
733
|
prereqEdges: sortEdges(
|
|
495
734
|
teaches.flatMap((to) =>
|
|
496
735
|
(prerequisitesById.get(to) ?? [])
|
|
@@ -519,6 +758,9 @@ export async function compileManifest(
|
|
|
519
758
|
catalog,
|
|
520
759
|
lintIgnore: loadLintIgnore(),
|
|
521
760
|
conventions: courseConfig.conventions,
|
|
761
|
+
...(courseConfig.operators === undefined
|
|
762
|
+
? {}
|
|
763
|
+
: { operators: courseConfig.operators }),
|
|
522
764
|
});
|
|
523
765
|
|
|
524
766
|
const rawNotation = [...loadNotationDefinitions()].sort((a, b) =>
|
|
@@ -530,17 +772,54 @@ export async function compileManifest(
|
|
|
530
772
|
const sources = [...loadSourceRecords()].sort(byId);
|
|
531
773
|
const tracks = catalog.tracks.map((entry) => ({ ...entry })).sort(byId);
|
|
532
774
|
|
|
533
|
-
const
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
775
|
+
const hashes = contentHashes();
|
|
776
|
+
|
|
777
|
+
// Knowledge version (C3): `about.version` vs the newest `content/CHANGELOG.md`
|
|
778
|
+
// entry. Every finding is a Tier-3 warning; the resolved entry feeds the
|
|
779
|
+
// `[?]` About panel.
|
|
780
|
+
const knowledgeChangelogPath = join(CONTENT_ROOT, 'CHANGELOG.md');
|
|
781
|
+
const knowledge = resolveKnowledgeVersion({
|
|
782
|
+
changelogText: existsSync(knowledgeChangelogPath)
|
|
783
|
+
? readFileSync(knowledgeChangelogPath, 'utf8')
|
|
784
|
+
: undefined,
|
|
785
|
+
declaredVersion: courseConfig.about.version,
|
|
786
|
+
file: relative(REPOSITORY_ROOT, knowledgeChangelogPath).replace(/\\/g, '/'),
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
const enginePkg = readEnginePackage();
|
|
790
|
+
const about: ManifestAbout = {
|
|
791
|
+
engine: {
|
|
792
|
+
name: enginePkg.name,
|
|
793
|
+
version: enginePkg.version,
|
|
794
|
+
...(enginePkg.homepage === undefined
|
|
795
|
+
? {}
|
|
796
|
+
: { homepage: enginePkg.homepage }),
|
|
797
|
+
},
|
|
798
|
+
material: {
|
|
799
|
+
name: courseConfig.about.materialName ?? courseConfig.title,
|
|
800
|
+
summary: courseConfig.about.summary ?? courseConfig.description,
|
|
801
|
+
},
|
|
802
|
+
knowledge: {
|
|
803
|
+
version: knowledge.version,
|
|
804
|
+
...(knowledge.latest?.date === undefined
|
|
805
|
+
? {}
|
|
806
|
+
: { date: knowledge.latest.date }),
|
|
807
|
+
notes: knowledge.latest?.notes ?? [],
|
|
808
|
+
contentTree: hashes.contentTree,
|
|
809
|
+
},
|
|
810
|
+
authors: courseConfig.about.authors.map((author) => ({
|
|
811
|
+
name: author.name,
|
|
812
|
+
...(author.contact === undefined ? {} : { contact: author.contact }),
|
|
813
|
+
})),
|
|
814
|
+
disclaimer: courseConfig.about.disclaimer,
|
|
815
|
+
...(courseConfig.about.changelogHref === undefined
|
|
816
|
+
? {}
|
|
817
|
+
: { changelogHref: courseConfig.about.changelogHref }),
|
|
818
|
+
};
|
|
541
819
|
|
|
542
820
|
return {
|
|
543
821
|
schemaVersion: MANIFEST_SCHEMA_VERSION,
|
|
822
|
+
about,
|
|
544
823
|
notation: {
|
|
545
824
|
definitions: registry.definitions,
|
|
546
825
|
bundles: registry.bundles,
|
|
@@ -558,6 +837,14 @@ export async function compileManifest(
|
|
|
558
837
|
labels: equationValidation.labels,
|
|
559
838
|
numbersBySlug: equationValidation.numbersBySlug,
|
|
560
839
|
},
|
|
840
|
+
tables: {
|
|
841
|
+
labels: tableValidation.labels,
|
|
842
|
+
numbersBySlug: tableValidation.numbersBySlug,
|
|
843
|
+
},
|
|
844
|
+
figures: {
|
|
845
|
+
labels: figureValidation.labels,
|
|
846
|
+
numbersBySlug: figureValidation.numbersBySlug,
|
|
847
|
+
},
|
|
561
848
|
diagnostics: {
|
|
562
849
|
curriculum: curriculumIssues,
|
|
563
850
|
notation: registry.diagnostics,
|
|
@@ -565,8 +852,11 @@ export async function compileManifest(
|
|
|
565
852
|
math: mathIssues,
|
|
566
853
|
consistency: consistencyIssues,
|
|
567
854
|
equations: equationValidation.diagnostics,
|
|
855
|
+
tables: tableValidation.diagnostics,
|
|
856
|
+
figures: figureValidation.diagnostics,
|
|
857
|
+
knowledge: knowledge.diagnostics,
|
|
568
858
|
},
|
|
569
|
-
hashes
|
|
859
|
+
hashes,
|
|
570
860
|
};
|
|
571
861
|
}
|
|
572
862
|
|
|
@@ -620,13 +910,17 @@ export function readManifestSync(): Manifest {
|
|
|
620
910
|
export async function loadManifest(
|
|
621
911
|
courseConfig?: CourseConfig,
|
|
622
912
|
): Promise<Manifest> {
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
913
|
+
// A caller that has the course config is an authoring/build entry point. It
|
|
914
|
+
// must compile the current content tree even when a persisted manifest is
|
|
915
|
+
// present: using that file here can split the page in two, with remark
|
|
916
|
+
// seeing current frontmatter while Astro components render stale cards.
|
|
917
|
+
if (courseConfig) {
|
|
918
|
+
const manifest = await compileManifest(courseConfig);
|
|
919
|
+
writeManifest(manifest);
|
|
920
|
+
return manifest;
|
|
628
921
|
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
922
|
+
if (existsSync(MANIFEST_PATH)) return readManifestSync();
|
|
923
|
+
throw new Error(
|
|
924
|
+
`${relative(REPOSITORY_ROOT, MANIFEST_PATH)} not found. Run \`pnpm validate:content\` first.`,
|
|
925
|
+
);
|
|
632
926
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { getCollection } from 'astro:content';
|
|
3
3
|
import { loadManifest } from '../compiler/manifest';
|
|
4
4
|
import { isLessonSlug, lessonIdFromSlug } from '../compiler/lesson-derivation';
|
|
5
|
+
import Diagram, { type DiagramEdge, type DiagramNode } from './diagram/Diagram.astro';
|
|
5
6
|
|
|
6
7
|
const [tracks, docs, competencies, manifest] = await Promise.all([
|
|
7
8
|
getCollection('tracks'),
|
|
@@ -22,6 +23,20 @@ const lessonById = new Map(
|
|
|
22
23
|
const competencyById = new Map(
|
|
23
24
|
competencies.map((entry) => [entry.data.id, entry.data]),
|
|
24
25
|
);
|
|
26
|
+
|
|
27
|
+
// The dependency graph is read straight off the authored `prerequisites`
|
|
28
|
+
// lists — no separate content to keep in sync, and the same data the build's
|
|
29
|
+
// DAG/cycle validator already checks.
|
|
30
|
+
const competencyNodes: DiagramNode[] = competencies.map(({ data }) => ({
|
|
31
|
+
id: data.id,
|
|
32
|
+
text: data.title,
|
|
33
|
+
}));
|
|
34
|
+
const competencyEdges: DiagramEdge[] = competencies.flatMap(({ data }) =>
|
|
35
|
+
data.prerequisites.map((prerequisiteId) => ({
|
|
36
|
+
from: prerequisiteId,
|
|
37
|
+
to: data.id,
|
|
38
|
+
})),
|
|
39
|
+
);
|
|
25
40
|
---
|
|
26
41
|
|
|
27
42
|
{
|
|
@@ -110,6 +125,18 @@ const competencyById = new Map(
|
|
|
110
125
|
The build rejects unknown references, cycles, invalid lesson ordering, and
|
|
111
126
|
insufficient assessment coverage.
|
|
112
127
|
</p>
|
|
128
|
+
|
|
129
|
+
{
|
|
130
|
+
competencyNodes.length > 0 && (
|
|
131
|
+
<Diagram
|
|
132
|
+
id="competency-graph"
|
|
133
|
+
label="Competency dependency graph"
|
|
134
|
+
nodes={competencyNodes}
|
|
135
|
+
edges={competencyEdges}
|
|
136
|
+
/>
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
|
|
113
140
|
<ol class="competency-map">
|
|
114
141
|
{
|
|
115
142
|
competencies
|