explico 0.11.0 → 0.12.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 +189 -1
- package/dist/compiler/astro.d.ts +7 -1
- package/dist/compiler/astro.js +5 -2
- package/dist/compiler/cli.d.ts +1 -0
- package/dist/compiler/collections.d.ts +25 -2
- package/dist/compiler/collections.js +37 -4
- package/dist/compiler/exports/standalone-html.d.ts +1 -0
- package/dist/compiler/manifest.d.ts +30 -4
- package/dist/compiler/manifest.js +49 -10
- package/dist/compiler/resolution-report.d.ts +1 -0
- package/dist/reference/base-operators.d.mts +6 -0
- package/dist/reference/base-operators.mjs +6 -8
- package/dist/reference/figure-symbols.d.ts +32 -0
- package/dist/reference/figure-symbols.js +55 -0
- package/dist/reference/gate-non-chapter-pages.d.ts +39 -0
- package/dist/reference/gate-non-chapter-pages.js +156 -0
- package/dist/reference/gate-part-pages.d.ts +3 -1
- package/dist/reference/gate-part-pages.js +2 -1
- package/dist/reference/lab-katex-validate.d.ts +43 -0
- package/dist/reference/lab-katex-validate.js +96 -0
- package/dist/reference/lint-ignore.js +1 -1
- package/dist/reference/page-notation.d.ts +5 -3
- package/dist/reference/page-notation.js +8 -8
- package/dist/reference/raw-floats.mjs +37 -13
- package/dist/reference/registry.js +11 -1
- package/dist/reference/remark-notation.mjs +195 -7
- package/dist/reference/render-lab-math.d.ts +11 -2
- package/dist/reference/render-lab-math.js +2 -2
- package/dist/rules/catalog.js +33 -3
- package/dist/rules/rules.schema.json +22 -4
- package/docs/VERSIONING.md +3 -1
- package/docs/authoring/README.md +2 -1
- package/docs/authoring/components.md +4 -4
- package/docs/authoring/migrating.md +271 -0
- package/docs/authoring/notation.md +4 -1
- package/docs/authoring/structure-rules.md +75 -5
- package/docs/authoring/structure.md +14 -0
- package/docs/authoring/visualizations.md +26 -10
- package/package.json +1 -1
- package/src/compiler/astro.ts +17 -6
- package/src/compiler/collections.ts +67 -9
- package/src/compiler/manifest.ts +89 -13
- package/src/components/CourseContents.astro +6 -0
- package/src/components/PartOverview.astro +6 -0
- package/src/components/abbreviation/AbbreviationGlossary.astro +6 -2
- package/src/components/disclosure/print-as-shown.ts +42 -6
- package/src/components/figure/Figure.astro +6 -1
- package/src/components/figure/figure-state.ts +26 -17
- package/src/components/starlight/LessonFooter.astro +27 -7
- package/src/components/table/Table.astro +1 -1
- package/src/reference/base-operators.mjs +6 -8
- package/src/reference/figure-symbols.ts +117 -0
- package/src/reference/gate-non-chapter-pages.ts +241 -0
- package/src/reference/gate-part-pages.ts +1 -1
- package/src/reference/lab-katex-validate.ts +156 -0
- package/src/reference/lint-ignore.ts +4 -1
- package/src/reference/page-notation.ts +14 -11
- package/src/reference/raw-floats.mjs +37 -13
- package/src/reference/registry.ts +16 -1
- package/src/reference/remark-notation.mjs +195 -7
- package/src/reference/render-lab-math.ts +12 -1
- package/src/rules/catalog.ts +37 -3
- package/src/styles/global.css +57 -38
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,192 @@ under **Breaking** below; a PATCH never breaks.
|
|
|
14
14
|
|
|
15
15
|
## [Unreleased]
|
|
16
16
|
|
|
17
|
+
## [0.12.0] - 2026-09-28
|
|
18
|
+
|
|
19
|
+
### Breaking
|
|
20
|
+
|
|
21
|
+
- **Every symbol a figure shows opens its notation card, and the build
|
|
22
|
+
checks it** (rule V3, rule N4). Two checks fail a lab that draws math with
|
|
23
|
+
KaTeX itself, whose symbols have no card:
|
|
24
|
+
- **`lab-katex-direct`** (error, rule V3; `reference/lab-katex-validate.ts`,
|
|
25
|
+
in `diagnostics.figures`): `pnpm validate:content` follows each lesson's
|
|
26
|
+
relative imports to the course's components and fails a file that
|
|
27
|
+
imports `katex` (not a type-only import, not the stylesheet), naming the
|
|
28
|
+
chain of imports that reaches it. V3 now has a check, so it counts as
|
|
29
|
+
partly enforced.
|
|
30
|
+
- **`astro build` fails a figure's unbound symbol**: at `astro:build:done`,
|
|
31
|
+
every letter KaTeX drew inside a `figure.keyed-figure` (its tools
|
|
32
|
+
included) must sit in a `[data-notation-key]`; upright text
|
|
33
|
+
(`\text{}`, `\mathrm{}`, a named operator such as `\sin`) is exempt
|
|
34
|
+
(`reference/figure-symbols.ts`, `assertFigureSymbolsBound`).
|
|
35
|
+
- **`setFigureState` takes no symbol typed as text.** A run is a string or
|
|
36
|
+
`{ math }`; the italic (`{ text, italic: true }`), plain `{ text }` and
|
|
37
|
+
subscript (`{ sub }`) runs are gone, and one passed anyway throws, naming
|
|
38
|
+
the fix. `FigureStateRun` is `string | { math: RenderedLabMath }`, so
|
|
39
|
+
`astro check` fails an old run.
|
|
40
|
+
- **A page that is not a chapter numbers nothing, and `validate:content` says
|
|
41
|
+
so** (rule S10, locked). On the landing page, a part's overview page, or
|
|
42
|
+
any other page outside a part directory, `pnpm validate:content`
|
|
43
|
+
(`reference/gate-non-chapter-pages.ts`) fails `<Table>`, `<Figure>`,
|
|
44
|
+
`<Diagram>`, `<CompactExamples>`, `<AssessmentSet>`, a lab (a component
|
|
45
|
+
imported from a `labs/` module, or any `client:*` island) and
|
|
46
|
+
`\label{eq:…}` (`non-chapter-float`); `notation.local` and
|
|
47
|
+
`notation.suspend` (`non-chapter-local-notation`); and `$…$` or `[[…]]` in
|
|
48
|
+
`hero.title`, `hero.tagline` or a `<PartOverview chain>` step, which print
|
|
49
|
+
literally (`non-chapter-plain-text`). A `<Diagram>` rendered there
|
|
50
|
+
unnumbered before, and `<Table>` and `<Figure>` threw at render;
|
|
51
|
+
`remarkNotation` now fails the same blocks and labels at render, naming
|
|
52
|
+
S10. Link the chapter's float instead: `[[<chapter>#tab-<key>]]` shows it
|
|
53
|
+
in its card.
|
|
54
|
+
- **Math on a page that is not a chapter binds like a chapter's** (rule N4,
|
|
55
|
+
rule S10). Every symbol needs a `[[key]]` or an `\explain` on the page, or
|
|
56
|
+
the build fails; plain `$v$` on a part page used to render with no card
|
|
57
|
+
and no check. The text and math gates (rule N4, rule N8, rule N9,
|
|
58
|
+
rule N10, rule N12, rule N14, rule N22, and a course's `X` rules) and
|
|
59
|
+
per-place waivers now scan these pages too.
|
|
60
|
+
|
|
61
|
+
### Added
|
|
62
|
+
|
|
63
|
+
- **`renderLabMath(template, scope, { display: false })`** renders inline lab
|
|
64
|
+
math — a `.katex` with no `.katex-display` — for a formula that sits in a
|
|
65
|
+
line: a legend entry, an axis title, a control label. It never scrolls.
|
|
66
|
+
`RenderLabMathOptions` is exported; the default stays a display equation.
|
|
67
|
+
- **A `{ math }` run for the caption's live clause.** `setFigureState`
|
|
68
|
+
writes a `RenderedLabMath` from `renderLabMath(…, { display: false })` as
|
|
69
|
+
`span.float-caption__math`, so "Shown for v₀ = 20 m/s" names the lesson's
|
|
70
|
+
own `v₀`, which opens its card.
|
|
71
|
+
- **`unboundFigureSymbols(html)`** (`explico/reference/figure-symbols`) lists
|
|
72
|
+
the figures in an HTML string, and the symbols in each, that KaTeX drew
|
|
73
|
+
outside a notation binding. A course's browser test runs it on a hydrated
|
|
74
|
+
figure's `outerHTML`, which also holds what an island draws only in the
|
|
75
|
+
browser; the playground's `labs.spec.ts` does.
|
|
76
|
+
- **A link to a chapter shows the chapter's full title as its tooltip.** A
|
|
77
|
+
Markdown link or a JSX `<a href>` the author wrote to another chapter —
|
|
78
|
+
root-relative, relative, or under the course's `base`, with or without a
|
|
79
|
+
`#` anchor — gets the chapter's `title` as its `title` attribute
|
|
80
|
+
(`remarkNotation`, from the manifest's lessons). A link with a title of its
|
|
81
|
+
own, a same-page anchor, and a `[[…]]` reference, which opens a card of its
|
|
82
|
+
own, are left alone.
|
|
83
|
+
- **Cards on a page that is not a chapter** (rule S10). A `[[key]]` term,
|
|
84
|
+
bound math, a citation, an abbreviation and a `[[chapter#tab-key]]`
|
|
85
|
+
reference open their cards on the landing page and on a part's overview
|
|
86
|
+
page: `LessonFooter` mounts the notation, citation, abbreviation and
|
|
87
|
+
float-reference layers there, from the page's own notation bundle (shared
|
|
88
|
+
entries only). The manifest schema is now v25: `pages` lists every such
|
|
89
|
+
page with its `id` (`index`, `motion.index`), `slug`, `file` and a
|
|
90
|
+
`notation` bundle of the lesson shape, and `notation.bundles` and
|
|
91
|
+
`notation.backlinks` carry those pages' ids too.
|
|
92
|
+
|
|
93
|
+
### Changed
|
|
94
|
+
|
|
95
|
+
- **The operator baseline drops `\limsup` / `\liminf` and the
|
|
96
|
+
category-theory `\injlim` / `\projlim` / `\varlimsup` / `\varliminf` /
|
|
97
|
+
`\varinjlim` / `\varprojlim`.** Only `\lim` stays in `BASE_OPERATORS`
|
|
98
|
+
(`reference/base-operators.mjs`): the other eight are not "everyone knows
|
|
99
|
+
this" the way a plain limit, `\max`, or `\min` is, so rule N8's
|
|
100
|
+
`operator-explained` warning now asks a course that uses one for a
|
|
101
|
+
notation entry, like any other domain operator. A course that relies on
|
|
102
|
+
the old baseline can restore it with `rules.N8.allow: ['limsup',
|
|
103
|
+
'liminf', 'injlim', 'projlim', 'varlimsup', 'varliminf', 'varinjlim',
|
|
104
|
+
'varprojlim']` in its settings.
|
|
105
|
+
- **A lab's math wrapper never scrolls.** `.lab-math` loses its
|
|
106
|
+
`overflow-x: auto`, `overflow-y: hidden` and `padding-block`: a display
|
|
107
|
+
template's `.katex-display` scrolls sideways when it is wider than its
|
|
108
|
+
box, like a lesson equation, and carries the padding. `.lab-math` keeps
|
|
109
|
+
its `margin-block` only when it holds display math.
|
|
110
|
+
- **A float's caption line is at least as tall as its buttons**
|
|
111
|
+
(`min-block-size: 1.5rem` while a `.float-tool` shows, reset in print), so
|
|
112
|
+
the row never hangs below the line — past a figure's body, or into a
|
|
113
|
+
table's header — even when a course hides the caption's text.
|
|
114
|
+
- **The local-notation mismatch names what it compared.**
|
|
115
|
+
`assertLocalNotationParity` says "notation mismatch", names the page's
|
|
116
|
+
frontmatter `notation.local` and the manifest's bundle for the page, and
|
|
117
|
+
gives both causes left: a stale manifest, or an entry `validate:content`
|
|
118
|
+
rejected.
|
|
119
|
+
- **`notationDefinitionsForRenderedPage(manifest, pageId, dependencies)`**
|
|
120
|
+
takes a lesson's id or a non-chapter page's (`manifest.pages[].id`), and
|
|
121
|
+
its errors say "on page" instead of "in lesson". The `<Table>` and
|
|
122
|
+
`<Figure>` missing-number errors name rule S10.
|
|
123
|
+
|
|
124
|
+
### Fixed
|
|
125
|
+
|
|
126
|
+
- **An abbreviation glossary entry can be scrolled to.** Its anchor
|
|
127
|
+
(`#abbr-…`) sat on a `display: contents` wrapper, which has no box, so a
|
|
128
|
+
direct URL, a full navigation and a same-page click all left the reader
|
|
129
|
+
where they were. The `id` is on the entry's `<dt>`, with the
|
|
130
|
+
`scroll-margin-block-start: 5rem` the other anchor targets carry.
|
|
131
|
+
- **On a narrow screen, the header no longer covers Starlight's menu
|
|
132
|
+
button**, so the navigation opens again on a phone: the button sits one
|
|
133
|
+
level above the raised header (`starlight-menu-button > button`).
|
|
134
|
+
- **Links on a part overview page and in `<CourseContents>` use the course
|
|
135
|
+
accent** (`--sl-color-text-accent`) instead of the browser's default link
|
|
136
|
+
colour, which `.not-content` let through.
|
|
137
|
+
- **Printing no longer breaks a knowledge check that hydrates while the page
|
|
138
|
+
is prepared for print** (React error #418). A collapsed-content note never
|
|
139
|
+
goes inside an Astro island: a closed disclosure inside one gets its note
|
|
140
|
+
after the outermost island, once per target URL, so the folded questions
|
|
141
|
+
of one check share one note after the check.
|
|
142
|
+
- **A `notation.local` entry that a page uses only by shape** — an operator
|
|
143
|
+
entry `{}'` for `w'`, as rule N22 suggests — **no longer fails the build
|
|
144
|
+
as a stale manifest.** A page's own local entries seed its notation bundle
|
|
145
|
+
(`buildNotationRegistry`), so none is `unused-definition` any longer; the
|
|
146
|
+
`weak-local` advice still names one the page barely uses.
|
|
147
|
+
- **A `lint-ignore.yml` `<detail>` can name a hyphenated notation key.**
|
|
148
|
+
`parseLintIgnore`'s line grammar excluded `-` from the detail segment, so
|
|
149
|
+
`- notation-units:arg-min — …` (or any acknowledgement keyed to a
|
|
150
|
+
kebab-case notation key, the shape every key in a course that follows the
|
|
151
|
+
authoring docs' own naming convention takes) never matched the line
|
|
152
|
+
pattern and `validate:content` threw "expected `- <code>[:<detail>] —
|
|
153
|
+
<reason>`" instead of loading the file. Only whitespace and the em-dash
|
|
154
|
+
separator are excluded from `<detail>` now; a bare `-`, `--`, or ` - ` was
|
|
155
|
+
never valid *inside* a detail on its own (the separator needs the em dash,
|
|
156
|
+
or the ASCII forms flanked the way the grammar already required), so this
|
|
157
|
+
only admits what the docstring already promised: "a notation key".
|
|
158
|
+
- **A table inside a worked example no longer reports rule S3's
|
|
159
|
+
`raw-table-markdown`** (or `raw-table-html`): such a table is
|
|
160
|
+
illustrative, like an equation there. The scan counts a table only where a
|
|
161
|
+
`<Table>` would number it, at the lesson's top level or in a `<Memo>`,
|
|
162
|
+
`<ShortNote>` or `<Fold>` (`scanRawFloats`, `collectRawFloats`), as
|
|
163
|
+
`structure-rules.md#s3` always said.
|
|
164
|
+
- **A `<Table>` or `<Figure>` nested in a worked example fails with the real
|
|
165
|
+
reason.** `remarkNotation` names the component it sits in and the edit to
|
|
166
|
+
make, at its line; the components' own guards name the nesting before the
|
|
167
|
+
Astro configuration.
|
|
168
|
+
- **A figure never draws a vertical scrollbar.** `.keyed-figure__body` kept
|
|
169
|
+
`overflow-x: auto`, which computes `overflow-y` to `auto` as well, so a
|
|
170
|
+
fractional height at a browser zoom scrolled by a pixel; it now clips
|
|
171
|
+
vertically (`overflow-y: hidden`).
|
|
172
|
+
- **A display equation that just fits no longer scrolls by 2 px.** KaTeX's
|
|
173
|
+
stacked scripts hang a 2 px spacer outside their box; `.katex-display` (in
|
|
174
|
+
lesson content and in `.lab-math`) reserves it with `padding-inline: 2px`
|
|
175
|
+
on both sides, which keeps a centred equation centred.
|
|
176
|
+
- **A `[[key]]` or an `\explain` on a part overview page no longer fails
|
|
177
|
+
`astro build` after `validate:content` passed.** The page had no notation
|
|
178
|
+
entries, so the payload invariant found a trigger with no card. Rule S2's
|
|
179
|
+
"its math and `[[key]]` references render" now holds.
|
|
180
|
+
- **An indented `[[key]]` inside a component of an `.mdx` page reaches the
|
|
181
|
+
page's bundle.** The reference extractor masked a line indented by four
|
|
182
|
+
spaces as an indented code block, which MDX does not have, so text
|
|
183
|
+
indented inside `<Card>` or `<Memo>` rendered a trigger its bundle lacked,
|
|
184
|
+
and the build failed. A `.md` file keeps CommonMark's indented code.
|
|
185
|
+
|
|
186
|
+
## [0.11.1] - 2026-09-28
|
|
187
|
+
|
|
188
|
+
### Fixed
|
|
189
|
+
|
|
190
|
+
- **A side panel's labels wrap instead of scrolling sideways.** A chapter,
|
|
191
|
+
lesson or heading longer than the navigation or the "On this page" panel
|
|
192
|
+
is wide goes onto more lines, at any width, including one the reader
|
|
193
|
+
dragged a panel down to; a word longer than the panel hyphenates where the
|
|
194
|
+
language allows, else breaks. A resized panel used to keep each label on
|
|
195
|
+
one line and scroll sideways (`white-space: nowrap` on
|
|
196
|
+
`html[data-navigation-resized]` / `html[data-contents-resized]`), and the
|
|
197
|
+
"On this page" column kept a width taken from the navigation's
|
|
198
|
+
(`--sl-sidebar-width`), running past a narrowed panel's edge; it now fills
|
|
199
|
+
its panel. `.sidebar-pane` clips sideways (`overflow-x: hidden`), where a
|
|
200
|
+
compact chapter's invisible resting tooltip reached past it; a showing
|
|
201
|
+
tooltip is `position: fixed` and is never cut.
|
|
202
|
+
|
|
17
203
|
## [0.11.0] - 2026-09-28
|
|
18
204
|
|
|
19
205
|
### Breaking
|
|
@@ -2126,7 +2312,9 @@ under **Breaking** below; a PATCH never breaks.
|
|
|
2126
2312
|
system with its completeness gate, the curriculum-graph validator, the
|
|
2127
2313
|
Astro/Starlight UI, the LMS / session / analytics seams, and the `content` CLI.
|
|
2128
2314
|
|
|
2129
|
-
[Unreleased]: https://github.com/danieltuzes/explico/compare/v0.
|
|
2315
|
+
[Unreleased]: https://github.com/danieltuzes/explico/compare/v0.12.0...HEAD
|
|
2316
|
+
[0.12.0]: https://github.com/danieltuzes/explico/compare/v0.11.1...v0.12.0
|
|
2317
|
+
[0.11.1]: https://github.com/danieltuzes/explico/compare/v0.11.0...v0.11.1
|
|
2130
2318
|
[0.11.0]: https://github.com/danieltuzes/explico/compare/v0.10.0...v0.11.0
|
|
2131
2319
|
[0.10.0]: https://github.com/danieltuzes/explico/compare/v0.9.0...v0.10.0
|
|
2132
2320
|
[0.9.0]: https://github.com/danieltuzes/explico/compare/v0.8.0...v0.9.0
|
package/dist/compiler/astro.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ import '../reference/examples-validate.js';
|
|
|
17
17
|
import '../reference/folds-validate.js';
|
|
18
18
|
import '../reference/knowledge-checks-validate.js';
|
|
19
19
|
import '../reference/raw-floats-validate.js';
|
|
20
|
+
import '../reference/lab-katex-validate.js';
|
|
20
21
|
import '../reference/gate-math.js';
|
|
21
22
|
import '../reference/mixed-content.js';
|
|
22
23
|
import '../reference/gloss.js';
|
|
@@ -26,7 +27,12 @@ import './lesson-derivation.js';
|
|
|
26
27
|
|
|
27
28
|
/** Match only a configured lesson artifact below Astro's deployment base. */
|
|
28
29
|
declare function matchStandaloneDownload(pathname: string, base: string, lessonSlugs: readonly string[]): string | undefined;
|
|
29
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* Fail loudly instead of rendering math triggers whose local cards are
|
|
32
|
+
* missing. The manifest bundles every `notation.local` entry with its page,
|
|
33
|
+
* so the two lists differ only when the manifest is older than the page, or
|
|
34
|
+
* when `validate:content` rejected an entry.
|
|
35
|
+
*/
|
|
30
36
|
declare function assertLocalNotationParity(lessonId: string, authoredKeys: readonly string[], renderedKeys: readonly string[]): void;
|
|
31
37
|
/** Fail an Astro build if final rendered HTML contains an unusable trigger. */
|
|
32
38
|
declare function assertNotationPayloadHtml(page: string, html: string): void;
|
package/dist/compiler/astro.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
writeManifest
|
|
9
9
|
} from "./manifest.js";
|
|
10
10
|
import { missingNotationPayloadKeys } from "../reference/page-notation.js";
|
|
11
|
+
import { assertFigureSymbolsBound } from "../reference/figure-symbols.js";
|
|
11
12
|
import { writeStandaloneLessonExports } from "./exports/standalone-html.js";
|
|
12
13
|
import remarkAbbreviations from "../reference/remark-abbreviations.mjs";
|
|
13
14
|
import remarkCitation from "../reference/remark-citation.mjs";
|
|
@@ -87,7 +88,7 @@ function assertLocalNotationParity(lessonId, authoredKeys, renderedKeys) {
|
|
|
87
88
|
return;
|
|
88
89
|
}
|
|
89
90
|
throw new Error(
|
|
90
|
-
`
|
|
91
|
+
`Explico notation mismatch for ${lessonId}: the page's frontmatter notation.local declares [${authored.join(", ")}], but the manifest's notation bundle for the page holds [${rendered.join(", ")}]. The manifest bundles every notation.local entry with its page, so either it is stale \u2014 pass courseConfig to loadManifest() and add explicoContentRefresh() to the Astro integrations \u2014 or \`pnpm validate:content\` rejected an entry: fix the errors it reports.`
|
|
91
92
|
);
|
|
92
93
|
}
|
|
93
94
|
function assertNotationPayloadHtml(page, html) {
|
|
@@ -139,7 +140,9 @@ async function validateGeneratedNotationPayloads(directory) {
|
|
|
139
140
|
if (entry.isDirectory()) {
|
|
140
141
|
await walk(child);
|
|
141
142
|
} else if (entry.isFile() && entry.name.endsWith(".html")) {
|
|
142
|
-
|
|
143
|
+
const html = await readFile(child, "utf8");
|
|
144
|
+
assertNotationPayloadHtml(relative(root, child), html);
|
|
145
|
+
assertFigureSymbolsBound(relative(root, child), html);
|
|
143
146
|
}
|
|
144
147
|
}
|
|
145
148
|
};
|
package/dist/compiler/cli.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ import '../reference/examples-validate.js';
|
|
|
18
18
|
import '../reference/folds-validate.js';
|
|
19
19
|
import '../reference/knowledge-checks-validate.js';
|
|
20
20
|
import '../reference/raw-floats-validate.js';
|
|
21
|
+
import '../reference/lab-katex-validate.js';
|
|
21
22
|
import '../reference/mixed-content.js';
|
|
22
23
|
import '../reference/gloss.js';
|
|
23
24
|
import '../rules/waivers.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CurriculumCatalog } from '../curriculum/validation.js';
|
|
2
|
-
import { NotationReferenceInput, AbbreviationDefinitionInput, NotationRegistryInput, LocalNotationDefinitionInput, NotationSuspension } from '../reference/types.js';
|
|
2
|
+
import { NotationLessonInput, NotationReferenceInput, AbbreviationDefinitionInput, NotationRegistryInput, LocalNotationDefinitionInput, NotationSuspension } from '../reference/types.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* The single path from content files to typed data.
|
|
@@ -54,6 +54,29 @@ declare function loadAbbreviationRegistryInput(): Promise<readonly AbbreviationD
|
|
|
54
54
|
* decoration (the page would contradict itself) each fail the load.
|
|
55
55
|
*/
|
|
56
56
|
declare function notationSuspensions(raw: unknown, localDefinitions: readonly LocalNotationDefinitionInput[], file: string): NotationSuspension[];
|
|
57
|
+
/**
|
|
58
|
+
* A docs page that is **not a chapter** (rule S10): the landing page, a part's
|
|
59
|
+
* overview page, and any other page outside a part directory — every slug
|
|
60
|
+
* `isLessonSlug` rejects. Such a page may explain its symbols like a chapter,
|
|
61
|
+
* but it recalls them only: it takes no `notation.local` and no float numbers.
|
|
62
|
+
*/
|
|
63
|
+
interface NonChapterPage {
|
|
64
|
+
/** `lessonIdFromSlug(slug)` — `index`, `motion.index`, `glossary`. Never a lesson id. */
|
|
65
|
+
readonly id: string;
|
|
66
|
+
/** Docs slug without extension, e.g. `motion/index`. */
|
|
67
|
+
readonly slug: string;
|
|
68
|
+
readonly file: string;
|
|
69
|
+
readonly data: Record<string, unknown>;
|
|
70
|
+
readonly body: string;
|
|
71
|
+
readonly bodyLine: number;
|
|
72
|
+
/**
|
|
73
|
+
* The page as notation input — no local entries, no suspensions — so the
|
|
74
|
+
* registry resolves its `[[key]]` references and the text and math gates
|
|
75
|
+
* scan it exactly as they scan a chapter.
|
|
76
|
+
*/
|
|
77
|
+
readonly notation: NotationLessonInput;
|
|
78
|
+
}
|
|
79
|
+
declare function loadNonChapterPages(): NonChapterPage[];
|
|
57
80
|
declare function loadNotationRegistryInput(): Promise<NotationRegistryInput>;
|
|
58
81
|
/**
|
|
59
82
|
* Notation frontmatter objects for `remark-notation`'s `definitions` option.
|
|
@@ -101,4 +124,4 @@ declare function notationSourceLocatorGaps(): {
|
|
|
101
124
|
total: number;
|
|
102
125
|
};
|
|
103
126
|
|
|
104
|
-
export { type NotationSpec, type PartOverviewPage, extractNotationReferences, loadAbbreviationRegistryInput, loadCurriculumCatalog, loadLessonChecks, loadNotationDefinitions, loadNotationRegistryInput, loadNotationSpecs, loadPartOverviews, loadSourceRecords, notationSourceLocatorGaps, notationSuspensions };
|
|
127
|
+
export { type NonChapterPage, type NotationSpec, type PartOverviewPage, extractNotationReferences, loadAbbreviationRegistryInput, loadCurriculumCatalog, loadLessonChecks, loadNonChapterPages, loadNotationDefinitions, loadNotationRegistryInput, loadNotationSpecs, loadPartOverviews, loadSourceRecords, notationSourceLocatorGaps, notationSuspensions };
|
|
@@ -139,16 +139,20 @@ function sourceSpan(file, text, index) {
|
|
|
139
139
|
column: (lines.at(-1)?.length ?? 0) + 1
|
|
140
140
|
};
|
|
141
141
|
}
|
|
142
|
-
function maskMarkdownLiterals(text) {
|
|
142
|
+
function maskMarkdownLiterals(text, mdx) {
|
|
143
143
|
const mask = (value) => value.replace(/[^\n]/g, " ");
|
|
144
|
-
|
|
144
|
+
const fenced = text.replace(
|
|
145
145
|
/(^|\n)[ \t]{0,3}(`{3,}|~{3,})[^\n]*\n[\s\S]*?(?:\n[ \t]{0,3}\2[^\n]*(?=\n|$)|$)/g,
|
|
146
146
|
mask
|
|
147
|
-
)
|
|
147
|
+
);
|
|
148
|
+
return (mdx ? fenced : fenced.replace(/^(?: {4}|\t).*$/gm, mask)).replace(
|
|
149
|
+
/(`+)(?!`)([^\n]*?)\1/g,
|
|
150
|
+
mask
|
|
151
|
+
);
|
|
148
152
|
}
|
|
149
153
|
function extractNotationReferences(text, file, kind) {
|
|
150
154
|
const references = [];
|
|
151
|
-
const searchable = maskMarkdownLiterals(text);
|
|
155
|
+
const searchable = maskMarkdownLiterals(text, file.endsWith(".mdx"));
|
|
152
156
|
const patterns = [
|
|
153
157
|
[/\[\[\s*([^\][]+?)\s*\]\]/g, kind ?? "prose"],
|
|
154
158
|
[/\\explain\{([^{}]+)\}/g, kind ?? "math"]
|
|
@@ -412,6 +416,34 @@ function notationLessonEntries() {
|
|
|
412
416
|
}
|
|
413
417
|
return lessons;
|
|
414
418
|
}
|
|
419
|
+
function loadNonChapterPages() {
|
|
420
|
+
const pages = [];
|
|
421
|
+
for (const path of filesBelow(join(CONTENT_ROOT, "docs"), [".md", ".mdx"])) {
|
|
422
|
+
const slug = docSlug(path);
|
|
423
|
+
if (isLessonSlug(slug)) continue;
|
|
424
|
+
const { file, data, body, bodyLine } = parseDocument(path);
|
|
425
|
+
const id = lessonIdFromSlug(slug);
|
|
426
|
+
pages.push({
|
|
427
|
+
id,
|
|
428
|
+
slug: slug.replace(/\.(md|mdx)$/, ""),
|
|
429
|
+
file,
|
|
430
|
+
data,
|
|
431
|
+
body,
|
|
432
|
+
bodyLine,
|
|
433
|
+
notation: {
|
|
434
|
+
lessonId: id,
|
|
435
|
+
status: status(data.editorialStatus ?? "draft", `${file} editorialStatus`),
|
|
436
|
+
localDefinitions: [],
|
|
437
|
+
suspended: [],
|
|
438
|
+
references: extractNotationReferences(body, file, void 0),
|
|
439
|
+
body,
|
|
440
|
+
bodyLine,
|
|
441
|
+
source: { file }
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
return pages;
|
|
446
|
+
}
|
|
415
447
|
async function loadNotationRegistryInput() {
|
|
416
448
|
return {
|
|
417
449
|
sharedDefinitions: sharedNotationEntries(),
|
|
@@ -506,6 +538,7 @@ export {
|
|
|
506
538
|
loadAbbreviationRegistryInput,
|
|
507
539
|
loadCurriculumCatalog,
|
|
508
540
|
loadLessonChecks,
|
|
541
|
+
loadNonChapterPages,
|
|
509
542
|
loadNotationDefinitions,
|
|
510
543
|
loadNotationRegistryInput,
|
|
511
544
|
loadNotationSpecs,
|
|
@@ -16,6 +16,7 @@ import '../../reference/examples-validate.js';
|
|
|
16
16
|
import '../../reference/folds-validate.js';
|
|
17
17
|
import '../../reference/knowledge-checks-validate.js';
|
|
18
18
|
import '../../reference/raw-floats-validate.js';
|
|
19
|
+
import '../../reference/lab-katex-validate.js';
|
|
19
20
|
import '../../reference/gate-math.js';
|
|
20
21
|
import '../../reference/mixed-content.js';
|
|
21
22
|
import '../../reference/gloss.js';
|
|
@@ -8,6 +8,7 @@ import { ExampleLabelRecord, ExampleDiagnostic } from '../reference/examples-val
|
|
|
8
8
|
import { FoldDiagnostic } from '../reference/folds-validate.js';
|
|
9
9
|
import { KnowledgeCheckLabelRecord, KnowledgeCheckDiagnostic } from '../reference/knowledge-checks-validate.js';
|
|
10
10
|
import { RawFloatDiagnostic } from '../reference/raw-floats-validate.js';
|
|
11
|
+
import { LabKatexDiagnostic } from '../reference/lab-katex-validate.js';
|
|
11
12
|
import { MathGateDiagnostic } from '../reference/gate-math.js';
|
|
12
13
|
import { ContentSegment } from '../reference/mixed-content.js';
|
|
13
14
|
import { GlyphScopeEntry } from '../reference/gloss.js';
|
|
@@ -167,8 +168,14 @@ import '../rules/course-rules.js';
|
|
|
167
168
|
* state of a `<Fold>`; `disclosure.figures` now says whether a lab's tools
|
|
168
169
|
* start open; `diagnostics.folds` holds `fold-unnamed`
|
|
169
170
|
* (docs/plans/figures-and-short-captions.md, Parts 1–2).
|
|
171
|
+
*
|
|
172
|
+
* v25 (cards on non-chapter pages, rule S10): `pages` — every docs page that
|
|
173
|
+
* is not a chapter (the landing page, a part's overview page, …) with its
|
|
174
|
+
* `id`, `slug`, `file` and a `notation` bundle of the lesson shape, so the
|
|
175
|
+
* footer renders the cards its `[[key]]` terms and math open.
|
|
176
|
+
* `notation.bundles` and `notation.backlinks` now carry those pages' ids too.
|
|
170
177
|
*/
|
|
171
|
-
declare const MANIFEST_SCHEMA_VERSION =
|
|
178
|
+
declare const MANIFEST_SCHEMA_VERSION = 25;
|
|
172
179
|
declare const MANIFEST_PATH: string;
|
|
173
180
|
interface ManifestCitation {
|
|
174
181
|
readonly id: string;
|
|
@@ -340,6 +347,19 @@ interface ManifestLesson {
|
|
|
340
347
|
readonly resolution: readonly ManifestNotationResolution[];
|
|
341
348
|
};
|
|
342
349
|
}
|
|
350
|
+
/**
|
|
351
|
+
* A docs page that is not a chapter (v25, rule S10) — the landing page, a
|
|
352
|
+
* part's overview page, any page outside a part directory. It numbers
|
|
353
|
+
* nothing, so it carries only what its cards need: the notation bundle its
|
|
354
|
+
* `[[key]]` terms and math reach, shared entries only.
|
|
355
|
+
*/
|
|
356
|
+
interface ManifestPage {
|
|
357
|
+
/** `index`, `motion.index`, `glossary` — `lessonIdFromSlug` of the slug; never a lesson id. */
|
|
358
|
+
readonly id: string;
|
|
359
|
+
readonly slug: string;
|
|
360
|
+
readonly file: string;
|
|
361
|
+
readonly notation: ManifestLesson['notation'];
|
|
362
|
+
}
|
|
343
363
|
/** The reader-facing "about this material" bundle — the `[?]` panel's data. */
|
|
344
364
|
interface ManifestAbout {
|
|
345
365
|
/** `explico`'s own identity, from its `package.json` (the version's golden source). */
|
|
@@ -438,6 +458,8 @@ interface Manifest {
|
|
|
438
458
|
/** The course's parts in config order, with their chapters and overview pages. */
|
|
439
459
|
readonly parts: readonly ManifestPart[];
|
|
440
460
|
readonly lessons: readonly ManifestLesson[];
|
|
461
|
+
/** Every docs page that is not a chapter, by id (v25). */
|
|
462
|
+
readonly pages: readonly ManifestPage[];
|
|
441
463
|
/** Full competency prerequisite graph. */
|
|
442
464
|
readonly prereqEdges: readonly PrereqEdge[];
|
|
443
465
|
/** Equation identity (D7): every labelled display equation + the render-path lookup. */
|
|
@@ -501,8 +523,12 @@ interface Manifest {
|
|
|
501
523
|
readonly equations: readonly EquationDiagnostic[];
|
|
502
524
|
/** `<Table>` identity checks (F-tables-figures) — same codes, `tab-` prefixed. */
|
|
503
525
|
readonly tables: readonly TableDiagnostic[];
|
|
504
|
-
/**
|
|
505
|
-
|
|
526
|
+
/**
|
|
527
|
+
* `<Figure>` identity checks (F-tables-figures) — same codes, `fig-`
|
|
528
|
+
* prefixed — and rule V3's `lab-katex-direct`: a file a lesson imports
|
|
529
|
+
* that calls KaTeX itself.
|
|
530
|
+
*/
|
|
531
|
+
readonly figures: readonly (FigureDiagnostic | LabKatexDiagnostic)[];
|
|
506
532
|
/** `<Diagram>` identity checks — same codes, `dia-` prefixed. */
|
|
507
533
|
readonly diagrams: readonly DiagramDiagnostic[];
|
|
508
534
|
/** Knowledge-check identity and `[[check-…]]` reference checks. */
|
|
@@ -549,4 +575,4 @@ declare function readManifestSync(): Manifest;
|
|
|
549
575
|
*/
|
|
550
576
|
declare function loadManifest(courseConfig?: CourseConfig): Promise<Manifest>;
|
|
551
577
|
|
|
552
|
-
export { MANIFEST_PATH, MANIFEST_SCHEMA_VERSION, type Manifest, type ManifestAbout, type ManifestAssessmentItemContent, type ManifestCitation, type ManifestLesson, type ManifestLessonFloat, type ManifestLessonNotation, type ManifestNotationResolution, type ManifestPart, type ManifestTitledBlock, type PrereqEdge, compileManifest, loadManifest, readManifestSync, serializeManifest, writeManifest };
|
|
578
|
+
export { MANIFEST_PATH, MANIFEST_SCHEMA_VERSION, type Manifest, type ManifestAbout, type ManifestAssessmentItemContent, type ManifestCitation, type ManifestLesson, type ManifestLessonFloat, type ManifestLessonNotation, type ManifestNotationResolution, type ManifestPage, type ManifestPart, type ManifestTitledBlock, type PrereqEdge, compileManifest, loadManifest, readManifestSync, serializeManifest, writeManifest };
|
|
@@ -40,6 +40,9 @@ import {
|
|
|
40
40
|
import {
|
|
41
41
|
validateRawFloats
|
|
42
42
|
} from "../reference/raw-floats-validate.js";
|
|
43
|
+
import {
|
|
44
|
+
validateLabKatexImports
|
|
45
|
+
} from "../reference/lab-katex-validate.js";
|
|
43
46
|
import {
|
|
44
47
|
gateContentMath,
|
|
45
48
|
pageScope
|
|
@@ -57,6 +60,7 @@ import { gateCourseRules } from "../reference/gate-course-rules.js";
|
|
|
57
60
|
import { gateHeadingLength } from "../reference/gate-heading-length.js";
|
|
58
61
|
import { gateHeadingStructure } from "../reference/gate-heading-structure.js";
|
|
59
62
|
import { gatePartPages } from "../reference/gate-part-pages.js";
|
|
63
|
+
import { gateNonChapterPages } from "../reference/gate-non-chapter-pages.js";
|
|
60
64
|
import {
|
|
61
65
|
scanSectionHeadings,
|
|
62
66
|
scanTitledBlocks
|
|
@@ -96,6 +100,7 @@ import {
|
|
|
96
100
|
loadCurriculumCatalog,
|
|
97
101
|
loadNotationDefinitions,
|
|
98
102
|
loadNotationRegistryInput,
|
|
103
|
+
loadNonChapterPages,
|
|
99
104
|
loadNotationSpecs,
|
|
100
105
|
loadPartOverviews,
|
|
101
106
|
loadSourceRecords
|
|
@@ -106,7 +111,7 @@ import {
|
|
|
106
111
|
isLessonSlug,
|
|
107
112
|
lessonIdFromSlug
|
|
108
113
|
} from "./lesson-derivation.js";
|
|
109
|
-
const MANIFEST_SCHEMA_VERSION =
|
|
114
|
+
const MANIFEST_SCHEMA_VERSION = 25;
|
|
110
115
|
const REPOSITORY_ROOT = process.cwd();
|
|
111
116
|
const CONTENT_ROOT = join(REPOSITORY_ROOT, "content");
|
|
112
117
|
const MANIFEST_PATH = join(REPOSITORY_ROOT, "build", "manifest.json");
|
|
@@ -260,7 +265,15 @@ async function compileManifest(courseConfig) {
|
|
|
260
265
|
loadNotationRegistryInput(),
|
|
261
266
|
loadAbbreviationRegistryInput()
|
|
262
267
|
]);
|
|
263
|
-
const
|
|
268
|
+
const nonChapterPages = loadNonChapterPages();
|
|
269
|
+
const withPages = {
|
|
270
|
+
...notationInput,
|
|
271
|
+
lessons: [
|
|
272
|
+
...notationInput.lessons,
|
|
273
|
+
...nonChapterPages.map((page) => page.notation)
|
|
274
|
+
]
|
|
275
|
+
};
|
|
276
|
+
const registry = buildNotationRegistry(withPages);
|
|
264
277
|
const cardsByKey = new Map(
|
|
265
278
|
notationInput.sharedDefinitions.map((definition) => [
|
|
266
279
|
definition.key,
|
|
@@ -283,7 +296,7 @@ async function compileManifest(courseConfig) {
|
|
|
283
296
|
};
|
|
284
297
|
const mathIssues = [
|
|
285
298
|
...gateContentMath({
|
|
286
|
-
notationInput,
|
|
299
|
+
notationInput: withPages,
|
|
287
300
|
assessments: assessmentsInput,
|
|
288
301
|
lessonAssessments: catalog.lessons.map((lesson) => ({
|
|
289
302
|
lessonId: lesson.id,
|
|
@@ -291,7 +304,7 @@ async function compileManifest(courseConfig) {
|
|
|
291
304
|
}))
|
|
292
305
|
}),
|
|
293
306
|
...rules.isEnabled("N9") ? [
|
|
294
|
-
...gateProseNotation(
|
|
307
|
+
...gateProseNotation(withPages.lessons, {
|
|
295
308
|
allowSpelledUnits: rules.strings("N9", "allow")
|
|
296
309
|
}),
|
|
297
310
|
...gateProseNotationInAssessments(assessmentsInput, {
|
|
@@ -299,11 +312,11 @@ async function compileManifest(courseConfig) {
|
|
|
299
312
|
})
|
|
300
313
|
] : [],
|
|
301
314
|
...rules.isEnabled("N12") ? [
|
|
302
|
-
...gateMathPunctuation(
|
|
315
|
+
...gateMathPunctuation(withPages.lessons, mathEndAbbreviations),
|
|
303
316
|
...gateMathPunctuationInAssessments(assessmentsInput, mathEndAbbreviations)
|
|
304
317
|
] : [],
|
|
305
318
|
...rules.isEnabled("N14") ? gateAbbreviations({
|
|
306
|
-
notationInput,
|
|
319
|
+
notationInput: withPages,
|
|
307
320
|
abbreviations: abbreviationsInput,
|
|
308
321
|
courseAbbreviations: rules.settings("N14")
|
|
309
322
|
}) : [],
|
|
@@ -317,7 +330,9 @@ async function compileManifest(courseConfig) {
|
|
|
317
330
|
// The course's own rules (`courseRules`): one pass for every enabled `X`
|
|
318
331
|
// rule that carries a `forbid` pattern. A disabled one, and a guide rule
|
|
319
332
|
// with no pattern, do no work here.
|
|
320
|
-
...gateCourseRules(
|
|
333
|
+
...gateCourseRules(withPages.lessons, rules.course),
|
|
334
|
+
// S10 is locked: a float on a non-chapter page has no number to render.
|
|
335
|
+
...gateNonChapterPages(nonChapterPages)
|
|
321
336
|
];
|
|
322
337
|
const notationLessonById = new Map(
|
|
323
338
|
notationInput.lessons.map((lesson) => [lesson.lessonId, lesson])
|
|
@@ -381,7 +396,7 @@ async function compileManifest(courseConfig) {
|
|
|
381
396
|
}
|
|
382
397
|
const waiverDiagnostics = [];
|
|
383
398
|
const waiverRules = { courseRules: rules.course };
|
|
384
|
-
const waivers =
|
|
399
|
+
const waivers = withPages.lessons.flatMap((lesson) => {
|
|
385
400
|
const collected = collectWaivers(lesson, waiverRules);
|
|
386
401
|
waiverDiagnostics.push(...collected.diagnostics);
|
|
387
402
|
return collected.waivers;
|
|
@@ -593,6 +608,14 @@ async function compileManifest(courseConfig) {
|
|
|
593
608
|
}
|
|
594
609
|
);
|
|
595
610
|
};
|
|
611
|
+
const labKatexDiagnostics = validateLabKatexImports(
|
|
612
|
+
notationInput.lessons.map((lesson) => ({
|
|
613
|
+
lessonId: lesson.lessonId,
|
|
614
|
+
file: lesson.source.file,
|
|
615
|
+
body: lesson.body
|
|
616
|
+
})),
|
|
617
|
+
REPOSITORY_ROOT
|
|
618
|
+
);
|
|
596
619
|
const rawFloatDiagnostics = validateRawFloats(
|
|
597
620
|
notationInput.lessons.map((lesson) => ({
|
|
598
621
|
lessonId: lesson.lessonId,
|
|
@@ -665,9 +688,24 @@ async function compileManifest(courseConfig) {
|
|
|
665
688
|
});
|
|
666
689
|
}
|
|
667
690
|
lessons.sort((a, b) => a.id.localeCompare(b.id));
|
|
691
|
+
const pages = nonChapterPages.map((page) => ({
|
|
692
|
+
id: page.id,
|
|
693
|
+
slug: page.slug,
|
|
694
|
+
file: page.file,
|
|
695
|
+
notation: lessonNotationBundle(
|
|
696
|
+
bundlesByLesson.get(page.id),
|
|
697
|
+
definitionsById,
|
|
698
|
+
lessonNotationResolution(
|
|
699
|
+
page.notation,
|
|
700
|
+
definitionsById,
|
|
701
|
+
availableDefinitionIds
|
|
702
|
+
),
|
|
703
|
+
cardsByKey
|
|
704
|
+
)
|
|
705
|
+
})).sort((a, b) => a.id.localeCompare(b.id));
|
|
668
706
|
const consistencyIssues = runConsistencyChecks({
|
|
669
707
|
registry,
|
|
670
|
-
notationInput,
|
|
708
|
+
notationInput: withPages,
|
|
671
709
|
specs: loadNotationSpecs(),
|
|
672
710
|
catalog,
|
|
673
711
|
lintIgnore: loadLintIgnore(),
|
|
@@ -741,6 +779,7 @@ async function compileManifest(courseConfig) {
|
|
|
741
779
|
sidebar,
|
|
742
780
|
parts: manifestParts,
|
|
743
781
|
lessons,
|
|
782
|
+
pages,
|
|
744
783
|
prereqEdges,
|
|
745
784
|
equations: {
|
|
746
785
|
labels: equationValidation.labels,
|
|
@@ -776,7 +815,7 @@ async function compileManifest(courseConfig) {
|
|
|
776
815
|
consistency: dropDisabled(consistencyIssues, off),
|
|
777
816
|
equations: dropDisabled(equationValidation.diagnostics, off),
|
|
778
817
|
tables: dropDisabled(tableValidation.diagnostics, off),
|
|
779
|
-
figures: dropDisabled(figureValidation.diagnostics, off),
|
|
818
|
+
figures: dropDisabled([...figureValidation.diagnostics, ...labKatexDiagnostics], off),
|
|
780
819
|
diagrams: dropDisabled(diagramValidation.diagnostics, off),
|
|
781
820
|
checks: dropDisabled(checkValidation.diagnostics, off),
|
|
782
821
|
examples: dropDisabled(exampleValidation.diagnostics, off),
|
|
@@ -16,6 +16,7 @@ import '../reference/examples-validate.js';
|
|
|
16
16
|
import '../reference/folds-validate.js';
|
|
17
17
|
import '../reference/knowledge-checks-validate.js';
|
|
18
18
|
import '../reference/raw-floats-validate.js';
|
|
19
|
+
import '../reference/lab-katex-validate.js';
|
|
19
20
|
import '../reference/gate-math.js';
|
|
20
21
|
import '../reference/mixed-content.js';
|
|
21
22
|
import '../reference/gloss.js';
|
|
@@ -26,6 +26,12 @@ export function isBaseOperator(name: any, operators: any): boolean;
|
|
|
26
26
|
* baseline — trig, the exponential and logarithms, limits, extrema, and the
|
|
27
27
|
* big operators — kept greppable and small like the base library.
|
|
28
28
|
*
|
|
29
|
+
* Only the plain limit, `\lim`, is baseline. `\limsup` / `\liminf` and the
|
|
30
|
+
* category-theory `\injlim` / `\projlim` / `\varlimsup` / `\varliminf` /
|
|
31
|
+
* `\varinjlim` / `\varprojlim` read fine to someone who already knows them,
|
|
32
|
+
* but none is "everyone knows this" the way `\lim` or `\max` is — a course
|
|
33
|
+
* that uses one owes it a notation entry, like any other domain operator.
|
|
34
|
+
*
|
|
29
35
|
* A domain operator (`Var`, `Cov`, `Corr`, `sgn`, `erf`, `Tr`, `argmax`, …) is
|
|
30
36
|
* written `\operatorname{…}` and is NOT here: it earns a card, a
|
|
31
37
|
* `notation.local` entry, or an explicit line in `course.config.ts`
|