explico 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +344 -1
- package/README.md +117 -3
- package/dist/compiler/astro.d.ts +38 -0
- package/dist/compiler/astro.js +258 -0
- package/dist/compiler/cli.d.ts +13 -0
- package/dist/compiler/cli.js +32 -12
- package/dist/compiler/collections.d.ts +3 -2
- package/dist/compiler/collections.js +28 -1
- package/dist/compiler/course-config.d.ts +86 -4
- package/dist/compiler/course-config.js +39 -0
- package/dist/compiler/exports/standalone-html.d.ts +34 -0
- package/dist/compiler/exports/standalone-html.js +803 -0
- package/dist/compiler/lesson-derivation.d.ts +1 -1
- package/dist/compiler/lesson-derivation.js +2 -2
- package/dist/compiler/manifest.d.ts +112 -7
- package/dist/compiler/manifest.js +205 -40
- package/dist/compiler/resolution-report.d.ts +4 -0
- package/dist/reference/base-abbreviations.d.mts +30 -0
- package/dist/reference/base-abbreviations.mjs +60 -0
- package/dist/reference/base-operators.d.mts +15 -0
- package/dist/reference/base-operators.mjs +40 -0
- package/dist/reference/base-roman-labels.d.mts +35 -0
- package/dist/reference/base-roman-labels.mjs +43 -0
- package/dist/reference/consistency.d.ts +8 -2
- package/dist/reference/consistency.js +50 -3
- package/dist/reference/diagrams-validate.d.ts +21 -0
- package/dist/reference/diagrams-validate.js +13 -0
- package/dist/reference/equations-validate.d.ts +8 -3
- package/dist/reference/equations-validate.js +1 -1
- package/dist/reference/equations.d.mts +66 -35
- package/dist/reference/equations.mjs +142 -40
- package/dist/reference/gate-abbreviations.d.ts +17 -0
- package/dist/reference/gate-abbreviations.js +64 -0
- package/dist/reference/gate-math.d.ts +28 -5
- package/dist/reference/gate-math.js +5 -2
- package/dist/reference/gate-prose-notation.d.ts +60 -0
- package/dist/reference/gate-prose-notation.js +125 -0
- package/dist/reference/keyed-float-validate.d.ts +14 -8
- package/dist/reference/keyed-float-validate.js +1 -1
- package/dist/reference/keyed-jsx-floats.d.mts +24 -21
- package/dist/reference/keyed-jsx-floats.mjs +90 -41
- package/dist/reference/knowledge-checks-validate.d.ts +35 -0
- package/dist/reference/knowledge-checks-validate.js +78 -0
- package/dist/reference/knowledge-checks.d.mts +25 -0
- package/dist/reference/knowledge-checks.mjs +45 -0
- package/dist/reference/lab-math-scope.d.ts +9 -1
- package/dist/reference/lab-math-scope.js +32 -4
- package/dist/reference/math-glyphs.d.mts +30 -0
- package/dist/reference/math-glyphs.mjs +367 -0
- package/dist/reference/mdx-attributes.d.mts +13 -0
- package/dist/reference/mdx-attributes.mjs +38 -0
- package/dist/reference/mixed-content.d.ts +17 -0
- package/dist/reference/mixed-content.js +29 -0
- package/dist/reference/page-notation.d.ts +52 -0
- package/dist/reference/page-notation.js +171 -0
- package/dist/reference/raw-floats-validate.d.ts +18 -0
- package/dist/reference/raw-floats-validate.js +53 -0
- package/dist/reference/raw-floats.d.mts +18 -0
- package/dist/reference/raw-floats.mjs +129 -0
- package/dist/reference/rehype-heading-anchors.d.mts +1 -1
- package/dist/reference/rehype-heading-anchors.mjs +107 -1
- package/dist/reference/rehype-table-header-labels.d.mts +26 -0
- package/dist/reference/rehype-table-header-labels.mjs +152 -0
- package/dist/reference/remark-abbreviations.d.mts +7 -0
- package/dist/reference/remark-abbreviations.mjs +174 -0
- package/dist/reference/remark-memo.d.mts +16 -0
- package/dist/reference/remark-memo.mjs +339 -0
- package/dist/reference/remark-notation.d.mts +102 -5
- package/dist/reference/remark-notation.mjs +506 -50
- package/dist/reference/remark-short-note.d.mts +4 -0
- package/dist/reference/remark-short-note.mjs +237 -0
- package/dist/reference/types.d.ts +16 -1
- package/dist/reference/units-vocab.js +4 -0
- package/docs/VERSIONING.md +33 -4
- package/docs/authoring/README.md +12 -0
- package/docs/authoring/agents.md +17 -2
- package/docs/authoring/components.md +135 -0
- package/docs/authoring/migrating.md +154 -7
- package/docs/authoring/notation.md +568 -8
- package/docs/authoring/structure.md +251 -0
- package/docs/authoring/visualizations.md +36 -3
- package/package.json +5 -2
- package/src/compiler/astro.ts +332 -0
- package/src/compiler/cli.ts +33 -10
- package/src/compiler/collections.ts +45 -4
- package/src/compiler/course-config.ts +131 -3
- package/src/compiler/exports/standalone-html.ts +963 -0
- package/src/compiler/lesson-derivation.ts +2 -2
- package/src/compiler/manifest.ts +367 -49
- package/src/components/HoverPanel.astro +45 -4
- package/src/components/abbreviation/AbbreviationGlossary.astro +70 -0
- package/src/components/abbreviation/AbbreviationLayer.astro +127 -0
- package/src/components/assessments/AssessmentRunner.tsx +52 -17
- package/src/components/assessments/AssessmentSet.astro +98 -14
- package/src/components/assessments/RenderedContent.tsx +55 -0
- package/src/components/assessments/assessment-randomization.ts +16 -0
- package/src/components/diagram/Diagram.astro +444 -83
- package/src/components/disclosure/hover-preview.ts +202 -0
- package/src/components/disclosure/persisted-disclosure.ts +66 -0
- package/src/components/examples/CompactExamples.astro +2 -0
- package/src/components/figure/Figure.astro +279 -10
- package/src/components/memo/Memo.astro +453 -0
- package/src/components/memo/ShortNote.astro +223 -0
- package/src/components/notation/NotationFormula.astro +18 -1
- package/src/components/notation/NotationLayer.astro +116 -8
- package/src/components/notation/render-notation-math.ts +39 -2
- package/src/components/notation/safe-math-to-react.ts +50 -0
- package/src/components/reference/FloatRefLayer.astro +233 -15
- package/src/components/starlight/CollapsibleSections.astro +461 -0
- package/src/components/starlight/DiagramEnhancer.astro +385 -0
- package/src/components/starlight/HeadingAnchorEnhancer.astro +5 -17
- package/src/components/starlight/KeyedFloatEnhancer.astro +81 -3
- package/src/components/starlight/LayoutHeader.astro +50 -0
- package/src/components/starlight/LayoutSidebar.astro +62 -1
- package/src/components/starlight/LayoutSocialIcons.astro +41 -2
- package/src/components/starlight/LessonExportActions.astro +436 -0
- package/src/components/starlight/LessonFooter.astro +38 -2
- package/src/components/starlight/section-parts.ts +31 -0
- package/src/components/table/Table.astro +296 -9
- package/src/components/table/TableOverflowExpander.astro +489 -0
- package/src/content-config.ts +20 -0
- package/src/reference/base-abbreviations.mjs +60 -0
- package/src/reference/base-operators.mjs +40 -0
- package/src/reference/base-roman-labels.mjs +43 -0
- package/src/reference/consistency.ts +73 -2
- package/src/reference/diagrams-validate.ts +40 -0
- package/src/reference/equations-validate.ts +6 -4
- package/src/reference/equations.mjs +142 -40
- package/src/reference/gate-abbreviations.ts +128 -0
- package/src/reference/gate-math.ts +27 -7
- package/src/reference/gate-prose-notation.ts +224 -0
- package/src/reference/keyed-float-validate.ts +12 -9
- package/src/reference/keyed-jsx-floats.mjs +90 -41
- package/src/reference/knowledge-checks-validate.ts +130 -0
- package/src/reference/knowledge-checks.mjs +45 -0
- package/src/reference/lab-math-scope.ts +45 -3
- package/src/reference/math-glyphs.mjs +367 -0
- package/src/reference/mdx-attributes.mjs +38 -0
- package/src/reference/mixed-content.ts +69 -0
- package/src/reference/page-notation.ts +252 -0
- package/src/reference/raw-floats-validate.ts +103 -0
- package/src/reference/raw-floats.mjs +129 -0
- package/src/reference/rehype-heading-anchors.mjs +107 -1
- package/src/reference/rehype-table-header-labels.mjs +152 -0
- package/src/reference/remark-abbreviations.mjs +174 -0
- package/src/reference/remark-memo.mjs +339 -0
- package/src/reference/remark-notation.mjs +506 -50
- package/src/reference/remark-short-note.mjs +237 -0
- package/src/reference/types.ts +16 -0
- package/src/reference/units-vocab.ts +4 -0
- package/src/styles/global.css +1125 -50
|
@@ -156,7 +156,7 @@ export type SidebarGroup =
|
|
|
156
156
|
export function buildSidebar(
|
|
157
157
|
tracks: readonly TrackLike[],
|
|
158
158
|
lessonSlugs: readonly string[],
|
|
159
|
-
|
|
159
|
+
parts: readonly SidebarSection[],
|
|
160
160
|
): SidebarGroup[] {
|
|
161
161
|
const rank = new Map(
|
|
162
162
|
mergeTrackOrder(tracks).map((lessonId, index) => [lessonId, index]),
|
|
@@ -164,7 +164,7 @@ export function buildSidebar(
|
|
|
164
164
|
const positionOf = (slug: string) =>
|
|
165
165
|
rank.get(lessonIdFromSlug(slug)) ?? Number.POSITIVE_INFINITY;
|
|
166
166
|
|
|
167
|
-
const groups: SidebarGroup[] =
|
|
167
|
+
const groups: SidebarGroup[] = parts.map(({ dir, label }) => ({
|
|
168
168
|
label,
|
|
169
169
|
items: lessonSlugs
|
|
170
170
|
.filter((slug) => slug.startsWith(`${dir}/`))
|
package/src/compiler/manifest.ts
CHANGED
|
@@ -47,6 +47,8 @@ import {
|
|
|
47
47
|
type ConsistencyDiagnostic,
|
|
48
48
|
} from '../reference/consistency';
|
|
49
49
|
import { validateNotationAlignment } from '../reference/curriculum-alignment';
|
|
50
|
+
import { countDisplayEquations } from '../reference/equations.mjs';
|
|
51
|
+
import { countKeyedJsxFloats } from '../reference/keyed-jsx-floats.mjs';
|
|
50
52
|
import {
|
|
51
53
|
validateEquations,
|
|
52
54
|
type EquationDiagnostic,
|
|
@@ -62,10 +64,34 @@ import {
|
|
|
62
64
|
type FigureDiagnostic,
|
|
63
65
|
type FigureLabelRecord,
|
|
64
66
|
} from '../reference/figures-validate';
|
|
67
|
+
import {
|
|
68
|
+
validateDiagrams,
|
|
69
|
+
type DiagramDiagnostic,
|
|
70
|
+
type DiagramLabelRecord,
|
|
71
|
+
} from '../reference/diagrams-validate';
|
|
72
|
+
import {
|
|
73
|
+
validateKnowledgeChecks,
|
|
74
|
+
type KnowledgeCheckDiagnostic,
|
|
75
|
+
type KnowledgeCheckLabelRecord,
|
|
76
|
+
} from '../reference/knowledge-checks-validate';
|
|
77
|
+
import {
|
|
78
|
+
validateRawFloats,
|
|
79
|
+
type RawFloatDiagnostic,
|
|
80
|
+
} from '../reference/raw-floats-validate';
|
|
65
81
|
import {
|
|
66
82
|
gateContentMath,
|
|
83
|
+
pageScope,
|
|
67
84
|
type MathGateDiagnostic,
|
|
68
85
|
} from '../reference/gate-math';
|
|
86
|
+
import {
|
|
87
|
+
gateProseNotation,
|
|
88
|
+
gateProseNotationInAssessments,
|
|
89
|
+
} from '../reference/gate-prose-notation';
|
|
90
|
+
import { gateAbbreviations } from '../reference/gate-abbreviations';
|
|
91
|
+
import {
|
|
92
|
+
splitMixedContent,
|
|
93
|
+
type ContentSegment,
|
|
94
|
+
} from '../reference/mixed-content';
|
|
69
95
|
import { loadLintIgnore } from '../reference/lint-ignore';
|
|
70
96
|
import { buildNotationRegistry } from '../reference/registry';
|
|
71
97
|
import { formulaGlyphScope } from '../reference/gloss';
|
|
@@ -80,12 +106,18 @@ import type {
|
|
|
80
106
|
NotationLessonInput,
|
|
81
107
|
NotationPageBundle,
|
|
82
108
|
} from '../reference/types';
|
|
83
|
-
import
|
|
109
|
+
import {
|
|
110
|
+
coursePartsOf,
|
|
111
|
+
normalizeLessonExports,
|
|
112
|
+
type CourseConfig,
|
|
113
|
+
type NormalizedLessonExports,
|
|
114
|
+
} from './course-config';
|
|
84
115
|
import {
|
|
85
116
|
resolveKnowledgeVersion,
|
|
86
117
|
type KnowledgeVersionDiagnostic,
|
|
87
118
|
} from './knowledge-version';
|
|
88
119
|
import {
|
|
120
|
+
loadAbbreviationRegistryInput,
|
|
89
121
|
loadCurriculumCatalog,
|
|
90
122
|
loadNotationDefinitions,
|
|
91
123
|
loadNotationRegistryInput,
|
|
@@ -110,14 +142,43 @@ import {
|
|
|
110
142
|
* `lessons[].equations`, and `diagnostics.equations`.
|
|
111
143
|
* v4 (F-tables-figures): `tables`/`figures` (same shape as `equations`, for
|
|
112
144
|
* `<Table>`/`<Figure>`), `lessons[].tables`/`lessons[].figures`, and
|
|
113
|
-
* `diagnostics.tables`/`diagnostics.figures`.
|
|
145
|
+
* `diagnostics.tables`/`diagnostics.figures`/`diagnostics.diagrams`.
|
|
114
146
|
* v5 (about): `about` (engine identity from `explico`'s `package.json`, the
|
|
115
147
|
* course's reader-facing `about` block, and the knowledge version resolved
|
|
116
148
|
* against `content/CHANGELOG.md`) and `diagnostics.knowledge`.
|
|
117
149
|
* v6 (chapter numbering): `lessons[].chapter` — the lesson's 1-based course
|
|
118
150
|
* ordinal, the chapter in every equation/table/figure number (`(2.1)`).
|
|
151
|
+
* v7 (part numbering): `lessons[].part` (the sidebar group's 1-based ordinal,
|
|
152
|
+
* now the number before the dot), `lessons[].chapter` redefined as the
|
|
153
|
+
* lesson's ordinal *within its part*, and `lessons[].numbering` — the
|
|
154
|
+
* per-kind offsets that make `n` run continuously across a part.
|
|
155
|
+
* v8 (assessment math): `assessmentContent` — every assessment item's
|
|
156
|
+
* `prompt` / `explanation`, pre-split into text/math segments and resolved
|
|
157
|
+
* against the scope of the lesson that embeds it, so `AssessmentSet.astro`
|
|
158
|
+
* can render them through the same KaTeX pipeline as lesson body math instead
|
|
159
|
+
* of showing raw text.
|
|
160
|
+
* v9 (diagram identity): `diagrams` (the independent `<Diagram>` number and
|
|
161
|
+
* cross-page lookup), `lessons[].diagrams`, `lessons[].numbering.diagrams`,
|
|
162
|
+
* and `diagnostics.diagrams`.
|
|
163
|
+
* v10 (knowledge-check identity): `checks` (part-wide numbering and cross-page
|
|
164
|
+
* lookup), `lessons[].knowledgeChecks`, `lessons[].numbering.checks`, and
|
|
165
|
+
* `diagnostics.checks`.
|
|
166
|
+
* v11 (lesson exports): normalized standalone HTML and print build/UI settings.
|
|
167
|
+
* v12 (abbreviation hover cards): `manifest.abbreviations` — the
|
|
168
|
+
* `abbreviations` content collection (N14), flattened to plain
|
|
169
|
+
* `{key, abbr, expansion, aliases, domain?}` records sorted by key, the way
|
|
170
|
+
* `manifest.notation.raw` already carries notation frontmatter for
|
|
171
|
+
* `remarkNotation`. `remarkAbbreviations` reads it to wrap the first
|
|
172
|
+
* occurrence of a known abbreviation in a lesson's prose with a hover/click
|
|
173
|
+
* card.
|
|
174
|
+
* v13 (raw-float diagnostics, additive): `diagnostics.rawFloats` — a raw
|
|
175
|
+
* Markdown table, a raw HTML `<table>`, or an Explico `<Diagram>` that is
|
|
176
|
+
* unkeyed or nested outside `<Table>`/`<Figure>` never reaches keyed-float
|
|
177
|
+
* validation and can never get a stable number or `[[…]]` reference; this
|
|
178
|
+
* flags it (warning-only — the manifest shape a client already reads is
|
|
179
|
+
* unchanged).
|
|
119
180
|
*/
|
|
120
|
-
export const MANIFEST_SCHEMA_VERSION =
|
|
181
|
+
export const MANIFEST_SCHEMA_VERSION = 13;
|
|
121
182
|
|
|
122
183
|
const REPOSITORY_ROOT = process.cwd();
|
|
123
184
|
// Content is the top-level `content/` folder; the build output is `build/`
|
|
@@ -206,11 +267,31 @@ export interface ManifestLesson {
|
|
|
206
267
|
/** Docs-loader slug, e.g. `bonds/price-yield-relationship`. */
|
|
207
268
|
readonly slug: string;
|
|
208
269
|
/**
|
|
209
|
-
* The
|
|
210
|
-
*
|
|
211
|
-
*
|
|
270
|
+
* The 1-based ordinal of the **part** this lesson sits in — its sidebar
|
|
271
|
+
* group's position in `courseConfig.parts`. It is the number before the dot
|
|
272
|
+
* in every equation, table, figure, diagram, and knowledge-check number on
|
|
273
|
+
* the page: `(2.1)` is the first equation of part 2. See
|
|
274
|
+
* `docs/authoring/structure.md`.
|
|
275
|
+
*/
|
|
276
|
+
readonly part: number;
|
|
277
|
+
/**
|
|
278
|
+
* The lesson's 1-based ordinal **within its part** (merged track order, then
|
|
279
|
+
* any unranked lessons by slug) — its chapter number. This orders the
|
|
280
|
+
* sidebar and the reading path; it does not appear in a float's number.
|
|
212
281
|
*/
|
|
213
282
|
readonly chapter: number;
|
|
283
|
+
/**
|
|
284
|
+
* How many floats of each kind the earlier lessons of this part already
|
|
285
|
+
* used. The lesson's own floats continue from here, so a part's sequence
|
|
286
|
+
* runs unbroken across its chapters instead of restarting on every page.
|
|
287
|
+
*/
|
|
288
|
+
readonly numbering: {
|
|
289
|
+
readonly equations: number;
|
|
290
|
+
readonly tables: number;
|
|
291
|
+
readonly figures: number;
|
|
292
|
+
readonly diagrams: number;
|
|
293
|
+
readonly checks: number;
|
|
294
|
+
};
|
|
214
295
|
readonly file: string;
|
|
215
296
|
readonly title: string;
|
|
216
297
|
readonly description: string;
|
|
@@ -220,6 +301,11 @@ export interface ManifestLesson {
|
|
|
220
301
|
readonly requires: readonly string[];
|
|
221
302
|
readonly assumptions: readonly string[];
|
|
222
303
|
readonly assessments: readonly string[];
|
|
304
|
+
/** Numbered knowledge checks from `assessments`, in `.checks.yml` order. */
|
|
305
|
+
readonly knowledgeChecks: readonly {
|
|
306
|
+
readonly key: string;
|
|
307
|
+
readonly number: string;
|
|
308
|
+
}[];
|
|
223
309
|
readonly sources: readonly string[];
|
|
224
310
|
readonly citations: readonly ManifestCitation[];
|
|
225
311
|
/** Competency prerequisite edges this lesson introduces. */
|
|
@@ -239,6 +325,11 @@ export interface ManifestLesson {
|
|
|
239
325
|
readonly key: string;
|
|
240
326
|
readonly number: string;
|
|
241
327
|
}[];
|
|
328
|
+
/** Keyed `<Diagram>` elements in appearance order. */
|
|
329
|
+
readonly diagrams: readonly {
|
|
330
|
+
readonly key: string;
|
|
331
|
+
readonly number: string;
|
|
332
|
+
}[];
|
|
242
333
|
readonly notation: {
|
|
243
334
|
readonly bindings: readonly {
|
|
244
335
|
readonly key: string;
|
|
@@ -278,8 +369,19 @@ export interface ManifestAbout {
|
|
|
278
369
|
readonly changelogHref?: string;
|
|
279
370
|
}
|
|
280
371
|
|
|
372
|
+
/** One assessment item's pre-rendered `prompt` / `explanation` (v8). */
|
|
373
|
+
export interface ManifestAssessmentItemContent {
|
|
374
|
+
readonly lessonId: string;
|
|
375
|
+
readonly assessmentId: string;
|
|
376
|
+
readonly itemId: string;
|
|
377
|
+
readonly promptSegments: readonly ContentSegment[];
|
|
378
|
+
readonly explanationSegments: readonly ContentSegment[];
|
|
379
|
+
}
|
|
380
|
+
|
|
281
381
|
export interface Manifest {
|
|
282
382
|
readonly schemaVersion: number;
|
|
383
|
+
/** Normalized build/UI settings for opt-in per-lesson exports. */
|
|
384
|
+
readonly lessonExports: NormalizedLessonExports;
|
|
283
385
|
/** Reader-facing identity + versions (the `[?]` About panel). */
|
|
284
386
|
readonly about: ManifestAbout;
|
|
285
387
|
readonly notation: {
|
|
@@ -293,6 +395,8 @@ export interface Manifest {
|
|
|
293
395
|
readonly competencies: readonly Record<string, unknown>[];
|
|
294
396
|
readonly sources: readonly Record<string, unknown>[];
|
|
295
397
|
readonly tracks: readonly Record<string, unknown>[];
|
|
398
|
+
/** Abbreviations table (N14), for `remarkAbbreviations`. Ordered by key. */
|
|
399
|
+
readonly abbreviations: readonly Record<string, unknown>[];
|
|
296
400
|
readonly sidebar: readonly SidebarGroup[];
|
|
297
401
|
readonly lessons: readonly ManifestLesson[];
|
|
298
402
|
/** Full competency prerequisite graph. */
|
|
@@ -313,6 +417,26 @@ export interface Manifest {
|
|
|
313
417
|
readonly labels: readonly FigureLabelRecord[];
|
|
314
418
|
readonly numbersBySlug: Record<string, Record<string, string>>;
|
|
315
419
|
};
|
|
420
|
+
/** Keyed-float identity for `<Diagram>` — independent of figure numbering. */
|
|
421
|
+
readonly diagrams: {
|
|
422
|
+
readonly labels: readonly DiagramLabelRecord[];
|
|
423
|
+
readonly numbersBySlug: Record<string, Record<string, string>>;
|
|
424
|
+
};
|
|
425
|
+
/** Knowledge-check identity, derived from each lesson's `.checks.yml`. */
|
|
426
|
+
readonly checks: {
|
|
427
|
+
readonly labels: readonly KnowledgeCheckLabelRecord[];
|
|
428
|
+
readonly numbersBySlug: Record<string, Record<string, string>>;
|
|
429
|
+
};
|
|
430
|
+
/**
|
|
431
|
+
* Every assessment item's `prompt` / `explanation`, pre-split into
|
|
432
|
+
* text/math segments (v8). `AssessmentSet.astro` filters this by
|
|
433
|
+
* `lessonId` + `assessmentId` and renders each `math` segment's `latex`
|
|
434
|
+
* through `renderNotationMath` before handing the result to
|
|
435
|
+
* `AssessmentRunner`. An assessment embedded in more than one lesson (via
|
|
436
|
+
* more than one `checks.yml`) appears once per lesson here, since each
|
|
437
|
+
* lesson's scope can resolve its glyphs differently.
|
|
438
|
+
*/
|
|
439
|
+
readonly assessmentContent: readonly ManifestAssessmentItemContent[];
|
|
316
440
|
readonly diagnostics: {
|
|
317
441
|
readonly curriculum: readonly CurriculumIssue[];
|
|
318
442
|
readonly notation: readonly NotationDiagnostic[];
|
|
@@ -328,6 +452,16 @@ export interface Manifest {
|
|
|
328
452
|
readonly tables: readonly TableDiagnostic[];
|
|
329
453
|
/** `<Figure>` identity checks (F-tables-figures) — same codes, `fig-` prefixed. */
|
|
330
454
|
readonly figures: readonly FigureDiagnostic[];
|
|
455
|
+
/** `<Diagram>` identity checks — same codes, `dia-` prefixed. */
|
|
456
|
+
readonly diagrams: readonly DiagramDiagnostic[];
|
|
457
|
+
/** Knowledge-check identity and `[[check-…]]` reference checks. */
|
|
458
|
+
readonly checks: readonly KnowledgeCheckDiagnostic[];
|
|
459
|
+
/**
|
|
460
|
+
* A raw Markdown table, a raw HTML `<table>`, or an Explico `<Diagram>`
|
|
461
|
+
* that is unkeyed or nested outside `<Table>`/`<Figure>` — invisible to
|
|
462
|
+
* keyed-float validation above. Warnings only.
|
|
463
|
+
*/
|
|
464
|
+
readonly rawFloats: readonly RawFloatDiagnostic[];
|
|
331
465
|
};
|
|
332
466
|
readonly hashes: {
|
|
333
467
|
/** `sha256` of every content source file, keyed by repo-relative path. */
|
|
@@ -523,9 +657,10 @@ export async function compileManifest(
|
|
|
523
657
|
);
|
|
524
658
|
}
|
|
525
659
|
|
|
526
|
-
const [catalog, notationInput] = await Promise.all([
|
|
660
|
+
const [catalog, notationInput, abbreviationsInput] = await Promise.all([
|
|
527
661
|
loadCurriculumCatalog(),
|
|
528
662
|
loadNotationRegistryInput(),
|
|
663
|
+
loadAbbreviationRegistryInput(),
|
|
529
664
|
]);
|
|
530
665
|
|
|
531
666
|
const registry = buildNotationRegistry(notationInput);
|
|
@@ -545,19 +680,65 @@ export async function compileManifest(
|
|
|
545
680
|
registry.bundles.map((bundle) => [bundle.lessonId, bundle]),
|
|
546
681
|
);
|
|
547
682
|
|
|
683
|
+
const assessmentsInput = catalog.assessments as unknown as {
|
|
684
|
+
id: string;
|
|
685
|
+
items?: { id?: string; prompt?: string; explanation?: string }[];
|
|
686
|
+
}[];
|
|
687
|
+
|
|
548
688
|
const curriculumIssues = validateCurriculum(catalog);
|
|
549
689
|
const alignmentIssues = validateNotationAlignment(registry, catalog);
|
|
550
|
-
const mathIssues =
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
690
|
+
const mathIssues = [
|
|
691
|
+
...gateContentMath({
|
|
692
|
+
notationInput,
|
|
693
|
+
assessments: assessmentsInput,
|
|
694
|
+
lessonAssessments: catalog.lessons.map((lesson) => ({
|
|
695
|
+
lessonId: lesson.id,
|
|
696
|
+
assessmentIds: lesson.assessments,
|
|
697
|
+
})),
|
|
698
|
+
}),
|
|
699
|
+
...gateProseNotation(notationInput.lessons),
|
|
700
|
+
...gateProseNotationInAssessments(assessmentsInput),
|
|
701
|
+
...gateAbbreviations({
|
|
702
|
+
notationInput,
|
|
703
|
+
abbreviations: abbreviationsInput,
|
|
704
|
+
courseAbbreviations: courseConfig.abbreviations,
|
|
705
|
+
}),
|
|
706
|
+
];
|
|
707
|
+
|
|
708
|
+
// Pre-render every assessment `prompt` / `explanation` into text/math
|
|
709
|
+
// segments, resolved against the scope of whichever lesson embeds it —
|
|
710
|
+
// same relationship, same `pageScope`, as the `gateContentMath` assessment
|
|
711
|
+
// check above, so a symbol that passes the gate is exactly the symbol
|
|
712
|
+
// `AssessmentSet.astro` renders. An assessment shared by two lessons is
|
|
713
|
+
// rendered once per (lesson, assessment) pair, since the two lessons can
|
|
714
|
+
// have different scopes and therefore different resolved LaTeX.
|
|
715
|
+
const notationLessonById = new Map(
|
|
716
|
+
notationInput.lessons.map((lesson) => [lesson.lessonId, lesson]),
|
|
717
|
+
);
|
|
718
|
+
const assessmentInputById = new Map(
|
|
719
|
+
assessmentsInput.map((assessment) => [assessment.id, assessment]),
|
|
720
|
+
);
|
|
721
|
+
const assessmentContent: ManifestAssessmentItemContent[] = [];
|
|
722
|
+
for (const lesson of catalog.lessons) {
|
|
723
|
+
if (lesson.assessments.length === 0) continue;
|
|
724
|
+
const lessonInput = notationLessonById.get(lesson.id);
|
|
725
|
+
if (!lessonInput) continue;
|
|
726
|
+
const scope = pageScope(lessonInput, cardsByKey);
|
|
727
|
+
for (const assessmentId of lesson.assessments) {
|
|
728
|
+
const assessment = assessmentInputById.get(assessmentId);
|
|
729
|
+
if (!assessment?.items) continue;
|
|
730
|
+
for (const item of assessment.items) {
|
|
731
|
+
if (!item.id) continue;
|
|
732
|
+
assessmentContent.push({
|
|
733
|
+
lessonId: lesson.id,
|
|
734
|
+
assessmentId,
|
|
735
|
+
itemId: item.id,
|
|
736
|
+
promptSegments: splitMixedContent(item.prompt ?? '', scope),
|
|
737
|
+
explanationSegments: splitMixedContent(item.explanation ?? '', scope),
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
561
742
|
|
|
562
743
|
const prerequisitesById = new Map(
|
|
563
744
|
catalog.competencies.map((competency) => [
|
|
@@ -586,51 +767,148 @@ export async function compileManifest(
|
|
|
586
767
|
const sidebar = buildSidebar(
|
|
587
768
|
catalog.tracks.map((track) => ({ id: track.id, lessons: track.lessons })),
|
|
588
769
|
lessonSlugs,
|
|
589
|
-
courseConfig
|
|
770
|
+
coursePartsOf(courseConfig),
|
|
590
771
|
);
|
|
591
772
|
|
|
592
|
-
//
|
|
593
|
-
//
|
|
594
|
-
//
|
|
595
|
-
//
|
|
596
|
-
// one
|
|
597
|
-
|
|
598
|
-
|
|
773
|
+
// Part and chapter, read straight off the sidebar so there is one
|
|
774
|
+
// lesson-ordering source and not two: a part is a sidebar group (parts in
|
|
775
|
+
// config order), a chapter is a lesson's position inside its part (merged
|
|
776
|
+
// track order, then slug). The part is what prefixes every numbered item —
|
|
777
|
+
// `(2.1)` is the first item of one independent kind in part 2 — while `n`
|
|
778
|
+
// continues across the part's chapters rather than restarting on each page,
|
|
779
|
+
// so every lesson also needs the running count that precedes it.
|
|
780
|
+
// See `docs/authoring/structure.md`.
|
|
781
|
+
const bodyByLessonId = new Map(
|
|
782
|
+
notationInput.lessons.map((lesson) => [lesson.lessonId, lesson.body]),
|
|
783
|
+
);
|
|
784
|
+
const placementByLessonId = new Map<
|
|
785
|
+
string,
|
|
786
|
+
{
|
|
787
|
+
part: number;
|
|
788
|
+
chapter: number;
|
|
789
|
+
numbering: {
|
|
790
|
+
equations: number;
|
|
791
|
+
tables: number;
|
|
792
|
+
figures: number;
|
|
793
|
+
diagrams: number;
|
|
794
|
+
checks: number;
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
>();
|
|
798
|
+
let partCursor = 0;
|
|
599
799
|
for (const group of sidebar) {
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
800
|
+
const lessonIds = group.items
|
|
801
|
+
.filter((item): item is string => typeof item === 'string')
|
|
802
|
+
.map(lessonIdFromSlug);
|
|
803
|
+
if (lessonIds.length === 0) continue; // the static Reference group
|
|
804
|
+
partCursor += 1;
|
|
805
|
+
// Running totals for this part, advanced one chapter at a time.
|
|
806
|
+
const used = {
|
|
807
|
+
equations: 0,
|
|
808
|
+
tables: 0,
|
|
809
|
+
figures: 0,
|
|
810
|
+
diagrams: 0,
|
|
811
|
+
checks: 0,
|
|
812
|
+
};
|
|
813
|
+
let chapterCursor = 0;
|
|
814
|
+
for (const lessonId of lessonIds) {
|
|
815
|
+
const body = bodyByLessonId.get(lessonId) ?? '';
|
|
816
|
+
placementByLessonId.set(lessonId, {
|
|
817
|
+
part: partCursor,
|
|
818
|
+
chapter: ++chapterCursor,
|
|
819
|
+
numbering: { ...used },
|
|
820
|
+
});
|
|
821
|
+
used.equations += countDisplayEquations(body);
|
|
822
|
+
used.tables += countKeyedJsxFloats(body, { tagName: 'Table' });
|
|
823
|
+
used.figures += countKeyedJsxFloats(body, { tagName: 'Figure' });
|
|
824
|
+
used.diagrams += countKeyedJsxFloats(body, { tagName: 'Diagram' });
|
|
825
|
+
used.checks += lessonMetaById.get(lessonId)?.assessments.length ?? 0;
|
|
603
826
|
}
|
|
604
827
|
}
|
|
605
828
|
|
|
606
|
-
const
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
829
|
+
const floatInputs = (
|
|
830
|
+
kind: 'equations' | 'tables' | 'figures' | 'diagrams',
|
|
831
|
+
) =>
|
|
832
|
+
notationInput.lessons.map((lesson) => {
|
|
833
|
+
const placement = placementByLessonId.get(lesson.lessonId);
|
|
834
|
+
return {
|
|
835
|
+
lessonId: lesson.lessonId,
|
|
836
|
+
slug: lesson.lessonId.replace(/\./g, '/'),
|
|
837
|
+
file: lesson.source.file,
|
|
838
|
+
body: lesson.body,
|
|
839
|
+
...(placement
|
|
840
|
+
? {
|
|
841
|
+
numbering: {
|
|
842
|
+
prefix: placement.part,
|
|
843
|
+
offset: placement.numbering[kind],
|
|
844
|
+
},
|
|
845
|
+
}
|
|
846
|
+
: {}),
|
|
847
|
+
};
|
|
848
|
+
});
|
|
849
|
+
const equationValidation = validateEquations(floatInputs('equations'));
|
|
614
850
|
const equationsBySlug = new Map(
|
|
615
851
|
Object.entries(equationValidation.numbersBySlug).map(([slug, table]) => [
|
|
616
852
|
slug,
|
|
617
853
|
Object.entries(table).map(([key, number]) => ({ key, number })),
|
|
618
854
|
]),
|
|
619
855
|
);
|
|
620
|
-
const tableValidation = validateTables(
|
|
856
|
+
const tableValidation = validateTables(floatInputs('tables'));
|
|
621
857
|
const tablesBySlug = new Map(
|
|
622
858
|
Object.entries(tableValidation.numbersBySlug).map(([slug, table]) => [
|
|
623
859
|
slug,
|
|
624
860
|
Object.entries(table).map(([key, number]) => ({ key, number })),
|
|
625
861
|
]),
|
|
626
862
|
);
|
|
627
|
-
const figureValidation = validateFigures(
|
|
863
|
+
const figureValidation = validateFigures(floatInputs('figures'));
|
|
628
864
|
const figuresBySlug = new Map(
|
|
629
865
|
Object.entries(figureValidation.numbersBySlug).map(([slug, table]) => [
|
|
630
866
|
slug,
|
|
631
867
|
Object.entries(table).map(([key, number]) => ({ key, number })),
|
|
632
868
|
]),
|
|
633
869
|
);
|
|
870
|
+
const diagramValidation = validateDiagrams(floatInputs('diagrams'));
|
|
871
|
+
const diagramsBySlug = new Map(
|
|
872
|
+
Object.entries(diagramValidation.numbersBySlug).map(([slug, table]) => [
|
|
873
|
+
slug,
|
|
874
|
+
Object.entries(table).map(([key, number]) => ({ key, number })),
|
|
875
|
+
]),
|
|
876
|
+
);
|
|
877
|
+
const checkValidation = validateKnowledgeChecks(
|
|
878
|
+
notationInput.lessons.map((lesson) => {
|
|
879
|
+
const placement = placementByLessonId.get(lesson.lessonId);
|
|
880
|
+
return {
|
|
881
|
+
lessonId: lesson.lessonId,
|
|
882
|
+
slug: lesson.lessonId.replace(/\./g, '/'),
|
|
883
|
+
file: lesson.source.file,
|
|
884
|
+
body: lesson.body,
|
|
885
|
+
assessmentIds:
|
|
886
|
+
lessonMetaById.get(lesson.lessonId)?.assessments ?? [],
|
|
887
|
+
...(placement
|
|
888
|
+
? {
|
|
889
|
+
numbering: {
|
|
890
|
+
prefix: placement.part,
|
|
891
|
+
offset: placement.numbering.checks,
|
|
892
|
+
},
|
|
893
|
+
}
|
|
894
|
+
: {}),
|
|
895
|
+
};
|
|
896
|
+
}),
|
|
897
|
+
);
|
|
898
|
+
const checksBySlug = new Map(
|
|
899
|
+
Object.entries(checkValidation.numbersBySlug).map(([slug, table]) => [
|
|
900
|
+
slug,
|
|
901
|
+
Object.entries(table).map(([key, number]) => ({ key, number })),
|
|
902
|
+
]),
|
|
903
|
+
);
|
|
904
|
+
const rawFloatDiagnostics = validateRawFloats(
|
|
905
|
+
notationInput.lessons.map((lesson) => ({
|
|
906
|
+
lessonId: lesson.lessonId,
|
|
907
|
+
slug: lesson.lessonId.replace(/\./g, '/'),
|
|
908
|
+
file: lesson.source.file,
|
|
909
|
+
body: lesson.body,
|
|
910
|
+
})),
|
|
911
|
+
);
|
|
634
912
|
|
|
635
913
|
const lessons: ManifestLesson[] = [];
|
|
636
914
|
for (const notationLesson of notationInput.lessons) {
|
|
@@ -645,10 +923,18 @@ export async function compileManifest(
|
|
|
645
923
|
lessons.push({
|
|
646
924
|
id,
|
|
647
925
|
slug: id.replace(/\./g, '/'),
|
|
648
|
-
// 0 = the lesson is not placed in any configured
|
|
926
|
+
// 0 = the lesson is not placed in any configured part; a curriculum
|
|
649
927
|
// diagnostic covers that separately, and both numbering paths then fall
|
|
650
928
|
// back to the legacy `##`-section scheme.
|
|
651
|
-
|
|
929
|
+
part: placementByLessonId.get(id)?.part ?? 0,
|
|
930
|
+
chapter: placementByLessonId.get(id)?.chapter ?? 0,
|
|
931
|
+
numbering: placementByLessonId.get(id)?.numbering ?? {
|
|
932
|
+
equations: 0,
|
|
933
|
+
tables: 0,
|
|
934
|
+
figures: 0,
|
|
935
|
+
diagrams: 0,
|
|
936
|
+
checks: 0,
|
|
937
|
+
},
|
|
652
938
|
file: notationLesson.source.file,
|
|
653
939
|
title: frontmatter.title,
|
|
654
940
|
description: frontmatter.description,
|
|
@@ -657,11 +943,13 @@ export async function compileManifest(
|
|
|
657
943
|
requires: [...meta.requires],
|
|
658
944
|
assumptions: [...meta.assumptions],
|
|
659
945
|
assessments: [...meta.assessments],
|
|
946
|
+
knowledgeChecks: checksBySlug.get(id.replace(/\./g, '/')) ?? [],
|
|
660
947
|
sources: deriveSources(notationLesson.body),
|
|
661
948
|
citations: lessonCitations(notationLesson.body),
|
|
662
949
|
equations: equationsBySlug.get(id.replace(/\./g, '/')) ?? [],
|
|
663
950
|
tables: tablesBySlug.get(id.replace(/\./g, '/')) ?? [],
|
|
664
951
|
figures: figuresBySlug.get(id.replace(/\./g, '/')) ?? [],
|
|
952
|
+
diagrams: diagramsBySlug.get(id.replace(/\./g, '/')) ?? [],
|
|
665
953
|
prereqEdges: sortEdges(
|
|
666
954
|
teaches.flatMap((to) =>
|
|
667
955
|
(prerequisitesById.get(to) ?? [])
|
|
@@ -693,6 +981,9 @@ export async function compileManifest(
|
|
|
693
981
|
...(courseConfig.operators === undefined
|
|
694
982
|
? {}
|
|
695
983
|
: { operators: courseConfig.operators }),
|
|
984
|
+
...(courseConfig.romanLabels === undefined
|
|
985
|
+
? {}
|
|
986
|
+
: { romanLabels: courseConfig.romanLabels }),
|
|
696
987
|
});
|
|
697
988
|
|
|
698
989
|
const rawNotation = [...loadNotationDefinitions()].sort((a, b) =>
|
|
@@ -703,6 +994,15 @@ export async function compileManifest(
|
|
|
703
994
|
.sort(byId);
|
|
704
995
|
const sources = [...loadSourceRecords()].sort(byId);
|
|
705
996
|
const tracks = catalog.tracks.map((entry) => ({ ...entry })).sort(byId);
|
|
997
|
+
const abbreviations = abbreviationsInput
|
|
998
|
+
.map((entry) => ({
|
|
999
|
+
key: entry.key,
|
|
1000
|
+
abbr: entry.abbr,
|
|
1001
|
+
expansion: entry.expansion,
|
|
1002
|
+
aliases: entry.aliases,
|
|
1003
|
+
...(entry.domain === undefined ? {} : { domain: entry.domain }),
|
|
1004
|
+
}))
|
|
1005
|
+
.sort((a, b) => a.key.localeCompare(b.key));
|
|
706
1006
|
|
|
707
1007
|
const hashes = contentHashes();
|
|
708
1008
|
|
|
@@ -751,6 +1051,7 @@ export async function compileManifest(
|
|
|
751
1051
|
|
|
752
1052
|
return {
|
|
753
1053
|
schemaVersion: MANIFEST_SCHEMA_VERSION,
|
|
1054
|
+
lessonExports: normalizeLessonExports(courseConfig),
|
|
754
1055
|
about,
|
|
755
1056
|
notation: {
|
|
756
1057
|
definitions: registry.definitions,
|
|
@@ -762,6 +1063,7 @@ export async function compileManifest(
|
|
|
762
1063
|
competencies,
|
|
763
1064
|
sources,
|
|
764
1065
|
tracks,
|
|
1066
|
+
abbreviations,
|
|
765
1067
|
sidebar,
|
|
766
1068
|
lessons,
|
|
767
1069
|
prereqEdges,
|
|
@@ -777,6 +1079,15 @@ export async function compileManifest(
|
|
|
777
1079
|
labels: figureValidation.labels,
|
|
778
1080
|
numbersBySlug: figureValidation.numbersBySlug,
|
|
779
1081
|
},
|
|
1082
|
+
diagrams: {
|
|
1083
|
+
labels: diagramValidation.labels,
|
|
1084
|
+
numbersBySlug: diagramValidation.numbersBySlug,
|
|
1085
|
+
},
|
|
1086
|
+
checks: {
|
|
1087
|
+
labels: checkValidation.labels,
|
|
1088
|
+
numbersBySlug: checkValidation.numbersBySlug,
|
|
1089
|
+
},
|
|
1090
|
+
assessmentContent,
|
|
780
1091
|
diagnostics: {
|
|
781
1092
|
curriculum: curriculumIssues,
|
|
782
1093
|
notation: registry.diagnostics,
|
|
@@ -786,7 +1097,10 @@ export async function compileManifest(
|
|
|
786
1097
|
equations: equationValidation.diagnostics,
|
|
787
1098
|
tables: tableValidation.diagnostics,
|
|
788
1099
|
figures: figureValidation.diagnostics,
|
|
1100
|
+
diagrams: diagramValidation.diagnostics,
|
|
1101
|
+
checks: checkValidation.diagnostics,
|
|
789
1102
|
knowledge: knowledge.diagnostics,
|
|
1103
|
+
rawFloats: rawFloatDiagnostics,
|
|
790
1104
|
},
|
|
791
1105
|
hashes,
|
|
792
1106
|
};
|
|
@@ -842,13 +1156,17 @@ export function readManifestSync(): Manifest {
|
|
|
842
1156
|
export async function loadManifest(
|
|
843
1157
|
courseConfig?: CourseConfig,
|
|
844
1158
|
): Promise<Manifest> {
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
1159
|
+
// A caller that has the course config is an authoring/build entry point. It
|
|
1160
|
+
// must compile the current content tree even when a persisted manifest is
|
|
1161
|
+
// present: using that file here can split the page in two, with remark
|
|
1162
|
+
// seeing current frontmatter while Astro components render stale cards.
|
|
1163
|
+
if (courseConfig) {
|
|
1164
|
+
const manifest = await compileManifest(courseConfig);
|
|
1165
|
+
writeManifest(manifest);
|
|
1166
|
+
return manifest;
|
|
850
1167
|
}
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
1168
|
+
if (existsSync(MANIFEST_PATH)) return readManifestSync();
|
|
1169
|
+
throw new Error(
|
|
1170
|
+
`${relative(REPOSITORY_ROOT, MANIFEST_PATH)} not found. Run \`pnpm validate:content\` first.`,
|
|
1171
|
+
);
|
|
854
1172
|
}
|