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
|
@@ -4,7 +4,8 @@ import {
|
|
|
4
4
|
equationLinkFor,
|
|
5
5
|
mathSpans,
|
|
6
6
|
pageScope,
|
|
7
|
-
suspendedOperators
|
|
7
|
+
suspendedOperators,
|
|
8
|
+
scopeLocals
|
|
8
9
|
} from "./gate-math.js";
|
|
9
10
|
import { formulaGlyphScope } from "./gloss.js";
|
|
10
11
|
import { indexOfEntry } from "./math-glyphs.mjs";
|
|
@@ -38,7 +39,7 @@ function message(lessonId, code, details, place = "body") {
|
|
|
38
39
|
case "index-swallowed":
|
|
39
40
|
return `${lessonId}: \\explain{${details.key}}{${details.token}} wraps an indexed symbol in ${details.key}, whose latex (${details.notation}) has no index, so its card cannot explain ${JSON.stringify(details.index)}. Drop the wrap and give the subscript an index entry (latex: "{}_${details.index}"), or wrap it in an entry for the indexed symbol (rule N23).`;
|
|
40
41
|
case "index-range":
|
|
41
|
-
return `${lessonId}: the running index ${details.letter} (first in ${JSON.stringify(details.token)}) has no stated set. Give ${details.where} a range (range: "1, \\\\dots, n"), state it after a \\quad on the first display line that uses it (\\quad ${details.letter} = 1, \\dots, n), or mark the card fixedIndex: true if ${details.letter} is a label there, not a running index (rule N23).`;
|
|
42
|
+
return `${lessonId}: the running index ${details.letter} (first in ${JSON.stringify(details.token)}) has no stated set. Give ${details.where} a range (range: "1, \\\\dots, n"), state it after a \\quad on the first display line that uses it (\\quad ${details.letter} = 1, \\dots, n), or mark the card fixedIndex: true if ${details.letter} is a label there, not a running index; a family indexed by ${details.letter} then explains none of its instances (rule N23).`;
|
|
42
43
|
case "derived-key-taken":
|
|
43
44
|
return `${lessonId}: ${JSON.stringify(details.token)} composes the derived card ${details.key}, but an authored entry has that key. Rename the entry: a key of the form <symbol>.sub.<index> is the build's (rule N23).`;
|
|
44
45
|
}
|
|
@@ -51,7 +52,7 @@ function gateIndices(lessons, entries, extras = {}) {
|
|
|
51
52
|
const bodyOffset = (lesson.bodyLine ?? 1) - 1;
|
|
52
53
|
const scope = pageScope(lesson, entries);
|
|
53
54
|
const local = new Map(
|
|
54
|
-
lesson.
|
|
55
|
+
scopeLocals(lesson).map((definition) => [definition.key, definition])
|
|
55
56
|
);
|
|
56
57
|
const factsOf = (key) => key === null ? void 0 : local.get(key) ?? entries.get(key);
|
|
57
58
|
const suspended = suspendedOperators(lesson);
|
|
@@ -185,7 +186,7 @@ function gateIndices(lessons, entries, extras = {}) {
|
|
|
185
186
|
if (!lesson) continue;
|
|
186
187
|
const scope = pageScope(lesson, entries);
|
|
187
188
|
const local = new Map(
|
|
188
|
-
lesson.
|
|
189
|
+
scopeLocals(lesson).map((definition) => [definition.key, definition])
|
|
189
190
|
);
|
|
190
191
|
const factsOf = (key) => key === null ? void 0 : local.get(key) ?? entries.get(key);
|
|
191
192
|
for (const assessmentId of link.assessmentIds) {
|
|
@@ -43,7 +43,7 @@ interface MathGateDiagnostic {
|
|
|
43
43
|
* one the engine ships; its severity is the course's choice, so it is the one
|
|
44
44
|
* context whose severity is not fixed here.
|
|
45
45
|
*/
|
|
46
|
-
readonly severity: 'error' | 'warning';
|
|
46
|
+
readonly severity: 'error' | 'warning' | 'info';
|
|
47
47
|
/** Owned by exactly one rule or invariant in `src/rules/catalog.ts`. */
|
|
48
48
|
readonly code: string;
|
|
49
49
|
readonly file: string;
|
|
@@ -106,6 +106,33 @@ declare function scopeEntry(definition: {
|
|
|
106
106
|
* assessment's math (`mixed-content.ts`), say — doesn't reconstruct it by
|
|
107
107
|
* hand and risk drifting from what the completeness gate actually checks.
|
|
108
108
|
*/
|
|
109
|
+
/**
|
|
110
|
+
* The `notation.local` entries in scope on a page: its own, and for a deck
|
|
111
|
+
* beside its chapter the chapter's, which are the lesson's
|
|
112
|
+
* (`NotationLessonInput.inheritedLocalDefinitions`).
|
|
113
|
+
*/
|
|
114
|
+
declare function scopeLocals(lesson: NotationLessonInput): readonly NotationLessonInput['localDefinitions'][number][];
|
|
115
|
+
/** Every `\explain{key}{inner}` in a span, in source order, with its offset. */
|
|
116
|
+
declare function explainCalls(latex: string): {
|
|
117
|
+
key: string;
|
|
118
|
+
inner: string;
|
|
119
|
+
index: number;
|
|
120
|
+
}[];
|
|
121
|
+
/**
|
|
122
|
+
* The `\explain` wraps in a span that bind nothing new (rule N4): the bare
|
|
123
|
+
* inner form already resolves to the same card, with no glyph left over and
|
|
124
|
+
* no decoration left unexplained (rule N22). Such a wrap costs an author
|
|
125
|
+
* tokens on every use and the resolver nothing; `\explain` is for a compound
|
|
126
|
+
* or a colliding glyph.
|
|
127
|
+
*/
|
|
128
|
+
declare function redundantExplainWraps(latex: string, scope: readonly ScopeEntry[], options?: {
|
|
129
|
+
readonly suspended?: readonly string[];
|
|
130
|
+
readonly derive?: boolean;
|
|
131
|
+
}): {
|
|
132
|
+
key: string;
|
|
133
|
+
inner: string;
|
|
134
|
+
index: number;
|
|
135
|
+
}[];
|
|
109
136
|
declare function pageScope(lesson: NotationLessonInput, sharedByKey: ReadonlyMap<string, ScopeEntry>): ScopeEntry[];
|
|
110
137
|
/**
|
|
111
138
|
* The `\label{eq:…}` a math source carries, as a key plus the anchor the
|
|
@@ -136,4 +163,4 @@ declare function gateContentMath(input: {
|
|
|
136
163
|
readonly lessonAssessments: readonly LessonAssessmentLink[];
|
|
137
164
|
}): MathGateDiagnostic[];
|
|
138
165
|
|
|
139
|
-
export { type AssessmentItemLike, type AssessmentLike, type EquationLink, type LessonAssessmentLink, type MathGateDiagnostic, type MathSpan, type ScopeEntry, currencyDollars, equationLinkFor, gateContentMath, mathSpans, pageScope, scopeEntry, suspendedOperators };
|
|
166
|
+
export { type AssessmentItemLike, type AssessmentLike, type EquationLink, type LessonAssessmentLink, type MathGateDiagnostic, type MathSpan, type ScopeEntry, currencyDollars, equationLinkFor, explainCalls, gateContentMath, mathSpans, pageScope, redundantExplainWraps, scopeEntry, scopeLocals, suspendedOperators };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveMathGlyphs, GlyphResolutionError } from "./math-glyphs.mjs";
|
|
1
|
+
import { resolveMathGlyphs, GlyphResolutionError, fixedLenderMessage, mathExcerpt } from "./math-glyphs.mjs";
|
|
2
2
|
import {
|
|
3
3
|
EQ_LABEL_PATTERN,
|
|
4
4
|
equationAnchorId,
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
import { lintDelimiterSizes } from "./delimiter-size.js";
|
|
8
8
|
import { formulaGlyphScope, unknownFormulaScopeKeys } from "./gloss.js";
|
|
9
9
|
import { blankPropStrings } from "./gate-prop-text.js";
|
|
10
|
+
import { REGISTRY_BODY_FIX, registryGlyphScope } from "./registry-scope.mjs";
|
|
10
11
|
const SEVERITY = {
|
|
11
12
|
body: "error",
|
|
12
13
|
formula: "error",
|
|
@@ -55,10 +56,40 @@ function scopeEntry(definition) {
|
|
|
55
56
|
...definition.fixedIndex ? { fixedIndex: true } : {}
|
|
56
57
|
};
|
|
57
58
|
}
|
|
59
|
+
function scopeLocals(lesson) {
|
|
60
|
+
const inherited = lesson.inheritedLocalDefinitions ?? [];
|
|
61
|
+
return inherited.length === 0 ? lesson.localDefinitions : [...lesson.localDefinitions, ...inherited];
|
|
62
|
+
}
|
|
63
|
+
const EXPLAIN_CALL = /\\explain\{([^{}]+)\}\{((?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*)\}/g;
|
|
64
|
+
function explainCalls(latex) {
|
|
65
|
+
return [...latex.matchAll(EXPLAIN_CALL)].map((match) => ({
|
|
66
|
+
key: match[1],
|
|
67
|
+
inner: match[2],
|
|
68
|
+
index: match.index ?? 0
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
function redundantExplainWraps(latex, scope, options = {}) {
|
|
72
|
+
const redundant = [];
|
|
73
|
+
for (const call of explainCalls(latex)) {
|
|
74
|
+
let bare;
|
|
75
|
+
try {
|
|
76
|
+
bare = resolveMathGlyphs(call.inner, [...scope], {
|
|
77
|
+
suspended: options.suspended ?? [],
|
|
78
|
+
derive: options.derive ?? false
|
|
79
|
+
});
|
|
80
|
+
} catch {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (bare.unresolved.length === 0 && bare.bindings.length > 0 && bare.bindings.every((binding) => binding.key === call.key && binding.level !== "explicit") && bare.decorations.every((decoration) => decoration.wholeForm !== null || decoration.operator !== null)) {
|
|
84
|
+
redundant.push(call);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return redundant;
|
|
88
|
+
}
|
|
58
89
|
function pageScope(lesson, sharedByKey) {
|
|
59
|
-
const local = lesson.
|
|
90
|
+
const local = scopeLocals(lesson).map(scopeEntry);
|
|
60
91
|
const localKeys = new Set(local.map((entry) => entry.key));
|
|
61
|
-
const referenced =
|
|
92
|
+
const referenced = new Set(lesson.inheritedKeys ?? []);
|
|
62
93
|
for (const pattern of [TERM_KEY, EXPLAIN_KEY]) {
|
|
63
94
|
for (const match of lesson.body.matchAll(pattern)) referenced.add(match[1]);
|
|
64
95
|
}
|
|
@@ -117,6 +148,21 @@ function resolveInto(latex, scope, context, file, line, column, fix, out, equati
|
|
|
117
148
|
});
|
|
118
149
|
return;
|
|
119
150
|
}
|
|
151
|
+
if (context !== "formula") {
|
|
152
|
+
for (const wrap of redundantExplainWraps(stripEquationLabels(latex), scope, { suspended, derive })) {
|
|
153
|
+
out.push({
|
|
154
|
+
context,
|
|
155
|
+
code: "explain-redundant",
|
|
156
|
+
severity: "info",
|
|
157
|
+
file,
|
|
158
|
+
line,
|
|
159
|
+
...column === void 0 ? {} : { column },
|
|
160
|
+
token: wrap.inner,
|
|
161
|
+
...at,
|
|
162
|
+
message: `\\explain{${wrap.key}}{${wrap.inner}} wraps what ${wrap.inner} already resolves to on this page; write it bare \u2014 \\explain is for a compound or a colliding glyph (rule N4)`
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
120
166
|
for (const item of result.unresolved) {
|
|
121
167
|
const { token } = item;
|
|
122
168
|
if ("suspended" in item && typeof item.suspended === "string") {
|
|
@@ -133,7 +179,12 @@ function resolveInto(latex, scope, context, file, line, column, fix, out, equati
|
|
|
133
179
|
continue;
|
|
134
180
|
}
|
|
135
181
|
const fontHint = "font" in item && typeof item.font === "string" ? ` ${token} is a different symbol from the plain letter: give it its own entry (latex: ${token}) or wrap it in \\explain{key}{${token}}.` : "";
|
|
136
|
-
const
|
|
182
|
+
const fixedLenders = "fixedLenders" in item && Array.isArray(item.fixedLenders) ? item.fixedLenders : [];
|
|
183
|
+
const instance = "instance" in item && typeof item.instance === "string" ? item.instance : token;
|
|
184
|
+
const fixedHint = fixedLenders.map((lender) => ` ${fixedLenderMessage(lender, instance)}`).join("");
|
|
185
|
+
const fixedKeys = new Set(fixedLenders.map((lender) => lender.key));
|
|
186
|
+
const lenders = "lenders" in item && Array.isArray(item.lenders) ? item.lenders.filter((key) => !fixedKeys.has(key)) : [];
|
|
187
|
+
const lenderHint = lenders.length > 0 ? ` ${lenders.join(", ")} ${lenders.length === 1 ? "has" : "have"} ${token} with a subscript, and an indexed entry explains its own instances only, never a bare ${token} (rule N4). If ${token} is a different object, give it its own entry (latex: ${token}); if it is shorthand for that entry, wrap it in \\explain{${lenders[0]}}{${token}}.` : "";
|
|
137
188
|
out.push({
|
|
138
189
|
context,
|
|
139
190
|
code: "unresolved-glyph",
|
|
@@ -142,7 +193,7 @@ function resolveInto(latex, scope, context, file, line, column, fix, out, equati
|
|
|
142
193
|
line,
|
|
143
194
|
token,
|
|
144
195
|
...at,
|
|
145
|
-
message: `Unresolved notation ${JSON.stringify(token)} in ${context} math.${fontHint}${lenderHint} ${fix}`
|
|
196
|
+
message: `Unresolved notation ${JSON.stringify(token)} in ${context} math, in ${mathExcerpt(latex)}.${fontHint}${fixedHint}${lenderHint} ${fix}`
|
|
146
197
|
});
|
|
147
198
|
}
|
|
148
199
|
}
|
|
@@ -170,6 +221,21 @@ function currencyDollars(body) {
|
|
|
170
221
|
});
|
|
171
222
|
return spans;
|
|
172
223
|
}
|
|
224
|
+
function reportCurrencyDollars(body, file, bodyOffset, out) {
|
|
225
|
+
for (const span of currencyDollars(body)) {
|
|
226
|
+
const shown = span.latex.replace(/\s+$/, "");
|
|
227
|
+
out.push({
|
|
228
|
+
context: "body",
|
|
229
|
+
code: "currency-dollar",
|
|
230
|
+
severity: SEVERITY.body,
|
|
231
|
+
file,
|
|
232
|
+
line: span.line + bodyOffset,
|
|
233
|
+
column: span.column,
|
|
234
|
+
token: `$${span.latex}$`,
|
|
235
|
+
message: `"$${span.latex}$" reads as math: remark-math pairs any two dollars on a line, so the page shows "${shown}" without them. Write a currency dollar as \\$ (\\$5 to \\$6), or the amount in words.`
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
173
239
|
const BODY_FIX = "Introduce the symbol with [[key]], define it in notation.local, or wrap it in \\explain{key}{latex}.";
|
|
174
240
|
function dedupeScope(scope) {
|
|
175
241
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -251,19 +317,7 @@ function gateContentMath(input) {
|
|
|
251
317
|
true
|
|
252
318
|
);
|
|
253
319
|
}
|
|
254
|
-
|
|
255
|
-
const shown = span.latex.replace(/\s+$/, "");
|
|
256
|
-
diagnostics.push({
|
|
257
|
-
context: "body",
|
|
258
|
-
code: "currency-dollar",
|
|
259
|
-
severity: SEVERITY.body,
|
|
260
|
-
file,
|
|
261
|
-
line: span.line + bodyOffset,
|
|
262
|
-
column: span.column,
|
|
263
|
-
token: `$${span.latex}$`,
|
|
264
|
-
message: `"$${span.latex}$" reads as math: remark-math pairs any two dollars on a line, so the page shows "${shown}" without them. Write a currency dollar as \\$ (\\$5 to \\$6), or the amount in words.`
|
|
265
|
-
});
|
|
266
|
-
}
|
|
320
|
+
reportCurrencyDollars(lesson.body, file, bodyOffset, diagnostics);
|
|
267
321
|
for (const definition of lesson.localDefinitions) {
|
|
268
322
|
if (!definition.formula) continue;
|
|
269
323
|
resolveInto(
|
|
@@ -285,6 +339,30 @@ function gateContentMath(input) {
|
|
|
285
339
|
reportUnknownScopeKeys(definition, cardKeys, file, diagnostics);
|
|
286
340
|
}
|
|
287
341
|
}
|
|
342
|
+
const registryScope = registryGlyphScope(
|
|
343
|
+
notationInput.sharedDefinitions
|
|
344
|
+
);
|
|
345
|
+
for (const definition of notationInput.sharedDefinitions) {
|
|
346
|
+
const file = definition.source.file;
|
|
347
|
+
const bodyOffset = (definition.bodyLine ?? 1) - 1;
|
|
348
|
+
for (const span of mathSpans(definition.body)) {
|
|
349
|
+
resolveInto(
|
|
350
|
+
span.latex,
|
|
351
|
+
registryScope,
|
|
352
|
+
"body",
|
|
353
|
+
file,
|
|
354
|
+
span.line + bodyOffset,
|
|
355
|
+
span.column,
|
|
356
|
+
REGISTRY_BODY_FIX,
|
|
357
|
+
diagnostics,
|
|
358
|
+
void 0,
|
|
359
|
+
// An entry's body suspends nothing and derives no cards.
|
|
360
|
+
[],
|
|
361
|
+
false
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
reportCurrencyDollars(definition.body, file, bodyOffset, diagnostics);
|
|
365
|
+
}
|
|
288
366
|
for (const definition of notationInput.sharedDefinitions) {
|
|
289
367
|
if (!definition.formula) continue;
|
|
290
368
|
resolveInto(
|
|
@@ -349,9 +427,12 @@ function gateContentMath(input) {
|
|
|
349
427
|
export {
|
|
350
428
|
currencyDollars,
|
|
351
429
|
equationLinkFor,
|
|
430
|
+
explainCalls,
|
|
352
431
|
gateContentMath,
|
|
353
432
|
mathSpans,
|
|
354
433
|
pageScope,
|
|
434
|
+
redundantExplainWraps,
|
|
355
435
|
scopeEntry,
|
|
436
|
+
scopeLocals,
|
|
356
437
|
suspendedOperators
|
|
357
438
|
};
|
|
@@ -7,9 +7,12 @@
|
|
|
7
7
|
* Starting at each lesson's own imports, this follows every relative import
|
|
8
8
|
* — the course's visuals, wherever they live — and reports each file that
|
|
9
9
|
* imports `katex` as `lab-katex-direct`, with the chain of imports that
|
|
10
|
-
* reaches it.
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* reaches it. The answer is per lesson: a helper that three lessons reach is
|
|
11
|
+
* reported three times, each with that lesson's own chain, and a lesson that
|
|
12
|
+
* reaches none is clean — so `content check <lesson>` can say so. A package
|
|
13
|
+
* import (`explico/…`, `react`) is not followed: explico's own KaTeX use is
|
|
14
|
+
* the seam. A type-only import and the KaTeX stylesheet draw nothing and are
|
|
15
|
+
* not reported.
|
|
13
16
|
*
|
|
14
17
|
* `figure-symbols.ts` checks the same promise on the rendered page, which
|
|
15
18
|
* also catches math a lab draws some other way.
|
|
@@ -20,7 +23,7 @@ interface LabKatexDiagnostic {
|
|
|
20
23
|
readonly message: string;
|
|
21
24
|
/** The file that imports `katex`, repository-relative. */
|
|
22
25
|
readonly file: string;
|
|
23
|
-
/** The
|
|
26
|
+
/** The lesson whose imports reach it; one diagnostic per lesson and file. */
|
|
24
27
|
readonly lessonId: string;
|
|
25
28
|
}
|
|
26
29
|
interface LabKatexLessonInput {
|
|
@@ -35,8 +38,9 @@ interface LabKatexLessonInput {
|
|
|
35
38
|
*/
|
|
36
39
|
declare function importSpecifiers(source: string): string[];
|
|
37
40
|
/**
|
|
38
|
-
* Each course file that
|
|
39
|
-
* `root` is the repository root the lesson
|
|
41
|
+
* Each course file that imports `katex`, once for every lesson whose imports
|
|
42
|
+
* reach it, by the shortest chain. `root` is the repository root the lesson
|
|
43
|
+
* paths are relative to.
|
|
40
44
|
*/
|
|
41
45
|
declare function validateLabKatexImports(lessons: readonly LabKatexLessonInput[], root?: string): LabKatexDiagnostic[];
|
|
42
46
|
|
|
@@ -44,18 +44,23 @@ function resolveRelative(fromFile, specifier) {
|
|
|
44
44
|
return candidates.find(isFile);
|
|
45
45
|
}
|
|
46
46
|
function validateLabKatexImports(lessons, root = process.cwd()) {
|
|
47
|
+
const sources = /* @__PURE__ */ new Map();
|
|
47
48
|
const read = (path) => {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
if (!sources.has(path)) {
|
|
50
|
+
try {
|
|
51
|
+
sources.set(path, readFileSync(path, "utf8"));
|
|
52
|
+
} catch {
|
|
53
|
+
sources.set(path, void 0);
|
|
54
|
+
}
|
|
52
55
|
}
|
|
56
|
+
return sources.get(path);
|
|
53
57
|
};
|
|
54
58
|
const repoPath = (path) => relative(root, path).split(sep).join("/");
|
|
55
|
-
const diagnostics =
|
|
56
|
-
const seen = /* @__PURE__ */ new Set();
|
|
59
|
+
const diagnostics = [];
|
|
57
60
|
for (const lesson of [...lessons].sort((a, b) => a.lessonId.localeCompare(b.lessonId))) {
|
|
58
61
|
const lessonPath = join(root, lesson.file);
|
|
62
|
+
const reported = /* @__PURE__ */ new Set();
|
|
63
|
+
const seen = /* @__PURE__ */ new Set();
|
|
59
64
|
const queue = [
|
|
60
65
|
{ path: lessonPath, source: stripFencedCode(lesson.body), chain: [lesson.file] }
|
|
61
66
|
];
|
|
@@ -64,8 +69,9 @@ function validateLabKatexImports(lessons, root = process.cwd()) {
|
|
|
64
69
|
for (const specifier of importSpecifiers(source)) {
|
|
65
70
|
if (isKatexModule(specifier)) {
|
|
66
71
|
const file = repoPath(path);
|
|
67
|
-
if (!
|
|
68
|
-
|
|
72
|
+
if (!reported.has(file)) {
|
|
73
|
+
reported.add(file);
|
|
74
|
+
diagnostics.push({
|
|
69
75
|
code: "lab-katex-direct",
|
|
70
76
|
severity: "error",
|
|
71
77
|
file,
|
|
@@ -88,7 +94,9 @@ function validateLabKatexImports(lessons, root = process.cwd()) {
|
|
|
88
94
|
}
|
|
89
95
|
}
|
|
90
96
|
}
|
|
91
|
-
return
|
|
97
|
+
return diagnostics.sort(
|
|
98
|
+
(a, b) => a.file.localeCompare(b.file) || a.lessonId.localeCompare(b.lessonId)
|
|
99
|
+
);
|
|
92
100
|
}
|
|
93
101
|
export {
|
|
94
102
|
importSpecifiers,
|
|
@@ -2,18 +2,44 @@ import { loadManifest } from "../compiler/manifest.js";
|
|
|
2
2
|
import {
|
|
3
3
|
bindScopeToPage,
|
|
4
4
|
registerPageNotationDependencies,
|
|
5
|
-
routeIdFromLocals
|
|
5
|
+
routeIdFromLocals,
|
|
6
|
+
scopeLessonsOf
|
|
6
7
|
} from "./page-notation.js";
|
|
7
8
|
const TERM_KEY = /\[\[\s*([a-z0-9]+(?:[.-][a-z0-9]+)*)\s*\]\]/g;
|
|
8
9
|
const EXPLAIN_KEY = /\\explain\s*\{([a-z0-9]+(?:[.-][a-z0-9]+)*)\}/g;
|
|
9
10
|
async function labMathScope(notation, body = "", component) {
|
|
10
|
-
const
|
|
11
|
+
const manifest = await loadManifest();
|
|
12
|
+
const routeId = component ? routeIdFromLocals(component.locals) : void 0;
|
|
13
|
+
const deck = routeId === void 0 ? void 0 : manifest.decks?.find(
|
|
14
|
+
(entry) => entry.slug === routeId && (entry.chapter !== void 0 || entry.part !== void 0)
|
|
15
|
+
);
|
|
16
|
+
const scopeLessons = deck ? scopeLessonsOf(manifest, deck.pageId) : [];
|
|
17
|
+
const lessonLocal = [];
|
|
18
|
+
for (const lessonId of scopeLessons) {
|
|
19
|
+
for (const definition of manifest.notation.definitions) {
|
|
20
|
+
if (definition.kind !== "local" || !definition.id.startsWith(`page:${lessonId}:`)) continue;
|
|
21
|
+
if (lessonLocal.some((known) => known.key === definition.key)) continue;
|
|
22
|
+
lessonLocal.push({
|
|
23
|
+
key: definition.key,
|
|
24
|
+
latex: definition.notation,
|
|
25
|
+
...definition.fixedIndex ? { fixedIndex: true } : {}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const lessonKeys = scopeLessons.flatMap(
|
|
30
|
+
(lessonId) => (manifest.lessons.find((lesson) => lesson.id === lessonId)?.notation.bindings ?? []).map((binding) => binding.key)
|
|
31
|
+
);
|
|
32
|
+
const ownLocal = notation?.local ?? [];
|
|
33
|
+
const local = [
|
|
34
|
+
...ownLocal,
|
|
35
|
+
...lessonLocal.filter((definition) => !ownLocal.some((own) => own.key === definition.key))
|
|
36
|
+
];
|
|
11
37
|
const scope = local.map((definition) => ({
|
|
12
38
|
key: definition.key,
|
|
13
39
|
notation: definition.latex,
|
|
14
40
|
...definition.fixedIndex === true ? { fixedIndex: true } : {}
|
|
15
41
|
}));
|
|
16
|
-
const referenced =
|
|
42
|
+
const referenced = new Set(lessonKeys);
|
|
17
43
|
const explicitlyExplained = /* @__PURE__ */ new Set();
|
|
18
44
|
for (const pattern of [TERM_KEY, EXPLAIN_KEY]) {
|
|
19
45
|
for (const match of body.matchAll(pattern)) {
|
|
@@ -22,7 +48,6 @@ async function labMathScope(notation, body = "", component) {
|
|
|
22
48
|
}
|
|
23
49
|
}
|
|
24
50
|
const localKeys = new Set(local.map((definition) => definition.key));
|
|
25
|
-
const manifest = await loadManifest();
|
|
26
51
|
const sharedByKey = new Map(
|
|
27
52
|
manifest.notation.definitions.filter((definition) => definition.kind === "shared").map((definition) => [definition.key, definition])
|
|
28
53
|
);
|
|
@@ -97,6 +97,31 @@ export function indexDisplayLatex(latex: string): string;
|
|
|
97
97
|
* @returns {boolean}
|
|
98
98
|
*/
|
|
99
99
|
export function entryHasIndex(latex: string): boolean;
|
|
100
|
+
/**
|
|
101
|
+
* The math span a finding sits in, as an author wrote it: `$…$`, whitespace
|
|
102
|
+
* collapsed, cut at 100 characters. A line number can drift (a loader that
|
|
103
|
+
* hands over a body, a generated file), but the span can be searched for, so a
|
|
104
|
+
* message that quotes it still leads to the spot.
|
|
105
|
+
*
|
|
106
|
+
* @param {string} latex
|
|
107
|
+
* @returns {string}
|
|
108
|
+
*/
|
|
109
|
+
export function mathExcerpt(latex: string): string;
|
|
110
|
+
/**
|
|
111
|
+
* What to tell an author whose subscripted symbol a family would have
|
|
112
|
+
* explained, were its index not fixed (rule N23): the cause, and the repair
|
|
113
|
+
* that fits it. One sentence, for an unresolved item's `fixedLenders`.
|
|
114
|
+
*
|
|
115
|
+
* @param {{ key: string, via: string, card?: string, letter?: string }} lender
|
|
116
|
+
* @param {string} instance The subscripted symbol as written, `r_2`.
|
|
117
|
+
* @returns {string}
|
|
118
|
+
*/
|
|
119
|
+
export function fixedLenderMessage(lender: {
|
|
120
|
+
key: string;
|
|
121
|
+
via: string;
|
|
122
|
+
card?: string;
|
|
123
|
+
letter?: string;
|
|
124
|
+
}, instance: string): string;
|
|
100
125
|
/**
|
|
101
126
|
* Resolve and annotate one KaTeX expression against a page's flat glyph table.
|
|
102
127
|
* Source offsets in the result always refer to the original, unmodified LaTeX.
|