explico 0.13.1 → 0.15.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 +990 -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 +100 -0
- package/dist/compiler/cli-main.js +433 -0
- package/dist/compiler/cli-reference.d.ts +50 -0
- package/dist/compiler/cli-reference.js +213 -0
- package/dist/compiler/cli-verbs.d.ts +45 -0
- package/dist/compiler/cli-verbs.js +224 -0
- package/dist/compiler/cli-verify.d.ts +51 -0
- package/dist/compiler/cli-verify.js +151 -0
- package/dist/compiler/cli.d.ts +408 -24
- package/dist/compiler/cli.js +1328 -8
- package/dist/compiler/collections.d.ts +88 -1
- package/dist/compiler/collections.js +170 -25
- package/dist/compiler/content-release.d.ts +49 -0
- package/dist/compiler/content-release.js +63 -0
- package/dist/compiler/content-schemas.d.ts +2 -2
- 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 +364 -58
- package/dist/compiler/page-versions.d.ts +184 -0
- package/dist/compiler/page-versions.js +217 -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 +82 -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 +6 -2
- package/dist/reference/diagram-fit.js +5 -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 +237 -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 +34 -10
- package/dist/rules/rules.schema.json +280 -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 +31 -9
- package/docs/authoring/agents.md +72 -18
- package/docs/authoring/cli-reference.json +2172 -0
- package/docs/authoring/cli.md +887 -0
- package/docs/authoring/components.md +15 -0
- package/docs/authoring/deck.md +612 -0
- package/docs/authoring/migrating.md +1195 -16
- package/docs/authoring/notation.md +49 -6
- package/docs/authoring/structure.md +80 -1
- package/docs/authoring/versioning.md +56 -3
- package/docs/authoring/visualizations.md +7 -2
- package/docs/course-agent/AGENTS.md +40 -0
- package/docs/course-agent/agents/rules-reviewer.md +33 -0
- package/docs/course-agent/skills/draft-lesson/SKILL.md +38 -0
- package/docs/course-agent/skills/migrate-course/SKILL.md +32 -0
- package/docs/course-agent/skills/release-content/SKILL.md +26 -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 +557 -0
- package/src/compiler/cli-reference.ts +285 -0
- package/src/compiler/cli-verbs.ts +270 -0
- package/src/compiler/cli-verify.ts +241 -0
- package/src/compiler/cli.ts +2119 -170
- package/src/compiler/collections.ts +284 -25
- package/src/compiler/content-release.ts +91 -0
- 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 +568 -63
- package/src/compiler/page-versions.ts +401 -0
- package/src/compiler/resolution-report.ts +38 -7
- package/src/compiler/validate-content.ts +215 -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 +15 -7
- 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 +137 -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-fit.ts +18 -4
- 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 +274 -4
- package/src/rules/course-rules.ts +21 -1
- package/src/rules/in-effect.ts +58 -11
- 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
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `pnpm validate:content` — regenerate `build/manifest.json` and report its
|
|
3
|
+
* diagnostics, whole. A course's `scripts/compile-manifest.ts` is three lines:
|
|
4
|
+
*
|
|
5
|
+
* import { runValidateContent } from 'explico/compiler/validate-content';
|
|
6
|
+
* import { courseConfig } from '../content/course.config';
|
|
7
|
+
* process.exitCode = await runValidateContent(courseConfig);
|
|
8
|
+
*
|
|
9
|
+
* It compiles the one manifest (the single walk of the content tree), writes
|
|
10
|
+
* it deterministically, prints the warnings, refreshes the per-lesson
|
|
11
|
+
* resolution reports, `content/versions.json` (rule C5) and the
|
|
12
|
+
* rules-in-effect page, and returns 1 if any blocking diagnostic is present.
|
|
13
|
+
* `pnpm build` runs it first, so `astro build` and every page component read a
|
|
14
|
+
* current manifest and never re-read the content tree.
|
|
15
|
+
*
|
|
16
|
+
* Every line printed is also recorded, in Python's `logging` format, to
|
|
17
|
+
* `build/logs/<timestamp>/{error,warning,info}.log` (`./logging`); `log.<level>()`
|
|
18
|
+
* returns its message so the printed line and the recorded one cannot drift.
|
|
19
|
+
*/
|
|
20
|
+
import { notationSourceLocatorGaps } from './collections';
|
|
21
|
+
import type { CourseConfig } from './course-config';
|
|
22
|
+
import { createRunLog, diagnosticLine } from './logging';
|
|
23
|
+
import { compileManifest, MANIFEST_PATH, writeManifest } from './manifest';
|
|
24
|
+
import { syncPageVersions } from './page-versions';
|
|
25
|
+
import { syncResolutionReports } from './resolution-report';
|
|
26
|
+
import { consistencyCounts } from '../reference/consistency';
|
|
27
|
+
import { curriculumErrors } from '../curriculum/validation';
|
|
28
|
+
import { writeRulesInEffect } from '../rules/in-effect';
|
|
29
|
+
|
|
30
|
+
export async function runValidateContent(courseConfig: CourseConfig): Promise<number> {
|
|
31
|
+
const log = createRunLog();
|
|
32
|
+
|
|
33
|
+
let manifest;
|
|
34
|
+
try {
|
|
35
|
+
manifest = await compileManifest(courseConfig);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
// The log keeps why the run ended; the console gets the message, not a stack.
|
|
38
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
39
|
+
log.error('manifest', `Manifest compilation failed: ${message}`);
|
|
40
|
+
const summary = log.close();
|
|
41
|
+
console.error(`Manifest compilation failed: ${message}\nRun log: ${summary.relativeDirectory}.`);
|
|
42
|
+
return 1;
|
|
43
|
+
}
|
|
44
|
+
writeManifest(manifest);
|
|
45
|
+
|
|
46
|
+
const {
|
|
47
|
+
curriculum,
|
|
48
|
+
notation,
|
|
49
|
+
alignment,
|
|
50
|
+
math,
|
|
51
|
+
consistency,
|
|
52
|
+
equations,
|
|
53
|
+
tables,
|
|
54
|
+
figures,
|
|
55
|
+
diagrams,
|
|
56
|
+
checks,
|
|
57
|
+
examples,
|
|
58
|
+
folds,
|
|
59
|
+
knowledge,
|
|
60
|
+
rawFloats,
|
|
61
|
+
config,
|
|
62
|
+
waivers,
|
|
63
|
+
} = manifest.diagnostics;
|
|
64
|
+
|
|
65
|
+
for (const warning of curriculum.filter((issue) => issue.severity === 'warning')) {
|
|
66
|
+
console.warn(log.warning('curriculum', `[${warning.kind}] ${warning.message}`));
|
|
67
|
+
}
|
|
68
|
+
for (const warning of notation.filter((d) => d.severity === 'warning')) {
|
|
69
|
+
console.warn(log.warning('notation', `[${warning.code}] ${warning.message}`));
|
|
70
|
+
}
|
|
71
|
+
for (const warning of alignment.filter((d) => d.severity === 'warning')) {
|
|
72
|
+
console.warn(log.warning('alignment', `[${warning.code}] ${warning.message}`));
|
|
73
|
+
}
|
|
74
|
+
const floatGroups = [
|
|
75
|
+
['equations', equations],
|
|
76
|
+
['tables', tables],
|
|
77
|
+
['figures', figures],
|
|
78
|
+
['diagrams', diagrams],
|
|
79
|
+
['checks', checks],
|
|
80
|
+
['examples', examples],
|
|
81
|
+
['folds', folds],
|
|
82
|
+
['rawFloats', rawFloats],
|
|
83
|
+
] as const;
|
|
84
|
+
for (const [source, group] of floatGroups) {
|
|
85
|
+
for (const warning of group.filter((d) => d.severity === 'warning')) {
|
|
86
|
+
console.warn(log.warning(source, `[${warning.code}] ${warning.message}`));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// `diagnosticLine` prefixes a code owned by a lettered rule with its ID
|
|
90
|
+
// (`[N9 prose-notation] file:line: …`), so the console names the rule page.
|
|
91
|
+
for (const issue of math.filter((item) => item.severity === 'warning')) {
|
|
92
|
+
console.warn(log.warning('math', diagnosticLine(issue)));
|
|
93
|
+
}
|
|
94
|
+
for (const warning of knowledge) {
|
|
95
|
+
console.warn(log.warning('knowledge', `[${warning.code}] ${warning.message}`));
|
|
96
|
+
}
|
|
97
|
+
// The course settings themselves: a deprecated top-level block, a retired rule.
|
|
98
|
+
for (const warning of config) {
|
|
99
|
+
console.warn(log.warning('config', `[${warning.code}] ${warning.message}`));
|
|
100
|
+
}
|
|
101
|
+
// Per-place waivers: a stale one is a warning; a malformed or invalid one
|
|
102
|
+
// fails below with the other errors.
|
|
103
|
+
for (const warning of waivers.filter((d) => d.severity === 'warning')) {
|
|
104
|
+
console.warn(log.warning('waivers', diagnosticLine(warning)));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const locatorGaps = notationSourceLocatorGaps();
|
|
108
|
+
if (locatorGaps.bare > 0) {
|
|
109
|
+
console.warn(
|
|
110
|
+
log.warning(
|
|
111
|
+
'consistency',
|
|
112
|
+
`[notation-source-locator] ${locatorGaps.bare} of ${locatorGaps.total} notation source citations lack a non-empty locator.`,
|
|
113
|
+
),
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const consistencyWarnings = consistency.filter((diagnostic) => diagnostic.severity === 'warning');
|
|
118
|
+
if (consistencyWarnings.length > 0) {
|
|
119
|
+
// The console gets one digest line; the log records each finding at
|
|
120
|
+
// WARNING (below), so its counts agree with `content status`, and the
|
|
121
|
+
// digest at INFO, so it is not counted as one more warning.
|
|
122
|
+
console.warn(
|
|
123
|
+
log.info(
|
|
124
|
+
'consistency',
|
|
125
|
+
`[consistency] ${consistencyWarnings.length} Tier-3 finding(s): ` +
|
|
126
|
+
Object.entries(consistencyCounts(consistencyWarnings))
|
|
127
|
+
.filter(([, count]) => count > 0)
|
|
128
|
+
.map(([code, count]) => `${code}=${count}`)
|
|
129
|
+
.join(', '),
|
|
130
|
+
),
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Every severity-`info` diagnostic goes to the log only.
|
|
135
|
+
log.infoDiagnostics(manifest.diagnostics);
|
|
136
|
+
for (const diagnostic of consistencyWarnings) {
|
|
137
|
+
log.warning('consistency', `[${diagnostic.code}] ${diagnostic.message}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const reports = syncResolutionReports(manifest);
|
|
141
|
+
console.log(
|
|
142
|
+
log.info(
|
|
143
|
+
'manifest',
|
|
144
|
+
`Resolution report: ${reports.written} file(s) under resolution/` +
|
|
145
|
+
(reports.refreshed.length > 0
|
|
146
|
+
? ` (${reports.refreshed.length} refreshed; git does not track them, nothing to commit).`
|
|
147
|
+
: '.'),
|
|
148
|
+
),
|
|
149
|
+
);
|
|
150
|
+
// Each page's own version (rule C5): rewritten here and committed with the
|
|
151
|
+
// change; in CI a record that would change fails below, like a lockfile.
|
|
152
|
+
const pageVersions = syncPageVersions(manifest);
|
|
153
|
+
if (pageVersions.written) {
|
|
154
|
+
console.log(
|
|
155
|
+
log.info(
|
|
156
|
+
'page-versions',
|
|
157
|
+
`content/versions.json ${
|
|
158
|
+
pageVersions.changed[0] === '*' ? 'written' : `updated for ${pageVersions.changed.length} page(s)`
|
|
159
|
+
} — commit it with the change (rule C5).`,
|
|
160
|
+
),
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
// The page an agent reads before drafting. Regenerated every run, never committed.
|
|
164
|
+
writeRulesInEffect(manifest);
|
|
165
|
+
console.log(
|
|
166
|
+
log.info('manifest', `Rules in effect: build/rules-in-effect.md (${manifest.rules.disabled.length} rule(s) off).`),
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const failures: string[] = [];
|
|
170
|
+
// `block(source, …)`: a blocking finding under its log source (not a diagnostic code).
|
|
171
|
+
const block = (source: string, message: string): void => {
|
|
172
|
+
failures.push(log.error(source, message));
|
|
173
|
+
};
|
|
174
|
+
for (const stalePath of reports.stale) {
|
|
175
|
+
block('resolution-report', `[resolution-report] ${stalePath} is out of date — commit the regenerated resolution/ files`);
|
|
176
|
+
}
|
|
177
|
+
if (pageVersions.diagnostic) block('page-versions', diagnosticLine(pageVersions.diagnostic));
|
|
178
|
+
for (const issue of curriculumErrors(curriculum)) block('curriculum', `[curriculum:${issue.kind}] ${issue.message}`);
|
|
179
|
+
for (const issue of notation.filter((d) => d.severity === 'error')) block('notation', `[notation:${issue.code}] ${issue.message}`);
|
|
180
|
+
for (const issue of alignment.filter((d) => d.severity === 'error')) block('alignment', `[alignment:${issue.code}] ${issue.message}`);
|
|
181
|
+
for (const issue of math.filter((item) => item.severity === 'error')) block('math', diagnosticLine(issue));
|
|
182
|
+
for (const issue of waivers.filter((d) => d.severity === 'error')) block('waivers', diagnosticLine(issue));
|
|
183
|
+
for (const [source, group] of floatGroups) {
|
|
184
|
+
for (const issue of group.filter((d) => d.severity === 'error')) block(source, `[${issue.code}] ${issue.message}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (failures.length > 0) {
|
|
188
|
+
const summary = log.close();
|
|
189
|
+
console.error(
|
|
190
|
+
`Content manifest has ${failures.length} blocking diagnostic(s):\n${failures.map((line) => `- ${line}`).join('\n')}\n` +
|
|
191
|
+
`Run log: ${summary.relativeDirectory} (${summary.counts.ERROR} error(s), ${summary.counts.WARNING} warning(s)).`,
|
|
192
|
+
);
|
|
193
|
+
return 1;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const itemCount = manifest.lessons.reduce((count, lesson) => count + lesson.assessments.length, 0);
|
|
197
|
+
const relPath = MANIFEST_PATH.replace(`${process.cwd()}/`, '');
|
|
198
|
+
console.log(
|
|
199
|
+
log.info(
|
|
200
|
+
'manifest',
|
|
201
|
+
`Manifest written to ${relPath} (schema v${manifest.schemaVersion}, knowledge v${manifest.about.knowledge.version}): ` +
|
|
202
|
+
`${manifest.competencies.length} competencies, ${manifest.lessons.length} lessons, ` +
|
|
203
|
+
`${itemCount} lesson assessment links, ${manifest.notation.definitions.length} notation definitions, ` +
|
|
204
|
+
`${manifest.sources.length} sources, ${manifest.tracks.length} track(s). ` +
|
|
205
|
+
`Content fingerprint ${manifest.hashes.contentTree}.`,
|
|
206
|
+
),
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
const summary = log.close();
|
|
210
|
+
console.log(
|
|
211
|
+
`Run log: ${summary.relativeDirectory} ` +
|
|
212
|
+
`(${summary.counts.ERROR} error(s), ${summary.counts.WARNING} warning(s), ${summary.counts.INFO} info).`,
|
|
213
|
+
);
|
|
214
|
+
return 0;
|
|
215
|
+
}
|
|
@@ -9,6 +9,13 @@
|
|
|
9
9
|
* each part heading linking its overview page where one exists, and study
|
|
10
10
|
* times totalled per part and for the course when every chapter states one.
|
|
11
11
|
*
|
|
12
|
+
* When the course has a deck, each title starts with the glyph of what it
|
|
13
|
+
* opens (a chapter's sheet, a slides-only lesson's frame), a key and a deck
|
|
14
|
+
* count join the counts, and a slides column ends every row: a lesson's own
|
|
15
|
+
* deck is a tile on its title line, a part's deck one bar along the chapters
|
|
16
|
+
* it covers (`deck-lane.ts`). It lists every edition whatever the sidebar's
|
|
17
|
+
* controls say: it answers "what is in the course".
|
|
18
|
+
*
|
|
12
19
|
* Usage, on a splash page:
|
|
13
20
|
*
|
|
14
21
|
* ```mdx
|
|
@@ -18,15 +25,21 @@
|
|
|
18
25
|
* ```
|
|
19
26
|
*/
|
|
20
27
|
import { loadManifest } from '../compiler/manifest';
|
|
28
|
+
import { contentsKey, deckLanes, laneLink, titleGlyph } from './deck-lane';
|
|
21
29
|
|
|
22
30
|
interface Props {
|
|
23
31
|
/** Heading level for each part's name. Defaults to `h2`. */
|
|
24
32
|
readonly headingLevel?: 2 | 3;
|
|
25
33
|
/** Set `false` to drop the per-chapter description column. */
|
|
26
34
|
readonly descriptions?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Set `false` to drop the slides column, the glyphs, the key and the deck
|
|
37
|
+
* count. They show only when the course has a deck.
|
|
38
|
+
*/
|
|
39
|
+
readonly slides?: boolean;
|
|
27
40
|
}
|
|
28
41
|
|
|
29
|
-
const { headingLevel = 2, descriptions = true } = Astro.props;
|
|
42
|
+
const { headingLevel = 2, descriptions = true, slides = true } = Astro.props;
|
|
30
43
|
const Heading = `h${headingLevel}` as 'h2' | 'h3';
|
|
31
44
|
|
|
32
45
|
const manifest = await loadManifest();
|
|
@@ -44,7 +57,24 @@ const parts = manifest.parts
|
|
|
44
57
|
(lesson): lesson is NonNullable<typeof lesson> => lesson !== undefined,
|
|
45
58
|
),
|
|
46
59
|
}))
|
|
47
|
-
.filter((part) => part.chapters.length > 0)
|
|
60
|
+
.filter((part) => part.chapters.length > 0)
|
|
61
|
+
.map((part) => ({
|
|
62
|
+
...part,
|
|
63
|
+
lanes: slides ? deckLanes({ lessons: part.chapters, decks: manifest.decks, part }) : [],
|
|
64
|
+
}));
|
|
65
|
+
|
|
66
|
+
// The glyphs, the key and the count say which lessons have slides: in a
|
|
67
|
+
// course with no deck they would explain one glyph, so the list stays as it
|
|
68
|
+
// was. The column is as wide as the part that needs most lanes, so it lines
|
|
69
|
+
// up across parts.
|
|
70
|
+
const key = slides ? contentsKey(manifest.lessons, manifest.decks) : [];
|
|
71
|
+
const glyphs = key.length > 0;
|
|
72
|
+
const laneCount = Math.max(0, ...parts.map((part) => part.lanes.length));
|
|
73
|
+
const keyLabel = {
|
|
74
|
+
chapter: 'Chapter',
|
|
75
|
+
deck: 'Slides',
|
|
76
|
+
'part-deck': 'Slides for a whole part',
|
|
77
|
+
} as const;
|
|
48
78
|
|
|
49
79
|
const chapterCount = parts.reduce(
|
|
50
80
|
(total, part) => total + part.chapters.length,
|
|
@@ -74,19 +104,42 @@ const hours = (range: { min: number; max: number }) => {
|
|
|
74
104
|
};
|
|
75
105
|
---
|
|
76
106
|
|
|
77
|
-
<div
|
|
107
|
+
<div
|
|
108
|
+
class="course-contents not-content"
|
|
109
|
+
data-deck-lanes={laneCount > 0 ? laneCount : undefined}
|
|
110
|
+
style={laneCount > 0 ? `--deck-lanes: ${laneCount}` : undefined}
|
|
111
|
+
>
|
|
78
112
|
<p class="course-meta">
|
|
79
113
|
<span>{parts.length} parts</span>
|
|
80
114
|
<span>{chapterCount} chapters</span>
|
|
115
|
+
{
|
|
116
|
+
glyphs && (
|
|
117
|
+
<span>
|
|
118
|
+
{manifest.decks.length} {manifest.decks.length === 1 ? 'slide deck' : 'slide decks'}
|
|
119
|
+
</span>
|
|
120
|
+
)
|
|
121
|
+
}
|
|
81
122
|
{courseMinutes && <span>{hours(courseMinutes)} of study</span>}
|
|
82
123
|
</p>
|
|
124
|
+
{
|
|
125
|
+
glyphs && (
|
|
126
|
+
<ul class="contents-key" aria-label="Key">
|
|
127
|
+
{key.map((glyph) => (
|
|
128
|
+
<li>
|
|
129
|
+
<span class={`contents-glyph contents-glyph--${glyph}`} aria-hidden="true" />
|
|
130
|
+
{keyLabel[glyph]}
|
|
131
|
+
</li>
|
|
132
|
+
))}
|
|
133
|
+
</ul>
|
|
134
|
+
)
|
|
135
|
+
}
|
|
83
136
|
|
|
84
137
|
{
|
|
85
138
|
parts.map((part) => (
|
|
86
139
|
<section class="course-part">
|
|
87
140
|
<Heading id={`contents-${part.dir}`}>
|
|
88
141
|
{part.overview ? (
|
|
89
|
-
<a href={`${base}${part.link}`}>{part.label}</a>
|
|
142
|
+
<a href={`${base}${part.link}`}>{glyphs && <span class="contents-glyph contents-glyph--part" aria-hidden="true" />}{part.label}</a>
|
|
90
143
|
) : (
|
|
91
144
|
part.label
|
|
92
145
|
)}
|
|
@@ -99,13 +152,23 @@ const hours = (range: { min: number; max: number }) => {
|
|
|
99
152
|
{part.studyMinutes && <span>{minutes(part.studyMinutes)}</span>}
|
|
100
153
|
</p>
|
|
101
154
|
<ol aria-labelledby={`contents-${part.dir}`}>
|
|
102
|
-
{part.chapters.map((lesson) => (
|
|
155
|
+
{part.chapters.map((lesson, row) => (
|
|
103
156
|
<li>
|
|
104
|
-
<a href={`${base}/${lesson.slug}/`}>{lesson.title}</a>
|
|
157
|
+
<a href={`${base}/${lesson.slug}/`}>{glyphs && <span class={`contents-glyph contents-glyph--${titleGlyph(lesson)}`} aria-hidden="true" />}{lesson.title}</a>
|
|
105
158
|
{lesson.studyMinutes && (
|
|
106
159
|
<span class="chapter-time">{minutes(lesson.studyMinutes)}</span>
|
|
107
160
|
)}
|
|
108
161
|
{descriptions && <p>{lesson.description}</p>}
|
|
162
|
+
{part.lanes.map((lane, index) => {
|
|
163
|
+
const cell = lane[row];
|
|
164
|
+
return (
|
|
165
|
+
cell && (
|
|
166
|
+
<a {...laneLink(cell, index, laneCount, base)}>
|
|
167
|
+
<span class="sr-only">{cell.name}</span>
|
|
168
|
+
</a>
|
|
169
|
+
)
|
|
170
|
+
);
|
|
171
|
+
})}
|
|
109
172
|
</li>
|
|
110
173
|
))}
|
|
111
174
|
</ol>
|
|
@@ -224,4 +287,59 @@ const hours = (range: { min: number; max: number }) => {
|
|
|
224
287
|
text-align: start;
|
|
225
288
|
}
|
|
226
289
|
}
|
|
290
|
+
|
|
291
|
+
/* The key under the counts: the glyphs the list uses, in the accent. */
|
|
292
|
+
.contents-key {
|
|
293
|
+
display: flex;
|
|
294
|
+
flex-wrap: wrap;
|
|
295
|
+
gap: 0.25rem 1.25rem;
|
|
296
|
+
margin: -1.1rem 0 0;
|
|
297
|
+
padding: 0;
|
|
298
|
+
list-style: none;
|
|
299
|
+
color: var(--sl-color-gray-2);
|
|
300
|
+
font-size: var(--sl-text-sm);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.contents-key li {
|
|
304
|
+
margin: 0;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.contents-key :global(.contents-glyph) {
|
|
308
|
+
color: var(--sl-color-text-accent);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/* The slides column (`global.css`): one 1.7rem column per lane at the
|
|
312
|
+
row's end. The description keeps to the title's and time's columns. A
|
|
313
|
+
bar's cell spans a row's title and description, and reaches the line
|
|
314
|
+
between two rows: the row's bottom padding and the list's gap below it,
|
|
315
|
+
the next row's top border and padding above. */
|
|
316
|
+
.course-contents[data-deck-lanes] {
|
|
317
|
+
--deck-lane-rows: 2;
|
|
318
|
+
--deck-lane-down: 0.5rem;
|
|
319
|
+
--deck-lane-up: calc(0.4rem + 1px);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.course-contents[data-deck-lanes] .course-part li {
|
|
323
|
+
grid-template-columns: 1.9rem minmax(0, 1fr) auto repeat(var(--deck-lanes), 1.7rem);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.course-contents[data-deck-lanes] .course-part li > p {
|
|
327
|
+
grid-column: 2 / 4;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/* On a phone the time goes under the title, and the cells span the three
|
|
331
|
+
rows. */
|
|
332
|
+
@media (max-width: 40rem) {
|
|
333
|
+
.course-contents[data-deck-lanes] {
|
|
334
|
+
--deck-lane-rows: 3;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.course-contents[data-deck-lanes] .course-part li {
|
|
338
|
+
grid-template-columns: 1.9rem minmax(0, 1fr) repeat(var(--deck-lanes), 1.7rem);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.course-contents[data-deck-lanes] .course-part li > p {
|
|
342
|
+
grid-column: 2;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
227
345
|
</style>
|
|
@@ -2,8 +2,29 @@
|
|
|
2
2
|
import { getCollection } from 'astro:content';
|
|
3
3
|
import { loadManifest } from '../compiler/manifest';
|
|
4
4
|
import { isLessonSlug, lessonIdFromSlug } from '../compiler/lesson-derivation';
|
|
5
|
+
import { competencyGraphsByPart } from '../curriculum/competency-graph';
|
|
5
6
|
import Diagram, { type DiagramEdge, type DiagramNode } from './diagram/Diagram.astro';
|
|
6
7
|
|
|
8
|
+
export interface Props {
|
|
9
|
+
/**
|
|
10
|
+
* `'whole'` (the default) draws the competency graph as one diagram;
|
|
11
|
+
* `'per-part'` draws one diagram per part, in sidebar order, holding the
|
|
12
|
+
* competencies its lessons teach first and, labelled with their part, the
|
|
13
|
+
* earlier competencies they build on. A graph too wide for the column as
|
|
14
|
+
* one diagram (rule V8) usually fits as one per part.
|
|
15
|
+
*/
|
|
16
|
+
graph?: 'whole' | 'per-part';
|
|
17
|
+
/** The layout direction of every competency diagram; `Diagram`'s default is `'TB'`. */
|
|
18
|
+
direction?: 'TB' | 'LR';
|
|
19
|
+
/**
|
|
20
|
+
* Passed to every competency diagram: the width, in px, above which rule V8
|
|
21
|
+
* warns, or `false` to let a whole-course graph scroll without a warning.
|
|
22
|
+
*/
|
|
23
|
+
warnAboveWidth?: number | false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const { graph = 'whole', direction, warnAboveWidth } = Astro.props;
|
|
27
|
+
|
|
7
28
|
const [tracks, docs, competencies, manifest] = await Promise.all([
|
|
8
29
|
getCollection('tracks'),
|
|
9
30
|
getCollection('docs'),
|
|
@@ -37,6 +58,15 @@ const competencyEdges: DiagramEdge[] = competencies.flatMap(({ data }) =>
|
|
|
37
58
|
to: data.id,
|
|
38
59
|
})),
|
|
39
60
|
);
|
|
61
|
+
|
|
62
|
+
const partGraphs =
|
|
63
|
+
graph === 'per-part'
|
|
64
|
+
? competencyGraphsByPart(
|
|
65
|
+
manifest.parts,
|
|
66
|
+
new Map(manifest.lessons.map((lesson) => [lesson.id, lesson.teaches])),
|
|
67
|
+
competencies.map(({ data }) => data),
|
|
68
|
+
)
|
|
69
|
+
: [];
|
|
40
70
|
---
|
|
41
71
|
|
|
42
72
|
{
|
|
@@ -127,12 +157,30 @@ const competencyEdges: DiagramEdge[] = competencies.flatMap(({ data }) =>
|
|
|
127
157
|
</p>
|
|
128
158
|
|
|
129
159
|
{
|
|
130
|
-
|
|
160
|
+
partGraphs.map((partGraph) => (
|
|
161
|
+
<>
|
|
162
|
+
<h3 id={`${partGraph.id}-heading`}>{partGraph.title}</h3>
|
|
163
|
+
<Diagram
|
|
164
|
+
id={partGraph.id}
|
|
165
|
+
caption={`The competencies taught in ${partGraph.title}: an arrow runs from a prerequisite to the competency that builds on it, and a box labelled with an earlier part is a competency taught there`}
|
|
166
|
+
nodes={partGraph.nodes}
|
|
167
|
+
edges={partGraph.edges}
|
|
168
|
+
direction={direction}
|
|
169
|
+
warnAboveWidth={warnAboveWidth}
|
|
170
|
+
/>
|
|
171
|
+
</>
|
|
172
|
+
))
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
{
|
|
176
|
+
graph === 'whole' && competencyNodes.length > 0 && (
|
|
131
177
|
<Diagram
|
|
132
178
|
id="competency-graph"
|
|
133
179
|
caption="The competency dependency graph: an arrow runs from a prerequisite to the competency that builds on it"
|
|
134
180
|
nodes={competencyNodes}
|
|
135
181
|
edges={competencyEdges}
|
|
182
|
+
direction={direction}
|
|
183
|
+
warnAboveWidth={warnAboveWidth}
|
|
136
184
|
/>
|
|
137
185
|
)
|
|
138
186
|
}
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* ```
|
|
28
28
|
*/
|
|
29
29
|
import { loadManifest, type ManifestLessonFloat } from '../compiler/manifest';
|
|
30
|
-
import { FLOAT_KIND_WORDS, type FloatKind } from '
|
|
30
|
+
import { FLOAT_KIND_WORDS, type FloatKind } from '../reference/float-name';
|
|
31
31
|
|
|
32
32
|
interface Props {
|
|
33
33
|
/** Heading level for each part's name; a chapter's is one below. Defaults to `h2`. */
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
* collection — chapter order, titles, descriptions, float counts, and which
|
|
8
8
|
* parts this one builds on or feeds. A course authors the page's prose; it
|
|
9
9
|
* never re-types the list, so a renamed chapter or a new prerequisite cannot
|
|
10
|
-
* leave a stale copy behind on the part page.
|
|
10
|
+
* leave a stale copy behind on the part page. When the course has a deck, the
|
|
11
|
+
* list has the contents list's glyphs and slides column (`deck-lane.ts`,
|
|
12
|
+
* `CourseContents.astro`).
|
|
11
13
|
*
|
|
12
14
|
* Usage, in `content/docs/<part>/index.mdx`:
|
|
13
15
|
*
|
|
@@ -38,6 +40,7 @@
|
|
|
38
40
|
*/
|
|
39
41
|
import { getCollection } from 'astro:content';
|
|
40
42
|
import { loadManifest, type ManifestPart } from '../compiler/manifest';
|
|
43
|
+
import { deckLanes, laneLink, titleGlyph } from './deck-lane';
|
|
41
44
|
|
|
42
45
|
interface Props {
|
|
43
46
|
/**
|
|
@@ -53,9 +56,14 @@ interface Props {
|
|
|
53
56
|
readonly chain?: readonly string[];
|
|
54
57
|
/** Set `false` to drop the rail and keep only the reading list. */
|
|
55
58
|
readonly rail?: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Set `false` to drop the slides column and the glyphs before the titles.
|
|
61
|
+
* They show only when the course has a deck.
|
|
62
|
+
*/
|
|
63
|
+
readonly slides?: boolean;
|
|
56
64
|
}
|
|
57
65
|
|
|
58
|
-
const { part: partProp, chain = [], rail = true } = Astro.props;
|
|
66
|
+
const { part: partProp, chain = [], rail = true, slides = true } = Astro.props;
|
|
59
67
|
|
|
60
68
|
const routeSlug = Astro.locals.starlightRoute.entry.id;
|
|
61
69
|
const partDir = partProp ?? routeSlug.replace(/\/index$/, '').split('/')[0];
|
|
@@ -82,6 +90,11 @@ const chapters = part.lessons
|
|
|
82
90
|
.map((id) => lessonById.get(id))
|
|
83
91
|
.filter((lesson): lesson is NonNullable<typeof lesson> => lesson !== undefined);
|
|
84
92
|
|
|
93
|
+
// As in the contents list: glyphs only in a course with a deck, and the
|
|
94
|
+
// column only when this part has one.
|
|
95
|
+
const glyphs = slides && manifest.decks.length > 0;
|
|
96
|
+
const lanes = glyphs ? deckLanes({ lessons: chapters, decks: manifest.decks, part }) : [];
|
|
97
|
+
|
|
85
98
|
const competencyById = new Map(
|
|
86
99
|
competencyEntries.map(({ data }) => [data.id, data]),
|
|
87
100
|
);
|
|
@@ -169,11 +182,15 @@ const hasRail =
|
|
|
169
182
|
</ul>
|
|
170
183
|
|
|
171
184
|
<div class="part-body">
|
|
172
|
-
<ol
|
|
185
|
+
<ol
|
|
186
|
+
class="part-chapters"
|
|
187
|
+
data-deck-lanes={lanes.length > 0 ? lanes.length : undefined}
|
|
188
|
+
style={lanes.length > 0 ? `--deck-lanes: ${lanes.length}` : undefined}
|
|
189
|
+
>
|
|
173
190
|
{
|
|
174
|
-
chapters.map((lesson) => (
|
|
191
|
+
chapters.map((lesson, row) => (
|
|
175
192
|
<li>
|
|
176
|
-
<a href={`${base}/${lesson.slug}/`}>{lesson.title}</a>
|
|
193
|
+
<a href={`${base}/${lesson.slug}/`}>{glyphs && <span class={`contents-glyph contents-glyph--${titleGlyph(lesson)}`} aria-hidden="true" />}{lesson.title}</a>
|
|
177
194
|
<p>{lesson.description}</p>
|
|
178
195
|
<p class="chapter-meta">
|
|
179
196
|
{lesson.studyMinutes && (
|
|
@@ -192,6 +209,16 @@ const hasRail =
|
|
|
192
209
|
</span>
|
|
193
210
|
)}
|
|
194
211
|
</p>
|
|
212
|
+
{lanes.map((lane, index) => {
|
|
213
|
+
const cell = lane[row];
|
|
214
|
+
return (
|
|
215
|
+
cell && (
|
|
216
|
+
<a {...laneLink(cell, index, lanes.length, base)}>
|
|
217
|
+
<span class="sr-only">{cell.name}</span>
|
|
218
|
+
</a>
|
|
219
|
+
)
|
|
220
|
+
);
|
|
221
|
+
})}
|
|
195
222
|
</li>
|
|
196
223
|
))
|
|
197
224
|
}
|
|
@@ -342,6 +369,25 @@ const hasRail =
|
|
|
342
369
|
line-height: 1.5;
|
|
343
370
|
}
|
|
344
371
|
|
|
372
|
+
/* The slides column (`global.css`): one 1.7rem column per lane at the
|
|
373
|
+
row's end, the title, description and meta line kept to the second
|
|
374
|
+
column. A bar's cell spans the three, and reaches the line under its
|
|
375
|
+
row: the row's bottom padding, then the next row's top padding and the
|
|
376
|
+
line itself above. */
|
|
377
|
+
.part-chapters[data-deck-lanes] {
|
|
378
|
+
--deck-lane-rows: 3;
|
|
379
|
+
--deck-lane-down: 0.85rem;
|
|
380
|
+
--deck-lane-up: calc(0.85rem + 1px);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.part-chapters[data-deck-lanes] > li {
|
|
384
|
+
grid-template-columns: 2.25rem minmax(0, 1fr) repeat(var(--deck-lanes), 1.7rem);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
.part-chapters[data-deck-lanes] > li > :not(.deck-lane) {
|
|
388
|
+
grid-column: 2;
|
|
389
|
+
}
|
|
390
|
+
|
|
345
391
|
.chapter-meta {
|
|
346
392
|
display: flex;
|
|
347
393
|
flex-wrap: wrap;
|
|
@@ -7,6 +7,8 @@ import { renderNotationMath } from '../notation/render-notation-math';
|
|
|
7
7
|
import type { MixedContentNode } from '../notation/render-notation-math';
|
|
8
8
|
import { knowledgeCheckAnchorId } from '../../reference/knowledge-checks.mjs';
|
|
9
9
|
import BlockHeading from '../BlockHeading.astro';
|
|
10
|
+
import { deckOfRoute } from '../deck/deck-context';
|
|
11
|
+
import { routeIdFromLocals } from '../../reference/page-notation';
|
|
10
12
|
import AssessmentRunner from './AssessmentRunner';
|
|
11
13
|
|
|
12
14
|
interface Props {
|
|
@@ -17,17 +19,29 @@ interface Props {
|
|
|
17
19
|
* `[[check-…]]` references; undeclared legacy embeds keep a local fallback.
|
|
18
20
|
*/
|
|
19
21
|
id?: string;
|
|
22
|
+
/**
|
|
23
|
+
* The lesson whose checks the set asks, by its id. Set by the engine on a
|
|
24
|
+
* part's deck (`deck-remark.mjs`): the chapter the frame's section covers.
|
|
25
|
+
* Elsewhere the page's own lesson.
|
|
26
|
+
*/
|
|
27
|
+
lesson?: string;
|
|
20
28
|
}
|
|
21
29
|
|
|
22
30
|
const explicitId = Astro.props.id;
|
|
23
31
|
|
|
24
32
|
const base = import.meta.env.BASE_URL.replace(/\/$/, '');
|
|
25
|
-
const lessonId = Astro.url.pathname
|
|
26
|
-
.replace(new RegExp(`^${base}/`), '')
|
|
27
|
-
.replace(/\/$/, '')
|
|
28
|
-
.replace(/\//g, '.');
|
|
29
|
-
|
|
30
33
|
const manifest = await loadManifest();
|
|
34
|
+
// On a deck (docs/authoring/deck.md) the checks are its lesson's, and a check
|
|
35
|
+
// is a question frame: its label, not a heading of its own. On a part's deck
|
|
36
|
+
// they are the chapter's whose section the frame is in.
|
|
37
|
+
const deck = deckOfRoute(manifest, routeIdFromLocals(Astro.locals));
|
|
38
|
+
const lessonId =
|
|
39
|
+
Astro.props.lesson ??
|
|
40
|
+
deck?.lessonId ??
|
|
41
|
+
Astro.url.pathname
|
|
42
|
+
.replace(new RegExp(`^${base}/`), '')
|
|
43
|
+
.replace(/\/$/, '')
|
|
44
|
+
.replace(/\//g, '.');
|
|
31
45
|
// A check folds like a section, by the course's `disclosure.knowledgeChecks`
|
|
32
46
|
// (collapsible and open unless the course says otherwise).
|
|
33
47
|
const { knowledgeChecks } = manifest.disclosure ?? DISCLOSURE_DEFAULTS;
|
|
@@ -101,16 +115,28 @@ function renderedContent(
|
|
|
101
115
|
{
|
|
102
116
|
entries.map(({ entry, number, anchorId }) => (
|
|
103
117
|
<>
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
118
|
+
{deck ? (
|
|
119
|
+
<p
|
|
120
|
+
class="deck-check__label"
|
|
121
|
+
id={anchorId}
|
|
122
|
+
data-block-kind="check"
|
|
123
|
+
data-check-number={number}
|
|
124
|
+
data-check-title={entry.data.title}
|
|
125
|
+
>
|
|
126
|
+
<span class="deck-check__kind">Knowledge check {number}</span> {entry.data.title}
|
|
127
|
+
</p>
|
|
128
|
+
) : (
|
|
129
|
+
<BlockHeading
|
|
130
|
+
kind="check"
|
|
131
|
+
anchorId={anchorId}
|
|
132
|
+
title={entry.data.title}
|
|
133
|
+
number={number}
|
|
134
|
+
collapsible={knowledgeChecks.collapsible}
|
|
135
|
+
defaultOpen={knowledgeChecks.defaultOpen}
|
|
136
|
+
card="accent"
|
|
137
|
+
class="assessment-heading-wrapper"
|
|
138
|
+
/>
|
|
139
|
+
)}
|
|
114
140
|
<AssessmentRunner
|
|
115
141
|
client:visible
|
|
116
142
|
assessmentId={entry.data.id}
|