explico 0.12.0 → 0.13.1
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 +1137 -1
- package/README.md +91 -13
- package/dist/compiler/astro.d.ts +4 -3
- package/dist/compiler/astro.js +45 -15
- package/dist/compiler/cli.d.ts +26 -12
- package/dist/compiler/cli.js +96 -25
- package/dist/compiler/collections.d.ts +9 -1
- package/dist/compiler/collections.js +55 -16
- package/dist/compiler/content-schema.d.ts +31 -0
- package/dist/compiler/content-schema.js +82 -0
- package/dist/compiler/content-schemas.d.ts +422 -0
- package/dist/compiler/content-schemas.js +229 -0
- package/dist/compiler/course-config.d.ts +1 -1
- package/dist/compiler/course-config.js +30 -2
- package/dist/compiler/exports/combined-html.d.ts +97 -0
- package/dist/compiler/exports/combined-html.js +492 -0
- package/dist/compiler/exports/download-index.d.ts +9 -0
- package/dist/compiler/exports/download-index.js +33 -0
- package/dist/compiler/exports/export-chapters.d.ts +98 -0
- package/dist/compiler/exports/export-chapters.js +164 -0
- package/dist/compiler/exports/export-pages.d.ts +48 -0
- package/dist/compiler/exports/export-pages.js +22 -0
- package/dist/compiler/exports/html-tree.d.ts +22 -0
- package/dist/compiler/exports/html-tree.js +47 -0
- package/dist/compiler/exports/latex/capture.d.ts +106 -0
- package/dist/compiler/exports/latex/capture.js +435 -0
- package/dist/compiler/exports/latex/compile.d.ts +73 -0
- package/dist/compiler/exports/latex/compile.js +115 -0
- package/dist/compiler/exports/latex/document.d.ts +102 -0
- package/dist/compiler/exports/latex/document.js +349 -0
- package/dist/compiler/exports/latex/explico.cls +415 -0
- package/dist/compiler/exports/latex/numbering.d.ts +18 -0
- package/dist/compiler/exports/latex/numbering.js +57 -0
- package/dist/compiler/exports/latex/serialize.d.ts +112 -0
- package/dist/compiler/exports/latex/serialize.js +606 -0
- package/dist/compiler/exports/latex-export.d.ts +44 -0
- package/dist/compiler/exports/latex-export.js +169 -0
- package/dist/compiler/exports/markdown/serialize.d.ts +51 -0
- package/dist/compiler/exports/markdown/serialize.js +527 -0
- package/dist/compiler/exports/markdown-export.d.ts +39 -0
- package/dist/compiler/exports/markdown-export.js +240 -0
- package/dist/compiler/exports/standalone-html.d.ts +14 -5
- package/dist/compiler/exports/standalone-html.js +23 -51
- package/dist/compiler/exports/static-tree.d.ts +253 -0
- package/dist/compiler/exports/static-tree.js +625 -0
- package/dist/compiler/exports/zip.d.ts +16 -0
- package/dist/compiler/exports/zip.js +72 -0
- package/dist/compiler/lesson-derivation.d.ts +8 -2
- package/dist/compiler/lesson-derivation.js +6 -7
- package/dist/compiler/manifest.d.ts +87 -6
- package/dist/compiler/manifest.js +174 -31
- package/dist/compiler/render-diagnostics.d.ts +65 -0
- package/dist/compiler/render-diagnostics.js +56 -0
- package/dist/compiler/resolution-report.d.ts +2 -1
- package/dist/{course-config-Dsnri9Tk.d.ts → course-config-rRtME5UV.d.ts} +56 -13
- package/dist/curriculum/validation.d.ts +9 -3
- package/dist/curriculum/validation.js +35 -17
- package/dist/reference/base-abbreviations.d.mts +2 -2
- package/dist/reference/base-abbreviations.mjs +2 -2
- package/dist/reference/base-operators.d.mts +2 -2
- package/dist/reference/base-operators.mjs +2 -2
- package/dist/reference/base-roman-labels.d.mts +4 -3
- package/dist/reference/base-roman-labels.mjs +4 -3
- package/dist/reference/citation-format.d.mts +35 -0
- package/dist/reference/citation-format.mjs +74 -0
- package/dist/reference/consistency.d.ts +5 -9
- package/dist/reference/consistency.js +34 -38
- package/dist/reference/curriculum-alignment.js +3 -24
- package/dist/reference/derived-entries.d.ts +45 -0
- package/dist/reference/derived-entries.js +50 -0
- package/dist/reference/derived-notation.d.mts +125 -0
- package/dist/reference/derived-notation.mjs +121 -0
- package/dist/reference/diagram-fit.d.ts +45 -0
- package/dist/reference/diagram-fit.js +54 -0
- package/dist/reference/diagram-layout.d.ts +59 -10
- package/dist/reference/diagram-layout.js +85 -24
- package/dist/reference/diagrams-validate.d.ts +1 -1
- package/dist/reference/equations-validate.d.ts +1 -1
- package/dist/reference/equations-validate.js +26 -1
- package/dist/reference/equations.d.mts +27 -0
- package/dist/reference/equations.mjs +120 -22
- package/dist/reference/examples-validate.d.ts +1 -1
- package/dist/reference/figure-fit.d.ts +70 -0
- package/dist/reference/figure-fit.js +60 -0
- package/dist/reference/figure-pictures.d.ts +27 -0
- package/dist/reference/figure-pictures.js +49 -0
- package/dist/reference/figures-validate.d.ts +1 -1
- package/dist/reference/gate-abbreviations.d.ts +1 -1
- package/dist/reference/gate-abbreviations.js +1 -1
- package/dist/reference/gate-course-rules.d.ts +4 -3
- package/dist/reference/gate-course-rules.js +9 -4
- package/dist/reference/gate-heading-length.d.ts +2 -0
- package/dist/reference/gate-heading-length.js +7 -5
- package/dist/reference/gate-heading-structure.js +26 -0
- package/dist/reference/gate-indices.d.ts +35 -0
- package/dist/reference/gate-indices.js +299 -0
- package/dist/reference/gate-math.d.ts +32 -2
- package/dist/reference/gate-math.js +68 -12
- package/dist/reference/gate-mdx-expression.d.ts +17 -0
- package/dist/reference/gate-mdx-expression.js +29 -0
- package/dist/reference/gate-part-pages.d.ts +1 -1
- package/dist/reference/gate-prop-text.d.ts +28 -0
- package/dist/reference/gate-prop-text.js +106 -0
- package/dist/reference/gate-prose-notation.d.ts +3 -1
- package/dist/reference/gate-prose-notation.js +4 -2
- package/dist/reference/gate-retired-keys.d.ts +35 -0
- package/dist/reference/gate-retired-keys.js +58 -0
- package/dist/reference/gloss.d.ts +2 -0
- package/dist/reference/katex-measure.d.ts +17 -0
- package/dist/reference/katex-measure.js +223 -0
- package/dist/reference/keyed-float-validate.js +12 -0
- package/dist/reference/keyed-jsx-floats.d.mts +2 -2
- package/dist/reference/keyed-jsx-floats.mjs +11 -14
- package/dist/reference/lab-math-scope.d.ts +1 -0
- package/dist/reference/lab-math-scope.js +13 -2
- package/dist/reference/math-glyphs.d.mts +94 -2
- package/dist/reference/math-glyphs.mjs +851 -46
- package/dist/reference/mixed-content.d.ts +8 -3
- package/dist/reference/mixed-content.js +4 -2
- package/dist/reference/page-notation.d.ts +34 -4
- package/dist/reference/page-notation.js +54 -2
- package/dist/reference/references.d.ts +11 -1
- package/dist/reference/references.js +6 -0
- package/dist/reference/registry.js +104 -10
- package/dist/reference/rehype-fail-katex-errors.d.mts +9 -0
- package/dist/reference/rehype-fail-katex-errors.mjs +19 -0
- package/dist/reference/rehype-heading-anchors.mjs +11 -1
- package/dist/reference/remark-citation.mjs +0 -0
- package/dist/reference/remark-notation.mjs +84 -20
- package/dist/reference/render-lab-math.d.ts +2 -0
- package/dist/reference/render-lab-math.js +17 -5
- package/dist/reference/sections-validate.d.ts +28 -3
- package/dist/reference/sections-validate.js +31 -4
- package/dist/reference/sections.d.mts +24 -6
- package/dist/reference/sections.mjs +92 -17
- package/dist/reference/tables-validate.d.ts +1 -1
- package/dist/reference/tables-validate.js +24 -1
- package/dist/reference/text-measure.d.ts +10 -0
- package/dist/reference/text-measure.js +41 -0
- package/dist/reference/text-widths.d.ts +10 -0
- package/dist/reference/text-widths.js +1238 -0
- package/dist/reference/types.d.ts +64 -8
- package/dist/rules/catalog.js +191 -30
- package/dist/rules/in-effect.d.ts +1 -1
- package/dist/rules/rules.schema.json +79 -24
- package/dist/rules/settings.d.ts +1 -1
- package/dist/rules/settings.js +7 -0
- package/dist/session/PreferenceStore.d.ts +18 -1
- package/dist/session/PreferenceStore.js +29 -1
- package/docs/authoring/README.md +32 -21
- package/docs/authoring/agents.md +38 -7
- package/docs/authoring/components.md +111 -6
- package/docs/authoring/migrating.md +707 -0
- package/docs/authoring/notation.md +281 -29
- package/docs/authoring/retired.md +1 -0
- package/docs/authoring/structure-rules.md +10 -41
- package/docs/authoring/structure.md +53 -2
- package/docs/authoring/visualizations.md +144 -18
- package/package.json +12 -7
- package/src/compiler/astro.ts +57 -17
- package/src/compiler/cli.ts +143 -35
- package/src/compiler/collections.ts +92 -19
- package/src/compiler/content-schema.ts +108 -0
- package/src/compiler/content-schemas.ts +391 -0
- package/src/compiler/course-config.ts +86 -15
- package/src/compiler/exports/combined-html.ts +669 -0
- package/src/compiler/exports/download-index.ts +36 -0
- package/src/compiler/exports/export-chapters.ts +233 -0
- package/src/compiler/exports/export-pages.ts +44 -0
- package/src/compiler/exports/html-tree.ts +62 -0
- package/src/compiler/exports/latex/capture.ts +651 -0
- package/src/compiler/exports/latex/compile.ts +168 -0
- package/src/compiler/exports/latex/document.ts +479 -0
- package/src/compiler/exports/latex/explico.cls +415 -0
- package/src/compiler/exports/latex/numbering.ts +74 -0
- package/src/compiler/exports/latex/serialize.ts +787 -0
- package/src/compiler/exports/latex/texlive-packages.txt +76 -0
- package/src/compiler/exports/latex-export.ts +255 -0
- package/src/compiler/exports/markdown/serialize.ts +632 -0
- package/src/compiler/exports/markdown-export.ts +329 -0
- package/src/compiler/exports/standalone-html.ts +39 -70
- package/src/compiler/exports/static-tree.ts +944 -0
- package/src/compiler/exports/zip.ts +88 -0
- package/src/compiler/lesson-derivation.ts +13 -8
- package/src/compiler/manifest.ts +299 -28
- package/src/compiler/render-diagnostics.ts +111 -0
- package/src/components/HoverPanel.astro +204 -6
- package/src/components/PartOverview.astro +12 -9
- package/src/components/abbreviation/AbbreviationLayer.astro +21 -12
- package/src/components/assessments/AssessmentRunner.tsx +7 -1
- package/src/components/assessments/AssessmentSet.astro +14 -1
- package/src/components/block-heading.ts +38 -0
- package/src/components/card-arrow.ts +18 -4
- package/src/components/card-carry.ts +406 -0
- package/src/components/card-drag.ts +1 -30
- package/src/components/card-fetch.ts +87 -0
- package/src/components/card-manager.ts +1222 -38
- package/src/components/card-memory.ts +5 -1
- package/src/components/card-placement.ts +211 -13
- package/src/components/card-side.ts +281 -31
- package/src/components/citation/CitationLayer.astro +45 -40
- package/src/components/diagram/Diagram.astro +123 -44
- package/src/components/figure/Figure.astro +11 -0
- package/src/components/figure/FloatCaption.astro +4 -17
- package/src/components/float-toolbar.ts +274 -0
- package/src/components/memo/Memo.astro +21 -10
- package/src/components/memo/ShortNote.astro +4 -0
- package/src/components/notation/NotationGlossary.astro +22 -17
- package/src/components/notation/NotationLayer.astro +321 -79
- package/src/components/notation/NotationMath.astro +8 -1
- package/src/components/notation/render-notation-math.ts +51 -5
- package/src/components/readable-text.ts +25 -0
- package/src/components/reference/FloatRefLayer.astro +162 -89
- package/src/components/starlight/HeadingAnchorEnhancer.astro +13 -8
- package/src/components/starlight/KeyedFloatEnhancer.astro +28 -9
- package/src/components/starlight/LayoutPageSidebar.astro +17 -3
- package/src/components/starlight/LayoutSocialIcons.astro +79 -29
- package/src/components/starlight/LessonExportActions.astro +384 -310
- package/src/components/starlight/LessonFooter.astro +47 -9
- package/src/components/starlight/PrintController.astro +28 -0
- package/src/components/starlight/TextSizeControl.astro +137 -0
- package/src/components/starlight/print-controller.ts +380 -0
- package/src/components/starlight/section-summary.ts +13 -5
- package/src/components/table/Table.astro +13 -21
- package/src/components/table/table-truncation.ts +15 -0
- package/src/content-config.ts +20 -312
- package/src/curriculum/validation.ts +72 -24
- package/src/reference/base-abbreviations.mjs +2 -2
- package/src/reference/base-operators.mjs +2 -2
- package/src/reference/base-roman-labels.mjs +4 -3
- package/src/reference/citation-format.mjs +74 -0
- package/src/reference/consistency.ts +47 -61
- package/src/reference/curriculum-alignment.ts +4 -41
- package/src/reference/derived-entries.ts +68 -0
- package/src/reference/derived-notation.mjs +121 -0
- package/src/reference/diagram-fit.ts +104 -0
- package/src/reference/diagram-layout.ts +182 -51
- package/src/reference/diagrams-validate.ts +2 -1
- package/src/reference/equations-validate.ts +41 -2
- package/src/reference/equations.mjs +120 -22
- package/src/reference/examples-validate.ts +1 -0
- package/src/reference/figure-fit.ts +139 -0
- package/src/reference/figure-pictures.ts +96 -0
- package/src/reference/figures-validate.ts +2 -1
- package/src/reference/gate-abbreviations.ts +1 -1
- package/src/reference/gate-course-rules.ts +22 -7
- package/src/reference/gate-heading-length.ts +25 -7
- package/src/reference/gate-heading-structure.ts +40 -1
- package/src/reference/gate-indices.ts +470 -0
- package/src/reference/gate-math.ts +102 -13
- package/src/reference/gate-mdx-expression.ts +45 -0
- package/src/reference/gate-prop-text.ts +145 -0
- package/src/reference/gate-prose-notation.ts +19 -5
- package/src/reference/gate-retired-keys.ts +101 -0
- package/src/reference/gloss.ts +2 -0
- package/src/reference/katex-measure.ts +384 -0
- package/src/reference/keyed-float-validate.ts +22 -0
- package/src/reference/keyed-jsx-floats.mjs +11 -14
- package/src/reference/lab-math-scope.ts +15 -1
- package/src/reference/math-glyphs.mjs +851 -46
- package/src/reference/mixed-content.ts +11 -2
- package/src/reference/page-notation.ts +100 -3
- package/src/reference/references.ts +14 -0
- package/src/reference/registry.ts +154 -8
- package/src/reference/rehype-fail-katex-errors.mjs +19 -0
- package/src/reference/rehype-heading-anchors.mjs +11 -1
- package/src/reference/remark-citation.mjs +0 -0
- package/src/reference/remark-notation.mjs +84 -20
- package/src/reference/render-lab-math.ts +29 -5
- package/src/reference/sections-validate.ts +67 -3
- package/src/reference/sections.mjs +92 -17
- package/src/reference/tables-validate.ts +40 -2
- package/src/reference/text-measure.ts +63 -0
- package/src/reference/text-widths.ts +133 -0
- package/src/reference/types.ts +75 -8
- package/src/rules/catalog.ts +205 -29
- package/src/rules/settings.ts +10 -0
- package/src/session/PreferenceStore.ts +53 -0
- package/src/styles/global.css +291 -75
|
@@ -10,7 +10,6 @@ interface CompetencyDefinition {
|
|
|
10
10
|
}
|
|
11
11
|
interface LessonDefinition {
|
|
12
12
|
readonly id: string;
|
|
13
|
-
readonly status: 'draft' | 'in-review' | 'reviewed';
|
|
14
13
|
readonly requires: readonly string[];
|
|
15
14
|
readonly teaches: readonly string[];
|
|
16
15
|
readonly assessments: readonly string[];
|
|
@@ -23,6 +22,8 @@ interface AssessmentItemBase {
|
|
|
23
22
|
readonly id: string;
|
|
24
23
|
readonly competencyId: string;
|
|
25
24
|
readonly evidenceKind: 'direct' | 'transfer';
|
|
25
|
+
readonly prompt?: string;
|
|
26
|
+
readonly explanation?: string;
|
|
26
27
|
}
|
|
27
28
|
interface NumericAssessmentItem extends AssessmentItemBase {
|
|
28
29
|
readonly type: 'numeric';
|
|
@@ -45,7 +46,6 @@ interface AssessmentDefinition {
|
|
|
45
46
|
}
|
|
46
47
|
interface SourceDefinition {
|
|
47
48
|
readonly id: string;
|
|
48
|
-
readonly editorialStatus: 'draft' | 'in-review' | 'reviewed';
|
|
49
49
|
}
|
|
50
50
|
interface TrackDefinition {
|
|
51
51
|
readonly id: string;
|
|
@@ -59,8 +59,14 @@ interface CurriculumCatalog {
|
|
|
59
59
|
readonly tracks: readonly TrackDefinition[];
|
|
60
60
|
}
|
|
61
61
|
interface CurriculumIssue {
|
|
62
|
-
readonly kind: 'invalid-id' | 'duplicate-id' | 'unknown-reference' | 'duplicate-reference' | 'self-prerequisite' | 'cycle' | 'lesson-order' | 'track-order' | 'assessment-coverage' | 'invalid-assessment' | '
|
|
62
|
+
readonly kind: 'invalid-id' | 'duplicate-id' | 'unknown-reference' | 'duplicate-reference' | 'self-prerequisite' | 'cycle' | 'lesson-order' | 'track-order' | 'assessment-coverage' | 'invalid-assessment' | 'missing-assumptions' | 'teaches-support' | 'retired-key' | 'content-schema' | 'part-empty' | 'lesson-outside-parts' | 'unsupported-key' | 'ignored-key' | 'markup-in-prop' | 'citation-in-link';
|
|
63
63
|
readonly message: string;
|
|
64
|
+
/**
|
|
65
|
+
* The content file the issue is about, when it is about one file rather
|
|
66
|
+
* than the graph (`retired-key`, `content-schema`), and the 1-based line in it.
|
|
67
|
+
*/
|
|
68
|
+
readonly file?: string;
|
|
69
|
+
readonly line?: number;
|
|
64
70
|
/**
|
|
65
71
|
* `error` (default) blocks the build; `warning` is reported but non-blocking.
|
|
66
72
|
* New checks enter as warnings and are promoted once they run clean on real
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { citationsInLinks, citedIds, withoutLinkCitations } from "../reference/citation-format.mjs";
|
|
2
|
+
const CHECK_TEXT_REFERENCE = /\[\[[^\]\n]*\]\]|\[@[^\]\n]*\]/;
|
|
1
3
|
const VALID_ID = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;
|
|
2
4
|
function buildIndex(kind, entries, issues) {
|
|
3
5
|
const index = /* @__PURE__ */ new Map();
|
|
@@ -23,13 +25,7 @@ function buildIndex(kind, entries, issues) {
|
|
|
23
25
|
function citedSourceIds(body) {
|
|
24
26
|
if (!body) return /* @__PURE__ */ new Set();
|
|
25
27
|
const withoutCode = body.replace(/```[\s\S]*?```/g, "").replace(/`[^`\n]*`/g, "");
|
|
26
|
-
|
|
27
|
-
for (const match of withoutCode.matchAll(
|
|
28
|
-
/\[@([a-z0-9][a-z0-9.-]*)(?:;[^\]]*)?\]/g
|
|
29
|
-
)) {
|
|
30
|
-
ids.add((match[1] ?? "").trim());
|
|
31
|
-
}
|
|
32
|
-
return ids;
|
|
28
|
+
return citedIds(withoutLinkCitations(withoutCode));
|
|
33
29
|
}
|
|
34
30
|
function findDuplicates(values) {
|
|
35
31
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -173,18 +169,22 @@ function validateCurriculum(catalog) {
|
|
|
173
169
|
}
|
|
174
170
|
}
|
|
175
171
|
const cited = citedSourceIds(lesson.body);
|
|
172
|
+
if (lesson.body !== void 0) {
|
|
173
|
+
const blank = (code) => code.replace(/[^\n]/g, " ");
|
|
174
|
+
const prose = lesson.body.replace(/```[\s\S]*?```/g, blank).replace(/`[^`\n]*`/g, blank);
|
|
175
|
+
for (const { text } of citationsInLinks(prose)) {
|
|
176
|
+
issues.push({
|
|
177
|
+
kind: "citation-in-link",
|
|
178
|
+
message: `${lesson.id} puts the citation ${text} inside a link's text, where it prints as typed: a link cannot hold the citation's own link. Cite after the link: [the words](https://\u2026) ${text}.`
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
176
182
|
for (const sourceId of lesson.sources) {
|
|
177
|
-
|
|
178
|
-
if (!source) {
|
|
183
|
+
if (!sources.has(sourceId)) {
|
|
179
184
|
issues.push({
|
|
180
185
|
kind: "unknown-reference",
|
|
181
186
|
message: `${lesson.id} references unknown source ${sourceId}`
|
|
182
187
|
});
|
|
183
|
-
} else if (lesson.status === "reviewed" && source.editorialStatus !== "reviewed") {
|
|
184
|
-
issues.push({
|
|
185
|
-
kind: "review-state",
|
|
186
|
-
message: `${lesson.id} is reviewed but source ${sourceId} is ${source.editorialStatus}`
|
|
187
|
-
});
|
|
188
188
|
}
|
|
189
189
|
if (lesson.body !== void 0 && !cited.has(sourceId)) {
|
|
190
190
|
issues.push({
|
|
@@ -203,7 +203,7 @@ function validateCurriculum(catalog) {
|
|
|
203
203
|
}
|
|
204
204
|
if (lesson.teaches.length > 0 && lesson.assumptions.length === 0) {
|
|
205
205
|
issues.push({
|
|
206
|
-
kind: "
|
|
206
|
+
kind: "missing-assumptions",
|
|
207
207
|
message: `${lesson.id} teaches quantitative content but declares no assumptions`
|
|
208
208
|
});
|
|
209
209
|
}
|
|
@@ -232,10 +232,28 @@ function validateCurriculum(catalog) {
|
|
|
232
232
|
message: `${assessment.id}/${item.id} has unknown correct option ${item.correctOptionId}`
|
|
233
233
|
});
|
|
234
234
|
}
|
|
235
|
-
} else if (!Number.isFinite(item.answer.value)
|
|
235
|
+
} else if (!Number.isFinite(item.answer.value)) {
|
|
236
|
+
issues.push({
|
|
237
|
+
kind: "invalid-assessment",
|
|
238
|
+
message: `${assessment.id}/${item.id} answer.value must be a number`
|
|
239
|
+
});
|
|
240
|
+
} else if (!Number.isFinite(item.answer.tolerance) || item.answer.tolerance <= 0) {
|
|
241
|
+
issues.push({
|
|
242
|
+
kind: "invalid-assessment",
|
|
243
|
+
message: `${assessment.id}/${item.id} answer.tolerance must be a number greater than 0, not ${String(item.answer.tolerance)}: an answer within it of answer.value is correct. For an exact whole-number answer, use a tolerance below one half, such as 0.4.`
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
const texts = [
|
|
247
|
+
["prompt", item.prompt],
|
|
248
|
+
["explanation", item.explanation],
|
|
249
|
+
...item.type === "single-choice" ? item.options.map((option) => [`option ${option.id}`, option.label]) : []
|
|
250
|
+
];
|
|
251
|
+
for (const [field, text] of texts) {
|
|
252
|
+
const found = text === void 0 ? null : CHECK_TEXT_REFERENCE.exec(text);
|
|
253
|
+
if (!found) continue;
|
|
236
254
|
issues.push({
|
|
237
255
|
kind: "invalid-assessment",
|
|
238
|
-
message: `${assessment.id}/${item.id}
|
|
256
|
+
message: `${assessment.id}/${item.id} ${field} contains ${found[0]}, which a knowledge check shows exactly as written: its text renders math, not references or citations. Name the equation, section or source in words, or cite it in the lesson text the check follows.`
|
|
239
257
|
});
|
|
240
258
|
}
|
|
241
259
|
const evidence = evidenceByCompetency.get(item.competencyId) ?? {
|
|
@@ -24,7 +24,7 @@ export function isBaseAbbreviation(token: any, abbreviations: any): boolean;
|
|
|
24
24
|
* `content/abbreviations/*.md` table, not here. This baseline only holds
|
|
25
25
|
* abbreviations so common in ordinary English/academic writing that spelling
|
|
26
26
|
* them out on every course would be noise, not help. A course adds a real
|
|
27
|
-
* one via `
|
|
28
|
-
* `
|
|
27
|
+
* one via `rules.N14.allow` in its course config, the same shape as
|
|
28
|
+
* `rules.N8.allow` / `rules.N10.allow`.
|
|
29
29
|
*/
|
|
30
30
|
export const BASE_ABBREVIATIONS: Readonly<Set<string>>;
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
* `content/abbreviations/*.md` table, not here. This baseline only holds
|
|
12
12
|
* abbreviations so common in ordinary English/academic writing that spelling
|
|
13
13
|
* them out on every course would be noise, not help. A course adds a real
|
|
14
|
-
* one via `
|
|
15
|
-
* `
|
|
14
|
+
* one via `rules.N14.allow` in its course config, the same shape as
|
|
15
|
+
* `rules.N8.allow` / `rules.N10.allow`.
|
|
16
16
|
*/
|
|
17
17
|
export const BASE_ABBREVIATIONS = Object.freeze(
|
|
18
18
|
new Set([
|
|
@@ -34,8 +34,8 @@ export function isBaseOperator(name: any, operators: any): boolean;
|
|
|
34
34
|
*
|
|
35
35
|
* A domain operator (`Var`, `Cov`, `Corr`, `sgn`, `erf`, `Tr`, `argmax`, …) is
|
|
36
36
|
* written `\operatorname{…}` and is NOT here: it earns a card, a
|
|
37
|
-
* `notation.local` entry, or an explicit line in
|
|
38
|
-
* (`
|
|
37
|
+
* `notation.local` entry, or an explicit line in the course config
|
|
38
|
+
* (`rules.N8.allow`).
|
|
39
39
|
*
|
|
40
40
|
* Entries are the KaTeX command without its backslash (`exp`, not `\exp`) for
|
|
41
41
|
* `op` nodes, and the verbatim body for `\operatorname{…}` nodes (`Var`).
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
*
|
|
22
22
|
* A domain operator (`Var`, `Cov`, `Corr`, `sgn`, `erf`, `Tr`, `argmax`, …) is
|
|
23
23
|
* written `\operatorname{…}` and is NOT here: it earns a card, a
|
|
24
|
-
* `notation.local` entry, or an explicit line in
|
|
25
|
-
* (`
|
|
24
|
+
* `notation.local` entry, or an explicit line in the course config
|
|
25
|
+
* (`rules.N8.allow`).
|
|
26
26
|
*
|
|
27
27
|
* Entries are the KaTeX command without its backslash (`exp`, not `\exp`) for
|
|
28
28
|
* `op` nodes, and the verbatim body for `\operatorname{…}` nodes (`Var`).
|
|
@@ -28,8 +28,9 @@ export function isBaseRomanLabel(name: any, romanLabels: any): boolean;
|
|
|
28
28
|
* always a course-specific named quantity (`KE`, `PV`, `Var`-as-a-label) that
|
|
29
29
|
* genuinely benefits from a hover card, so the shipped baseline holds only
|
|
30
30
|
* the qualifiers that function like an operator's extremum subscript
|
|
31
|
-
* (`x_{\mathrm{opt}}`) rather than naming a quantity of their own.
|
|
32
|
-
*
|
|
33
|
-
*
|
|
31
|
+
* (`x_{\mathrm{opt}}`) rather than naming a quantity of their own. That
|
|
32
|
+
* clears rule N10 only: the indexed symbol still needs a card (rule N23).
|
|
33
|
+
* A course adds a real one via `rules.N10.allow` in its course config, the
|
|
34
|
+
* same shape as `rules.N8.allow`.
|
|
34
35
|
*/
|
|
35
36
|
export const BASE_ROMAN_LABELS: Readonly<Set<string>>;
|
|
@@ -15,9 +15,10 @@
|
|
|
15
15
|
* always a course-specific named quantity (`KE`, `PV`, `Var`-as-a-label) that
|
|
16
16
|
* genuinely benefits from a hover card, so the shipped baseline holds only
|
|
17
17
|
* the qualifiers that function like an operator's extremum subscript
|
|
18
|
-
* (`x_{\mathrm{opt}}`) rather than naming a quantity of their own.
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* (`x_{\mathrm{opt}}`) rather than naming a quantity of their own. That
|
|
19
|
+
* clears rule N10 only: the indexed symbol still needs a card (rule N23).
|
|
20
|
+
* A course adds a real one via `rules.N10.allow` in its course config, the
|
|
21
|
+
* same shape as `rules.N8.allow`.
|
|
21
22
|
*/
|
|
22
23
|
export const BASE_ROMAN_LABELS = Object.freeze(new Set(['opt']));
|
|
23
24
|
|
|
@@ -44,6 +44,35 @@ export function referenceLabelFromText(referenceText: string, locator: string |
|
|
|
44
44
|
* @returns {string}
|
|
45
45
|
*/
|
|
46
46
|
export function formatSourceLabel(record: Record<string, unknown>): string;
|
|
47
|
+
/**
|
|
48
|
+
* The citations one bracket holds, in order, as `{ id, locator }`. A
|
|
49
|
+
* `;`-separated part that is only `@id` starts another citation (Pandoc's
|
|
50
|
+
* `[@a; @b]`); any other part belongs to the locator of the citation before
|
|
51
|
+
* it, so a locator may itself contain `;` (`[@a; §1.2; eq. 3]`).
|
|
52
|
+
*
|
|
53
|
+
* @param {string} firstId
|
|
54
|
+
* @param {string | undefined} rest
|
|
55
|
+
* @returns {{ id: string, locator: string }[]}
|
|
56
|
+
*/
|
|
57
|
+
export function bracketCitations(firstId: string, rest: string | undefined): {
|
|
58
|
+
id: string;
|
|
59
|
+
locator: string;
|
|
60
|
+
}[];
|
|
61
|
+
/** Every source id a text cites, `[@a; @b]` included. */
|
|
62
|
+
export function citedIds(text: any): Set<any>;
|
|
63
|
+
/**
|
|
64
|
+
* Each citation written inside a link's text, as `{ index, text }`: the
|
|
65
|
+
* offset of its `[@` in `text`, and the citation as written.
|
|
66
|
+
*/
|
|
67
|
+
export function citationsInLinks(text: any): {
|
|
68
|
+
index: number;
|
|
69
|
+
text: string;
|
|
70
|
+
}[];
|
|
71
|
+
/**
|
|
72
|
+
* `text` with every citation inside a link's text blanked, offsets kept: what
|
|
73
|
+
* the page cites, for a scan that counts a lesson's sources.
|
|
74
|
+
*/
|
|
75
|
+
export function withoutLinkCitations(text: any): string;
|
|
47
76
|
/**
|
|
48
77
|
* Pure helpers for turning a source record plus a per-citation locator into
|
|
49
78
|
* the strings shown in the inline panel and the end-of-page reference list.
|
|
@@ -53,3 +82,9 @@ export function formatSourceLabel(record: Record<string, unknown>): string;
|
|
|
53
82
|
* file system, or Astro APIs.
|
|
54
83
|
*/
|
|
55
84
|
export const SOURCE_ID_PATTERN: RegExp;
|
|
85
|
+
/**
|
|
86
|
+
* One `[@…]` bracket: `[@id]`, `[@id; locator]`, or several citations as
|
|
87
|
+
* Pandoc writes them, `[@a; @b; p. 7]`. Group 1 is the first id, group 2 the
|
|
88
|
+
* rest after the first `;`, read by {@link bracketCitations}.
|
|
89
|
+
*/
|
|
90
|
+
export const CITATION_BRACKET: RegExp;
|
|
@@ -131,3 +131,77 @@ export function formatSourceLabel(record) {
|
|
|
131
131
|
.filter(Boolean)
|
|
132
132
|
.join(', ');
|
|
133
133
|
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* One `[@…]` bracket: `[@id]`, `[@id; locator]`, or several citations as
|
|
137
|
+
* Pandoc writes them, `[@a; @b; p. 7]`. Group 1 is the first id, group 2 the
|
|
138
|
+
* rest after the first `;`, read by {@link bracketCitations}.
|
|
139
|
+
*/
|
|
140
|
+
export const CITATION_BRACKET = /\[@([a-z0-9][a-z0-9.-]*)(?:;[ \t\r\n]*([^\]]*))?\]/g;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* The citations one bracket holds, in order, as `{ id, locator }`. A
|
|
144
|
+
* `;`-separated part that is only `@id` starts another citation (Pandoc's
|
|
145
|
+
* `[@a; @b]`); any other part belongs to the locator of the citation before
|
|
146
|
+
* it, so a locator may itself contain `;` (`[@a; §1.2; eq. 3]`).
|
|
147
|
+
*
|
|
148
|
+
* @param {string} firstId
|
|
149
|
+
* @param {string | undefined} rest
|
|
150
|
+
* @returns {{ id: string, locator: string }[]}
|
|
151
|
+
*/
|
|
152
|
+
export function bracketCitations(firstId, rest) {
|
|
153
|
+
const citations = [{ id: String(firstId).trim(), parts: [] }];
|
|
154
|
+
for (const part of String(rest ?? '').split(';')) {
|
|
155
|
+
const next = /^\s*@([a-z0-9][a-z0-9.-]*)\s*$/.exec(part);
|
|
156
|
+
if (next) citations.push({ id: next[1], parts: [] });
|
|
157
|
+
else if (part.trim()) citations[citations.length - 1].parts.push(part);
|
|
158
|
+
}
|
|
159
|
+
return citations.map(({ id, parts }) => ({
|
|
160
|
+
id,
|
|
161
|
+
locator: parts.join(';').replace(/\s+/g, ' ').trim(),
|
|
162
|
+
}));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Every source id a text cites, `[@a; @b]` included. */
|
|
166
|
+
export function citedIds(text) {
|
|
167
|
+
const ids = new Set();
|
|
168
|
+
for (const match of String(text).matchAll(CITATION_BRACKET)) {
|
|
169
|
+
for (const { id } of bracketCitations(match[1] ?? '', match[2])) ids.add(id);
|
|
170
|
+
}
|
|
171
|
+
return ids;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* A Markdown link whose text holds a citation, `[as measured in [@id]](url)`:
|
|
176
|
+
* remark-citation leaves link text alone (a link cannot hold the marker's
|
|
177
|
+
* link), so the citation would print as typed. Group 1 is the link text.
|
|
178
|
+
*/
|
|
179
|
+
const LINK_WITH_CITATION = /\[((?:[^[\]\n]|\[[^[\]\n]*\])*?\[@[^\]\n]*\](?:[^[\]\n]|\[[^[\]\n]*\])*)\]\(/g;
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Each citation written inside a link's text, as `{ index, text }`: the
|
|
183
|
+
* offset of its `[@` in `text`, and the citation as written.
|
|
184
|
+
*/
|
|
185
|
+
export function citationsInLinks(text) {
|
|
186
|
+
const found = [];
|
|
187
|
+
for (const link of String(text).matchAll(LINK_WITH_CITATION)) {
|
|
188
|
+
const start = (link.index ?? 0) + 1;
|
|
189
|
+
for (const citation of (link[1] ?? '').matchAll(/\[@[^\]\n]*\]/g)) {
|
|
190
|
+
found.push({ index: start + (citation.index ?? 0), text: citation[0] });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return found;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* `text` with every citation inside a link's text blanked, offsets kept: what
|
|
198
|
+
* the page cites, for a scan that counts a lesson's sources.
|
|
199
|
+
*/
|
|
200
|
+
export function withoutLinkCitations(text) {
|
|
201
|
+
let out = String(text);
|
|
202
|
+
for (const { index, text: citation } of citationsInLinks(out)) {
|
|
203
|
+
out = out.slice(0, index) + citation.replace(/[^\n]/g, ' ') + out.slice(index + citation.length);
|
|
204
|
+
}
|
|
205
|
+
return out;
|
|
206
|
+
}
|
|
207
|
+
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { CurriculumCatalog } from '../curriculum/validation.js';
|
|
1
|
+
import { e as CourseOperators, f as CourseRomanLabels, g as CourseAccents } from '../course-config-rRtME5UV.js';
|
|
3
2
|
import { NotationSpec } from '../compiler/collections.js';
|
|
4
3
|
import { LintIgnore } from './lint-ignore.js';
|
|
5
4
|
import { NotationRegistry, NotationRegistryInput } from './types.js';
|
|
6
5
|
import '../rules/catalog.js';
|
|
7
6
|
import '../rules/course-rules.js';
|
|
7
|
+
import '../curriculum/validation.js';
|
|
8
|
+
import './gate-retired-keys.js';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Corpus consistency checks (Step D6) — the Tier 1–2 checks from the cleanup
|
|
@@ -20,9 +21,7 @@ import '../rules/course-rules.js';
|
|
|
20
21
|
* (a) glyph-unique-in-corpus one rendered glyph → one meaning corpus-wide
|
|
21
22
|
* (b) notation-units / units-vocab every entry has units or `dimensionless`,
|
|
22
23
|
* unit strings drawn from a controlled vocab
|
|
23
|
-
* (
|
|
24
|
-
* lesson uses resolves to one declaration
|
|
25
|
-
* (f) weak-local a `notation.local` symbol used only in its
|
|
24
|
+
* (f) weak-local a `notation.local` symbol used only in its
|
|
26
25
|
* own defining equation and prose
|
|
27
26
|
* (g) notation-source-locator every notation `sources` entry has a locator
|
|
28
27
|
* (h) operator-explained a named operator a lesson uses is on the
|
|
@@ -45,7 +44,7 @@ import '../rules/course-rules.js';
|
|
|
45
44
|
* `src/content/resolution-report.ts`.
|
|
46
45
|
*/
|
|
47
46
|
|
|
48
|
-
type ConsistencyCode = 'glyph-unique-in-corpus' | 'notation-units' | 'units-vocab' | '
|
|
47
|
+
type ConsistencyCode = 'glyph-unique-in-corpus' | 'notation-units' | 'units-vocab' | 'weak-local' | 'notation-source-locator' | 'gloss-wants-promoting' | 'card-wants-demoting' | 'gloss-name-shape' | 'operator-explained' | 'roman-label-explained' | 'accent-explained' | 'accent-swallowed' | 'operator-suspended' | 'operator-suspension-reason' | 'operator-shadowed';
|
|
49
48
|
interface ConsistencyDiagnostic {
|
|
50
49
|
readonly code: ConsistencyCode;
|
|
51
50
|
/** Tier 3 — repairable violations are warnings; optional advice is info. */
|
|
@@ -62,10 +61,7 @@ interface ConsistencyInput {
|
|
|
62
61
|
readonly registry: NotationRegistry;
|
|
63
62
|
readonly notationInput: NotationRegistryInput;
|
|
64
63
|
readonly specs: readonly NotationSpec[];
|
|
65
|
-
readonly catalog: CurriculumCatalog;
|
|
66
64
|
readonly lintIgnore: LintIgnore;
|
|
67
|
-
/** Convention keys this course anchors on (from `content/course.config.ts`). */
|
|
68
|
-
readonly conventions: CourseConventions;
|
|
69
65
|
/** This course's edits to the named-operator baseline (rule N8). */
|
|
70
66
|
readonly operators?: CourseOperators;
|
|
71
67
|
/** This course's edits to the roman-label baseline (rule N10). */
|
|
@@ -2,7 +2,8 @@ import { effectiveAccentAllowlist } from "./base-accents.mjs";
|
|
|
2
2
|
import { effectiveOperatorAllowlist } from "./base-operators.mjs";
|
|
3
3
|
import { effectiveRomanLabelAllowlist } from "./base-roman-labels.mjs";
|
|
4
4
|
import { stripEquationLabels } from "./equations.mjs";
|
|
5
|
-
import { pageScope } from "./gate-math.js";
|
|
5
|
+
import { pageScope, scopeEntry } from "./gate-math.js";
|
|
6
|
+
import { isIndexEntryLatex } from "../compiler/content-schemas.js";
|
|
6
7
|
import {
|
|
7
8
|
collectDecorations,
|
|
8
9
|
collectOperators,
|
|
@@ -12,12 +13,12 @@ import {
|
|
|
12
13
|
resolveMathGlyphs
|
|
13
14
|
} from "./math-glyphs.mjs";
|
|
14
15
|
import { offVocabularyTokens } from "./units-vocab.js";
|
|
15
|
-
|
|
16
|
+
import { blankPropStrings } from "./gate-prop-text.js";
|
|
16
17
|
function stripCode(text) {
|
|
17
18
|
return text.replace(/```[\s\S]*?```/g, (block) => block.replace(/[^\n]/g, " ")).replace(/(^|[^`])`[^`\n]*`/g, (match) => match.replace(/[^\n]/g, " "));
|
|
18
19
|
}
|
|
19
20
|
function mathSpans(body) {
|
|
20
|
-
const searchable = stripCode(body);
|
|
21
|
+
const searchable = stripCode(blankPropStrings(body));
|
|
21
22
|
const spans = [];
|
|
22
23
|
for (const match of searchable.matchAll(/\$\$([\s\S]+?)\$\$/g)) {
|
|
23
24
|
spans.push({ latex: stripEquationLabels(match[1] ?? "") });
|
|
@@ -79,6 +80,7 @@ function checkGlyphUniqueness(input, out) {
|
|
|
79
80
|
}
|
|
80
81
|
function checkNotationUnits(input, out) {
|
|
81
82
|
for (const spec of input.specs) {
|
|
83
|
+
if (isIndexEntryLatex(spec.latex)) continue;
|
|
82
84
|
if (spec.units === void 0 && !spec.dimensionless) {
|
|
83
85
|
if (input.lintIgnore.matches("notation-units", spec.key)) continue;
|
|
84
86
|
out.push({
|
|
@@ -105,35 +107,6 @@ function checkNotationUnits(input, out) {
|
|
|
105
107
|
});
|
|
106
108
|
}
|
|
107
109
|
}
|
|
108
|
-
function checkConventionSingleDefinition(input, out) {
|
|
109
|
-
const lessonById = new Map(
|
|
110
|
-
input.catalog.lessons.map((lesson) => [lesson.id, lesson])
|
|
111
|
-
);
|
|
112
|
-
for (const lesson of input.notationInput.lessons) {
|
|
113
|
-
const body = lesson.body;
|
|
114
|
-
if (!CONVENTION_RE.test(body)) continue;
|
|
115
|
-
const meta = lessonById.get(lesson.lessonId);
|
|
116
|
-
const { signConventionKey, cashFlowPerspectiveCompetency } = input.conventions;
|
|
117
|
-
const anchoredByCompetency = [
|
|
118
|
-
...meta?.teaches ?? [],
|
|
119
|
-
...meta?.requires ?? []
|
|
120
|
-
].includes(cashFlowPerspectiveCompetency);
|
|
121
|
-
const anchoredByTerm = new RegExp(
|
|
122
|
-
`\\[\\[\\s*${signConventionKey}\\s*\\]\\]`
|
|
123
|
-
).test(body);
|
|
124
|
-
if (anchoredByCompetency || anchoredByTerm) continue;
|
|
125
|
-
if (input.lintIgnore.matches("convention-single-definition", lesson.lessonId)) {
|
|
126
|
-
continue;
|
|
127
|
-
}
|
|
128
|
-
out.push({
|
|
129
|
-
code: "convention-single-definition",
|
|
130
|
-
severity: "warning",
|
|
131
|
-
lessonId: lesson.lessonId,
|
|
132
|
-
file: lesson.source.file,
|
|
133
|
-
message: `${lesson.lessonId} states a sign / cash-flow-perspective convention in prose but does not resolve it to a single declaration ([[${signConventionKey}]] or ${cashFlowPerspectiveCompetency})`
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
110
|
function checkWeakLocal(input, out) {
|
|
138
111
|
for (const lesson of input.notationInput.lessons) {
|
|
139
112
|
const spans = mathSpans(lesson.body).map((span) => span.latex);
|
|
@@ -295,7 +268,7 @@ function checkOperatorExplained(input, out) {
|
|
|
295
268
|
lessonId: lesson.lessonId,
|
|
296
269
|
file: lesson.source.file,
|
|
297
270
|
glyph: operator.name,
|
|
298
|
-
message: `${lesson.lessonId}: operator ${JSON.stringify(display)} is neither on the course operator allowlist nor explained by a notation entry; give it a card or notation.local entry (latex ${JSON.stringify(display)}) introduced with [[key]], or add ${JSON.stringify(operator.name)} to
|
|
271
|
+
message: `${lesson.lessonId}: operator ${JSON.stringify(display)} is neither on the course operator allowlist nor explained by a notation entry; give it a card or notation.local entry (latex ${JSON.stringify(display)}) introduced with [[key]], or add ${JSON.stringify(operator.name)} to rules.N8.allow in the course config (course.config.ts, or the course.settings.json it reads)`
|
|
299
272
|
});
|
|
300
273
|
}
|
|
301
274
|
}
|
|
@@ -339,7 +312,7 @@ function checkRomanLabelExplained(input, out) {
|
|
|
339
312
|
lessonId: lesson.lessonId,
|
|
340
313
|
file: lesson.source.file,
|
|
341
314
|
glyph: label.name,
|
|
342
|
-
message: `${lesson.lessonId}: roman label ${JSON.stringify(`\\mathrm{${label.name}}`)} is neither on the course roman-label allowlist nor explained by a notation entry; give it a card or notation.local entry (latex ${JSON.stringify(`\\mathrm{${label.name}}`)}) introduced with [[key]], or add ${JSON.stringify(label.name)} to
|
|
315
|
+
message: `${lesson.lessonId}: roman label ${JSON.stringify(`\\mathrm{${label.name}}`)} is neither on the course roman-label allowlist nor explained by a notation entry; give it a card or notation.local entry (latex ${JSON.stringify(`\\mathrm{${label.name}}`)}) introduced with [[key]], or add ${JSON.stringify(label.name)} to rules.N10.allow in the course config (course.config.ts, or the course.settings.json it reads)`
|
|
343
316
|
});
|
|
344
317
|
}
|
|
345
318
|
}
|
|
@@ -349,7 +322,7 @@ function sharedScopeByKey(input) {
|
|
|
349
322
|
return new Map(
|
|
350
323
|
input.notationInput.sharedDefinitions.map((definition) => [
|
|
351
324
|
definition.key,
|
|
352
|
-
|
|
325
|
+
scopeEntry(definition)
|
|
353
326
|
])
|
|
354
327
|
);
|
|
355
328
|
}
|
|
@@ -363,11 +336,35 @@ function checkAccentExplained(input, out) {
|
|
|
363
336
|
for (const span of mathSpans(lesson.body)) {
|
|
364
337
|
let decorations;
|
|
365
338
|
try {
|
|
366
|
-
({ decorations } = resolveMathGlyphs(span.latex, scope, {
|
|
339
|
+
({ decorations } = resolveMathGlyphs(span.latex, scope, {
|
|
340
|
+
suspended,
|
|
341
|
+
derive: true
|
|
342
|
+
}));
|
|
367
343
|
} catch {
|
|
368
344
|
continue;
|
|
369
345
|
}
|
|
370
346
|
for (const decoration of decorations) {
|
|
347
|
+
if (decoration.wholeForm !== null && decoration.wholeFormExplicit) {
|
|
348
|
+
const notation = scope.find((entry) => entry.key === decoration.wholeForm)?.notation ?? "";
|
|
349
|
+
const carries = collectDecorations(notation).some(
|
|
350
|
+
(item) => item.name === decoration.name
|
|
351
|
+
);
|
|
352
|
+
const id = `swallowed:${decoration.token}`;
|
|
353
|
+
if (!carries && !reported.has(id)) {
|
|
354
|
+
reported.add(id);
|
|
355
|
+
if (!input.lintIgnore.matches("accent-swallowed", decoration.name)) {
|
|
356
|
+
out.push({
|
|
357
|
+
code: "accent-swallowed",
|
|
358
|
+
severity: "warning",
|
|
359
|
+
lessonId: lesson.lessonId,
|
|
360
|
+
file: lesson.source.file,
|
|
361
|
+
glyph: decoration.name,
|
|
362
|
+
message: `${lesson.lessonId}: \\explain{${decoration.wholeForm}}{${decoration.token}} wraps the decoration ${JSON.stringify(decoration.name)} in ${decoration.wholeForm}, whose latex (${notation}) has none, so its card cannot explain it; wrap only the symbol under it and give the decoration an operator entry (latex ${JSON.stringify(operatorEntryLatex(decoration.name))}), or name an entry for the decorated form (latex ${JSON.stringify(decoration.token)})`
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
371
368
|
if (decoration.wholeForm !== null || decoration.operator !== null) continue;
|
|
372
369
|
if (suspended.includes(decoration.name)) continue;
|
|
373
370
|
if (allowlist.has(decoration.name)) continue;
|
|
@@ -436,7 +433,6 @@ function checkOperatorSuspension(input, out) {
|
|
|
436
433
|
const CHECKS = [
|
|
437
434
|
checkGlyphUniqueness,
|
|
438
435
|
checkNotationUnits,
|
|
439
|
-
checkConventionSingleDefinition,
|
|
440
436
|
checkWeakLocal,
|
|
441
437
|
checkNotationSourceLocator,
|
|
442
438
|
checkGlossWantsPromoting,
|
|
@@ -459,7 +455,6 @@ function consistencyCounts(diagnostics) {
|
|
|
459
455
|
"glyph-unique-in-corpus": 0,
|
|
460
456
|
"notation-units": 0,
|
|
461
457
|
"units-vocab": 0,
|
|
462
|
-
"convention-single-definition": 0,
|
|
463
458
|
"weak-local": 0,
|
|
464
459
|
"notation-source-locator": 0,
|
|
465
460
|
"gloss-wants-promoting": 0,
|
|
@@ -468,6 +463,7 @@ function consistencyCounts(diagnostics) {
|
|
|
468
463
|
"operator-explained": 0,
|
|
469
464
|
"roman-label-explained": 0,
|
|
470
465
|
"accent-explained": 0,
|
|
466
|
+
"accent-swallowed": 0,
|
|
471
467
|
"operator-suspended": 0,
|
|
472
468
|
"operator-suspension-reason": 0,
|
|
473
469
|
"operator-shadowed": 0
|
|
@@ -2,13 +2,10 @@ import { compareDiagnostics } from "./references.js";
|
|
|
2
2
|
function validateNotationAlignment(registry, catalog) {
|
|
3
3
|
const diagnostics = [];
|
|
4
4
|
const competencies = new Map(
|
|
5
|
-
catalog.competencies.map((entry) => [
|
|
6
|
-
entry.id,
|
|
7
|
-
entry
|
|
8
|
-
])
|
|
5
|
+
catalog.competencies.map((entry) => [entry.id, entry])
|
|
9
6
|
);
|
|
10
7
|
const lessons = new Map(catalog.lessons.map((entry) => [entry.id, entry]));
|
|
11
|
-
const sources = new
|
|
8
|
+
const sources = new Set(catalog.sources.map((entry) => entry.id));
|
|
12
9
|
const trackPositions = buildTrackPositions(catalog, diagnostics);
|
|
13
10
|
for (const definition of registry.definitions) {
|
|
14
11
|
validateSources(definition, sources, diagnostics);
|
|
@@ -60,15 +57,6 @@ function validateNotationAlignment(registry, catalog) {
|
|
|
60
57
|
trackPositions,
|
|
61
58
|
diagnostics
|
|
62
59
|
);
|
|
63
|
-
if (definition.status === "reviewed" && (lesson.status !== "reviewed" || competency?.editorialStatus !== "reviewed")) {
|
|
64
|
-
diagnostics.push(
|
|
65
|
-
error(
|
|
66
|
-
"alignment-review-state",
|
|
67
|
-
`${definition.id} is reviewed while its owning lesson or competency is not reviewed`,
|
|
68
|
-
definition
|
|
69
|
-
)
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
60
|
}
|
|
73
61
|
return diagnostics.sort(compareDiagnostics);
|
|
74
62
|
}
|
|
@@ -129,8 +117,7 @@ function validateIntroductionOrder(definition, introductionLessonId, registry, t
|
|
|
129
117
|
}
|
|
130
118
|
function validateSources(definition, sources, diagnostics) {
|
|
131
119
|
for (const sourceId of definition.sources) {
|
|
132
|
-
|
|
133
|
-
if (!source) {
|
|
120
|
+
if (!sources.has(sourceId)) {
|
|
134
121
|
diagnostics.push(
|
|
135
122
|
error(
|
|
136
123
|
"alignment-unavailable",
|
|
@@ -138,14 +125,6 @@ function validateSources(definition, sources, diagnostics) {
|
|
|
138
125
|
definition
|
|
139
126
|
)
|
|
140
127
|
);
|
|
141
|
-
} else if (definition.status === "reviewed" && source.editorialStatus !== "reviewed") {
|
|
142
|
-
diagnostics.push(
|
|
143
|
-
error(
|
|
144
|
-
"alignment-review-state",
|
|
145
|
-
`${definition.id} is reviewed while source ${sourceId} is ${source.editorialStatus}`,
|
|
146
|
-
definition
|
|
147
|
-
)
|
|
148
|
-
);
|
|
149
128
|
}
|
|
150
129
|
}
|
|
151
130
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ManifestDerivedNotation } from '../compiler/manifest.js';
|
|
2
|
+
import { GlyphScopeEntry } from './gloss.js';
|
|
3
|
+
import { DerivedUse, NotationDefinitionRecord } from './types.js';
|
|
4
|
+
import '../curriculum/validation.js';
|
|
5
|
+
import './consistency.js';
|
|
6
|
+
import '../course-config-rRtME5UV.js';
|
|
7
|
+
import '../rules/catalog.js';
|
|
8
|
+
import '../rules/course-rules.js';
|
|
9
|
+
import '../compiler/collections.js';
|
|
10
|
+
import './gate-retired-keys.js';
|
|
11
|
+
import './lint-ignore.js';
|
|
12
|
+
import './equations-validate.js';
|
|
13
|
+
import './tables-validate.js';
|
|
14
|
+
import './keyed-float-validate.js';
|
|
15
|
+
import './figures-validate.js';
|
|
16
|
+
import './diagrams-validate.js';
|
|
17
|
+
import './examples-validate.js';
|
|
18
|
+
import './folds-validate.js';
|
|
19
|
+
import './knowledge-checks-validate.js';
|
|
20
|
+
import './raw-floats-validate.js';
|
|
21
|
+
import './lab-katex-validate.js';
|
|
22
|
+
import './gate-math.js';
|
|
23
|
+
import './mixed-content.js';
|
|
24
|
+
import '../rules/waivers.js';
|
|
25
|
+
import '../compiler/knowledge-version.js';
|
|
26
|
+
import '../compiler/lesson-derivation.js';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Derived cards (rule N23) as the page payload carries them: each derived
|
|
30
|
+
* card a page's math uses, composed from its base and index entries by
|
|
31
|
+
* `derived-notation.mjs`, with the glyph table its formula resolves against.
|
|
32
|
+
* The manifest builds the ones lesson-body and assessment math use; the page
|
|
33
|
+
* payload (`page-notation.ts`) builds the ones a lab or a diagram registers
|
|
34
|
+
* while it renders, the same way.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The derived cards for `uses`, sorted by key. `records` holds the page's
|
|
39
|
+
* entries by key, local ones winning; a use whose base or index entry is not
|
|
40
|
+
* there is skipped. A card's formula resolves against the derived symbol,
|
|
41
|
+
* the index entry's glosses, the base, and the cards the formula names.
|
|
42
|
+
*/
|
|
43
|
+
declare function derivedNotationEntries(uses: readonly DerivedUse[], records: ReadonlyMap<string, NotationDefinitionRecord>, cardsByKey: ReadonlyMap<string, GlyphScopeEntry>): ManifestDerivedNotation[];
|
|
44
|
+
|
|
45
|
+
export { derivedNotationEntries };
|