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.
Files changed (83) hide show
  1. package/CHANGELOG.md +173 -0
  2. package/README.md +67 -7
  3. package/dist/compiler/astro.d.ts +30 -0
  4. package/dist/compiler/astro.js +79 -0
  5. package/dist/compiler/cli.d.ts +16 -0
  6. package/dist/compiler/cli.js +43 -9
  7. package/dist/compiler/collections.js +4 -1
  8. package/dist/compiler/course-config.d.ts +80 -4
  9. package/dist/compiler/course-config.js +12 -0
  10. package/dist/compiler/knowledge-version.d.ts +58 -0
  11. package/dist/compiler/knowledge-version.js +80 -0
  12. package/dist/compiler/lesson-derivation.d.ts +1 -1
  13. package/dist/compiler/lesson-derivation.js +2 -2
  14. package/dist/compiler/manifest.d.ts +98 -2
  15. package/dist/compiler/manifest.js +153 -24
  16. package/dist/compiler/resolution-report.d.ts +4 -0
  17. package/dist/reference/base-operators.d.mts +38 -0
  18. package/dist/reference/base-operators.mjs +103 -0
  19. package/dist/reference/consistency.d.ts +7 -2
  20. package/dist/reference/consistency.js +51 -2
  21. package/dist/reference/diagram-layout.d.ts +63 -0
  22. package/dist/reference/diagram-layout.js +122 -0
  23. package/dist/reference/equations-validate.d.ts +10 -0
  24. package/dist/reference/equations-validate.js +1 -1
  25. package/dist/reference/equations.d.mts +80 -13
  26. package/dist/reference/equations.mjs +109 -19
  27. package/dist/reference/figures-validate.d.ts +21 -0
  28. package/dist/reference/figures-validate.js +13 -0
  29. package/dist/reference/keyed-float-validate.d.ts +73 -0
  30. package/dist/reference/keyed-float-validate.js +110 -0
  31. package/dist/reference/keyed-jsx-floats.d.mts +64 -0
  32. package/dist/reference/keyed-jsx-floats.mjs +191 -0
  33. package/dist/reference/math-glyphs.d.mts +18 -0
  34. package/dist/reference/math-glyphs.mjs +92 -0
  35. package/dist/reference/remark-notation.d.mts +61 -0
  36. package/dist/reference/remark-notation.mjs +344 -10
  37. package/dist/reference/tables-validate.d.ts +21 -0
  38. package/dist/reference/tables-validate.js +13 -0
  39. package/dist/reference/units-vocab.js +19 -1
  40. package/docs/VERSIONING.md +66 -0
  41. package/docs/authoring/README.md +100 -0
  42. package/docs/authoring/agents.md +63 -0
  43. package/docs/authoring/migrating.md +169 -0
  44. package/docs/authoring/notation.md +297 -0
  45. package/docs/authoring/structure.md +139 -0
  46. package/docs/authoring/versioning.md +116 -0
  47. package/docs/authoring/visualizations.md +195 -0
  48. package/package.json +20 -5
  49. package/src/compiler/astro.ts +127 -0
  50. package/src/compiler/cli.ts +52 -7
  51. package/src/compiler/collections.ts +12 -3
  52. package/src/compiler/course-config.ts +93 -3
  53. package/src/compiler/knowledge-version.ts +157 -0
  54. package/src/compiler/lesson-derivation.ts +2 -2
  55. package/src/compiler/manifest.ts +320 -26
  56. package/src/components/CurriculumMap.astro +27 -0
  57. package/src/components/diagram/Diagram.astro +317 -0
  58. package/src/components/examples/CompactExamples.astro +1 -1
  59. package/src/components/figure/Figure.astro +104 -0
  60. package/src/components/notation/NotationLayer.astro +11 -0
  61. package/src/components/notation/render-notation-math.ts +13 -2
  62. package/src/components/reference/FloatRefLayer.astro +671 -0
  63. package/src/components/starlight/AboutPanel.astro +255 -0
  64. package/src/components/starlight/HeadingAnchorEnhancer.astro +3 -3
  65. package/src/components/starlight/KeyedFloatEnhancer.astro +137 -0
  66. package/src/components/starlight/LayoutSidebar.astro +62 -1
  67. package/src/components/starlight/LayoutSocialIcons.astro +12 -0
  68. package/src/components/starlight/LessonFooter.astro +15 -2
  69. package/src/components/table/Table.astro +105 -0
  70. package/src/reference/base-operators.mjs +103 -0
  71. package/src/reference/consistency.ts +83 -2
  72. package/src/reference/diagram-layout.ts +246 -0
  73. package/src/reference/equations-validate.ts +8 -1
  74. package/src/reference/equations.mjs +109 -19
  75. package/src/reference/figures-validate.ts +40 -0
  76. package/src/reference/keyed-float-validate.ts +210 -0
  77. package/src/reference/keyed-jsx-floats.mjs +191 -0
  78. package/src/reference/math-glyphs.mjs +92 -0
  79. package/src/reference/remark-notation.mjs +344 -10
  80. package/src/reference/tables-validate.ts +40 -0
  81. package/src/reference/units-vocab.ts +18 -0
  82. package/src/styles/global.css +244 -7
  83. package/src/components/starlight/EquationEnhancer.astro +0 -87
@@ -6,26 +6,102 @@
6
6
  * data value and passes it here, typed against `CourseConfig`. A second course
7
7
  * supplies its own value of this shape.
8
8
  */
9
- interface CourseSection {
9
+ /**
10
+ * One **part** — a sidebar group, holding the chapters (lessons) whose slugs
11
+ * start with `dir`. A part's 1-based position in `CourseConfig.parts` is the
12
+ * number before the dot in every equation, table, and figure number inside it.
13
+ * See `docs/authoring/structure.md` for the full level taxonomy.
14
+ */
15
+ interface CoursePart {
10
16
  /** Lesson-slug prefix, e.g. `bonds` for `bonds/yield-to-maturity`. */
11
17
  readonly dir: string;
12
18
  /** Sidebar group heading. */
13
19
  readonly label: string;
14
20
  }
21
+ /**
22
+ * @deprecated Renamed to {@link CoursePart}. "Section" now only ever means an
23
+ * `##` heading inside a lesson. Removed in the next MINOR release.
24
+ */
25
+ type CourseSection = CoursePart;
15
26
  interface CourseConventions {
16
27
  /** Notation key a lesson's stated sign convention must resolve to. */
17
28
  readonly signConventionKey: string;
18
29
  /** Competency id that also counts as declaring the cash-flow perspective. */
19
30
  readonly cashFlowPerspectiveCompetency: string;
20
31
  }
32
+ /**
33
+ * A course's edits to the shipped named-operator baseline (`BASE_OPERATORS`),
34
+ * consumed by the `operator-explained` consistency check (rule N8). An operator
35
+ * a lesson uses is fine when it is on the baseline plus `allow` minus `deny`;
36
+ * otherwise it needs a notation entry the reader can hover.
37
+ *
38
+ * Names are the KaTeX command without its backslash (`erf`, not `\erf`) for a
39
+ * built-in, and the verbatim body for an `\operatorname{…}` operator (`Var`).
40
+ */
41
+ interface CourseOperators {
42
+ /** Operators this course also treats as universal (`Var`, `Cov`, `sgn`, …). */
43
+ readonly allow?: readonly string[];
44
+ /** Baseline operators this course wants explained anyway. */
45
+ readonly deny?: readonly string[];
46
+ }
47
+ /** One person or team credited for the study material. */
48
+ interface CourseAuthor {
49
+ readonly name: string;
50
+ /**
51
+ * Email, URL, or handle. Rendered in the About panel as a link when it looks
52
+ * like a `mailto:` or `https:` target, plain text otherwise.
53
+ */
54
+ readonly contact?: string;
55
+ }
56
+ /**
57
+ * The reader-facing "about this material" block, surfaced by the `[?]` panel in
58
+ * the header (`components/starlight/AboutPanel.astro`) on every page.
59
+ *
60
+ * `version` is the course's **knowledge version** — semver over the
61
+ * learning-facing content, independent of the `explico` engine's own version.
62
+ * It must equal the newest entry in `content/CHANGELOG.md`; a mismatch is a
63
+ * Tier-3 `knowledge-version-mismatch` warning (rule C3, `docs/authoring/versioning.md`).
64
+ */
65
+ interface CourseAbout {
66
+ /** Display name of the material. Defaults to {@link CourseConfig.title}. */
67
+ readonly materialName?: string;
68
+ /** One or two sentences on what the material covers. Defaults to {@link CourseConfig.description}. */
69
+ readonly summary?: string;
70
+ /** Knowledge version — semver; equals the latest `content/CHANGELOG.md` heading. */
71
+ readonly version: string;
72
+ /** Who authored / maintains the material. At least one entry. */
73
+ readonly authors: readonly CourseAuthor[];
74
+ /** Shown verbatim in the About panel — scope, warranty, AI-assistance note. */
75
+ readonly disclaimer: string;
76
+ /** Link to the published content changelog (repo blob, docs page, …). */
77
+ readonly changelogHref?: string;
78
+ }
21
79
  interface CourseConfig {
22
80
  readonly title: string;
23
81
  readonly description: string;
24
- /** Sidebar section groups, in order. */
25
- readonly sections: readonly CourseSection[];
82
+ /**
83
+ * The course's parts (sidebar groups), in order. Exactly one of `parts` or
84
+ * the deprecated `sections` must be given.
85
+ */
86
+ readonly parts?: readonly CoursePart[];
87
+ /**
88
+ * @deprecated Renamed to {@link CourseConfig.parts}. Still honoured, so a
89
+ * course on the old name keeps building; removed in the next MINOR release.
90
+ */
91
+ readonly sections?: readonly CoursePart[];
26
92
  /** Namespaces used by competency / notation `domain` fields. */
27
93
  readonly domains: readonly string[];
28
94
  readonly conventions: CourseConventions;
95
+ /** Reader-facing identity + knowledge version (the `[?]` About panel). */
96
+ readonly about: CourseAbout;
97
+ /** Edits to the shipped named-operator baseline (rule N8). Optional. */
98
+ readonly operators?: CourseOperators;
29
99
  }
100
+ /**
101
+ * The course's parts, from whichever field the config uses. Throws rather than
102
+ * silently producing a course with no sidebar groups — a config with neither
103
+ * field would otherwise number every float by the `##`-section fallback.
104
+ */
105
+ declare function coursePartsOf(config: CourseConfig): readonly CoursePart[];
30
106
 
31
- export type { CourseConfig, CourseConventions, CourseSection };
107
+ export { type CourseAbout, type CourseAuthor, type CourseConfig, type CourseConventions, type CourseOperators, type CoursePart, type CourseSection, coursePartsOf };
@@ -0,0 +1,12 @@
1
+ function coursePartsOf(config) {
2
+ const parts = config.parts ?? config.sections;
3
+ if (!parts || parts.length === 0) {
4
+ throw new Error(
5
+ "CourseConfig needs at least one entry in `parts` (a sidebar group: { dir, label }). The old name `sections` is still accepted."
6
+ );
7
+ }
8
+ return parts;
9
+ }
10
+ export {
11
+ coursePartsOf
12
+ };
@@ -0,0 +1,58 @@
1
+ /**
2
+ * The course's **knowledge version** — semver over the learning-facing content,
3
+ * independent of the `explico` engine's own package version.
4
+ *
5
+ * The declared value lives in `content/course.config.ts` (`about.version`); the
6
+ * history and the human-readable notes live in `content/CHANGELOG.md`
7
+ * (Keep a Changelog shape). This module parses that file and checks the two
8
+ * agree — rule C3 in `docs/authoring/versioning.md`. Every finding is a Tier-3
9
+ * warning: `pnpm validate:content` prints it, `manifest.diagnostics.knowledge`
10
+ * records it, and the build still passes. The resolved entry is what the `[?]`
11
+ * About panel publishes to readers (`manifest.about.knowledge`).
12
+ *
13
+ * Pure: `parseKnowledgeChangelog` takes text, `resolveKnowledgeVersion` takes
14
+ * text + the declared version. The caller (`compileManifest`) does the file IO.
15
+ */
16
+ /** A released entry from `content/CHANGELOG.md`, newest first in file order. */
17
+ interface KnowledgeChangelogEntry {
18
+ readonly version: string;
19
+ /** The `- YYYY-MM-DD` part of the heading, if present. */
20
+ readonly date?: string;
21
+ /** Bullet lines under the heading, marker stripped, in file order. */
22
+ readonly notes: readonly string[];
23
+ }
24
+ type KnowledgeVersionCode = 'knowledge-changelog-missing' | 'knowledge-version-invalid' | 'knowledge-version-mismatch';
25
+ interface KnowledgeVersionDiagnostic {
26
+ readonly code: KnowledgeVersionCode;
27
+ /** Tier 3 — a knowledge-version drift never fails the build. */
28
+ readonly severity: 'warning';
29
+ readonly message: string;
30
+ readonly file?: string;
31
+ }
32
+ interface KnowledgeVersion {
33
+ /** The declared `about.version`, echoed for consumers. */
34
+ readonly version: string;
35
+ /** The newest released `## [x.y.z]` entry, when the changelog has one. */
36
+ readonly latest?: KnowledgeChangelogEntry;
37
+ readonly diagnostics: readonly KnowledgeVersionDiagnostic[];
38
+ }
39
+ declare function isKnowledgeSemver(value: string): boolean;
40
+ /**
41
+ * Every released entry in a `content/CHANGELOG.md` body, in file order (newest
42
+ * first, by Keep a Changelog convention). `## [Unreleased]` is skipped.
43
+ */
44
+ declare function parseKnowledgeChangelog(text: string): KnowledgeChangelogEntry[];
45
+ /**
46
+ * Check the declared knowledge version against `content/CHANGELOG.md`.
47
+ *
48
+ * @param options.changelogText the file body, or `undefined` when it is absent
49
+ * @param options.declaredVersion `courseConfig.about.version`
50
+ * @param options.file repo-relative path, for diagnostic messages
51
+ */
52
+ declare function resolveKnowledgeVersion(options: {
53
+ readonly changelogText: string | undefined;
54
+ readonly declaredVersion: string;
55
+ readonly file?: string;
56
+ }): KnowledgeVersion;
57
+
58
+ export { type KnowledgeChangelogEntry, type KnowledgeVersion, type KnowledgeVersionCode, type KnowledgeVersionDiagnostic, isKnowledgeSemver, parseKnowledgeChangelog, resolveKnowledgeVersion };
@@ -0,0 +1,80 @@
1
+ const SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
2
+ const HEADING_RE = /^##\s+\[([^\]]+)\]\s*(?:[-–—]\s*(\S.*?))?\s*$/;
3
+ const BULLET_RE = /^[-*]\s+(.*\S)\s*$/;
4
+ function isKnowledgeSemver(value) {
5
+ return SEMVER_RE.test(value.trim());
6
+ }
7
+ function parseKnowledgeChangelog(text) {
8
+ const lines = text.split(/\r?\n/);
9
+ const entries = [];
10
+ let current = null;
11
+ for (const line of lines) {
12
+ const heading = HEADING_RE.exec(line);
13
+ if (heading) {
14
+ const token = (heading[1] ?? "").trim();
15
+ if (/^unreleased$/i.test(token)) {
16
+ current = null;
17
+ continue;
18
+ }
19
+ current = { version: token, notes: [] };
20
+ const date = (heading[2] ?? "").trim();
21
+ if (date) current.date = date;
22
+ entries.push(current);
23
+ continue;
24
+ }
25
+ if (/^#{1,3}\s/.test(line) && !line.startsWith("## [")) {
26
+ current = null;
27
+ continue;
28
+ }
29
+ if (!current) continue;
30
+ const bullet = BULLET_RE.exec(line);
31
+ if (bullet) current.notes.push(bullet[1] ?? "");
32
+ }
33
+ return entries.map((entry) => ({
34
+ version: entry.version,
35
+ ...entry.date === void 0 ? {} : { date: entry.date },
36
+ notes: [...entry.notes]
37
+ }));
38
+ }
39
+ function resolveKnowledgeVersion(options) {
40
+ const { changelogText, declaredVersion } = options;
41
+ const file = options.file ?? "content/CHANGELOG.md";
42
+ const diagnostics = [];
43
+ const warn = (code, message) => {
44
+ diagnostics.push({ code, severity: "warning", message, file });
45
+ };
46
+ if (!isKnowledgeSemver(declaredVersion)) {
47
+ warn(
48
+ "knowledge-version-invalid",
49
+ `about.version ${JSON.stringify(declaredVersion)} in content/course.config.ts is not a semver \`x.y.z\` string.`
50
+ );
51
+ }
52
+ if (changelogText === void 0) {
53
+ warn(
54
+ "knowledge-changelog-missing",
55
+ `No ${file}. Add a Keep a Changelog file whose newest \`## [x.y.z]\` heading is ${JSON.stringify(declaredVersion)} (rule C1).`
56
+ );
57
+ return { version: declaredVersion, diagnostics };
58
+ }
59
+ const entries = parseKnowledgeChangelog(changelogText);
60
+ const latest = entries[0];
61
+ if (!latest) {
62
+ warn(
63
+ "knowledge-changelog-missing",
64
+ `${file} has no released \`## [x.y.z]\` entry \u2014 only \`## [Unreleased]\`. Cut a release: move it to \`## [${declaredVersion}] - <date>\` (rule C4).`
65
+ );
66
+ return { version: declaredVersion, diagnostics };
67
+ }
68
+ if (isKnowledgeSemver(declaredVersion) && latest.version !== declaredVersion) {
69
+ warn(
70
+ "knowledge-version-mismatch",
71
+ `about.version is ${JSON.stringify(declaredVersion)} but the newest ${file} entry is ${JSON.stringify(latest.version)}. Bump one to match the other (rule C3).`
72
+ );
73
+ }
74
+ return { version: declaredVersion, latest, diagnostics };
75
+ }
76
+ export {
77
+ isKnowledgeSemver,
78
+ parseKnowledgeChangelog,
79
+ resolveKnowledgeVersion
80
+ };
@@ -84,6 +84,6 @@ type SidebarGroup = {
84
84
  * merged track order (ties broken by slug), then the static Reference group.
85
85
  * Replaces seven `autogenerate` blocks that read an authored `sidebar.order`.
86
86
  */
87
- declare function buildSidebar(tracks: readonly TrackLike[], lessonSlugs: readonly string[], sections: readonly SidebarSection[]): SidebarGroup[];
87
+ declare function buildSidebar(tracks: readonly TrackLike[], lessonSlugs: readonly string[], parts: readonly SidebarSection[]): SidebarGroup[];
88
88
 
89
89
  export { type SidebarGroup, type SidebarSection, buildSidebar, citedSourceIds, deriveRequires, deriveSources, isLessonSlug, lessonIdFromSlug, mergeTrackOrder, parseCheckList };
@@ -59,12 +59,12 @@ function mergeTrackOrder(tracks) {
59
59
  }
60
60
  return ordered;
61
61
  }
62
- function buildSidebar(tracks, lessonSlugs, sections) {
62
+ function buildSidebar(tracks, lessonSlugs, parts) {
63
63
  const rank = new Map(
64
64
  mergeTrackOrder(tracks).map((lessonId, index) => [lessonId, index])
65
65
  );
66
66
  const positionOf = (slug) => rank.get(lessonIdFromSlug(slug)) ?? Number.POSITIVE_INFINITY;
67
- const groups = sections.map(({ dir, label }) => ({
67
+ const groups = parts.map(({ dir, label }) => ({
68
68
  label,
69
69
  items: lessonSlugs.filter((slug) => slug.startsWith(`${dir}/`)).sort(
70
70
  (left, right) => positionOf(left) - positionOf(right) || left.localeCompare(right)
@@ -1,13 +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
7
  import { GlyphScopeEntry } from '../reference/gloss.js';
6
8
  import { NotationDefinitionRecord, NotationPageBundle, NotationBacklink, NotationDiagnostic, EditorialStatus, NotationGlossEntry } from '../reference/types.js';
7
9
  import { CourseConfig } from './course-config.js';
10
+ import { KnowledgeVersionDiagnostic } from './knowledge-version.js';
8
11
  import { SidebarGroup } from './lesson-derivation.js';
9
12
  import './collections.js';
10
13
  import '../reference/lint-ignore.js';
14
+ import '../reference/keyed-float-validate.js';
11
15
 
12
16
  /**
13
17
  * The one compiler manifest (Phase D5).
@@ -45,8 +49,20 @@ import '../reference/lint-ignore.js';
45
49
  * `diagnostics.consistency` (Tier-3 corpus checks).
46
50
  * v3 (D7): `equations` (labelled display equations + cross-page number table),
47
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)`).
60
+ * v7 (part numbering): `lessons[].part` (the sidebar group's 1-based ordinal,
61
+ * now the number before the dot), `lessons[].chapter` redefined as the
62
+ * lesson's ordinal *within its part*, and `lessons[].numbering` — the
63
+ * per-kind offsets that make `n` run continuously across a part.
48
64
  */
49
- declare const MANIFEST_SCHEMA_VERSION = 3;
65
+ declare const MANIFEST_SCHEMA_VERSION = 7;
50
66
  declare const MANIFEST_PATH: string;
51
67
  interface ManifestCitation {
52
68
  readonly id: string;
@@ -95,6 +111,29 @@ interface ManifestLesson {
95
111
  readonly id: string;
96
112
  /** Docs-loader slug, e.g. `bonds/price-yield-relationship`. */
97
113
  readonly slug: string;
114
+ /**
115
+ * The 1-based ordinal of the **part** this lesson sits in — its sidebar
116
+ * group's position in `courseConfig.parts`. It is the number before the dot
117
+ * in every equation, table, and figure number on the page: `(2.1)` is the
118
+ * first equation of part 2. See `docs/authoring/structure.md`.
119
+ */
120
+ readonly part: number;
121
+ /**
122
+ * The lesson's 1-based ordinal **within its part** (merged track order, then
123
+ * any unranked lessons by slug) — its chapter number. This orders the
124
+ * sidebar and the reading path; it does not appear in a float's number.
125
+ */
126
+ readonly chapter: number;
127
+ /**
128
+ * How many floats of each kind the earlier lessons of this part already
129
+ * used. The lesson's own floats continue from here, so a part's sequence
130
+ * runs unbroken across its chapters instead of restarting on every page.
131
+ */
132
+ readonly numbering: {
133
+ readonly equations: number;
134
+ readonly tables: number;
135
+ readonly figures: number;
136
+ };
98
137
  readonly file: string;
99
138
  readonly title: string;
100
139
  readonly description: string;
@@ -113,6 +152,16 @@ interface ManifestLesson {
113
152
  readonly key: string;
114
153
  readonly number: string;
115
154
  }[];
155
+ /** Keyed `<Table>` elements in appearance order. */
156
+ readonly tables: readonly {
157
+ readonly key: string;
158
+ readonly number: string;
159
+ }[];
160
+ /** Keyed `<Figure>` elements in appearance order. */
161
+ readonly figures: readonly {
162
+ readonly key: string;
163
+ readonly number: string;
164
+ }[];
116
165
  readonly notation: {
117
166
  readonly bindings: readonly {
118
167
  readonly key: string;
@@ -124,8 +173,39 @@ interface ManifestLesson {
124
173
  readonly resolution: readonly ManifestNotationResolution[];
125
174
  };
126
175
  }
176
+ /** The reader-facing "about this material" bundle — the `[?]` panel's data. */
177
+ interface ManifestAbout {
178
+ /** `explico`'s own identity, from its `package.json` (the version's golden source). */
179
+ readonly engine: {
180
+ readonly name: string;
181
+ readonly version: string;
182
+ readonly homepage?: string;
183
+ };
184
+ readonly material: {
185
+ readonly name: string;
186
+ readonly summary: string;
187
+ };
188
+ /** The course's knowledge version — semver over the content, engine-independent. */
189
+ readonly knowledge: {
190
+ readonly version: string;
191
+ /** Date of the newest `content/CHANGELOG.md` entry, if it has one. */
192
+ readonly date?: string;
193
+ /** Bullet notes from that entry. */
194
+ readonly notes: readonly string[];
195
+ /** Content fingerprint at compile time — ties the shown version to a build. */
196
+ readonly contentTree: string;
197
+ };
198
+ readonly authors: readonly {
199
+ readonly name: string;
200
+ readonly contact?: string;
201
+ }[];
202
+ readonly disclaimer: string;
203
+ readonly changelogHref?: string;
204
+ }
127
205
  interface Manifest {
128
206
  readonly schemaVersion: number;
207
+ /** Reader-facing identity + versions (the `[?]` About panel). */
208
+ readonly about: ManifestAbout;
129
209
  readonly notation: {
130
210
  readonly definitions: readonly NotationDefinitionRecord[];
131
211
  readonly bundles: readonly NotationPageBundle[];
@@ -147,6 +227,16 @@ interface Manifest {
147
227
  /** `slug -> { eqKey: number }` — passed to `remarkNotation` for `[[slug#eq:key]]`. */
148
228
  readonly numbersBySlug: Record<string, Record<string, string>>;
149
229
  };
230
+ /** Keyed-float identity for `<Table>` — same shape as `equations`. */
231
+ readonly tables: {
232
+ readonly labels: readonly TableLabelRecord[];
233
+ readonly numbersBySlug: Record<string, Record<string, string>>;
234
+ };
235
+ /** Keyed-float identity for `<Figure>` — same shape as `equations`. */
236
+ readonly figures: {
237
+ readonly labels: readonly FigureLabelRecord[];
238
+ readonly numbersBySlug: Record<string, Record<string, string>>;
239
+ };
150
240
  readonly diagnostics: {
151
241
  readonly curriculum: readonly CurriculumIssue[];
152
242
  readonly notation: readonly NotationDiagnostic[];
@@ -154,8 +244,14 @@ interface Manifest {
154
244
  readonly math: readonly MathGateDiagnostic[];
155
245
  /** Tier-3 corpus consistency checks (D6). Warnings only. */
156
246
  readonly consistency: readonly ConsistencyDiagnostic[];
247
+ /** Knowledge-version checks (C3): `about.version` vs `content/CHANGELOG.md`. Warnings only. */
248
+ readonly knowledge: readonly KnowledgeVersionDiagnostic[];
157
249
  /** Equation-identity checks (D7): `eq-duplicate-key`, `eq-ref-resolves` (error); `eq-key-unused` (warning). */
158
250
  readonly equations: readonly EquationDiagnostic[];
251
+ /** `<Table>` identity checks (F-tables-figures) — same codes, `tab-` prefixed. */
252
+ readonly tables: readonly TableDiagnostic[];
253
+ /** `<Figure>` identity checks (F-tables-figures) — same codes, `fig-` prefixed. */
254
+ readonly figures: readonly FigureDiagnostic[];
159
255
  };
160
256
  readonly hashes: {
161
257
  /** `sha256` of every content source file, keyed by repo-relative path. */
@@ -184,4 +280,4 @@ declare function readManifestSync(): Manifest;
184
280
  */
185
281
  declare function loadManifest(courseConfig?: CourseConfig): Promise<Manifest>;
186
282
 
187
- export { MANIFEST_PATH, MANIFEST_SCHEMA_VERSION, type Manifest, type ManifestCitation, type ManifestLesson, type ManifestLessonNotation, type ManifestNotationResolution, type PrereqEdge, compileManifest, loadManifest, readManifestSync, serializeManifest, writeManifest };
283
+ export { MANIFEST_PATH, MANIFEST_SCHEMA_VERSION, type Manifest, type ManifestAbout, type ManifestCitation, type ManifestLesson, type ManifestLessonNotation, type ManifestNotationResolution, type PrereqEdge, compileManifest, loadManifest, readManifestSync, serializeManifest, writeManifest };