explico 0.13.0 → 0.14.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 +894 -1
- package/README.md +200 -30
- package/dist/compiler/astro.d.ts +15 -2
- package/dist/compiler/astro.js +40 -11
- package/dist/compiler/cli-main.d.ts +43 -0
- package/dist/compiler/cli-main.js +392 -0
- package/dist/compiler/cli.d.ts +424 -9
- package/dist/compiler/cli.js +1289 -8
- package/dist/compiler/collections.d.ts +88 -1
- package/dist/compiler/collections.js +170 -25
- package/dist/compiler/course-config.d.ts +1 -1
- package/dist/compiler/course-config.js +105 -1
- package/dist/compiler/decks.d.ts +161 -0
- package/dist/compiler/decks.js +235 -0
- package/dist/compiler/exports/combined-html.d.ts +5 -1
- package/dist/compiler/exports/deck-fit.d.ts +55 -0
- package/dist/compiler/exports/deck-fit.js +90 -0
- package/dist/compiler/exports/export-chapters.d.ts +37 -6
- package/dist/compiler/exports/export-chapters.js +64 -5
- package/dist/compiler/exports/export-pages.d.ts +30 -3
- package/dist/compiler/exports/export-pages.js +24 -1
- package/dist/compiler/exports/latex/beamerthemeexplico.sty +124 -0
- package/dist/compiler/exports/latex/cache.d.ts +78 -0
- package/dist/compiler/exports/latex/cache.js +129 -0
- package/dist/compiler/exports/latex/capture.d.ts +10 -1
- package/dist/compiler/exports/latex/capture.js +3 -1
- package/dist/compiler/exports/latex/compile.d.ts +13 -2
- package/dist/compiler/exports/latex/compile.js +12 -1
- package/dist/compiler/exports/latex/deck.d.ts +92 -0
- package/dist/compiler/exports/latex/deck.js +349 -0
- package/dist/compiler/exports/latex/document.d.ts +51 -6
- package/dist/compiler/exports/latex/document.js +71 -12
- package/dist/compiler/exports/latex/explico-common.sty +198 -0
- package/dist/compiler/exports/latex/explico.cls +13 -183
- package/dist/compiler/exports/latex/serialize.d.ts +60 -4
- package/dist/compiler/exports/latex/serialize.js +4 -2
- package/dist/compiler/exports/latex-export.d.ts +24 -2
- package/dist/compiler/exports/latex-export.js +107 -26
- package/dist/compiler/exports/markdown/serialize.d.ts +24 -4
- package/dist/compiler/exports/markdown/serialize.js +85 -8
- package/dist/compiler/exports/markdown-export.d.ts +6 -1
- package/dist/compiler/exports/markdown-export.js +50 -11
- package/dist/compiler/exports/standalone-html.d.ts +10 -5
- package/dist/compiler/exports/standalone-html.js +3 -3
- package/dist/compiler/exports/static-tree.d.ts +38 -1
- package/dist/compiler/exports/static-tree.js +120 -4
- package/dist/compiler/knowledge-version.d.ts +26 -2
- package/dist/compiler/knowledge-version.js +27 -17
- package/dist/compiler/lesson-derivation.d.ts +143 -6
- package/dist/compiler/lesson-derivation.js +145 -13
- package/dist/compiler/manifest.d.ts +133 -4
- package/dist/compiler/manifest.js +359 -58
- package/dist/compiler/page-versions.d.ts +160 -0
- package/dist/compiler/page-versions.js +198 -0
- package/dist/compiler/render-diagnostics.d.ts +5 -1
- package/dist/compiler/resolution-report.d.ts +24 -6
- package/dist/compiler/resolution-report.js +22 -4
- package/dist/compiler/validate-content.d.ts +7 -0
- package/dist/compiler/validate-content.js +160 -0
- package/dist/{course-config-rRtME5UV.d.ts → course-config-BLtIdkei.d.ts} +164 -1
- package/dist/curriculum/competency-graph.d.ts +54 -0
- package/dist/curriculum/competency-graph.js +41 -0
- package/dist/reference/base-library.d.mts +8 -0
- package/dist/reference/base-library.mjs +19 -0
- package/dist/reference/consistency.d.ts +8 -4
- package/dist/reference/consistency.js +76 -26
- package/dist/reference/deck-remark.d.mts +123 -0
- package/dist/reference/deck-remark.mjs +600 -0
- package/dist/reference/deck-source.d.ts +107 -0
- package/dist/reference/deck-source.js +140 -0
- package/dist/reference/decks-validate.d.ts +133 -0
- package/dist/reference/decks-validate.js +359 -0
- package/dist/reference/derived-entries.d.ts +5 -1
- package/dist/reference/diagram-fit.d.ts +1 -1
- package/dist/reference/figure-symbols.d.ts +23 -6
- package/dist/reference/figure-symbols.js +51 -15
- package/dist/reference/float-name.d.ts +43 -0
- package/dist/reference/float-name.js +58 -0
- package/dist/reference/folds-validate.d.ts +1 -1
- package/dist/reference/gate-abbreviations.d.ts +1 -1
- package/dist/reference/gate-indices.js +5 -4
- package/dist/reference/gate-math.d.ts +29 -2
- package/dist/reference/gate-math.js +99 -18
- package/dist/reference/gate-part-pages.d.ts +1 -1
- package/dist/reference/lab-katex-validate.d.ts +10 -6
- package/dist/reference/lab-katex-validate.js +17 -9
- package/dist/reference/lab-math-scope.js +29 -4
- package/dist/reference/math-glyphs.d.mts +25 -0
- package/dist/reference/math-glyphs.mjs +189 -25
- package/dist/reference/page-notation.d.ts +22 -3
- package/dist/reference/page-notation.js +32 -10
- package/dist/reference/references.d.ts +14 -1
- package/dist/reference/references.js +13 -0
- package/dist/reference/registry-scope.d.mts +33 -0
- package/dist/reference/registry-scope.mjs +34 -0
- package/dist/reference/registry.js +29 -10
- package/dist/reference/rehype-deck.d.mts +6 -0
- package/dist/reference/rehype-deck.mjs +286 -0
- package/dist/reference/rehype-heading-anchors.d.mts +20 -0
- package/dist/reference/rehype-heading-anchors.mjs +48 -6
- package/dist/reference/remark-notation.mjs +249 -40
- package/dist/reference/source-lines.d.mts +12 -0
- package/dist/reference/source-lines.mjs +65 -0
- package/dist/reference/types.d.ts +28 -0
- package/dist/rules/catalog.d.ts +21 -4
- package/dist/rules/catalog.js +221 -2
- package/dist/rules/course-rules.d.ts +5 -1
- package/dist/rules/course-rules.js +17 -3
- package/dist/rules/course-rules.schema.json +12 -0
- package/dist/rules/in-effect.d.ts +13 -3
- package/dist/rules/in-effect.js +30 -7
- package/dist/rules/rules.schema.json +259 -1
- package/dist/rules/settings.d.ts +1 -1
- package/dist/session/PreferenceStore.d.ts +6 -0
- package/dist/session/PreferenceStore.js +18 -1
- package/dist/session/deck-memory.d.ts +116 -0
- package/dist/session/deck-memory.js +143 -0
- package/dist/session/reading-memory.d.ts +96 -0
- package/dist/session/reading-memory.js +133 -0
- package/docs/VERSIONING.md +21 -18
- package/docs/authoring/README.md +29 -9
- package/docs/authoring/agents.md +63 -16
- package/docs/authoring/components.md +15 -0
- package/docs/authoring/deck.md +588 -0
- package/docs/authoring/migrating.md +1087 -14
- package/docs/authoring/notation.md +49 -6
- package/docs/authoring/structure.md +80 -1
- package/docs/authoring/versioning.md +54 -2
- package/docs/authoring/visualizations.md +7 -2
- package/docs/course-agent/AGENTS.md +37 -0
- package/docs/course-agent/agents/rules-reviewer.md +33 -0
- package/docs/course-agent/skills/draft-lesson/SKILL.md +37 -0
- package/docs/course-agent/skills/migrate-course/SKILL.md +31 -0
- package/docs/course-agent/skills/release-content/SKILL.md +24 -0
- package/docs/diagram-scaling.md +148 -0
- package/docs/privacy.md +59 -0
- package/package.json +8 -2
- package/src/compiler/astro.ts +60 -21
- package/src/compiler/cli-main.ts +469 -0
- package/src/compiler/cli.ts +1905 -16
- package/src/compiler/collections.ts +284 -25
- package/src/compiler/course-config.ts +267 -0
- package/src/compiler/decks.ts +455 -0
- package/src/compiler/exports/deck-fit.ts +127 -0
- package/src/compiler/exports/export-chapters.ts +105 -6
- package/src/compiler/exports/export-pages.ts +46 -2
- package/src/compiler/exports/latex/beamerthemeexplico.sty +124 -0
- package/src/compiler/exports/latex/cache.ts +183 -0
- package/src/compiler/exports/latex/capture.ts +5 -2
- package/src/compiler/exports/latex/compile.ts +18 -0
- package/src/compiler/exports/latex/deck.ts +419 -0
- package/src/compiler/exports/latex/document.ts +117 -21
- package/src/compiler/exports/latex/explico-common.sty +198 -0
- package/src/compiler/exports/latex/explico.cls +13 -183
- package/src/compiler/exports/latex/serialize.ts +25 -21
- package/src/compiler/exports/latex/texlive-packages.txt +6 -2
- package/src/compiler/exports/latex-export.ts +187 -45
- package/src/compiler/exports/markdown/serialize.ts +118 -9
- package/src/compiler/exports/markdown-export.ts +56 -11
- package/src/compiler/exports/standalone-html.ts +8 -7
- package/src/compiler/exports/static-tree.ts +175 -3
- package/src/compiler/knowledge-version.ts +56 -21
- package/src/compiler/lesson-derivation.ts +282 -16
- package/src/compiler/manifest.ts +558 -63
- package/src/compiler/page-versions.ts +359 -0
- package/src/compiler/resolution-report.ts +38 -7
- package/src/compiler/validate-content.ts +212 -0
- package/src/components/CourseContents.astro +124 -6
- package/src/components/CurriculumMap.astro +49 -1
- package/src/components/FloatIndex.astro +1 -1
- package/src/components/PartOverview.astro +51 -5
- package/src/components/assessments/AssessmentSet.astro +41 -15
- package/src/components/block-heading.ts +15 -6
- package/src/components/card-carry.ts +7 -4
- package/src/components/card-manager.ts +14 -2
- package/src/components/card-memory.ts +13 -9
- package/src/components/deck/DeckEnhancer.astro +13 -0
- package/src/components/deck/deck-context.ts +104 -0
- package/src/components/deck/deck-controller.ts +1287 -0
- package/src/components/deck/deck-map.ts +91 -0
- package/src/components/deck-lane.ts +163 -0
- package/src/components/diagram/Diagram.astro +131 -86
- package/src/components/disclosure/persisted-disclosure.ts +9 -1
- package/src/components/figure/Figure.astro +7 -2
- package/src/components/figure/FloatCaption.astro +5 -3
- package/src/components/fold/Fold.astro +2 -2
- package/src/components/notation/NotationFormula.astro +81 -11
- package/src/components/notation/NotationGlossary.astro +132 -1
- package/src/components/notation/NotationLayer.astro +29 -0
- package/src/components/starlight/AboutPanel.astro +186 -3
- package/src/components/starlight/CollapsibleSections.astro +24 -3
- package/src/components/starlight/DiagramEnhancer.astro +5 -1
- package/src/components/starlight/HeadingAnchorEnhancer.astro +1 -1
- package/src/components/starlight/LayoutHeader.astro +17 -0
- package/src/components/starlight/LayoutPageTitle.astro +110 -0
- package/src/components/starlight/LayoutSidebar.astro +807 -21
- package/src/components/starlight/LayoutSocialIcons.astro +62 -6
- package/src/components/starlight/LessonExportActions.astro +27 -9
- package/src/components/starlight/LessonFooter.astro +59 -0
- package/src/components/starlight/ReadingMemoryNotice.astro +114 -0
- package/src/components/starlight/TextSizeControl.astro +24 -13
- package/src/components/starlight/page-marks.ts +139 -0
- package/src/components/starlight/section-summary.ts +1 -1
- package/src/components/starlight/sidebar-editions.ts +245 -0
- package/src/components/table/Table.astro +15 -7
- package/src/content-config.ts +87 -1
- package/src/curriculum/competency-graph.ts +95 -0
- package/src/reference/base-library.mjs +19 -0
- package/src/reference/consistency.ts +126 -39
- package/src/reference/deck-remark.mjs +600 -0
- package/src/reference/deck-source.ts +273 -0
- package/src/reference/decks-validate.ts +604 -0
- package/src/reference/diagram-layout.ts +3 -1
- package/src/reference/figure-symbols.ts +108 -31
- package/src/reference/folds-validate.ts +1 -1
- package/src/reference/gate-indices.ts +5 -3
- package/src/reference/gate-math.ts +169 -25
- package/src/reference/lab-katex-validate.ts +30 -15
- package/src/reference/lab-math-scope.ts +34 -3
- package/src/reference/math-glyphs.mjs +189 -25
- package/src/reference/page-notation.ts +69 -15
- package/src/reference/references.ts +28 -0
- package/src/reference/registry-scope.mjs +34 -0
- package/src/reference/registry.ts +41 -11
- package/src/reference/rehype-deck.mjs +286 -0
- package/src/reference/rehype-heading-anchors.mjs +48 -6
- package/src/reference/remark-notation.mjs +249 -40
- package/src/reference/source-lines.mjs +65 -0
- package/src/reference/types.ts +28 -0
- package/src/rules/catalog.ts +257 -4
- package/src/rules/course-rules.ts +21 -1
- package/src/rules/in-effect.ts +54 -8
- package/src/session/PreferenceStore.ts +23 -0
- package/src/session/deck-memory.ts +263 -0
- package/src/session/reading-memory.ts +243 -0
- package/src/styles/deck.css +1150 -0
- package/src/styles/global.css +958 -27
- package/src/styles/zones.css +277 -0
- /package/src/{components → reference}/float-name.ts +0 -0
package/src/compiler/manifest.ts
CHANGED
|
@@ -92,6 +92,8 @@ import {
|
|
|
92
92
|
gateContentMath,
|
|
93
93
|
pageScope,
|
|
94
94
|
scopeEntry,
|
|
95
|
+
scopeLocals,
|
|
96
|
+
suspendedOperators,
|
|
95
97
|
type MathGateDiagnostic,
|
|
96
98
|
} from '../reference/gate-math';
|
|
97
99
|
import { gateIndices, type DerivedUse } from '../reference/gate-indices';
|
|
@@ -129,7 +131,7 @@ import { loadLintIgnore } from '../reference/lint-ignore';
|
|
|
129
131
|
import { buildNotationRegistry } from '../reference/registry';
|
|
130
132
|
import { formulaGlyphScope } from '../reference/gloss';
|
|
131
133
|
import type { GlyphScopeEntry } from '../reference/gloss';
|
|
132
|
-
import {
|
|
134
|
+
import { pageScopeLessonIds, resolvePageReferenceId } from '../reference/references';
|
|
133
135
|
import type { NotationGlossEntry } from '../reference/types';
|
|
134
136
|
import type {
|
|
135
137
|
NotationBacklink,
|
|
@@ -142,12 +144,22 @@ import {
|
|
|
142
144
|
coursePartsOf,
|
|
143
145
|
normalizeBlockLabels,
|
|
144
146
|
normalizeCards,
|
|
147
|
+
normalizeSidebarMarks,
|
|
148
|
+
normalizeSidebarEditions,
|
|
149
|
+
normalizeReadingMemory,
|
|
150
|
+
normalizeAppearance,
|
|
145
151
|
normalizeLessonExports,
|
|
146
152
|
normalizeDisclosure,
|
|
147
153
|
normalizeNumbering,
|
|
154
|
+
normalizeDeck,
|
|
148
155
|
type BlockLabels,
|
|
149
156
|
type CourseConfig,
|
|
157
|
+
type NormalizedDeck,
|
|
150
158
|
type NormalizedCards,
|
|
159
|
+
type NormalizedSidebarMarks,
|
|
160
|
+
type NormalizedSidebarEditions,
|
|
161
|
+
type ReadingMemorySetting,
|
|
162
|
+
type NormalizedAppearance,
|
|
151
163
|
type NormalizedLessonExports,
|
|
152
164
|
type NormalizedDisclosure,
|
|
153
165
|
type NormalizedNumbering,
|
|
@@ -170,6 +182,14 @@ import {
|
|
|
170
182
|
resolveKnowledgeVersion,
|
|
171
183
|
type KnowledgeVersionDiagnostic,
|
|
172
184
|
} from './knowledge-version';
|
|
185
|
+
import {
|
|
186
|
+
nextPageVersions,
|
|
187
|
+
PAGE_VERSIONS_FILE,
|
|
188
|
+
pageSourceDigest,
|
|
189
|
+
readPageVersions,
|
|
190
|
+
sinceOf,
|
|
191
|
+
type ManifestPageVersions,
|
|
192
|
+
} from './page-versions';
|
|
173
193
|
import {
|
|
174
194
|
loadAbbreviationRegistryInput,
|
|
175
195
|
loadContentRecords,
|
|
@@ -180,12 +200,23 @@ import {
|
|
|
180
200
|
loadNotationSpecs,
|
|
181
201
|
loadPartOverviews,
|
|
182
202
|
loadSourceRecords,
|
|
203
|
+
loadDeckFiles,
|
|
204
|
+
loadDeckNotationPages,
|
|
205
|
+
loadLessonFiles,
|
|
206
|
+
type LessonDeckFile,
|
|
207
|
+
type PartDeckFile,
|
|
183
208
|
} from './collections';
|
|
209
|
+
import { buildDecks, chapterEquationLatex, type BuiltDeck, type DeckPartFacts } from './decks';
|
|
210
|
+
import { readDeckSource } from '../reference/deck-source';
|
|
211
|
+
import { coveredSections, type ChapterFloats } from '../reference/decks-validate';
|
|
184
212
|
import {
|
|
185
213
|
buildSidebar,
|
|
186
214
|
deriveSources,
|
|
187
215
|
isLessonSlug,
|
|
188
216
|
lessonIdFromSlug,
|
|
217
|
+
mergeTrackOrder,
|
|
218
|
+
sidebarLessonSlug,
|
|
219
|
+
withPartDeckFrames,
|
|
189
220
|
type SidebarGroup,
|
|
190
221
|
} from './lesson-derivation';
|
|
191
222
|
|
|
@@ -346,8 +377,65 @@ import {
|
|
|
346
377
|
* `formulaTemplate` when its entry sets them, and a bundle definition its
|
|
347
378
|
* `range`. `diagnostics.math` carries `index-explained`, `index-swallowed`,
|
|
348
379
|
* `index-range` and `derived-key-taken`.
|
|
380
|
+
*
|
|
381
|
+
* v32 (a page's own version, rule C5): `lessons[].since` and
|
|
382
|
+
* `parts[].overview.since` carry the knowledge version each chapter's and
|
|
383
|
+
* part page's content last changed in (`null` for a change not yet
|
|
384
|
+
* released), read from the committed `content/versions.json` through
|
|
385
|
+
* `pageVersions` — the record as `validate:content` writes it.
|
|
386
|
+
* `about.knowledge` gains `releases` (every released entry with its date)
|
|
387
|
+
* and `unreleased` (whether `## [Unreleased]` holds bullets).
|
|
388
|
+
* `diagnostics.knowledge` can carry `page-versions-invalid`.
|
|
389
|
+
* `sidebarMarks` carries the course's `sidebarMarks` setting, defaults
|
|
390
|
+
* applied: whether the sidebar marks a page the reader has not opened yet,
|
|
391
|
+
* and one whose content changed since they last did. `readingMemory` says
|
|
392
|
+
* whether the course asks the reader to keep that memory (`ask`) or never
|
|
393
|
+
* keeps it (`off`).
|
|
394
|
+
* v33 (decks, additive): `decks` lists each lesson's slide edition
|
|
395
|
+
* (`<lesson>.deck.mdx`) with its frames, numbered as the deck shows them;
|
|
396
|
+
* `deck` carries the course's `deck` settings, defaults applied, and the
|
|
397
|
+
* canvas; `lessons[].editions` says which editions a lesson has
|
|
398
|
+
* (`chapter`, `deck`). A slides-first lesson (a deck with no chapter) is a
|
|
399
|
+
* lesson like any other, with `editions: ["deck"]`. `decks[].since` is the
|
|
400
|
+
* version a deck's page last changed in (rule C5), as a chapter's `since`.
|
|
401
|
+
* In `sidebar`, a deck beside its chapter follows the chapter's slug as
|
|
402
|
+
* `{ slug, label: "Slides", attrs: { class: "explico-deck-row" } }`, and a
|
|
403
|
+
* slides-first lesson is `{ slug, attrs: { class: "explico-deck-lesson" } }`
|
|
404
|
+
* in place of its slug (`sidebarLessonSlug` reads either kind of lesson).
|
|
405
|
+
*
|
|
406
|
+
* v34 (editions in the sidebar): `sidebarEditions` carries the course's
|
|
407
|
+
* `sidebarEditions` setting, defaults applied: the edition a lesson's row
|
|
408
|
+
* opens (`lead`: `chapter` or `slides`) and whether the row lists the other
|
|
409
|
+
* (`other`: `listed` or `hidden`). In `sidebar`, every item of a lesson
|
|
410
|
+
* carries `data-edition` (`chapter` or `slides`), `data-lesson` (the
|
|
411
|
+
* lesson's slug) and `data-title` (its title) in `attrs`, so a chapter is
|
|
412
|
+
* `{ slug, attrs }` where it was a bare slug. An item lists its lesson when
|
|
413
|
+
* it is served at the lesson's own URL, `data-lesson` equal to its `slug`:
|
|
414
|
+
* a chapter, or a slides-first lesson's deck; a chapter's slides row
|
|
415
|
+
* follows it as before.
|
|
416
|
+
*
|
|
417
|
+
* v35 (the slides a reader has seen): every frame of `decks[].frames`
|
|
418
|
+
* carries `id`, 12 hex digits of a SHA-256 over what it shows, which a
|
|
419
|
+
* reader's record of the frames shown in Present mode keeps
|
|
420
|
+
* (`ManifestDeckFrame.id`).
|
|
421
|
+
*
|
|
422
|
+
* v36 (a part's deck): `decks` lists each part's deck
|
|
423
|
+
* (`<part>/index.deck.mdx`, at `<part>/slides`) too, with `part` (its
|
|
424
|
+
* directory) and no `lessonId`, which is now optional. Every deck carries
|
|
425
|
+
* `covers`, the chapters a part's deck covers with the frame each one's
|
|
426
|
+
* section opens on (empty on a lesson's deck); a part's deck carries
|
|
427
|
+
* `recalledFrom`, the chapter of each float it recalls, and each frame of
|
|
428
|
+
* a covered section `chapter`. In `sidebar`, a part's deck follows the
|
|
429
|
+
* part's Overview as `{ slug, label: "Slides", attrs: { class:
|
|
430
|
+
* "explico-deck-row explico-part-deck", "data-edition": "slides",
|
|
431
|
+
* "data-part" } }`, and a chapter it covers that has no deck of its own
|
|
432
|
+
* carries `data-deck-href`, the address of its section.
|
|
433
|
+
*
|
|
434
|
+
* v37 (the page's zones and slide edges): `appearance` carries the course's
|
|
435
|
+
* `appearance` setting, defaults applied: `zones` (`tonal`, `accent-header`,
|
|
436
|
+
* `paper` or `none`) and `slides` (`lifted`, `edge` or `sorter`).
|
|
349
437
|
*/
|
|
350
|
-
export const MANIFEST_SCHEMA_VERSION =
|
|
438
|
+
export const MANIFEST_SCHEMA_VERSION = 37;
|
|
351
439
|
|
|
352
440
|
const REPOSITORY_ROOT = process.cwd();
|
|
353
441
|
// Content is the top-level `content/` folder; the build output is `build/`
|
|
@@ -453,6 +541,17 @@ export interface ManifestDerivedNotation {
|
|
|
453
541
|
readonly formulaScope: readonly GlyphScopeEntry[];
|
|
454
542
|
}
|
|
455
543
|
|
|
544
|
+
/**
|
|
545
|
+
* One lesson's deck (v33), or a part's (v36): its frames as `decks.ts` reads
|
|
546
|
+
* them, and the notation its page shows — its own bundle, which holds its
|
|
547
|
+
* chapters'.
|
|
548
|
+
*/
|
|
549
|
+
export interface ManifestDeck extends BuiltDeck {
|
|
550
|
+
readonly notation: ManifestLesson['notation'];
|
|
551
|
+
/** The version the deck's page last changed in (rule C5), as a chapter's `since`. */
|
|
552
|
+
readonly since: string | null;
|
|
553
|
+
}
|
|
554
|
+
|
|
456
555
|
/**
|
|
457
556
|
* One keyed float of a lesson (v21): its key and number, and its caption pair
|
|
458
557
|
* — `short` (an equation's `\caption{…}`, or the `short` prop) and, for a
|
|
@@ -484,6 +583,12 @@ export interface ManifestLesson {
|
|
|
484
583
|
readonly id: string;
|
|
485
584
|
/** Docs-loader slug, e.g. `bonds/price-yield-relationship`. */
|
|
486
585
|
readonly slug: string;
|
|
586
|
+
/**
|
|
587
|
+
* The lesson's editions (v33): `chapter` (its `.mdx`), `deck` (its
|
|
588
|
+
* `.deck.mdx`, listed in `manifest.decks`), or both. A slides-first lesson
|
|
589
|
+
* is `["deck"]`: its slug is its deck's route and its floats are unnumbered.
|
|
590
|
+
*/
|
|
591
|
+
readonly editions: readonly ('chapter' | 'deck')[];
|
|
487
592
|
/**
|
|
488
593
|
* The 1-based ordinal of the **part** this lesson sits in — its sidebar
|
|
489
594
|
* group's position in `courseConfig.parts`. It is the first number of every
|
|
@@ -532,6 +637,15 @@ export interface ManifestLesson {
|
|
|
532
637
|
readonly file: string;
|
|
533
638
|
readonly title: string;
|
|
534
639
|
readonly description: string;
|
|
640
|
+
/**
|
|
641
|
+
* The knowledge version this chapter's content last changed in (v32, rule
|
|
642
|
+
* C5): its file, its checks and assessments, the shared notation it uses,
|
|
643
|
+
* the sources it cites and its numbers, against `content/versions.json`.
|
|
644
|
+
* `null` while it has a change `## [Unreleased]` has not released. What
|
|
645
|
+
* the exports and the `[?]` panel print for the page; with rule C5 off,
|
|
646
|
+
* the course's own version.
|
|
647
|
+
*/
|
|
648
|
+
readonly since: string | null;
|
|
535
649
|
/** Authored reader-facing estimate; `40` and `[30, 40]` both land here. */
|
|
536
650
|
readonly studyMinutes?: { readonly min: number; readonly max: number };
|
|
537
651
|
/** Ordered — the curriculum contract. */
|
|
@@ -614,6 +728,13 @@ export interface ManifestAbout {
|
|
|
614
728
|
readonly date?: string;
|
|
615
729
|
/** Bullet notes from that entry. */
|
|
616
730
|
readonly notes: readonly string[];
|
|
731
|
+
/**
|
|
732
|
+
* Every released entry, newest first, with its date (v32): what dates a
|
|
733
|
+
* document whose content last changed in an earlier release (rule C5).
|
|
734
|
+
*/
|
|
735
|
+
readonly releases: readonly { readonly version: string; readonly date?: string }[];
|
|
736
|
+
/** `## [Unreleased]` holds bullets: a change is waiting for a release (v32). */
|
|
737
|
+
readonly unreleased: boolean;
|
|
617
738
|
/** Content fingerprint at compile time — ties the shown version to a build. */
|
|
618
739
|
readonly contentTree: string;
|
|
619
740
|
};
|
|
@@ -677,6 +798,8 @@ export interface ManifestPart {
|
|
|
677
798
|
readonly title: string;
|
|
678
799
|
readonly file: string;
|
|
679
800
|
readonly description?: string;
|
|
801
|
+
/** The version the page's content last changed in (v32, rule C5), as a chapter's `since`. */
|
|
802
|
+
readonly since: string | null;
|
|
680
803
|
};
|
|
681
804
|
}
|
|
682
805
|
|
|
@@ -692,6 +815,30 @@ export interface Manifest {
|
|
|
692
815
|
readonly blockLabels: BlockLabels;
|
|
693
816
|
/** How the explanation cards start for a reader, defaults applied (`CourseConfig.cards`). */
|
|
694
817
|
readonly cards: NormalizedCards;
|
|
818
|
+
/**
|
|
819
|
+
* Which marks the sidebar shows a reader beside a chapter or part page
|
|
820
|
+
* (v32, `CourseConfig.sidebarMarks`): not opened yet, changed since opened.
|
|
821
|
+
*/
|
|
822
|
+
readonly sidebarMarks: NormalizedSidebarMarks;
|
|
823
|
+
/**
|
|
824
|
+
* Which edition a lesson's sidebar row opens for a reader who has not
|
|
825
|
+
* chosen, and whether it lists the other (v34, `CourseConfig.sidebarEditions`).
|
|
826
|
+
*/
|
|
827
|
+
readonly sidebarEditions: NormalizedSidebarEditions;
|
|
828
|
+
/**
|
|
829
|
+
* Whether the course asks the reader to keep their reading memory (v32,
|
|
830
|
+
* `CourseConfig.readingMemory`): `ask` or `off`.
|
|
831
|
+
*/
|
|
832
|
+
readonly readingMemory: ReadingMemorySetting;
|
|
833
|
+
/**
|
|
834
|
+
* How the page tells its zones apart and how slides sit on it, defaults
|
|
835
|
+
* applied (v37, `CourseConfig.appearance`).
|
|
836
|
+
*/
|
|
837
|
+
readonly appearance: NormalizedAppearance;
|
|
838
|
+
/** The course's `deck` settings, defaults applied, and its canvas (v33). */
|
|
839
|
+
readonly deck: NormalizedDeck;
|
|
840
|
+
/** Each lesson's deck, by route (v33). */
|
|
841
|
+
readonly decks: readonly ManifestDeck[];
|
|
695
842
|
/** Reader-facing identity + versions (the `[?]` About panel). */
|
|
696
843
|
readonly about: ManifestAbout;
|
|
697
844
|
readonly notation: {
|
|
@@ -711,6 +858,13 @@ export interface Manifest {
|
|
|
711
858
|
/** The course's parts in config order, with their chapters and overview pages. */
|
|
712
859
|
readonly parts: readonly ManifestPart[];
|
|
713
860
|
readonly lessons: readonly ManifestLesson[];
|
|
861
|
+
/**
|
|
862
|
+
* The record of every chapter's and part page's own version (v32, rule
|
|
863
|
+
* C5), as `validate:content` writes it to `content/versions.json`
|
|
864
|
+
* (`syncPageVersions`); `lessons[].since` and `parts[].overview.since`
|
|
865
|
+
* are read from it.
|
|
866
|
+
*/
|
|
867
|
+
readonly pageVersions: ManifestPageVersions;
|
|
714
868
|
/** Every docs page that is not a chapter, by id (v25). */
|
|
715
869
|
readonly pages: readonly ManifestPage[];
|
|
716
870
|
/** Full competency prerequisite graph. */
|
|
@@ -872,11 +1026,12 @@ function lessonNotationResolution(
|
|
|
872
1026
|
definitionsById: ReadonlyMap<string, NotationDefinitionRecord>,
|
|
873
1027
|
availableDefinitionIds: ReadonlySet<string>,
|
|
874
1028
|
): ManifestNotationResolution[] {
|
|
875
|
-
|
|
1029
|
+
// A deck's references resolve in its lessons' scopes, as the registry resolves them.
|
|
1030
|
+
const scopes = pageScopeLessonIds(lesson);
|
|
876
1031
|
return lesson.references.map((reference) => {
|
|
877
|
-
const definitionId =
|
|
1032
|
+
const definitionId = resolvePageReferenceId(
|
|
878
1033
|
reference.key,
|
|
879
|
-
|
|
1034
|
+
scopes,
|
|
880
1035
|
availableDefinitionIds,
|
|
881
1036
|
);
|
|
882
1037
|
const definition =
|
|
@@ -982,7 +1137,6 @@ function lessonFrontmatterById(): Map<
|
|
|
982
1137
|
studyMinutes?: { min: number; max: number };
|
|
983
1138
|
}
|
|
984
1139
|
> {
|
|
985
|
-
const base = join(CONTENT_ROOT, 'docs');
|
|
986
1140
|
const result = new Map<
|
|
987
1141
|
string,
|
|
988
1142
|
{
|
|
@@ -991,29 +1145,18 @@ function lessonFrontmatterById(): Map<
|
|
|
991
1145
|
studyMinutes?: { min: number; max: number };
|
|
992
1146
|
}
|
|
993
1147
|
>();
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
{}) as Record<string, unknown>;
|
|
1007
|
-
const studyMinutes = studyMinutesRange(data.studyMinutes);
|
|
1008
|
-
result.set(id, {
|
|
1009
|
-
title: typeof data.title === 'string' ? data.title : id,
|
|
1010
|
-
description:
|
|
1011
|
-
typeof data.description === 'string' ? data.description : '',
|
|
1012
|
-
...(studyMinutes === undefined ? {} : { studyMinutes }),
|
|
1013
|
-
});
|
|
1014
|
-
}
|
|
1015
|
-
};
|
|
1016
|
-
walk(base);
|
|
1148
|
+
// Every lesson's defining file: its chapter, or the deck of a slides-first lesson.
|
|
1149
|
+
for (const { path, lessonId: id } of loadLessonFiles()) {
|
|
1150
|
+
const data = (parseFrontmatter(readFileSync(path, 'utf8')).frontmatter ??
|
|
1151
|
+
{}) as Record<string, unknown>;
|
|
1152
|
+
const studyMinutes = studyMinutesRange(data.studyMinutes);
|
|
1153
|
+
result.set(id, {
|
|
1154
|
+
title: typeof data.title === 'string' ? data.title : id,
|
|
1155
|
+
description:
|
|
1156
|
+
typeof data.description === 'string' ? data.description : '',
|
|
1157
|
+
...(studyMinutes === undefined ? {} : { studyMinutes }),
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1017
1160
|
return result;
|
|
1018
1161
|
}
|
|
1019
1162
|
|
|
@@ -1050,6 +1193,7 @@ export async function compileManifest(
|
|
|
1050
1193
|
// Throws on a bad `numbering` or `blockLabels` value, like `resolveRules` above.
|
|
1051
1194
|
const numberingSettings = normalizeNumbering(courseConfig);
|
|
1052
1195
|
const blockLabels = normalizeBlockLabels(courseConfig);
|
|
1196
|
+
const deckSettings = normalizeDeck(courseConfig);
|
|
1053
1197
|
|
|
1054
1198
|
const [catalog, notationInput, abbreviationsInput] = await Promise.all([
|
|
1055
1199
|
loadCurriculumCatalog(),
|
|
@@ -1062,11 +1206,41 @@ export async function compileManifest(
|
|
|
1062
1206
|
// text and math pass the same gates. It stays out of `notationInput.lessons`,
|
|
1063
1207
|
// which is the chapter list every numbering and curriculum pass reads.
|
|
1064
1208
|
const nonChapterPages = loadNonChapterPages();
|
|
1209
|
+
// A deck beside its chapter is a page of its own with its chapter's scope
|
|
1210
|
+
// (`loadDeckNotationPages`), and a part's deck one with its chapters': its
|
|
1211
|
+
// text and math pass the same gates. A slides-first lesson's deck is the
|
|
1212
|
+
// lesson itself, already in `notationInput.lessons`; it numbers nothing,
|
|
1213
|
+
// so the chapter-only passes below (`chapterInputs`) leave it out.
|
|
1214
|
+
const deckFiles = loadDeckFiles();
|
|
1215
|
+
const lessonDeckFiles = deckFiles.filter((deck): deck is LessonDeckFile => deck.scope === 'lesson');
|
|
1216
|
+
// A part's lessons in the order the tracks read them, as the sidebar
|
|
1217
|
+
// lists them: a part's deck binds a key its chapters define to the first.
|
|
1218
|
+
const trackRank = new Map(
|
|
1219
|
+
mergeTrackOrder(catalog.tracks.map((track) => ({ id: track.id, lessons: track.lessons }))).map(
|
|
1220
|
+
(lessonId, index) => [lessonId, index],
|
|
1221
|
+
),
|
|
1222
|
+
);
|
|
1223
|
+
const readingOrder = (lessonIds: readonly string[]): string[] =>
|
|
1224
|
+
[...lessonIds].sort(
|
|
1225
|
+
(left, right) =>
|
|
1226
|
+
(trackRank.get(left) ?? Number.POSITIVE_INFINITY) - (trackRank.get(right) ?? Number.POSITIVE_INFINITY) ||
|
|
1227
|
+
left.localeCompare(right),
|
|
1228
|
+
);
|
|
1229
|
+
const deckPages = loadDeckNotationPages(deckFiles, { order: readingOrder });
|
|
1230
|
+
const deckLessonIds = new Set(
|
|
1231
|
+
loadLessonFiles()
|
|
1232
|
+
.filter((file) => file.edition === 'deck')
|
|
1233
|
+
.map((file) => file.lessonId),
|
|
1234
|
+
);
|
|
1235
|
+
const isChapter = (lessonId: string): boolean => !deckLessonIds.has(lessonId);
|
|
1236
|
+
const deckPageIds = new Set(deckPages.map((page) => page.notation.lessonId));
|
|
1237
|
+
const chapterInputs = notationInput.lessons.filter((lesson) => isChapter(lesson.lessonId));
|
|
1065
1238
|
const withPages: typeof notationInput = {
|
|
1066
1239
|
...notationInput,
|
|
1067
1240
|
lessons: [
|
|
1068
1241
|
...notationInput.lessons,
|
|
1069
1242
|
...nonChapterPages.map((page) => page.notation),
|
|
1243
|
+
...deckPages.map((page) => page.notation),
|
|
1070
1244
|
],
|
|
1071
1245
|
};
|
|
1072
1246
|
const registry = buildNotationRegistry(withPages);
|
|
@@ -1120,9 +1294,9 @@ export async function compileManifest(
|
|
|
1120
1294
|
notationInput.sharedDefinitions.map((definition) => [definition.key, definition.label]),
|
|
1121
1295
|
);
|
|
1122
1296
|
const localLabelsByLesson = new Map(
|
|
1123
|
-
|
|
1297
|
+
withPages.lessons.map((lesson) => [
|
|
1124
1298
|
lesson.lessonId,
|
|
1125
|
-
new Map(lesson.
|
|
1299
|
+
new Map(scopeLocals(lesson).map((definition) => [definition.key, definition.label])),
|
|
1126
1300
|
]),
|
|
1127
1301
|
);
|
|
1128
1302
|
const termLabel = (lessonId: string, key: string): string | undefined =>
|
|
@@ -1178,19 +1352,27 @@ export async function compileManifest(
|
|
|
1178
1352
|
})
|
|
1179
1353
|
: []),
|
|
1180
1354
|
...(rules.isEnabled('S1')
|
|
1181
|
-
? gateHeadingLength(
|
|
1355
|
+
? gateHeadingLength(chapterInputs, {
|
|
1182
1356
|
maxLength: rules.number('S1', 'maxLength'),
|
|
1183
1357
|
labelFor: termLabel,
|
|
1184
1358
|
})
|
|
1185
1359
|
: []),
|
|
1186
|
-
...gateHeadingStructure(
|
|
1360
|
+
...gateHeadingStructure(chapterInputs, {
|
|
1187
1361
|
levels: rules.isEnabled('S6'),
|
|
1188
1362
|
adjacency: rules.isEnabled('S7'),
|
|
1189
1363
|
}),
|
|
1190
1364
|
// The course's own rules (`courseRules`): one pass for every enabled `X`
|
|
1191
1365
|
// rule that carries a `forbid` pattern. A disabled one, and a guide rule
|
|
1192
1366
|
// with no pattern, do no work here.
|
|
1193
|
-
|
|
1367
|
+
// A course rule about one edition is checked on that edition's pages.
|
|
1368
|
+
...gateCourseRules(
|
|
1369
|
+
withPages.lessons.filter((lesson) => isChapter(lesson.lessonId) && !deckPageIds.has(lesson.lessonId)),
|
|
1370
|
+
rules.course.filter((rule) => rule.editions !== 'deck'),
|
|
1371
|
+
),
|
|
1372
|
+
...gateCourseRules(
|
|
1373
|
+
withPages.lessons.filter((lesson) => !isChapter(lesson.lessonId) || deckPageIds.has(lesson.lessonId)),
|
|
1374
|
+
rules.course.filter((rule) => rule.editions !== 'chapter'),
|
|
1375
|
+
),
|
|
1194
1376
|
// S10 is locked: a float on a non-chapter page has no number to render.
|
|
1195
1377
|
...gateNonChapterPages(nonChapterPages),
|
|
1196
1378
|
];
|
|
@@ -1311,6 +1493,17 @@ export async function compileManifest(
|
|
|
1311
1493
|
'is left out of the sidebar and the numbering until it has one.',
|
|
1312
1494
|
});
|
|
1313
1495
|
}
|
|
1496
|
+
// A part's deck, of a part the sidebar lists: one in a directory no part
|
|
1497
|
+
// lists is `deck-outside-part` (`decks-validate.ts`).
|
|
1498
|
+
const configuredDirs = new Set(configuredParts.map((part) => part.dir));
|
|
1499
|
+
const partDeckFiles = deckFiles.filter(
|
|
1500
|
+
(deck): deck is PartDeckFile => deck.scope === 'part' && configuredDirs.has(deck.partDir),
|
|
1501
|
+
);
|
|
1502
|
+
/** A part's chapters, by their names in its directory (what `[chapter: <name>]` writes). */
|
|
1503
|
+
const partChapterNames = (dir: string): string[] =>
|
|
1504
|
+
lessonSlugs
|
|
1505
|
+
.filter((slug) => slug.startsWith(`${dir}/`) && isChapter(lessonIdFromSlug(slug)))
|
|
1506
|
+
.map((slug) => slug.slice(dir.length + 1));
|
|
1314
1507
|
// A lesson whose directory no part lists has no part, no chapter number and
|
|
1315
1508
|
// no place in the sidebar.
|
|
1316
1509
|
const partDirs = new Set(listedParts.map((part) => part.dir));
|
|
@@ -1339,21 +1532,6 @@ export async function compileManifest(
|
|
|
1339
1532
|
),
|
|
1340
1533
|
);
|
|
1341
1534
|
}
|
|
1342
|
-
// Per-place waivers: drop the located findings each one covers, and
|
|
1343
|
-
// report the waivers themselves — a stale one is a warning, a bad one an
|
|
1344
|
-
// error. Chapters and non-chapter pages are scanned alike, since the text
|
|
1345
|
-
// and math gates run on both; S2 and S10 are per-file or locked, so no
|
|
1346
|
-
// waiver reaches them.
|
|
1347
|
-
const waiverDiagnostics: WaiverDiagnostic[] = [];
|
|
1348
|
-
const waiverRules = { courseRules: rules.course };
|
|
1349
|
-
const waivers = withPages.lessons.flatMap((lesson) => {
|
|
1350
|
-
const collected = collectWaivers(lesson, waiverRules);
|
|
1351
|
-
waiverDiagnostics.push(...collected.diagnostics);
|
|
1352
|
-
return collected.waivers;
|
|
1353
|
-
});
|
|
1354
|
-
const waived = applyWaivers(mathIssues, waivers, waiverRules);
|
|
1355
|
-
mathIssues.splice(0, mathIssues.length, ...waived.kept);
|
|
1356
|
-
waiverDiagnostics.push(...waived.unused);
|
|
1357
1535
|
|
|
1358
1536
|
const sidebar = buildSidebar(
|
|
1359
1537
|
catalog.tracks.map((track) => ({ id: track.id, lessons: track.lessons })),
|
|
@@ -1368,6 +1546,34 @@ export async function compileManifest(
|
|
|
1368
1546
|
floatIndex: ['.mdx', '.md'].some((extension) =>
|
|
1369
1547
|
existsSync(join(CONTENT_ROOT, 'docs', `figures-and-tables${extension}`)),
|
|
1370
1548
|
),
|
|
1549
|
+
// A deck beside its chapter is listed right under it.
|
|
1550
|
+
decks: new Map(
|
|
1551
|
+
lessonDeckFiles
|
|
1552
|
+
.filter((deck) => deck.chapterPath !== undefined && deck.inPart)
|
|
1553
|
+
.map((deck) => [deck.lessonSlug, deck.route]),
|
|
1554
|
+
),
|
|
1555
|
+
// A deck with no chapter is the lesson: its row carries the deck's glyph.
|
|
1556
|
+
slidesFirst: new Set(
|
|
1557
|
+
lessonDeckFiles
|
|
1558
|
+
.filter((deck) => deck.chapterPath === undefined && deck.inPart)
|
|
1559
|
+
.map((deck) => deck.lessonSlug),
|
|
1560
|
+
),
|
|
1561
|
+
// A part's deck follows the part's Overview; a chapter it covers that
|
|
1562
|
+
// has no deck of its own links into it, at its section's divider.
|
|
1563
|
+
partDecks: new Map(
|
|
1564
|
+
partDeckFiles.map((deck) => {
|
|
1565
|
+
const { covered } = coveredSections(readDeckSource(deck.body), new Set(partChapterNames(deck.partDir)));
|
|
1566
|
+
return [
|
|
1567
|
+
deck.partDir,
|
|
1568
|
+
{
|
|
1569
|
+
route: deck.route,
|
|
1570
|
+
covers: new Map(covered.map(({ name, position }) => [`${deck.partDir}/${name}`, position + 2])),
|
|
1571
|
+
},
|
|
1572
|
+
];
|
|
1573
|
+
}),
|
|
1574
|
+
),
|
|
1575
|
+
// Each edition's row names its lesson, which the sidebar's own row shows.
|
|
1576
|
+
titleOf: (slug) => frontmatterById.get(lessonIdFromSlug(slug))?.title,
|
|
1371
1577
|
},
|
|
1372
1578
|
);
|
|
1373
1579
|
|
|
@@ -1397,9 +1603,10 @@ export async function compileManifest(
|
|
|
1397
1603
|
// after them is no part. An overview-only part counts, as `parts[]` does.
|
|
1398
1604
|
for (const group of sidebar.slice(0, configuredParts.length)) {
|
|
1399
1605
|
partCursor += 1;
|
|
1400
|
-
const lessonIds = group.items
|
|
1401
|
-
|
|
1402
|
-
|
|
1606
|
+
const lessonIds = group.items.flatMap((item) => {
|
|
1607
|
+
const slug = sidebarLessonSlug(item);
|
|
1608
|
+
return slug === undefined ? [] : [lessonIdFromSlug(slug)];
|
|
1609
|
+
});
|
|
1403
1610
|
if (lessonIds.length === 0) continue;
|
|
1404
1611
|
// Running totals for this part, advanced one chapter at a time; they
|
|
1405
1612
|
// only reach a number under the part scope.
|
|
@@ -1413,7 +1620,8 @@ export async function compileManifest(
|
|
|
1413
1620
|
};
|
|
1414
1621
|
let chapterCursor = 0;
|
|
1415
1622
|
for (const lessonId of lessonIds) {
|
|
1416
|
-
|
|
1623
|
+
// A slides-first lesson's floats are unnumbered (rule D8): it adds none.
|
|
1624
|
+
const body = isChapter(lessonId) ? (bodyByLessonId.get(lessonId) ?? '') : '';
|
|
1417
1625
|
const chapter = ++chapterCursor;
|
|
1418
1626
|
placementByLessonId.set(lessonId, {
|
|
1419
1627
|
part: partCursor,
|
|
@@ -1448,7 +1656,8 @@ export async function compileManifest(
|
|
|
1448
1656
|
return [
|
|
1449
1657
|
lesson.lessonId,
|
|
1450
1658
|
scanSectionHeadings(lesson.body, {
|
|
1451
|
-
|
|
1659
|
+
// A deck's headings are sections and frames, numbered as frames.
|
|
1660
|
+
...(placement && isChapter(lesson.lessonId)
|
|
1452
1661
|
? { prefix: `${placement.part}.${placement.chapter}` }
|
|
1453
1662
|
: {}),
|
|
1454
1663
|
depth: numberingSettings.sections.depth,
|
|
@@ -1475,7 +1684,11 @@ export async function compileManifest(
|
|
|
1475
1684
|
// The parts themselves, in config order, with their chapters in reading
|
|
1476
1685
|
// order — the one place a part-level page or a breadcrumb reads, so neither
|
|
1477
1686
|
// has to re-derive a part from lesson slugs.
|
|
1478
|
-
|
|
1687
|
+
// A part's overview page gets its `since` with the lessons, below.
|
|
1688
|
+
type PartBeforeVersions = Omit<ManifestPart, 'overview'> & {
|
|
1689
|
+
readonly overview?: Omit<NonNullable<ManifestPart['overview']>, 'since'>;
|
|
1690
|
+
};
|
|
1691
|
+
const manifestParts: PartBeforeVersions[] = configuredParts.map((part, index) => {
|
|
1479
1692
|
const overview = overviewByDir.get(part.dir);
|
|
1480
1693
|
const lessonIds = [...placementByLessonId]
|
|
1481
1694
|
.filter(([lessonId]) => lessonId.startsWith(`${part.dir}.`))
|
|
@@ -1520,7 +1733,7 @@ export async function compileManifest(
|
|
|
1520
1733
|
const floatInputs = (
|
|
1521
1734
|
kind: 'equations' | 'tables' | 'figures' | 'diagrams' | 'examples',
|
|
1522
1735
|
) =>
|
|
1523
|
-
|
|
1736
|
+
chapterInputs.map((lesson) => {
|
|
1524
1737
|
const placement = placementByLessonId.get(lesson.lessonId);
|
|
1525
1738
|
return {
|
|
1526
1739
|
lessonId: lesson.lessonId,
|
|
@@ -1658,6 +1871,8 @@ export async function compileManifest(
|
|
|
1658
1871
|
// `<AssessmentSet />` draws every check of the lesson, or the one its `id`
|
|
1659
1872
|
// names; `<CompactExamples>` draws its own set.
|
|
1660
1873
|
const titledBlocksFor = (lessonId: string): ManifestTitledBlock[] => {
|
|
1874
|
+
// A deck's question and example frames are frames, listed as frames.
|
|
1875
|
+
if (!isChapter(lessonId)) return [];
|
|
1661
1876
|
const slug = lessonId.replace(/\./g, '/');
|
|
1662
1877
|
const body = bodyByLessonId.get(lessonId) ?? '';
|
|
1663
1878
|
const headings = headingsByLessonId.get(lessonId) ?? [];
|
|
@@ -1688,7 +1903,7 @@ export async function compileManifest(
|
|
|
1688
1903
|
// Rule V3: the course files a lesson's imports reach draw math through
|
|
1689
1904
|
// `renderLabMath`, never KaTeX itself.
|
|
1690
1905
|
const labKatexDiagnostics = validateLabKatexImports(
|
|
1691
|
-
notationInput.lessons.map((lesson) => ({
|
|
1906
|
+
[...notationInput.lessons, ...deckPages.map((page) => page.notation)].map((lesson) => ({
|
|
1692
1907
|
lessonId: lesson.lessonId,
|
|
1693
1908
|
file: lesson.source.file,
|
|
1694
1909
|
body: lesson.body,
|
|
@@ -1696,7 +1911,7 @@ export async function compileManifest(
|
|
|
1696
1911
|
REPOSITORY_ROOT,
|
|
1697
1912
|
);
|
|
1698
1913
|
const rawFloatDiagnostics = validateRawFloats(
|
|
1699
|
-
notationInput.lessons.map((lesson) => ({
|
|
1914
|
+
[...notationInput.lessons, ...deckPages.map((page) => page.notation)].map((lesson) => ({
|
|
1700
1915
|
lessonId: lesson.lessonId,
|
|
1701
1916
|
slug: lesson.lessonId.replace(/\./g, '/'),
|
|
1702
1917
|
file: lesson.source.file,
|
|
@@ -1704,7 +1919,124 @@ export async function compileManifest(
|
|
|
1704
1919
|
})),
|
|
1705
1920
|
);
|
|
1706
1921
|
|
|
1707
|
-
|
|
1922
|
+
// The decks (rules D1–D10 and the `decks` invariant): each read as frames,
|
|
1923
|
+
// against its page's scope and its chapter's numbers.
|
|
1924
|
+
const pageInputById = new Map(withPages.lessons.map((lesson) => [lesson.lessonId, lesson]));
|
|
1925
|
+
const chapterBodyById = new Map(chapterInputs.map((lesson) => [lesson.lessonId, lesson.body]));
|
|
1926
|
+
const floatNumbers = (floats: readonly { readonly key: string; readonly number: string }[] | undefined) =>
|
|
1927
|
+
new Map((floats ?? []).map((float) => [float.key, float.number]));
|
|
1928
|
+
// A competency's `outcome` (`content-schemas.ts`), which the outcomes frame lists.
|
|
1929
|
+
const outcomeById = new Map(
|
|
1930
|
+
catalog.competencies.flatMap((competency): [string, string][] => {
|
|
1931
|
+
const outcome = (competency as { readonly outcome?: unknown }).outcome;
|
|
1932
|
+
return typeof outcome === 'string' ? [[competency.id, outcome]] : [];
|
|
1933
|
+
}),
|
|
1934
|
+
);
|
|
1935
|
+
/** What a chapter numbers, which a deck may recall: each float's number, an equation's LaTeX. */
|
|
1936
|
+
const chapterFloatsOf = (lessonId: string): ChapterFloats => {
|
|
1937
|
+
const slug = lessonId.replace(/\./g, '/');
|
|
1938
|
+
const chapterBody = chapterBodyById.get(lessonId);
|
|
1939
|
+
const latex = chapterBody === undefined ? new Map<string, string>() : chapterEquationLatex(chapterBody);
|
|
1940
|
+
return {
|
|
1941
|
+
equations: new Map(
|
|
1942
|
+
(equationsBySlug.get(slug) ?? []).map((equation) => [
|
|
1943
|
+
equation.key,
|
|
1944
|
+
{ number: equation.number, ...(latex.has(equation.key) ? { latex: latex.get(equation.key) } : {}) },
|
|
1945
|
+
]),
|
|
1946
|
+
),
|
|
1947
|
+
figures: floatNumbers(figuresBySlug.get(slug)),
|
|
1948
|
+
tables: floatNumbers(tablesBySlug.get(slug)),
|
|
1949
|
+
diagrams: floatNumbers(diagramsBySlug.get(slug)),
|
|
1950
|
+
};
|
|
1951
|
+
};
|
|
1952
|
+
const outcomesOf = (lessonId: string): string[] =>
|
|
1953
|
+
(lessonMetaById.get(lessonId)?.teaches ?? []).flatMap((id) => {
|
|
1954
|
+
const outcome = outcomeById.get(id);
|
|
1955
|
+
return outcome ? [outcome] : [];
|
|
1956
|
+
});
|
|
1957
|
+
// Each part with a deck: its chapters in reading order, what each numbers
|
|
1958
|
+
// and teaches; its slides-first lessons, which no section can cover.
|
|
1959
|
+
const deckParts = new Map<string, DeckPartFacts>(
|
|
1960
|
+
partDeckFiles.map((deck) => {
|
|
1961
|
+
const part = manifestParts.find((entry) => entry.dir === deck.partDir);
|
|
1962
|
+
const lessonIds = part?.lessons ?? [];
|
|
1963
|
+
const nameOf = (lessonId: string) => lessonId.replace(/\./g, '/').slice(deck.partDir.length + 1);
|
|
1964
|
+
return [
|
|
1965
|
+
deck.partDir,
|
|
1966
|
+
{
|
|
1967
|
+
label: part?.label ?? deck.partDir,
|
|
1968
|
+
chapters: lessonIds.filter(isChapter).map((lessonId) => ({
|
|
1969
|
+
name: nameOf(lessonId),
|
|
1970
|
+
slug: lessonId.replace(/\./g, '/'),
|
|
1971
|
+
teaches: lessonMetaById.get(lessonId)?.teaches ?? [],
|
|
1972
|
+
outcomes: outcomesOf(lessonId),
|
|
1973
|
+
floats: chapterFloatsOf(lessonId),
|
|
1974
|
+
})),
|
|
1975
|
+
slidesFirst: lessonIds.filter((lessonId) => !isChapter(lessonId)).map(nameOf),
|
|
1976
|
+
},
|
|
1977
|
+
];
|
|
1978
|
+
}),
|
|
1979
|
+
);
|
|
1980
|
+
const deckModel = buildDecks({
|
|
1981
|
+
decks: deckFiles,
|
|
1982
|
+
settings: deckSettings,
|
|
1983
|
+
rules: {
|
|
1984
|
+
maxWords: rules.number('D2', 'maxWords'),
|
|
1985
|
+
maxBullets: rules.number('D2', 'maxBullets'),
|
|
1986
|
+
maxTitleLength: rules.number('D5', 'maxLength'),
|
|
1987
|
+
maxSteps: rules.number('D6', 'maxSteps'),
|
|
1988
|
+
maxNewSymbols: rules.number('D9', 'maxNewSymbols'),
|
|
1989
|
+
},
|
|
1990
|
+
lessons: new Map(
|
|
1991
|
+
lessonDeckFiles.map((deck) => [
|
|
1992
|
+
deck.lessonId,
|
|
1993
|
+
{
|
|
1994
|
+
title: frontmatterById.get(deck.lessonId)?.title ?? deck.lessonId,
|
|
1995
|
+
teaches: lessonMetaById.get(deck.lessonId)?.teaches ?? [],
|
|
1996
|
+
outcomes: outcomesOf(deck.lessonId),
|
|
1997
|
+
...(deck.chapterPath === undefined ? {} : { chapterFloats: chapterFloatsOf(deck.lessonId) }),
|
|
1998
|
+
},
|
|
1999
|
+
]),
|
|
2000
|
+
),
|
|
2001
|
+
parts: deckParts,
|
|
2002
|
+
// As the title frame shows them (`titleFrameNode` in `deck-remark.mjs`).
|
|
2003
|
+
course: {
|
|
2004
|
+
name: courseConfig.about.materialName ?? courseConfig.title,
|
|
2005
|
+
authors: courseConfig.about.authors.map((author) => author.name),
|
|
2006
|
+
},
|
|
2007
|
+
scopeOf: (pageId) => {
|
|
2008
|
+
const page = pageInputById.get(pageId);
|
|
2009
|
+
return page === undefined ? [] : pageScope(page, cardsByKey);
|
|
2010
|
+
},
|
|
2011
|
+
suspendedOf: (pageId) => {
|
|
2012
|
+
const page = pageInputById.get(pageId);
|
|
2013
|
+
return page === undefined ? [] : suspendedOperators(page);
|
|
2014
|
+
},
|
|
2015
|
+
labelFor: termLabel,
|
|
2016
|
+
});
|
|
2017
|
+
mathIssues.push(...deckModel.diagnostics);
|
|
2018
|
+
// The part's deck and each chapter's section of it, as the decks number their frames.
|
|
2019
|
+
const sidebarWithFrames = withPartDeckFrames(sidebar, deckModel.decks);
|
|
2020
|
+
|
|
2021
|
+
// Per-place waivers: drop the located findings each one covers, and
|
|
2022
|
+
// report the waivers themselves — a stale one is a warning, a bad one an
|
|
2023
|
+
// error. Chapters, decks and non-chapter pages are scanned alike, since the
|
|
2024
|
+
// text and math gates run on all of them; S2 and S10 are per-file or
|
|
2025
|
+
// locked, so no waiver reaches them. Applied once every located finding is
|
|
2026
|
+
// in, the deck rules' included.
|
|
2027
|
+
const waiverDiagnostics: WaiverDiagnostic[] = [];
|
|
2028
|
+
const waiverRules = { courseRules: rules.course };
|
|
2029
|
+
const waivers = withPages.lessons.flatMap((lesson) => {
|
|
2030
|
+
const collected = collectWaivers(lesson, waiverRules);
|
|
2031
|
+
waiverDiagnostics.push(...collected.diagnostics);
|
|
2032
|
+
return collected.waivers;
|
|
2033
|
+
});
|
|
2034
|
+
const waived = applyWaivers(mathIssues, waivers, waiverRules);
|
|
2035
|
+
mathIssues.splice(0, mathIssues.length, ...waived.kept);
|
|
2036
|
+
waiverDiagnostics.push(...waived.unused);
|
|
2037
|
+
|
|
2038
|
+
// Each lesson gets its `since` below, once every page's inputs are known.
|
|
2039
|
+
const lessons: Omit<ManifestLesson, 'since'>[] = [];
|
|
1708
2040
|
for (const notationLesson of notationInput.lessons) {
|
|
1709
2041
|
const id = notationLesson.lessonId;
|
|
1710
2042
|
const meta = lessonMetaById.get(id);
|
|
@@ -1717,6 +2049,10 @@ export async function compileManifest(
|
|
|
1717
2049
|
lessons.push({
|
|
1718
2050
|
id,
|
|
1719
2051
|
slug: id.replace(/\./g, '/'),
|
|
2052
|
+
editions: [
|
|
2053
|
+
...(isChapter(id) ? (['chapter'] as const) : []),
|
|
2054
|
+
...(lessonDeckFiles.some((deck) => deck.lessonId === id) ? (['deck'] as const) : []),
|
|
2055
|
+
],
|
|
1720
2056
|
// 0 = the lesson is not placed in any configured part; a curriculum
|
|
1721
2057
|
// diagnostic covers that separately, and both numbering paths then fall
|
|
1722
2058
|
// back to the legacy `##`-section scheme.
|
|
@@ -1836,6 +2172,127 @@ export async function compileManifest(
|
|
|
1836
2172
|
file: relative(REPOSITORY_ROOT, knowledgeChangelogPath).replace(/\\/g, '/'),
|
|
1837
2173
|
});
|
|
1838
2174
|
|
|
2175
|
+
// Each chapter's and part page's own version (rule C5): the committed
|
|
2176
|
+
// `content/versions.json`, brought to the pages' inputs as they are now —
|
|
2177
|
+
// the page's file, its checks and assessments, the shared notation entries
|
|
2178
|
+
// it uses, the sources it cites, and its numbers. With C5 off, nothing is
|
|
2179
|
+
// read or tracked and every page carries the course's version.
|
|
2180
|
+
const trackPageVersions = !rules.disabled.includes('C5');
|
|
2181
|
+
const previousVersions = trackPageVersions
|
|
2182
|
+
? readPageVersions(join(CONTENT_ROOT, 'versions.json'), PAGE_VERSIONS_FILE)
|
|
2183
|
+
: { record: undefined, diagnostics: [] };
|
|
2184
|
+
const digestOf = (file: string): string => hashes.content[file] ?? 'missing';
|
|
2185
|
+
const presentFiles = (files: readonly string[]) => files.filter((file) => hashes.content[file] !== undefined);
|
|
2186
|
+
const sourceFileById = new Map<string, string>();
|
|
2187
|
+
for (const record of contentRecords) {
|
|
2188
|
+
const file = record.file.replace(/\\/g, '/');
|
|
2189
|
+
if (file.startsWith('content/sources/') && typeof record.data.id === 'string') {
|
|
2190
|
+
sourceFileById.set(record.data.id, file);
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
const sharedEntryInputs = (definitionIds: readonly string[]) =>
|
|
2194
|
+
definitionIds.flatMap((definitionId) => {
|
|
2195
|
+
const definition = definitionsById.get(definitionId);
|
|
2196
|
+
return definition?.kind === 'shared'
|
|
2197
|
+
? [{ label: `notation ${definition.source.file}`, digest: digestOf(definition.source.file) }]
|
|
2198
|
+
: [];
|
|
2199
|
+
});
|
|
2200
|
+
/** Each float a part's deck recalls, by ref, as its chapter numbers, captions and (an equation) writes it. */
|
|
2201
|
+
const recalledFloatFacts = (recalledFrom: Readonly<Record<string, string>>) =>
|
|
2202
|
+
Object.fromEntries(
|
|
2203
|
+
Object.entries(recalledFrom).map(([ref, chapter]) => {
|
|
2204
|
+
const [, prefix, key] = /^(eq|fig|tab|dia)-(.+)$/u.exec(ref) ?? [];
|
|
2205
|
+
const floats =
|
|
2206
|
+
prefix === 'eq'
|
|
2207
|
+
? equationsBySlug
|
|
2208
|
+
: prefix === 'fig'
|
|
2209
|
+
? figuresBySlug
|
|
2210
|
+
: prefix === 'tab'
|
|
2211
|
+
? tablesBySlug
|
|
2212
|
+
: diagramsBySlug;
|
|
2213
|
+
const float = floats.get(chapter)?.find((entry) => entry.key === key);
|
|
2214
|
+
const latex = prefix === 'eq' ? chapterFloatsOf(chapter.replace(/\//g, '.')).equations.get(key ?? '')?.latex : undefined;
|
|
2215
|
+
return [ref, { chapter, ...float, ...(latex === undefined ? {} : { latex }) }];
|
|
2216
|
+
}),
|
|
2217
|
+
);
|
|
2218
|
+
const trackedPages = [
|
|
2219
|
+
...lessons.map((lesson) => ({
|
|
2220
|
+
slug: lesson.slug,
|
|
2221
|
+
source: pageSourceDigest(
|
|
2222
|
+
[
|
|
2223
|
+
{ label: `page ${lesson.file}`, digest: digestOf(lesson.file) },
|
|
2224
|
+
...presentFiles([lesson.file.replace(/\.mdx?$/u, '.checks.yml')]).map((file) => ({
|
|
2225
|
+
label: `checks ${file}`,
|
|
2226
|
+
digest: digestOf(file),
|
|
2227
|
+
})),
|
|
2228
|
+
...presentFiles(lesson.assessments.map((id) => `content/assessments/${id}.json`)).map((file) => ({
|
|
2229
|
+
label: `assessment ${file}`,
|
|
2230
|
+
digest: digestOf(file),
|
|
2231
|
+
})),
|
|
2232
|
+
...sharedEntryInputs(lesson.notation.definitionIds),
|
|
2233
|
+
...lesson.sources.map((id) => {
|
|
2234
|
+
const file = sourceFileById.get(id);
|
|
2235
|
+
return { label: `source ${id}`, digest: file === undefined ? 'missing' : digestOf(file) };
|
|
2236
|
+
}),
|
|
2237
|
+
],
|
|
2238
|
+
{ part: lesson.part, chapter: lesson.chapter, numbering: lesson.numbering },
|
|
2239
|
+
),
|
|
2240
|
+
})),
|
|
2241
|
+
// A deck beside its chapter has a version of its own, by its route, and
|
|
2242
|
+
// so has a part's deck; a slides-first lesson's deck is the lesson, above.
|
|
2243
|
+
...deckModel.decks.flatMap((deck) => {
|
|
2244
|
+
if (deck.chapter === undefined && deck.part === undefined) return [];
|
|
2245
|
+
const page = deckPages.find((entry) => entry.deck.route === deck.slug);
|
|
2246
|
+
return [
|
|
2247
|
+
{
|
|
2248
|
+
slug: deck.slug,
|
|
2249
|
+
source: pageSourceDigest(
|
|
2250
|
+
[
|
|
2251
|
+
{ label: `page ${deck.file}`, digest: digestOf(deck.file) },
|
|
2252
|
+
...sharedEntryInputs(bundlesByLesson.get(deck.pageId)?.definitionIds ?? []),
|
|
2253
|
+
...(page === undefined ? [] : deriveSources(page.notation.body)).map((id) => {
|
|
2254
|
+
const file = sourceFileById.get(id);
|
|
2255
|
+
return { label: `source ${id}`, digest: file === undefined ? 'missing' : digestOf(file) };
|
|
2256
|
+
}),
|
|
2257
|
+
],
|
|
2258
|
+
// A part's deck shows each float it recalls as its chapter
|
|
2259
|
+
// numbers and captions it: those count as the page's too.
|
|
2260
|
+
deck.recalledFrom === undefined ? {} : { recalls: recalledFloatFacts(deck.recalledFrom) },
|
|
2261
|
+
),
|
|
2262
|
+
},
|
|
2263
|
+
];
|
|
2264
|
+
}),
|
|
2265
|
+
...manifestParts.flatMap((part) => {
|
|
2266
|
+
if (!part.overview) return [];
|
|
2267
|
+
const slug = `${part.dir}/index`;
|
|
2268
|
+
const page = pages.find((entry) => entry.slug === slug);
|
|
2269
|
+
return [
|
|
2270
|
+
{
|
|
2271
|
+
slug,
|
|
2272
|
+
source: pageSourceDigest([
|
|
2273
|
+
{ label: `page ${part.overview.file}`, digest: digestOf(part.overview.file) },
|
|
2274
|
+
...sharedEntryInputs(page?.notation.definitionIds ?? []),
|
|
2275
|
+
]),
|
|
2276
|
+
},
|
|
2277
|
+
];
|
|
2278
|
+
}),
|
|
2279
|
+
];
|
|
2280
|
+
const pageVersionsRecord = nextPageVersions({
|
|
2281
|
+
previous: previousVersions.record,
|
|
2282
|
+
version: knowledge.version,
|
|
2283
|
+
unreleased: knowledge.unreleased.length > 0,
|
|
2284
|
+
pages: trackedPages,
|
|
2285
|
+
});
|
|
2286
|
+
const pageVersions: ManifestPageVersions = {
|
|
2287
|
+
enabled: trackPageVersions,
|
|
2288
|
+
file: PAGE_VERSIONS_FILE,
|
|
2289
|
+
record: pageVersionsRecord,
|
|
2290
|
+
};
|
|
2291
|
+
const sinceOfPage = (slug: string): string | null => {
|
|
2292
|
+
const entry = pageVersionsRecord.pages[slug];
|
|
2293
|
+
return trackPageVersions && entry !== undefined ? sinceOf(entry) : knowledge.version;
|
|
2294
|
+
};
|
|
2295
|
+
|
|
1839
2296
|
const enginePkg = readEnginePackage();
|
|
1840
2297
|
const about: ManifestAbout = {
|
|
1841
2298
|
engine: {
|
|
@@ -1855,6 +2312,11 @@ export async function compileManifest(
|
|
|
1855
2312
|
? {}
|
|
1856
2313
|
: { date: knowledge.latest.date }),
|
|
1857
2314
|
notes: knowledge.latest?.notes ?? [],
|
|
2315
|
+
releases: knowledge.entries.map((entry) => ({
|
|
2316
|
+
version: entry.version,
|
|
2317
|
+
...(entry.date === undefined ? {} : { date: entry.date }),
|
|
2318
|
+
})),
|
|
2319
|
+
unreleased: knowledge.unreleased.length > 0,
|
|
1858
2320
|
contentTree: hashes.contentTree,
|
|
1859
2321
|
},
|
|
1860
2322
|
authors: courseConfig.about.authors.map((author) => ({
|
|
@@ -1874,6 +2336,34 @@ export async function compileManifest(
|
|
|
1874
2336
|
numbering: numberingSettings,
|
|
1875
2337
|
blockLabels,
|
|
1876
2338
|
cards: normalizeCards(courseConfig),
|
|
2339
|
+
sidebarMarks: normalizeSidebarMarks(courseConfig),
|
|
2340
|
+
sidebarEditions: normalizeSidebarEditions(courseConfig),
|
|
2341
|
+
readingMemory: normalizeReadingMemory(courseConfig),
|
|
2342
|
+
appearance: normalizeAppearance(courseConfig),
|
|
2343
|
+
deck: deckSettings,
|
|
2344
|
+
decks: deckModel.decks.map((deck) => {
|
|
2345
|
+
const page = pageInputById.get(deck.pageId);
|
|
2346
|
+
return {
|
|
2347
|
+
...deck,
|
|
2348
|
+
since: sinceOfPage(deck.slug),
|
|
2349
|
+
notation: lessonNotationBundle(
|
|
2350
|
+
bundlesByLesson.get(deck.pageId),
|
|
2351
|
+
definitionsById,
|
|
2352
|
+
page === undefined ? [] : lessonNotationResolution(page, definitionsById, availableDefinitionIds),
|
|
2353
|
+
cardsByKey,
|
|
2354
|
+
// A deck shows its lessons' symbols: the derived cards (rule N23)
|
|
2355
|
+
// its chapters' bodies and checks use ship with it, as its bundle
|
|
2356
|
+
// holds the chapters' entries.
|
|
2357
|
+
[
|
|
2358
|
+
...(indexGate.derivedByLesson.get(deck.pageId) ?? []),
|
|
2359
|
+
...(page?.scopeLessonIds ?? []).flatMap((lessonId) => [
|
|
2360
|
+
...(indexGate.derivedByLesson.get(lessonId) ?? []),
|
|
2361
|
+
...(assessmentDerived.get(lessonId)?.values() ?? []),
|
|
2362
|
+
]),
|
|
2363
|
+
].filter((use, index, all) => all.findIndex((other) => other.key === use.key) === index),
|
|
2364
|
+
),
|
|
2365
|
+
};
|
|
2366
|
+
}),
|
|
1877
2367
|
about,
|
|
1878
2368
|
notation: {
|
|
1879
2369
|
definitions: registry.definitions,
|
|
@@ -1886,9 +2376,14 @@ export async function compileManifest(
|
|
|
1886
2376
|
sources,
|
|
1887
2377
|
tracks,
|
|
1888
2378
|
abbreviations,
|
|
1889
|
-
sidebar,
|
|
1890
|
-
parts: manifestParts,
|
|
1891
|
-
|
|
2379
|
+
sidebar: sidebarWithFrames,
|
|
2380
|
+
parts: manifestParts.map(({ overview, ...part }) =>
|
|
2381
|
+
overview
|
|
2382
|
+
? { ...part, overview: { ...overview, since: sinceOfPage(`${part.dir}/index`) } }
|
|
2383
|
+
: part,
|
|
2384
|
+
),
|
|
2385
|
+
lessons: lessons.map((lesson) => ({ ...lesson, since: sinceOfPage(lesson.slug) })),
|
|
2386
|
+
pageVersions,
|
|
1892
2387
|
pages,
|
|
1893
2388
|
prereqEdges,
|
|
1894
2389
|
equations: {
|
|
@@ -1930,7 +2425,7 @@ export async function compileManifest(
|
|
|
1930
2425
|
checks: dropDisabled(checkValidation.diagnostics, off),
|
|
1931
2426
|
examples: dropDisabled(exampleValidation.diagnostics, off),
|
|
1932
2427
|
folds: dropDisabled(foldValidation.diagnostics, off),
|
|
1933
|
-
knowledge: dropDisabled(knowledge.diagnostics, off),
|
|
2428
|
+
knowledge: dropDisabled([...knowledge.diagnostics, ...previousVersions.diagnostics], off),
|
|
1934
2429
|
rawFloats: dropDisabled(rawFloatDiagnostics, off),
|
|
1935
2430
|
config: rules.diagnostics,
|
|
1936
2431
|
waivers: waiverDiagnostics,
|