explico 0.4.0 → 0.5.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 +273 -1
- package/README.md +103 -8
- package/dist/compiler/astro.d.ts +9 -1
- package/dist/compiler/astro.js +182 -3
- package/dist/compiler/cli.d.ts +13 -0
- package/dist/compiler/cli.js +28 -4
- package/dist/compiler/collections.d.ts +3 -2
- package/dist/compiler/collections.js +28 -1
- package/dist/compiler/course-config.d.ts +58 -1
- package/dist/compiler/course-config.js +28 -1
- package/dist/compiler/exports/standalone-html.d.ts +34 -0
- package/dist/compiler/exports/standalone-html.js +803 -0
- package/dist/compiler/manifest.d.ts +90 -6
- package/dist/compiler/manifest.js +150 -17
- package/dist/compiler/resolution-report.d.ts +4 -0
- package/dist/reference/base-abbreviations.d.mts +30 -0
- package/dist/reference/base-abbreviations.mjs +60 -0
- package/dist/reference/base-operators.d.mts +15 -0
- package/dist/reference/base-operators.mjs +40 -0
- package/dist/reference/base-roman-labels.d.mts +35 -0
- package/dist/reference/base-roman-labels.mjs +43 -0
- package/dist/reference/consistency.d.ts +8 -2
- package/dist/reference/consistency.js +50 -3
- package/dist/reference/diagrams-validate.d.ts +21 -0
- package/dist/reference/diagrams-validate.js +13 -0
- package/dist/reference/equations.d.mts +10 -15
- package/dist/reference/equations.mjs +67 -14
- package/dist/reference/gate-abbreviations.d.ts +17 -0
- package/dist/reference/gate-abbreviations.js +64 -0
- package/dist/reference/gate-math.d.ts +28 -5
- package/dist/reference/gate-math.js +5 -2
- package/dist/reference/gate-prose-notation.d.ts +60 -0
- package/dist/reference/gate-prose-notation.js +125 -0
- package/dist/reference/keyed-float-validate.d.ts +5 -5
- package/dist/reference/keyed-jsx-floats.d.mts +7 -12
- package/dist/reference/keyed-jsx-floats.mjs +50 -25
- package/dist/reference/knowledge-checks-validate.d.ts +35 -0
- package/dist/reference/knowledge-checks-validate.js +78 -0
- package/dist/reference/knowledge-checks.d.mts +25 -0
- package/dist/reference/knowledge-checks.mjs +45 -0
- package/dist/reference/lab-math-scope.d.ts +9 -1
- package/dist/reference/lab-math-scope.js +32 -4
- package/dist/reference/math-glyphs.d.mts +30 -0
- package/dist/reference/math-glyphs.mjs +367 -0
- package/dist/reference/mdx-attributes.d.mts +13 -0
- package/dist/reference/mdx-attributes.mjs +38 -0
- package/dist/reference/mixed-content.d.ts +17 -0
- package/dist/reference/mixed-content.js +29 -0
- package/dist/reference/page-notation.d.ts +52 -0
- package/dist/reference/page-notation.js +171 -0
- package/dist/reference/raw-floats-validate.d.ts +18 -0
- package/dist/reference/raw-floats-validate.js +53 -0
- package/dist/reference/raw-floats.d.mts +18 -0
- package/dist/reference/raw-floats.mjs +129 -0
- package/dist/reference/rehype-heading-anchors.d.mts +1 -1
- package/dist/reference/rehype-heading-anchors.mjs +107 -1
- package/dist/reference/rehype-table-header-labels.d.mts +26 -0
- package/dist/reference/rehype-table-header-labels.mjs +152 -0
- package/dist/reference/remark-abbreviations.d.mts +7 -0
- package/dist/reference/remark-abbreviations.mjs +174 -0
- package/dist/reference/remark-memo.d.mts +16 -0
- package/dist/reference/remark-memo.mjs +339 -0
- package/dist/reference/remark-notation.d.mts +57 -9
- package/dist/reference/remark-notation.mjs +354 -42
- package/dist/reference/remark-short-note.d.mts +4 -0
- package/dist/reference/remark-short-note.mjs +237 -0
- package/dist/reference/types.d.ts +16 -1
- package/dist/reference/units-vocab.js +4 -0
- package/docs/VERSIONING.md +33 -4
- package/docs/authoring/README.md +8 -0
- package/docs/authoring/agents.md +17 -2
- package/docs/authoring/components.md +135 -0
- package/docs/authoring/migrating.md +92 -7
- package/docs/authoring/notation.md +560 -1
- package/docs/authoring/structure.md +120 -8
- package/docs/authoring/visualizations.md +36 -3
- package/package.json +5 -2
- package/src/compiler/astro.ts +208 -3
- package/src/compiler/cli.ts +29 -2
- package/src/compiler/collections.ts +45 -4
- package/src/compiler/course-config.ts +92 -0
- package/src/compiler/exports/standalone-html.ts +963 -0
- package/src/compiler/manifest.ts +268 -22
- package/src/components/HoverPanel.astro +45 -4
- package/src/components/abbreviation/AbbreviationGlossary.astro +70 -0
- package/src/components/abbreviation/AbbreviationLayer.astro +127 -0
- package/src/components/assessments/AssessmentRunner.tsx +52 -17
- package/src/components/assessments/AssessmentSet.astro +98 -14
- package/src/components/assessments/RenderedContent.tsx +55 -0
- package/src/components/assessments/assessment-randomization.ts +16 -0
- package/src/components/diagram/Diagram.astro +444 -83
- package/src/components/disclosure/hover-preview.ts +202 -0
- package/src/components/disclosure/persisted-disclosure.ts +66 -0
- package/src/components/examples/CompactExamples.astro +2 -0
- package/src/components/figure/Figure.astro +279 -10
- package/src/components/memo/Memo.astro +453 -0
- package/src/components/memo/ShortNote.astro +223 -0
- package/src/components/notation/NotationFormula.astro +18 -1
- package/src/components/notation/NotationLayer.astro +105 -8
- package/src/components/notation/render-notation-math.ts +39 -2
- package/src/components/notation/safe-math-to-react.ts +50 -0
- package/src/components/reference/FloatRefLayer.astro +233 -15
- package/src/components/starlight/CollapsibleSections.astro +461 -0
- package/src/components/starlight/DiagramEnhancer.astro +385 -0
- package/src/components/starlight/HeadingAnchorEnhancer.astro +5 -17
- package/src/components/starlight/KeyedFloatEnhancer.astro +81 -3
- package/src/components/starlight/LayoutHeader.astro +50 -0
- package/src/components/starlight/LayoutSocialIcons.astro +41 -2
- package/src/components/starlight/LessonExportActions.astro +436 -0
- package/src/components/starlight/LessonFooter.astro +27 -2
- package/src/components/starlight/section-parts.ts +31 -0
- package/src/components/table/Table.astro +293 -7
- package/src/components/table/TableOverflowExpander.astro +489 -0
- package/src/content-config.ts +20 -0
- package/src/reference/base-abbreviations.mjs +60 -0
- package/src/reference/base-operators.mjs +40 -0
- package/src/reference/base-roman-labels.mjs +43 -0
- package/src/reference/consistency.ts +73 -2
- package/src/reference/diagrams-validate.ts +40 -0
- package/src/reference/equations.mjs +67 -14
- package/src/reference/gate-abbreviations.ts +128 -0
- package/src/reference/gate-math.ts +27 -7
- package/src/reference/gate-prose-notation.ts +224 -0
- package/src/reference/keyed-float-validate.ts +5 -5
- package/src/reference/keyed-jsx-floats.mjs +50 -25
- package/src/reference/knowledge-checks-validate.ts +130 -0
- package/src/reference/knowledge-checks.mjs +45 -0
- package/src/reference/lab-math-scope.ts +45 -3
- package/src/reference/math-glyphs.mjs +367 -0
- package/src/reference/mdx-attributes.mjs +38 -0
- package/src/reference/mixed-content.ts +69 -0
- package/src/reference/page-notation.ts +252 -0
- package/src/reference/raw-floats-validate.ts +103 -0
- package/src/reference/raw-floats.mjs +129 -0
- package/src/reference/rehype-heading-anchors.mjs +107 -1
- package/src/reference/rehype-table-header-labels.mjs +152 -0
- package/src/reference/remark-abbreviations.mjs +174 -0
- package/src/reference/remark-memo.mjs +339 -0
- package/src/reference/remark-notation.mjs +354 -42
- package/src/reference/remark-short-note.mjs +237 -0
- package/src/reference/types.ts +16 -0
- package/src/reference/units-vocab.ts +4 -0
- package/src/styles/global.css +996 -50
|
@@ -131,8 +131,32 @@ _How to apply._ See the engine README's notation section and the `notation`
|
|
|
131
131
|
collection schema. A letter that exists only so one entry's own `formula` can
|
|
132
132
|
be stated is a **gloss**, not a card — it creates no curriculum edge.
|
|
133
133
|
|
|
134
|
+
Math authored inside a nested Astro component is outside the lesson MDX scan,
|
|
135
|
+
so the component must declare the formula text at render time. `Diagram` and
|
|
136
|
+
`NotationFormula` do this automatically from the notation keys they actually
|
|
137
|
+
emit. A lab wrapper must pass its formula/template source and request context to
|
|
138
|
+
`labMathScope`:
|
|
139
|
+
|
|
140
|
+
```astro
|
|
141
|
+
---
|
|
142
|
+
const formula = String.raw`\explain{motion.launch-speed}{v_0}`;
|
|
143
|
+
const scope = await labMathScope(undefined, formula, {
|
|
144
|
+
locals: Astro.locals,
|
|
145
|
+
context: 'LaunchSummary fixed-speed formula',
|
|
146
|
+
});
|
|
147
|
+
const math = renderLabMath(formula, scope);
|
|
148
|
+
---
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The registration is request-local: it adds only keys used by that component
|
|
152
|
+
(and their normal definition dependencies) to that lesson's popup payload. It
|
|
153
|
+
does not make the global registry page-wide. An unknown key fails with the
|
|
154
|
+
lesson and supplied component/formula context; a final Astro build check also
|
|
155
|
+
rejects any `data-notation-key` in generated HTML that has no matching
|
|
156
|
+
`data-notation-entry` source.
|
|
157
|
+
|
|
134
158
|
_Enforcement._ Build-enforced (completeness gate + one-meaning-per-glyph
|
|
135
|
-
consistency check).
|
|
159
|
+
consistency check + final rendered-trigger/payload parity check).
|
|
136
160
|
|
|
137
161
|
_Exceptions._ None. Genuinely different meanings need different glyphs or
|
|
138
162
|
different pages.
|
|
@@ -295,3 +319,538 @@ reviewed line in `lint-ignore.yml` (`operator-explained:Var — <reason>`). Like
|
|
|
295
319
|
|
|
296
320
|
_Exceptions._ Quoting a source that uses an operator glyph **and** showing what
|
|
297
321
|
it means in the same passage.
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## N9
|
|
326
|
+
|
|
327
|
+
**A physical quantity, unit, or math operator is typeset in `$…$`, never as
|
|
328
|
+
plain text — in prose, a table cell, an assessment `prompt` / `explanation`,
|
|
329
|
+
or a caption that itself lives outside math.**
|
|
330
|
+
|
|
331
|
+
If it would be written in italics or a special symbol in a printed textbook —
|
|
332
|
+
a variable, a subscript or superscript, a unit abbreviation, an absolute-value
|
|
333
|
+
bar, a division of two symbols — it goes in `$…$` (or `$$…$$`), full stop, no
|
|
334
|
+
matter where on the page it sits. `45 m/s`, `t (s)`, `vx`, `\|v\|`, `KE/m` are
|
|
335
|
+
all the same mistake: notation typed as if the reader's browser were a plain
|
|
336
|
+
text file instead of the KaTeX pipeline this engine already ships.
|
|
337
|
+
|
|
338
|
+
A unit inside math is still upright roman, never italic — SI symbols are
|
|
339
|
+
upright in every printed textbook too (`\text{m/s}`, not a bare italic `m/s`,
|
|
340
|
+
which KaTeX would render as the product of two variables `m` and `s`). Wrap
|
|
341
|
+
the unit portion in `\text{}` and fold it into the _same_ math span as the
|
|
342
|
+
symbol it modifies (`$t\ \text{(s)}$`, not two adjacent spans `$t$
|
|
343
|
+
$\text{(s)}$`) — and prefer the short symbol over the spelled-out word even
|
|
344
|
+
inside `\text{}` (`\text{(s)}`, not `\text{(seconds)}`), the same way a
|
|
345
|
+
variable is `v_0` and not "initial velocity" once it's introduced. This does
|
|
346
|
+
**not** cover a fully spelled-out unit used as an ordinary English noun in a
|
|
347
|
+
sentence — "the ball leaves at 10 m/s, 30 **degrees** above the horizontal"
|
|
348
|
+
keeps "degrees" as a word; that carve-out is about flowing prose, not a
|
|
349
|
+
compact data label like a table header or a computed answer.
|
|
350
|
+
|
|
351
|
+
This also does not apply inside a JSX component prop (`<Figure caption="…">`,
|
|
352
|
+
`<Table caption="…">`, `title="…"`) — a prop string is handed to the DOM as
|
|
353
|
+
plain text with no math rendering at all, so `$…$` there would show up as
|
|
354
|
+
broken literal dollar signs instead of rendering; write the unit as a word or
|
|
355
|
+
the symbol as plain Unicode there instead ("peaks at 45°" in a caption is
|
|
356
|
+
fine).
|
|
357
|
+
|
|
358
|
+
_Rationale._ A reader who can hover `$v_0$` for its notation card gets nothing
|
|
359
|
+
from the same quantity spelled `v0` in a table header — the two look
|
|
360
|
+
interchangeable to a drafter but only one is inside the notation system this
|
|
361
|
+
course is built on — hover cards, one meaning per glyph, the completeness
|
|
362
|
+
gate. A table of sampled values is exactly where this slips in, because the
|
|
363
|
+
completeness gate ([N4](#n4)) only ever looks _inside_ `$…$` — text sitting
|
|
364
|
+
next to it was invisible to every check until this rule.
|
|
365
|
+
|
|
366
|
+
_How to apply._
|
|
367
|
+
|
|
368
|
+
- A table header cell that names a quantity uses the same bound symbol the
|
|
369
|
+
lesson already introduced, `\explain{key}{…}`-wrapped if the column needs a
|
|
370
|
+
component or a different style than the card's own notation (`v_x` for
|
|
371
|
+
`[[motion.velocity]]`'s `v(t)`, exactly as `motion.acceleration`'s own
|
|
372
|
+
`formula:` already does for `\explain{motion.velocity}{\mathbf{v}}`), with
|
|
373
|
+
its unit folded into the same span as a `\text{}` suffix:
|
|
374
|
+
|
|
375
|
+
```text
|
|
376
|
+
| $t\ \text{(s)}$ | $\explain{motion.velocity}{v_x}\ \text{(m/s)}$ |
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
- An absolute value or norm is `\lvert…\rvert`, never a literal `|` — a raw
|
|
380
|
+
`|` inside a Markdown table cell also collides with the table's own column
|
|
381
|
+
delimiter, which is why it shows up escaped (`\|v\|`) instead of rendering
|
|
382
|
+
as anything.
|
|
383
|
+
|
|
384
|
+
- A specific (per-unit) quantity like kinetic energy per unit mass is
|
|
385
|
+
`\mathrm{KE}/m`, not `KE/m` — multi-letter roman labels still take
|
|
386
|
+
`\mathrm{}` ([N3](#n3)) inside the math span.
|
|
387
|
+
|
|
388
|
+
- A unit exponent is a real LaTeX superscript (`\text{m/s}^2`), never the
|
|
389
|
+
Unicode "²"/"³" character — KaTeX's strict mode rejects those as unknown
|
|
390
|
+
symbols (`src/reference/math-glyphs.mjs`'s `parseMath` calls
|
|
391
|
+
`katex.__parse(latex, { strict: 'error' })` on every math span the
|
|
392
|
+
completeness gate collects, table headers included), so this is already
|
|
393
|
+
build-enforced via [N4](#n4)'s existing gate, not a separate check N9 needs
|
|
394
|
+
of its own. This is exactly why a unit has to be _inside_ `$…$` in the
|
|
395
|
+
first place: outside math, nothing parses the LaTeX at all, so a stray "²"
|
|
396
|
+
typed as plain Unicode text would sail through unchecked.
|
|
397
|
+
|
|
398
|
+
- A header cell with **no plain-language word next to its symbol** — a bare
|
|
399
|
+
`$t$`, `$x$`, `$v_x$` column, not `Launch speed $\text{(m/s)}$` — ends up
|
|
400
|
+
100% KaTeX output, which axe-core's `empty-table-header` check flags as
|
|
401
|
+
having no text "visible to screen readers" (it does not read into `<math>`
|
|
402
|
+
for the accessible-name computation, so a header that is entirely `$…$`
|
|
403
|
+
reads as empty to it even though a sighted or MathML-supporting reader sees
|
|
404
|
+
the symbol fine). This is handled automatically, not by writing anything
|
|
405
|
+
differently: `rehype-table-header-labels.mjs` (wired into
|
|
406
|
+
`playground/astro.config.mjs`'s `rehypePlugins`, right after
|
|
407
|
+
`rehypeKatex`) finds any `<th>` with no visible text, reads the
|
|
408
|
+
`\explain{key}{…}` key(s) its LaTeX names, and inserts a visually-hidden
|
|
409
|
+
(`sr-only`) text node from that key's own `label` / `units` notation
|
|
410
|
+
metadata — a fuller, more descriptive accessible name than the visible
|
|
411
|
+
abbreviation ever showed, generated for free from data already authored.
|
|
412
|
+
It has to be real (`sr-only`-clipped, not `display:none`) content, not
|
|
413
|
+
just an `aria-label` attribute: axe-core's `empty-table-header` check
|
|
414
|
+
specifically runs `has-visible-text` (real text content), with no
|
|
415
|
+
`aria-label`/`aria-labelledby` alternative the way most of its other
|
|
416
|
+
name-checking rules allow — confirmed by reading the rule itself in
|
|
417
|
+
`node_modules/axe-core/axe.js`. The one thing this means for an author: a
|
|
418
|
+
header's symbol has to actually resolve to a key for this to have anything
|
|
419
|
+
to work with. A bare `$t$`/`$x$` already does, via the ordinary `[[key]]`
|
|
420
|
+
intro + auto-resolution every other bound glyph on the page uses; the one
|
|
421
|
+
case that doesn't is a `\mathrm{…}` compound like `\mathrm{KE}/m`, because
|
|
422
|
+
the glyph resolver never looks inside a bare `\mathrm{}` body ([N3](#n3),
|
|
423
|
+
[N10](#n10)) — explicitly `\explain{}`-wrap each side there
|
|
424
|
+
(`\explain{kinetic-energy}{\mathrm{KE}}/\explain{mass}{m}`) so the plugin
|
|
425
|
+
has something to read; a header with more than one bound key composes them
|
|
426
|
+
joined by `" / "`.
|
|
427
|
+
|
|
428
|
+
_Enforcement._ Build-enforced (`gate-prose-notation.ts`, wired into the same
|
|
429
|
+
`pnpm validate:content` / `astro build` completeness gate as [N4](#n4), context
|
|
430
|
+
`prose`). `gateProseNotation` scans lesson prose and table cells — with code
|
|
431
|
+
spans, `$…$` math, `[[key]]` references, `import` lines, and JSX tags already
|
|
432
|
+
blanked out; `gateProseNotationInAssessments` runs the same checks over every
|
|
433
|
+
assessment item's `prompt` / `explanation` string, since `AssessmentRunner`
|
|
434
|
+
renders both through the same KaTeX pipeline as lesson body math
|
|
435
|
+
(`manifest.assessmentContent`, schema v8) — a plain-text quantity there is
|
|
436
|
+
just as invisible to a hover card as one in a lesson. Both look for: an
|
|
437
|
+
escaped `\|`; a numeral glued to an abbreviated unit (`45 m/s`); a bare unit
|
|
438
|
+
in parens (`(s)`, `(m/s²)`); a hand-typed `_`/`^` between alphanumerics; and a
|
|
439
|
+
`/` between two short alphabetic tokens that isn't on a small reviewed idiom
|
|
440
|
+
allowlist (`and/or`, `his/her`, …) in the check's own source. Unlike every
|
|
441
|
+
other build-enforced rule here, N9 ships as a hard error
|
|
442
|
+
with no Tier-3 warming-up period and no `lint-ignore.yml` entry, because it
|
|
443
|
+
landed in the same change that brought the whole corpus to zero — see the
|
|
444
|
+
check's file for the exact patterns and the allowlist.
|
|
445
|
+
|
|
446
|
+
_Exceptions._ None acknowledged through `lint-ignore.yml` — that registry is
|
|
447
|
+
for corpus-level warnings a reviewer accepts, and N9 has no warning tier to
|
|
448
|
+
accept into. A genuinely non-mathematical slash idiom (`input/output`, …) is
|
|
449
|
+
added to the allowlist in `gate-prose-notation.ts` itself, reviewed like any
|
|
450
|
+
other change to this file.
|
|
451
|
+
|
|
452
|
+
---
|
|
453
|
+
|
|
454
|
+
## N10
|
|
455
|
+
|
|
456
|
+
**A `\mathrm{…}` multi-letter label is universal, or it is explained.**
|
|
457
|
+
|
|
458
|
+
Every multi-letter roman label a lesson uses in math — `\mathrm{KE}`,
|
|
459
|
+
`\mathrm{PV}`, `\mathrm{opt}` ([N3](#n3)) — is either on the shipped
|
|
460
|
+
roman-label baseline (`src/reference/base-roman-labels.mjs` plus this
|
|
461
|
+
course's `romanLabels.allow` in `course.config.ts`) or it carries a notation
|
|
462
|
+
entry the reader can hover.
|
|
463
|
+
|
|
464
|
+
_Rationale._ N3 fixes the _format_ (`\mathrm{}`, never bare letters) but is
|
|
465
|
+
silent on whether the label is ever explained, because — same as an
|
|
466
|
+
`\operatorname{}` body ([N8](#n8)) — the glyph resolver skips a `\mathrm{}`
|
|
467
|
+
body whole; nothing about satisfying N3 requires a definition to exist. N9's
|
|
468
|
+
enforcement fixed one direction of this exact page (`KE`/`PE` typed as plain
|
|
469
|
+
text); this rule closes the other: `KE`/`PE` typed correctly as `\mathrm{}`
|
|
470
|
+
but with no card at all, which N9 cannot see because the notation is already
|
|
471
|
+
inside `$…$`.
|
|
472
|
+
|
|
473
|
+
_How to apply._
|
|
474
|
+
|
|
475
|
+
- A generic qualifier that functions like an operator's extremum subscript —
|
|
476
|
+
`x_{\mathrm{opt}}`, an "optimal" reading with no quantity of its own — just
|
|
477
|
+
works. The full baseline is the greppable set in `base-roman-labels.mjs`,
|
|
478
|
+
kept deliberately small: unlike a trig function, an upright label is almost
|
|
479
|
+
always a named quantity specific to one course, so it defaults to needing a
|
|
480
|
+
card rather than defaulting to universal.
|
|
481
|
+
|
|
482
|
+
- A named quantity gets a notation entry, exactly like a glyph ([N4](#n4)) —
|
|
483
|
+
a shared card if the course teaches it broadly, otherwise `notation.local`,
|
|
484
|
+
same shape as [N9](#n9)'s `mass` example:
|
|
485
|
+
|
|
486
|
+
```yaml
|
|
487
|
+
key: kinetic-energy
|
|
488
|
+
latex: \mathrm{KE}
|
|
489
|
+
label: Kinetic energy
|
|
490
|
+
meaning: The energy an object has from its motion.
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
Then introduce it in prose with `[[kinetic-energy]]` before its first
|
|
494
|
+
appearance in math.
|
|
495
|
+
|
|
496
|
+
- A course whose domain treats a label as common lists it once in
|
|
497
|
+
`course.config.ts` instead:
|
|
498
|
+
|
|
499
|
+
```ts
|
|
500
|
+
romanLabels: { allow: ['PV', 'NPV'] }
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
Names are the `\mathrm{…}` body. `romanLabels.deny` forces a baseline label
|
|
504
|
+
to be explained anyway.
|
|
505
|
+
|
|
506
|
+
_Enforcement._ Tier-3 warning (`roman-label-explained`), surfaced by
|
|
507
|
+
`pnpm validate:content`, recorded in the manifest, and silenced only by a
|
|
508
|
+
reviewed line in `lint-ignore.yml` (`roman-label-explained:KE — <reason>`).
|
|
509
|
+
Same rollout shape as [N8](#n8): the tier can be promoted once a corpus is
|
|
510
|
+
clean.
|
|
511
|
+
|
|
512
|
+
_Exceptions._ Quoting a source that uses a roman-label glyph **and** showing
|
|
513
|
+
what it means in the same passage. A single-letter `\mathrm{…}` body (`d`,
|
|
514
|
+
[N7](#n7)) is out of scope entirely — that is a fixed typographic operator,
|
|
515
|
+
not a named quantity.
|
|
516
|
+
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
## N11
|
|
520
|
+
|
|
521
|
+
**A worked example never drops a unit from a substituted value, and its
|
|
522
|
+
result is dimensionally consistent.**
|
|
523
|
+
|
|
524
|
+
The moment a symbol in an equation is replaced by a number — in a
|
|
525
|
+
`CompactExample`, an assessment `explanation`, a derivation step, anywhere —
|
|
526
|
+
that number keeps the unit its symbol carried. Never substitute bare numbers
|
|
527
|
+
and attach a unit only to the final answer:
|
|
528
|
+
|
|
529
|
+
```text
|
|
530
|
+
v(4) = 5 + 2 \times 4 = 13\ \text{m/s} ✗ 5 and 2 have silently lost their units
|
|
531
|
+
v(4) = 5\ \text{m/s} + 2\ \text{m/s}^2 \times 4\ \text{s} = 13\ \text{m/s} ✓
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
The mirror failure is a result whose unit does not actually follow from the
|
|
535
|
+
arithmetic — copy-pasting the previous line's unit onto a different quantity.
|
|
536
|
+
`v = s/t = 10/5 = 2\ \text{s}` is wrong twice over: the substitution dropped
|
|
537
|
+
$s$'s and $t$'s units, which is what let `2` pick up `\text{s}` (a time unit)
|
|
538
|
+
for what is actually a velocity — the arithmetic $10/5$ alone is just the
|
|
539
|
+
number 2, and nothing about it says "seconds". Carried through, the units
|
|
540
|
+
themselves catch the mistake: $10\ \text{m} / 5\ \text{s} = 2\ \text{m/s}$
|
|
541
|
+
leaves no room to mislabel the answer, which is the entire point of the rule.
|
|
542
|
+
|
|
543
|
+
_Rationale._ A worked example is the one place a reader checks their own
|
|
544
|
+
understanding by redoing the arithmetic. If the displayed steps drop units,
|
|
545
|
+
the example teaches "plug in the numbers" instead of "the units are part of
|
|
546
|
+
the equation" — and it stops being able to catch the author's own dimensional
|
|
547
|
+
mistakes, since a number has no unit to contradict. N9 already forces every
|
|
548
|
+
quantity into `$…$` with its unit properly typeset; this rule is the
|
|
549
|
+
complementary content requirement that the _substitution steps themselves_
|
|
550
|
+
keep carrying those units all the way to the answer, not just the input
|
|
551
|
+
values and the final line.
|
|
552
|
+
|
|
553
|
+
_How to apply._
|
|
554
|
+
|
|
555
|
+
- Attach each value's unit with `\text{}` at the moment it replaces its
|
|
556
|
+
symbol, exactly as [N9](#n9) already typesets a bound quantity — folded
|
|
557
|
+
into the same math term as its number, not a separate span
|
|
558
|
+
(`2\ \text{m/s}^2`, not `2` and `\text{m/s}^2` as two adjacent `$…$`).
|
|
559
|
+
- Carry the units through every intermediate line a reader can see, not just
|
|
560
|
+
the first substitution and the last answer. If the full chain would be
|
|
561
|
+
unreasonably long, it is fine to skip straight from substitution to answer
|
|
562
|
+
for a single-step calculation — the rule is about not _losing_ units
|
|
563
|
+
mid-derivation, not about spelling out unit algebra for its own sake.
|
|
564
|
+
- A ratio of two same-unit quantities (`\arctan(y/x)` for two lengths) is
|
|
565
|
+
dimensionless by cancellation; leaving it unitless is correct, not an
|
|
566
|
+
omission — write the units only where they actually survive to the result.
|
|
567
|
+
- Squaring or otherwise combining a substituted value carries its unit
|
|
568
|
+
through the operation: `(3\ \text{m})^{2} = 9\ \text{m}^2`, not `3^2 =
|
|
569
|
+
9\ \text{m}^2` with the unit reattached after the fact.
|
|
570
|
+
|
|
571
|
+
_Enforcement._ Review-only. Checking that a substitution step's units are
|
|
572
|
+
_present_ is straightforward text matching, but checking that they are
|
|
573
|
+
_dimensionally correct_ — that `m/s²` times `s` really does collapse to
|
|
574
|
+
`m/s` — is a small computer-algebra problem, not a pattern match; no gate in
|
|
575
|
+
this repo attempts it. A reviewer reads the worked example the way a student
|
|
576
|
+
would: redo the substitution and confirm every number's unit, then confirm
|
|
577
|
+
the arithmetic on the units themselves lands on the stated result's unit.
|
|
578
|
+
|
|
579
|
+
_Exceptions._ A dimensionless ratio or angle carries no unit to begin with
|
|
580
|
+
(see "how to apply" above) — that is not an exception, just correctly having
|
|
581
|
+
nothing to attach.
|
|
582
|
+
|
|
583
|
+
---
|
|
584
|
+
|
|
585
|
+
## N12
|
|
586
|
+
|
|
587
|
+
**Math syntax never carries the sentence's own trailing punctuation.**
|
|
588
|
+
|
|
589
|
+
A period that ends a sentence, or a comma that joins it to the next clause,
|
|
590
|
+
stays in the prose — outside `$…$` / `$$…$$` — even when the math is the last
|
|
591
|
+
thing before it. Never let it drift inside the span, and never bury it inside
|
|
592
|
+
a trailing `\text{}` unit:
|
|
593
|
+
|
|
594
|
+
```text
|
|
595
|
+
$$
|
|
596
|
+
\rho = \sqrt{3^2 + 4^2} = 5\ \text{m,} ✗ the comma is now part of the LaTeX
|
|
597
|
+
$$
|
|
598
|
+
|
|
599
|
+
$$
|
|
600
|
+
\rho = \sqrt{3^2 + 4^2} = 5\ \text{m} ✓ drop it — the next sentence needs no connector here
|
|
601
|
+
$$
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
This is about the sentence's _own_ punctuation riding along with the math by
|
|
605
|
+
accident, not about punctuation that is itself part of the mathematical
|
|
606
|
+
notation — a comma separating two side-by-side equations in one display block
|
|
607
|
+
(`x = \ldots, \qquad y = \ldots`) is a list separator inside the math, not
|
|
608
|
+
prose leaking in, and is unaffected by this rule.
|
|
609
|
+
|
|
610
|
+
_Rationale._ A trailing period or comma inside `$…$` becomes part of the
|
|
611
|
+
LaTeX source: KaTeX renders it in math italic/spacing rather than as
|
|
612
|
+
punctuation, it is included in whatever `\text{}` it landed inside (turning a
|
|
613
|
+
unit label `\text{m}` into the unrelated string `\text{m,}`), and a reader
|
|
614
|
+
who copies the equation out of the rendered page copies the stray mark with
|
|
615
|
+
it. A display equation followed by a `\label{eq:…}` and then a fresh
|
|
616
|
+
paragraph — the pattern most of this corpus already uses — never has this
|
|
617
|
+
problem in the first place, which is the simplest fix in practice.
|
|
618
|
+
|
|
619
|
+
_How to apply._ Omit the connector. A display equation does not need a
|
|
620
|
+
trailing comma to link it to the sentence that continues below it — the
|
|
621
|
+
paragraph break already does that job, and every rule-following equation in
|
|
622
|
+
this corpus (see [N5](#n5)'s `\label{eq:…}` pattern) already ends bare. For
|
|
623
|
+
inline math (`$x = 5$` mid-sentence), put the period or comma after the
|
|
624
|
+
closing `$`, where it has always belonged.
|
|
625
|
+
|
|
626
|
+
_Enforcement._ Review-only. A gate could, in principle, flag `.` or `,` as
|
|
627
|
+
the last character before a closing `$`/`$$`, but it cannot tell a stray
|
|
628
|
+
sentence-ending mark from a legitimate mathematical use of the same
|
|
629
|
+
character (a decimal point, a `\ldots`, a matrix row separator) without
|
|
630
|
+
false positives outweighing the catches — left to review, the same as
|
|
631
|
+
[N5](#n5).
|
|
632
|
+
|
|
633
|
+
_Exceptions._ A comma or period that is itself notation — a decimal point in
|
|
634
|
+
a number, `\ldots`, a matrix/vector component separator — is not this rule's
|
|
635
|
+
target and stays exactly where the math needs it.
|
|
636
|
+
|
|
637
|
+
---
|
|
638
|
+
|
|
639
|
+
## N13
|
|
640
|
+
|
|
641
|
+
**A nonlinear function always takes a parenthesized argument, even a single
|
|
642
|
+
symbol.**
|
|
643
|
+
|
|
644
|
+
Every use of a trigonometric, inverse-trigonometric, or hyperbolic function,
|
|
645
|
+
and every logarithm or exponential, wraps its argument in a visible
|
|
646
|
+
parenthesis (or bracket) — never a bare symbol or number sitting directly
|
|
647
|
+
after the function name:
|
|
648
|
+
|
|
649
|
+
```text
|
|
650
|
+
\sin x = \sin y ✗
|
|
651
|
+
\sin(x) = \sin(y) ✓
|
|
652
|
+
|
|
653
|
+
log x ✗
|
|
654
|
+
\log(x) ✓
|
|
655
|
+
|
|
656
|
+
\sin^2 x ✗ — the exponent doesn't excuse the missing parens
|
|
657
|
+
\sin^2(x) ✓
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
_Rationale._ `\sin x` leaves how far the argument extends ambiguous the
|
|
661
|
+
instant anything follows it — `\sin x + 1` visually reads as "sine of x,
|
|
662
|
+
plus one" but a reader has to already know the convention to be sure it
|
|
663
|
+
isn't "sine of (x + 1)". `\sin(x)` is unambiguous regardless of what
|
|
664
|
+
surrounds it, at the cost of nothing when the argument really is a single
|
|
665
|
+
symbol. Textbooks vary on this; this course picks the form that never
|
|
666
|
+
requires the reader to know the convention.
|
|
667
|
+
|
|
668
|
+
_How to apply._
|
|
669
|
+
|
|
670
|
+
- Wrap the argument in `(...)` — or `\left( ... \right)` when it needs to
|
|
671
|
+
grow with its contents, or `[...]`/`\left[ ... \right]` when an outer
|
|
672
|
+
pair of parentheses is already in use for something else on the same
|
|
673
|
+
line. A bare `{...}` does not count: LaTeX braces are invisible grouping,
|
|
674
|
+
so `\sin{x}` renders identically to the forbidden `\sin x`.
|
|
675
|
+
- An exponent between the function and its argument does not exempt it —
|
|
676
|
+
`\sin^{2}(x)`, `\log_{2}(x)`, `\sin^{-1}(x)` all still need the
|
|
677
|
+
parenthesis after the modifier.
|
|
678
|
+
- The function covered by this rule is exactly
|
|
679
|
+
[`PARENTHESIZED_FUNCTIONS`](../../src/reference/base-operators.mjs): the
|
|
680
|
+
six trigonometric functions, `arcsin`/`arccos`/`arctan`, the four
|
|
681
|
+
hyperbolic functions in the base operator library, and `exp`/`log`/`ln`/
|
|
682
|
+
`lg`. A different named operator ([N8](#n8)'s `\operatorname{Var}` and
|
|
683
|
+
friends) is unaffected — this rule is scoped to the functions whose
|
|
684
|
+
argument's extent is genuinely ambiguous without a mark, not every named
|
|
685
|
+
operator.
|
|
686
|
+
|
|
687
|
+
_Enforcement._ Build-enforced, with no partial-corpus warm-up tier — like
|
|
688
|
+
[N9](#n9), it ships as a hard error. `resolveMathGlyphs`
|
|
689
|
+
(`src/reference/math-glyphs.mjs`) walks the parsed KaTeX tree for every
|
|
690
|
+
rendered-math context this engine has — the same single resolver [N1](#n1)
|
|
691
|
+
through [N10](#n10) already rely on — and fails the moment a
|
|
692
|
+
`PARENTHESIZED_FUNCTIONS` name is not immediately followed by a visible
|
|
693
|
+
opening delimiter. Because every context (lesson body, `notation.formula`,
|
|
694
|
+
assessment `prompt`/`explanation`, a `Diagram` node's `formula`, interactive
|
|
695
|
+
lab math) calls this one function, the check needs no separate wiring per
|
|
696
|
+
content type: `pnpm validate:content` catches it in lesson body, formula, and
|
|
697
|
+
assessment math; `astro build` catches a `Diagram` or lab-math violation
|
|
698
|
+
(these are authored outside any manifest-scanned MDX body, in a component's
|
|
699
|
+
own `nodes={[...]}` or lab template) as a hard render failure, since neither
|
|
700
|
+
`Diagram` nor lab math has a code path that tolerates degrading a formula
|
|
701
|
+
silently for this rule the way an ambiguous-glyph error can.
|
|
702
|
+
|
|
703
|
+
_Exceptions._ None. A quoted source that uses `\sin x` still gets
|
|
704
|
+
parenthesized in this course's rendering — the parenthesis is a typesetting
|
|
705
|
+
choice, not a claim about the source's own notation, so quoting accurately
|
|
706
|
+
does not require reproducing it.
|
|
707
|
+
|
|
708
|
+
---
|
|
709
|
+
|
|
710
|
+
## N14
|
|
711
|
+
|
|
712
|
+
**An abbreviation-shaped word in prose is universal, or it is explained.**
|
|
713
|
+
|
|
714
|
+
Every all-caps acronym (`OLS`, `MSE`, `SSR`) or dotted abbreviation (`w.r.t.`,
|
|
715
|
+
`i.i.d.`) a lesson uses in ordinary prose — outside any `$…$` — is either on
|
|
716
|
+
the shipped abbreviation baseline (`src/reference/base-abbreviations.mjs`
|
|
717
|
+
plus this course's `abbreviations.allow` in `course.config.ts`), carries an
|
|
718
|
+
entry in the `abbreviations` content collection
|
|
719
|
+
(`content/abbreviations/*.md`), or is already explained as a math quantity on
|
|
720
|
+
the same page ([N8](#n8) / [N10](#n10)). A term resolved by the table is not
|
|
721
|
+
just build-checked: `remarkAbbreviations` wraps its first occurrence on the
|
|
722
|
+
page in the same hover/click card the math-notation and citation systems
|
|
723
|
+
already use, linking to its entry on the glossary page.
|
|
724
|
+
|
|
725
|
+
_Rationale._ [N9](#n9) already forces a _math_ quantity or unit into `$…$` so
|
|
726
|
+
it gets a hover card; this rule closes the parallel gap for a term that reads
|
|
727
|
+
as English, not math — a reader who knows `\mathrm{MSE}` has a card two
|
|
728
|
+
sections later gets nothing from "the MSE is minimized" if that sentence
|
|
729
|
+
never binds to it. An abbreviation is exactly the kind of thing an
|
|
730
|
+
experienced writer stops noticing they haven't defined.
|
|
731
|
+
|
|
732
|
+
_How to apply._
|
|
733
|
+
|
|
734
|
+
- An abbreviation that names no quantity of its own — `OLS`, `w.r.t.`,
|
|
735
|
+
`i.i.d.` — gets an entry in `content/abbreviations/*.md`:
|
|
736
|
+
|
|
737
|
+
```yaml
|
|
738
|
+
key: ols
|
|
739
|
+
abbr: OLS
|
|
740
|
+
expansion: Ordinary least squares — the estimator that minimizes the sum of squared residuals.
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
This is a lighter tier than a notation card: a name resolution only, no
|
|
744
|
+
`formula`, `units`, or curriculum `alignment`. It has no authoring syntax
|
|
745
|
+
of its own in prose (no `[[key]]` needed) — the gate below recognizes the
|
|
746
|
+
literal written form (`abbr`, or one of its `aliases`) anywhere on the
|
|
747
|
+
page.
|
|
748
|
+
|
|
749
|
+
- An abbreviation that _is_ a math quantity — `MSE`, `SSR`, kinetic energy's
|
|
750
|
+
`KE` — is introduced the normal [N8](#n8)/[N10](#n10) way instead: a
|
|
751
|
+
notation entry whose `latex` is `\mathrm{MSE}` (or an `\operatorname{…}`),
|
|
752
|
+
bound with `[[key]]` or `\explain{key}{…}`. Once that entry is in scope on
|
|
753
|
+
a page, this rule already treats the bare word `MSE` in that page's prose
|
|
754
|
+
as explained — it does **not** also need an `abbreviations` table entry.
|
|
755
|
+
|
|
756
|
+
- A course whose readers already know a term — `AI`, `GDP`, whatever the
|
|
757
|
+
domain treats as common — lists it once in `course.config.ts` instead:
|
|
758
|
+
|
|
759
|
+
```ts
|
|
760
|
+
abbreviations: { allow: ['GDP', 'CPU'] }
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
`abbreviations.deny` forces a shipped baseline entry (`e.g.`, `i.e.`, …) to
|
|
764
|
+
be explained anyway.
|
|
765
|
+
|
|
766
|
+
_Enforcement._ Build-enforced (`gate-abbreviations.ts`), wired into the same
|
|
767
|
+
`pnpm validate:content` pass as [N9](#n9), context `abbreviation`. It scans
|
|
768
|
+
the exact same "prose-only" surface N9 does (code, math, `[[key]]`
|
|
769
|
+
references, and JSX tags already blanked out), looking for an all-caps run of
|
|
770
|
+
2+ letters or a run of 2+ single-letter-then-period groups. Like N9, it ships
|
|
771
|
+
as a hard error with no Tier-3 warm-up period and no `lint-ignore.yml` entry
|
|
772
|
+
— the corpus had no prior instances of either shape in prose when the check
|
|
773
|
+
was added.
|
|
774
|
+
|
|
775
|
+
_Exceptions._ None acknowledged through `lint-ignore.yml`. A genuinely
|
|
776
|
+
universal abbreviation this course's readers cannot be expected to look up is
|
|
777
|
+
added to `abbreviations.allow` in `course.config.ts`, reviewed like any other
|
|
778
|
+
change to that file.
|
|
779
|
+
|
|
780
|
+
---
|
|
781
|
+
|
|
782
|
+
## N15
|
|
783
|
+
|
|
784
|
+
**A nested visible delimiter — `(`, `[`, `\{`, or `|` inside another one of
|
|
785
|
+
them — must use its `\left`/`\right` sizing form, not the bare character.**
|
|
786
|
+
|
|
787
|
+
`(`, `[`, `\{` (an escaped, visible brace — bare `{`/`}` is invisible LaTeX
|
|
788
|
+
grouping and out of scope here), and `|` may be written bare only when they
|
|
789
|
+
are not nested — not inside one another, and not inside a second instance of
|
|
790
|
+
themselves. The moment one of them sits inside another, **every level of that
|
|
791
|
+
nest** switches to the auto-sizing form: `\left(`/`\right)`, `\left[`/`\right]`,
|
|
792
|
+
`\left\{`/`\right\}`, `\left|`/`\right|`.
|
|
793
|
+
|
|
794
|
+
```text
|
|
795
|
+
\sin(2(90\text{°}-\theta)) ✗ two bare ( nested
|
|
796
|
+
\sin\left(2\left(90\text{°}-\theta\right)\right) ✓
|
|
797
|
+
|
|
798
|
+
\left(1 - \sin^{2}(\theta)\right) ✗ bare ( one level inside \left(
|
|
799
|
+
\left(1 - \sin^{2}\left(\theta\right)\right) ✓ — sizing the outer pair
|
|
800
|
+
does not size the inner one
|
|
801
|
+
|
|
802
|
+
(a + [b - c]) ✗ nested across types too
|
|
803
|
+
\left(a + \left[b - c\right]\right) ✓
|
|
804
|
+
|
|
805
|
+
(x) ✓ — not nested, bare is fine
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
_Rationale._ A bare delimiter is a fixed-height glyph; `\left`/`\right` grows
|
|
809
|
+
to match whatever it wraps. Two bare pairs nested inside each other render at
|
|
810
|
+
the _same_ fixed size regardless of which one's content is actually taller,
|
|
811
|
+
so the inner and outer marks visually collide or look like a typo the moment
|
|
812
|
+
either side holds anything but a single short symbol. Requiring the sizing
|
|
813
|
+
form at every level of a nest — rather than trying to judge, pair by pair,
|
|
814
|
+
whether _this particular_ nesting happens to look fine — is the simplest rule
|
|
815
|
+
that is actually checkable by machine and never leaves a judgment call to the
|
|
816
|
+
author on a case that can render invisibly wrong.
|
|
817
|
+
|
|
818
|
+
_How to apply._
|
|
819
|
+
|
|
820
|
+
- Reach for `\left(`/`\right)` (or the bracket/brace/pipe equivalents) the
|
|
821
|
+
moment a second one of these four glyphs would end up inside the first —
|
|
822
|
+
regardless of which of the four it is, and regardless of whether the outer
|
|
823
|
+
one is already sized. Sizing the outer pair does not exempt an inner bare
|
|
824
|
+
one; both levels of a two-level nest need their own `\left`/`\right`.
|
|
825
|
+
- Mixing types counts as nesting too: `(a + [b])`, `\{a \mid (b)\}`, and
|
|
826
|
+
`(|a|)` all need every level sized, exactly like nesting the same character
|
|
827
|
+
twice.
|
|
828
|
+
- A `|` has no distinct open/close glyph, so a lone pair — `\left|x\right|`,
|
|
829
|
+
or bare `|x|` when it is not nested in anything — reads as one absolute
|
|
830
|
+
value/norm, matched left to right. Conditional probability still prefers
|
|
831
|
+
`\mid` over a bare `|` regardless of nesting ([N6](#n6)).
|
|
832
|
+
- A delimiter inside `\text{…}` is display text, not a sizing mark — a unit
|
|
833
|
+
written `\text{(s)}` ([N9](#n9)) is untouched by this rule even when the
|
|
834
|
+
math around it also uses `(`.
|
|
835
|
+
- This is unrelated to [N13](#n13)'s requirement that a nonlinear function's
|
|
836
|
+
argument always be visibly parenthesized: N13 is satisfied by a bare
|
|
837
|
+
`\sin(x)` just as well as `\sin\left(x\right)`; N15 only fires once a
|
|
838
|
+
second one of these four glyphs nests inside the first.
|
|
839
|
+
|
|
840
|
+
_Enforcement._ Build-enforced, with no partial-corpus warm-up tier — like
|
|
841
|
+
[N9](#n9)/[N13](#n13), it ships as a hard error, and the corpus's two prior
|
|
842
|
+
instances (`playground/content/docs/projectiles/optimal-launch-angle.mdx`)
|
|
843
|
+
were fixed in the same change that added the check.
|
|
844
|
+
`findNestedBareDelimiters` (`src/reference/math-glyphs.mjs`) walks the same
|
|
845
|
+
parsed KaTeX tree `resolveMathGlyphs` and [N13](#n13) already build, carrying
|
|
846
|
+
one stack through the whole walk: pushing a tracked bare or `\left`-sized
|
|
847
|
+
delimiter, and flagging the enclosing entry (if bare) and the new one (if
|
|
848
|
+
bare) the moment a second one opens while the first is still open. Because
|
|
849
|
+
every rendered-math context calls `resolveMathGlyphs` — lesson body,
|
|
850
|
+
`notation.formula`, assessment `prompt`/`explanation`, `Diagram`/lab math —
|
|
851
|
+
the check needs no separate wiring per content type, the same story as N13.
|
|
852
|
+
|
|
853
|
+
_Exceptions._ None. A quoted source nesting bare delimiters still renders
|
|
854
|
+
sized in this course, the same way N13 reparenthesizes a quoted `\sin x` —
|
|
855
|
+
the sizing is a typesetting choice, not a claim about the source's own
|
|
856
|
+
notation.
|