explico 0.2.0 → 0.3.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.
Files changed (73) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/README.md +46 -5
  3. package/dist/compiler/cli.d.ts +16 -0
  4. package/dist/compiler/cli.js +41 -3
  5. package/dist/compiler/collections.js +4 -1
  6. package/dist/compiler/course-config.d.ts +52 -1
  7. package/dist/compiler/knowledge-version.d.ts +58 -0
  8. package/dist/compiler/knowledge-version.js +80 -0
  9. package/dist/compiler/manifest.d.ts +77 -2
  10. package/dist/compiler/manifest.js +115 -18
  11. package/dist/compiler/resolution-report.d.ts +4 -0
  12. package/dist/reference/base-operators.d.mts +38 -0
  13. package/dist/reference/base-operators.mjs +103 -0
  14. package/dist/reference/consistency.d.ts +7 -2
  15. package/dist/reference/consistency.js +51 -2
  16. package/dist/reference/diagram-layout.d.ts +63 -0
  17. package/dist/reference/diagram-layout.js +122 -0
  18. package/dist/reference/equations-validate.d.ts +5 -0
  19. package/dist/reference/equations-validate.js +1 -1
  20. package/dist/reference/equations.d.mts +42 -11
  21. package/dist/reference/equations.mjs +58 -17
  22. package/dist/reference/figures-validate.d.ts +21 -0
  23. package/dist/reference/figures-validate.js +13 -0
  24. package/dist/reference/keyed-float-validate.d.ts +67 -0
  25. package/dist/reference/keyed-float-validate.js +110 -0
  26. package/dist/reference/keyed-jsx-floats.d.mts +56 -0
  27. package/dist/reference/keyed-jsx-floats.mjs +167 -0
  28. package/dist/reference/math-glyphs.d.mts +18 -0
  29. package/dist/reference/math-glyphs.mjs +92 -0
  30. package/dist/reference/remark-notation.d.mts +12 -0
  31. package/dist/reference/remark-notation.mjs +199 -9
  32. package/dist/reference/tables-validate.d.ts +21 -0
  33. package/dist/reference/tables-validate.js +13 -0
  34. package/dist/reference/units-vocab.js +19 -1
  35. package/docs/VERSIONING.md +66 -0
  36. package/docs/authoring/README.md +96 -0
  37. package/docs/authoring/agents.md +63 -0
  38. package/docs/authoring/migrating.md +107 -0
  39. package/docs/authoring/notation.md +296 -0
  40. package/docs/authoring/versioning.md +116 -0
  41. package/docs/authoring/visualizations.md +195 -0
  42. package/package.json +20 -5
  43. package/src/compiler/cli.ts +49 -0
  44. package/src/compiler/collections.ts +12 -3
  45. package/src/compiler/course-config.ts +54 -0
  46. package/src/compiler/knowledge-version.ts +157 -0
  47. package/src/compiler/manifest.ts +239 -17
  48. package/src/components/CurriculumMap.astro +27 -0
  49. package/src/components/diagram/Diagram.astro +317 -0
  50. package/src/components/examples/CompactExamples.astro +1 -1
  51. package/src/components/figure/Figure.astro +104 -0
  52. package/src/components/notation/render-notation-math.ts +13 -2
  53. package/src/components/reference/FloatRefLayer.astro +671 -0
  54. package/src/components/starlight/AboutPanel.astro +255 -0
  55. package/src/components/starlight/HeadingAnchorEnhancer.astro +3 -3
  56. package/src/components/starlight/KeyedFloatEnhancer.astro +137 -0
  57. package/src/components/starlight/LayoutSocialIcons.astro +12 -0
  58. package/src/components/starlight/LessonFooter.astro +4 -2
  59. package/src/components/table/Table.astro +104 -0
  60. package/src/reference/base-operators.mjs +103 -0
  61. package/src/reference/consistency.ts +83 -2
  62. package/src/reference/diagram-layout.ts +246 -0
  63. package/src/reference/equations-validate.ts +6 -1
  64. package/src/reference/equations.mjs +58 -17
  65. package/src/reference/figures-validate.ts +40 -0
  66. package/src/reference/keyed-float-validate.ts +207 -0
  67. package/src/reference/keyed-jsx-floats.mjs +167 -0
  68. package/src/reference/math-glyphs.mjs +92 -0
  69. package/src/reference/remark-notation.mjs +199 -9
  70. package/src/reference/tables-validate.ts +40 -0
  71. package/src/reference/units-vocab.ts +18 -0
  72. package/src/styles/global.css +115 -7
  73. package/src/components/starlight/EquationEnhancer.astro +0 -87
@@ -0,0 +1,13 @@
1
+ import {
2
+ validateKeyedFloats
3
+ } from "./keyed-float-validate.js";
4
+ function validateTables(lessons) {
5
+ return validateKeyedFloats(lessons, {
6
+ tagName: "Table",
7
+ prefix: "tab",
8
+ noun: "table"
9
+ });
10
+ }
11
+ export {
12
+ validateTables
13
+ };
@@ -97,7 +97,25 @@ const UNITS_VOCAB = /* @__PURE__ */ new Set([
97
97
  "quantity",
98
98
  "quoted",
99
99
  "explicitly",
100
- "calculations"
100
+ "calculations",
101
+ // motion / physical quantities (playground corpus)
102
+ "meter",
103
+ "meters",
104
+ "second",
105
+ "seconds",
106
+ "speed",
107
+ "velocity",
108
+ "acceleration",
109
+ "gravitational",
110
+ "launch",
111
+ "angle",
112
+ "degrees",
113
+ "horizontal",
114
+ "vertical",
115
+ "level",
116
+ "ground",
117
+ "squared",
118
+ "above"
101
119
  ]);
102
120
  function unitsTokens(units) {
103
121
  return units.toLowerCase().split(/[\s]+/).flatMap((word) => word.split(/(?<=[a-z0-9])-(?=[a-z])/)).map((word) => word.replace(/^[^a-z0-9_]+|[^a-z0-9_]+$/g, "")).filter((word) => word.length > 0 && !/^\d+$/.test(word));
@@ -0,0 +1,66 @@
1
+ # Versioning
2
+
3
+ `explico` follows [Semantic Versioning](https://semver.org/). The installed
4
+ `package.json` `version` is authoritative for what a consumer has;
5
+ [`CHANGELOG.md`](../CHANGELOG.md) records what changed; this file defines what a
6
+ bump *means*.
7
+
8
+ There are two independent version streams. This document is about the **engine**.
9
+ A course built on explico carries its own **knowledge version** over its
10
+ content — see [`authoring/versioning.md`](authoring/versioning.md).
11
+
12
+ ## The public surface
13
+
14
+ A change is "breaking" when it can break a consumer who followed the docs. The
15
+ consumer-visible surface is:
16
+
17
+ | Surface | Where |
18
+ | --- | --- |
19
+ | Published module entry points | `package.json` `publishConfig.exports` — the `explico/*` specifiers and their `.d.ts` |
20
+ | Astro / React components | names + props of everything under `components/` that the README wires |
21
+ | remark / rehype adapter options | the option objects of `reference/*.mjs` (`remarkNotation`, `remarkCitation`, `createNotationKatexOptions`, …) |
22
+ | The compiler manifest | `MANIFEST_SCHEMA_VERSION` and the shape of `Manifest` |
23
+ | The `content` CLI | the commands, their flags, and every diagnostic `code` string |
24
+ | `CourseConfig` | the interface in `compiler/course-config.ts` |
25
+ | Build-enforced authoring rules | the `N*` / `V*` / `C*` rules marked *Build-enforced* in `docs/authoring/` |
26
+
27
+ ## What each bump means
28
+
29
+ **MAJOR** — an incompatible change to any surface above: removing or renaming an
30
+ export, a component, a prop, or a CLI command; adding a required `CourseConfig`
31
+ field; a breaking change to the manifest shape (which also bumps
32
+ `MANIFEST_SCHEMA_VERSION`); tightening a build-enforced rule so content that
33
+ passed now fails.
34
+
35
+ **MINOR** — a backward-compatible addition: a new export, component, or optional
36
+ `CourseConfig` field; a new opt-in authoring rule; a new manifest field
37
+ (additive — still bump `MANIFEST_SCHEMA_VERSION`, note it additive in
38
+ `CHANGELOG.md`).
39
+
40
+ **PATCH** — a bug fix, a docs change, an internal refactor with no surface
41
+ change, or a new **Tier-3 warning** (warnings never fail a build, so surfacing
42
+ one does not break a consumer).
43
+
44
+ ### Pre-1.0
45
+
46
+ While the version is `0.x`, a **MINOR** bump *may* include a breaking change —
47
+ every one is listed under a `### Breaking` heading in `CHANGELOG.md` for that
48
+ version. A **PATCH** never breaks. Once the API settles this clause is dropped
49
+ and normal semver applies.
50
+
51
+ ## Cutting a release
52
+
53
+ 1. Decide the next version from the rules above and the `## [Unreleased]`
54
+ section of `CHANGELOG.md`.
55
+ 2. `CHANGELOG.md`: rename `## [Unreleased]` to `## [x.y.z] - YYYY-MM-DD`, add a
56
+ fresh empty `## [Unreleased]`, and fix up the compare links at the bottom.
57
+ 3. Bump `package.json` `version` (`npm version x.y.z --no-git-tag-version`).
58
+ 4. `pnpm build && pnpm test`.
59
+ 5. Commit, then `git tag vx.y.z`.
60
+
61
+ ## When you change the engine
62
+
63
+ Every PR that touches the public surface adds a bullet under `## [Unreleased]`
64
+ in `CHANGELOG.md`, in the right group (`Added` / `Changed` / `Fixed` /
65
+ `Removed`, plus `Breaking` when it is). Pick the group by the rules above; the
66
+ version number itself is set only at release time. See [`AGENTS.md`](../AGENTS.md).
@@ -0,0 +1,96 @@
1
+ # Authoring rules
2
+
3
+ Content rules for explico courses — strong recommendations for the people and
4
+ agents who write lessons, notation entries, and interactive figures.
5
+
6
+ ## What these are
7
+
8
+ A course built on explico is authored by many hands — lecturers, TAs, AI
9
+ drafters — over a long time. These files fix the decisions that should not be
10
+ re-litigated per lesson: how a symbol is written, which library draws a graph,
11
+ how a randomised figure picks its axes.
12
+
13
+ Follow a rule unless you have a concrete, stateable reason not to — and then
14
+ leave that reason in the diff or the PR description. "I preferred it" is not a
15
+ reason; "the cited source writes it this way and the passage shows the mapping"
16
+ is.
17
+
18
+ Two kinds of rule live here:
19
+
20
+ - **Build-enforced** — the engine already fails the build when the rule is
21
+ broken. The rule text tells you _how_ to satisfy the gate, not whether to.
22
+ - **Review-only** — nothing checks it automatically; a reviewer does. Breaking
23
+ one is a review comment, not a broken build.
24
+
25
+ Every rule is written to be falsifiable: you can look at a lesson and say
26
+ whether it complies.
27
+
28
+ ## Precedence
29
+
30
+ 1. Build-enforced engine behaviour. If a rule here contradicts what the engine
31
+ actually does, the engine wins and the rule is stale — fix the rule.
32
+ 2. These rules.
33
+ 3. Author discretion, recorded in the diff.
34
+
35
+ A course may add its own rules on top — a house style for prose, a domain glyph
36
+ table. It must not silently relax one of these; loosening a rule is a change to
37
+ this folder, reviewed like any other change.
38
+
39
+ ## How a rule is written
40
+
41
+ Each rule has a stable ID — a letter for its area (`N` notation,
42
+ `V` visualizations, `C` versioning) plus a number — and this shape:
43
+
44
+ > **N1 — One-line imperative.**
45
+ > _Rationale._ Why the rule exists; the failure it prevents.
46
+ > _How to apply._ Concrete steps, with the smallest example that shows it.
47
+ > _Enforcement._ Build-enforced (which gate) or Review-only.
48
+ > _Exceptions._ The stateable reasons that override it, if any.
49
+
50
+ IDs are permanent. A retired rule keeps its number, is struck through in the
51
+ index below, and gains a one-line `Retired <date>: <why>` note — so a `N3` in an
52
+ old commit message never goes ambiguous.
53
+
54
+ ## Amending
55
+
56
+ 1. Open a PR that touches exactly one area file plus the index table below.
57
+ 2. Give a new rule the next unused number in its area. Never renumber.
58
+ 3. Write the four parts above. If the rule could be build-enforced but is not
59
+ yet, say so and link the engine issue.
60
+ 4. One maintainer review. The bar: is it falsifiable, is the rationale real, is
61
+ it not already covered by an existing rule or a schema.
62
+
63
+ A new area — a new letter and file — is the same PR shape plus a row in the
64
+ file table.
65
+
66
+ ## Files
67
+
68
+ | File | Area | Covers |
69
+ | -------------------------------- | ---- | --------------------------------------------------------- |
70
+ | [notation.md](notation.md) | `N` | how math symbols are written and bound |
71
+ | [visualizations.md](visualizations.md) | `V` | diagrams, charts, interactive figures, randomness |
72
+ | [versioning.md](versioning.md) | `C` | the knowledge version and `content/CHANGELOG.md` |
73
+ | [agents.md](agents.md) | — | _guide_: working on a course repo (with an AI agent) |
74
+ | [migrating.md](migrating.md) | — | _guide_: per-version steps when bumping the `explico` dep |
75
+
76
+ ## Rule index
77
+
78
+ | ID | Rule | Enforcement |
79
+ | ------------------------------- | ---------------------------------------------------------------- | ------------------------------------------- |
80
+ | [N1](notation.md#n1) | The normal distribution is `\mathcal{N}` | Build-enforced (completeness gate) + review |
81
+ | [N2](notation.md#n2) | `\mathbb{N,Z,R,C}` are reserved for the number systems | Partial — gate forces every other `\mathbb{}` to resolve |
82
+ | [N3](notation.md#n3) | Multi-letter roman labels use `\mathrm{…}` | Build-enforced (completeness gate) |
83
+ | [N4](notation.md#n4) | One meaning per glyph; define once; `[[key]]` before first math | Build-enforced (completeness gate) |
84
+ | [N5](notation.md#n5) | Referenced display equations carry `\label{eq:key}` | Review-only |
85
+ | [N6](notation.md#n6) | Probability is `\mathbb{P}` (double-struck `P`) | Build-enforced (completeness gate) + review |
86
+ | [N7](notation.md#n7) | The differential `d` is upright `\mathrm{d}` | Build-enforced (completeness gate) |
87
+ | [N8](notation.md#n8) | A named operator is universal (shipped/course list) or explained | Tier-3 warning (`operator-explained`) |
88
+ | [V1](visualizations.md#v1) | Graphs and flowcharts use the `Diagram` component | Review-only |
89
+ | [V2](visualizations.md#v2) | Static charts: SVG → D3 → Observable Plot, chosen per figure | Review-only |
90
+ | [V3](visualizations.md#v3) | Interactive figures: island + `renderLabMath` seam + text alt | Review-only |
91
+ | [V4](visualizations.md#v4) | Randomised figures expose an adjustable, deterministic seed | Review-only |
92
+ | [V5](visualizations.md#v5) | Axis ranges span the first 10 seeds at the current control values | Review-only |
93
+ | [C1](versioning.md#c1) | Every content change lands with a `content/CHANGELOG.md` entry | Review-only |
94
+ | [C2](versioning.md#c2) | The knowledge version is semver over learning-facing content | Review-only |
95
+ | [C3](versioning.md#c3) | `about.version` equals the newest `content/CHANGELOG.md` heading | Build-enforced (Tier-3 `knowledge-version-mismatch`) |
96
+ | [C4](versioning.md#c4) | On release, move `[Unreleased]` and bump `about.version` together | Review-only |
@@ -0,0 +1,63 @@
1
+ # Working on an explico course (with an agent)
2
+
3
+ Guidance for an AI agent — or a person — editing a course repo built on
4
+ `explico`. The engine ships this so a downstream course inherits a baseline; add
5
+ a repo-specific `AGENTS.md` at your course root for anything below that is
6
+ particular to your layout.
7
+
8
+ This is a guide, not a rule area. The enforceable rules are in
9
+ [`notation.md`](notation.md) (`N`), [`visualizations.md`](visualizations.md)
10
+ (`V`), and [`versioning.md`](versioning.md) (`C`). When the engine's behaviour
11
+ and a rule disagree, the engine wins — see [`README.md`](README.md).
12
+
13
+ ## The loop
14
+
15
+ - **Start from the CLI, not a file hunt.** `pnpm --filter <course> content
16
+ context <lesson>` prints a lesson plus exactly its notation, references, and
17
+ assumptions. `pnpm content status` is the draft-debt overview. `pnpm content
18
+ check <lesson> | --changed` is schema + refs + MDX→KaTeX, exits non-zero on an
19
+ error. Read those before opening files.
20
+ - **Validate, twice.** `pnpm --filter <course> validate:content` regenerates
21
+ `build/resolution/*` and then fails on the same run that changed them. Run it
22
+ again; the second run is clean. Commit the regenerated `build/` and
23
+ `resolution/` files.
24
+ - **Before handing work back:** `validate:content` (×2) → `astro check` →
25
+ `astro build`. `astro build` is the only step that runs every gate the CI
26
+ does.
27
+
28
+ ## Reading and running
29
+
30
+ - Grep for a symbol and open a line range; don't read a whole engine module out
31
+ of `node_modules/explico/`. The parts you actually need are
32
+ [`notation.md`](notation.md), this file, and the `content` CLI output.
33
+ - If a page 500s or a change doesn't show, the dev server's content store is
34
+ stale — a **new file in a `content/` collection needs a dev-server restart**,
35
+ not just a save. Restart it; don't diagnose it with `ps` / `curl` scrapes.
36
+ - Ad-hoc Node scripts run from the course package (workspace deps aren't at the
37
+ repo root); Playwright imports as `@playwright/test`.
38
+ - Pipe long command output to `| tail -n 40`. One-off probes and fetched pages
39
+ go to a scratch file you then grep — not into the conversation whole.
40
+
41
+ ## Notation `formula:` fields (the sharp edges)
42
+
43
+ - Every glyph in a `formula:` must resolve: to the entry itself, one of its
44
+ `glosses:`, a card named with `\explain{key}{latex}`, or the base library.
45
+ An unresolved bare letter fails the build with the entry name.
46
+ - The notation reference graph must be **acyclic** — if `a`'s formula names `b`
47
+ and `b`'s names `a`, the build rejects it. Link only *down* a chain
48
+ (acceleration → velocity → position, not back up).
49
+ - Use `\mathbf` for a vector, not `\vec`: `\vec` currently trips the KaTeX
50
+ sanitizer in `render-notation-math`. (Engine bug, not a rule — but `\mathbf`
51
+ is what builds today.)
52
+
53
+ ## Versioning
54
+
55
+ Every content change adds a bullet under `## [Unreleased]` in
56
+ `content/CHANGELOG.md`; a release moves it to a dated `## [x.y.z]` heading and
57
+ sets `about.version` to match, in the same commit. Rules C1–C4,
58
+ [`versioning.md`](versioning.md).
59
+
60
+ ## Upgrading the engine
61
+
62
+ [`migrating.md`](migrating.md) — the per-version steps when you bump the
63
+ `explico` dependency across a **Breaking** entry in `CHANGELOG.md`.
@@ -0,0 +1,107 @@
1
+ # Upgrading the explico engine
2
+
3
+ What a course repo has to change when it bumps its `explico` dependency across a
4
+ version that carries **Breaking** notes. Each section is the concrete work; the
5
+ matching `CHANGELOG.md` entry (shipped in the installed package) is the full
6
+ list of what moved and why.
7
+
8
+ The pattern every time:
9
+
10
+ 1. `pnpm add -D explico@<version>`
11
+ 2. Do the per-version steps below.
12
+ 3. `pnpm validate:content` (run it **twice** — the first run regenerates
13
+ `build/resolution/*` and reports it stale; the second is clean), then
14
+ `pnpm content check --changed`, then `astro check`, then `astro build`.
15
+ Each stage names what it doesn't like; fix and re-run.
16
+
17
+ The engine never fails silently on a migration it can detect: a missing
18
+ required config block throws with the section of this file to read.
19
+
20
+ ---
21
+
22
+ ## 0.2.x → 0.3.0
23
+
24
+ ### 1. Add the required `about` block to `content/course.config.ts`
25
+
26
+ `CourseConfig` now requires an `about` block — the reader-facing identity behind
27
+ the header `[?]` panel, and the course's **knowledge version**. Building without
28
+ it throws `course.config.ts is missing a complete about block …`.
29
+
30
+ ```ts
31
+ export const courseConfig = {
32
+ title: 'My Course',
33
+ description: '…',
34
+ // …existing fields…
35
+ about: {
36
+ // materialName and summary default to title / description
37
+ version: '0.1.0', // the knowledge version — semver
38
+ authors: [{ name: 'A. Lecturer', contact: 'a.lecturer@example.edu' }],
39
+ disclaimer:
40
+ 'Draft study material. No warranty. Parts are AI-assisted and under review.',
41
+ changelogHref: 'https://…/content/CHANGELOG.md', // optional
42
+ },
43
+ } satisfies CourseConfig;
44
+ ```
45
+
46
+ ### 2. Add `content/CHANGELOG.md`
47
+
48
+ The knowledge version (`about.version`) must equal the newest `## [x.y.z]`
49
+ heading in `content/CHANGELOG.md`. A missing file or a mismatch is a Tier-3
50
+ `knowledge-changelog-missing` / `knowledge-version-mismatch` warning (it does
51
+ not fail the build). Governance: [`versioning.md`](versioning.md), rules C1–C4.
52
+
53
+ ```markdown
54
+ # Knowledge changelog — <course>
55
+
56
+ ## [Unreleased]
57
+
58
+ ## [0.1.0] - 2026-01-01
59
+
60
+ - Initial content.
61
+ ```
62
+
63
+ ### 3. Show the About panel
64
+
65
+ The `[?]` panel renders through Starlight's `SocialIcons` slot. Add it to the
66
+ `components` map in `astro.config.mjs` (alongside the `Footer` override you
67
+ already have):
68
+
69
+ ```js
70
+ starlight({
71
+ components: {
72
+ Footer: 'explico/components/starlight/LessonFooter.astro',
73
+ SocialIcons: 'explico/components/starlight/LayoutSocialIcons.astro',
74
+ // …
75
+ },
76
+ })
77
+ ```
78
+
79
+ The build still succeeds without this; the panel just won't appear.
80
+
81
+ ### 4. Only if you imported the equation-reference components directly
82
+
83
+ `EquationEnhancer` and `EquationRefLayer` are gone, replaced by
84
+ `KeyedFloatEnhancer` + `FloatRefLayer` (one hover/pin/jump engine now shared by
85
+ equation, `Figure`, and `Table` references). **If you only wire
86
+ `Footer: 'explico/components/starlight/LessonFooter.astro'`, there is nothing to
87
+ do** — the swap is internal to that component. Only a course that forked
88
+ `LessonFooter` or imported the old components by hand updates its imports:
89
+
90
+ | Removed | Use |
91
+ | --- | --- |
92
+ | `explico/components/starlight/EquationEnhancer.astro` | `explico/components/starlight/KeyedFloatEnhancer.astro` |
93
+ | `explico/components/equation/EquationRefLayer.astro` | `explico/components/reference/FloatRefLayer.astro` |
94
+
95
+ ### 5. Regenerate — the manifest schema bumped
96
+
97
+ `MANIFEST_SCHEMA_VERSION` increased (additive: `manifest.about`,
98
+ `manifest.diagnostics.knowledge`; existing fields unchanged). `pnpm
99
+ validate:content` rewrites `build/manifest.json`; commit the result. An external
100
+ consumer that pins the schema version needs to accept the new number.
101
+
102
+ ### New, no action required
103
+
104
+ `Figure` / `Table` components with numbering and `[[fig-<id>]]` / `[[tab-<id>]]`
105
+ references; `Diagram` gains `direction="LR"` and an optional `description`;
106
+ `\explain{key}{latex}` works inside notation `formula:` fields; heading anchors
107
+ and numbered equations are on by default.
@@ -0,0 +1,296 @@
1
+ # Notation rules
2
+
3
+ `N` rules govern how a mathematical symbol is written in any rendered-math
4
+ context: lesson body, `notation.formula`, `checks.yml`, assessment prompts, and
5
+ the `formula` slots of `Diagram` / lab components. The completeness gate scans
6
+ all of these, so "the build passed" already means most of what follows holds —
7
+ these rules pin the _choices_ the gate leaves open.
8
+
9
+ See [README.md](README.md) for what "strong recommendation", "Build-enforced",
10
+ and "Review-only" mean here, and how to amend a rule.
11
+
12
+ ---
13
+
14
+ ## N1
15
+
16
+ **The normal distribution is `\mathcal{N}`.**
17
+
18
+ Write a Gaussian as `\mathcal{N}(\mu, \sigma^2)` — calligraphic `N`,
19
+ parameterised by **mean and variance**. Name any other named distribution the
20
+ same way: a calligraphic letter through `\mathcal{…}`.
21
+
22
+ Do not use `N(\mu, \sigma^2)`, `\mathrm{N}`, `Norm(...)`, or `\mathbb{N}` — the
23
+ last is the natural numbers ([N2](#n2)) and a build-reserved token.
24
+
25
+ _Rationale._ One corpus-wide spelling makes the symbol searchable, lets it bind
26
+ to a single notation card, and keeps it clear of `N` used as a count and
27
+ `\mathbb{N}` used as a set. Mean/variance is the form the standard-normal CDF
28
+ `\Phi` and PDF `\varphi` compose with cleanly.
29
+
30
+ _How to apply._
31
+
32
+ - Define it once. Add a notation entry — a shared card if the course teaches
33
+ probability, otherwise `notation.local` on the first lesson that needs it:
34
+
35
+ ```yaml
36
+ key: prob.normal
37
+ latex: \mathcal{N}
38
+ label: Normal distribution
39
+ meaning: Gaussian distribution with mean μ and variance σ².
40
+ ```
41
+
42
+ Then introduce it in prose with `[[prob.normal]]` before its first appearance
43
+ in math, exactly like any other shared glyph ([N4](#n4)).
44
+
45
+ - In a one-off context where a full entry is overkill, use the escape hatch:
46
+ `\explain{prob.normal}{\mathcal{N}}(\mu, \sigma^2)`. Prefer the entry.
47
+
48
+ - If a course standardises on standard deviation rather than variance, state
49
+ that once in the domain notes, then write `\mathcal{N}(\mu, \sigma)`
50
+ consistently. Never mix the two forms in one corpus.
51
+
52
+ - Compose consistently: `X \sim \mathcal{N}(\mu, \sigma^2)`,
53
+ `\Phi` for its CDF, `\varphi` for its PDF. Pick the CDF/PDF glyphs once per
54
+ course.
55
+
56
+ _Enforcement._ Build-enforced that the symbol resolves to a key — `\mathcal{N}`
57
+ is a bindable identifier atom to the glyph resolver, so an undefined one fails
58
+ the completeness gate at `file:line:token`. The choice of `\mathcal{N}` over
59
+ `N` / `\mathrm{N}` / `Norm` is convention, checked in review.
60
+
61
+ _Exceptions._ Quoting a source that uses a different glyph **and** showing the
62
+ mapping to `\mathcal{N}` in the same passage.
63
+
64
+ ---
65
+
66
+ ## N2
67
+
68
+ **`\mathbb{N}`, `\mathbb{Z}`, `\mathbb{R}`, `\mathbb{C}` are reserved for the
69
+ number systems.**
70
+
71
+ These four blackboard-bold tokens mean the naturals, integers, reals, and
72
+ complexes, always, and need no notation key. Every _other_ `\mathbb{…}` —
73
+ `\mathbb{E}`, `\mathbb{P}`, `\mathbb{Q}`, `\mathbb{1}` — is an ordinary
74
+ identifier that must resolve to a semantic key.
75
+
76
+ _Rationale._ The universal sets are genuinely universal; forcing a per-page
77
+ definition of `\mathbb{R}` would be noise. Everything else in blackboard bold
78
+ is domain-specific (an expectation, a probability measure, an indicator) and
79
+ earns a card.
80
+
81
+ _How to apply._ Need the rationals as a set on one lesson? That collides with a
82
+ reserved token — reach for `\explain{...}{\mathbb{Q}}` or a differently-styled
83
+ glyph, and note it in the domain vocabulary.
84
+
85
+ _Enforcement._ Partial. The gate forces every non-reserved `\mathbb{…}` to
86
+ resolve; keeping `N/Z/R/C` for the number systems (not, say, `\mathbb{N}` for a
87
+ sample size) is convention.
88
+
89
+ _Exceptions._ None for the four reserved tokens.
90
+
91
+ ---
92
+
93
+ ## N3
94
+
95
+ **Multi-letter roman labels use `\mathrm{…}`.**
96
+
97
+ An upright multi-letter name — `\mathrm{PV}`, `\mathrm{opt}`, `\mathrm{max}`
98
+ where no built-in operator exists — is wrapped in `\mathrm{}`. Never write it as
99
+ a run of bare italic letters (`PV`), which reads as a product `P \cdot V`.
100
+
101
+ _Rationale._ `\mathrm{}` is a typographic label, not a bindable atom — the glyph
102
+ resolver skips its body whole. Bare letter runs are parsed as identifier atoms,
103
+ so `PV` asks the gate to resolve `P` and `V` separately: a build failure at
104
+ best, a wrong binding at worst.
105
+
106
+ _How to apply._ Subscripts too: `v_{\mathrm{max}}`, not `v_{max}`.
107
+
108
+ _Enforcement._ Build-enforced indirectly — bare multi-letter runs hit the
109
+ completeness gate as unresolved identifiers.
110
+
111
+ _Exceptions._ Genuine products, and single-letter subscripts that are
112
+ themselves notation keys.
113
+
114
+ ---
115
+
116
+ ## N4
117
+
118
+ **One meaning per glyph per page; define once; introduce with `[[key]]` before
119
+ the first math use.**
120
+
121
+ A glyph has exactly one meaning on a page. A shared symbol is introduced in
122
+ prose with `[[key]]` before it appears in any equation. `\explain{key}{latex}`
123
+ is the escape hatch for a compound or glyph-colliding symbol only — not the
124
+ default way to bind.
125
+
126
+ _Rationale._ This is the core contract the completeness gate enforces
127
+ corpus-wide; the rule here is just its author-facing statement. A reader hovers
128
+ any symbol and gets exactly one card.
129
+
130
+ _How to apply._ See the engine README's notation section and the `notation`
131
+ collection schema. A letter that exists only so one entry's own `formula` can
132
+ be stated is a **gloss**, not a card — it creates no curriculum edge.
133
+
134
+ _Enforcement._ Build-enforced (completeness gate + one-meaning-per-glyph
135
+ consistency check).
136
+
137
+ _Exceptions._ None. Genuinely different meanings need different glyphs or
138
+ different pages.
139
+
140
+ ---
141
+
142
+ ## N5
143
+
144
+ **A display equation referenced later carries `\label{eq:key}`.**
145
+
146
+ If prose points back at a display equation — this page or another — the equation
147
+ has `\label{eq:key}` and the reference is `[[eq-key]]`. Do not hand-number
148
+ equations in the text.
149
+
150
+ _Rationale._ The engine numbers every display equation `‹chapter›.‹n›` — the
151
+ chapter is the lesson's position in the course, `n` counts the display
152
+ equations in the lesson in appearance order (`##` sub-headings do not affect
153
+ it) — and derives the anchor and any cross-lesson link from the label. A
154
+ hand-typed "(3)" goes stale the moment an equation is inserted above it, or the
155
+ lesson moves in the course order. `<Table>` and `<Figure>` are numbered the
156
+ same way, each with its own counter.
157
+
158
+ _How to apply._ Only label equations you actually reference; an unlabelled
159
+ display equation is still fine.
160
+
161
+ _Enforcement._ Review-only — an unlabelled-but-referenced equation builds; a
162
+ reviewer catches the hand-numbering.
163
+
164
+ _Exceptions._ None worth stating.
165
+
166
+ ---
167
+
168
+ ## N6
169
+
170
+ **Probability is `\mathbb{P}` — double-struck `P`.**
171
+
172
+ Write a probability as `\mathbb{P}(A)` — blackboard-bold `P`. Not `P(A)` (a bare
173
+ `P` is a variable — a price, a power, a point), not `\Pr`, not `\text{Prob}`.
174
+ Expectation is the same family: `\mathbb{E}[X]`.
175
+
176
+ _Rationale._ `\mathbb{P}` is unmistakably the probability functional, never a
177
+ quantity. It reads next to `\mathbb{E}` and `\mathbb{Q}` as one notation family
178
+ and never collides with a `P` the lesson uses for something physical.
179
+
180
+ _How to apply._
181
+
182
+ - Define it once, like [N1](#n1):
183
+
184
+ ```yaml
185
+ key: prob.p
186
+ latex: \mathbb{P}
187
+ label: Probability
188
+ meaning: Probability of an event.
189
+ ```
190
+
191
+ Introduce it in prose with `[[prob.p]]` before its first appearance in math.
192
+
193
+ - Conditional probability is `\mathbb{P}(A \mid B)` — `\mid`, not a bare `|`.
194
+
195
+ - A course doing measure theory may define `\mathbb{P}` as the real-world
196
+ measure and `\mathbb{Q}` as a risk-neutral one, consistent with the reserved
197
+ reading of `\mathbb{Q}` ([N2](#n2)). State that split once in the domain
198
+ notes.
199
+
200
+ _Enforcement._ Build-enforced that the symbol resolves to a key — `\mathbb{P}`
201
+ is a non-reserved `\mathbb{…}` and the completeness gate demands a binding
202
+ ([N2](#n2)). The choice of `\mathbb{P}` over `P` / `\Pr` is convention.
203
+
204
+ _Exceptions._ Quoting a source that writes `\Pr` or `P` **and** showing the
205
+ mapping to `\mathbb{P}` in the same passage.
206
+
207
+ ---
208
+
209
+ ## N7
210
+
211
+ **The differential `d` is upright `\mathrm{d}`.**
212
+
213
+ The `d` of an integrand and the `d` of a derivative are the same operator and
214
+ get the same upright form — distinct from an italic `d`, which is always a
215
+ variable (a distance, a day count, a dimension):
216
+
217
+ - `\int_a^b f(x)\,\mathrm{d}x` — thin space `\,`, then `\mathrm{d}x`.
218
+ - `\dfrac{\mathrm{d}f}{\mathrm{d}x}`, `\dfrac{\mathrm{d}^2 f}{\mathrm{d}x^2}`.
219
+ - `\mathrm{d}s`, `\mathrm{d}A`, `\mathrm{d}V`, `\mathrm{d}t` — same rule for
220
+ every differential.
221
+
222
+ A partial derivative uses `\partial` bare — `\dfrac{\partial f}{\partial x}` —
223
+ which is already distinct; do not wrap it in anything.
224
+
225
+ _Rationale._ This is the ISO 80000-2 convention. Upright `\mathrm{d}` cannot be
226
+ read as a factor `d \cdot x`, and it stays clear of a notation key `d` on a
227
+ lesson that happens to have one.
228
+
229
+ _How to apply._ `\mathrm{d}` is a typographic operator, not a bindable atom —
230
+ the glyph resolver skips its body like any `\mathrm{…}` label ([N3](#n3)), so it
231
+ needs no notation key. Only the variable of integration (`x`, `t`) still has to
232
+ resolve.
233
+
234
+ _Enforcement._ Build-enforced indirectly — a bare `\int f(x) dx` puts an
235
+ unresolved identifier `d` in front of the completeness gate ([N3](#n3)).
236
+
237
+ _Exceptions._ A verbatim quotation of a source in a blockquote.
238
+
239
+ ---
240
+
241
+ ## N8
242
+
243
+ **A named operator is universal, or it is explained.**
244
+
245
+ Every named operator a lesson uses — `\exp`, `\int`, `\sin`, … as built-ins,
246
+ `\operatorname{Var}`, `\operatorname{Cov}`, `\operatorname{E}` as declared ones
247
+ — is either on the shipped operator baseline (`src/reference/base-operators.mjs`
248
+ plus this course's `operators.allow` in `course.config.ts`) or it carries a
249
+ notation entry the reader can hover.
250
+
251
+ _Rationale._ The completeness gate has no opinion about operators: KaTeX parses
252
+ `\exp` as an operator node and `\operatorname{Var}` as an `operatorname` node,
253
+ and the glyph resolver skips both whole ([N3](#n3)). So `\operatorname{Var}`
254
+ renders fine while the reader is never told what "Var" means. `\exp` and `\int`
255
+ need no such help — everyone knows them — but a domain operator does, and the
256
+ line between the two is a course decision, made once, not per lesson.
257
+
258
+ _How to apply._
259
+
260
+ - Universal operators — trig, `\exp` / `\ln` / `\log`, `\lim`, `\max` / `\min`
261
+ / `\sup` / `\inf`, `\sum` / `\prod` / `\int` — just work. The full baseline is
262
+ the greppable set in `base-operators.mjs`.
263
+
264
+ - A domain operator gets a notation entry, exactly like a glyph ([N4](#n4)) — a
265
+ shared card if the course teaches it broadly, otherwise `notation.local`:
266
+
267
+ ```yaml
268
+ key: stats.variance
269
+ latex: \operatorname{Var}
270
+ label: Variance
271
+ meaning: Expected squared deviation of a random variable from its mean.
272
+ ```
273
+
274
+ Then introduce it in prose with `[[stats.variance]]` before its first
275
+ appearance in math, and write it `\operatorname{Var}(X)` — never `Var(X)`
276
+ (bare letters, [N3](#n3)) and never `\mathrm{Var}` (a label, not an operator).
277
+
278
+ - A course whose domain treats an operator as common — `\operatorname{Var}`,
279
+ `\operatorname{Cov}`, `\operatorname{Corr}`, `\operatorname{sgn}` in a
280
+ statistics course — lists it once in `course.config.ts` instead:
281
+
282
+ ```ts
283
+ operators: { allow: ['Var', 'Cov', 'Corr', 'sgn'] }
284
+ ```
285
+
286
+ Names are the `\operatorname{…}` body, or the command without its backslash
287
+ for a built-in. `operators.deny` forces a baseline operator to be explained
288
+ anyway.
289
+
290
+ _Enforcement._ Tier-3 warning (`operator-explained`), surfaced by
291
+ `pnpm validate:content`, recorded in the manifest, and silenced only by a
292
+ reviewed line in `lint-ignore.yml` (`operator-explained:Var — <reason>`). Like
293
+ `formula` before it (D15), the tier can be promoted once a corpus is clean.
294
+
295
+ _Exceptions._ Quoting a source that uses an operator glyph **and** showing what
296
+ it means in the same passage.