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.
Files changed (143) hide show
  1. package/CHANGELOG.md +273 -1
  2. package/README.md +103 -8
  3. package/dist/compiler/astro.d.ts +9 -1
  4. package/dist/compiler/astro.js +182 -3
  5. package/dist/compiler/cli.d.ts +13 -0
  6. package/dist/compiler/cli.js +28 -4
  7. package/dist/compiler/collections.d.ts +3 -2
  8. package/dist/compiler/collections.js +28 -1
  9. package/dist/compiler/course-config.d.ts +58 -1
  10. package/dist/compiler/course-config.js +28 -1
  11. package/dist/compiler/exports/standalone-html.d.ts +34 -0
  12. package/dist/compiler/exports/standalone-html.js +803 -0
  13. package/dist/compiler/manifest.d.ts +90 -6
  14. package/dist/compiler/manifest.js +150 -17
  15. package/dist/compiler/resolution-report.d.ts +4 -0
  16. package/dist/reference/base-abbreviations.d.mts +30 -0
  17. package/dist/reference/base-abbreviations.mjs +60 -0
  18. package/dist/reference/base-operators.d.mts +15 -0
  19. package/dist/reference/base-operators.mjs +40 -0
  20. package/dist/reference/base-roman-labels.d.mts +35 -0
  21. package/dist/reference/base-roman-labels.mjs +43 -0
  22. package/dist/reference/consistency.d.ts +8 -2
  23. package/dist/reference/consistency.js +50 -3
  24. package/dist/reference/diagrams-validate.d.ts +21 -0
  25. package/dist/reference/diagrams-validate.js +13 -0
  26. package/dist/reference/equations.d.mts +10 -15
  27. package/dist/reference/equations.mjs +67 -14
  28. package/dist/reference/gate-abbreviations.d.ts +17 -0
  29. package/dist/reference/gate-abbreviations.js +64 -0
  30. package/dist/reference/gate-math.d.ts +28 -5
  31. package/dist/reference/gate-math.js +5 -2
  32. package/dist/reference/gate-prose-notation.d.ts +60 -0
  33. package/dist/reference/gate-prose-notation.js +125 -0
  34. package/dist/reference/keyed-float-validate.d.ts +5 -5
  35. package/dist/reference/keyed-jsx-floats.d.mts +7 -12
  36. package/dist/reference/keyed-jsx-floats.mjs +50 -25
  37. package/dist/reference/knowledge-checks-validate.d.ts +35 -0
  38. package/dist/reference/knowledge-checks-validate.js +78 -0
  39. package/dist/reference/knowledge-checks.d.mts +25 -0
  40. package/dist/reference/knowledge-checks.mjs +45 -0
  41. package/dist/reference/lab-math-scope.d.ts +9 -1
  42. package/dist/reference/lab-math-scope.js +32 -4
  43. package/dist/reference/math-glyphs.d.mts +30 -0
  44. package/dist/reference/math-glyphs.mjs +367 -0
  45. package/dist/reference/mdx-attributes.d.mts +13 -0
  46. package/dist/reference/mdx-attributes.mjs +38 -0
  47. package/dist/reference/mixed-content.d.ts +17 -0
  48. package/dist/reference/mixed-content.js +29 -0
  49. package/dist/reference/page-notation.d.ts +52 -0
  50. package/dist/reference/page-notation.js +171 -0
  51. package/dist/reference/raw-floats-validate.d.ts +18 -0
  52. package/dist/reference/raw-floats-validate.js +53 -0
  53. package/dist/reference/raw-floats.d.mts +18 -0
  54. package/dist/reference/raw-floats.mjs +129 -0
  55. package/dist/reference/rehype-heading-anchors.d.mts +1 -1
  56. package/dist/reference/rehype-heading-anchors.mjs +107 -1
  57. package/dist/reference/rehype-table-header-labels.d.mts +26 -0
  58. package/dist/reference/rehype-table-header-labels.mjs +152 -0
  59. package/dist/reference/remark-abbreviations.d.mts +7 -0
  60. package/dist/reference/remark-abbreviations.mjs +174 -0
  61. package/dist/reference/remark-memo.d.mts +16 -0
  62. package/dist/reference/remark-memo.mjs +339 -0
  63. package/dist/reference/remark-notation.d.mts +57 -9
  64. package/dist/reference/remark-notation.mjs +354 -42
  65. package/dist/reference/remark-short-note.d.mts +4 -0
  66. package/dist/reference/remark-short-note.mjs +237 -0
  67. package/dist/reference/types.d.ts +16 -1
  68. package/dist/reference/units-vocab.js +4 -0
  69. package/docs/VERSIONING.md +33 -4
  70. package/docs/authoring/README.md +8 -0
  71. package/docs/authoring/agents.md +17 -2
  72. package/docs/authoring/components.md +135 -0
  73. package/docs/authoring/migrating.md +92 -7
  74. package/docs/authoring/notation.md +560 -1
  75. package/docs/authoring/structure.md +120 -8
  76. package/docs/authoring/visualizations.md +36 -3
  77. package/package.json +5 -2
  78. package/src/compiler/astro.ts +208 -3
  79. package/src/compiler/cli.ts +29 -2
  80. package/src/compiler/collections.ts +45 -4
  81. package/src/compiler/course-config.ts +92 -0
  82. package/src/compiler/exports/standalone-html.ts +963 -0
  83. package/src/compiler/manifest.ts +268 -22
  84. package/src/components/HoverPanel.astro +45 -4
  85. package/src/components/abbreviation/AbbreviationGlossary.astro +70 -0
  86. package/src/components/abbreviation/AbbreviationLayer.astro +127 -0
  87. package/src/components/assessments/AssessmentRunner.tsx +52 -17
  88. package/src/components/assessments/AssessmentSet.astro +98 -14
  89. package/src/components/assessments/RenderedContent.tsx +55 -0
  90. package/src/components/assessments/assessment-randomization.ts +16 -0
  91. package/src/components/diagram/Diagram.astro +444 -83
  92. package/src/components/disclosure/hover-preview.ts +202 -0
  93. package/src/components/disclosure/persisted-disclosure.ts +66 -0
  94. package/src/components/examples/CompactExamples.astro +2 -0
  95. package/src/components/figure/Figure.astro +279 -10
  96. package/src/components/memo/Memo.astro +453 -0
  97. package/src/components/memo/ShortNote.astro +223 -0
  98. package/src/components/notation/NotationFormula.astro +18 -1
  99. package/src/components/notation/NotationLayer.astro +105 -8
  100. package/src/components/notation/render-notation-math.ts +39 -2
  101. package/src/components/notation/safe-math-to-react.ts +50 -0
  102. package/src/components/reference/FloatRefLayer.astro +233 -15
  103. package/src/components/starlight/CollapsibleSections.astro +461 -0
  104. package/src/components/starlight/DiagramEnhancer.astro +385 -0
  105. package/src/components/starlight/HeadingAnchorEnhancer.astro +5 -17
  106. package/src/components/starlight/KeyedFloatEnhancer.astro +81 -3
  107. package/src/components/starlight/LayoutHeader.astro +50 -0
  108. package/src/components/starlight/LayoutSocialIcons.astro +41 -2
  109. package/src/components/starlight/LessonExportActions.astro +436 -0
  110. package/src/components/starlight/LessonFooter.astro +27 -2
  111. package/src/components/starlight/section-parts.ts +31 -0
  112. package/src/components/table/Table.astro +293 -7
  113. package/src/components/table/TableOverflowExpander.astro +489 -0
  114. package/src/content-config.ts +20 -0
  115. package/src/reference/base-abbreviations.mjs +60 -0
  116. package/src/reference/base-operators.mjs +40 -0
  117. package/src/reference/base-roman-labels.mjs +43 -0
  118. package/src/reference/consistency.ts +73 -2
  119. package/src/reference/diagrams-validate.ts +40 -0
  120. package/src/reference/equations.mjs +67 -14
  121. package/src/reference/gate-abbreviations.ts +128 -0
  122. package/src/reference/gate-math.ts +27 -7
  123. package/src/reference/gate-prose-notation.ts +224 -0
  124. package/src/reference/keyed-float-validate.ts +5 -5
  125. package/src/reference/keyed-jsx-floats.mjs +50 -25
  126. package/src/reference/knowledge-checks-validate.ts +130 -0
  127. package/src/reference/knowledge-checks.mjs +45 -0
  128. package/src/reference/lab-math-scope.ts +45 -3
  129. package/src/reference/math-glyphs.mjs +367 -0
  130. package/src/reference/mdx-attributes.mjs +38 -0
  131. package/src/reference/mixed-content.ts +69 -0
  132. package/src/reference/page-notation.ts +252 -0
  133. package/src/reference/raw-floats-validate.ts +103 -0
  134. package/src/reference/raw-floats.mjs +129 -0
  135. package/src/reference/rehype-heading-anchors.mjs +107 -1
  136. package/src/reference/rehype-table-header-labels.mjs +152 -0
  137. package/src/reference/remark-abbreviations.mjs +174 -0
  138. package/src/reference/remark-memo.mjs +339 -0
  139. package/src/reference/remark-notation.mjs +354 -42
  140. package/src/reference/remark-short-note.mjs +237 -0
  141. package/src/reference/types.ts +16 -0
  142. package/src/reference/units-vocab.ts +4 -0
  143. package/src/styles/global.css +996 -50
package/CHANGELOG.md CHANGED
@@ -14,6 +14,277 @@ under **Breaking** below; a PATCH never breaks.
14
14
 
15
15
  ## [Unreleased]
16
16
 
17
+ ## [0.5.0] - 2026-09-15
18
+
19
+ ### Added
20
+
21
+ - New `createRemarkNotationOptions(manifest, overrides?)` helper
22
+ (`remark-notation.mjs`) returns `{ definitions: manifest.notation.raw,
23
+ manifest, ...overrides }` — the options `remarkNotation` needs, read off one
24
+ manifest in one call, so a course no longer hand-assembles `definitions`,
25
+ `equations`, `tables`, `figures`, and the superseded `chapters` map
26
+ separately (easy to do partially, which is how a page numbered itself under
27
+ the legacy `##`-section scheme while every cross-page reference to it read
28
+ the manifest). `playground/astro.config.mjs`, `README.md`, and
29
+ `src/compiler/cli.ts`'s own `content check` pipeline now wire the plugin
30
+ this way.
31
+
32
+ - New `strict` option on `remarkNotation` (default `false`, so existing wiring
33
+ keeps building unchanged). The existing manifest/number-table cross-check
34
+ only ever caught a disagreement for a *keyed* equation or `<Table>`/
35
+ `<Figure>` — an unkeyed one still silently fell back to legacy
36
+ `##`-section numbering with no diagnostic at all if its lesson had no
37
+ `numbering` placement or cross-page entry. `strict: true` fails the build in
38
+ that case too, for any catalogued lesson with at least one equation,
39
+ `<Table>`, or `<Figure>` (keyed or not). `<Diagram>` is exempt — it already
40
+ renders unnumbered by design when it has no placement.
41
+ `createRemarkNotationOptions(manifest, { strict: true })` is the recommended
42
+ way to turn it on.
43
+
44
+ - New raw/unwrapped-float diagnostics (`src/reference/raw-floats.mjs`,
45
+ `raw-floats-validate.ts`): a raw Markdown table or raw HTML `<table>` never
46
+ wrapped in `<Table>`/`<Figure>`, an `<Diagram>` with no `id`, or an
47
+ `<Diagram>` nested inside anything but `<Memo>`/`<ShortNote>`/`<Table>`/
48
+ `<Figure>` are all invisible to keyed-float validation — they render fine
49
+ but can never get a stable number or `[[…]]` reference. New codes
50
+ `raw-table-markdown`, `raw-table-html`, `diagram-unkeyed`, `diagram-nested`
51
+ surface this in `manifest.diagnostics.rawFloats` (warning-severity,
52
+ aggregated the same way as `tables`/`figures`/`diagrams`/`checks` in
53
+ `cli.ts` and `playground/scripts/compile-manifest.ts`); `remarkNotation`
54
+ reports the same at render time as a defense-in-depth check for content
55
+ added since the manifest was last compiled. See
56
+ [`docs/authoring/structure.md`](docs/authoring/structure.md#raw-and-unwrapped-floats).
57
+
58
+ - Courses can opt into a per-lesson **Print / save as PDF** action with
59
+ `lessonExports.print`. It opens the native browser print dialog—no generated
60
+ PDF artifact or server-side renderer—and prepares a chrome-free, paper-safe
61
+ lesson layout: disclosures, complete tables, diagrams, and assessment items
62
+ are exposed; non-printable controls are removed; linked text stays visibly
63
+ green and remains a real PDF link with canonical destinations; lazy
64
+ interactive plots are hydrated before the print dialog opens; and the reader's
65
+ exact disclosure state is restored after printing without changing saved
66
+ preferences.
67
+
68
+ - Courses can opt into fully functional per-lesson standalone HTML with
69
+ `lessonExports.standaloneHtml` and `canonicalBaseUrl`. The Astro build now
70
+ emits `downloads/<lesson-slug>.html` files that open through `file://`, embed
71
+ the complete style/font/media/module graph, hydrate Astro React islands from
72
+ one shared browser bundle, preserve local lesson interactions, remove site
73
+ chrome, and rewrite deliberate outbound course links to the canonical site.
74
+ Builds fail before publishing an artifact when a render dependency cannot be
75
+ embedded. Downloads retain an offline Light/Dark/Auto theme control. Lesson
76
+ pages expose the download in the top-right header cluster, and the development
77
+ server generates and serves the artifact on demand.
78
+
79
+ - Rule N14: every acronym (`OLS`, `MSE`) or dotted abbreviation (`w.r.t.`,
80
+ `i.i.d.`) a lesson uses in plain prose must now be universal (the shipped
81
+ `base-abbreviations.mjs` baseline plus `abbreviations.allow`/`deny` in
82
+ `course.config.ts`), carry an entry in the new, lighter-weight
83
+ `abbreviations` content collection (`content/abbreviations/*.md` — a name
84
+ resolution only, no formula or units), or already be explained as a math
85
+ quantity via [N8](docs/authoring/notation.md#n8)/[N10](docs/authoring/notation.md#n10).
86
+ Build-enforced (`gate-abbreviations.ts`), same error-severity, no-warm-up
87
+ shape as N9. A new "Abbreviations" section on the glossary page
88
+ (`AbbreviationGlossary.astro`) lists every catalogued entry, and the first
89
+ occurrence of a table-resolved abbreviation on a lesson page is now also an
90
+ inline hover/click card (`remark-abbreviations.mjs` +
91
+ `AbbreviationLayer.astro`), the same card shell the math-notation and
92
+ citation systems use, linking to its glossary entry.
93
+
94
+ ### Fixed
95
+
96
+ - Standalone lesson links no longer return 404 during `astro dev`; the first
97
+ local request now builds the same audited artifact emitted by a static build.
98
+
99
+ - Expanded keyed tables retain their native `<figure>` semantics inside a valid
100
+ landmark wrapper instead of applying an invalid ARIA `region` role directly
101
+ to the figure. Their overflow controls also remain distinguishable from the
102
+ separate row-pagination toggle.
103
+
104
+ - Nested Astro component math can no longer emit a working-looking
105
+ `data-notation-key` without shipping its popup definition. `Diagram` and
106
+ `NotationFormula` register their rendered keys in request-local page scope,
107
+ `labMathScope` accepts component context for lab templates, and the Astro
108
+ integration checks final generated HTML for trigger/payload parity. Known
109
+ component-only shared keys now receive cards without making the entire global
110
+ registry page-wide; unknown keys fail with lesson/component/formula context.
111
+
112
+ ### Changed
113
+
114
+ - Every published engine version must now ship an agent-readable section in
115
+ `docs/authoring/migrating.md`, with exact downstream content/config/script
116
+ steps or an explicit no-action statement. The release checklist and downstream
117
+ agent guide now treat this as part of the release artifact, and a new
118
+ `prepack` guard rejects a version without its matching migration section or
119
+ without the guide included in `package.json#files`.
120
+
121
+ ### Breaking
122
+
123
+ - **The compiler manifest schema is now v13.** It adds
124
+ `diagnostics.rawFloats` (see Added above). The change is additive, but
125
+ consumers that validate `MANIFEST_SCHEMA_VERSION` must accept the new
126
+ version.
127
+
128
+ - **The compiler manifest schema is now v12.** It adds `manifest.abbreviations`
129
+ — the `abbreviations` content collection (N14), flattened to plain
130
+ `{key, abbr, expansion, aliases, domain?}` records sorted by key, so the new
131
+ `remarkAbbreviations` plugin can wrap the first occurrence of a known
132
+ abbreviation in a lesson's prose. The change is additive, but consumers that
133
+ validate `MANIFEST_SCHEMA_VERSION` must accept the new version.
134
+
135
+ - **The compiler manifest schema is now v11.** It adds the normalized
136
+ `manifest.lessonExports` settings used by the build integration and lesson
137
+ UI. The change is additive, but consumers that validate
138
+ `MANIFEST_SCHEMA_VERSION` must accept the new version.
139
+
140
+ - **The compiler manifest schema is now v10.** Diagrams are first-class keyed
141
+ floats with their own `manifest.diagrams`, `lessons[].diagrams`,
142
+ `lessons[].numbering.diagrams`, and `diagnostics.diagrams` fields. Numbered
143
+ knowledge checks add `manifest.checks`, `lessons[].knowledgeChecks`,
144
+ `lessons[].numbering.checks`, and `diagnostics.checks`. The changes are
145
+ additive, but consumers that validate `MANIFEST_SCHEMA_VERSION` must accept
146
+ the new version.
147
+
148
+ - **New build-enforced authoring rule N9: a physical quantity, unit, or math
149
+ operator must be typeset in `$…$`, never as plain text, anywhere on a
150
+ lesson page (prose or a table cell).** `gate-prose-notation.ts` fails
151
+ `pnpm validate:content` / `astro build` on a numeral glued to an
152
+ abbreviated unit (`45 m/s`), a bare unit in parens (`(s)`, `(m/s²)`), an
153
+ escaped `\|` standing in for an absolute-value bar, a hand-typed `_`/`^`
154
+ sub/superscript, or a `/` between two symbol-like tokens outside a small
155
+ reviewed idiom allowlist. A downstream course with existing plain-text
156
+ notation in a lesson body or table will now fail the build; see
157
+ `docs/authoring/notation.md#n9` for the fix (wrap it in `$…$`, unit
158
+ included — `\text{}`-wrapped and folded into the same span as its symbol,
159
+ e.g. `$t\ \text{(s)}$`) — there is no `lint-ignore.yml` escape hatch for
160
+ this one. The rule does not apply inside a JSX component prop
161
+ (`caption="…"`, `title="…"`), since that text never reaches KaTeX. N9 now
162
+ also scans every assessment item's `prompt` / `explanation` string the same
163
+ way (`gateProseNotationInAssessments`) — same fix, same "no escape hatch."
164
+ - **New `rehype-table-header-labels.mjs` gives an automatic accessible name
165
+ to a table `<th>` that ends up 100% KaTeX (every symbol *and* unit now in
166
+ `$…$` per N9 above, so a bare `$t\ \text{(s)}$` column has no plain DOM
167
+ text at all).** axe-core's `empty-table-header` rule requires real text
168
+ content — not just an `aria-label` (confirmed by reading the rule itself:
169
+ it runs only `has-visible-text`, with none of the
170
+ `aria-label`/`aria-labelledby` fallbacks most other axe name-checking rules
171
+ allow) — so this plugin inserts a visually-hidden `<span class="sr-only">`
172
+ (Starlight's own clip-based utility, not `display:none`) composed from the
173
+ `\explain{key}{…}` key(s) the header's LaTeX names and that key's own
174
+ `label` / `units` notation metadata, already authored for hover cards
175
+ elsewhere. Wired into `playground/astro.config.mjs`'s `rehypePlugins`
176
+ right after `rehypeKatex`. A `\mathrm{…}` compound (`\mathrm{KE}/m`) needs
177
+ each side explicitly `\explain{}`-wrapped for the plugin to have anything
178
+ to read, since the glyph resolver never looks inside a bare `\mathrm{}`
179
+ body — every bare symbol column resolves automatically the same way it
180
+ already did for hover cards.
181
+ - **`AssessmentRunner` now renders `prompt` / `explanation` through the same
182
+ KaTeX pipeline as lesson body math, instead of showing the raw authored
183
+ string.** The gate that checks an assessment math span's glyphs resolve
184
+ (context `assessment` in `gate-math.ts`) is promoted from a Tier-3 warning
185
+ to a build error for the same reason `formula` was at D15: an unresolved
186
+ glyph is now a visible hole in the rendered UI, not an invisible warning. A
187
+ downstream course with an unresolved glyph in assessment math, or
188
+ plain-text notation in an assessment `prompt` / `explanation` (see N9
189
+ above), will now fail the build. Manifest schema is now **v8**:
190
+ `assessmentContent` is new — every assessment item's `prompt` /
191
+ `explanation`, pre-split into text/math segments and resolved against the
192
+ scope of the lesson that embeds it; `AssessmentSet.astro` renders each
193
+ `math` segment via `renderNotationMath` before handing the result to
194
+ `AssessmentRunner`, so `AssessmentRunner`'s `Item` props changed shape:
195
+ `prompt` / `explanation` (`string`) are now `promptContent` /
196
+ `explanationContent` (`MixedContentNode[]`, rendered with the new
197
+ `RenderedContent` component) — a host embedding `AssessmentRunner` directly
198
+ (bypassing `AssessmentSet.astro`) needs to pre-render the same way.
199
+
200
+ - **New build-enforced authoring rule N13: a nonlinear function must take a
201
+ parenthesized argument, even a single symbol — `\sin x` is now a hard
202
+ error, `\sin(x)` is required.** Covers every trigonometric,
203
+ inverse-trigonometric, and hyperbolic function plus `exp`/`log`/`ln`/`lg`
204
+ (the new `PARENTHESIZED_FUNCTIONS` set in `base-operators.mjs`).
205
+ `resolveMathGlyphs` (`math-glyphs.mjs`) walks the parsed KaTeX tree of
206
+ every rendered-math context — lesson body, `notation.formula`, assessment
207
+ `prompt`/`explanation`, a `Diagram` node's `formula`, interactive lab math
208
+ — since all of them already call this one resolver; a violation fails
209
+ `pnpm validate:content` / `astro build` the same way an unresolved glyph
210
+ does, with no `lint-ignore.yml` escape hatch, like N9. `Diagram` and
211
+ `NotationFormula` formulas render through `renderNotationFormula`
212
+ (`render-notation-math.ts`), which previously caught *any*
213
+ `resolveMathGlyphs` failure and degraded to an inert fallback (or, for
214
+ `Diagram`, a generic "could not bind to notation scope" error); it now
215
+ rethrows this specific error instead, so a violation there fails loudly
216
+ with the actual fix rather than a misleading message. A downstream course
217
+ with `\sin\theta`, `\log x`, `\exp t`, etc. anywhere in rendered math will
218
+ now fail the build; see `docs/authoring/notation.md#n13`.
219
+
220
+ ### Added
221
+
222
+ - **Numbered, referenceable, collapsible knowledge checks.** Assessment ids in
223
+ each lesson's `.checks.yml` now form an independent part-wide number stream.
224
+ `<AssessmentSet>` renders the assessment record's title as the sole heading,
225
+ with a stable `#check-<assessment-id>` permalink; `[[check-<assessment-id>]]`
226
+ and cross-page `[[lesson/slug#check-<assessment-id>]]` references render the
227
+ published number. The check heading reuses the standard section disclosure,
228
+ including persistence, hover peek, deep-link opening, print expansion, and
229
+ the hidden-until-hover expanded chevron. Single-choice answer options are
230
+ Fisher–Yates shuffled in the browser for every full page view while grading
231
+ remains keyed by stable option ids. The manifest schema is now v10, adding
232
+ `checks`, `lessons[].knowledgeChecks`, `lessons[].numbering.checks`, and
233
+ `diagnostics.checks`.
234
+
235
+ - **Detailed memos and inline short notes.** A flow `<Memo>` is now a
236
+ subsection-like optional derivation that can be placed later and cited from
237
+ prose as `[[memo-id]]`; references, the memo's visible `[n]`, and a familiar
238
+ chain-icon permalink beside its title share a URL fragment. Memo blocks
239
+ retain persistent collapse, hover previews,
240
+ deep-link reveal, and print behavior. The new `remark-short-note.mjs` adapter
241
+ and `components/memo/ShortNote.astro` provide the complementary inline form:
242
+ its author-supplied link text expands in place into arbitrary block Markdown
243
+ and restores the intact sentence on collapse. Equations, tables, figures,
244
+ and diagrams inside either detail form remain in their normal numbering
245
+ streams.
246
+
247
+ - New authoring rule N10: a `\mathrm{…}` multi-letter label (`\mathrm{KE}`,
248
+ `\mathrm{PV}`) must be on the shipped/course roman-label baseline
249
+ (`base-roman-labels.mjs`, `course.config.ts` `romanLabels`) or carry a
250
+ notation entry the reader can hover — the `\mathrm{…}` counterpart to N8's
251
+ `operator-explained`, since N3 requires the format but the glyph resolver
252
+ skips a `\mathrm{}` body whole either way, so nothing previously checked
253
+ whether it was ever explained. Surfaced as a `roman-label-explained` Tier-3
254
+ warning by `pnpm validate:content`, same rollout and `lint-ignore.yml`
255
+ shape as N8.
256
+
257
+ ### Added
258
+
259
+ - **Independent diagram numbering and references.** A top-level
260
+ `<Diagram id="…">` is numbered in its own `‹part›.‹n›` stream, renders a
261
+ `#dia-<id>` anchor, and can be cited with `[[dia-<id>]]` (including cross-page
262
+ references). Wide diagrams emit an author-facing build warning above the
263
+ configurable `warnAboveWidth` threshold and gain an unobtrusive horizontal
264
+ expand/shrink control. Expansion moves the original diagram into a non-modal,
265
+ vertically scrollable in-page overlay that follows its document position.
266
+ Diagram titles also minimize/restore the body with an animated chevron,
267
+ permalink icon, and persistent reader state while retaining a complete
268
+ no-JavaScript fallback.
269
+
270
+ ### Fixed
271
+
272
+ - `Diagram` now preserves its build-time SVG dimensions inside its horizontal
273
+ scroller instead of inheriting Starlight's responsive SVG shrinking. This
274
+ keeps the HTML/KaTeX embedded in `foreignObject` nodes aligned with their
275
+ boxes on iOS WebKit (Safari and Chrome), which does not reliably scale mixed
276
+ SVG/HTML content as one unit.
277
+ - Clicking a same-page diagram reference now dismisses its hover card and
278
+ navigates immediately; the card also closes once scrolling has made the
279
+ target diagram fully visible. Preview SVG ids are isolated so cloned arrow
280
+ markers cannot interfere with the original diagram.
281
+ - An expanded diagram title now hides its down-pointing disclosure chevron
282
+ after the interaction, matching collapsible sections and subsections; the
283
+ right-pointing chevron remains visible while the diagram is minimized.
284
+ - Expanded tables and diagrams now remain beneath the sticky site header as
285
+ they scroll upward. Escape now restores an expanded diagram, matching the
286
+ existing expanded-table shortcut.
287
+
17
288
  ## [0.4.0] - 2026-09-09
18
289
 
19
290
  ### Breaking
@@ -165,7 +436,8 @@ under **Breaking** below; a PATCH never breaks.
165
436
  system with its completeness gate, the curriculum-graph validator, the
166
437
  Astro/Starlight UI, the LMS / session / analytics seams, and the `content` CLI.
167
438
 
168
- [Unreleased]: https://github.com/danieltuzes/explico/compare/v0.4.0...HEAD
439
+ [Unreleased]: https://github.com/danieltuzes/explico/compare/v0.5.0...HEAD
440
+ [0.5.0]: https://github.com/danieltuzes/explico/compare/v0.4.0...v0.5.0
169
441
  [0.4.0]: https://github.com/danieltuzes/explico/compare/v0.3.0...v0.4.0
170
442
  [0.3.0]: https://github.com/danieltuzes/explico/compare/v0.2.0...v0.3.0
171
443
  [0.2.0]: https://github.com/danieltuzes/explico/compare/v0.1.1...v0.2.0
package/README.md CHANGED
@@ -71,7 +71,11 @@ export { collections } from 'explico/content-config';
71
71
  // astro.config.mjs
72
72
  import { explicoContentRefresh } from 'explico/compiler/astro.js';
73
73
  import { loadManifest } from 'explico/compiler/manifest.js';
74
- import remarkNotation from 'explico/reference/remark-notation.mjs';
74
+ import remarkMemo from 'explico/reference/remark-memo.mjs';
75
+ import remarkNotation, {
76
+ createRemarkNotationOptions,
77
+ } from 'explico/reference/remark-notation.mjs';
78
+ import remarkShortNote from 'explico/reference/remark-short-note.mjs';
75
79
  import remarkCitation from 'explico/reference/remark-citation.mjs';
76
80
  import { createNotationKatexOptions } from 'explico/reference/katex-options.mjs';
77
81
  import rehypeFailKatexErrors from 'explico/reference/rehype-fail-katex-errors.mjs';
@@ -80,13 +84,15 @@ import { courseConfig } from './content/course.config.ts';
80
84
  const manifest = await loadManifest(courseConfig);
81
85
  // markdown.remarkPlugins: [
82
86
  // 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 }],
87
+ // remarkShortNote,
88
+ // remarkMemo,
89
+ // // `createRemarkNotationOptions` reads every numbering input off one
90
+ // // manifest — the equation / table / figure number tables *and* the part
91
+ // // each lesson sits in — so they can't drift apart. `strict: true` fails
92
+ // // the build when a catalogued lesson has equations/tables/figures but no
93
+ // // numbering or cross-page entry, instead of silently numbering it by
94
+ // // `##` section while references to it read the part scheme.
95
+ // [remarkNotation, createRemarkNotationOptions(manifest, { strict: true })],
90
96
  // [remarkCitation, { sources: manifest.sources }],
91
97
  // ]
92
98
  // … integrations: [explicoContentRefresh(courseConfig, manifest),
@@ -102,6 +108,85 @@ and restarts the development server when any file under `content/` is added,
102
108
  changed, or removed, keeping Markdown transforms and UI components on the same
103
109
  model without an editor-specific task.
104
110
 
111
+ A memo is a substantial optional derivation: it behaves like a collapsible,
112
+ numbered subsection without adding a heading to the page outline. Cite it from
113
+ earlier prose with `[[memo-id]]`, then place the block wherever it best fits.
114
+ The citation, the block's own number, and its chain icon link to the same URL
115
+ fragment:
116
+
117
+ ```mdx
118
+ The omitted calculation is recorded in [[memo-full-check]].
119
+
120
+ Several paragraphs may intervene.
121
+
122
+ <Memo id="full-check" title="Full check" description="The omitted algebra and evidence.">
123
+
124
+ The longer derivation can contain paragraphs, equations, tables, and figures.
125
+
126
+ </Memo>
127
+ ```
128
+
129
+ For a reveal that really belongs inside one sentence, import
130
+ `explico/components/memo/ShortNote.astro` and use `<ShortNote>`. Its `title` is
131
+ the collapsed inline link; expanding replaces that link with the block-capable
132
+ body, then rejoins the sentence when collapsed:
133
+
134
+ ```mdx
135
+ The derivative agrees <ShortNote id="derivative" title="(show how)" description="Show the calculus calculation.">
136
+
137
+ Differentiate the polynomial, set the result to zero, and solve.
138
+
139
+ </ShortNote>. But symmetry is quicker.
140
+ ```
141
+
142
+ Both components require literal `id`, `title`, and `description` strings.
143
+ Memos start collapsed and persist reader state unless `rememberState={false}`;
144
+ short notes always start collapsed.
145
+
146
+ ### Lesson exports
147
+
148
+ A course can opt into a self-contained HTML download and browser-native printing
149
+ for each lesson:
150
+
151
+ ```ts
152
+ export const courseConfig: CourseConfig = {
153
+ // ...the normal course fields
154
+ lessonExports: {
155
+ standaloneHtml: true,
156
+ print: true,
157
+ canonicalBaseUrl: 'https://example.edu/course/',
158
+ },
159
+ };
160
+ ```
161
+
162
+ The existing `explicoContentRefresh(courseConfig, manifest)` integration emits
163
+ `downloads/<lesson-slug>.html` after Astro's static build and adds a download
164
+ control to the top-right header cluster on each lesson. During `astro dev`, the
165
+ first download request runs an isolated static build and streams the resulting
166
+ file; later requests use that result until a watched source file changes. The
167
+ downloaded file contains its CSS,
168
+ KaTeX fonts, images, client scripts, Astro island components, and renderer; it
169
+ opens directly through `file://` without a server or network access. Search,
170
+ sidebars, pagination, and other site chrome are omitted. Same-page references
171
+ stay local, while links that leave the lesson use `canonicalBaseUrl` and
172
+ cross-lesson hover previews degrade to ordinary links. A retained theme control
173
+ lets an offline reader choose Light, Dark, or Auto; Auto follows changes to the
174
+ viewer’s system color scheme.
175
+
176
+ The **Print / save as PDF** action calls the browser's native print dialog; it
177
+ does not download or build a `.pdf` file itself. The print layout removes site
178
+ chrome, expands disclosures and complete data surfaces without changing saved
179
+ reader state, hides controls that have no paper equivalent while retaining their
180
+ current visual result, and keeps linked text clickable with canonical destinations.
181
+ Paper size, margins, browser-added headers/footers, final filename, and the
182
+ choice to save as PDF remain browser and reader settings.
183
+
184
+ Set `canonicalBaseUrl` to the deployed course root whenever retained lesson
185
+ content links elsewhere in the course. If such a link exists without the base,
186
+ the build fails rather than shipping a dead local link. An unresolved or remote
187
+ stylesheet, script, font, image, iframe, media source, worker, WASM file, or CSS
188
+ URL likewise fails the standalone export audit.
189
+
105
190
  ```jsonc
106
191
  // package.json — the read-side CLI
107
192
  "scripts": { "validate:content": "tsx scripts/compile-manifest.ts" }
@@ -135,6 +220,10 @@ version and `content/CHANGELOG.md` move with a content change (area `C`,
135
220
  surfaced to readers in the About `[?]` panel).
136
221
  [`docs/authoring/README.md`](docs/authoring/README.md) is the governance: rule
137
222
  IDs, enforcement levels, and how to amend.
223
+ [`docs/authoring/components.md`](docs/authoring/components.md) is a syntax
224
+ catalog of what a lesson body can contain — native tooltips, tables, figures,
225
+ diagrams, memos, abbreviations, citations — for an author who wants "what's
226
+ available" rather than "why this rule."
138
227
 
139
228
  ## Build
140
229
 
@@ -164,5 +253,11 @@ content — `about.version` in `content/course.config.ts`, tracked in the course
164
253
  For AI authors: [`AGENTS.md`](AGENTS.md) says which changelog and which version a
165
254
  given change updates.
166
255
 
256
+ When upgrading an existing course, use the migration guide shipped with the
257
+ target package at `node_modules/explico/docs/authoring/migrating.md`. Every
258
+ published version has a section, including versions that require no course-side
259
+ changes, so an agent can update content, configuration, and scripts from the
260
+ installed artifact rather than guessing from the changelog.
261
+
167
262
  Status: extracted from the `equations` project; API not yet stable (pre-1.0);
168
263
  the license is undecided (`UNLICENSED` until chosen).
@@ -10,13 +10,21 @@ import '../reference/equations-validate.js';
10
10
  import '../reference/tables-validate.js';
11
11
  import '../reference/keyed-float-validate.js';
12
12
  import '../reference/figures-validate.js';
13
+ import '../reference/diagrams-validate.js';
14
+ import '../reference/knowledge-checks-validate.js';
15
+ import '../reference/raw-floats-validate.js';
13
16
  import '../reference/gate-math.js';
17
+ import '../reference/mixed-content.js';
14
18
  import '../reference/gloss.js';
15
19
  import './knowledge-version.js';
16
20
  import './lesson-derivation.js';
17
21
 
22
+ /** Match only a configured lesson artifact below Astro's deployment base. */
23
+ declare function matchStandaloneDownload(pathname: string, base: string, lessonSlugs: readonly string[]): string | undefined;
18
24
  /** Fail loudly instead of rendering math triggers whose local cards are stale. */
19
25
  declare function assertLocalNotationParity(lessonId: string, authoredKeys: readonly string[], renderedKeys: readonly string[]): void;
26
+ /** Fail an Astro build if final rendered HTML contains an unusable trigger. */
27
+ declare function assertNotationPayloadHtml(page: string, html: string): void;
20
28
  /**
21
29
  * Restart Astro when an authored content file changes. Astro reloads MDX on
22
30
  * its own, but Explico's manifest is created by `astro.config.mjs`; a restart
@@ -27,4 +35,4 @@ declare function assertLocalNotationParity(lessonId: string, authoredKeys: reado
27
35
  */
28
36
  declare function explicoContentRefresh(courseConfig: CourseConfig, manifest: Manifest): AstroIntegration;
29
37
 
30
- export { assertLocalNotationParity, explicoContentRefresh };
38
+ export { assertLocalNotationParity, assertNotationPayloadHtml, explicoContentRefresh, matchStandaloneDownload };