explico 0.5.0 → 0.7.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 +295 -7
- package/README.md +16 -2
- package/dist/compiler/astro.d.ts +13 -2
- package/dist/compiler/astro.js +39 -0
- package/dist/compiler/cli.d.ts +22 -3
- package/dist/compiler/cli.js +71 -10
- package/dist/compiler/course-config.d.ts +31 -1
- package/dist/compiler/course-config.js +7 -0
- package/dist/compiler/exports/standalone-html.js +13 -0
- package/dist/compiler/logging.d.ts +109 -0
- package/dist/compiler/logging.js +107 -0
- package/dist/compiler/manifest.d.ts +16 -4
- package/dist/compiler/manifest.js +7 -3
- package/dist/reference/base-abbreviations.mjs +8 -2
- package/dist/reference/consistency.d.ts +8 -10
- package/dist/reference/consistency.js +3 -47
- package/dist/reference/delimiter-size.d.ts +39 -0
- package/dist/reference/delimiter-size.js +177 -0
- package/dist/reference/diagrams-validate.d.ts +1 -1
- package/dist/reference/equations-validate.d.ts +2 -2
- package/dist/reference/equations-validate.js +1 -1
- package/dist/reference/figures-validate.d.ts +1 -1
- package/dist/reference/gate-heading-length.d.ts +12 -0
- package/dist/reference/gate-heading-length.js +79 -0
- package/dist/reference/gate-math.d.ts +8 -2
- package/dist/reference/gate-math.js +30 -7
- package/dist/reference/keyed-float-validate.d.ts +2 -2
- package/dist/reference/keyed-float-validate.js +1 -1
- package/dist/reference/math-glyphs.d.mts +12 -0
- package/dist/reference/math-glyphs.mjs +183 -42
- package/dist/reference/references.js +2 -1
- package/dist/reference/registry.js +1 -1
- package/dist/reference/rehype-base-links.d.mts +23 -0
- package/dist/reference/rehype-base-links.mjs +40 -0
- package/dist/reference/remark-abbreviations.mjs +10 -12
- package/dist/reference/remark-citation.d.mts +5 -1
- package/dist/reference/remark-citation.mjs +0 -0
- package/dist/reference/render-lab-math.d.ts +18 -1
- package/dist/reference/render-lab-math.js +10 -3
- package/dist/reference/tables-validate.d.ts +1 -1
- package/dist/reference/types.d.ts +1 -1
- package/docs/VERSIONING.md +24 -4
- package/docs/authoring/README.md +14 -2
- package/docs/authoring/agents.md +7 -3
- package/docs/authoring/components.md +4 -1
- package/docs/authoring/migrating.md +477 -8
- package/docs/authoring/notation.md +194 -7
- package/docs/authoring/prose.md +64 -0
- package/docs/authoring/structure-rules.md +69 -0
- package/docs/authoring/visualizations.md +2 -2
- package/package.json +1 -1
- package/src/compiler/astro.ts +89 -0
- package/src/compiler/cli.ts +144 -12
- package/src/compiler/course-config.ts +40 -0
- package/src/compiler/exports/standalone-html.ts +15 -0
- package/src/compiler/logging.ts +248 -0
- package/src/compiler/manifest.ts +20 -3
- package/src/components/diagram/Diagram.astro +0 -55
- package/src/components/memo/Memo.astro +2 -5
- package/src/components/memo/ShortNote.astro +2 -5
- package/src/components/notation/NotationFormula.astro +199 -10
- package/src/components/notation/NotationGlossary.astro +89 -58
- package/src/components/notation/NotationLayer.astro +238 -156
- package/src/components/scroll-animation.ts +160 -0
- package/src/components/starlight/AnchorScrollEnhancer.astro +102 -0
- package/src/components/starlight/HeadingAnchorEnhancer.astro +2 -4
- package/src/components/starlight/KeyedFloatEnhancer.astro +17 -16
- package/src/components/starlight/LessonFooter.astro +5 -1
- package/src/content-config.ts +16 -1
- package/src/reference/base-abbreviations.mjs +8 -2
- package/src/reference/consistency.ts +10 -66
- package/src/reference/delimiter-size.ts +301 -0
- package/src/reference/diagrams-validate.ts +1 -1
- package/src/reference/equations-validate.ts +3 -3
- package/src/reference/figures-validate.ts +1 -1
- package/src/reference/gate-heading-length.ts +131 -0
- package/src/reference/gate-math.ts +48 -8
- package/src/reference/keyed-float-validate.ts +3 -3
- package/src/reference/math-glyphs.mjs +183 -42
- package/src/reference/references.ts +2 -1
- package/src/reference/registry.ts +1 -1
- package/src/reference/rehype-base-links.mjs +40 -0
- package/src/reference/remark-abbreviations.mjs +10 -12
- package/src/reference/remark-citation.mjs +0 -0
- package/src/reference/render-lab-math.ts +24 -1
- package/src/reference/tables-validate.ts +1 -1
- package/src/reference/types.ts +1 -1
- package/src/styles/global.css +120 -15
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,275 @@ under **Breaking** below; a PATCH never breaks.
|
|
|
14
14
|
|
|
15
15
|
## [Unreleased]
|
|
16
16
|
|
|
17
|
+
## [0.7.0] - 2026-09-18
|
|
18
|
+
|
|
19
|
+
### Breaking
|
|
20
|
+
|
|
21
|
+
- **The compiler manifest schema is now v15 and diagnostic severity has three
|
|
22
|
+
levels.** Public diagnostic severity unions now include `info`, whose findings
|
|
23
|
+
are optional advice rather than repair debt. `eq-key-unused`, every keyed-float
|
|
24
|
+
`*-key-unused`, `unused-definition`, `weak-local`, `gloss-wants-promoting`, and
|
|
25
|
+
`card-wants-demoting` now emit `info` instead of `warning`. Consumers must stop
|
|
26
|
+
treating every non-error as a warning and accept schema v15.
|
|
27
|
+
|
|
28
|
+
- **`explicoContentRefresh()` now fails the build when a manifest-driven
|
|
29
|
+
remark plugin is missing from `markdown.remarkPlugins`.** Its
|
|
30
|
+
`astro:config:setup` hook checks `manifest.abbreviations`,
|
|
31
|
+
`manifest.sources`, and `manifest.notation.raw` against the configured
|
|
32
|
+
plugins and throws if a non-empty table has no matching
|
|
33
|
+
`remarkAbbreviations`, `remarkCitation`, or `remarkNotation` registration.
|
|
34
|
+
Previously this "half-migrated" shape — content catalogued, plugin wiring
|
|
35
|
+
skipped — passed `pnpm validate:content` and `astro build` with no signal
|
|
36
|
+
anywhere, silently rendering triggers as plain text (see
|
|
37
|
+
[`docs/authoring/migrating.md`, rule N14](docs/authoring/migrating.md#4-rule-n14)).
|
|
38
|
+
A course with this exact gap will now fail its next build until the
|
|
39
|
+
missing plugin is registered.
|
|
40
|
+
|
|
41
|
+
- **Retired the `numerals-tagged` Tier-3 consistency diagnostic.** It counted
|
|
42
|
+
every numeral in an untagged math span or table row—including unit exponents,
|
|
43
|
+
transparent worked-example arithmetic, and repeated table values—while
|
|
44
|
+
reporting only a lesson-level aggregate. `ConsistencyCode` no longer includes
|
|
45
|
+
`numerals-tagged`; remove any consumer logic or `lint-ignore.yml` entry for
|
|
46
|
+
that code. Numerical provenance should be enforced only by a future rule with
|
|
47
|
+
an explicit authoring surface and occurrence-level diagnostics.
|
|
48
|
+
|
|
49
|
+
- **Removed the notation popup's "Copy LaTeX" button and the glossary's `#`
|
|
50
|
+
copy-link button** (`.notation-popup-copy`, `[data-popup-copy]`, and
|
|
51
|
+
`[data-glossary-anchor]` with its "Link copied" flash are gone from the
|
|
52
|
+
DOM). Copying a formula's LaTeX is now a click on the equation itself (see
|
|
53
|
+
**Added**); a glossary entry's title is now a link to its own
|
|
54
|
+
`#notation-<key>` anchor, with a hover-revealed link-icon anchor beside it
|
|
55
|
+
that navigates like any heading permalink instead of writing to the
|
|
56
|
+
clipboard. `NotationFormula`'s keyboard-focusable scroll region also moved
|
|
57
|
+
from `.notation-formula-math` to a new nested `.notation-formula-scroll`.
|
|
58
|
+
A course's own CSS or end-to-end tests targeting any of these selectors
|
|
59
|
+
need updating.
|
|
60
|
+
|
|
61
|
+
### Added
|
|
62
|
+
|
|
63
|
+
- **Click any rendered equation to copy its LaTeX, and every glossary formula
|
|
64
|
+
is numbered.** `NotationLayer.astro` (loaded on every page through the
|
|
65
|
+
Footer override) now handles clicks on any `[data-notation-formula]` box —
|
|
66
|
+
a glossary formula, an `\explain{}` formula inside a popup or `Diagram`, or
|
|
67
|
+
an interactive lab equation — copying its `data-latex` and flashing
|
|
68
|
+
"Copied!"; a hover tooltip names the gesture, and a click on an explained
|
|
69
|
+
symbol or an equation number keeps its own meaning. A glossary entry's
|
|
70
|
+
formula gets a `G.<n>` number (page order) that links to its entry, plus a
|
|
71
|
+
copy button; `NotationFormula` gains optional `number` / `numberHref` props
|
|
72
|
+
and renders neither when they are omitted. For interactive labs,
|
|
73
|
+
`renderLabMath()` now also returns `plainLatexTemplate` (bare symbols, each
|
|
74
|
+
`\slot{name}{…}` replaced by `slotPlaceholder(name)`), and the new
|
|
75
|
+
`slotPlaceholder` export lets an island substitute each slot's live value
|
|
76
|
+
so `data-latex` tracks the sliders. A lab that sets `data-notation-formula`
|
|
77
|
+
and `data-latex` on its math root joins the gesture; one that does not is
|
|
78
|
+
unchanged.
|
|
79
|
+
|
|
80
|
+
- **Smooth, eased scrolling for in-page anchor links.** The new
|
|
81
|
+
`AnchorScrollEnhancer.astro`, mounted by `LessonFooter.astro`, catches every
|
|
82
|
+
same-page `<a href="#…">` — headings, equation/table/figure numbers,
|
|
83
|
+
footnote and memo markers, the glossary's links, the table of contents —
|
|
84
|
+
animates to the target with the shared `animatedScrollIntoView` helper, then
|
|
85
|
+
performs a real fragment navigation on arrival so `:target` styling and
|
|
86
|
+
`hashchange` behave as for a plain click. `prefers-reduced-motion` is
|
|
87
|
+
honored, and scrolling away mid-animation just updates the URL.
|
|
88
|
+
`HeadingAnchorEnhancer`, `KeyedFloatEnhancer`, `Memo`, and `ShortNote` now
|
|
89
|
+
share that helper. A course that overrides `Footer` with its own component
|
|
90
|
+
instead of `LessonFooter.astro` must render `<AnchorScrollEnhancer />` itself
|
|
91
|
+
to get it.
|
|
92
|
+
|
|
93
|
+
- **New review-only authoring rule P1 and a new `P` (prose) rule area: "or"
|
|
94
|
+
never introduces a second name for the same thing.** Write "i.e.,", "also
|
|
95
|
+
called", "also known as", or parentheses instead, because "or" also
|
|
96
|
+
separates real alternatives and a reader cannot tell which is meant ("the
|
|
97
|
+
residual or error term"). Nothing is checked at build time, so no course
|
|
98
|
+
needs to change to keep building. See `docs/authoring/prose.md#p1`.
|
|
99
|
+
|
|
100
|
+
- **The shipped abbreviation baseline (rule N14) now covers every common
|
|
101
|
+
time-of-day and "and so on" spelling.** `AM`, `PM`, `A.M.`, `P.M.`, and
|
|
102
|
+
`e.t.c.` join `a.m.`, `p.m.`, `e.g.`, `i.e.`, and `etc.`, so a lesson can
|
|
103
|
+
write "9 AM" or "e.g." without a `content/abbreviations/*.md` entry or an
|
|
104
|
+
`abbreviations.allow` line. This only loosens the gate — no course needs to
|
|
105
|
+
change to keep building; a course that wants one of them explained anyway
|
|
106
|
+
lists it in `abbreviations.deny`. See `docs/authoring/notation.md#n14`.
|
|
107
|
+
|
|
108
|
+
- **Run logs for `pnpm validate:content`, in Python's `logging` format.** The
|
|
109
|
+
new `explico/compiler/logging` export (`createRunLog()`) records every
|
|
110
|
+
diagnostic a validate run reports to `build/logs/<timestamp>/`, formatted as
|
|
111
|
+
`%(asctime)s %(levelname)s %(name)s: %(message)s` so the lines filter with
|
|
112
|
+
the same tooling a Python log does. Three cumulative files per run —
|
|
113
|
+
`error.log` (errors), `warning.log` (errors + warnings), and `info.log`
|
|
114
|
+
(everything) — let you open the narrowest one that still answers the
|
|
115
|
+
question, and `build/logs/latest` symlinks to the newest run. Logger names
|
|
116
|
+
are `explico.<subsystem>` (`explico.notation`, `explico.curriculum`,
|
|
117
|
+
`explico.raw-floats`, …), derived from the diagnostic category, so filtering
|
|
118
|
+
by name is filtering by subsystem. Nothing is pruned; `build/` is
|
|
119
|
+
git-ignored. `info.log` also captures the severity-`info` diagnostics the
|
|
120
|
+
engine has always produced but never printed, plus the run summary lines.
|
|
121
|
+
The terminal output is deliberately unchanged — the compact `[code] message`
|
|
122
|
+
lines stay exactly as they were, with one added pointer line naming the run's
|
|
123
|
+
log directory and its counts. Blocking diagnostics still throw and still exit
|
|
124
|
+
non-zero; logging is purely additive. A course picks this up by routing its
|
|
125
|
+
own `scripts/compile-manifest.ts` through `createRunLog()`.
|
|
126
|
+
|
|
127
|
+
- **New severity-filtered content diagnostics and VS Code actions.** `pnpm
|
|
128
|
+
content diagnostics [info|warning|error] [--json]` flattens manifest findings
|
|
129
|
+
into stable, source-aware `group:code` diagnostics at or above the requested
|
|
130
|
+
severity; the new `content warnings` is the warning-only view, and
|
|
131
|
+
`content status` now also counts `info` findings. Both commands are exported
|
|
132
|
+
from `explico/compiler/cli` (`contentDiagnostics`, `contentWarnings`), so a
|
|
133
|
+
course's own `scripts/content.ts` must route them to expose the commands. The
|
|
134
|
+
playground provides **Explico: Show content diagnostics (info+)**,
|
|
135
|
+
**(warning+)**, and **(errors)** tasks, which feed locatable findings into
|
|
136
|
+
VS Code's Problems panel.
|
|
137
|
+
|
|
138
|
+
- **New delimiter authoring rules N16 and N17, with warning-level size
|
|
139
|
+
linting.** Rule N16 tells AI authors to alternate `()`, `[]`, and `\{\}` for
|
|
140
|
+
purely stylistic nested grouping while preserving function calls,
|
|
141
|
+
expectations, intervals, sets, tuples, probability, indexing, norms, and
|
|
142
|
+
every other semantically meaningful conventional delimiter. It is
|
|
143
|
+
review-only. Rule N17 independently requires nested scalable delimiter pairs
|
|
144
|
+
to grow from inside to outside. The new stack-based
|
|
145
|
+
`lintDelimiterSizes` parser handles multiline and arbitrarily deep
|
|
146
|
+
`\left...\right` plus `\big`/`\Big`/`\bigg`/`\Bigg` pairs, emits the
|
|
147
|
+
non-blocking `delimiter-size-hierarchy` diagnostic at the outer pair's line
|
|
148
|
+
and column, and suggests a larger manual size without changing delimiter
|
|
149
|
+
shape. It deliberately does not lint type alternation or infer mathematical
|
|
150
|
+
semantics.
|
|
151
|
+
|
|
152
|
+
- **New `explico/reference/rehype-base-links.mjs`.** Wire `[rehypeBaseLinks, base]`
|
|
153
|
+
into `markdown.rehypePlugins` and it prefixes every root-relative
|
|
154
|
+
`<a href="/...">` a lesson's Markdown/MDX body produces — a lesson's own
|
|
155
|
+
prose links, `remark-notation.mjs`'s equation cross-references, and
|
|
156
|
+
`remark-citation.mjs`'s citation links — with the course's Astro `base`.
|
|
157
|
+
Starlight only applies `base` to its own generated navigation (sidebar,
|
|
158
|
+
pagination, breadcrumbs); it never touches anything the Markdown/MDX →
|
|
159
|
+
rehype pipeline produces, so on any course deployed under a subpath (a
|
|
160
|
+
GitHub Pages *project* site, e.g. `base: '/my-course'`) every one of those
|
|
161
|
+
links 404s without this. No-op when the course has no base, so it's safe
|
|
162
|
+
to wire in unconditionally. A lesson's Starlight frontmatter (e.g.
|
|
163
|
+
`hero.actions[].link`) never enters this pipeline either — write those as
|
|
164
|
+
paths relative to the current page instead.
|
|
165
|
+
|
|
166
|
+
- **The `↩` link at the end of each entry in a page's References section now
|
|
167
|
+
has a hover tooltip.** `remark-citation.mjs` already gave that back-link an
|
|
168
|
+
`aria-label` ("Back to the citation in the text"); it now sets the same text
|
|
169
|
+
as `title`, so a mouse user hovering the bare arrow sees what it does. No
|
|
170
|
+
course wiring or config change needed.
|
|
171
|
+
|
|
172
|
+
### Changed
|
|
173
|
+
|
|
174
|
+
- **`remarkAbbreviations` (rule N14) now wraps every occurrence of a
|
|
175
|
+
catalogued abbreviation in a lesson, not just the first per page.** A
|
|
176
|
+
reader who deep-links into a mid-page section — the sidebar TOC, a heading
|
|
177
|
+
anchor, an external link — used to land on plain, unlinked text with no
|
|
178
|
+
visual cue that a hover/click glossary card existed elsewhere on the page,
|
|
179
|
+
because only the earliest occurrence in document order was ever wrapped.
|
|
180
|
+
`AbbreviationLayer.astro` already resolved every marker independently by
|
|
181
|
+
`data-abbr-key`, so no island-side change was needed. This changes rendered
|
|
182
|
+
output for every existing course using this plugin: abbreviation-dense
|
|
183
|
+
prose that repeats the same term many times in one passage will now show a
|
|
184
|
+
dotted-underline link on each instance.
|
|
185
|
+
|
|
186
|
+
### Fixed
|
|
187
|
+
|
|
188
|
+
- **Standalone HTML export no longer runs every `<link>` through the asset
|
|
189
|
+
inliner.** Only a `<link>` whose `rel` is `stylesheet` or an icon type
|
|
190
|
+
(`icon`, `shortcut icon`, `apple-touch-icon`,
|
|
191
|
+
`apple-touch-icon-precomposed`, `mask-icon`) is inlined; any other `rel`
|
|
192
|
+
(`canonical`, `alternate`, `preload`, …) is left as authored. No course
|
|
193
|
+
change is needed.
|
|
194
|
+
|
|
195
|
+
## [0.6.0] - 2026-09-16
|
|
196
|
+
|
|
197
|
+
### Breaking
|
|
198
|
+
|
|
199
|
+
- **New build-enforced authoring rule S1: no Markdown heading (`##` through
|
|
200
|
+
`######`) and no `<Figure title>` / `<Diagram label>` / `<Memo title>` /
|
|
201
|
+
`<ShortNote title>` may run past 65 characters.** A `<Figure>` with a long
|
|
202
|
+
`caption` and no explicit `title` was silently using the whole caption as
|
|
203
|
+
its always-visible collapsible title-bar text — the reader saw the full
|
|
204
|
+
sentence twice. `gate-heading-length.ts` scans every lesson body; a course
|
|
205
|
+
with an over-limit heading or float title anywhere will now fail
|
|
206
|
+
`pnpm validate:content` / `astro build`. `<Table>` is exempt — it has only
|
|
207
|
+
a `caption`, not a separate `title`. A lesson's own `frontmatter.title`
|
|
208
|
+
(the page `<h1>`) is Review-only, not build-enforced — the gate scans body
|
|
209
|
+
text and cannot see frontmatter. See
|
|
210
|
+
`docs/authoring/structure-rules.md#s1`.
|
|
211
|
+
|
|
212
|
+
- **Removed the "Pin explanation" button from the "Page notation" panel**
|
|
213
|
+
(`.notation-pin`, `[data-notation-control]` — both gone from the DOM). Pin
|
|
214
|
+
a card from the list the same way an inline prose symbol always has: click
|
|
215
|
+
its glyph, now also Tab + Enter/Space-operable (`role="button"`,
|
|
216
|
+
`tabindex="0"`, `aria-pressed`) since it was the button's only remaining
|
|
217
|
+
job. A course's own CSS or tests targeting either selector need updating.
|
|
218
|
+
|
|
219
|
+
- **`NotationLayer.astro`'s separate "Page notation" `<h2>` is gone** —
|
|
220
|
+
the panel had two headings back to back ("Page notation" over "Notation
|
|
221
|
+
used on this page (N)"), saying the same thing twice. The panel's
|
|
222
|
+
`<summary>` is now the only heading, and `.notation-layer-heading` no
|
|
223
|
+
longer exists.
|
|
224
|
+
|
|
225
|
+
- **`Diagram`'s `description` prop and its collapsed "Diagram description"
|
|
226
|
+
fallback block are gone** (`.diagram-fallback` no longer exists in the
|
|
227
|
+
DOM). The caption (`label`) is the diagram's one authored piece of text,
|
|
228
|
+
matching how a LaTeX figure/table caption already tells the whole story —
|
|
229
|
+
a separate always-present description block was inventing a second text
|
|
230
|
+
category most courses never wanted and that only added visible chrome
|
|
231
|
+
under every diagram. Drop any `description="…"` passed to `<Diagram>`;
|
|
232
|
+
the build no longer accepts it (`astro check` fails on an excess prop).
|
|
233
|
+
|
|
234
|
+
### Added
|
|
235
|
+
|
|
236
|
+
- **`NotationLayer.astro`'s "Page notation" panel and `remark-citation.mjs`'s
|
|
237
|
+
generated "References" section are both properly collapsible now** — same
|
|
238
|
+
native, persisted, deep-linkable disclosure `Figure`/`CollapsibleSections`
|
|
239
|
+
already use, not the always-native-default `<details>` "Page notation" had
|
|
240
|
+
before (no persistence, no course-configurable default, no anchor).
|
|
241
|
+
- `NotationLayer` gains `defaultOpen`/`rememberState` props (mirroring
|
|
242
|
+
`Figure`'s) and a hover-reveal permalink anchor next to its title bar
|
|
243
|
+
(`#page-notation` by default).
|
|
244
|
+
- `remark-citation.mjs`'s generated References heading now carries the
|
|
245
|
+
same `[collapsible]`/`[collapsed]` marker an authored `##` heading uses,
|
|
246
|
+
via a new `defaultOpen` plugin option — so it rides the *same*
|
|
247
|
+
`rehypeHeadingAnchors` / `CollapsibleSections` machinery every other
|
|
248
|
+
heading does, with no engine changes needed on that side. This only
|
|
249
|
+
takes visible effect on a course whose `astro.config.mjs` wires
|
|
250
|
+
`rehypeHeadingAnchors` into its rehype pipeline.
|
|
251
|
+
- New `CourseConfig.disclosureDefaults` (the manifest schema is now v14,
|
|
252
|
+
additive):
|
|
253
|
+
`{ notation?, references? }`, each `true` = open, closed (the engine's
|
|
254
|
+
own default) when omitted or the whole block is absent. Both are
|
|
255
|
+
engine-authored sections, not written by a lesson, so they get their own
|
|
256
|
+
default independent of `defaultCollapsible.sections`/`.subsections` (an
|
|
257
|
+
author's own `##` headings). See `docs/authoring`,
|
|
258
|
+
`playground/astro.config.mjs` for the wiring convention.
|
|
259
|
+
|
|
260
|
+
### Fixed
|
|
261
|
+
|
|
262
|
+
- **A symbol explained once via `\explain{key}{…}` can now auto-resolve at
|
|
263
|
+
every later bare occurrence of the same declared `latex` on the page — this
|
|
264
|
+
previously failed silently (no tooltip, no build error) for several common
|
|
265
|
+
shapes.** A definition whose whole canonical form is a roman label
|
|
266
|
+
(`\mathrm{SSR}`, `\mathrm{SST}`) never entered the auto-annotator's
|
|
267
|
+
signature index at all, so a bare occurrence of such a symbol could never
|
|
268
|
+
resolve, anywhere on the page, independent of position. Also fixed: a bare
|
|
269
|
+
symbol sitting immediately next to an `\explain{…}{…}` call, a canonical
|
|
270
|
+
match found by seeing through an already-explicit call's own wrapping, and
|
|
271
|
+
a partial `\explain{key}{…}` wrap covering only part of a declared
|
|
272
|
+
multi-token form — all three previously either dropped the symbol silently
|
|
273
|
+
or produced malformed, unparseable output. All of these now resolve
|
|
274
|
+
correctly, or fail loudly as "Unresolved notation" (`resolveMathGlyphs`,
|
|
275
|
+
shared by lesson-body rendering and `gate-math.ts`'s `pnpm validate:content`
|
|
276
|
+
check — so a regression here fails the build immediately rather than
|
|
277
|
+
passing silently through to a render-time surprise). One side effect: the
|
|
278
|
+
fix also restores the `ambiguous-canonical-notation` check for
|
|
279
|
+
`\operatorname{…}`/`\mathrm{…}`-only glyphs, which the same bug had
|
|
280
|
+
silently disabled — a course with two different notation keys declaring
|
|
281
|
+
structurally identical LaTeX (e.g. two keys both `\operatorname{Var}`) will
|
|
282
|
+
now correctly fail `pnpm validate:content` where it previously built with a
|
|
283
|
+
silent, unresolvable ambiguity. See
|
|
284
|
+
[`docs/authoring/notation.md`](docs/authoring/notation.md).
|
|
285
|
+
|
|
17
286
|
## [0.5.0] - 2026-09-15
|
|
18
287
|
|
|
19
288
|
### Added
|
|
@@ -137,13 +406,16 @@ under **Breaking** below; a PATCH never breaks.
|
|
|
137
406
|
UI. The change is additive, but consumers that validate
|
|
138
407
|
`MANIFEST_SCHEMA_VERSION` must accept the new version.
|
|
139
408
|
|
|
140
|
-
- **The compiler manifest schema is now v10.**
|
|
409
|
+
- **The compiler manifest schema is now v10.** Numbered knowledge checks add
|
|
410
|
+
`manifest.checks`, `lessons[].knowledgeChecks`, `lessons[].numbering.checks`,
|
|
411
|
+
and `diagnostics.checks`. The change is additive, but consumers that
|
|
412
|
+
validate `MANIFEST_SCHEMA_VERSION` must accept the new version.
|
|
413
|
+
|
|
414
|
+
- **The compiler manifest schema is now v9.** Diagrams are first-class keyed
|
|
141
415
|
floats with their own `manifest.diagrams`, `lessons[].diagrams`,
|
|
142
|
-
`lessons[].numbering.diagrams`, and `diagnostics.diagrams` fields.
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
additive, but consumers that validate `MANIFEST_SCHEMA_VERSION` must accept
|
|
146
|
-
the new version.
|
|
416
|
+
`lessons[].numbering.diagrams`, and `diagnostics.diagrams` fields. The
|
|
417
|
+
change is additive, but consumers that validate `MANIFEST_SCHEMA_VERSION`
|
|
418
|
+
must accept the new version.
|
|
147
419
|
|
|
148
420
|
- **New build-enforced authoring rule N9: a physical quantity, unit, or math
|
|
149
421
|
operator must be typeset in `$…$`, never as plain text, anywhere on a
|
|
@@ -217,6 +489,20 @@ under **Breaking** below; a PATCH never breaks.
|
|
|
217
489
|
with `\sin\theta`, `\log x`, `\exp t`, etc. anywhere in rendered math will
|
|
218
490
|
now fail the build; see `docs/authoring/notation.md#n13`.
|
|
219
491
|
|
|
492
|
+
- **New build-enforced authoring rule N15: a nested visible delimiter — `(`,
|
|
493
|
+
`[`, `\{`, or `|` inside another one of them — must use its `\left`/`\right`
|
|
494
|
+
sizing form, not the bare character.** The moment a second one of these four
|
|
495
|
+
glyphs sits inside the first — same type or mixed, e.g. `(a + [b])` — every
|
|
496
|
+
level of that nest switches to the sizing form: `\sin(2(90°-\theta))` is now
|
|
497
|
+
a hard error, `\sin\left(2\left(90°-\theta\right)\right)` is required.
|
|
498
|
+
`findNestedBareDelimiters` (`math-glyphs.mjs`) walks the same parsed KaTeX
|
|
499
|
+
tree `resolveMathGlyphs` and N13 already build; a violation fails
|
|
500
|
+
`pnpm validate:content` / `astro build` with no `lint-ignore.yml` escape
|
|
501
|
+
hatch, like N9/N13. A single, non-nested bare delimiter (`(x)`) is
|
|
502
|
+
untouched, and a delimiter inside `\text{…}` (a unit per N9) is unaffected.
|
|
503
|
+
A downstream course with a nested bare delimiter anywhere in rendered math
|
|
504
|
+
will now fail the build; see `docs/authoring/notation.md#n15`.
|
|
505
|
+
|
|
220
506
|
### Added
|
|
221
507
|
|
|
222
508
|
- **Numbered, referenceable, collapsible knowledge checks.** Assessment ids in
|
|
@@ -436,7 +722,9 @@ under **Breaking** below; a PATCH never breaks.
|
|
|
436
722
|
system with its completeness gate, the curriculum-graph validator, the
|
|
437
723
|
Astro/Starlight UI, the LMS / session / analytics seams, and the `content` CLI.
|
|
438
724
|
|
|
439
|
-
[Unreleased]: https://github.com/danieltuzes/explico/compare/v0.
|
|
725
|
+
[Unreleased]: https://github.com/danieltuzes/explico/compare/v0.7.0...HEAD
|
|
726
|
+
[0.7.0]: https://github.com/danieltuzes/explico/compare/v0.6.0...v0.7.0
|
|
727
|
+
[0.6.0]: https://github.com/danieltuzes/explico/compare/v0.5.0...v0.6.0
|
|
440
728
|
[0.5.0]: https://github.com/danieltuzes/explico/compare/v0.4.0...v0.5.0
|
|
441
729
|
[0.4.0]: https://github.com/danieltuzes/explico/compare/v0.3.0...v0.4.0
|
|
442
730
|
[0.3.0]: https://github.com/danieltuzes/explico/compare/v0.2.0...v0.3.0
|
package/README.md
CHANGED
|
@@ -41,8 +41,9 @@ What it gives a course:
|
|
|
41
41
|
figure — nothing in the engine assumes one plotting approach.
|
|
42
42
|
- **LMS / analytics / identity seams** — framework-free interfaces shipped as
|
|
43
43
|
no-ops; a host supplies real implementations without touching a component.
|
|
44
|
-
- **A `content` CLI** — `status`, `
|
|
45
|
-
MDX → remark → KaTeX and fails loudly),
|
|
44
|
+
- **A `content` CLI** — `status`, `diagnostics`, `warnings`, `context <lesson>`,
|
|
45
|
+
`check <lesson>` (compiles MDX → remark → KaTeX and fails loudly),
|
|
46
|
+
`new lesson|term`.
|
|
46
47
|
|
|
47
48
|
The prerequisite graph itself is authored by the lecturer or an AI; the engine
|
|
48
49
|
validates it (DAG, ordering, coverage, orphans) rather than inferring it.
|
|
@@ -192,6 +193,19 @@ URL likewise fails the standalone export audit.
|
|
|
192
193
|
"scripts": { "validate:content": "tsx scripts/compile-manifest.ts" }
|
|
193
194
|
```
|
|
194
195
|
|
|
196
|
+
Diagnostics have three distinct severities: an `error` blocks validation because
|
|
197
|
+
content is incorrect, ambiguous, unexplainable, or cannot render reliably; a
|
|
198
|
+
`warning` is non-blocking but identifies a rule or usability problem that should
|
|
199
|
+
be repaired; `info` is optional advice that may be intentionally left as-is.
|
|
200
|
+
|
|
201
|
+
Run `pnpm --filter explico-playground content diagnostics info` to print every
|
|
202
|
+
diagnostic. Replace `info` with `warning` to show warnings and errors, or `error`
|
|
203
|
+
to show blockers only. `content warnings` remains the warning-only view, and
|
|
204
|
+
`--json` returns normalized data for tooling. VS Code exposes matching
|
|
205
|
+
**Explico: Show content diagnostics (info+)**, **(warning+)**, and **(errors)**
|
|
206
|
+
tasks; locatable findings populate the Problems panel at the reported line and
|
|
207
|
+
column.
|
|
208
|
+
|
|
195
209
|
The reference course is
|
|
196
210
|
[**credit-and-derivatives**](https://github.com/danieltuzes/credit-and-derivatives):
|
|
197
211
|
discounting → bonds → credit/CDS → derivatives and options.
|
package/dist/compiler/astro.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AstroIntegration } from 'astro';
|
|
1
|
+
import { AstroConfig, AstroIntegration } from 'astro';
|
|
2
2
|
import { CourseConfig } from './course-config.js';
|
|
3
3
|
import { Manifest } from './manifest.js';
|
|
4
4
|
import '../curriculum/validation.js';
|
|
@@ -25,6 +25,17 @@ declare function matchStandaloneDownload(pathname: string, base: string, lessonS
|
|
|
25
25
|
declare function assertLocalNotationParity(lessonId: string, authoredKeys: readonly string[], renderedKeys: readonly string[]): void;
|
|
26
26
|
/** Fail an Astro build if final rendered HTML contains an unusable trigger. */
|
|
27
27
|
declare function assertNotationPayloadHtml(page: string, html: string): void;
|
|
28
|
+
type MarkdownConfig = AstroConfig['markdown'];
|
|
29
|
+
/**
|
|
30
|
+
* Fail the build when a manifest-driven remark plugin has catalogued content
|
|
31
|
+
* but was never registered in Astro's markdown config — the "half-migrated"
|
|
32
|
+
* failure mode `docs/authoring/migrating.md` documents but, until now, never
|
|
33
|
+
* checked: `pnpm validate:content` stays green because the content-side gate
|
|
34
|
+
* (e.g. `gate-abbreviations.ts`'s N14 check) only scans prose, not Astro
|
|
35
|
+
* config, so a skipped plugin registration renders every trigger as plain
|
|
36
|
+
* text with no signal anywhere.
|
|
37
|
+
*/
|
|
38
|
+
declare function assertRemarkPluginsWired(manifest: Pick<Manifest, 'abbreviations' | 'sources' | 'notation'>, markdown: MarkdownConfig): void;
|
|
28
39
|
/**
|
|
29
40
|
* Restart Astro when an authored content file changes. Astro reloads MDX on
|
|
30
41
|
* its own, but Explico's manifest is created by `astro.config.mjs`; a restart
|
|
@@ -35,4 +46,4 @@ declare function assertNotationPayloadHtml(page: string, html: string): void;
|
|
|
35
46
|
*/
|
|
36
47
|
declare function explicoContentRefresh(courseConfig: CourseConfig, manifest: Manifest): AstroIntegration;
|
|
37
48
|
|
|
38
|
-
export { assertLocalNotationParity, assertNotationPayloadHtml, explicoContentRefresh, matchStandaloneDownload };
|
|
49
|
+
export { assertLocalNotationParity, assertNotationPayloadHtml, assertRemarkPluginsWired, explicoContentRefresh, matchStandaloneDownload };
|
package/dist/compiler/astro.js
CHANGED
|
@@ -9,6 +9,9 @@ import {
|
|
|
9
9
|
} from "./manifest.js";
|
|
10
10
|
import { missingNotationPayloadKeys } from "../reference/page-notation.js";
|
|
11
11
|
import { writeStandaloneLessonExports } from "./exports/standalone-html.js";
|
|
12
|
+
import remarkAbbreviations from "../reference/remark-abbreviations.mjs";
|
|
13
|
+
import remarkCitation from "../reference/remark-citation.mjs";
|
|
14
|
+
import remarkNotation from "../reference/remark-notation.mjs";
|
|
12
15
|
const watchedServers = /* @__PURE__ */ new WeakSet();
|
|
13
16
|
function normalizedBasePath(base) {
|
|
14
17
|
const segment = base.replace(/^\/+|\/+$/gu, "");
|
|
@@ -94,6 +97,40 @@ function assertNotationPayloadHtml(page, html) {
|
|
|
94
97
|
`Explico notation payload invariant failed for rendered page "${page}": ${missing.map((key) => `data-notation-key="${key}"`).join(", ")} ${missing.length === 1 ? "has" : "have"} no matching \`data-notation-entry\` popup definition. A nested Astro component that emits notation must register its formula dependencies; use Diagram, NotationFormula, or labMathScope(..., { locals: Astro.locals, context }).`
|
|
95
98
|
);
|
|
96
99
|
}
|
|
100
|
+
function configuredRemarkPlugins(markdown) {
|
|
101
|
+
const direct = markdown?.remarkPlugins ?? [];
|
|
102
|
+
const processorOptions = markdown?.processor?.options;
|
|
103
|
+
return [...direct, ...processorOptions?.remarkPlugins ?? []];
|
|
104
|
+
}
|
|
105
|
+
function remarkPluginIsWired(remarkPlugins, plugin) {
|
|
106
|
+
return remarkPlugins.some(
|
|
107
|
+
(entry) => entry === plugin || Array.isArray(entry) && entry[0] === plugin
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
function assertRemarkPluginsWired(manifest, markdown) {
|
|
111
|
+
const remarkPlugins = configuredRemarkPlugins(markdown);
|
|
112
|
+
const missing = [];
|
|
113
|
+
if (manifest.abbreviations.length > 0 && !remarkPluginIsWired(remarkPlugins, remarkAbbreviations)) {
|
|
114
|
+
missing.push(
|
|
115
|
+
`remarkAbbreviations is missing from markdown.remarkPlugins, but manifest.abbreviations has ${manifest.abbreviations.length} entr${manifest.abbreviations.length === 1 ? "y" : "ies"}. Import it from 'explico/reference/remark-abbreviations.mjs' and add [remarkAbbreviations, { abbreviations: manifest.abbreviations }] to markdown.remarkPlugins \u2014 see docs/authoring/migrating.md#4-rule-n14.`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
if (manifest.sources.length > 0 && !remarkPluginIsWired(remarkPlugins, remarkCitation)) {
|
|
119
|
+
missing.push(
|
|
120
|
+
`remarkCitation is missing from markdown.remarkPlugins, but manifest.sources has ${manifest.sources.length} entr${manifest.sources.length === 1 ? "y" : "ies"}. Import it from 'explico/reference/remark-citation.mjs' and add [remarkCitation, { sources: manifest.sources }] to markdown.remarkPlugins.`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
if (manifest.notation.raw.length > 0 && !remarkPluginIsWired(remarkPlugins, remarkNotation)) {
|
|
124
|
+
missing.push(
|
|
125
|
+
`remarkNotation is missing from markdown.remarkPlugins, but manifest.notation.raw has ${manifest.notation.raw.length} entr${manifest.notation.raw.length === 1 ? "y" : "ies"}. Import it from 'explico/reference/remark-notation.mjs' and add [remarkNotation, createRemarkNotationOptions(manifest)] to markdown.remarkPlugins.`
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
if (missing.length === 0) return;
|
|
129
|
+
throw new Error(
|
|
130
|
+
`Explico manifest-driven remark plugin${missing.length === 1 ? " is" : "s are"} not wired into Astro's markdown config:
|
|
131
|
+
${missing.map((line) => ` - ${line}`).join("\n")}`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
97
134
|
async function validateGeneratedNotationPayloads(directory) {
|
|
98
135
|
const root = fileURLToPath(directory);
|
|
99
136
|
const walk = async (path) => {
|
|
@@ -136,6 +173,7 @@ function explicoContentRefresh(courseConfig, manifest) {
|
|
|
136
173
|
name: "explico-content-refresh",
|
|
137
174
|
hooks: {
|
|
138
175
|
"astro:config:setup": ({ command, config }) => {
|
|
176
|
+
assertRemarkPluginsWired(manifest, config.markdown);
|
|
139
177
|
if (command === "dev") {
|
|
140
178
|
developmentConfig = { root: config.root, base: config.base };
|
|
141
179
|
}
|
|
@@ -253,6 +291,7 @@ ${message}
|
|
|
253
291
|
export {
|
|
254
292
|
assertLocalNotationParity,
|
|
255
293
|
assertNotationPayloadHtml,
|
|
294
|
+
assertRemarkPluginsWired,
|
|
256
295
|
explicoContentRefresh,
|
|
257
296
|
matchStandaloneDownload
|
|
258
297
|
};
|
package/dist/compiler/cli.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { DiagnosticSeverity } from '../reference/types.js';
|
|
1
2
|
import { Manifest, ManifestLesson } from './manifest.js';
|
|
2
3
|
import '../curriculum/validation.js';
|
|
3
4
|
import '../reference/consistency.js';
|
|
4
5
|
import './course-config.js';
|
|
5
6
|
import './collections.js';
|
|
6
|
-
import '../reference/types.js';
|
|
7
7
|
import '../reference/lint-ignore.js';
|
|
8
8
|
import '../reference/equations-validate.js';
|
|
9
9
|
import '../reference/tables-validate.js';
|
|
@@ -38,6 +38,9 @@ import './lesson-derivation.js';
|
|
|
38
38
|
* source metadata + locators, the section's domain signatures, and the
|
|
39
39
|
* lesson's current diagnostics — and reports token counts against the doc
|
|
40
40
|
* set + corpus so the size win is visible.
|
|
41
|
+
* - `content diagnostics [severity]` flattens every diagnostic category at
|
|
42
|
+
* or above a threshold into one stable, source-aware list for editors.
|
|
43
|
+
* - `content warnings` is the warning-only compatibility view.
|
|
41
44
|
* - `content check` compiles the lesson MDX → remark → KaTeX → HTML and fails
|
|
42
45
|
* on any renderability error (unparseable MDX, KaTeX error, unresolved
|
|
43
46
|
* notation, unknown `[[key]]`, unknown citation), on top of the manifest's
|
|
@@ -53,10 +56,11 @@ declare class ContentCliError extends Error {
|
|
|
53
56
|
/** One normalized diagnostic. `code` is `<source>:<kind>` and always stable. */
|
|
54
57
|
interface CliDiagnostic {
|
|
55
58
|
readonly code: string;
|
|
56
|
-
readonly severity:
|
|
59
|
+
readonly severity: DiagnosticSeverity;
|
|
57
60
|
readonly message: string;
|
|
58
61
|
readonly file?: string;
|
|
59
62
|
readonly line?: number;
|
|
63
|
+
readonly column?: number;
|
|
60
64
|
}
|
|
61
65
|
/**
|
|
62
66
|
* Every manifest diagnostic that names this lesson (by id or file), normalized
|
|
@@ -121,9 +125,24 @@ interface StatusReport {
|
|
|
121
125
|
readonly diagnostics: {
|
|
122
126
|
readonly errors: number;
|
|
123
127
|
readonly warnings: number;
|
|
128
|
+
readonly infos: number;
|
|
124
129
|
};
|
|
125
130
|
}
|
|
126
131
|
declare function contentStatus(manifest: Manifest): StatusReport;
|
|
132
|
+
type DiagnosticThreshold = DiagnosticSeverity;
|
|
133
|
+
interface DiagnosticsReport {
|
|
134
|
+
readonly minimumSeverity: DiagnosticThreshold;
|
|
135
|
+
readonly count: number;
|
|
136
|
+
readonly diagnostics: readonly CliDiagnostic[];
|
|
137
|
+
}
|
|
138
|
+
interface WarningsReport {
|
|
139
|
+
readonly count: number;
|
|
140
|
+
readonly warnings: readonly CliDiagnostic[];
|
|
141
|
+
}
|
|
142
|
+
/** Manifest diagnostics at or above a severity, normalized for tooling. */
|
|
143
|
+
declare function contentDiagnostics(manifest: Manifest, minimumSeverity?: DiagnosticThreshold): DiagnosticsReport;
|
|
144
|
+
/** Every warning exactly; errors and informational advice are excluded. */
|
|
145
|
+
declare function contentWarnings(manifest: Manifest): WarningsReport;
|
|
127
146
|
interface ContextReport {
|
|
128
147
|
readonly lesson: {
|
|
129
148
|
readonly id: string;
|
|
@@ -216,4 +235,4 @@ interface Scaffold {
|
|
|
216
235
|
declare function scaffoldLesson(rawId: string): Scaffold;
|
|
217
236
|
declare function scaffoldTerm(rawKey: string): Scaffold;
|
|
218
237
|
|
|
219
|
-
export { type CheckLessonResult, type CheckReport, type CliDiagnostic, type ContentCliCode, ContentCliError, type ContextReport, type DomainFileSignatures, type RenderResult, type Scaffold, type StatusReport, type TokenCounts, changedLessonIds, contentCheck, contentContext, contentStatus, estimateTokens, findLesson, lessonDiagnostics, normalizeLessonId, renderLesson, scaffoldLesson, scaffoldTerm };
|
|
238
|
+
export { type CheckLessonResult, type CheckReport, type CliDiagnostic, type ContentCliCode, ContentCliError, type ContextReport, type DiagnosticThreshold, type DiagnosticsReport, type DomainFileSignatures, type RenderResult, type Scaffold, type StatusReport, type TokenCounts, type WarningsReport, changedLessonIds, contentCheck, contentContext, contentDiagnostics, contentStatus, contentWarnings, estimateTokens, findLesson, lessonDiagnostics, normalizeLessonId, renderLesson, scaffoldLesson, scaffoldTerm };
|