explico 0.2.0 → 0.4.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 +173 -0
- package/README.md +67 -7
- package/dist/compiler/astro.d.ts +30 -0
- package/dist/compiler/astro.js +79 -0
- package/dist/compiler/cli.d.ts +16 -0
- package/dist/compiler/cli.js +43 -9
- package/dist/compiler/collections.js +4 -1
- package/dist/compiler/course-config.d.ts +80 -4
- package/dist/compiler/course-config.js +12 -0
- package/dist/compiler/knowledge-version.d.ts +58 -0
- package/dist/compiler/knowledge-version.js +80 -0
- package/dist/compiler/lesson-derivation.d.ts +1 -1
- package/dist/compiler/lesson-derivation.js +2 -2
- package/dist/compiler/manifest.d.ts +98 -2
- package/dist/compiler/manifest.js +153 -24
- package/dist/compiler/resolution-report.d.ts +4 -0
- package/dist/reference/base-operators.d.mts +38 -0
- package/dist/reference/base-operators.mjs +103 -0
- package/dist/reference/consistency.d.ts +7 -2
- package/dist/reference/consistency.js +51 -2
- package/dist/reference/diagram-layout.d.ts +63 -0
- package/dist/reference/diagram-layout.js +122 -0
- package/dist/reference/equations-validate.d.ts +10 -0
- package/dist/reference/equations-validate.js +1 -1
- package/dist/reference/equations.d.mts +80 -13
- package/dist/reference/equations.mjs +109 -19
- package/dist/reference/figures-validate.d.ts +21 -0
- package/dist/reference/figures-validate.js +13 -0
- package/dist/reference/keyed-float-validate.d.ts +73 -0
- package/dist/reference/keyed-float-validate.js +110 -0
- package/dist/reference/keyed-jsx-floats.d.mts +64 -0
- package/dist/reference/keyed-jsx-floats.mjs +191 -0
- package/dist/reference/math-glyphs.d.mts +18 -0
- package/dist/reference/math-glyphs.mjs +92 -0
- package/dist/reference/remark-notation.d.mts +61 -0
- package/dist/reference/remark-notation.mjs +344 -10
- package/dist/reference/tables-validate.d.ts +21 -0
- package/dist/reference/tables-validate.js +13 -0
- package/dist/reference/units-vocab.js +19 -1
- package/docs/VERSIONING.md +66 -0
- package/docs/authoring/README.md +100 -0
- package/docs/authoring/agents.md +63 -0
- package/docs/authoring/migrating.md +169 -0
- package/docs/authoring/notation.md +297 -0
- package/docs/authoring/structure.md +139 -0
- package/docs/authoring/versioning.md +116 -0
- package/docs/authoring/visualizations.md +195 -0
- package/package.json +20 -5
- package/src/compiler/astro.ts +127 -0
- package/src/compiler/cli.ts +52 -7
- package/src/compiler/collections.ts +12 -3
- package/src/compiler/course-config.ts +93 -3
- package/src/compiler/knowledge-version.ts +157 -0
- package/src/compiler/lesson-derivation.ts +2 -2
- package/src/compiler/manifest.ts +320 -26
- package/src/components/CurriculumMap.astro +27 -0
- package/src/components/diagram/Diagram.astro +317 -0
- package/src/components/examples/CompactExamples.astro +1 -1
- package/src/components/figure/Figure.astro +104 -0
- package/src/components/notation/NotationLayer.astro +11 -0
- package/src/components/notation/render-notation-math.ts +13 -2
- package/src/components/reference/FloatRefLayer.astro +671 -0
- package/src/components/starlight/AboutPanel.astro +255 -0
- package/src/components/starlight/HeadingAnchorEnhancer.astro +3 -3
- package/src/components/starlight/KeyedFloatEnhancer.astro +137 -0
- package/src/components/starlight/LayoutSidebar.astro +62 -1
- package/src/components/starlight/LayoutSocialIcons.astro +12 -0
- package/src/components/starlight/LessonFooter.astro +15 -2
- package/src/components/table/Table.astro +105 -0
- package/src/reference/base-operators.mjs +103 -0
- package/src/reference/consistency.ts +83 -2
- package/src/reference/diagram-layout.ts +246 -0
- package/src/reference/equations-validate.ts +8 -1
- package/src/reference/equations.mjs +109 -19
- package/src/reference/figures-validate.ts +40 -0
- package/src/reference/keyed-float-validate.ts +210 -0
- package/src/reference/keyed-jsx-floats.mjs +191 -0
- package/src/reference/math-glyphs.mjs +92 -0
- package/src/reference/remark-notation.mjs +344 -10
- package/src/reference/tables-validate.ts +40 -0
- package/src/reference/units-vocab.ts +18 -0
- package/src/styles/global.css +244 -7
- package/src/components/starlight/EquationEnhancer.astro +0 -87
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to the **`explico` engine** are recorded here. The installed
|
|
4
|
+
`package.json` `version` is the single source of truth for what you have; this
|
|
5
|
+
file says what changed between versions and [`docs/VERSIONING.md`](docs/VERSIONING.md)
|
|
6
|
+
says what a version bump means.
|
|
7
|
+
|
|
8
|
+
A course built on explico keeps its **own** knowledge version — see the course's
|
|
9
|
+
`content/CHANGELOG.md` and [`docs/authoring/versioning.md`](docs/authoring/versioning.md).
|
|
10
|
+
|
|
11
|
+
Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
12
|
+
This project is pre-1.0: a MINOR bump may carry a breaking change, always listed
|
|
13
|
+
under **Breaking** below; a PATCH never breaks.
|
|
14
|
+
|
|
15
|
+
## [Unreleased]
|
|
16
|
+
|
|
17
|
+
## [0.4.0] - 2026-09-09
|
|
18
|
+
|
|
19
|
+
### Breaking
|
|
20
|
+
|
|
21
|
+
- **Equation, table, and figure numbers are now `‹part›.‹n›`, not
|
|
22
|
+
`‹lesson›.‹n›`.** A *part* is a sidebar group; `n` counts that kind of float
|
|
23
|
+
across the whole part, running unbroken from one chapter into the next.
|
|
24
|
+
Previously the prefix was the lesson's ordinal down the entire course and `n`
|
|
25
|
+
restarted on every page, so a sixteen-lesson course had sixteen prefixes.
|
|
26
|
+
Every number in an existing course changes. Nothing an author wrote changes —
|
|
27
|
+
numbers were never hand-typed — but a published link of the form `#eq-2-4`
|
|
28
|
+
(the positional anchor of an *unkeyed* equation) moves; a keyed `#eq-<key>`
|
|
29
|
+
anchor is unaffected. Manifest schema is now **v7**: `lessons[].part` is new,
|
|
30
|
+
`lessons[].chapter` is redefined as the lesson's ordinal *within its part*,
|
|
31
|
+
and `lessons[].numbering` carries the per-kind running counts.
|
|
32
|
+
- **`courseConfig.sections` is renamed `courseConfig.parts`.** "Section" now
|
|
33
|
+
only ever means an `##` heading inside a lesson. The old name still works and
|
|
34
|
+
is removed in the next MINOR release; `CourseSection` is a deprecated alias
|
|
35
|
+
for `CoursePart`.
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
|
|
39
|
+
- [`docs/authoring/structure.md`](docs/authoring/structure.md) — the structural
|
|
40
|
+
taxonomy the rest of the docs speak in: course → part → chapter → section →
|
|
41
|
+
subsection, which levels a number counts, and why the numbering invariant is
|
|
42
|
+
build-enforced. Two levels were previously both called "section", and two
|
|
43
|
+
were both called "chapter".
|
|
44
|
+
- `remarkNotation` accepts a `manifest` option that supplies every numbering
|
|
45
|
+
input at once — `equations`, `tables`, `figures`, and the part/offset
|
|
46
|
+
placement map, all read off the same compiled manifest. This is now the recommended wiring in
|
|
47
|
+
`astro.config.mjs`; the individual options remain, and an explicit one still
|
|
48
|
+
wins over the manifest. `numberingFromManifest(manifest)` is exported for
|
|
49
|
+
callers that need the derived object on its own.
|
|
50
|
+
- `explicoContentRefresh(courseConfig, manifest)` recompiles the manifest and
|
|
51
|
+
restarts Astro development after any `content/` change, independent of the
|
|
52
|
+
editor making it.
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
|
|
56
|
+
- **Equation, table, and figure numbers no longer disagree between a page and
|
|
57
|
+
the references to it (WERN).** A course that passed the manifest number tables
|
|
58
|
+
to `remarkNotation` without the placement map silently numbered every page by
|
|
59
|
+
`##` section — restarting at `1.1` on each page — while cross-page references
|
|
60
|
+
kept reading the manifest, so one equation was `(2.2)` on its own page and
|
|
61
|
+
`(4.5)` everywhere else. `remarkNotation` now cross-checks the number
|
|
62
|
+
it computes for each keyed equation / table / figure against the manifest
|
|
63
|
+
number for the same key and fails the build on any disagreement, naming the
|
|
64
|
+
key, both numbers, and the missing placement. The same omission was
|
|
65
|
+
present in `explico content check`, which therefore validated lessons under a
|
|
66
|
+
numbering scheme the real build never used; it now shares the manifest wiring.
|
|
67
|
+
|
|
68
|
+
### Changed
|
|
69
|
+
|
|
70
|
+
- Parts in the left sidebar now start collapsed, retain compact chapter icons
|
|
71
|
+
with hover/focus labels, and expand to the full chapter names when the part
|
|
72
|
+
title is opened. (Previously described as "chapter groups" with "subchapter"
|
|
73
|
+
icons — the same UI, named per the taxonomy.)
|
|
74
|
+
|
|
75
|
+
### Fixed
|
|
76
|
+
|
|
77
|
+
- `loadManifest(courseConfig)` now always compiles current course content, and
|
|
78
|
+
`LessonFooter` rejects local-notation key drift. Current math hover markers can
|
|
79
|
+
no longer be silently paired with stale cards from a persisted manifest.
|
|
80
|
+
- Notation hover markers inside `\sqrt{…}` (and under an extensible arrow such
|
|
81
|
+
as `\xrightarrow`) are reachable again: KaTeX paints the radical/arrow SVG as
|
|
82
|
+
an absolutely-positioned overlay on top of the content it decorates, which
|
|
83
|
+
was swallowing the pointer. `NotationLayer` now sets `pointer-events: none`
|
|
84
|
+
on that overlay layer, matching the existing fraction/subscript workaround.
|
|
85
|
+
|
|
86
|
+
## [0.3.0] - 2026-09-05
|
|
87
|
+
|
|
88
|
+
### Breaking
|
|
89
|
+
|
|
90
|
+
- `CourseConfig` now requires an `about` block (`materialName?`, `summary?`,
|
|
91
|
+
`version`, `authors`, `disclaimer`, `changelogHref?`). It carries the course's
|
|
92
|
+
reader-facing identity and its **knowledge version**. Add one to
|
|
93
|
+
`content/course.config.ts`.
|
|
94
|
+
- Manifest gains `manifest.about` (engine identity + the course `about` block +
|
|
95
|
+
the knowledge version resolved from `content/CHANGELOG.md`) and
|
|
96
|
+
`manifest.diagnostics.knowledge`; `schemaVersion` bumped. Additive — existing
|
|
97
|
+
fields are unchanged.
|
|
98
|
+
- `EquationRefLayer` / `EquationEnhancer` are replaced by `FloatRefLayer` +
|
|
99
|
+
`KeyedFloatEnhancer`, one hover/pin/jump engine shared by equation, table, and
|
|
100
|
+
figure references. Update the wiring in your Starlight `Footer` override.
|
|
101
|
+
|
|
102
|
+
### Added
|
|
103
|
+
|
|
104
|
+
- **Figures and tables as first-class keyed floats.** New `Figure` and `Table`
|
|
105
|
+
components with appearance-order numbering, `#fig-<id>` / `#tab-<id>` anchors,
|
|
106
|
+
and `[[fig-<id>]]` / `[[tab-<id>]]` cross-references that highlight the target
|
|
107
|
+
when it is on screen and preview it when it is not — the same model equations
|
|
108
|
+
already had. New build gates `reference/figures-validate.ts`,
|
|
109
|
+
`reference/tables-validate.ts`, `reference/keyed-float-validate.ts`, and the
|
|
110
|
+
`reference/keyed-jsx-floats.mjs` remark pass.
|
|
111
|
+
- **About `[?]` panel.** `components/starlight/AboutPanel.astro` +
|
|
112
|
+
`LayoutSocialIcons.astro` render a header control, on every route, showing the
|
|
113
|
+
material name and summary, the knowledge version and date, the engine version,
|
|
114
|
+
the authors and contact, the disclaimer, and a link to the content changelog.
|
|
115
|
+
Wire it with `components: { SocialIcons: 'explico/components/starlight/LayoutSocialIcons.astro' }`.
|
|
116
|
+
- **Knowledge versioning.** `compiler/knowledge-version.ts` parses
|
|
117
|
+
`content/CHANGELOG.md` and checks it against `about.version`; drift is a Tier-3
|
|
118
|
+
`knowledge-version-mismatch` / `knowledge-changelog-missing` /
|
|
119
|
+
`knowledge-version-invalid` warning (never fails the build). Governed by new
|
|
120
|
+
authoring area **C** (`docs/authoring/versioning.md`, rules C1–C4).
|
|
121
|
+
- `Diagram` component (Dagre + build-time KaTeX), with an `LR` direction and an
|
|
122
|
+
optional hand-written `description` for the text fallback.
|
|
123
|
+
- Notation engine: a base **operator** library (`reference/base-operators.mjs`)
|
|
124
|
+
alongside the base glyph library, wider automatic glyph resolution in
|
|
125
|
+
`reference/math-glyphs.mjs`, and `\explain{key}{latex}` support inside notation
|
|
126
|
+
`formula:` fields and `Diagram` node formulas.
|
|
127
|
+
- `content status` now reports the course's knowledge version.
|
|
128
|
+
- Course-author docs in the published package: `docs/authoring/agents.md`
|
|
129
|
+
(working on a course repo with an AI agent) and `docs/authoring/migrating.md`
|
|
130
|
+
(per-version upgrade steps). The published `files` are `dist`, `src`,
|
|
131
|
+
`README.md`, `CHANGELOG.md`, `docs/VERSIONING.md`, `docs/authoring/` — the
|
|
132
|
+
repo's own `AGENTS.md` is engine-development guidance and is **no longer
|
|
133
|
+
shipped** (it described `src/` internals a consumer does not have).
|
|
134
|
+
- `compileManifest` throws a named error, pointing at `migrating.md`, when a
|
|
135
|
+
course upgrading from 0.2.x has no `about` block — instead of a bare
|
|
136
|
+
`Cannot read properties of undefined`.
|
|
137
|
+
- `scripts/agent/` (`verify`, `fresh`, `shot`) and a generated, staleness-gated
|
|
138
|
+
repo map in the repo's `AGENTS.md` (`scripts/agent/gen-map.mjs`) — repo tooling,
|
|
139
|
+
not part of the package.
|
|
140
|
+
|
|
141
|
+
### Changed
|
|
142
|
+
|
|
143
|
+
- Deep-linkable heading anchors (`rehype-heading-anchors` +
|
|
144
|
+
`HeadingAnchorEnhancer`) and numbered lesson equations are on by default.
|
|
145
|
+
- `remark-notation` and `consistency` gained substantial coverage for the
|
|
146
|
+
keyed-float syntax, operator resolution, and `\explain` handling.
|
|
147
|
+
|
|
148
|
+
## [0.2.0] - 2026-09-04
|
|
149
|
+
|
|
150
|
+
- Two-tier notation vocabulary: named operators (rule N8) are a Tier-3
|
|
151
|
+
`operator-explained` warning against a shipped baseline plus a per-course
|
|
152
|
+
`operators.allow` / `operators.deny` list, softer than the identifier gate.
|
|
153
|
+
- Heading deep links: `rehype-heading-anchors` + `HeadingAnchorEnhancer.astro`.
|
|
154
|
+
- `popup-stack.ts` — nested, independently positioned notation popups.
|
|
155
|
+
|
|
156
|
+
## [0.1.1] - 2026-09-03
|
|
157
|
+
|
|
158
|
+
- Make the published build load under Node ESM (`.mjs` extension fixups, `dist/`
|
|
159
|
+
resolution).
|
|
160
|
+
|
|
161
|
+
## [0.1.0] - 2026-09-03
|
|
162
|
+
|
|
163
|
+
- Initial import of the explico engine, extracted from the `equations` project:
|
|
164
|
+
the one manifest compiler, the notation / citation / equation reference
|
|
165
|
+
system with its completeness gate, the curriculum-graph validator, the
|
|
166
|
+
Astro/Starlight UI, the LMS / session / analytics seams, and the `content` CLI.
|
|
167
|
+
|
|
168
|
+
[Unreleased]: https://github.com/danieltuzes/explico/compare/v0.4.0...HEAD
|
|
169
|
+
[0.4.0]: https://github.com/danieltuzes/explico/compare/v0.3.0...v0.4.0
|
|
170
|
+
[0.3.0]: https://github.com/danieltuzes/explico/compare/v0.2.0...v0.3.0
|
|
171
|
+
[0.2.0]: https://github.com/danieltuzes/explico/compare/v0.1.1...v0.2.0
|
|
172
|
+
[0.1.1]: https://github.com/danieltuzes/explico/compare/v0.1.0...v0.1.1
|
|
173
|
+
[0.1.0]: https://github.com/danieltuzes/explico/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -14,7 +14,10 @@ What it gives a course:
|
|
|
14
14
|
meaning; a **completeness gate** fails the build if any identifier in any
|
|
15
15
|
rendered-math context (lesson body, `notation.formula`, `checks.yml`,
|
|
16
16
|
assessment prompts, component slots) does not resolve to a key. One meaning
|
|
17
|
-
per glyph per page, enforced corpus-wide.
|
|
17
|
+
per glyph per page, enforced corpus-wide. Named operators get the same
|
|
18
|
+
treatment one tier softer: a lesson's `\operatorname{Var}` is a warning
|
|
19
|
+
(`operator-explained`) until it carries an entry or the course lists it in
|
|
20
|
+
`operators.allow`; a shipped baseline (`\exp`, `\int`, trig, …) is exempt.
|
|
18
21
|
- **Inline citations** — `[@source-id; locator]` → numbered marker + generated
|
|
19
22
|
reference list, resolved against a schema-checked source collection.
|
|
20
23
|
- **Equation identity** — `\label{eq:key}` gives a display equation a stable
|
|
@@ -28,6 +31,14 @@ What it gives a course:
|
|
|
28
31
|
- **Shared Astro/Starlight UI** — a hover-panel primitive shared by the
|
|
29
32
|
notation and citation layers, a glossary, a curriculum map, compact examples,
|
|
30
33
|
layout overrides.
|
|
34
|
+
- **Interactive figures, with room to grow** — a `labMathScope`/`renderLabMath`
|
|
35
|
+
seam compiles a `\slot{}`-templated formula against the page's real notation
|
|
36
|
+
at build time, so a client-side island only ever patches numbers into
|
|
37
|
+
pre-rendered KaTeX. Hand-rolled SVG is the default chart (zero dependency,
|
|
38
|
+
covers a single custom figure); reach for D3 once a figure needs real
|
|
39
|
+
tick-labeled axes, log scales, or several series sharing a scale; Observable
|
|
40
|
+
Plot pays off once a course is authoring many charts per lecture. Pick per
|
|
41
|
+
figure — nothing in the engine assumes one plotting approach.
|
|
31
42
|
- **LMS / analytics / identity seams** — framework-free interfaces shipped as
|
|
32
43
|
no-ops; a host supplies real implementations without touching a component.
|
|
33
44
|
- **A `content` CLI** — `status`, `context <lesson>`, `check <lesson>` (compiles
|
|
@@ -58,6 +69,7 @@ export { collections } from 'explico/content-config';
|
|
|
58
69
|
|
|
59
70
|
```js
|
|
60
71
|
// astro.config.mjs
|
|
72
|
+
import { explicoContentRefresh } from 'explico/compiler/astro.js';
|
|
61
73
|
import { loadManifest } from 'explico/compiler/manifest.js';
|
|
62
74
|
import remarkNotation from 'explico/reference/remark-notation.mjs';
|
|
63
75
|
import remarkCitation from 'explico/reference/remark-citation.mjs';
|
|
@@ -66,11 +78,30 @@ import rehypeFailKatexErrors from 'explico/reference/rehype-fail-katex-errors.mj
|
|
|
66
78
|
import { courseConfig } from './content/course.config.ts';
|
|
67
79
|
|
|
68
80
|
const manifest = await loadManifest(courseConfig);
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
//
|
|
81
|
+
// markdown.remarkPlugins: [
|
|
82
|
+
// remarkMath,
|
|
83
|
+
// // One `manifest` carries every numbering input — the equation / table /
|
|
84
|
+
// // figure number tables *and* the part each lesson sits in. Pass the
|
|
85
|
+
// // tables without the placement and the page numbers its equations by `##`
|
|
86
|
+
// // section while references to it read the part scheme, so the same
|
|
87
|
+
// // equation reads (2.2) at home and (4.5) abroad; the plugin rejects that
|
|
88
|
+
// // at build time rather than shipping it.
|
|
89
|
+
// [remarkNotation, { definitions: manifest.notation.raw, manifest }],
|
|
90
|
+
// [remarkCitation, { sources: manifest.sources }],
|
|
91
|
+
// ]
|
|
92
|
+
// … integrations: [explicoContentRefresh(courseConfig, manifest),
|
|
93
|
+
// starlight({ customCss: ['explico/styles/global.css'],
|
|
94
|
+
// components: { Footer: 'explico/components/starlight/LessonFooter.astro',
|
|
95
|
+
// SocialIcons: 'explico/components/starlight/LayoutSocialIcons.astro', … } })
|
|
96
|
+
// … rehypePlugins: [[rehypeKatex, createNotationKatexOptions()], rehypeFailKatexErrors]
|
|
72
97
|
```
|
|
73
98
|
|
|
99
|
+
`loadManifest(courseConfig)` always recompiles current content before Astro
|
|
100
|
+
starts. `explicoContentRefresh(courseConfig, manifest)` recompiles the manifest
|
|
101
|
+
and restarts the development server when any file under `content/` is added,
|
|
102
|
+
changed, or removed, keeping Markdown transforms and UI components on the same
|
|
103
|
+
model without an editor-specific task.
|
|
104
|
+
|
|
74
105
|
```jsonc
|
|
75
106
|
// package.json — the read-side CLI
|
|
76
107
|
"scripts": { "validate:content": "tsx scripts/compile-manifest.ts" }
|
|
@@ -92,6 +123,19 @@ discounting → bonds → credit/CDS → derivatives and options.
|
|
|
92
123
|
| `src/content-config.ts` | the Zod collection schemas (Astro re-exports them) |
|
|
93
124
|
| `src/styles/` | `global.css` (notation panel, keyed-equation flash, KaTeX) |
|
|
94
125
|
|
|
126
|
+
## Authoring
|
|
127
|
+
|
|
128
|
+
Course content follows the rules in [`docs/authoring/`](docs/authoring/) —
|
|
129
|
+
strong recommendations for human and AI authors covering notation spelling
|
|
130
|
+
(`\mathcal{N}` for the normal, `\mathbb{P}` for probability, upright `\mathrm{d}`
|
|
131
|
+
for the differential, …), which library draws a diagram or
|
|
132
|
+
chart (`Diagram` for graphs; SVG → D3 → Observable Plot for charts), how
|
|
133
|
+
randomised interactive figures pick their axes, and how the course's knowledge
|
|
134
|
+
version and `content/CHANGELOG.md` move with a content change (area `C`,
|
|
135
|
+
surfaced to readers in the About `[?]` panel).
|
|
136
|
+
[`docs/authoring/README.md`](docs/authoring/README.md) is the governance: rule
|
|
137
|
+
IDs, enforcement levels, and how to amend.
|
|
138
|
+
|
|
95
139
|
## Build
|
|
96
140
|
|
|
97
141
|
```bash
|
|
@@ -102,7 +146,23 @@ pnpm build # tsup → dist/ (ESM + .d.ts) for the .ts core; .astro / .tsx / .c
|
|
|
102
146
|
Published `exports` resolve to `dist/` for the typed core and to `src/` for the
|
|
103
147
|
Astro components and styles (see `publishConfig` in `package.json`).
|
|
104
148
|
|
|
105
|
-
##
|
|
149
|
+
## Versioning
|
|
150
|
+
|
|
151
|
+
The engine follows [semver](https://semver.org). The **golden source** for the
|
|
152
|
+
version is the installed `package.json` (`explico`'s own `version`); the About
|
|
153
|
+
`[?]` panel reads it at build time and the docs never repeat a number.
|
|
154
|
+
|
|
155
|
+
- [`CHANGELOG.md`](CHANGELOG.md) — what changed between engine versions.
|
|
156
|
+
- [`docs/VERSIONING.md`](docs/VERSIONING.md) — what a MAJOR / MINOR / PATCH bump
|
|
157
|
+
means here, the public surface it is measured against, and the pre-1.0 clause.
|
|
158
|
+
|
|
159
|
+
A course built on explico carries an **independent knowledge version** over its
|
|
160
|
+
content — `about.version` in `content/course.config.ts`, tracked in the course's
|
|
161
|
+
`content/CHANGELOG.md`, surfaced to readers in the same `[?]` panel. See
|
|
162
|
+
[`docs/authoring/versioning.md`](docs/authoring/versioning.md).
|
|
163
|
+
|
|
164
|
+
For AI authors: [`AGENTS.md`](AGENTS.md) says which changelog and which version a
|
|
165
|
+
given change updates.
|
|
106
166
|
|
|
107
|
-
|
|
108
|
-
license is undecided (`UNLICENSED` until chosen).
|
|
167
|
+
Status: extracted from the `equations` project; API not yet stable (pre-1.0);
|
|
168
|
+
the license is undecided (`UNLICENSED` until chosen).
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { AstroIntegration } from 'astro';
|
|
2
|
+
import { CourseConfig } from './course-config.js';
|
|
3
|
+
import { Manifest } from './manifest.js';
|
|
4
|
+
import '../curriculum/validation.js';
|
|
5
|
+
import '../reference/consistency.js';
|
|
6
|
+
import './collections.js';
|
|
7
|
+
import '../reference/types.js';
|
|
8
|
+
import '../reference/lint-ignore.js';
|
|
9
|
+
import '../reference/equations-validate.js';
|
|
10
|
+
import '../reference/tables-validate.js';
|
|
11
|
+
import '../reference/keyed-float-validate.js';
|
|
12
|
+
import '../reference/figures-validate.js';
|
|
13
|
+
import '../reference/gate-math.js';
|
|
14
|
+
import '../reference/gloss.js';
|
|
15
|
+
import './knowledge-version.js';
|
|
16
|
+
import './lesson-derivation.js';
|
|
17
|
+
|
|
18
|
+
/** Fail loudly instead of rendering math triggers whose local cards are stale. */
|
|
19
|
+
declare function assertLocalNotationParity(lessonId: string, authoredKeys: readonly string[], renderedKeys: readonly string[]): void;
|
|
20
|
+
/**
|
|
21
|
+
* Restart Astro when an authored content file changes. Astro reloads MDX on
|
|
22
|
+
* its own, but Explico's manifest is created by `astro.config.mjs`; a restart
|
|
23
|
+
* is what makes the remark plugins and page UI consume the same fresh model.
|
|
24
|
+
*
|
|
25
|
+
* Add this integration once to `integrations` in `astro.config.mjs`. The
|
|
26
|
+
* restart is automatic and editor-agnostic (change/add/unlink all work).
|
|
27
|
+
*/
|
|
28
|
+
declare function explicoContentRefresh(courseConfig: CourseConfig, manifest: Manifest): AstroIntegration;
|
|
29
|
+
|
|
30
|
+
export { assertLocalNotationParity, explicoContentRefresh };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { resolve, sep } from "node:path";
|
|
2
|
+
import {
|
|
3
|
+
compileManifest,
|
|
4
|
+
writeManifest
|
|
5
|
+
} from "./manifest.js";
|
|
6
|
+
const watchedServers = /* @__PURE__ */ new WeakSet();
|
|
7
|
+
function assertLocalNotationParity(lessonId, authoredKeys, renderedKeys) {
|
|
8
|
+
const authored = [...new Set(authoredKeys)].sort();
|
|
9
|
+
const rendered = [...new Set(renderedKeys)].sort();
|
|
10
|
+
if (authored.length === rendered.length && authored.every((key, index) => key === rendered[index])) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
throw new Error(
|
|
14
|
+
`Stale Explico manifest for ${lessonId}: lesson notation.local has [${authored.join(", ")}], but the page notation bundle has [${rendered.join(", ")}]. Pass courseConfig to loadManifest() and add explicoContentRefresh() to the Astro integrations.`
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
function replaceInPlace(target, source) {
|
|
18
|
+
if (Array.isArray(target) && Array.isArray(source)) {
|
|
19
|
+
target.splice(0, target.length, ...source);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (!target || !source || typeof target !== "object" || typeof source !== "object") {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const mutable = target;
|
|
26
|
+
const fresh = source;
|
|
27
|
+
for (const key of Object.keys(mutable)) {
|
|
28
|
+
if (!(key in fresh)) delete mutable[key];
|
|
29
|
+
}
|
|
30
|
+
for (const [key, value] of Object.entries(fresh)) {
|
|
31
|
+
const current = mutable[key];
|
|
32
|
+
if (Array.isArray(current) && Array.isArray(value) || current && value && typeof current === "object" && typeof value === "object" && !Array.isArray(current) && !Array.isArray(value)) {
|
|
33
|
+
replaceInPlace(current, value);
|
|
34
|
+
} else {
|
|
35
|
+
mutable[key] = value;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function explicoContentRefresh(courseConfig, manifest) {
|
|
40
|
+
return {
|
|
41
|
+
name: "explico-content-refresh",
|
|
42
|
+
hooks: {
|
|
43
|
+
"astro:server:setup": ({ server }) => {
|
|
44
|
+
if (watchedServers.has(server)) return;
|
|
45
|
+
watchedServers.add(server);
|
|
46
|
+
const contentRoot = `${resolve(process.cwd(), "content")}${sep}`;
|
|
47
|
+
let timer;
|
|
48
|
+
let restarting = false;
|
|
49
|
+
const refresh = (path) => {
|
|
50
|
+
const absolute = resolve(path);
|
|
51
|
+
if (!absolute.startsWith(contentRoot) || restarting) return;
|
|
52
|
+
if (timer) clearTimeout(timer);
|
|
53
|
+
timer = setTimeout(async () => {
|
|
54
|
+
restarting = true;
|
|
55
|
+
try {
|
|
56
|
+
const fresh = await compileManifest(courseConfig);
|
|
57
|
+
writeManifest(fresh);
|
|
58
|
+
replaceInPlace(manifest, fresh);
|
|
59
|
+
await server.restart();
|
|
60
|
+
} catch (error) {
|
|
61
|
+
server.config.logger.error(
|
|
62
|
+
`Explico could not refresh the content manifest: ${error instanceof Error ? error.message : String(error)}`
|
|
63
|
+
);
|
|
64
|
+
} finally {
|
|
65
|
+
restarting = false;
|
|
66
|
+
}
|
|
67
|
+
}, 50);
|
|
68
|
+
};
|
|
69
|
+
server.watcher.on("add", refresh);
|
|
70
|
+
server.watcher.on("change", refresh);
|
|
71
|
+
server.watcher.on("unlink", refresh);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export {
|
|
77
|
+
assertLocalNotationParity,
|
|
78
|
+
explicoContentRefresh
|
|
79
|
+
};
|
package/dist/compiler/cli.d.ts
CHANGED
|
@@ -6,8 +6,12 @@ import './collections.js';
|
|
|
6
6
|
import '../reference/types.js';
|
|
7
7
|
import '../reference/lint-ignore.js';
|
|
8
8
|
import '../reference/equations-validate.js';
|
|
9
|
+
import '../reference/tables-validate.js';
|
|
10
|
+
import '../reference/keyed-float-validate.js';
|
|
11
|
+
import '../reference/figures-validate.js';
|
|
9
12
|
import '../reference/gate-math.js';
|
|
10
13
|
import '../reference/gloss.js';
|
|
14
|
+
import './knowledge-version.js';
|
|
11
15
|
import './lesson-derivation.js';
|
|
12
16
|
|
|
13
17
|
/**
|
|
@@ -91,6 +95,8 @@ interface DomainFileSignatures {
|
|
|
91
95
|
readonly signatures: readonly string[];
|
|
92
96
|
}
|
|
93
97
|
interface StatusReport {
|
|
98
|
+
/** The course's published knowledge version (`about.version`). */
|
|
99
|
+
readonly knowledgeVersion: string;
|
|
94
100
|
readonly counts: {
|
|
95
101
|
readonly lessons: number;
|
|
96
102
|
readonly competencies: number;
|
|
@@ -99,6 +105,8 @@ interface StatusReport {
|
|
|
99
105
|
readonly tracks: number;
|
|
100
106
|
readonly notationDefinitions: number;
|
|
101
107
|
readonly keyedEquations: number;
|
|
108
|
+
readonly keyedTables: number;
|
|
109
|
+
readonly keyedFigures: number;
|
|
102
110
|
};
|
|
103
111
|
readonly draftDebt: {
|
|
104
112
|
readonly lessons: readonly string[];
|
|
@@ -145,6 +153,14 @@ interface ContextReport {
|
|
|
145
153
|
key: string;
|
|
146
154
|
number: string;
|
|
147
155
|
}[];
|
|
156
|
+
readonly tables: readonly {
|
|
157
|
+
key: string;
|
|
158
|
+
number: string;
|
|
159
|
+
}[];
|
|
160
|
+
readonly figures: readonly {
|
|
161
|
+
key: string;
|
|
162
|
+
number: string;
|
|
163
|
+
}[];
|
|
148
164
|
readonly sources: readonly {
|
|
149
165
|
id: string;
|
|
150
166
|
title: string;
|
package/dist/compiler/cli.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
} from "@astrojs/markdown-remark";
|
|
9
9
|
import rehypeKatex from "rehype-katex";
|
|
10
10
|
import remarkMath from "remark-math";
|
|
11
|
+
import remarkMdx from "remark-mdx";
|
|
11
12
|
import { createNotationKatexOptions } from "../reference/katex-options.mjs";
|
|
12
13
|
import rehypeFailKatexErrors from "../reference/rehype-fail-katex-errors.mjs";
|
|
13
14
|
import remarkCitation from "../reference/remark-citation.mjs";
|
|
@@ -81,6 +82,20 @@ function lessonDiagnostics(manifest, lesson) {
|
|
|
81
82
|
...withFile(diagnostic.file)
|
|
82
83
|
});
|
|
83
84
|
}
|
|
85
|
+
for (const diagnostic of [
|
|
86
|
+
...manifest.diagnostics.tables,
|
|
87
|
+
...manifest.diagnostics.figures
|
|
88
|
+
]) {
|
|
89
|
+
if (diagnostic.lessonId !== id && diagnostic.file !== file && !mentions(diagnostic.message, id, file)) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
out.push({
|
|
93
|
+
code: diagnostic.code,
|
|
94
|
+
severity: diagnostic.severity === "warning" ? "warning" : "error",
|
|
95
|
+
message: diagnostic.message,
|
|
96
|
+
...withFile(diagnostic.file)
|
|
97
|
+
});
|
|
98
|
+
}
|
|
84
99
|
for (const diagnostic of manifest.diagnostics.consistency) {
|
|
85
100
|
if (diagnostic.file !== file && diagnostic.lessonId !== id) continue;
|
|
86
101
|
out.push({
|
|
@@ -100,14 +115,17 @@ function processorFor(manifest) {
|
|
|
100
115
|
syntaxHighlight: false,
|
|
101
116
|
smartypants: false,
|
|
102
117
|
remarkPlugins: [
|
|
118
|
+
// Syntax only (no JSX evaluation) — teaches the parser `<Table>` /
|
|
119
|
+
// `<Figure>` / `<CompactExample>` are `mdxJsxFlowElement` nodes, same
|
|
120
|
+
// as the real Astro/MDX build, so `remarkNotation` sees the same tree
|
|
121
|
+
// shape here that it does there (D7/F-tables-figures parity).
|
|
122
|
+
remarkMdx,
|
|
103
123
|
remarkMath,
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
],
|
|
124
|
+
// `manifest` carries the number tables *and* the part/offset placement
|
|
125
|
+
// together, so this check numbers a page exactly as the Astro build
|
|
126
|
+
// does. Passing the tables alone silently numbered pages by `##` section
|
|
127
|
+
// here while cross-page references read the part-scheme manifest.
|
|
128
|
+
[remarkNotation, { definitions: manifest.notation.raw, manifest }],
|
|
111
129
|
[remarkCitation, { sources: manifest.sources }]
|
|
112
130
|
],
|
|
113
131
|
rehypePlugins: [
|
|
@@ -279,16 +297,28 @@ function contentStatus(manifest) {
|
|
|
279
297
|
...manifest.diagnostics.alignment.map((d) => d.severity),
|
|
280
298
|
...manifest.diagnostics.math.map((d) => d.severity),
|
|
281
299
|
...manifest.diagnostics.equations.map((d) => d.severity),
|
|
282
|
-
...manifest.diagnostics.
|
|
300
|
+
...manifest.diagnostics.tables.map((d) => d.severity),
|
|
301
|
+
...manifest.diagnostics.figures.map((d) => d.severity),
|
|
302
|
+
...manifest.diagnostics.consistency.map(() => "warning"),
|
|
303
|
+
...manifest.diagnostics.knowledge.map((d) => d.severity)
|
|
283
304
|
];
|
|
284
305
|
const keyedEquations = manifest.lessons.reduce(
|
|
285
306
|
(count, lesson) => count + lesson.equations.length,
|
|
286
307
|
0
|
|
287
308
|
);
|
|
309
|
+
const keyedTables = manifest.lessons.reduce(
|
|
310
|
+
(count, lesson) => count + lesson.tables.length,
|
|
311
|
+
0
|
|
312
|
+
);
|
|
313
|
+
const keyedFigures = manifest.lessons.reduce(
|
|
314
|
+
(count, lesson) => count + lesson.figures.length,
|
|
315
|
+
0
|
|
316
|
+
);
|
|
288
317
|
const assessments = new Set(
|
|
289
318
|
manifest.lessons.flatMap((lesson) => lesson.assessments)
|
|
290
319
|
).size;
|
|
291
320
|
return {
|
|
321
|
+
knowledgeVersion: manifest.about.knowledge.version,
|
|
292
322
|
counts: {
|
|
293
323
|
lessons: manifest.lessons.length,
|
|
294
324
|
competencies: manifest.competencies.length,
|
|
@@ -296,7 +326,9 @@ function contentStatus(manifest) {
|
|
|
296
326
|
sources: manifest.sources.length,
|
|
297
327
|
tracks: manifest.tracks.length,
|
|
298
328
|
notationDefinitions: manifest.notation.definitions.length,
|
|
299
|
-
keyedEquations
|
|
329
|
+
keyedEquations,
|
|
330
|
+
keyedTables,
|
|
331
|
+
keyedFigures
|
|
300
332
|
},
|
|
301
333
|
draftDebt: { lessons: draftLessons, notation: draftNotation },
|
|
302
334
|
orphans,
|
|
@@ -350,6 +382,8 @@ function contentContext(manifest, rawId) {
|
|
|
350
382
|
...definition.formula === void 0 ? {} : { formula: definition.formula }
|
|
351
383
|
})),
|
|
352
384
|
equations: lesson.equations.map((equation) => ({ ...equation })),
|
|
385
|
+
tables: lesson.tables.map((table) => ({ ...table })),
|
|
386
|
+
figures: lesson.figures.map((figure) => ({ ...figure })),
|
|
353
387
|
sources: lesson.sources.map((id) => {
|
|
354
388
|
const record = sourceById.get(id);
|
|
355
389
|
return {
|
|
@@ -3,6 +3,7 @@ import { basename, join, relative } from "node:path";
|
|
|
3
3
|
import { parseFrontmatter } from "@astrojs/markdown-remark";
|
|
4
4
|
import { isSubstantiveMeaning } from "../reference/prose.js";
|
|
5
5
|
import { parseEquationRef } from "../reference/equations.mjs";
|
|
6
|
+
import { parseKeyedFloatRef } from "../reference/keyed-jsx-floats.mjs";
|
|
6
7
|
import { resolveLabel } from "../reference/label.js";
|
|
7
8
|
import { resolveGlosses } from "../reference/gloss.js";
|
|
8
9
|
import {
|
|
@@ -136,7 +137,9 @@ function extractNotationReferences(text, file, kind) {
|
|
|
136
137
|
for (const [pattern, referenceKind] of patterns) {
|
|
137
138
|
for (const match of searchable.matchAll(pattern)) {
|
|
138
139
|
const key = (match[1] ?? "").trim();
|
|
139
|
-
if (parseEquationRef(key))
|
|
140
|
+
if (parseEquationRef(key) || parseKeyedFloatRef(key, { prefix: "tab" }) || parseKeyedFloatRef(key, { prefix: "fig" })) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
140
143
|
references.push({
|
|
141
144
|
key,
|
|
142
145
|
kind: referenceKind,
|