explico 0.7.0 → 0.11.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 +1409 -1
- package/README.md +60 -15
- package/dist/compiler/astro.d.ts +6 -1
- package/dist/compiler/cli.d.ts +54 -3
- package/dist/compiler/cli.js +80 -6
- package/dist/compiler/collections.d.ts +35 -2
- package/dist/compiler/collections.js +84 -8
- package/dist/compiler/course-config.d.ts +3 -194
- package/dist/compiler/course-config.js +78 -5
- package/dist/compiler/exports/standalone-html.d.ts +6 -1
- package/dist/compiler/knowledge-version.js +1 -1
- package/dist/compiler/lesson-derivation.d.ts +27 -10
- package/dist/compiler/lesson-derivation.js +23 -8
- package/dist/compiler/logging.d.ts +14 -1
- package/dist/compiler/logging.js +6 -2
- package/dist/compiler/manifest.d.ts +204 -31
- package/dist/compiler/manifest.js +270 -55
- package/dist/compiler/resolution-report.d.ts +6 -1
- package/dist/course-config-Dsnri9Tk.d.ts +464 -0
- package/dist/reference/base-accents.d.mts +26 -0
- package/dist/reference/base-accents.mjs +31 -0
- package/dist/reference/base-roman-labels.d.mts +2 -2
- package/dist/reference/base-roman-labels.mjs +2 -2
- package/dist/reference/consistency.d.ts +14 -2
- package/dist/reference/consistency.js +108 -4
- package/dist/reference/delimiter-size.d.ts +21 -11
- package/dist/reference/delimiter-size.js +15 -39
- package/dist/reference/diagrams-validate.d.ts +7 -2
- package/dist/reference/diagrams-validate.js +9 -1
- package/dist/reference/equations-validate.d.ts +8 -5
- package/dist/reference/equations-validate.js +1 -0
- package/dist/reference/equations.d.mts +69 -34
- package/dist/reference/equations.mjs +90 -44
- package/dist/reference/examples-validate.d.ts +31 -0
- package/dist/reference/examples-validate.js +48 -0
- package/dist/reference/figures-validate.d.ts +1 -1
- package/dist/reference/figures-validate.js +4 -1
- package/dist/reference/folds-validate.d.ts +33 -0
- package/dist/reference/folds-validate.js +32 -0
- package/dist/reference/gate-abbreviations.d.ts +3 -1
- package/dist/reference/gate-abbreviations.js +3 -2
- package/dist/reference/gate-course-rules.d.ts +32 -0
- package/dist/reference/gate-course-rules.js +85 -0
- package/dist/reference/gate-heading-length.d.ts +5 -2
- package/dist/reference/gate-heading-length.js +47 -23
- package/dist/reference/gate-heading-structure.d.ts +41 -0
- package/dist/reference/gate-heading-structure.js +113 -0
- package/dist/reference/gate-math-punctuation.d.ts +71 -0
- package/dist/reference/gate-math-punctuation.js +251 -0
- package/dist/reference/gate-math.d.ts +44 -6
- package/dist/reference/gate-math.js +63 -10
- package/dist/reference/gate-part-pages.d.ts +50 -0
- package/dist/reference/gate-part-pages.js +118 -0
- package/dist/reference/gate-prose-notation.d.ts +32 -11
- package/dist/reference/gate-prose-notation.js +30 -8
- package/dist/reference/katex-delimiters.d.mts +15 -0
- package/dist/reference/katex-delimiters.mjs +138 -0
- package/dist/reference/katex-options.mjs +4 -0
- package/dist/reference/keyed-float-validate.d.ts +27 -10
- package/dist/reference/keyed-float-validate.js +15 -1
- package/dist/reference/keyed-jsx-floats.d.mts +11 -5
- package/dist/reference/keyed-jsx-floats.mjs +73 -20
- package/dist/reference/knowledge-checks-validate.d.ts +1 -1
- package/dist/reference/knowledge-checks.d.mts +3 -1
- package/dist/reference/knowledge-checks.mjs +9 -4
- package/dist/reference/lab-math-scope.d.ts +1 -0
- package/dist/reference/lab-math-slot.d.ts +34 -0
- package/dist/reference/lab-math-slot.js +6 -0
- package/dist/reference/math-glyphs.d.mts +80 -4
- package/dist/reference/math-glyphs.mjs +588 -39
- package/dist/reference/page-notation.d.ts +6 -1
- package/dist/reference/raw-floats-validate.js +1 -1
- package/dist/reference/raw-floats.mjs +1 -1
- package/dist/reference/rehype-heading-anchors.d.mts +14 -0
- package/dist/reference/rehype-heading-anchors.mjs +116 -17
- package/dist/reference/rehype-table-header-labels.d.mts +6 -1
- package/dist/reference/rehype-table-header-labels.mjs +21 -3
- package/dist/reference/remark-asides.d.mts +3 -0
- package/dist/reference/remark-asides.mjs +180 -0
- package/dist/reference/remark-memo.d.mts +3 -1
- package/dist/reference/remark-memo.mjs +42 -11
- package/dist/reference/remark-notation.d.mts +60 -10
- package/dist/reference/remark-notation.mjs +387 -36
- package/dist/reference/render-lab-math.d.ts +4 -26
- package/dist/reference/render-lab-math.js +5 -5
- package/dist/reference/sections-validate.d.ts +27 -0
- package/dist/reference/sections-validate.js +41 -0
- package/dist/reference/sections.d.mts +97 -0
- package/dist/reference/sections.mjs +306 -0
- package/dist/reference/tables-validate.d.ts +1 -1
- package/dist/reference/types.d.ts +24 -1
- package/dist/reference/unit-words.d.ts +37 -0
- package/dist/reference/unit-words.js +170 -0
- package/dist/rules/catalog.d.ts +176 -0
- package/dist/rules/catalog.js +983 -0
- package/dist/rules/course-rules.d.ts +115 -0
- package/dist/rules/course-rules.js +292 -0
- package/dist/rules/course-rules.schema.json +138 -0
- package/dist/rules/in-effect.d.ts +53 -0
- package/dist/rules/in-effect.js +142 -0
- package/dist/rules/rules.schema.json +1072 -0
- package/dist/rules/settings.d.ts +3 -0
- package/dist/rules/settings.js +290 -0
- package/dist/rules/waivers.d.ts +53 -0
- package/dist/rules/waivers.js +132 -0
- package/dist/session/PreferenceStore.d.ts +5 -0
- package/dist/session/PreferenceStore.js +9 -0
- package/docs/VERSIONING.md +2 -2
- package/docs/authoring/README.md +232 -40
- package/docs/authoring/agents.md +55 -9
- package/docs/authoring/components.md +178 -22
- package/docs/authoring/migrating.md +1324 -2
- package/docs/authoring/notation.md +616 -344
- package/docs/authoring/prose.md +551 -0
- package/docs/authoring/retired.md +35 -0
- package/docs/authoring/structure-rules.md +422 -40
- package/docs/authoring/structure.md +445 -62
- package/docs/authoring/versioning.md +20 -28
- package/docs/authoring/visualizations.md +142 -20
- package/docs/katex-bracket-scaling.md +191 -0
- package/package.json +3 -2
- package/src/compiler/cli.ts +139 -6
- package/src/compiler/collections.ts +144 -10
- package/src/compiler/course-config.ts +273 -34
- package/src/compiler/knowledge-version.ts +1 -1
- package/src/compiler/lesson-derivation.ts +54 -15
- package/src/compiler/logging.ts +21 -4
- package/src/compiler/manifest.ts +573 -104
- package/src/components/BlockHeading.astro +113 -0
- package/src/components/CourseContents.astro +221 -0
- package/src/components/CurriculumMap.astro +5 -6
- package/src/components/FloatIndex.astro +200 -0
- package/src/components/HoverPanel.astro +431 -79
- package/src/components/PartBridge.astro +27 -0
- package/src/components/PartOverview.astro +401 -0
- package/src/components/PartScope.astro +27 -0
- package/src/components/abbreviation/AbbreviationLayer.astro +49 -51
- package/src/components/assessments/AssessmentRunner.tsx +8 -5
- package/src/components/assessments/AssessmentSet.astro +23 -26
- package/src/components/block-heading.ts +146 -0
- package/src/components/card-arrow.ts +243 -0
- package/src/components/card-drag.ts +631 -0
- package/src/components/card-jump.ts +45 -0
- package/src/components/card-manager.ts +1589 -0
- package/src/components/card-memory.ts +177 -0
- package/src/components/card-placement.ts +504 -0
- package/src/components/card-side.ts +336 -0
- package/src/components/card-travel.ts +197 -0
- package/src/components/citation/CitationLayer.astro +77 -76
- package/src/components/diagram/Diagram.astro +161 -316
- package/src/components/disclosure/course-disclosure.ts +24 -0
- package/src/components/disclosure/hover-preview.ts +122 -42
- package/src/components/disclosure/persisted-disclosure.ts +3 -3
- package/src/components/disclosure/print-as-shown.ts +252 -0
- package/src/components/examples/CompactExample.astro +3 -1
- package/src/components/examples/CompactExamples.astro +101 -94
- package/src/components/figure/Figure.astro +174 -274
- package/src/components/figure/FloatCaption.astro +87 -0
- package/src/components/figure/figure-state.ts +58 -0
- package/src/components/figure/figure-tools.ts +87 -0
- package/src/components/float-name.ts +101 -0
- package/src/components/fold/Fold.astro +125 -0
- package/src/components/fold/fold-id.ts +40 -0
- package/src/components/memo/Memo.astro +162 -231
- package/src/components/memo/ShortNote.astro +6 -6
- package/src/components/notation/NotationFormula.astro +5 -1
- package/src/components/notation/NotationGlossary.astro +24 -14
- package/src/components/notation/NotationLayer.astro +169 -162
- package/src/components/notation/NotationMath.astro +10 -1
- package/src/components/notation/render-notation-math.ts +111 -10
- package/src/components/reference/FloatRefLayer.astro +485 -423
- package/src/components/starlight/AboutPanel.astro +22 -6
- package/src/components/starlight/CollapsibleSections.astro +207 -93
- package/src/components/starlight/DiagramEnhancer.astro +15 -138
- package/src/components/starlight/HeadingAnchorEnhancer.astro +193 -1
- package/src/components/starlight/KeyedFloatEnhancer.astro +100 -9
- package/src/components/starlight/LayoutHeader.astro +298 -19
- package/src/components/starlight/LayoutPageSidebar.astro +99 -12
- package/src/components/starlight/LayoutPageTitle.astro +120 -0
- package/src/components/starlight/LayoutSidebar.astro +229 -11
- package/src/components/starlight/LessonExportActions.astro +53 -72
- package/src/components/starlight/LessonFooter.astro +26 -4
- package/src/components/starlight/section-parts.ts +15 -2
- package/src/components/starlight/section-summary.ts +181 -0
- package/src/components/table/Table.astro +95 -76
- package/src/components/table/TableOverflowExpander.astro +65 -28
- package/src/content-config.ts +40 -4
- package/src/reference/base-accents.mjs +31 -0
- package/src/reference/base-roman-labels.mjs +2 -2
- package/src/reference/consistency.ts +176 -5
- package/src/reference/delimiter-size.ts +40 -61
- package/src/reference/diagrams-validate.ts +17 -1
- package/src/reference/equations-validate.ts +10 -5
- package/src/reference/equations.mjs +90 -44
- package/src/reference/examples-validate.ts +97 -0
- package/src/reference/figures-validate.ts +6 -1
- package/src/reference/folds-validate.ts +65 -0
- package/src/reference/gate-abbreviations.ts +3 -2
- package/src/reference/gate-course-rules.ts +134 -0
- package/src/reference/gate-heading-length.ts +68 -37
- package/src/reference/gate-heading-structure.ts +193 -0
- package/src/reference/gate-math-punctuation.ts +381 -0
- package/src/reference/gate-math.ts +119 -12
- package/src/reference/gate-part-pages.ts +217 -0
- package/src/reference/gate-prose-notation.ts +74 -13
- package/src/reference/katex-delimiters.mjs +138 -0
- package/src/reference/katex-options.mjs +4 -0
- package/src/reference/keyed-float-validate.ts +45 -11
- package/src/reference/keyed-jsx-floats.mjs +73 -20
- package/src/reference/knowledge-checks-validate.ts +1 -1
- package/src/reference/knowledge-checks.mjs +9 -4
- package/src/reference/lab-math-slot.ts +36 -0
- package/src/reference/math-glyphs.mjs +588 -39
- package/src/reference/raw-floats-validate.ts +1 -1
- package/src/reference/raw-floats.mjs +1 -1
- package/src/reference/rehype-heading-anchors.mjs +116 -17
- package/src/reference/rehype-table-header-labels.mjs +21 -3
- package/src/reference/remark-asides.mjs +180 -0
- package/src/reference/remark-memo.mjs +42 -11
- package/src/reference/remark-notation.mjs +387 -36
- package/src/reference/render-lab-math.ts +17 -32
- package/src/reference/sections-validate.ts +81 -0
- package/src/reference/sections.mjs +306 -0
- package/src/reference/tables-validate.ts +2 -1
- package/src/reference/types.ts +24 -0
- package/src/reference/unit-words.ts +233 -0
- package/src/rules/catalog.ts +1298 -0
- package/src/rules/course-rules.ts +456 -0
- package/src/rules/in-effect.ts +235 -0
- package/src/rules/settings.ts +448 -0
- package/src/rules/waivers.ts +249 -0
- package/src/session/PreferenceStore.ts +15 -0
- package/src/styles/global.css +1471 -163
- package/src/styles/surfaces.css +182 -0
- package/src/components/hover-panel.ts +0 -339
- package/src/components/popup-stack.ts +0 -345
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,1410 @@ under **Breaking** below; a PATCH never breaks.
|
|
|
14
14
|
|
|
15
15
|
## [Unreleased]
|
|
16
16
|
|
|
17
|
+
## [0.11.0] - 2026-09-28
|
|
18
|
+
|
|
19
|
+
### Breaking
|
|
20
|
+
|
|
21
|
+
- **Figures and diagrams no longer compact.** A figure or a diagram is laid
|
|
22
|
+
out as LaTeX lays it out, in every state: its picture, then its caption
|
|
23
|
+
line (docs/plans/figures-and-short-captions.md, Part 1). The caption
|
|
24
|
+
line's compaction toggle (`.float-caption__toggle`), the `<details>`
|
|
25
|
+
around a figure's or a diagram's body (`.keyed-figure__details`,
|
|
26
|
+
`.diagram__details`, `.float-compact`) and
|
|
27
|
+
`components/disclosure/float-compaction.ts` are gone, and print never
|
|
28
|
+
notes a compacted figure. To let a reader fold a float away, put it in a
|
|
29
|
+
collapsible section.
|
|
30
|
+
- **`disclosure.diagrams` is removed.** A course that still sets it fails
|
|
31
|
+
the build (`normalizeDisclosure` throws) with the edit to make: delete the
|
|
32
|
+
key. `disclosure.figures` stays, and now sets whether a lab's tools start
|
|
33
|
+
open — as `defaultOpen` and `rememberState` on a `<Figure>` now do for one
|
|
34
|
+
figure's tools (below).
|
|
35
|
+
- **`<Diagram defaultOpen>` and `<Diagram rememberState>` are removed.**
|
|
36
|
+
Either fails `pnpm validate:content` (`dia-removed-prop`), and a diagram
|
|
37
|
+
that a wrapper component renders fails when it renders.
|
|
38
|
+
- **The manifest schema is now v24**: `disclosure` loses `diagrams` and
|
|
39
|
+
gains `folds`; `diagnostics.folds` is new (below). A course script that
|
|
40
|
+
lists the diagnostic groups it blocks on (`compile-manifest.ts`) should
|
|
41
|
+
add `folds`.
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
45
|
+
- **Rule S9** (guide; docs/plans/figures-and-short-captions.md, Part 7): a
|
|
46
|
+
float's short caption names it, and its long caption goes on from that
|
|
47
|
+
name instead of restating it; a figure holds no running text, and a lab's
|
|
48
|
+
instructions go in its tools (`structure-rules.md#s9`). With the short
|
|
49
|
+
caption printed as the caption line's title, `components.md` now asks
|
|
50
|
+
for one on every figure, table and diagram.
|
|
51
|
+
- **`FloatIndex.astro`: the "Figures and tables" page**
|
|
52
|
+
(docs/plans/figures-and-short-captions.md, Part 6) — LaTeX's
|
|
53
|
+
`\listoffigures` and `\listoftables` in one. It lists every numbered
|
|
54
|
+
figure, table and diagram with an `id`, by part and chapter (chapters
|
|
55
|
+
numbered as their sections are), each as its number, linked to its anchor,
|
|
56
|
+
and its short caption — or its long caption, muted, without one, or the
|
|
57
|
+
number alone when the manifest has neither. The sidebar's Reference group
|
|
58
|
+
lists "Figures and tables" when the course has a top-level
|
|
59
|
+
`content/docs/figures-and-tables.mdx` (`buildSidebar` takes
|
|
60
|
+
`{ floatIndex }`).
|
|
61
|
+
- **A lab's tools: `<Figure>`'s `tools` slot**
|
|
62
|
+
(docs/plans/figures-and-short-captions.md, Part 3). The default slot is
|
|
63
|
+
the picture; the `tools` slot — an instruction line, the sliders and
|
|
64
|
+
fields, the live equation — renders after the caption line, in a tray of
|
|
65
|
+
its own (a thin border, no tint) inside a `<details>`. Only the tools
|
|
66
|
+
fold: a fold button ⌃ / ⌄ ends the caption line (`.float-tool`, "Hide the
|
|
67
|
+
controls of Figure 2.1.1"), and folded tools leave a "Hidden here: 2
|
|
68
|
+
sliders, 1 equation" line that unfolds them
|
|
69
|
+
(`components/figure/figure-tools.ts`). They start open unless the course's
|
|
70
|
+
`disclosure.figures` or the figure's `defaultOpen` says otherwise, and the
|
|
71
|
+
reader's choice is remembered per page (`rememberState`). Print and export
|
|
72
|
+
show the figure as LaTeX would: the tools never print, and
|
|
73
|
+
`prepareDisclosures` leaves a `details[data-print-omit]` alone. A figure
|
|
74
|
+
without the slot has no button and no tray. `global.css` sets a lab's
|
|
75
|
+
tools island in the tray: `.keyed-figure__tools .lab-shell` brings no
|
|
76
|
+
margin, a `.lab-tools` root drops its last child's, and `.lab-instruction`
|
|
77
|
+
is the tools' first line, what to do.
|
|
78
|
+
- **The caption line's live end, and `setFigureState`.** A `<Figure>`'s
|
|
79
|
+
caption line ends with an empty `span.float-caption__state[data-figure-state]`,
|
|
80
|
+
outside `.float-caption__text`, where a lab writes the case its plot shows
|
|
81
|
+
— `setFigureState(element, runs)` in `components/figure/figure-state.ts`,
|
|
82
|
+
with runs of text, italic and subscript as data, never HTML. It prints
|
|
83
|
+
with the caption. `FloatCaption.astro` takes `live` for it, and renders
|
|
84
|
+
its button row only when the slot holds a button.
|
|
85
|
+
- **`<Fold>`: collapsible content with no heading, number or marker**
|
|
86
|
+
(`components/fold/Fold.astro`; docs/plans/figures-and-short-captions.md,
|
|
87
|
+
Part 2). It folds like a memo — the section chevron, "Hidden here: 1
|
|
88
|
+
diagram" while folded, the reader's choice remembered per page, opened by a
|
|
89
|
+
deep link or find-in-page, printed as shown — but it is the plain tier (no
|
|
90
|
+
frame or tint), puts no letter in the text, has no `[[…]]` key, is not in
|
|
91
|
+
"On this page" and takes no outline number. Its bar, semibold at
|
|
92
|
+
body-text size, is its `label` or, without one, the numbers and short
|
|
93
|
+
captions of the numbered floats inside ("Diagram 2.2.1 Inputs to the
|
|
94
|
+
launch-angle search", `components/float-name.ts`). A bar that names the
|
|
95
|
+
floats is their name: their caption lines inside the fold drop the bold
|
|
96
|
+
short-caption title, which would repeat it. This is how a figure or a diagram becomes
|
|
97
|
+
collapsible outside a collapsible section, now that floats do not fold by
|
|
98
|
+
themselves.
|
|
99
|
+
- **`disclosure.folds`** (`{ defaultOpen }`, closed by default) sets whether
|
|
100
|
+
a fold starts open; `defaultOpen` on one `<Fold>` overrides it, and
|
|
101
|
+
`rememberState={false}` keeps the default on every visit.
|
|
102
|
+
- **`fold-unnamed`** (error, the keyed-floats invariant;
|
|
103
|
+
`reference/folds-validate.ts`): a `<Fold>` with no `label` and no numbered
|
|
104
|
+
`<Figure>`, `<Table>` or `<Diagram>` inside fails `validate:content`, and
|
|
105
|
+
the component throws on it when it renders.
|
|
106
|
+
- A fold is transparent to numbering, like a memo: the equations and floats
|
|
107
|
+
in one keep their places in the lesson's sequence. The scans read one list,
|
|
108
|
+
`TRANSPARENT_COMPONENTS` in `reference/equations.mjs`.
|
|
109
|
+
- **Explanation cards can sit beside the article.** On a window at least
|
|
110
|
+
72rem wide, a card's new Side button docks it in a side column to the
|
|
111
|
+
right of the article, level with its trigger's line
|
|
112
|
+
(docs/plans/hover-card-consistency.md, Part 2; `components/card-side.ts`).
|
|
113
|
+
For a reader:
|
|
114
|
+
- Docked cards are 18–23rem wide, never cover the navigation or the "On
|
|
115
|
+
this page" rail, stack top down without overlapping, and scroll with the
|
|
116
|
+
page; one taller than the window scrolls inside. Side again, a click on
|
|
117
|
+
the card's trigger, × or Escape work as on a pinned card; a click on
|
|
118
|
+
another occurrence of its item moves the card level with that line.
|
|
119
|
+
- The room comes from sliding the article left, only while cards are
|
|
120
|
+
docked and only as far as they need, never past the navigation. When
|
|
121
|
+
there is not enough room even then, the Side button says "Collapse a
|
|
122
|
+
side panel to make room"; below 72rem it is hidden.
|
|
123
|
+
- "Keep the article left", at the top of the side column, keeps the
|
|
124
|
+
article on the left after the last docked card closes. The reader's
|
|
125
|
+
choice is remembered across pages and visits.
|
|
126
|
+
- Pointing at another occurrence of a pinned or docked card's item, or at
|
|
127
|
+
a docked card itself, draws an arrow from the card to that occurrence;
|
|
128
|
+
it fades 1.5 s after both are left (`components/card-arrow.ts`).
|
|
129
|
+
- Moving into or out of the side and the article's slide animate in
|
|
130
|
+
100 ms, and not at all under reduced motion; neither applies in print.
|
|
131
|
+
- A card with no free spot next to its trigger now goes to the side
|
|
132
|
+
column at its line, where there is room, before it would cover another
|
|
133
|
+
card.
|
|
134
|
+
- `HoverPanel.astro` renders the Side button (`data-card-side`) before Pin,
|
|
135
|
+
and a note line (`data-card-note`) under the header row.
|
|
136
|
+
- **Explanation cards can be dragged.** A grip at the start of every card's
|
|
137
|
+
header moves the card anywhere between the sidebars, or into the side
|
|
138
|
+
column (docs/plans/hover-card-consistency.md, Part 3;
|
|
139
|
+
`components/card-drag.ts`). For a reader:
|
|
140
|
+
- Only the grip drags, so text in the card stays selectable; a drag starts
|
|
141
|
+
after 4 px and keeps the card open. Dropped anywhere else, the card
|
|
142
|
+
stays there: it scrolls with the page, follows its trigger when the text
|
|
143
|
+
above reflows, and is never dropped over the navigation or the "On this
|
|
144
|
+
page" rail. Dropped within 32 px of its spot next to its trigger, it
|
|
145
|
+
goes back there, pinned.
|
|
146
|
+
- Where the window has a side column, the docking area is tinted during a
|
|
147
|
+
drag. A card whose left edge comes within 32 px of the article's edge
|
|
148
|
+
snaps: a dashed line marks the edge, a dashed outline shows where the
|
|
149
|
+
card will land, and "Release to pin to the side" says what a drop does.
|
|
150
|
+
Released, it docks at the height it was dropped at, the article sliding
|
|
151
|
+
first when the column needs room. It lets go again only past 48 px;
|
|
152
|
+
holding Alt (Option) turns snapping off.
|
|
153
|
+
- The grip is a button, "Move card": arrow keys move the card 8 px (48 px
|
|
154
|
+
with Shift) and Home puts it back next to its trigger. Escape during a
|
|
155
|
+
drag cancels it; the next Escape closes the card.
|
|
156
|
+
- Pointing at, or clicking, another occurrence of a moved card's item
|
|
157
|
+
flashes the card and draws its arrow; the card stays where it is. A
|
|
158
|
+
moved card, like a docked one, hides its Pin button.
|
|
159
|
+
- `HoverPanel.astro` renders the grip (`data-card-grip`) at the start of
|
|
160
|
+
the header row; a moved card carries `data-card-mode="moved"`.
|
|
161
|
+
- **A page remembers its cards.** The cards a reader keeps (pinned, docked
|
|
162
|
+
or moved) come back where they were when the reader returns to the page:
|
|
163
|
+
through a link, Back or a reload, in another tab, or after the browser
|
|
164
|
+
restarts, including a card opened from inside another. Hovered cards are
|
|
165
|
+
not kept, and closing a page's last card forgets the page
|
|
166
|
+
(`components/card-memory.ts`, in local storage).
|
|
167
|
+
- `PreferenceStore` gains an optional `remove(key)`; the browser and memory
|
|
168
|
+
stores implement it. A store without it still fits the interface.
|
|
169
|
+
|
|
170
|
+
### Changed
|
|
171
|
+
|
|
172
|
+
- **A float's short caption shows** (docs/plans/figures-and-short-captions.md,
|
|
173
|
+
Part 5):
|
|
174
|
+
- it opens the caption line of a figure, table or diagram as a bold title,
|
|
175
|
+
`b.float-caption__title`, with a closing period unless it has its own
|
|
176
|
+
punctuation — "**Sampled flight data.** Position, …". It sits outside
|
|
177
|
+
`.float-caption__text`, so every reader of the long caption still reads
|
|
178
|
+
it alone. `FloatCaption.astro` takes `short`;
|
|
179
|
+
- a folded section's "Hidden here" line names each numbered float with a
|
|
180
|
+
short caption, number in bold, before the counts of everything else:
|
|
181
|
+
"Hidden here: **Figure 2.1.1** Projectile trajectory explorer, 2
|
|
182
|
+
paragraphs" (`section-summary.ts`: `collectHiddenContent`,
|
|
183
|
+
`hiddenContentParts`; `countHiddenContent` is gone). A `<Fold>` whose bar
|
|
184
|
+
names its floats keeps counting them;
|
|
185
|
+
- every chain icon copies a named link: the confirmation says "Link copied
|
|
186
|
+
· Figure 2.1.1 · Projectile trajectory explorer" (a section's, check's
|
|
187
|
+
or example set's title, a memo's short caption), and where the browser
|
|
188
|
+
has `ClipboardItem`, a `text/html` link reading "Figure 2.1.1,
|
|
189
|
+
Projectile trajectory explorer" or "§ 1.2.1 Velocity over time" goes on
|
|
190
|
+
the clipboard beside the URL. The confirmation bubble wraps inside a cap
|
|
191
|
+
instead of running past the column.
|
|
192
|
+
- **A figure's preview card copies its picture as drawn.** A hydrated
|
|
193
|
+
island in the picture (a lab's plot) becomes plain markup in the copy, so
|
|
194
|
+
it never hydrates again inside the card, its controls are disabled, and
|
|
195
|
+
every id in the copy is prefixed, as a diagram's already were. A lab whose
|
|
196
|
+
plot is not drawn yet — or is on another page, drawn only in the browser —
|
|
197
|
+
leaves the card to its caption and number. A lab no longer needs
|
|
198
|
+
`previewable={false}`.
|
|
199
|
+
- **A diagram's widen button ↔ is a boxed button at the end of its caption
|
|
200
|
+
line**, the `.float-tool` a table's caption has, shown while the graph is
|
|
201
|
+
wider than the column; `.diagram-toolbar` and `.diagram-expand-button`
|
|
202
|
+
are gone. `FloatCaption.astro` wraps its default slot in the caption's
|
|
203
|
+
`.float-tools` row, rendered only when the slot is filled, and names the
|
|
204
|
+
float in `data-float-name`; a caption line keeps room for its button row
|
|
205
|
+
only while a button shows.
|
|
206
|
+
- **The side panels sit above explanation cards.** The navigation and the
|
|
207
|
+
"On this page" rail, open or peeking while collapsed, now cover a card
|
|
208
|
+
that reaches them, instead of the card covering them; a reader who needs
|
|
209
|
+
a panel's room collapses it. The site header stays above both. While a
|
|
210
|
+
table or diagram is expanded, the panels drop back under it as before.
|
|
211
|
+
- **A slimmer site header.** It is 3rem tall at every width (Starlight's
|
|
212
|
+
default is 3.5rem, and 4rem from 50rem up), with the site title at
|
|
213
|
+
`--sl-text-xl`, the logo sized to the height, and a 2rem search box.
|
|
214
|
+
Everything placed under the header follows `--sl-nav-height`; a course
|
|
215
|
+
that sizes its own header content should check it still fits.
|
|
216
|
+
- **Every explanation card now works the same way.** Notation symbols and
|
|
217
|
+
`[[key]]` terms, citations, abbreviations, and references to equations,
|
|
218
|
+
tables, figures, diagrams and sections used to run on four engines with
|
|
219
|
+
four sets of rules; one card manager (`components/card-manager.ts`) now
|
|
220
|
+
runs them all (docs/plans/hover-card-consistency.md, Part 1). For a reader:
|
|
221
|
+
- Hovering or keyboard-focusing a trigger opens its card, and the card
|
|
222
|
+
stays open while the pointer travels to it, even from a symbol at the
|
|
223
|
+
top of a tall equation. A symbol crossed on the way no longer takes
|
|
224
|
+
over; resting on it for a moment does.
|
|
225
|
+
- A click (or a tap) on any trigger pins its card and a second click
|
|
226
|
+
closes it. It never navigates: that is the card's own link, now worded
|
|
227
|
+
"Go to …" ("Go to the full definition", "Go to the reference list",
|
|
228
|
+
"Go to equation (1.4)"). A reference whose target is already fully on
|
|
229
|
+
screen still highlights it in place and navigates, and a standalone
|
|
230
|
+
export's cross-page references stay plain links. Diagram references pin
|
|
231
|
+
like the others.
|
|
232
|
+
- Clicks inside a card never close it, so text in a card can be selected
|
|
233
|
+
and copied; a click elsewhere never closes a pinned card. Every card
|
|
234
|
+
has a Pin toggle and a × in its header, and a pinned card shows an
|
|
235
|
+
accent border. Abbreviation cards can be pinned; the citation card's own
|
|
236
|
+
Pin button is gone.
|
|
237
|
+
- Every layer shows several cards at once, one per item; opening a card
|
|
238
|
+
closes only the hovered cards outside its chain, never a pinned one. A
|
|
239
|
+
symbol inside a card opens its own card as a child of it, across layers.
|
|
240
|
+
- Cards are placed clear of each other and of the equation their symbol
|
|
241
|
+
sits in, never at the top of the window, and scroll with the page.
|
|
242
|
+
- Escape closes one card per press, the newest first, across all layers.
|
|
243
|
+
- A `[[key]]` term no longer shows the browser's tooltip on top of its
|
|
244
|
+
card; the `title` stays in the HTML for readers without JavaScript.
|
|
245
|
+
- The collapsed-content peeks (a memo marker's preview, a collapsed
|
|
246
|
+
section's) keep their behaviour but close on the cards' terms: after a
|
|
247
|
+
short grace, not the moment the pointer steps off them.
|
|
248
|
+
- `HoverPanel.astro` keeps its props but renders a `<template>` that each
|
|
249
|
+
layer clones per card, with a header row (a `heading` slot, the Pin toggle
|
|
250
|
+
and ×) above its default slot.
|
|
251
|
+
- **A card's "Go to …" link says where it lands.** It ends in ↑ or ↓ for a
|
|
252
|
+
place on the same page (above or below the middle of the view), → for
|
|
253
|
+
another page of the course, and ↗ only for a citation's source, the one
|
|
254
|
+
link that leaves the site; every one used to end in ↗. A float reference
|
|
255
|
+
to another lesson names it: "Go to equation (1.1.6) in Position and
|
|
256
|
+
Coordinate Systems →" (`components/card-jump.ts`, `[data-jump-arrow]`).
|
|
257
|
+
- **Smaller headings, one even step apart.** From 50em up h1–h5 are 36, 28,
|
|
258
|
+
22, 18 and 16 px (Starlight's 42, 35, 29, 24, 18); below it 30, 24, 20,
|
|
259
|
+
17 and 16 (35, 29, 24, 20, 18). An h2 fitted some 25 characters of a
|
|
260
|
+
lesson column. The space above a section (`1.5em`), a folded bar and the
|
|
261
|
+
section chevron follow the same `--sl-text-h*` tokens, set in
|
|
262
|
+
`global.css`.
|
|
263
|
+
- **Knowledge-check and worked-example headings.** The kind's icon leads
|
|
264
|
+
the title, and the kind with the number ("KNOWLEDGE CHECK 1.2.1",
|
|
265
|
+
"EXAMPLE 1.2.1") is a small label in the card's top-right corner, so the
|
|
266
|
+
title keeps the whole line and the number no longer reads as a section
|
|
267
|
+
number. The heading's accessible name is unchanged. Markup:
|
|
268
|
+
`.block-badge` holds the word and `.block-heading__number`, and the icon
|
|
269
|
+
is `.block-heading__icon`, before `.block-heading__title` (it was
|
|
270
|
+
`.block-badge__icon`, inside the badge); course CSS on the badge's inside
|
|
271
|
+
moves with it.
|
|
272
|
+
- **A `[card=meta]` section is unnumbered**, with every heading inside it,
|
|
273
|
+
like References: logistics — what a chapter builds on, how to read it —
|
|
274
|
+
is outside the outline, and the sections after it keep their numbers
|
|
275
|
+
(`sectionNumbers`' `unnumbered` in `reference/sections.mjs`, read with
|
|
276
|
+
`headingCardTier` from `reference/rehype-heading-anchors.mjs`, in both the
|
|
277
|
+
manifest's walk and the page's).
|
|
278
|
+
- **A table's buttons sit together at the top right of its caption line.**
|
|
279
|
+
A widen button ↔, shown when the table is wider than the column, does
|
|
280
|
+
what the column at the table's right edge does (that column stays, and
|
|
281
|
+
so do the collapse buttons on both sides of a widened table); the fold
|
|
282
|
+
button next to it is a chevron, ⌃ / ⌄, where it was ▴ / ▾. Both use the
|
|
283
|
+
new boxed `.float-tool` style in a `.float-tools` row (`global.css`); the
|
|
284
|
+
fold toggle keeps `.keyed-table__pagination-toggle` but is no longer a
|
|
285
|
+
`.table-overflow-expand-btn`.
|
|
286
|
+
- **Less empty space around a lesson.** Accent and meta cards pad 1rem at
|
|
287
|
+
the top and bottom and up to 1.25rem at the sides (they were up to 1.5rem
|
|
288
|
+
all round); the first block under a lesson's title adds no margin to the
|
|
289
|
+
content panel's padding; Previous / Next sit side by side down to a 26rem
|
|
290
|
+
column, with 1.125rem titles, where they stacked below 36rem; and the
|
|
291
|
+
3rem Starlight keeps above its "last updated" and edit links is gone when
|
|
292
|
+
a course shows neither.
|
|
293
|
+
- **The notation panel's help text is inside the panel**, under its title,
|
|
294
|
+
shown when the panel opens, instead of three lines under the closed one.
|
|
295
|
+
- **A knowledge check's score** is empty until the first answer, then
|
|
296
|
+
reads "2 of 3 correct" under the questions, so it never pushes down the
|
|
297
|
+
question just answered. The "3 questions" line above an unanswered check
|
|
298
|
+
is gone; the list shows the questions.
|
|
299
|
+
|
|
300
|
+
### Removed
|
|
301
|
+
|
|
302
|
+
- `components/hover-panel.ts` (`createHoverPanel`, `claimActiveSurface`,
|
|
303
|
+
`releaseActiveSurface`) and `components/popup-stack.ts`, internal engines
|
|
304
|
+
outside the public surface in `docs/VERSIONING.md`. A layer registers a
|
|
305
|
+
`CardSource` with `registerCardSource` instead, and an overlay that must
|
|
306
|
+
clear the hovered cards calls `closeTransientCards()`.
|
|
307
|
+
|
|
308
|
+
### Fixed
|
|
309
|
+
|
|
310
|
+
- A folded section, memo or fold that the browser opens by itself — to show
|
|
311
|
+
a find-in-page match or a linked fragment inside it — now opens for real:
|
|
312
|
+
its bar reads open, "Hidden here" goes, and its body shows even after the
|
|
313
|
+
reader folded it (the fold animation's `max-height: 0` stayed behind). The
|
|
314
|
+
reader's choice is saved as open, as a click would save it.
|
|
315
|
+
- A heading's chain icon, including a knowledge check's, a worked
|
|
316
|
+
example's and a memo's, now says "Copy link" on hover, as a float's
|
|
317
|
+
already did: a click on it puts the link on the clipboard. The title
|
|
318
|
+
comes from `HeadingAnchorEnhancer.astro` and only where a click copies:
|
|
319
|
+
on a page without it, or in a browser without the Clipboard API, the icon
|
|
320
|
+
still only navigates and has no title.
|
|
321
|
+
- On a phone, or any touch screen, a knowledge check's and a worked
|
|
322
|
+
example's badge icon was drawn over the first letter of its word
|
|
323
|
+
("⊞AMPLE"): the heading's hanging indent reached into the badge.
|
|
324
|
+
- A long table's "… N more rows …" label is centred on the part of the table
|
|
325
|
+
on screen. It was centred on the whole table, columns scrolled out of view
|
|
326
|
+
included, so on a table wider than the column it sat far to the right.
|
|
327
|
+
- A worked example's closing display equation kept its bottom margin
|
|
328
|
+
inside the card: `CompactExample.astro`'s scoped `:last-child` rule never
|
|
329
|
+
matched slotted content.
|
|
330
|
+
- A reference to an equation, table or figure inside a closed memo or a
|
|
331
|
+
folded section now opens its preview card. The fold clips its content but
|
|
332
|
+
keeps its box on screen, so the reference counted it as visible: it stayed
|
|
333
|
+
a plain link with nothing to show, and a click jumped into the closed memo.
|
|
334
|
+
While the card is open, the fold's bar is marked (`.float-ref-fold-hint`),
|
|
335
|
+
so the reader sees where the target lives.
|
|
336
|
+
- A figure's preview card shows the figure: an SVG sized by its `viewBox`
|
|
337
|
+
alone had no width of its own in the card and collapsed to nothing, leaving
|
|
338
|
+
only the caption. The card's content now takes the card's width, and a
|
|
339
|
+
figure at least 22rem of it.
|
|
340
|
+
|
|
341
|
+
## [0.10.0] - 2026-09-27
|
|
342
|
+
|
|
343
|
+
### Breaking
|
|
344
|
+
|
|
345
|
+
- **Float numbers are chapter-scoped: `Figure 2.1` is now `Figure 2.2.1`.**
|
|
346
|
+
A display equation, `<Table>`, `<Figure>`, `<Diagram>` and knowledge check
|
|
347
|
+
is numbered `‹part›.‹chapter›.‹n›` and every kind restarts in each chapter,
|
|
348
|
+
the LaTeX book convention — `2.1` would read as "part 2, chapter 1" once
|
|
349
|
+
sections carry `part.chapter.section` numbers. `[[…]]` references need no
|
|
350
|
+
edit: they print from the manifest. What changes for a course is every
|
|
351
|
+
number a reader sees, the positional anchor of an *unkeyed* float
|
|
352
|
+
(`#eq-2-4` → `#eq-2-2-4`), and any e2e test or prose that quotes a number.
|
|
353
|
+
`numbering.floats: "part"` in `course.settings.json` keeps the old
|
|
354
|
+
`‹part›.‹n›` scheme.
|
|
355
|
+
- **The manifest schema is now v20**: `numbering` carries the course's
|
|
356
|
+
resolved numbering settings; `lessons[].headings` lists every top-level
|
|
357
|
+
heading with its anchor (`slug`), `text`, `level` and, when numbered, its
|
|
358
|
+
`number`; `lessons[].numbering` gains `prefix`, and under the default chapter
|
|
359
|
+
scope its per-kind counts, which used to carry a part's running total, are
|
|
360
|
+
all 0. `remarkNotation` reads all of it off the manifest (`sections` and
|
|
361
|
+
`sectionNumbering` join its options), gives each heading its anchor and
|
|
362
|
+
`data-sec-number`, and fails the build when a page's top-level headings are
|
|
363
|
+
not the ones the manifest numbered.
|
|
364
|
+
- **Six overlapping rules are folded into four; their IDs are retired.** Rules
|
|
365
|
+
N1 (the normal distribution is `\mathcal{N}`), N6 (probability is
|
|
366
|
+
`\mathbb{P}`) and N7 (the differential is `\mathrm{d}`) are now lines in
|
|
367
|
+
**rule N2**, which becomes "a reserved spelling means only what it is
|
|
368
|
+
reserved for, and every other styled letter is a symbol of its own" and
|
|
369
|
+
carries the table as a course setting, `rules.N2.reserved` (default: the four
|
|
370
|
+
number systems, `\mathbb{P}`, `\mathcal{N}(\mu, \sigma^2)`, `\mathrm{d}`)
|
|
371
|
+
— so a course edits its conventions in `course.settings.json` instead of
|
|
372
|
+
overriding a rule page, and an agent reads the effective table off
|
|
373
|
+
`build/rules-in-effect.md`. Rule N3 (write a multi-letter label as
|
|
374
|
+
`\mathrm{…}`) is folded into **rule N10**, which becomes "a multi-letter
|
|
375
|
+
roman label is written `\mathrm{…}`, and it is universal or it is explained"
|
|
376
|
+
and is now `partial`: the format half never had a check of its own — rule
|
|
377
|
+
N4's gate reports a bare `PV` as unresolved one-letter symbols. Rule N16
|
|
378
|
+
(alternate delimiter shapes for stylistic nesting) is folded into **rule
|
|
379
|
+
N17**, which becomes "a delimiter's shape carries its meaning and never
|
|
380
|
+
changes to show nesting: a nest is sized by the engine", also `partial`. The
|
|
381
|
+
preference for alternating shapes is **reversed**, not just moved: this
|
|
382
|
+
engine sizes a nest automatically, and a `[` inside a grouping nest reads as
|
|
383
|
+
an interval, an expectation or a matrix first. N16's "in AI-generated
|
|
384
|
+
content" scope is gone with it — a rule holds for whoever typed the page.
|
|
385
|
+
Rule C4 (a release moves `[Unreleased]` and bumps `about.version` together)
|
|
386
|
+
is folded into **rule C3**, whose `knowledge-changelog-missing` was already
|
|
387
|
+
the check that fired on a half-finished release; C3 is now `partial`. Each
|
|
388
|
+
retired ID keeps its number so an older commit message still resolves, but
|
|
389
|
+
nothing else: `RuleEntry.retired` is **gone**, and a retired rule leaves the
|
|
390
|
+
catalog for a new compact list (`RETIRED`, read by `retiredRules()` /
|
|
391
|
+
`retiredRule(id)`) holding only `{ id, title, date, into?, why }`. It has no
|
|
392
|
+
rule page, no row in the index, no entry in the settings schema and no line on
|
|
393
|
+
`build/rules-in-effect.md`, so an author or an agent that is drafting never
|
|
394
|
+
reads it; the one place it is written out is the generated table on the new
|
|
395
|
+
`docs/authoring/retired.md`, which is where a migration guide links.
|
|
396
|
+
`gen-rules.mjs` writes and `--check`s that table alongside the index. A course
|
|
397
|
+
with `rules.N1` / `rules.N3` / `rules.N6` / `rules.N7` / `rules.N16` /
|
|
398
|
+
`rules.C4` in its settings still gets a `config-retired-rule` warning — now
|
|
399
|
+
naming the successor and the date — and should move the block to the surviving
|
|
400
|
+
rule (only N2 takes settings).
|
|
401
|
+
- **Rule N9 catches a unit written as a word.** "a time of 4 seconds", "2
|
|
402
|
+
meters per second squared", "30 degrees above the horizontal" and
|
|
403
|
+
"$x = 3$ meters" now fail `pnpm validate:content` with the new code
|
|
404
|
+
`spelled-unit` (owned by rule N9), in lesson bodies and assessment
|
|
405
|
+
`prompt` / `explanation`; the message gives the math to write instead
|
|
406
|
+
(`$4\ \text{s}$`, `$2\ \text{m/s}^{2}$`, `$30^{\circ}$`,
|
|
407
|
+
`$x = 3\ \text{m}$`). The unit words live in `src/reference/unit-words.ts`
|
|
408
|
+
(SI-style units and their common prefixes; not calendar time or
|
|
409
|
+
currency). Flagged: a unit word after a number (digits or words, plain or
|
|
410
|
+
typeset), before or after `per`, or with `square` / `cubic` / `squared` /
|
|
411
|
+
`cubed`. A unit named on its own ("measured in meters"), "degrees of
|
|
412
|
+
freedom" and `second-order` pass. Rule N9's old carve-out for a
|
|
413
|
+
spelled-out unit in flowing prose is gone. A course with a violation
|
|
414
|
+
rewrites it, waives a defining sentence with
|
|
415
|
+
`{/* explico-allow N9: reason */}`, or lists unit words it keeps as words
|
|
416
|
+
in the new `rules.N9.allow` setting. `proseOnly` takes
|
|
417
|
+
`{ keepMath: true }`; `gateProseNotation` / `gateProseNotationInAssessments`
|
|
418
|
+
take `{ allowSpelledUnits }`.
|
|
419
|
+
- **Rule N12 is build-enforced: math never ends in the sentence's own
|
|
420
|
+
punctuation.** `gate-math-punctuation.ts` (`gateMathPunctuation`,
|
|
421
|
+
`gateMathPunctuationInAssessments`) reads every `$…$` / `$$…$$` span in a
|
|
422
|
+
lesson body and in assessment `prompt` / `explanation`. It fails
|
|
423
|
+
`pnpm validate:content` with `math-trailing-punctuation` when a `.`, `,`,
|
|
424
|
+
`;` or `:` is the last visible thing in the span, or in a row before a
|
|
425
|
+
`\\`. It looks through spacing, `\label{…}` / `\tag{…}`, row breaks,
|
|
426
|
+
`\end{…}` and `\right.`, and into a closing `\text{…}` (`5\ \text{m,}`). A
|
|
427
|
+
trailing decimal point (`5.`) and a typed `...` fail too; rule N12 no
|
|
428
|
+
longer lists a decimal point as an exception. An upright dotted
|
|
429
|
+
abbreviation that is notation passes: the shipped `a.e.`, `a.s.`, `c.c.`,
|
|
430
|
+
`const.`, `h.c.`, `i.i.d.`, edited with the new `rules.N12.allow` /
|
|
431
|
+
`rules.N12.deny` settings. A course with a violation fixes the math, waives
|
|
432
|
+
the place with `{/* explico-allow N12: reason */}`, or turns rule N12 off
|
|
433
|
+
in `rules`. See `docs/authoring/notation.md#n12`.
|
|
434
|
+
- **A font-styled letter is its own symbol (rule N4).** `\mathbf{x}`,
|
|
435
|
+
`\boldsymbol{\beta}`, `\bm{x}`, `\mathcal{N}`, `\mathscr{L}`,
|
|
436
|
+
`\mathfrak{g}`, `\mathbb{E}`, `\mathsf{x}` and `\mathtt{x}` no longer
|
|
437
|
+
borrow the notation card of the plain letter on the page, and a styled
|
|
438
|
+
base-library letter (`\mathbf{e}`, `\boldsymbol{\pi}`) no longer passes as
|
|
439
|
+
Euler's number or π. Each needs its own entry (`latex: \mathbf{x}`) or an
|
|
440
|
+
`\explain{key}{\mathbf{x}}` wrap, or rule N4's completeness gate fails with
|
|
441
|
+
`unresolved-glyph` — in lesson bodies, notation formulas, assessments and
|
|
442
|
+
labs alike; the message says the styled letter is a different symbol.
|
|
443
|
+
`\mathit` / `\mathnormal` still share the plain letter's card, and
|
|
444
|
+
`\mathbb{N,Z,R,C}` stay reserved. The wrap for `\boldsymbol`, `\bm`,
|
|
445
|
+
`\bold`, `\Bbb`, `\frak`, `\mathsf`, `\mathtt` and `\mathnormal` now
|
|
446
|
+
lands around the command (`\explain{k}{\boldsymbol{\beta}}`), not inside
|
|
447
|
+
it.
|
|
448
|
+
|
|
449
|
+
- **Every float is named by one caption pair: a long `caption` and an
|
|
450
|
+
optional `short` caption** (LaTeX's `\caption[short]{long}`). The other
|
|
451
|
+
name props are removed outright, with no alias period; each now fails the
|
|
452
|
+
build with the edit to make:
|
|
453
|
+
- `<Figure title="…">` → `short="…"` (the title bar is gone; the long
|
|
454
|
+
`caption` stays required).
|
|
455
|
+
- `<Diagram label="…">` → `caption="…"`, now shown below the graph; add
|
|
456
|
+
`short` for a long caption. A `<Diagram title>` — never a prop, but the
|
|
457
|
+
rule S4 example used it — fails the same way.
|
|
458
|
+
- `<Memo title="…" description="…">` → `caption="…"` (required: the memo's
|
|
459
|
+
bar and the head of its preview), plus `short="…"` (optional: the
|
|
460
|
+
marker's tooltip). A memo with no title is fine; one with no caption
|
|
461
|
+
fails.
|
|
462
|
+
- `<CompactExamples summary="…">` → `short="…"`.
|
|
463
|
+
|
|
464
|
+
`pnpm validate:content` reports a `<Figure title>` as `fig-removed-prop`
|
|
465
|
+
and a `<Diagram label>` / `title` as `dia-removed-prop` (errors, owned by
|
|
466
|
+
the keyed-floats invariant; `tab-removed-prop` is registered for the same
|
|
467
|
+
shared check); `remarkMemo` fails a `<Memo title>` / `description`; each
|
|
468
|
+
component throws on its removed prop when it renders.
|
|
469
|
+
- **A figure and a diagram put their number in a caption line below the
|
|
470
|
+
body, and compacting keeps that line.** The line reads `Figure 2.2.1`
|
|
471
|
+
(a link to the figure), a chain icon, and the long caption; the compaction
|
|
472
|
+
toggle sits beside it (in the gutter on a wide screen, at the start of the
|
|
473
|
+
line on a narrow or touch one), and a compacted float hides only its body —
|
|
474
|
+
its number, anchor and caption stay (a collapsed figure used to hide its
|
|
475
|
+
number). A diagram's number leaves its title bar for the same line, and its
|
|
476
|
+
widen control moves to the end of it; the curriculum map's competency graph
|
|
477
|
+
gets the same caption line. The float's `<details>` keeps a
|
|
478
|
+
native `<summary>` that shows only under `(scripting: none)`. Selectors that
|
|
479
|
+
moved: `.keyed-figure__titlebar`, `.keyed-figure__summary`,
|
|
480
|
+
`.keyed-figure__title-anchor`, `.diagram__titlebar`, `.diagram__summary`
|
|
481
|
+
and `.diagram__title-anchor` are gone; `.diagram__number` is now a link;
|
|
482
|
+
the caption line is `figcaption.float-caption` with `.float-caption__toggle`,
|
|
483
|
+
`.float-caption__number`, `.float-caption__anchor` and `.float-caption__text`
|
|
484
|
+
(`src/components/figure/FloatCaption.astro`,
|
|
485
|
+
`src/components/disclosure/float-compaction.ts`).
|
|
486
|
+
- **A memo's marker is a superscript letter**, `a`, `b`, … per page, in
|
|
487
|
+
italics — the footnote convention — where it was a bracketed `[1]` that read
|
|
488
|
+
as a citation. The same letter is the memo's permalink on its bar;
|
|
489
|
+
`data-memo-number` and the `number` prop `remarkMemo` injects carry the
|
|
490
|
+
letter (`memoLetter(n)` is exported from `remark-memo.mjs`).
|
|
491
|
+
- **The manifest schema is now v21**: `lessons[].equations` / `.tables` /
|
|
492
|
+
`.figures` / `.diagrams` entries, and the `labels` of `equations` /
|
|
493
|
+
`tables` / `figures` / `diagrams`, carry the float's `short` caption and,
|
|
494
|
+
for a `<Table>` / `<Figure>` / `<Diagram>`, its long `caption`, each when
|
|
495
|
+
the source states it as a literal (`ManifestLessonFloat`).
|
|
496
|
+
- **Rule S1 measures the short caption, not a title.** It caps every
|
|
497
|
+
heading, the `short` prop of a `Figure`, `Diagram`, `Table`, `Memo` and
|
|
498
|
+
`CompactExamples`, a display equation's `\caption{…}`, and a `ShortNote`'s
|
|
499
|
+
`title`; a long `caption` is never measured. A course whose `Figure title`
|
|
500
|
+
passed S1 moves it to `short`, where it is measured the same way.
|
|
501
|
+
|
|
502
|
+
- **Every collapse default is one `disclosure` block: `CourseConfig.disclosure`
|
|
503
|
+
replaces `CourseConfig.disclosureDefaults`.** It takes the course settings'
|
|
504
|
+
`disclosure` block in its own shape — `sections` / `subsections` /
|
|
505
|
+
`knowledgeChecks` as `{ collapsible, defaultOpen }`, `notation` /
|
|
506
|
+
`references` / `memos` / `figures` / `diagrams` / `workedExamples` as
|
|
507
|
+
`{ defaultOpen }`, `tables` as `{ truncateAfter }` — and
|
|
508
|
+
`normalizeDisclosure` (with `DISCLOSURE_DEFAULTS`) replaces
|
|
509
|
+
`normalizeDisclosureDefaults`. A course passes the block straight through
|
|
510
|
+
(`course.config.ts` no longer splits it off) and reads it back in
|
|
511
|
+
`astro.config.mjs` for `rehypeHeadingAnchors` (`defaultCollapsible:
|
|
512
|
+
courseConfig.disclosure`) and `remarkCitation` (`defaultOpen`); the old
|
|
513
|
+
`disclosureDefaults: { notation, references }` mapping goes. **The manifest
|
|
514
|
+
schema is now v22**: `manifest.disclosure` (`NormalizedDisclosure`, every
|
|
515
|
+
default applied) replaces `manifest.disclosureDefaults`.
|
|
516
|
+
- **A `<Memo>` is a `######`-rank section.** Its bar is an `<h6>` (the
|
|
517
|
+
`#memo-…` anchor, `role="none"`, carrying the caption and, as generated
|
|
518
|
+
content, the letter) in a heading wrapper `CollapsibleSections` folds like
|
|
519
|
+
any section — gutter chevron, hover peek, "Hidden here: …" line, animation,
|
|
520
|
+
persistence under `section:memo-…`, a chain icon that copies "Link copied ·
|
|
521
|
+
Memo a". `.memo-block` is now the frame (it was the `<details>`); the
|
|
522
|
+
`<details>`, `.memo-block__summary`, the letter link `[data-memo-self-link]`
|
|
523
|
+
and the separate chain link `[data-memo-anchor-link]` are gone. The marker
|
|
524
|
+
in the prose still opens it, goes there and folds it again. It is not a
|
|
525
|
+
heading of the lesson: not in the outline, "On this page" or the heading
|
|
526
|
+
rules.
|
|
527
|
+
|
|
528
|
+
- **A knowledge check and a set of worked examples draw their own heading;
|
|
529
|
+
the author writes only the topic.** Both render the new `BlockHeading.astro`
|
|
530
|
+
`<h2>`: the kind's badge (an icon and "Knowledge check" or "Example"), the
|
|
531
|
+
number, then the author's title — the assessment file's `title`, the set's
|
|
532
|
+
`short`. A check's heading was `Knowledge check 1.2.1 — ‹title›` in one
|
|
533
|
+
span (`.assessment-heading-number` is gone), so an assessment title that
|
|
534
|
+
repeats the kind ("Velocity check") now reads twice: write the topic alone.
|
|
535
|
+
**`<CompactExamples>` requires `id` and `short`**: `short` no longer
|
|
536
|
+
defaults to "Worked examples", and a set without either fails
|
|
537
|
+
`pnpm validate:content` (`ex-missing-prop`) and its own render, as
|
|
538
|
+
`summary` does (`ex-removed-prop`, now reported at validation too).
|
|
539
|
+
**A set is a `##` section**, folded by `CollapsibleSections` like a memo —
|
|
540
|
+
chevron, peek, "Hidden here: 2 worked examples", print-as-shown — instead of
|
|
541
|
+
its own `<details>`: `details.compact-examples`,
|
|
542
|
+
`.compact-examples__summary` and `.compact-examples__title` are gone
|
|
543
|
+
(`<compact-examples class="compact-examples">` is the accent card, the
|
|
544
|
+
heading is `#ex-‹id›`), and `hint` has no default ("Open and choose an
|
|
545
|
+
example." described the old disclosure; the folded bar now says what is
|
|
546
|
+
inside). Its heading is part of the outline:
|
|
547
|
+
it ends the section before it, and rule S6 and rule S7 read it as a `##`
|
|
548
|
+
(rule S7 already counted it as a heading), so a `###` → set → `####` is now
|
|
549
|
+
a `heading-skip`. `ex-` joins the reserved reference prefixes: a notation
|
|
550
|
+
key written `[[ex-…]]` is read as a set reference.
|
|
551
|
+
- **The manifest schema is now v23**: `examples` (the `<CompactExamples>`
|
|
552
|
+
number table, same shape as `figures`), `lessons[].examples`
|
|
553
|
+
(`ManifestLessonFloat`, `short` the title), `lessons[].numbering.examples`,
|
|
554
|
+
`diagnostics.examples`; `lessons[].knowledgeChecks` entries gain `title`;
|
|
555
|
+
`lessons[].titledBlocks` (`ManifestTitledBlock`: each check and set with its
|
|
556
|
+
heading's `anchor`, `title` and the top-level heading it follows, `after`);
|
|
557
|
+
and `blockLabels`. A course's own `compile-manifest.ts` that lists the
|
|
558
|
+
diagnostics groups must add `examples`, or an `ex-…` error does not fail
|
|
559
|
+
`validate:content` (the page build still fails on it).
|
|
560
|
+
|
|
561
|
+
### Added
|
|
562
|
+
|
|
563
|
+
- **Outline numbering.** A lesson's top-level headings are numbered
|
|
564
|
+
`‹part›.‹chapter›.‹section›[.‹subsection›…]` — `1.1.2`, `1.1.2.1` — down to
|
|
565
|
+
`####`, printed before the title on a wide screen and in print, and on a
|
|
566
|
+
collapsed section's bar at every width; an open section on a narrow screen
|
|
567
|
+
shows none. The number is CSS generated content from `data-sec-number`, so
|
|
568
|
+
the heading's text, its anchor and the "On this page" entry are unchanged.
|
|
569
|
+
Configured by the new `numbering` block in `course.settings.json`
|
|
570
|
+
(`CourseConfig.numbering`): `sections.show` (default `true`),
|
|
571
|
+
`sections.depth` (`2`–`6`, default `4`), `floats` (`"chapter"`, the
|
|
572
|
+
default, or `"part"`) and `aliases` (default `true`); a course's settings
|
|
573
|
+
schema needs the block to offer it in the form editor
|
|
574
|
+
(`playground/content/course.settings.schema.json` has it). A heading
|
|
575
|
+
inside a component (`<Memo>`, `<CompactExample>`) is not part of the
|
|
576
|
+
outline. The manifest decides each top-level heading's anchor — the same
|
|
577
|
+
slug Astro gives a plain-text heading — and the page takes it from there,
|
|
578
|
+
so a heading with inline math now gets a slug from its source (`$R$` → `r`)
|
|
579
|
+
instead of from KaTeX's output.
|
|
580
|
+
- **`[[sec-slug]]` cites a section**, and `[[lesson/slug#sec-slug]]` one on
|
|
581
|
+
another page: it prints `§ 1.1.2` (the title in quotes when the heading has
|
|
582
|
+
no number, or the course hides them) and gets the float-reference hover card,
|
|
583
|
+
with the section's title and first paragraph. `sec-` joins the reserved
|
|
584
|
+
reference prefixes. A reference to no top-level heading is a
|
|
585
|
+
`sec-ref-resolves` error (owned by the `keyed-floats` invariant, reported in
|
|
586
|
+
`diagnostics.math`), and the build fails on it too. `content context
|
|
587
|
+
--json` lists the lesson's `sections` — anchor, text, level and number — so
|
|
588
|
+
a drafter can cite one without deriving its slug.
|
|
589
|
+
- **Number aliases.** With `numbering.aliases` on, a fragment a reader types in
|
|
590
|
+
number form — `#sec-1.1.2`, `#fig-2.2.1`, `#eq-1.1.4`, `#tab-…`, `#dia-…`,
|
|
591
|
+
`#check-…` — lands on the element that shows that number on the page, and
|
|
592
|
+
the address bar switches to its named anchor (`KeyedFloatEnhancer` takes an
|
|
593
|
+
`aliases` prop; `LessonFooter` passes the manifest's). Inbound only: the
|
|
594
|
+
site links and copies named anchors alone. A knowledge check's heading
|
|
595
|
+
carries `data-check-number` for this.
|
|
596
|
+
- **A heading's chain icon copies its link.** One click puts the page URL with
|
|
597
|
+
the heading's named anchor on the clipboard (the page's canonical URL when it
|
|
598
|
+
declares one) and shows "Link copied · § 1.1.2" under the title, announced
|
|
599
|
+
through a polite live region; the click still navigates and highlights, and
|
|
600
|
+
a refused clipboard leaves exactly that. On a narrow screen the icon is
|
|
601
|
+
always visible, smaller and muted, since it is where the section number
|
|
602
|
+
surfaces there.
|
|
603
|
+
- **Container tiers.** `explico/styles/surfaces.css` defines the style
|
|
604
|
+
primitives once — `--ex-surface-*` (`none`, `tint-neutral`,
|
|
605
|
+
`tint-accent`), `--ex-edge-*` (a 1px border, a 3px left rule),
|
|
606
|
+
`--ex-depth-*` (`flat`, `raised`) and one `--ex-radius-*` per boxed tier —
|
|
607
|
+
for both themes, and composes them into `.ex-tier-meta` (grey, bordered,
|
|
608
|
+
flat), `.ex-tier-accent` (accent tint, bordered, shadowed) and
|
|
609
|
+
`.ex-tier-aside` (a left rule; `.ex-tier-aside--tinted` adds the accent
|
|
610
|
+
tint). No class is the **plain** tier. `global.css` imports it, so a
|
|
611
|
+
course's `customCss` needs no second entry. Tiers are fixed per kind; there
|
|
612
|
+
is no setting.
|
|
613
|
+
- **`[card]` / `[card=meta]` heading markers.** Appended to a heading like
|
|
614
|
+
`[collapsed]`, they put the whole section — heading and body, down to the
|
|
615
|
+
next heading of the same or a higher rank — in one accent or meta card. A
|
|
616
|
+
heading takes one marker of each kind, in either order
|
|
617
|
+
(`### Explore the range [collapsed] [card]`). `rehype-heading-anchors.mjs`
|
|
618
|
+
strips them (from the text, the anchor and the manifest's slug alike) and
|
|
619
|
+
writes `data-card="accent"|"meta"` on the heading wrapper;
|
|
620
|
+
`CollapsibleSections.astro` draws the card, after collapsing the section
|
|
621
|
+
when it is collapsible too. Inside a card, the collapse chevron sits in the
|
|
622
|
+
card's padding (`--ex-section-gutter`).
|
|
623
|
+
- **Starlight asides render in an explico course: `remarkAsides`**
|
|
624
|
+
(`explico/reference/remark-asides.mjs`). Starlight turns `:::note` /
|
|
625
|
+
`:::tip` / `:::caution` / `:::danger` into asides only under its own
|
|
626
|
+
`src/content/docs/`, so in a course's `content/docs/` they rendered as a
|
|
627
|
+
bare `<div>`. Add `remarkAsides` last in `markdown.remarkPlugins`: it hands
|
|
628
|
+
each one to Starlight's `<Aside>` (imported into the page), so Starlight
|
|
629
|
+
still draws the icon, the translated default title and its check of a named
|
|
630
|
+
`{icon="…"}`. In a `.md` lesson, which has no JSX, the aside is written out
|
|
631
|
+
with no icon. `icon=""` — on a directive or on an `<Aside>` — drops the
|
|
632
|
+
icon, which Starlight itself cannot do.
|
|
633
|
+
- **Rule S8 (guide): prefer `<Memo>` over a Starlight aside** when the
|
|
634
|
+
content needs an anchor, a caption or collapsing. Review-only; on by
|
|
635
|
+
default (`rules.S8`).
|
|
636
|
+
- **Rules S6 and S7: heading discipline**, both errors, both new in
|
|
637
|
+
`src/reference/gate-heading-structure.ts` (`gateHeadingStructure`). Rule S6
|
|
638
|
+
(`heading-skip`): headings descend one level at a time — a lesson body
|
|
639
|
+
starts at `##` (the page title is its one `#`, so a `#` in the body fails),
|
|
640
|
+
and `##` → `####` fails; going back up is free. Rule S7
|
|
641
|
+
(`heading-adjacent`): a heading is followed by body content, never directly
|
|
642
|
+
by another heading, and `<AssessmentSet />` and `<CompactExamples>` count as
|
|
643
|
+
headings since each opens with a title of its own — so `## Try it` straight
|
|
644
|
+
above a worked-example set fails. Both read a lesson's outline, the same
|
|
645
|
+
top-level headings the section numbers count (`scanOutlineBlocks`, new in
|
|
646
|
+
`explico/reference/sections.mjs`); a heading inside a component, and a
|
|
647
|
+
memo's bar, are not in it. Both are waivable at one place
|
|
648
|
+
(`{/* explico-allow S7: reason */}` above the heading) and switchable
|
|
649
|
+
(`rules.S6.enabled`, `rules.S7.enabled`). `docs/authoring/structure.md`
|
|
650
|
+
gains "Structure from the bottom up": run-in labels and description lists,
|
|
651
|
+
not deeper headings, for parallel items near the bottom of the outline.
|
|
652
|
+
- **Kind badges** (docs/plans/titles-and-containers.md Q11). A knowledge
|
|
653
|
+
check's and a worked-example set's heading opens with a badge — the kind's
|
|
654
|
+
icon and word, small caps on the accent tint — then the number, then the
|
|
655
|
+
author's topic, all one line of text that wraps word by word. The new
|
|
656
|
+
`blockLabels` setting (`CourseConfig.blockLabels`, `"icon-word"` by default,
|
|
657
|
+
or `"icon"` for the icon alone with the word kept for a screen reader)
|
|
658
|
+
chooses the form; `normalizeBlockLabels` and `BLOCK_LABELS_DEFAULT` are
|
|
659
|
+
exported from `explico/compiler/course-config`.
|
|
660
|
+
- **Worked-example sets are numbered and citable.** A `<CompactExamples
|
|
661
|
+
id="…">` is `Example ‹part›.‹chapter›.‹n›`, counted on its own like every
|
|
662
|
+
float kind (`numbering.floats` applies), anchored `#ex-‹id›`. `[[ex-id]]` and
|
|
663
|
+
`[[lesson/slug#ex-id]]` print `Example 1.1.1` and get the reference hover
|
|
664
|
+
card — the set's title and its examples' names. `remarkNotation` numbers
|
|
665
|
+
the set and passes `number` / `anchorId` to the component, and fails the
|
|
666
|
+
page on an unresolved reference; `validateExamples`
|
|
667
|
+
(`explico/reference/examples-validate`) reports `ex-invalid-key`,
|
|
668
|
+
`ex-duplicate-key`, `ex-ref-resolves`, `ex-key-unused` (info),
|
|
669
|
+
`ex-removed-prop` and `ex-missing-prop`, owned by the `keyed-floats`
|
|
670
|
+
invariant. The number-form alias `#ex-1.1.1` resolves, the chain icon says
|
|
671
|
+
"Link copied · Example 1.1.1", `content status` counts example sets and
|
|
672
|
+
`content context --json` lists a lesson's `examples`.
|
|
673
|
+
- **"On this page" lists a knowledge check and a worked-example set** (audit
|
|
674
|
+
§8.7) where each stands, by its title — as a section is listed without its
|
|
675
|
+
number — with the badge's icon before it: `LayoutPageSidebar.astro` rebuilds
|
|
676
|
+
Starlight's list with the manifest's `titledBlocks` in it
|
|
677
|
+
(`tocWithTitledBlocks` in `explico/components/block-heading`), for the
|
|
678
|
+
desktop and the mobile list alike, and marks those entries
|
|
679
|
+
`data-block-kind` for the icon. Starlight's scroll-spy marks them like any
|
|
680
|
+
heading.
|
|
681
|
+
- **`[[check-…]]` previews its check** like every other reference: a hover
|
|
682
|
+
card with the check's title and its number of questions, a highlight when
|
|
683
|
+
the check is already on screen, and a pinned card instead of a jump when it
|
|
684
|
+
is not (the card's link goes there). It is styled like `[[sec-…]]` now.
|
|
685
|
+
- **A course can add rules of its own** — `courseRules` in
|
|
686
|
+
`course.settings.json`, a list the settings form editor adds to and removes
|
|
687
|
+
from. IDs are `X1`…`X99`: the `X` area is permanently reserved for a course,
|
|
688
|
+
so the engine never ships one and no upgrade collides with a classroom's own
|
|
689
|
+
numbering. A rule is `{ id, title, summary?, elements?, enabled?, severity?,
|
|
690
|
+
forbid?, unchecked?, doc? }`. With a `forbid` pattern it is **enforced**: each
|
|
691
|
+
match is a diagnostic carrying the rule's ID as its code
|
|
692
|
+
(`[X1] content/docs/…:42: …`), at `severity` `error` (fails
|
|
693
|
+
`pnpm validate:content`) or `warning`. Patterns are matched against the same
|
|
694
|
+
"is this really prose" surface rule N9 scans — code, math, `[[key]]`,
|
|
695
|
+
`import` lines, JSX tags and MDX comments blanked first — in one of three
|
|
696
|
+
scopes: `paragraph` (the default; a block between blank lines, right-trimmed,
|
|
697
|
+
so `^`/`$` are its ends), `prose` (the whole lesson body) or `heading`.
|
|
698
|
+
Without a pattern it is a **guide**, listed for an author or an agent to
|
|
699
|
+
follow; `enabled: false` parks it in the page's "Off" list, where an agent
|
|
700
|
+
neither applies nor flags it. A course rule is waivable at one place like any
|
|
701
|
+
other located rule (`{/* explico-allow X1: reason */}`), and a stale waiver
|
|
702
|
+
still warns. A bad definition fails the build with the setting's path (a
|
|
703
|
+
regular expression that does not compile or that matches the empty string, an
|
|
704
|
+
ID outside `X`, a duplicate ID, an unknown key, a `severity` with no pattern
|
|
705
|
+
to report). `rules.X1` is refused with a message pointing at `courseRules`.
|
|
706
|
+
Implemented by `explico/rules/course-rules` (`resolveCourseRules`,
|
|
707
|
+
`courseRuleEntry`, `courseRulesJsonSchema`) and
|
|
708
|
+
`explico/reference/gate-course-rules` (`gateCourseRules`); `pnpm build` writes
|
|
709
|
+
`dist/rules/course-rules.schema.json`, which a course points at with
|
|
710
|
+
`"courseRules": { "$ref": "../node_modules/explico/dist/rules/course-rules.schema.json" }`.
|
|
711
|
+
See `docs/authoring/README.md` § Rules of your own.
|
|
712
|
+
- **The rule catalog as data** — `explico/rules/catalog` exports `CATALOG`
|
|
713
|
+
(one entry per authoring rule and per always-on invariant), `ownerOf(code)`,
|
|
714
|
+
`ruleById`, `enforcementOf`. Every diagnostic code the engine emits now has
|
|
715
|
+
exactly one owning entry; `tests/rules/catalog.test.ts` scans `src/` and
|
|
716
|
+
fails on a code no entry claims, or one claimed twice. `content diagnostics`
|
|
717
|
+
/ `content warnings` / `content check` carry the owner as `rule` in `--json`
|
|
718
|
+
and prefix the console line with it for a lettered rule
|
|
719
|
+
(`[N9 prose-notation] …`). Six rules name checks that already ran but had
|
|
720
|
+
no rule page: rule S3 (`raw-table-markdown`, `raw-table-html`), rule S4
|
|
721
|
+
(`diagram-unkeyed`, `diagram-nested`), rule S5
|
|
722
|
+
(`convention-single-definition`), rule N19 (`notation-units`,
|
|
723
|
+
`units-vocab`), rule N20 (`notation-source-locator`), rule N21
|
|
724
|
+
(`gloss-name-shape`). Design: `docs/plans/rule-catalog.md`.
|
|
725
|
+
- **Per-place waivers.** `{/* explico-allow <ID>[, <ID>]: <reason> */}` on
|
|
726
|
+
its own line waives the named rules for the next block (to the next blank
|
|
727
|
+
line). A rule whose checks all report a line — rule N9, rule N14, rule S1,
|
|
728
|
+
rule N17 — has the covered findings dropped; a guide rule's waiver is the
|
|
729
|
+
recorded reason and nothing more. `waiver-malformed` / `waiver-invalid`
|
|
730
|
+
(a locked rule, an invariant, a corpus-level rule, an unknown ID) are
|
|
731
|
+
errors; `waiver-unused` (a checked rule's waiver that silenced nothing) is
|
|
732
|
+
a warning; all three are `manifest.diagnostics.waivers`, owned by the new
|
|
733
|
+
`waivers` invariant (`explico/rules/waivers`: `collectWaivers`,
|
|
734
|
+
`applyWaivers`, `isWaivable`). The rules-in-effect page marks each waivable
|
|
735
|
+
rule and shows the syntax. `proseOnly` (rule N9 / rule N14 scans) now blanks
|
|
736
|
+
MDX comments, so a waiver's reason may quote the text it excuses. A course's
|
|
737
|
+
own `scripts/compile-manifest.ts` must print `diagnostics.config` and
|
|
738
|
+
`diagnostics.waivers` and fail on the latter's errors — the playground's
|
|
739
|
+
shows the two loops — and can print math findings through `diagnosticLine`
|
|
740
|
+
to get the `[N9 prose-notation]` rule prefix.
|
|
741
|
+
- **The rules-in-effect page and `content rules`.** `pnpm validate:content`
|
|
742
|
+
writes `build/rules-in-effect.md` (git-ignored, regenerated every run): the
|
|
743
|
+
rules this course has on, grouped by element, each with its enforcement,
|
|
744
|
+
diagnostic codes, settings in effect and a one-line summary, then the
|
|
745
|
+
always-on invariants, the rules turned off, and the locked ones — about a
|
|
746
|
+
hundred lines, so an agent reads it instead of the rule set. `pnpm content
|
|
747
|
+
rules [--element <e>[,<e>]] [--enforcement script|partial|guide|locked]
|
|
748
|
+
[--json]` prints the same page filtered (`explico/rules/in-effect`:
|
|
749
|
+
`renderRulesInEffect`, `rulesInEffect`, `writeRulesInEffect`;
|
|
750
|
+
`explico/compiler/cli`: `contentRules`, `parseRulesFilter`).
|
|
751
|
+
`docs/authoring/agents.md` now sends an agent there first and no longer
|
|
752
|
+
restates the `P` rules.
|
|
753
|
+
- **A rule turned off stops running.** With `rules.<ID>.enabled: false`
|
|
754
|
+
the manifest withholds every diagnostic that rule owns (its codes, per the
|
|
755
|
+
catalog) from every `diagnostics` group, and the whole-rule gates (rule N9,
|
|
756
|
+
rule N14, rule S1, rule S2) are not run at all; `remarkNotation` honours
|
|
757
|
+
rule S3 / rule S4 for its render-time raw-float warnings. Switchable:
|
|
758
|
+
N5, N8, N9, N10, N14, N17, N19, N20, N21, S1–S7, C3 — and every guide
|
|
759
|
+
rule, which then leaves the rules-in-effect page. `manifest.rules`
|
|
760
|
+
publishes `disabled` and each rule's effective settings.
|
|
761
|
+
`tests/rules/one-rule-off.test.ts` holds each switch to exactly its own
|
|
762
|
+
codes. `MathGateDiagnostic.code` is now required.
|
|
763
|
+
- **Per-rule settings: `CourseConfig.rules`.** One block per rule ID, holding
|
|
764
|
+
only what differs from the defaults — `{ "P5": { "enabled": false }, "S1":
|
|
765
|
+
{ "maxLength": 72 }, "N14": { "allow": ["GDP"] } }`. `resolveRules`
|
|
766
|
+
(`explico/rules/settings`) validates it against the catalog at the start
|
|
767
|
+
of `compileManifest` and fails the build on an unknown rule or key, a
|
|
768
|
+
value outside its spec, or `enabled: false` on a locked rule (N4, N13,
|
|
769
|
+
N15). Rule S1 gains `maxLength` (default 65), rule S2 `bridgeMinWords` /
|
|
770
|
+
`bridgeMaxWords` / `scopeMinWords` / `scopeMaxWords` (30 / 150), rule V5
|
|
771
|
+
`seeds` (10), rule V6 `minLabels` (3); rules N8, N10, N14 keep their
|
|
772
|
+
`allow` / `deny` lists, now under `rules.N8` / `rules.N10` / `rules.N14`.
|
|
773
|
+
`pnpm build` writes `dist/rules/rules.schema.json` — the `rules` block as
|
|
774
|
+
JSON Schema, properties ordered by element with an `x-element` hint — so a
|
|
775
|
+
course's `course.settings.schema.json` just points at it:
|
|
776
|
+
`"rules": { "$ref": "../node_modules/explico/dist/rules/rules.schema.json" }`.
|
|
777
|
+
The settings form editor (`tools/vscode-settings-editor/`) inlines such a
|
|
778
|
+
file `$ref` and groups the rules by element. `enabled: false` is recorded
|
|
779
|
+
now and takes effect in the next change (the gates); the manifest schema
|
|
780
|
+
is now v18 (`diagnostics.config`).
|
|
781
|
+
- **The rule index is generated.** `scripts/agent/gen-rules.mjs` writes the
|
|
782
|
+
table in `docs/authoring/README.md` from the catalog — each row now shows
|
|
783
|
+
the rule's element(s), its settings defaults, and its enforcement with the
|
|
784
|
+
emitting module and function linked — plus an "Always enforced
|
|
785
|
+
(invariants)" table. `--check` (in `scripts/agent/verify` and CI) fails on
|
|
786
|
+
a stale table, a missing `## <ID>` heading, a bold title that differs from
|
|
787
|
+
the catalog, or an `_Enforcement._` paragraph that does not name every
|
|
788
|
+
module the catalog lists for that rule. Rules N1, N2, N3, N6, N7 are
|
|
789
|
+
now stated as Review-only: their earlier "build-enforced" claim rested on
|
|
790
|
+
the completeness gate (rule N4), which checks that a glyph resolves, not
|
|
791
|
+
which spelling was chosen.
|
|
792
|
+
- **A part can have its own page.** `content/docs/<dir>/index.mdx` is now a
|
|
793
|
+
recognised, first-class page: the URL a part's directory already implies
|
|
794
|
+
(`/<part>/`), which until now was a 404 that nothing linked and nothing
|
|
795
|
+
could link. It is not a chapter — `isLessonSlug` has always excluded
|
|
796
|
+
`<dir>/index`, so the page teaches nothing, numbers nothing, and never
|
|
797
|
+
enters a track or the curriculum graph — and it is opt-in one part at a
|
|
798
|
+
time. See `docs/authoring/structure.md` § Part pages.
|
|
799
|
+
- **`<PartOverview>`** (`explico/components/PartOverview.astro`) renders that
|
|
800
|
+
page's body from the manifest: the part's chapters in reading order with
|
|
801
|
+
their descriptions and float counts, beside a rail carrying the competency
|
|
802
|
+
outcomes the part delivers and the parts it builds on and leads to. The
|
|
803
|
+
authored `chain` prop states the part's argument, one step per line, because
|
|
804
|
+
no manifest field knows why one chapter follows another; `rail={false}`
|
|
805
|
+
keeps the reading list alone.
|
|
806
|
+
- **The sidebar reaches a part page two ways.** A part whose overview page
|
|
807
|
+
exists gains an `Overview` row leading its group — in the served HTML, so it
|
|
808
|
+
is keyboard-operable, crawlable and present without JavaScript, and marked
|
|
809
|
+
`explico-part-overview` so it carries its own icon (two stacked sheets,
|
|
810
|
+
filled against a chapter's outlined page in compact mode, and hollowed out
|
|
811
|
+
again on the part's own page where the row is already an accent tile).
|
|
812
|
+
`LayoutSidebar.astro` then adds a `»` control at the end of the group
|
|
813
|
+
header, so the label keeps the collapse it already owned and neither gesture
|
|
814
|
+
is retrained.
|
|
815
|
+
- **`studyMinutes`** is an optional lesson frontmatter field — `40`, or
|
|
816
|
+
`[30, 40]`. `manifest.lessons[].studyMinutes` carries it in one shape and
|
|
817
|
+
`manifest.parts[].studyMinutes` totals a part whose chapters *all* state
|
|
818
|
+
one, so a half-annotated course reports no misleading sum. It is the
|
|
819
|
+
reader-facing half of the long-dropped `estimatedMinutes`, back now that
|
|
820
|
+
part and course pages have somewhere to add it up.
|
|
821
|
+
- **`<CourseContents>`** (`explico/components/CourseContents.astro`) lists
|
|
822
|
+
every part and chapter for a landing page, each part heading linking its
|
|
823
|
+
overview page, with per-part and whole-course study totals. A hero that
|
|
824
|
+
links only chapter one no longer hides the rest of the course, and the list
|
|
825
|
+
is the manifest's, so it cannot go stale.
|
|
826
|
+
- **Both rails show how far the reader has come.** The navigation rail
|
|
827
|
+
colours the line beside every chapter already passed — reading order is the
|
|
828
|
+
sidebar's own — and marks a part whole when all of its chapters are behind
|
|
829
|
+
the reader; in compact mode the chapter tiles carry it instead. The contents
|
|
830
|
+
rail does the same down one page, following Starlight's scroll-spy. Without
|
|
831
|
+
JavaScript both are simply uncoloured.
|
|
832
|
+
|
|
833
|
+
- **`LayoutPageTitle.astro`** puts the part's name, linked, above every
|
|
834
|
+
chapter title — the one route to a part page that does not depend on the
|
|
835
|
+
sidebar, and the answer to "where am I" for a reader who arrived from a
|
|
836
|
+
search result. Wire it as Starlight's `PageTitle` component override.
|
|
837
|
+
- **`CoursePart.overviewLabel`** renames that sidebar row per part, for a
|
|
838
|
+
course where `Overview` reads wrong.
|
|
839
|
+
- **Rule N18: a multi-step derivation is one aligned chain.** One step per
|
|
840
|
+
line in an `aligned` block, `&` directly before each relation so the signs
|
|
841
|
+
stand in one column, and any justification at the end of its step's line
|
|
842
|
+
after `\qquad`, as `\text{…}`. A justification names the fact it uses;
|
|
843
|
+
another display equation is linked with `[[eq-key]]` in the sentence that
|
|
844
|
+
introduces the chain, because math cannot carry a link and rule N5 forbids
|
|
845
|
+
hand-typed numbers. Review-only (`docs/authoring/notation.md#n18`).
|
|
846
|
+
- **Rule V6: every quantitative axis shows at least three labelled values.**
|
|
847
|
+
One value pins a point and two cannot tell a linear axis from a log one;
|
|
848
|
+
three show the offset, the unit, and the spacing. Covers hand-drawn SVG,
|
|
849
|
+
Observable Plot, islands, and printed pgfplots figures; a categorical axis
|
|
850
|
+
shows its categories, and a figure without axes is exempt. Review-only
|
|
851
|
+
(`docs/authoring/visualizations.md#v6`). The playground's
|
|
852
|
+
polynomial-shape memo figure now labels both axes.
|
|
853
|
+
- **Prose rules P2–P9: controlled scientific English, adapted from
|
|
854
|
+
ASD-STE100.** A lesson's main text puts clarity before variety and
|
|
855
|
+
entertainment. `prose.md` now defines _main text_ (everything a reader must
|
|
856
|
+
read) and _aside_ (a `<ShortNote>` or `<Memo>` body), and adds rule P2 (one
|
|
857
|
+
canonical term per concept, repeated; a notation entry's `label` is that
|
|
858
|
+
term), rule P3 (one meaning per term; no technical term in its everyday
|
|
859
|
+
sense), rule P4 (use the established term and define it where the argument
|
|
860
|
+
first needs it), rule P5 (literal main text: no idioms, metaphors,
|
|
861
|
+
colloquialisms, buzzwords, or entertainment devices; analogies go in an
|
|
862
|
+
aside), rule P6 (industry jargon only in an optional "Industry jargon"
|
|
863
|
+
aside that states its approximate meaning, context of use, and lack of a
|
|
864
|
+
standard definition), rule P7 (one claim per sentence, every logical link
|
|
865
|
+
stated), rule P8 (relation words match the math exactly), and rule P9 (a
|
|
866
|
+
display equation's lead-in sentence says what it gives and where it comes
|
|
867
|
+
from). All review-only (`docs/authoring/prose.md`).
|
|
868
|
+
- **Rule S2: a part page links the earlier parts to this one, then states
|
|
869
|
+
its own scope.** Every `content/docs/<dir>/index.mdx` carries a
|
|
870
|
+
`<PartScope>` paragraph on what the part covers, and every part after the
|
|
871
|
+
first also carries a `<PartBridge>` paragraph before it, on how the earlier
|
|
872
|
+
parts lead in. Both components are new (`explico/components/PartBridge.astro`,
|
|
873
|
+
`explico/components/PartScope.astro`) and render their Markdown child as-is.
|
|
874
|
+
Build-enforced by `gate-part-pages.ts` at `pnpm validate:content`: each slot
|
|
875
|
+
appears once, holds one paragraph, and falls inside a word range. The range
|
|
876
|
+
is `rules.S2` (`bridgeMinWords`, `bridgeMaxWords`, `scopeMinWords`,
|
|
877
|
+
`scopeMaxWords`), 30–150 words for any end left out. A part
|
|
878
|
+
without an overview page is unaffected
|
|
879
|
+
(`docs/authoring/structure-rules.md#s2`).
|
|
880
|
+
- **A closed section says so on every screen.** Under its title it now
|
|
881
|
+
shows what the fold holds — "Hidden here: 2 paragraphs, 1 equation"
|
|
882
|
+
(subsections, paragraphs, equations, figures, tables, diagrams, lists,
|
|
883
|
+
code blocks, memos, worked examples, questions) — which folds away once
|
|
884
|
+
the section opens. On a narrow or touch screen the chevron moves into the
|
|
885
|
+
title line and stays visible in both states (`›` closed, `⌄` open), and a
|
|
886
|
+
closed title adds "(tap to expand)" — "(click to expand)" with a mouse.
|
|
887
|
+
All of it is `aria-hidden` and part of the tap target; the heading alone
|
|
888
|
+
still names the disclosure. Nothing to author: every collapsible section
|
|
889
|
+
gets it.
|
|
890
|
+
- **Rule N22: an accent or mark on a symbol is an operator.** A decoration —
|
|
891
|
+
an accent (`\hat`, `\widehat`, `\bar`, `\tilde`, `\dot`, `\vec`, …),
|
|
892
|
+
`\overline` / `\underline`, or a superscript `'`, `^*`, `^\dagger` — is
|
|
893
|
+
on the course's `rules.N22.allow` list (shipped empty,
|
|
894
|
+
`src/reference/base-accents.mjs`), explained by an operator notation entry
|
|
895
|
+
(`latex: \hat{}`, `{}'`, `{}^{*}`), or part of a whole-form entry
|
|
896
|
+
(`latex: \hat{\beta}`), which wins for that exact form. New Tier-3
|
|
897
|
+
warning `accent-explained` (`consistency.ts` `checkAccentExplained`). The
|
|
898
|
+
resolver reports each decoration in `resolveMathGlyphs(…).decorations`
|
|
899
|
+
(`wholeForm`, `operator`); `collectDecorations`, `decorationOfEntry`,
|
|
900
|
+
`operatorEntryLatex`, `operatorDisplayLatex` and `DECORATION_NAMES` are
|
|
901
|
+
exported from `math-glyphs.mjs`. See `docs/authoring/notation.md#n22`.
|
|
902
|
+
- **Lesson frontmatter `notation.suspend` (rule N22).** A page can declare
|
|
903
|
+
that a decoration is not an operator there
|
|
904
|
+
(`- operator: "'"`, `reason: …`). Every decorated form of it on that page
|
|
905
|
+
then needs its own entry or a whole-form `\explain`, or the build fails
|
|
906
|
+
with `suspended-operator-unresolved` (the gate and the render path both
|
|
907
|
+
check lesson-body math; assessments, labs and notation formulas keep the
|
|
908
|
+
operator reading). New diagnostics `operator-suspended` (info),
|
|
909
|
+
`operator-suspension-reason` (warning, no reason given) and
|
|
910
|
+
`operator-shadowed` (info, a whole form over an operator the page
|
|
911
|
+
explains). An unknown decoration, one suspended twice, or a
|
|
912
|
+
`notation.local` operator entry for a suspended decoration fails the load.
|
|
913
|
+
`resolveMathGlyphs` takes `{ suspended }` as a third argument; an item
|
|
914
|
+
in `unresolved` may carry `suspended` or `font`.
|
|
915
|
+
- **Hovering an accent explained by an operator entry shows the operator's
|
|
916
|
+
card; the letter under it keeps its own.** The operator's `\explain` wraps
|
|
917
|
+
the decorated form around the letter's. A table header built from both
|
|
918
|
+
reads "Estimate of regression coefficient". An operator entry is drawn
|
|
919
|
+
over a placeholder square (`\dot{\square}`) wherever its symbol is shown
|
|
920
|
+
on its own.
|
|
921
|
+
- **The Page notation panel names each suspended decoration** and the reason
|
|
922
|
+
("On this page, ′ is not an operator. …"): `NotationLayer` takes
|
|
923
|
+
`suspended`, and `LessonFooter` passes the page's `notation.suspend`.
|
|
924
|
+
|
|
925
|
+
- **An equation takes a short caption: `\caption{…}` inside its `$$…$$`**,
|
|
926
|
+
next to its `\label{eq:…}`. It is stripped before KaTeX exactly like the
|
|
927
|
+
label (and so by every math gate), heads the `[[eq-…]]` preview card, and
|
|
928
|
+
names the copy action: the copy button and the click-to-copy tooltip read
|
|
929
|
+
"Copy LaTeX — Range formula". An equation takes no long caption. A
|
|
930
|
+
`\caption{}` in inline math, two in one equation, or an empty one fails
|
|
931
|
+
the page build. `EQ_CAPTION_PATTERN` and `equationCaption(latex)` are
|
|
932
|
+
exported from `equations.mjs`; `stripEquationLabels` now strips
|
|
933
|
+
`\caption{…}` too; the equation's container carries `data-eq-short`.
|
|
934
|
+
- **A `short` caption on `<Table>`, `<Figure>` and `<Diagram>`**, carried as
|
|
935
|
+
`data-tab-short` / `data-fig-short` / `data-dia-short`: the reference
|
|
936
|
+
preview card (`FloatRefLayer.astro`) heads with it, for every kind that has
|
|
937
|
+
one.
|
|
938
|
+
- **A float's chain icon copies its link**, the way a heading's does: one
|
|
939
|
+
click puts the page URL with the float's named anchor on the clipboard and
|
|
940
|
+
says "Link copied · Figure 2.2.1" (`a[data-copy-link]`, handled by
|
|
941
|
+
`HeadingAnchorEnhancer.astro`), then navigates and highlights as before.
|
|
942
|
+
A table's caption line gains the chain icon too, after its number.
|
|
943
|
+
- A notation formula's copy button, and its click-to-copy tooltip, read
|
|
944
|
+
"Copy LaTeX — ‹entry label›" (`data-copy-title` on the formula).
|
|
945
|
+
|
|
946
|
+
- **Collapse defaults for every kind**, in the `disclosure` settings block,
|
|
947
|
+
each at the value it used to have hardcoded: `knowledgeChecks`
|
|
948
|
+
(`{ collapsible: true, defaultOpen: true }`), `memos` (closed), `figures`
|
|
949
|
+
(open), `diagrams` (open), `workedExamples` (closed) and
|
|
950
|
+
`tables.truncateAfter` (6). A `defaultOpen` prop still overrides one
|
|
951
|
+
`<Figure>` / `<Diagram>`, and is new on `<Memo>` and `<CompactExamples>`;
|
|
952
|
+
`rememberState` is new on `<CompactExamples>`.
|
|
953
|
+
- **Worked examples remember the reader's choice**, like every other
|
|
954
|
+
collapsible (`section:ex-‹id›`).
|
|
955
|
+
- `section-parts.ts` markers for a block that is a section of its own:
|
|
956
|
+
`data-section-to-end` on its heading wrapper (its section is the rest of its
|
|
957
|
+
container) and `data-section-contained` on its container (it never ends a
|
|
958
|
+
section around it). `CollapsibleSections` honours
|
|
959
|
+
`data-remember-state="false"` on a heading wrapper.
|
|
960
|
+
|
|
961
|
+
### Changed
|
|
962
|
+
|
|
963
|
+
- **Every container is restyled into a tier** (reader-visible; no markup a
|
|
964
|
+
course writes changes). No container has a top rule any more, and the shadow
|
|
965
|
+
never grows with heading depth.
|
|
966
|
+
- `<Figure>`: a card with an accent top rule, a tint and a shadow → **plain**
|
|
967
|
+
(no box). A `.lab-shell` loses the same card, inside a figure or not.
|
|
968
|
+
- Knowledge check: a grey card around the questions → **accent**, one card
|
|
969
|
+
around the heading and the questions (the check's heading wrapper carries
|
|
970
|
+
`data-card="accent"`; `.assessment-shell` draws no box of its own).
|
|
971
|
+
- `<CompactExamples>`: a grey card → **accent**.
|
|
972
|
+
- The notation panel, a part page's rail, the notation glossary's entries
|
|
973
|
+
and the curriculum map's cards: grey cards → **meta**. The About popover:
|
|
974
|
+
an accent top rule on the page background → **meta**, keeping its overlay
|
|
975
|
+
shadow.
|
|
976
|
+
- `<Memo>`: a bordered box with a 4px accent left rule → **aside**, tinted:
|
|
977
|
+
the 3px rule and the tint, no box; its `<details>` no longer draws
|
|
978
|
+
Starlight's own 2px rule inside it. An opened `<ShortNote>`: a 2px rule →
|
|
979
|
+
the same 3px rule.
|
|
980
|
+
- Hover cards (a reference preview, a citation, a notation explanation) lose
|
|
981
|
+
their accent top rule and keep their overlay shadow.
|
|
982
|
+
- Starlight asides → **aside**: `note` and `tip` in the course accent,
|
|
983
|
+
`caution` and `danger` keeping an orange and a red rule, tint and title;
|
|
984
|
+
Starlight's icon per type stays. A Starlight `<Card>` → **meta**.
|
|
985
|
+
- References were already plain and are unchanged.
|
|
986
|
+
|
|
987
|
+
- **A table's caption line is the one every float uses**: `Table 2.2.1`, the
|
|
988
|
+
chain icon, then the caption, at the caption size a figure uses (it was
|
|
989
|
+
body size) and with no " — " between number and caption. Minimized, a table
|
|
990
|
+
still keeps its caption line and its header row (decision Q6).
|
|
991
|
+
- **A knowledge check folds by the course's `disclosure.knowledgeChecks`**
|
|
992
|
+
instead of a hardcoded "collapsible, open"; a figure, a diagram, a memo and
|
|
993
|
+
a worked-example set take their first state from their `disclosure` key; the
|
|
994
|
+
notation panel reads `manifest.disclosure.notation`.
|
|
995
|
+
- **A knowledge check's number follows the section numbers' rules**, as a
|
|
996
|
+
worked-example set's does: shown on a wide screen, in print and on a folded
|
|
997
|
+
bar, hidden on a narrow screen, and not at all when
|
|
998
|
+
`numbering.sections.show` is off — then `data-check-number` is not written
|
|
999
|
+
(so no `#check-…` alias resolves) and `[[check-…]]` / `[[ex-…]]` print
|
|
1000
|
+
`Knowledge check “‹title›”` / `Example “‹title›”` instead of a number. A
|
|
1001
|
+
float's number stays in its caption at every width.
|
|
1002
|
+
- **A truncated table shows up to `truncateAfter` rows, half from each end**
|
|
1003
|
+
(3 and 3 under the default 6, as before).
|
|
1004
|
+
- **A lab carries no title or caption of its own**; the `<Figure>` around it
|
|
1005
|
+
names the whole. The engine's `.lab-chart-figure` styles are gone (a lab
|
|
1006
|
+
draws its chart in a plain `.lab-chart`), and the "print as shown" note for
|
|
1007
|
+
a compacted figure or diagram follows its caption line.
|
|
1008
|
+
- **Rule V3 no longer asks an author for accessibility work.** An interactive
|
|
1009
|
+
figure needs no accessible name or description (`ariaLabel` /
|
|
1010
|
+
`ariaDescription` / `ariaHidden` on the plot) and no text alternative (a
|
|
1011
|
+
`<details>` block of assumptions and representative values): the
|
|
1012
|
+
`<Figure>`'s caption is its accessible name, since the figure renders a real
|
|
1013
|
+
`<figure>` and `<figcaption>`. Rule V3 keeps the hydrated island, the
|
|
1014
|
+
`labMathScope` / `renderLabMath` / `\slot{}` seam and labelled controls — it
|
|
1015
|
+
is narrowed, not switched off, since switching it off would drop the seam
|
|
1016
|
+
too. What stays automatic is unchanged: the hidden labels
|
|
1017
|
+
`rehype-table-header-labels.mjs` writes into an all-math table header, the
|
|
1018
|
+
names the engine gives its own buttons and links, and the axe checks on the
|
|
1019
|
+
engine's markup. `docs/authoring/README.md` § Accessibility states the
|
|
1020
|
+
posture: accessibility is the engine's job, and nothing asks a course author
|
|
1021
|
+
to write any. A course's existing `aria*` options may stay.
|
|
1022
|
+
|
|
1023
|
+
- **`build/rules-in-effect.md` and `content rules` list the course's own rules**
|
|
1024
|
+
beside the engine's, under their element, tagged `course`, with the settings
|
|
1025
|
+
file (or the rule's own `doc`) as the link; the count line says how many are
|
|
1026
|
+
the course's, and a disabled one joins the "Off" list. `content rules --json`
|
|
1027
|
+
gains `source: "engine" | "course"` per rule. A **retired** rule is now left
|
|
1028
|
+
off the page and out of `--json` entirely — it has been folded into another
|
|
1029
|
+
and keeps its ID only so old commit messages resolve. A string-array setting
|
|
1030
|
+
prints its items in backticks rather than as JSON, so
|
|
1031
|
+
`rules.N2.reserved`'s LaTeX reads as the author wrote it (`\mathbb{N} — the
|
|
1032
|
+
natural numbers`, not `"\\mathbb{N} …"`).
|
|
1033
|
+
- **The manifest schema is now v19**: `rules.course` carries the course's own
|
|
1034
|
+
rules as validated, in file order, so the rules-in-effect page and the CLI
|
|
1035
|
+
never re-read the settings file. A finding from one is in
|
|
1036
|
+
`diagnostics.math` with the rule's ID as its `code`, and
|
|
1037
|
+
`MathGateDiagnostic.context` gains `'course'` — the one context whose
|
|
1038
|
+
severity the course chooses.
|
|
1039
|
+
- **Every math-gate finding carries a `code`.** Rule N9 findings are
|
|
1040
|
+
`prose-notation`, rule N14 `abbreviation-unexplained`, rule S1
|
|
1041
|
+
`title-length`; an unresolved glyph is `unresolved-glyph`; a resolver
|
|
1042
|
+
failure keeps the resolver's own code (`unparenthesized-function`,
|
|
1043
|
+
`invalid-latex`, …) and any other compile crash is `math-compile-error`.
|
|
1044
|
+
They were code-less before, so the CLI labelled them by context
|
|
1045
|
+
(`math:prose`, `math:structure`); `code` in `content … --json` output is
|
|
1046
|
+
now `math:<code>` for all of them, and `MathGateDiagnostic.code` is
|
|
1047
|
+
required in the type.
|
|
1048
|
+
- **Print follows the screen ("print as shown").** A printed or
|
|
1049
|
+
PDF-saved lesson keeps every disclosure the reader controls — section,
|
|
1050
|
+
figure, diagram, memo, short note, worked-example set, question, the
|
|
1051
|
+
notation list — as the reader left it: open prints in full, closed prints
|
|
1052
|
+
its title and "(content collapsed — full text: `url`)", linking the item's
|
|
1053
|
+
permalink on the deployed site (`canonicalBaseUrl`). Until now print
|
|
1054
|
+
expanded everything. Rows past a long table's fold and an open example
|
|
1055
|
+
set's other tabs still print in full, since the reader never chose to hide
|
|
1056
|
+
them, and the Markdown export still expands everything. A course without
|
|
1057
|
+
`lessonExports.print` gets the same behaviour from the browser's own print.
|
|
1058
|
+
The shared logic is `explico/components/disclosure/print-as-shown.ts`.
|
|
1059
|
+
- **The part caret leads its label instead of trailing the row.** `>` before
|
|
1060
|
+
the name it opens, `v` when open, which is how a disclosure triangle reads
|
|
1061
|
+
and where the eye looks for it; the `»` takes the end of the row.
|
|
1062
|
+
- **Both sidebars answer a pointer with a filled row**, not only a colour
|
|
1063
|
+
change — on a list of twenty chapters, a text-colour shift is easy to lose.
|
|
1064
|
+
The part header answers too: it is a control, and it was the one row in the
|
|
1065
|
+
panel that looked inert. It also carries a standing tint while the open page
|
|
1066
|
+
belongs to it, so the three states stay distinct — a solid accent row for
|
|
1067
|
+
the current chapter, a tint for the part holding it, a neutral fill that
|
|
1068
|
+
comes and goes with the pointer.
|
|
1069
|
+
- **Manifest schema is now v17** (additive): `parts` carries every part with
|
|
1070
|
+
its ordinal, directory link, chapters in reading order, and overview page
|
|
1071
|
+
where one exists — so a page or a breadcrumb reads a part instead of
|
|
1072
|
+
re-deriving one from lesson slugs. `sidebar[].items` is now one mixed list
|
|
1073
|
+
of lesson slugs and `{ label, link, attrs? }` entries (`SidebarItem`)
|
|
1074
|
+
rather than one kind per group; `SidebarGroup` is a single interface
|
|
1075
|
+
instead of a union, which widens what a consumer may receive and narrows
|
|
1076
|
+
nothing. `lessons[].studyMinutes` and `parts[].studyMinutes` come with it.
|
|
1077
|
+
- **A whole-form notation match (`\hat{\beta}`, `b'`, `\theta^{*}`) now
|
|
1078
|
+
wraps the decoration too**, so its hover target includes the accent or
|
|
1079
|
+
prime: `\explain{k}{\hat{\beta}}`, not `\hat{\explain{k}{\beta}}`. The
|
|
1080
|
+
rendered HTML of an existing whole form changes accordingly.
|
|
1081
|
+
- **A marked entry no longer lends its card to the bare letter.** `latex: b'`
|
|
1082
|
+
or `\theta^{*}` used to make a bare `b` or `\theta` elsewhere on the page
|
|
1083
|
+
bind to it (the parameterised-symbol fallback meant for `t_k` → `t`). A
|
|
1084
|
+
mark makes a different quantity (rule N22), so the bare letter now needs
|
|
1085
|
+
its own entry.
|
|
1086
|
+
|
|
1087
|
+
### Deprecated
|
|
1088
|
+
|
|
1089
|
+
- **`CourseConfig.operators`, `romanLabels`, `abbreviations`** — the same
|
|
1090
|
+
`allow` / `deny` blocks now live at `rules.N8`, `rules.N10`, `rules.N14`
|
|
1091
|
+
(rule N8, rule N10, rule N14). The old keys are folded in with a
|
|
1092
|
+
`config-deprecated-key` warning; setting both the old key and its
|
|
1093
|
+
replacement is an error. They go away in a later MINOR. The unreleased
|
|
1094
|
+
`partPages` block became `rules.S2` (`bridgeMinWords` … `scopeMaxWords`)
|
|
1095
|
+
before it shipped, with no alias.
|
|
1096
|
+
|
|
1097
|
+
### Fixed
|
|
1098
|
+
|
|
1099
|
+
- **Grey boxes had no background in the dark theme.** The knowledge-check,
|
|
1100
|
+
worked-examples, notation-panel, glossary and curriculum-map cards and the
|
|
1101
|
+
knowledge-check feedback read `--sl-color-gray-7`, which Starlight defines
|
|
1102
|
+
for its light theme only; the tier tokens name a colour for each theme.
|
|
1103
|
+
- **Rule S1 measures a heading as the reader sees it**: its `[collapsed]` /
|
|
1104
|
+
`[card]` markers no longer count toward `rules.S1.maxLength`.
|
|
1105
|
+
|
|
1106
|
+
- **An equation's copy button shows its "Copy LaTeX" tooltip.** The
|
|
1107
|
+
equation-wide copy hint's `mouseout` handler removed every
|
|
1108
|
+
`title="Copy LaTeX"`, the button's own included, so the first time the
|
|
1109
|
+
pointer left the button (or crossed onto its icon) the tooltip was gone
|
|
1110
|
+
for good. It now removes only the hints it added itself, on both a lesson
|
|
1111
|
+
equation and a notation formula.
|
|
1112
|
+
- **A collapsed section is visible on a phone.** Its chevron sat left of the
|
|
1113
|
+
title in the content gutter, which a phone doesn't have, so it rendered
|
|
1114
|
+
off-screen and the section looked like a plain heading. On desktop the
|
|
1115
|
+
same fixed offset put the glyph on the sidebar's border line; it is now
|
|
1116
|
+
centred in the gutter at every width, level with the title's first line.
|
|
1117
|
+
- **The copy-link icon beside a section title shows on touch screens.** It
|
|
1118
|
+
was hidden unconditionally (Starlight hides it only where a hover can
|
|
1119
|
+
reveal it), yet stayed a live tap target — so a tap near the end of a
|
|
1120
|
+
title changed the URL instead of opening the section.
|
|
1121
|
+
- **Labs hydrate under `astro dev` again.** `katex-delimiters.mjs` imported
|
|
1122
|
+
`node:fs` at the top level and rewrote KaTeX's bundle on import; a lab
|
|
1123
|
+
island reaches it through `render-lab-math.ts`, so the dev server evaluated
|
|
1124
|
+
it in the browser, where Vite's `node:fs` stub throws — and the island
|
|
1125
|
+
never hydrated. The Node imports are now lazy and the rewrite installs only
|
|
1126
|
+
outside a browser. Builds and server rendering are unchanged: they still
|
|
1127
|
+
get the N17 sizing and still fail loudly when it can't be installed.
|
|
1128
|
+
- **A build no longer warns about `katex-delimiters.mjs`.** A lab island that
|
|
1129
|
+
imported `slotPlaceholder` from `render-lab-math.ts` pulled the server-only
|
|
1130
|
+
KaTeX chain into the browser graph, so Vite warned that `node:fs` and
|
|
1131
|
+
`node:module` were externalized. The island-facing half (`slotPlaceholder`,
|
|
1132
|
+
`RenderedLabMath`) now also lives in `explico/reference/lab-math-slot`,
|
|
1133
|
+
which imports nothing. `render-lab-math` still re-exports both, so no
|
|
1134
|
+
import breaks, but an island should switch to `lab-math-slot` to drop the
|
|
1135
|
+
warning. The SSR warning about the rewritten bundle's data-URL `import()`
|
|
1136
|
+
is silenced with `/* @vite-ignore */`.
|
|
1137
|
+
- **Printed square roots keep their radical sign.** The print stylesheet's
|
|
1138
|
+
`svg { height: auto }` reached KaTeX's own SVGs and collapsed the `\sqrt`
|
|
1139
|
+
radical (and stretchy arrows and accents) to zero height.
|
|
1140
|
+
- **`<Diagram>` formulas render in Safari and every iOS browser.** WebKit
|
|
1141
|
+
paints HTML inside an SVG `foreignObject` that gets its own render layer —
|
|
1142
|
+
every KaTeX `position: relative` span — without the SVG's internal
|
|
1143
|
+
transforms. So each node formula was drawn at the SVG origin, piled onto
|
|
1144
|
+
the first node, while its box and plain-text label stayed in place. Nodes
|
|
1145
|
+
are now positioned with `x`/`y` on the `<rect>` and `<foreignObject>`
|
|
1146
|
+
instead of a `transform` on their `<g>`. Printing had the same fault
|
|
1147
|
+
through the `viewBox` scale that fits a diagram to the page: WebKit
|
|
1148
|
+
dropped the formulas from a printed or PDF-saved lesson. Where CSS can
|
|
1149
|
+
divide lengths, the print stylesheet now keeps the SVG at its build-time
|
|
1150
|
+
size and scales the HTML canvas with a CSS `transform` instead. Browsers
|
|
1151
|
+
without that CSS support keep the `viewBox` scaling. No course changes.
|
|
1152
|
+
- **Lesson display equations are no longer clipped at the bottom.** A
|
|
1153
|
+
display equation scrolls sideways inside a box that hides vertical
|
|
1154
|
+
overflow, and that box had no padding, so it ended exactly at KaTeX's
|
|
1155
|
+
metric strut. Glyph ink overshoots those metrics by up to about 0.1em, which
|
|
1156
|
+
cut off the bottom of a denominator's `g` or a stretched `\right]`.
|
|
1157
|
+
`.sl-markdown-content .katex-display` now has `0.25em` block padding, so
|
|
1158
|
+
each lesson equation takes about 8px more vertical space. No course changes.
|
|
1159
|
+
- Rules N8 and N10 skipped every labelled display equation: the
|
|
1160
|
+
`\label{eq:…}` made the span fail KaTeX's parse, and the scans treated
|
|
1161
|
+
that as "nothing here". `consistency.ts` now strips the label first, so an
|
|
1162
|
+
unexplained `\operatorname{…}` or `\mathrm{…}` in a labelled equation is
|
|
1163
|
+
reported.
|
|
1164
|
+
- `x^\prime` failed the completeness gate as an unresolved `\prime`. A
|
|
1165
|
+
prime is a mark on the symbol before it, never a symbol.
|
|
1166
|
+
- An accent's glyph box, a full line tall, took every hover on the letter
|
|
1167
|
+
under it: pointing at the `x` of `\hat{\explain{k}{x}}` showed nothing,
|
|
1168
|
+
or the accent's card. The glyph box now lets the pointer through, and a
|
|
1169
|
+
strip across the top of the decorated form is the accent's hit area.
|
|
1170
|
+
|
|
1171
|
+
## [0.9.0] - 2026-09-19
|
|
1172
|
+
|
|
1173
|
+
### Added
|
|
1174
|
+
|
|
1175
|
+
- **The reader sets each desktop panel's width.** The navigation sidebar and
|
|
1176
|
+
the "On this page" rail each carry a drag handle on their inner edge
|
|
1177
|
+
(`.layout-edge-resizer`, `role="separator"`, also arrow keys plus
|
|
1178
|
+
`Home`/`End` from the keyboard). The navigation's width is written to
|
|
1179
|
+
Starlight's own `--sl-sidebar-width`, so the header's title column, the
|
|
1180
|
+
article's inline start and the `.main-pane` formula all follow it with no
|
|
1181
|
+
second source of truth; the contents rail, which has no such token, takes
|
|
1182
|
+
`--layout-contents-width`, honoured only while `data-contents-resized` says
|
|
1183
|
+
the reader has set one. Both persist per reader through the existing
|
|
1184
|
+
`PreferenceStore` seam (`explico:layout:navigation-width`,
|
|
1185
|
+
`explico:layout:contents-width`) and are dropped again outside the layout
|
|
1186
|
+
they belong to, so a desktop width never follows the page down to a phone.
|
|
1187
|
+
- **Dragging a panel below three characters of width collapses it** — `3ch`
|
|
1188
|
+
measured in the panel's own font, so a course that changes `--sl-font` gets
|
|
1189
|
+
the threshold its own text implies. The panel's control then reopens it at
|
|
1190
|
+
the width the reader last chose, not at the sliver the collapsing drag
|
|
1191
|
+
happened to pass through.
|
|
1192
|
+
- **A peek now waits out `--layout-peek-delay` (40ms) before it opens.** The
|
|
1193
|
+
rails sit on the two edges of the window, so a pointer on its way to the
|
|
1194
|
+
scrollbar, the tab strip, or across the article crosses one without meaning
|
|
1195
|
+
anything by it, and the panel swung open behind it every time. The CSS keeps
|
|
1196
|
+
`:hover` as the trigger and reads a new `data-navigation-peek-armed` /
|
|
1197
|
+
`data-page-sidebar-peek-armed` attribute as the gate in front of it, so
|
|
1198
|
+
peeking still has one description rather than two. Nothing is lost without
|
|
1199
|
+
JavaScript: a panel is only ever collapsed because that same script
|
|
1200
|
+
collapsed it.
|
|
1201
|
+
- **A panel narrowed past its own labels scrolls sideways** rather than
|
|
1202
|
+
wrapping every chapter title into a column of single words. The contents
|
|
1203
|
+
rail's scrollbar, which Starlight hides outright (`scrollbar-width: none`),
|
|
1204
|
+
comes back as `thin` while that panel is reader-sized.
|
|
1205
|
+
|
|
1206
|
+
### Changed
|
|
1207
|
+
|
|
1208
|
+
- **The collapse controls are rectangles docked into the corner the header
|
|
1209
|
+
and the panel's separator line make**, reading `<<<` / `>>>` for the
|
|
1210
|
+
direction their panel travels, instead of circled carets floating near that
|
|
1211
|
+
corner. Both panels' controls take the same block start, so the pair reads
|
|
1212
|
+
as one row across the page; both are narrow enough to leave the first
|
|
1213
|
+
chapter's own expand caret alone; and each panel now reserves that corner
|
|
1214
|
+
(`--layout-edge-toggle-reserve`) instead of running its first row under the
|
|
1215
|
+
control. New tokens: `--layout-edge-toggle-width`,
|
|
1216
|
+
`--layout-edge-toggle-height`, `--layout-edge-toggle-gap` (one number for
|
|
1217
|
+
both of the control's edges, `2px`), `--layout-edge-toggle-radius` (applied
|
|
1218
|
+
to the one corner free of them) and `--layout-navigation-width`.
|
|
1219
|
+
- **While a collapsed panel peeks open over the article, its control grows to
|
|
1220
|
+
meet the separator rather than travelling to it.** Hovering the rail is both
|
|
1221
|
+
what opens the peek and what a reader does on the way to clicking the
|
|
1222
|
+
control, so moving the control at that moment takes it out from under the
|
|
1223
|
+
pointer that was coming for it — 260px away, across the panel. Its outer
|
|
1224
|
+
edge, and the glyph inside it, now stay exactly where the pointer found
|
|
1225
|
+
them while its inner end reaches the separator the open panel puts there.
|
|
1226
|
+
The glyph sits in a box the width of the resting control, pinned to that
|
|
1227
|
+
outer edge, so it does not shift at all.
|
|
1228
|
+
- Layout preferences moved from the `credit-playground:layout:*` storage keys
|
|
1229
|
+
to `explico:layout:*`. The old keys are still read once as a fallback, so a
|
|
1230
|
+
returning reader keeps the layout they left.
|
|
1231
|
+
|
|
1232
|
+
### Fixed
|
|
1233
|
+
|
|
1234
|
+
- **A compact chapter's lesson tooltip is no longer cut off at the navigation
|
|
1235
|
+
panel's edge.** That panel is a scroll container (`overflow-y: auto`, which
|
|
1236
|
+
forces `overflow-x` to match), so an absolutely positioned tooltip could not
|
|
1237
|
+
leave it: any lesson title longer than the panel is wide simply ended there.
|
|
1238
|
+
A showing tooltip is now `position: fixed`, placed from its anchor's own
|
|
1239
|
+
viewport rect and kept on screen at either edge, with its cap raised to
|
|
1240
|
+
`min(22rem, 45vw)`. It is still the link's accessible name in the markup —
|
|
1241
|
+
only where it is painted changed.
|
|
1242
|
+
|
|
1243
|
+
### Breaking
|
|
1244
|
+
|
|
1245
|
+
- `--layout-edge-toggle-size` is gone, replaced by the separate
|
|
1246
|
+
`--layout-edge-toggle-width` and `--layout-edge-toggle-height`. A course
|
|
1247
|
+
that overrode the old token to resize the control sets the two new ones.
|
|
1248
|
+
- The control's inner markup is `span.layout-edge-glyph` (with `-hide` and
|
|
1249
|
+
`-restore` variants) rather than `svg.layout-edge-icon`. A course that
|
|
1250
|
+
styled `.layout-edge-icon` retargets it.
|
|
1251
|
+
|
|
1252
|
+
## [0.8.0] - 2026-09-19
|
|
1253
|
+
|
|
1254
|
+
### Breaking
|
|
1255
|
+
|
|
1256
|
+
- **Rule N17 is inverted: a nested delimiter is sized by the engine, never by
|
|
1257
|
+
hand.** The engine now installs its own KaTeX delimiter sizing
|
|
1258
|
+
(`src/reference/katex-delimiters.mjs`), changing one constant in
|
|
1259
|
+
`makeLeftRightDelim` — `delimiterExtend`, TeX's `\delimitershortfall`, from
|
|
1260
|
+
`5.0` to `-1.0` points.
|
|
1261
|
+
Stock TeX and stock KaTeX let a `\left … \right` pair fall up to 5pt short of
|
|
1262
|
+
its content, so two pairs at different depths around similar content round to
|
|
1263
|
+
the same glyph and the nesting vanishes; N17 used to repair that by making
|
|
1264
|
+
authors hand-pick `\bigl` → `\Bigl` → `\biggl` → `\Biggl` outward. Those are
|
|
1265
|
+
*fixed* glyphs: they stop tracking the content they wrap, they run out after
|
|
1266
|
+
four levels, and editing an inner expression silently invalidates every size
|
|
1267
|
+
above it. With a negative shortfall a pair is always strictly taller than its
|
|
1268
|
+
content, so each enclosing level grows on its own, at any depth, by exactly
|
|
1269
|
+
what it needs. `\left … \right` at every level is now the rule, and
|
|
1270
|
+
hand-sizing a pair inside a nest is the warning.
|
|
1271
|
+
|
|
1272
|
+
Consequences for a course:
|
|
1273
|
+
- The Tier-3 diagnostic code is renamed `delimiter-size-hierarchy` →
|
|
1274
|
+
**`delimiter-manual-size`**; a `lint-ignore.yml` entry or a script matching
|
|
1275
|
+
the old code must be updated. Its diagnostic shape changed too: the
|
|
1276
|
+
`outerSize`/`nestedSize` fields are replaced by a single `size` (the
|
|
1277
|
+
hand-picked size found), and `suggestion` is now always present and always
|
|
1278
|
+
a `\left`/`\right` pair — it no longer carries a `size` field.
|
|
1279
|
+
- Math that was N17-compliant now warns. A ladder such as
|
|
1280
|
+
`\Biggl[a+\biggl(b+\Bigl[c+\left(d+e\right)\Bigr]\biggr)\Biggr]` reports one
|
|
1281
|
+
finding per hand-sized level; the repair is `\left`/`\right` at every level.
|
|
1282
|
+
A *lone* hand-sized pair that is not part of a nest — `\bigl(x,\ y\bigr)`
|
|
1283
|
+
for a tuple — is deliberately left alone.
|
|
1284
|
+
- **A course needs no install step for this.** `delimiterExtend` is a
|
|
1285
|
+
function-local `const`, so nothing KaTeX exports reaches it and the change
|
|
1286
|
+
has to happen in the module's own source. Rather than a `patches/` file —
|
|
1287
|
+
which pnpm records per project and so cannot travel inside the npm artifact
|
|
1288
|
+
— the engine reads KaTeX's ESM bundle, rewrites that constant, and moves
|
|
1289
|
+
the render entry points onto the shared `katex` object. `rehype-katex`
|
|
1290
|
+
reads `katex.renderToString` at call time and KaTeX's default export is
|
|
1291
|
+
mutable, so lesson-body math inherits it without the plugin being forked or
|
|
1292
|
+
aliased. `astro.config.mjs` already imports
|
|
1293
|
+
`explico/reference/katex-options.mjs`, which installs it, so an existing
|
|
1294
|
+
course picks this up on upgrade with no edit. The installer verifies itself
|
|
1295
|
+
— a KaTeX upgrade that moves the constant, or anything that renders first,
|
|
1296
|
+
fails the build with an explicit message instead of silently reverting to
|
|
1297
|
+
stock spacing. [`docs/katex-bracket-scaling.md`](docs/katex-bracket-scaling.md)
|
|
1298
|
+
derives the change and documents the mechanism and its trade-offs.
|
|
1299
|
+
|
|
1300
|
+
### Added
|
|
1301
|
+
|
|
1302
|
+
- **A math diagnostic now names the equation it belongs to.** Every finding
|
|
1303
|
+
the math gate reports from a `$$…$$` block that carries a `\label{eq:…}`
|
|
1304
|
+
gains an `equation` field — `{ key, href }`, where `href` is the anchor the
|
|
1305
|
+
rendered page gives that equation (`/l1-ar/ar1-basics/#eq-ar1-variance`) —
|
|
1306
|
+
and `diagnosticLine()` appends it, so the console line, the run log, and
|
|
1307
|
+
`pnpm content diagnostics` all read
|
|
1308
|
+
`[code] file:line:column: message (eq:<key> -> <href>)`. A size-only
|
|
1309
|
+
`delimiter-manual-size` finding inside a six-line `aligned` block was
|
|
1310
|
+
otherwise anonymous: the message names delimiters, never the result they
|
|
1311
|
+
belong to, so an author had to reconstruct which equation was meant from a
|
|
1312
|
+
line number alone. `CliDiagnostic` carries the same optional field, so a
|
|
1313
|
+
course's own `scripts/content.ts` gets it without change. The manifest
|
|
1314
|
+
schema is now **v16** (additive: v15 → v16); nothing existing changed shape.
|
|
1315
|
+
|
|
1316
|
+
- **The About panel publishes the identity it shows.** `AboutPanel.astro` now
|
|
1317
|
+
stamps `data-knowledge-version`, `data-knowledge-date` (omitted when the
|
|
1318
|
+
content changelog has no date) and `data-engine-version` on its
|
|
1319
|
+
`details[data-about-panel]` element, and marks the rendered version line with
|
|
1320
|
+
`data-knowledge-line`. The two versions the panel exists to publish were
|
|
1321
|
+
readable only as prose — and as a `version · date` join — so a course's build
|
|
1322
|
+
check had to match that text, which pins the check to one version string and
|
|
1323
|
+
turns every routine bump into a test edit; this repo's own
|
|
1324
|
+
`playground/tests/e2e/about.spec.ts` was asserting `0.4.0` against a
|
|
1325
|
+
playground long since at `0.5.2`. A bare version substring is not scoped to
|
|
1326
|
+
the panel either, so the engine version printed one row below can satisfy it.
|
|
1327
|
+
The attributes are additive: nothing rendered changed, and the existing
|
|
1328
|
+
`data-about-panel` hook is untouched.
|
|
1329
|
+
|
|
1330
|
+
### Fixed
|
|
1331
|
+
|
|
1332
|
+
- **A glossary heading's accessible name is the term again.** The notation
|
|
1333
|
+
glossary's per-entry anchor link moved *inside* its `<h2>` when the entry
|
|
1334
|
+
header was rebuilt for smooth scrolling, so the `aria-label` on that link
|
|
1335
|
+
("Link to “Velocity”") joined the heading's accessible name — a screen reader
|
|
1336
|
+
announced "Velocity Link to Velocity", and any check for the heading by name
|
|
1337
|
+
stopped matching. `NotationGlossary.astro` now wraps the heading and its
|
|
1338
|
+
anchor in a `.glossary-heading` flex row, the shape
|
|
1339
|
+
`rehype-heading-anchors` already gives a lesson heading (`<h2>` and
|
|
1340
|
+
`a.sl-anchor-link` as siblings). Nothing visual changed; the anchor still
|
|
1341
|
+
reveals on hover and focus.
|
|
1342
|
+
|
|
1343
|
+
- **A diagnostic's `line` now addresses the file it names.** Every gate scans
|
|
1344
|
+
`lesson.body`, which Astro's `parseFrontmatter` hands over with the
|
|
1345
|
+
frontmatter already removed, but each finding is reported against
|
|
1346
|
+
`lesson.source.file` — so every `file:line:column` the engine has ever
|
|
1347
|
+
emitted for a lesson was short by the height of that lesson's frontmatter.
|
|
1348
|
+
It failed silently in the worst way: the number stayed plausible, so an
|
|
1349
|
+
editor, a VS Code problem matcher, or an author following
|
|
1350
|
+
`pnpm content diagnostics` jumped confidently to the wrong line, and a
|
|
1351
|
+
size-only N17 finding could not be located at all. `parseDocument` now
|
|
1352
|
+
records the body's start line, `NotationLessonInput` carries it as an
|
|
1353
|
+
optional `bodyLine`, and the new `lessonLine()` helper applies it — so
|
|
1354
|
+
`gate-math` (including every `delimiter-manual-size` column),
|
|
1355
|
+
`gate-prose-notation` (N9), `gate-abbreviations` (N14), and
|
|
1356
|
+
`gate-heading-length` (S1) are all corrected at once. A hand-built lesson
|
|
1357
|
+
input that omits `bodyLine` behaves exactly as before.
|
|
1358
|
+
|
|
1359
|
+
- **`equationAnchorId({ key })` type-checks without a `number`.** Its JSDoc
|
|
1360
|
+
required both properties although `number` is read only when there is no
|
|
1361
|
+
`key`, so a keyed caller had to pass a value the function ignores.
|
|
1362
|
+
|
|
1363
|
+
- **A notation `formula` can use a manual delimiter size, an accent, a box, or
|
|
1364
|
+
a color without failing the build.** `render-notation-math.ts` sanitizes
|
|
1365
|
+
KaTeX's output against an attribute allowlist that had only ever grown to
|
|
1366
|
+
cover what the corpus happened to use, so ordinary math threw
|
|
1367
|
+
`Unexpected KaTeX property` with no author-side workaround: `\Bigl`/`\biggl`/
|
|
1368
|
+
`\Biggl` (`minsize`, `maxsize`), `\xrightarrow` (`minsize`), `\boxed`
|
|
1369
|
+
(`notation`), and `\colorbox` (`voffset`, `mathbackground`). Rule N17 asks
|
|
1370
|
+
authors for exactly those delimiter commands, so a card that followed the
|
|
1371
|
+
engine's own advice could not build. The allowlist is now KaTeX's whole inert
|
|
1372
|
+
presentation surface — `columnlines`, `depth`, `display`, `largeop`,
|
|
1373
|
+
`linebreak`, `linethickness`, `mathbackground`, `mathcolor`, `mathsize`,
|
|
1374
|
+
`maxsize`, `minsize`, `notation`, `rowlines`, `valign`, and `voffset` join the
|
|
1375
|
+
set — with `alt`, `href`, `src`, and `title` explicitly denied, and a new test
|
|
1376
|
+
scans KaTeX's own `setAttribute` calls so a KaTeX upgrade that adds an
|
|
1377
|
+
attribute fails here instead of in a downstream course. Hex colors are
|
|
1378
|
+
accepted in an attribute or style value; no attribute that can carry a URL is
|
|
1379
|
+
allowed through.
|
|
1380
|
+
|
|
1381
|
+
- **`\vec` (and every other accent) renders again.** The style sanitizer
|
|
1382
|
+
required each CSS declaration to end in `;`, but KaTeX does not always
|
|
1383
|
+
terminate the last one — `\vec` emits `style="width:0.471em"` — and the
|
|
1384
|
+
rejection then fell through to the misleading
|
|
1385
|
+
`Unexpected KaTeX property: style`. Declarations are now validated one at a
|
|
1386
|
+
time, and an unsafe `style` or `d` value says so instead of reporting an
|
|
1387
|
+
unknown property.
|
|
1388
|
+
|
|
1389
|
+
- **A macro in a formula no longer invents a source span.** KaTeX gives a node
|
|
1390
|
+
it produced by expanding a macro a `loc` from the lexer over the *macro
|
|
1391
|
+
body*, so those offsets index that body and not the source being scanned —
|
|
1392
|
+
and they are still valid indices into a longer source, so the glyph resolver
|
|
1393
|
+
read them as a real span over unrelated text. `\vdots` expands to
|
|
1394
|
+
`{\varvdots\rule{0pt}{15pt}}`, whose atom claims characters 1–10: in
|
|
1395
|
+
`M=\begin{pmatrix}…` that is `"=\begin{p"`, and because no author can wrap
|
|
1396
|
+
that slice in an `\explain`, a matrix showing a column ellipsis failed the
|
|
1397
|
+
build with `Unresolved notation ""`. Where the stray span instead collided
|
|
1398
|
+
with a glyph in scope, the binding was injected at the macro body's offsets
|
|
1399
|
+
and split the source mid-command — `\varSigma=\operatorname{Cov}(X)` became
|
|
1400
|
+
`\varSigm\explain{cov-matrix}{a=\ope}ratorname{Cov}(X)`, a corrupt formula no
|
|
1401
|
+
gate rejected. `nodeSpan` now believes a `loc` only when it belongs to the
|
|
1402
|
+
string this pass parsed. 79 of KaTeX's 282 macros emit such a node, among
|
|
1403
|
+
them `\argmax`, `\argmin`, `\plim`, `\limsup`, `\liminf`, `\bmod`, `\pmod`,
|
|
1404
|
+
`\R`, `\N`, `\Z` and every upright `\varGamma`-style capital. A macro's
|
|
1405
|
+
*argument* keeps the author's own locs and still binds normally. One limit
|
|
1406
|
+
remains: a macro-expanded glyph has no authored span, so canonical matching
|
|
1407
|
+
and the completeness gate cannot see it — annotate it with an explicit
|
|
1408
|
+
`\explain{key}{\varSigma}`, which works and is unaffected by this change.
|
|
1409
|
+
|
|
1410
|
+
- **A malformed `\explain` / `\slot` key fails the build instead of reaching
|
|
1411
|
+
the reader.** KaTeX does not treat a denied `trust` callback as a parse
|
|
1412
|
+
error: it renders the command name in `errorColor` and continues, so
|
|
1413
|
+
`\explain{Not A Key}{\Omega}` produced red `\htmlData` text on the page with
|
|
1414
|
+
the build green. (Until now this was masked by `mathcolor` — the attribute
|
|
1415
|
+
KaTeX's error rendering carries — not being in the allowlist.)
|
|
1416
|
+
`renderNotationMath` now checks both marker macros against
|
|
1417
|
+
`trustNotationMarker` before KaTeX runs and throws naming the offending key.
|
|
1418
|
+
`renderNotationFormula` still degrades to inert-but-correct math rather than
|
|
1419
|
+
costing a reader the equation.
|
|
1420
|
+
|
|
17
1421
|
## [0.7.0] - 2026-09-18
|
|
18
1422
|
|
|
19
1423
|
### Breaking
|
|
@@ -722,7 +2126,11 @@ under **Breaking** below; a PATCH never breaks.
|
|
|
722
2126
|
system with its completeness gate, the curriculum-graph validator, the
|
|
723
2127
|
Astro/Starlight UI, the LMS / session / analytics seams, and the `content` CLI.
|
|
724
2128
|
|
|
725
|
-
[Unreleased]: https://github.com/danieltuzes/explico/compare/v0.
|
|
2129
|
+
[Unreleased]: https://github.com/danieltuzes/explico/compare/v0.11.0...HEAD
|
|
2130
|
+
[0.11.0]: https://github.com/danieltuzes/explico/compare/v0.10.0...v0.11.0
|
|
2131
|
+
[0.10.0]: https://github.com/danieltuzes/explico/compare/v0.9.0...v0.10.0
|
|
2132
|
+
[0.9.0]: https://github.com/danieltuzes/explico/compare/v0.8.0...v0.9.0
|
|
2133
|
+
[0.8.0]: https://github.com/danieltuzes/explico/compare/v0.7.0...v0.8.0
|
|
726
2134
|
[0.7.0]: https://github.com/danieltuzes/explico/compare/v0.6.0...v0.7.0
|
|
727
2135
|
[0.6.0]: https://github.com/danieltuzes/explico/compare/v0.5.0...v0.6.0
|
|
728
2136
|
[0.5.0]: https://github.com/danieltuzes/explico/compare/v0.4.0...v0.5.0
|