explico 0.13.1 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (256) hide show
  1. package/CHANGELOG.md +990 -1
  2. package/README.md +200 -30
  3. package/dist/compiler/astro.d.ts +15 -2
  4. package/dist/compiler/astro.js +40 -11
  5. package/dist/compiler/cli-main.d.ts +100 -0
  6. package/dist/compiler/cli-main.js +433 -0
  7. package/dist/compiler/cli-reference.d.ts +50 -0
  8. package/dist/compiler/cli-reference.js +213 -0
  9. package/dist/compiler/cli-verbs.d.ts +45 -0
  10. package/dist/compiler/cli-verbs.js +224 -0
  11. package/dist/compiler/cli-verify.d.ts +51 -0
  12. package/dist/compiler/cli-verify.js +151 -0
  13. package/dist/compiler/cli.d.ts +408 -24
  14. package/dist/compiler/cli.js +1328 -8
  15. package/dist/compiler/collections.d.ts +88 -1
  16. package/dist/compiler/collections.js +170 -25
  17. package/dist/compiler/content-release.d.ts +49 -0
  18. package/dist/compiler/content-release.js +63 -0
  19. package/dist/compiler/content-schemas.d.ts +2 -2
  20. package/dist/compiler/course-config.d.ts +1 -1
  21. package/dist/compiler/course-config.js +105 -1
  22. package/dist/compiler/decks.d.ts +161 -0
  23. package/dist/compiler/decks.js +235 -0
  24. package/dist/compiler/exports/combined-html.d.ts +5 -1
  25. package/dist/compiler/exports/deck-fit.d.ts +55 -0
  26. package/dist/compiler/exports/deck-fit.js +90 -0
  27. package/dist/compiler/exports/export-chapters.d.ts +37 -6
  28. package/dist/compiler/exports/export-chapters.js +64 -5
  29. package/dist/compiler/exports/export-pages.d.ts +30 -3
  30. package/dist/compiler/exports/export-pages.js +24 -1
  31. package/dist/compiler/exports/latex/beamerthemeexplico.sty +124 -0
  32. package/dist/compiler/exports/latex/cache.d.ts +78 -0
  33. package/dist/compiler/exports/latex/cache.js +129 -0
  34. package/dist/compiler/exports/latex/capture.d.ts +10 -1
  35. package/dist/compiler/exports/latex/capture.js +3 -1
  36. package/dist/compiler/exports/latex/compile.d.ts +13 -2
  37. package/dist/compiler/exports/latex/compile.js +12 -1
  38. package/dist/compiler/exports/latex/deck.d.ts +92 -0
  39. package/dist/compiler/exports/latex/deck.js +349 -0
  40. package/dist/compiler/exports/latex/document.d.ts +51 -6
  41. package/dist/compiler/exports/latex/document.js +71 -12
  42. package/dist/compiler/exports/latex/explico-common.sty +198 -0
  43. package/dist/compiler/exports/latex/explico.cls +13 -183
  44. package/dist/compiler/exports/latex/serialize.d.ts +60 -4
  45. package/dist/compiler/exports/latex/serialize.js +4 -2
  46. package/dist/compiler/exports/latex-export.d.ts +24 -2
  47. package/dist/compiler/exports/latex-export.js +107 -26
  48. package/dist/compiler/exports/markdown/serialize.d.ts +24 -4
  49. package/dist/compiler/exports/markdown/serialize.js +85 -8
  50. package/dist/compiler/exports/markdown-export.d.ts +6 -1
  51. package/dist/compiler/exports/markdown-export.js +50 -11
  52. package/dist/compiler/exports/standalone-html.d.ts +10 -5
  53. package/dist/compiler/exports/standalone-html.js +3 -3
  54. package/dist/compiler/exports/static-tree.d.ts +38 -1
  55. package/dist/compiler/exports/static-tree.js +120 -4
  56. package/dist/compiler/knowledge-version.d.ts +26 -2
  57. package/dist/compiler/knowledge-version.js +27 -17
  58. package/dist/compiler/lesson-derivation.d.ts +143 -6
  59. package/dist/compiler/lesson-derivation.js +145 -13
  60. package/dist/compiler/manifest.d.ts +133 -4
  61. package/dist/compiler/manifest.js +364 -58
  62. package/dist/compiler/page-versions.d.ts +184 -0
  63. package/dist/compiler/page-versions.js +217 -0
  64. package/dist/compiler/render-diagnostics.d.ts +5 -1
  65. package/dist/compiler/resolution-report.d.ts +24 -6
  66. package/dist/compiler/resolution-report.js +22 -4
  67. package/dist/compiler/validate-content.d.ts +7 -0
  68. package/dist/compiler/validate-content.js +160 -0
  69. package/dist/{course-config-rRtME5UV.d.ts → course-config-BLtIdkei.d.ts} +164 -1
  70. package/dist/curriculum/competency-graph.d.ts +54 -0
  71. package/dist/curriculum/competency-graph.js +41 -0
  72. package/dist/reference/base-library.d.mts +8 -0
  73. package/dist/reference/base-library.mjs +19 -0
  74. package/dist/reference/consistency.d.ts +8 -4
  75. package/dist/reference/consistency.js +82 -26
  76. package/dist/reference/deck-remark.d.mts +123 -0
  77. package/dist/reference/deck-remark.mjs +600 -0
  78. package/dist/reference/deck-source.d.ts +107 -0
  79. package/dist/reference/deck-source.js +140 -0
  80. package/dist/reference/decks-validate.d.ts +133 -0
  81. package/dist/reference/decks-validate.js +359 -0
  82. package/dist/reference/derived-entries.d.ts +5 -1
  83. package/dist/reference/diagram-fit.d.ts +6 -2
  84. package/dist/reference/diagram-fit.js +5 -1
  85. package/dist/reference/figure-symbols.d.ts +23 -6
  86. package/dist/reference/figure-symbols.js +51 -15
  87. package/dist/reference/float-name.d.ts +43 -0
  88. package/dist/reference/float-name.js +58 -0
  89. package/dist/reference/folds-validate.d.ts +1 -1
  90. package/dist/reference/gate-abbreviations.d.ts +1 -1
  91. package/dist/reference/gate-indices.js +5 -4
  92. package/dist/reference/gate-math.d.ts +29 -2
  93. package/dist/reference/gate-math.js +99 -18
  94. package/dist/reference/gate-part-pages.d.ts +1 -1
  95. package/dist/reference/lab-katex-validate.d.ts +10 -6
  96. package/dist/reference/lab-katex-validate.js +17 -9
  97. package/dist/reference/lab-math-scope.js +29 -4
  98. package/dist/reference/math-glyphs.d.mts +25 -0
  99. package/dist/reference/math-glyphs.mjs +189 -25
  100. package/dist/reference/page-notation.d.ts +22 -3
  101. package/dist/reference/page-notation.js +32 -10
  102. package/dist/reference/references.d.ts +14 -1
  103. package/dist/reference/references.js +13 -0
  104. package/dist/reference/registry-scope.d.mts +33 -0
  105. package/dist/reference/registry-scope.mjs +34 -0
  106. package/dist/reference/registry.js +29 -10
  107. package/dist/reference/rehype-deck.d.mts +6 -0
  108. package/dist/reference/rehype-deck.mjs +286 -0
  109. package/dist/reference/rehype-heading-anchors.d.mts +20 -0
  110. package/dist/reference/rehype-heading-anchors.mjs +48 -6
  111. package/dist/reference/remark-notation.mjs +249 -40
  112. package/dist/reference/source-lines.d.mts +12 -0
  113. package/dist/reference/source-lines.mjs +65 -0
  114. package/dist/reference/types.d.ts +28 -0
  115. package/dist/rules/catalog.d.ts +21 -4
  116. package/dist/rules/catalog.js +237 -2
  117. package/dist/rules/course-rules.d.ts +5 -1
  118. package/dist/rules/course-rules.js +17 -3
  119. package/dist/rules/course-rules.schema.json +12 -0
  120. package/dist/rules/in-effect.d.ts +13 -3
  121. package/dist/rules/in-effect.js +34 -10
  122. package/dist/rules/rules.schema.json +280 -1
  123. package/dist/rules/settings.d.ts +1 -1
  124. package/dist/session/PreferenceStore.d.ts +6 -0
  125. package/dist/session/PreferenceStore.js +18 -1
  126. package/dist/session/deck-memory.d.ts +116 -0
  127. package/dist/session/deck-memory.js +143 -0
  128. package/dist/session/reading-memory.d.ts +96 -0
  129. package/dist/session/reading-memory.js +133 -0
  130. package/docs/VERSIONING.md +21 -18
  131. package/docs/authoring/README.md +31 -9
  132. package/docs/authoring/agents.md +72 -18
  133. package/docs/authoring/cli-reference.json +2172 -0
  134. package/docs/authoring/cli.md +887 -0
  135. package/docs/authoring/components.md +15 -0
  136. package/docs/authoring/deck.md +612 -0
  137. package/docs/authoring/migrating.md +1195 -16
  138. package/docs/authoring/notation.md +49 -6
  139. package/docs/authoring/structure.md +80 -1
  140. package/docs/authoring/versioning.md +56 -3
  141. package/docs/authoring/visualizations.md +7 -2
  142. package/docs/course-agent/AGENTS.md +40 -0
  143. package/docs/course-agent/agents/rules-reviewer.md +33 -0
  144. package/docs/course-agent/skills/draft-lesson/SKILL.md +38 -0
  145. package/docs/course-agent/skills/migrate-course/SKILL.md +32 -0
  146. package/docs/course-agent/skills/release-content/SKILL.md +26 -0
  147. package/docs/diagram-scaling.md +148 -0
  148. package/docs/privacy.md +59 -0
  149. package/package.json +8 -2
  150. package/src/compiler/astro.ts +60 -21
  151. package/src/compiler/cli-main.ts +557 -0
  152. package/src/compiler/cli-reference.ts +285 -0
  153. package/src/compiler/cli-verbs.ts +270 -0
  154. package/src/compiler/cli-verify.ts +241 -0
  155. package/src/compiler/cli.ts +2119 -170
  156. package/src/compiler/collections.ts +284 -25
  157. package/src/compiler/content-release.ts +91 -0
  158. package/src/compiler/course-config.ts +267 -0
  159. package/src/compiler/decks.ts +455 -0
  160. package/src/compiler/exports/deck-fit.ts +127 -0
  161. package/src/compiler/exports/export-chapters.ts +105 -6
  162. package/src/compiler/exports/export-pages.ts +46 -2
  163. package/src/compiler/exports/latex/beamerthemeexplico.sty +124 -0
  164. package/src/compiler/exports/latex/cache.ts +183 -0
  165. package/src/compiler/exports/latex/capture.ts +5 -2
  166. package/src/compiler/exports/latex/compile.ts +18 -0
  167. package/src/compiler/exports/latex/deck.ts +419 -0
  168. package/src/compiler/exports/latex/document.ts +117 -21
  169. package/src/compiler/exports/latex/explico-common.sty +198 -0
  170. package/src/compiler/exports/latex/explico.cls +13 -183
  171. package/src/compiler/exports/latex/serialize.ts +25 -21
  172. package/src/compiler/exports/latex/texlive-packages.txt +6 -2
  173. package/src/compiler/exports/latex-export.ts +187 -45
  174. package/src/compiler/exports/markdown/serialize.ts +118 -9
  175. package/src/compiler/exports/markdown-export.ts +56 -11
  176. package/src/compiler/exports/standalone-html.ts +8 -7
  177. package/src/compiler/exports/static-tree.ts +175 -3
  178. package/src/compiler/knowledge-version.ts +56 -21
  179. package/src/compiler/lesson-derivation.ts +282 -16
  180. package/src/compiler/manifest.ts +568 -63
  181. package/src/compiler/page-versions.ts +401 -0
  182. package/src/compiler/resolution-report.ts +38 -7
  183. package/src/compiler/validate-content.ts +215 -0
  184. package/src/components/CourseContents.astro +124 -6
  185. package/src/components/CurriculumMap.astro +49 -1
  186. package/src/components/FloatIndex.astro +1 -1
  187. package/src/components/PartOverview.astro +51 -5
  188. package/src/components/assessments/AssessmentSet.astro +41 -15
  189. package/src/components/block-heading.ts +15 -6
  190. package/src/components/card-carry.ts +7 -4
  191. package/src/components/card-manager.ts +14 -2
  192. package/src/components/card-memory.ts +13 -9
  193. package/src/components/deck/DeckEnhancer.astro +13 -0
  194. package/src/components/deck/deck-context.ts +104 -0
  195. package/src/components/deck/deck-controller.ts +1287 -0
  196. package/src/components/deck/deck-map.ts +91 -0
  197. package/src/components/deck-lane.ts +163 -0
  198. package/src/components/diagram/Diagram.astro +131 -86
  199. package/src/components/disclosure/persisted-disclosure.ts +9 -1
  200. package/src/components/figure/Figure.astro +7 -2
  201. package/src/components/figure/FloatCaption.astro +5 -3
  202. package/src/components/fold/Fold.astro +2 -2
  203. package/src/components/notation/NotationFormula.astro +81 -11
  204. package/src/components/notation/NotationGlossary.astro +132 -1
  205. package/src/components/notation/NotationLayer.astro +15 -7
  206. package/src/components/starlight/AboutPanel.astro +186 -3
  207. package/src/components/starlight/CollapsibleSections.astro +24 -3
  208. package/src/components/starlight/DiagramEnhancer.astro +5 -1
  209. package/src/components/starlight/HeadingAnchorEnhancer.astro +1 -1
  210. package/src/components/starlight/LayoutHeader.astro +17 -0
  211. package/src/components/starlight/LayoutPageTitle.astro +110 -0
  212. package/src/components/starlight/LayoutSidebar.astro +807 -21
  213. package/src/components/starlight/LayoutSocialIcons.astro +62 -6
  214. package/src/components/starlight/LessonExportActions.astro +27 -9
  215. package/src/components/starlight/LessonFooter.astro +59 -0
  216. package/src/components/starlight/ReadingMemoryNotice.astro +114 -0
  217. package/src/components/starlight/TextSizeControl.astro +24 -13
  218. package/src/components/starlight/page-marks.ts +139 -0
  219. package/src/components/starlight/section-summary.ts +1 -1
  220. package/src/components/starlight/sidebar-editions.ts +245 -0
  221. package/src/components/table/Table.astro +15 -7
  222. package/src/content-config.ts +87 -1
  223. package/src/curriculum/competency-graph.ts +95 -0
  224. package/src/reference/base-library.mjs +19 -0
  225. package/src/reference/consistency.ts +137 -39
  226. package/src/reference/deck-remark.mjs +600 -0
  227. package/src/reference/deck-source.ts +273 -0
  228. package/src/reference/decks-validate.ts +604 -0
  229. package/src/reference/diagram-fit.ts +18 -4
  230. package/src/reference/diagram-layout.ts +3 -1
  231. package/src/reference/figure-symbols.ts +108 -31
  232. package/src/reference/folds-validate.ts +1 -1
  233. package/src/reference/gate-indices.ts +5 -3
  234. package/src/reference/gate-math.ts +169 -25
  235. package/src/reference/lab-katex-validate.ts +30 -15
  236. package/src/reference/lab-math-scope.ts +34 -3
  237. package/src/reference/math-glyphs.mjs +189 -25
  238. package/src/reference/page-notation.ts +69 -15
  239. package/src/reference/references.ts +28 -0
  240. package/src/reference/registry-scope.mjs +34 -0
  241. package/src/reference/registry.ts +41 -11
  242. package/src/reference/rehype-deck.mjs +286 -0
  243. package/src/reference/rehype-heading-anchors.mjs +48 -6
  244. package/src/reference/remark-notation.mjs +249 -40
  245. package/src/reference/source-lines.mjs +65 -0
  246. package/src/reference/types.ts +28 -0
  247. package/src/rules/catalog.ts +274 -4
  248. package/src/rules/course-rules.ts +21 -1
  249. package/src/rules/in-effect.ts +58 -11
  250. package/src/session/PreferenceStore.ts +23 -0
  251. package/src/session/deck-memory.ts +263 -0
  252. package/src/session/reading-memory.ts +243 -0
  253. package/src/styles/deck.css +1150 -0
  254. package/src/styles/global.css +958 -27
  255. package/src/styles/zones.css +277 -0
  256. /package/src/{components → reference}/float-name.ts +0 -0
@@ -0,0 +1,96 @@
1
+ import { PreferenceStore } from './PreferenceStore.js';
2
+
3
+ /**
4
+ * The one question explico asks a reader: may this course remember their
5
+ * reading in this browser? (`readingMemory` in the course settings.)
6
+ *
7
+ * **Reading memory** is what records what the reader did, across visits:
8
+ * which chapters they opened (the sidebar marks and the "changed since you
9
+ * last opened it" note, `page-marks.ts`), the cards they kept open on a page
10
+ * (`card-memory.ts`), the cards they carry from page to page
11
+ * (`card-carry.ts`), and where each deck stopped and which slides Present
12
+ * mode showed them (`deck-memory.ts`). It is kept only with the reader's yes.
13
+ * Without it — before they answer, after a no, or when the course turns the
14
+ * question off — nothing of their reading outlives the tab: no visit is
15
+ * recorded and no page is marked, and kept and carried cards and where a deck
16
+ * stopped live in the tab's session storage, so they still work until the tab
17
+ * is closed. The slides Present mode showed live there too before an answer,
18
+ * marked on the deck's page; a no clears them, and none is recorded after it.
19
+ *
20
+ * **Settings** are not reading memory: the theme, the text size, the panels'
21
+ * layout, a folded section or sidebar part, which edition a lesson's sidebar
22
+ * row opens, the "Carry cards" switch. The
23
+ * reader sets each of them, and they are kept because the reader set them,
24
+ * the exemption the ePrivacy rules give a preference the user asked for. They
25
+ * need no answer. Nothing explico stores is a cookie, and nothing leaves the
26
+ * browser.
27
+ *
28
+ * The answer itself is kept (storing a consent choice needs no consent), per
29
+ * course, for 13 months, after which the course asks again. A no deletes
30
+ * whatever reading memory the course had; "Forget what this course stored"
31
+ * (the `[?]` panel) deletes everything, the answer included.
32
+ *
33
+ * Storage goes through the `PreferenceStore` only. The pure half
34
+ * (`decodeAnswer`, `courseKeys`, …) is checked in Node by
35
+ * `tests/unit/reading-memory.test.ts`.
36
+ */
37
+
38
+ /** The reader's answer: yes, no, or not asked yet (or asked too long ago). */
39
+ type ReadingAnswer = 'yes' | 'no' | 'unasked';
40
+ /** The course's `readingMemory` setting: ask the reader, or never keep reading memory. */
41
+ type ReadingMemoryMode = 'ask' | 'off';
42
+ /** How long an answer stands before the course asks again: 13 months. */
43
+ declare const ANSWER_LIFETIME_DAYS = 396;
44
+ /** The event every part of the page listens to when the reader answers or forgets. */
45
+ declare const READING_MEMORY_EVENT = "explico:reading-memory";
46
+ interface ReadingMemoryChange {
47
+ readonly base: string;
48
+ readonly answer: ReadingAnswer;
49
+ }
50
+ /** Where a course keeps the reader's answer. */
51
+ declare function answerKey(base: string): string;
52
+ /** The stored answer's text. */
53
+ declare function encodeAnswer(yes: boolean, now: Date): string;
54
+ /** The answer a stored text holds on `now`: anything unreadable, or older than its lifetime, is not asked. */
55
+ declare function decodeAnswer(raw: string | null, now: Date): ReadingAnswer;
56
+ /**
57
+ * Where a course keeps, for each of its decks, the frame Present mode last
58
+ * stood on and the frames it showed (`deck-memory.ts`, `deck-controller.ts`).
59
+ */
60
+ declare function deckPositionsKey(base: string): string;
61
+ /**
62
+ * A key that is this course's reading memory: its visits, its kept and its
63
+ * carried cards, where its decks stopped and the slides they showed.
64
+ */
65
+ declare function isReadingMemoryKey(key: string, base: string): boolean;
66
+ /**
67
+ * Every key this course stored, of the keys a store holds: its reading
68
+ * memory, its answer, its sections' and cards' per-page state, and the
69
+ * settings every explico course on this site shares, which forgetting this
70
+ * course resets too. Starlight's own theme key goes with them.
71
+ */
72
+ declare function courseKeys(keys: readonly string[], base: string): string[];
73
+ /** The course's setting, from the element `ReadingMemoryNotice.astro` renders on every page. */
74
+ declare function readingMemoryMode(): ReadingMemoryMode;
75
+ /** The reader's answer for the course at `base`. */
76
+ declare function readingAnswer(base: string, now?: Date): ReadingAnswer;
77
+ /** Whether the course may keep the reader's reading memory: they said yes, and the course asks. */
78
+ declare function rememberReading(base: string): boolean;
79
+ /**
80
+ * Where reading memory goes: kept in the browser with the reader's yes, else
81
+ * the tab's session only. Read the session copy too when kept, so the cards
82
+ * of a visit that started before the yes are not lost.
83
+ */
84
+ declare function readingMemoryStore(base: string): PreferenceStore;
85
+ /** Delete the course's reading memory from the browser (the tab's session copy stays). */
86
+ declare function forgetReadingMemory(base: string): void;
87
+ /**
88
+ * Record the reader's answer. A yes keeps the slides the tab recorded before
89
+ * it; a no deletes the course's reading memory, and those slides. Everything
90
+ * on the page is told.
91
+ */
92
+ declare function answerReadingMemory(base: string, yes: boolean, now?: Date): void;
93
+ /** "Forget what this course stored": every key of the course, in the browser and the tab; the course asks again. */
94
+ declare function forgetCourse(base: string): void;
95
+
96
+ export { ANSWER_LIFETIME_DAYS, READING_MEMORY_EVENT, type ReadingAnswer, type ReadingMemoryChange, type ReadingMemoryMode, answerKey, answerReadingMemory, courseKeys, deckPositionsKey, decodeAnswer, encodeAnswer, forgetCourse, forgetReadingMemory, isReadingMemoryKey, readingAnswer, readingMemoryMode, readingMemoryStore, rememberReading };
@@ -0,0 +1,133 @@
1
+ import {
2
+ createBrowserPreferenceStore,
3
+ createSessionPreferenceStore
4
+ } from "./PreferenceStore.js";
5
+ import {
6
+ decodeDeckMemory,
7
+ encodeDeckMemory,
8
+ mergeDeckMemory,
9
+ withEveryDeckUnseen
10
+ } from "./deck-memory.js";
11
+ const ANSWER_LIFETIME_DAYS = 396;
12
+ const VERSION = 1;
13
+ const DAY = 24 * 60 * 60 * 1e3;
14
+ const READING_MEMORY_EVENT = "explico:reading-memory";
15
+ function answerKey(base) {
16
+ return `explico-reading-memory:${base}`;
17
+ }
18
+ function encodeAnswer(yes, now) {
19
+ return JSON.stringify({ v: VERSION, remember: yes, at: now.toISOString().slice(0, 10) });
20
+ }
21
+ function decodeAnswer(raw, now) {
22
+ if (!raw) return "unasked";
23
+ let parsed;
24
+ try {
25
+ parsed = JSON.parse(raw);
26
+ } catch {
27
+ return "unasked";
28
+ }
29
+ if (!parsed || typeof parsed !== "object") return "unasked";
30
+ const { v, remember, at } = parsed;
31
+ if (v !== VERSION || typeof remember !== "boolean" || typeof at !== "string") return "unasked";
32
+ const given = Date.parse(`${at}T00:00:00Z`);
33
+ if (!Number.isFinite(given) || now.getTime() - given > ANSWER_LIFETIME_DAYS * DAY || given - now.getTime() > DAY) {
34
+ return "unasked";
35
+ }
36
+ return remember ? "yes" : "no";
37
+ }
38
+ const SHARED_SETTINGS = [
39
+ /^explico:display:/u,
40
+ /^explico:layout:/u,
41
+ /^explico-sidebar-groups$/u,
42
+ /^explico-sidebar-editions$/u,
43
+ /^explico:cards:carry$/u
44
+ ];
45
+ function deckPositionsKey(base) {
46
+ return `explico:decks:${base}`;
47
+ }
48
+ function isReadingMemoryKey(key, base) {
49
+ return key === `explico-page-marks:${base}` || key === `explico:cards:carried:${base}` || key === deckPositionsKey(base) || key.startsWith(`explico:cards:${base}`) && !key.startsWith("explico:cards:carried:");
50
+ }
51
+ function courseKeys(keys, base) {
52
+ return keys.filter(
53
+ (key) => isReadingMemoryKey(key, base) || key === answerKey(base) || key.startsWith(`disclosure:${base}`) || key === "explico:cards:opened-at" || key === "starlight-theme" || key === "sl-sidebar-state" || SHARED_SETTINGS.some((pattern) => pattern.test(key))
54
+ );
55
+ }
56
+ const local = createBrowserPreferenceStore();
57
+ const session = createSessionPreferenceStore();
58
+ function remove(store, key) {
59
+ if (store.remove) store.remove(key);
60
+ else store.set(key, "");
61
+ }
62
+ function readingMemoryMode() {
63
+ const element = document.querySelector("[data-explico-reading-memory]");
64
+ return element?.dataset.explicoReadingMemory === "off" ? "off" : "ask";
65
+ }
66
+ function readingAnswer(base, now = /* @__PURE__ */ new Date()) {
67
+ return decodeAnswer(local.get(answerKey(base)), now);
68
+ }
69
+ function rememberReading(base) {
70
+ return readingMemoryMode() === "ask" && readingAnswer(base) === "yes";
71
+ }
72
+ function readingMemoryStore(base) {
73
+ if (!rememberReading(base)) return session;
74
+ return {
75
+ get: (key) => local.get(key) ?? session.get(key),
76
+ set: (key, value) => local.set(key, value),
77
+ remove: (key) => {
78
+ remove(local, key);
79
+ remove(session, key);
80
+ }
81
+ };
82
+ }
83
+ function forgetReadingMemory(base) {
84
+ for (const key of local.keys?.() ?? []) if (isReadingMemoryKey(key, base)) remove(local, key);
85
+ }
86
+ function announce(base) {
87
+ window.dispatchEvent(
88
+ new CustomEvent(READING_MEMORY_EVENT, { detail: { base, answer: readingAnswer(base) } })
89
+ );
90
+ }
91
+ function keepShownSlides(base) {
92
+ const raw = session.get(deckPositionsKey(base));
93
+ if (!raw) return;
94
+ const kept = mergeDeckMemory(decodeDeckMemory(local.get(deckPositionsKey(base))), decodeDeckMemory(raw));
95
+ local.set(deckPositionsKey(base), encodeDeckMemory(kept));
96
+ }
97
+ function forgetShownSlides(base) {
98
+ const raw = session.get(deckPositionsKey(base));
99
+ if (raw) session.set(deckPositionsKey(base), encodeDeckMemory(withEveryDeckUnseen(decodeDeckMemory(raw))));
100
+ }
101
+ function answerReadingMemory(base, yes, now = /* @__PURE__ */ new Date()) {
102
+ local.set(answerKey(base), encodeAnswer(yes, now));
103
+ if (yes) {
104
+ keepShownSlides(base);
105
+ } else {
106
+ forgetReadingMemory(base);
107
+ forgetShownSlides(base);
108
+ }
109
+ announce(base);
110
+ }
111
+ function forgetCourse(base) {
112
+ for (const store of [local, session]) {
113
+ for (const key of courseKeys(store.keys?.() ?? [], base)) remove(store, key);
114
+ }
115
+ announce(base);
116
+ }
117
+ export {
118
+ ANSWER_LIFETIME_DAYS,
119
+ READING_MEMORY_EVENT,
120
+ answerKey,
121
+ answerReadingMemory,
122
+ courseKeys,
123
+ deckPositionsKey,
124
+ decodeAnswer,
125
+ encodeAnswer,
126
+ forgetCourse,
127
+ forgetReadingMemory,
128
+ isReadingMemoryKey,
129
+ readingAnswer,
130
+ readingMemoryMode,
131
+ readingMemoryStore,
132
+ rememberReading
133
+ };
@@ -88,26 +88,29 @@ applies every intervening section in order.
88
88
 
89
89
  ## Cutting a release
90
90
 
91
+ Two judgment steps, then one command.
92
+
91
93
  1. Decide the next version from the rules above and the `## [Unreleased]`
92
94
  section of `CHANGELOG.md`.
93
- 2. Add the section for `x.y.z` to `docs/authoring/migrating.md`, derived from the
94
- complete release diff. Write explicit no-action instructions when nothing in
95
- a downstream course needs to change. Walk the `CHANGELOG.md` entry you're
96
- about to finalize bullet by bullet and give every `rule N#` it names, and
97
- every `is now vN` schema bump, its own coverage — not only the entry's
98
- headline item.
99
- 3. `CHANGELOG.md`: rename `## [Unreleased]` to `## [x.y.z] - YYYY-MM-DD`, add a
100
- fresh empty `## [Unreleased]`, and fix up the compare links at the bottom.
101
- 4. Bump `package.json` `version` (`npm version x.y.z --no-git-tag-version`).
102
- 5. Run `pnpm check:release-migration`. It fails when the new version has no
103
- migration section, the shipped-file configuration excludes the guide, or a
104
- `rule N#` / `is now vN` mention in the `CHANGELOG.md` entry has no match in
105
- the `migrating.md` section.
106
- 6. Run `pnpm build && pnpm test`, then inspect `pnpm pack --dry-run` and confirm
107
- that `docs/authoring/migrating.md` and `docs/authoring/agents.md` are listed.
108
- 7. Commit, then tag it annotated: `git tag -a vx.y.z -m "explico x.y.z"`. With
109
- `push.followTags` on, a plain `git push` sends an annotated tag with its
110
- commit; a lightweight tag never travels that way.
95
+ 2. Complete the `## <previous> → Unreleased` section at the top of
96
+ `docs/authoring/migrating.md`, derived from the complete changelog entry you
97
+ are about to finalize: every consumer-owned surface that moved, exact edits
98
+ or commands, conditional steps, the checks to run; write explicit no-action
99
+ instructions when nothing in a downstream course needs to change. Give every
100
+ `rule N#` the entry names and every `is now vN` schema bump its own
101
+ coverage, in that phrasing — `check:release-migration` matches them
102
+ mechanically.
103
+ 3. `pnpm release <x.y.z>` (`scripts/release.mjs`) does the rest: it refuses a
104
+ dirty tree, an empty `[Unreleased]` or a guide without the draft, then
105
+ renames the draft's heading to the version, dates the changelog entry and
106
+ fixes the compare links, bumps `package.json`, and runs
107
+ `check:release-migration`, the build, the tests and `pnpm pack --dry-run`
108
+ (the two shipped guides must be listed); a failed check puts the files back.
109
+ It stops at the diff; `--commit` commits and tags `v<x.y.z>` annotated
110
+ (with `push.followTags` on, a plain `git push` carries the tag; a
111
+ lightweight one never travels); `--dry-run` rehearses and restores.
112
+ 4. Publish with the VS Code task **Explico: Publish to npm**: the maintainer's
113
+ step, behind the account's security key.
111
114
 
112
115
  ## When you change the engine
113
116
 
@@ -3,6 +3,11 @@
3
3
  Content rules for explico courses — strong recommendations for the people and
4
4
  agents who write lessons, notation entries, and interactive figures.
5
5
 
6
+ A path these pages cite that starts with `playground/` is a file of the
7
+ playground course, which is not in the npm package: its source, at the engine's
8
+ latest release, is [downloadable](https://danieltuzes.github.io/explico/downloads/explico-playground-latest.zip) from https://danieltuzes.github.io/explico/start/, together with a full
9
+ course, credit-and-derivatives.
10
+
6
11
  ## What these are
7
12
 
8
13
  A course built on explico is authored by many hands — lecturers, TAs, AI
@@ -102,7 +107,7 @@ speak in: what a part, a chapter, a section, and a subsection are, and which of
102
107
  them an equation number counts.
103
108
 
104
109
  Each rule has a stable ID — a letter for its area (`N` notation,
105
- `V` visualizations, `C` versioning, `S` structure, `P` prose) plus a number — and this shape.
110
+ `V` visualizations, `C` versioning, `S` structure, `P` prose, `D` decks) plus a number — and this shape.
106
111
  `X` is not one of them: it is reserved for the rules a course writes for itself
107
112
  ([Rules of your own](#rules-of-your-own)), so the engine never ships an `X`
108
113
  rule and a course can number its own without waiting for a release.
@@ -230,11 +235,13 @@ of to the settings file.
230
235
  | -------------------------------- | ---- | --------------------------------------------------------- |
231
236
  | [notation.md](notation.md) | `N` | how math symbols are written and bound |
232
237
  | [visualizations.md](visualizations.md) | `V` | diagrams, charts, interactive figures, randomness |
233
- | [versioning.md](versioning.md) | `C` | the knowledge version and `content/CHANGELOG.md` |
238
+ | [versioning.md](versioning.md) | `C` | the knowledge version, `content/CHANGELOG.md`, each page's own version |
234
239
  | [structure-rules.md](structure-rules.md) | `S` | heading and float title length; part page paragraphs; tables and diagrams declared so they can be cited |
235
240
  | [prose.md](prose.md) | `P` | how a lesson's sentences, terms, and equation lead-ins are worded |
241
+ | [deck.md](deck.md) | `D` | a lesson's deck: its frames, steps, notes and recalls, and what fits a frame |
236
242
  | [components.md](components.md) | — | _catalog_: inline/block features available in a lesson body — tooltips, tables, figures, diagrams, memos, abbreviations, citations |
237
243
  | [agents.md](agents.md) | — | _guide_: working on a course repo (with an AI agent) |
244
+ | [cli.md](cli.md) | — | _reference_, generated: every `content` verb, its flags, what it writes, and the shape of its `--json` ([cli-reference.json](cli-reference.json) as JSON Schema) |
238
245
  | [migrating.md](migrating.md) | — | _guide_: per-version steps when bumping the `explico` dep |
239
246
  | [retired.md](retired.md) | — | _history_: one generated row per rule no longer in force, and its successor |
240
247
 
@@ -245,7 +252,7 @@ of to the settings file.
245
252
  | ID | Element | Rule | Enforcement |
246
253
  | --- | --- | --- | --- |
247
254
  | [N2](notation.md#n2) | math | A reserved spelling is used only for its reserved meaning — settings: `reserved` = `["\\mathbb{N} — the natural numbers","\\mathbb{Z} — the integers","\\mathbb{R} — the reals","\\mathbb{C} — the complex numbers","\\mathbb{P} — probability","\\mathcal{N}(\\mu, \\sigma^2) — the normal distribution (mean, variance)","\\mathrm{d} — the differential"]` | Guide |
248
- | [N4](notation.md#n4) | math, notation | One meaning per glyph; define once; `[[key]]` before first math | **Locked** — the notation layer is this rule — an unresolved glyph has no hover card and the render path needs every glyph bound. Script — `unresolved-glyph`, `glyph-unique-in-corpus` (warning), `weak-local` (info), `gloss-wants-promoting` (info), `card-wants-demoting` (info) in [`gate-math.ts`](../../src/reference/gate-math.ts) `resolveInto`; [`consistency.ts`](../../src/reference/consistency.ts) `checkGlyphUniqueness`, `checkWeakLocal`, `checkGlossWantsPromoting`, `checkCardWantsDemoting` |
255
+ | [N4](notation.md#n4) | math, notation | One meaning per glyph; define once; `[[key]]` before first math | **Locked** — the notation layer is this rule — an unresolved glyph has no hover card and the render path needs every glyph bound. Script — `unresolved-glyph`, `glyph-unique-in-corpus` (warning), `weak-local` (info), `definition-repeated` (warning), `meaning-repeated` (info), `explain-redundant` (info), `card-wants-demoting` (info) in [`gate-math.ts`](../../src/reference/gate-math.ts) `resolveInto`; [`consistency.ts`](../../src/reference/consistency.ts) `checkGlyphUniqueness`, `checkWeakLocal`, `checkDefinitionRepeated`, `checkMeaningRepeated`, `checkCardWantsDemoting` |
249
256
  | [N5](notation.md#n5) | equation | Referenced display equations carry `\label{eq:key}` | Partial — `eq-key-unused` (info) in [`equations-validate.ts`](../../src/reference/equations-validate.ts) `validateEquations`. review: that a referenced-in-prose equation actually carries a label |
250
257
  | [N8](notation.md#n8) | math, notation | A named operator is universal (shipped/course list) or explained — settings: `allow` = `[]`, `deny` = `[]` | Script — `operator-explained` (warning) in [`consistency.ts`](../../src/reference/consistency.ts) `checkOperatorExplained` |
251
258
  | [N9](notation.md#n9) | prose, assessment, table | A quantity/unit/operator is typeset in `$…$`, never as plain text — settings: `allow` = `[]` | Script — `prose-notation`, `spelled-unit` in [`gate-prose-notation.ts`](../../src/reference/gate-prose-notation.ts) `gateProseNotation`, `gateProseNotationInAssessments` |
@@ -268,17 +275,18 @@ of to the settings file.
268
275
  | [V4](visualizations.md#v4) | plot | Randomised figures expose an adjustable, deterministic seed | Guide |
269
276
  | [V5](visualizations.md#v5) | plot | Axis ranges span the first 10 seeds at the current control values — settings: `seeds` = `10` | Guide |
270
277
  | [V6](visualizations.md#v6) | plot | Every quantitative axis shows at least three labelled values — settings: `minLabels` = `3` | Guide |
271
- | [V7](visualizations.md#v7) | plot | A figure’s picture fits on one screen — settings: `maxPercent` = `80` | Partial — `figure-too-tall` (warning) in [`figure-fit.ts`](../../src/reference/figure-fit.ts) `checkFigureFit`. review: any page or viewport the course’s Playwright suite does not open — the check runs in the browser, not in the build |
272
- | [V8](visualizations.md#v8) | diagram | A diagram fits the lesson column — settings: `columnWidth` = `707` | Script — `dia-too-wide` (warning) in [`diagram-fit.ts`](../../src/reference/diagram-fit.ts) `oversizedDiagrams` |
278
+ | [V7](visualizations.md#v7) | plot | A figure’s picture fits on one screen _(chapter only)_ — settings: `maxPercent` = `80` | Partial — `figure-too-tall` (warning) in [`figure-fit.ts`](../../src/reference/figure-fit.ts) `checkFigureFit`. review: any page or viewport the course’s Playwright suite does not open — the check runs in the browser, not in the build |
279
+ | [V8](visualizations.md#v8) | diagram | A diagram fits the lesson column _(chapter only)_ — settings: `columnWidth` = `707` | Script — `dia-too-wide` (warning) in [`diagram-fit.ts`](../../src/reference/diagram-fit.ts) `oversizedDiagrams` |
273
280
  | [C1](versioning.md#c1) | changelog | Every content change lands with a `content/CHANGELOG.md` entry | Guide |
274
281
  | [C2](versioning.md#c2) | changelog | The knowledge version is semver over learning-facing content | Guide |
275
282
  | [C3](versioning.md#c3) | changelog | `about.version` equals the newest changelog heading; a release moves both | Partial — `knowledge-version-mismatch` (warning), `knowledge-version-invalid` (warning), `knowledge-changelog-missing` (warning) in [`knowledge-version.ts`](../../src/compiler/knowledge-version.ts) `resolveKnowledgeVersion`. review: that the two edits landed in one commit — the build only ever sees the end state, so a release split across commits warns until the second half lands |
276
- | [S1](structure-rules.md#s1) | heading | No heading, short caption or short-note title over 65 characters — settings: `maxLength` = `65` | Partial — `title-length` in [`gate-heading-length.ts`](../../src/reference/gate-heading-length.ts) `gateHeadingLength`. review: a lesson’s `frontmatter.title` — the gate scans the body only |
283
+ | [C5](versioning.md#c5) | changelog | Each chapter and part page carries the release its content last changed in; `content/versions.json` is generated and committed | Partial — `page-versions-stale`, `page-versions-invalid` (warning) in [`page-versions.ts`](../../src/compiler/page-versions.ts) `syncPageVersions`, `readPageVersions`. review: which bullet of `## [Unreleased]` belongs to which page — every page changed while there is any bullet counts as not yet released; and a change to a lab, `content/domain/` or a part’s label, which changes the page’s PDF without changing its version |
284
+ | [S1](structure-rules.md#s1) | heading | No heading, short caption or short-note title over 65 characters _(chapter only)_ — settings: `maxLength` = `65` | Partial — `title-length` in [`gate-heading-length.ts`](../../src/reference/gate-heading-length.ts) `gateHeadingLength`. review: a lesson’s `frontmatter.title` — the gate scans the body only |
277
285
  | [S2](structure-rules.md#s2) | part-page | A part page carries a `<PartBridge>` (earlier parts → this one) and a `<PartScope>` paragraph — settings: `bridgeMinWords` = `30`, `bridgeMaxWords` = `150`, `scopeMinWords` = `30`, `scopeMaxWords` = `150` | Partial — `part-page-paragraph` in [`gate-part-pages.ts`](../../src/reference/gate-part-pages.ts) `gatePartPages`. review: what each paragraph says — the gate cannot tell a bridge that links the parts from one that does not |
278
286
  | [S3](structure-rules.md#s3) | table | A table is a `<Table id="…">`, never a raw Markdown/HTML table | Script — `raw-table-markdown` (warning), `raw-table-html` (warning) in [`raw-floats-validate.ts`](../../src/reference/raw-floats-validate.ts) `validateRawFloats` |
279
287
  | [S4](structure-rules.md#s4) | diagram | A `<Diagram>` carries an `id` and sits where numbering can see it | Script — `diagram-unkeyed` (warning), `diagram-nested` (warning) in [`raw-floats-validate.ts`](../../src/reference/raw-floats-validate.ts) `validateRawFloats` |
280
- | [S6](structure-rules.md#s6) | heading | Headings descend one level at a time, from `##` | Script — `heading-skip`, `heading-nested` in [`gate-heading-structure.ts`](../../src/reference/gate-heading-structure.ts) `gateHeadingStructure` |
281
- | [S7](structure-rules.md#s7) | heading | A heading is followed by body content, never by another heading | Script — `heading-adjacent` in [`gate-heading-structure.ts`](../../src/reference/gate-heading-structure.ts) `gateHeadingStructure` |
288
+ | [S6](structure-rules.md#s6) | heading | Headings descend one level at a time, from `##` _(chapter only)_ | Script — `heading-skip`, `heading-nested` in [`gate-heading-structure.ts`](../../src/reference/gate-heading-structure.ts) `gateHeadingStructure` |
289
+ | [S7](structure-rules.md#s7) | heading | A heading is followed by body content, never by another heading _(chapter only)_ | Script — `heading-adjacent` in [`gate-heading-structure.ts`](../../src/reference/gate-heading-structure.ts) `gateHeadingStructure` |
282
290
  | [S8](structure-rules.md#s8) | prose | Prefer `<Memo>` over a Starlight aside when the content needs an anchor, a caption or collapsing | Guide |
283
291
  | [S9](structure-rules.md#s9) | plot, table, diagram | A float’s short caption names it; the long caption goes on from that name | Guide |
284
292
  | [S10](structure-rules.md#s10) | part-page, plot, table, diagram, equation, notation | A page that is not a chapter explains its symbols but numbers nothing: it links a chapter’s float instead | **Locked** — a float needs a chapter number, and such a page has none — turning the rule off would not render one. Script — `non-chapter-float`, `non-chapter-local-notation`, `non-chapter-plain-text` in [`gate-non-chapter-pages.ts`](../../src/reference/gate-non-chapter-pages.ts) `gateNonChapterPages` |
@@ -291,6 +299,17 @@ of to the settings file.
291
299
  | [P7](prose.md#p7) | prose | One claim per sentence; every logical link stated | Guide |
292
300
  | [P8](prose.md#p8) | prose, math | Words for a relation match the math exactly ("proportional to") | Guide |
293
301
  | [P9](prose.md#p9) | prose, equation | A display equation's lead-in says what it gives and where it comes from | Guide |
302
+ | [D1](deck.md#d1) | slide, heading | A deck is sections of frames: `##` a section, `###` a frame _(deck only)_ | **Locked** — a frame is the unit the deck page, Present mode and the PDF are built from — content outside one has no frame to show in. Script — `frame-depth`, `deck-loose-content`, `deck-empty` in [`decks-validate.ts`](../../src/reference/decks-validate.ts) `validateDecks` |
303
+ | [D2](deck.md#d2) | slide | A frame fits its canvas in every step _(deck only)_ — settings: `maxWords` = `70`, `maxBullets` = `7` | Partial — `frame-dense` (warning), `frame-overflow` in [`decks-validate.ts`](../../src/reference/decks-validate.ts) `validateDecks`; [`deck-fit.ts`](../../src/compiler/exports/deck-fit.ts) `checkFrameFit`. review: the paper: a frame that fits its canvas on the web can still run over in the PDF, which the export reports as a warning naming the frame |
304
+ | [D3](deck.md#d3) | slide, prose | A frame makes one claim, and its title states it _(deck only)_ | Guide |
305
+ | [D4](deck.md#d4) | slide, prose | A frame with text carries speaker notes _(deck only)_ | Script — `frame-notes-missing` (warning) in [`decks-validate.ts`](../../src/reference/decks-validate.ts) `validateDecks` |
306
+ | [D5](deck.md#d5) | slide, heading | A frame title fits one line _(deck only)_ — settings: `maxLength` = `50` | Script — `frame-title-length` in [`decks-validate.ts`](../../src/reference/decks-validate.ts) `validateDecks` |
307
+ | [D6](deck.md#d6) | slide | A frame shows in at most `maxSteps` steps, each adding to the last _(deck only)_ — settings: `maxSteps` = `4` | Partial — `frame-steps` (warning) in [`decks-validate.ts`](../../src/reference/decks-validate.ts) `validateDecks`. review: that a later step contradicts nothing already shown |
308
+ | [D7](deck.md#d7) | slide, plot, table, diagram | A frame holds one figure, table or diagram _(deck only)_ | Script — `frame-floats` (warning) in [`decks-validate.ts`](../../src/reference/decks-validate.ts) `validateDecks` |
309
+ | [D8](deck.md#d8) | slide, equation | Recall a chapter’s result; never retype it _(deck only)_ | Script — `frame-retyped` (warning) in [`decks-validate.ts`](../../src/reference/decks-validate.ts) `validateDecks` |
310
+ | [D9](deck.md#d9) | slide, math, notation | A frame introduces at most `maxNewSymbols` symbols _(deck only)_ — settings: `maxNewSymbols` = `4` | Script — `frame-new-symbols` (info) in [`decks-validate.ts`](../../src/reference/decks-validate.ts) `validateDecks` |
311
+ | [D10](deck.md#d10) | slide | A deck opens on its title and closes on what the student can now do _(deck only)_ | Guide |
312
+ | [D11](deck.md#d11) | slide, changelog | A deck is flagged when its chapter changed after it did _(deck only)_ | Script — `deck-stale` (info) in [`page-versions.ts`](../../src/compiler/page-versions.ts) `deckStaleDiagnostics` |
294
313
 
295
314
  _Element_ is what the rule is about; the first tag is primary. _Enforcement_ is
296
315
  derived from the catalog: **Script** — every violation a check can see is
@@ -299,7 +318,9 @@ reported; **Partial** — checks exist and the rule says what they miss;
299
318
  enforcement; **Locked** — the engine cannot run without it, so it has no
300
319
  checkbox in the course settings. A `(warning)` / `(info)` code does not fail
301
320
  the build. A rule with _settings_ takes them from `rules.<ID>` in the course
302
- settings; the value shown is the default.
321
+ settings; the value shown is the default. A rule marked _chapter only_ or _deck
322
+ only_ is about one edition of a lesson ([deck.md](deck.md)); every other rule
323
+ holds for both.
303
324
 
304
325
  A rule that has been retired is not listed here at all — see
305
326
  [retired.md](retired.md).
@@ -320,5 +341,6 @@ next to the syntax it belongs to.
320
341
  | [`content-schema`](structure.md#content-schemas) | notation, assessment, source, changelog | Every content file satisfies its collection’s schema. | `content-schema` in [`content-schema.ts`](../../src/compiler/content-schema.ts) `validateContentSchemas` |
321
342
  | [`retired-keys`](structure.md#retired-keys) | changelog | No content file sets a key the engine has retired or cannot honour. | `retired-key`, `unsupported-key`, `ignored-key` (warning) in [`gate-retired-keys.ts`](../../src/reference/gate-retired-keys.ts) `findRetiredKeys`, `findUnsupportedKeys` |
322
343
  | [`prop-text`](components.md#props-are-plain-text) | math, plot, diagram, table | A component’s quoted prop holds plain text, and prose holds no `{…}` expression. | `markup-in-prop`, `mdx-expression` in [`gate-prop-text.ts`](../../src/reference/gate-prop-text.ts) `findMarkupInProps`; [`gate-mdx-expression.ts`](../../src/reference/gate-mdx-expression.ts) `gateMdxExpressions` |
344
+ | [`decks`](deck.md#the-deck-file) | slide | A deck belongs to a lesson or a part, and recalls what its chapters number. _(deck only)_ | `deck-frontmatter`, `deck-outside-part`, `recall-unresolved`, `pause-invalid`, `deck-chapter-unknown`, `deck-chapter-repeated`, `deck-chapter-outside-part`, `deck-marker-misplaced`, `deck-check-no-chapter`, `deck-route-taken` in [`decks-validate.ts`](../../src/reference/decks-validate.ts) `validateDecks` |
323
345
 
324
346
  <!-- RULES-INDEX:END -->
@@ -5,6 +5,13 @@ Guidance for an AI agent — or a person — editing a course repo built on
5
5
  a repo-specific `AGENTS.md` at your course root for anything below that is
6
6
  particular to your layout.
7
7
 
8
+ **Examples to start from.** Two working courses — the playground, which every
9
+ `playground/…` path in these docs refers to, and credit-and-derivatives, a full
10
+ course — are downloadable at their newest version from https://danieltuzes.github.io/explico/start/
11
+ (the playground at https://danieltuzes.github.io/explico/downloads/explico-playground-latest.zip;
12
+ [`sources.json`](https://danieltuzes.github.io/explico/downloads/sources.json) lists both, machine-readably). Download the
13
+ playground to see how a feature is wired before writing your own.
14
+
8
15
  This is a guide, not a rule area. The enforceable rules are in
9
16
  [`notation.md`](notation.md) (`N`), [`visualizations.md`](visualizations.md)
10
17
  (`V`), [`versioning.md`](versioning.md) (`C`),
@@ -14,7 +21,10 @@ and a rule disagree, the engine wins — see [`README.md`](README.md).
14
21
 
15
22
  ## The loop
16
23
 
17
- - **Read the rules in effect first.** `pnpm --filter <course> content rules`
24
+ - **Read the rules in effect first, one rule at a time after that.**
25
+ `pnpm content rule <ID>` prints one rule whole: the entry as this course
26
+ has it and the page's own section with its example, about half a thousand
27
+ tokens (N4, the largest, about three thousand). `pnpm --filter <course> content rules`
18
28
  (or `build/rules-in-effect.md` after a `validate:content`) lists every rule
19
29
  this course has *on*, grouped by the element it is about — prose, math,
20
30
  equation, notation, heading, part-page, plot, diagram, table, assessment,
@@ -24,6 +34,9 @@ and a rule disagree, the engine wins — see [`README.md`](README.md).
24
34
  checks and is therefore on you. Open a rule's page only when it is listed
25
35
  there and you need the example. A rule that is not on the page is off for
26
36
  this course (or does not exist): do not apply it, do not flag it in review.
37
+ For a deck, `content rules --edition deck` (or
38
+ `build/rules-in-effect-deck.md`) is the page: the `D` rules for frames, then
39
+ every rule a deck shares with a chapter ([deck.md](deck.md)).
27
40
  A rule marked `waivable` there can be broken at one place with
28
41
  `{/* explico-allow <ID>: <reason> */}` above the block — that is where the
29
42
  reason for breaking a rule goes ([README.md § Waiving](README.md#waiving-a-rule-at-one-place)).
@@ -32,21 +45,59 @@ and a rule disagree, the engine wins — see [`README.md`](README.md).
32
45
  and it is the one kind you may be asked to *add*
33
46
  ([README.md § Rules of your own](README.md#rules-of-your-own)) — never by
34
47
  editing this folder.
35
- - **Start from the CLI, not a file hunt.** `pnpm --filter <course> content
36
- context <lesson>` prints a lesson plus exactly its notation, references, and
37
- assumptions. `pnpm content status` is the overview: counts, diagnostic
38
- totals and orphans. `pnpm content diagnostics info` lists everything;
39
- `warning` and `error` raise the minimum severity. `pnpm content warnings`
40
- lists warnings only.
41
- `pnpm content check <lesson> | --changed` is schema + refs + MDX→KaTeX, exits
42
- non-zero on an error. Read those before opening files.
43
- - **Validate, twice.** `pnpm --filter <course> validate:content` regenerates
44
- `build/resolution/*` and then fails on the same run that changed them. Run it
45
- again; the second run is clean. `build/` is git-ignored — there is nothing
46
- to commit from it.
47
- - **Before handing work back:** `validate:content` (×2) → `astro check` →
48
- `astro build`. `astro build` is the only step that runs every gate the CI
49
- does.
48
+ - **Start from the CLI, not a file hunt.** Run it from the course package;
49
+ `pnpm content help` lists every verb, `pnpm content help <verb>` prints one
50
+ verb's flags and the shape of its `--json` ([`cli.md`](cli.md) has them all),
51
+ and every verb takes `--json` for the whole report. Read what it prints
52
+ before opening files. The table under "The CLI" says which verb answers which
53
+ question.
54
+ - **Validate once.** `pnpm --filter <course> validate:content` rewrites
55
+ `build/resolution/*` and fails only on a report git tracks that it had to
56
+ rewrite. A course whose `build/` is git-ignored passes on the first run; one
57
+ that commits `build/resolution/` commits the rewritten reports with the
58
+ change and runs it again (after an engine upgrade every report is
59
+ rewritten).
60
+ - **Before handing work back:** `pnpm content verify` runs `validate:content`
61
+ → `content check --changed` → `astro check` → the course's tests →
62
+ `astro build` (`--e2e` adds the browser tests), one line per step and the
63
+ tail of a failed step's log; a rerun on an unchanged tree answers from its
64
+ last pass. `astro build` is the only step that runs every gate the CI does.
65
+
66
+ ## The CLI
67
+
68
+ The `content` CLI ships in the engine (`explico/compiler/cli-main`); a course's
69
+ `scripts/content.ts` is a three-line shim over it (migration step 14 of the
70
+ release that introduced it). Read-only except `new` and `init-agent`, and
71
+ `move`, `rename` and `fix`, which print a diff and change nothing without
72
+ `--write`.
73
+
74
+ | You want to know or do | Verb | It prints |
75
+ | --- | --- | --- |
76
+ | how the course stands | `status` | counts, diagnostic totals, the agent surfaces' sizes, whether this file's procedures are installed |
77
+ | what is wrong, where | `diagnostics [info\|warning\|error]` | counts by code (with each code's rule) and by file; `--all`, `--code <c>`, `--lesson <id>`, `--changed` list the lines |
78
+ | what a lesson draws on | `context <lesson>` | its outcomes, notation, sources, diagnostics |
79
+ | which rules apply | `rules [--element …] [--enforcement guide]` | the rules this course has on, by element |
80
+ | one rule, whole | `rule <ID>` | the entry as this course has it and the page's section, example included |
81
+ | is this symbol defined already | `symbol <latex \| key>` | the shared card, each local entry with its lesson, each gloss with its card, near misses |
82
+ | will this expression build | `equation "<latex>" [--lesson <id>]` | each glyph's card, unresolved glyphs with where they are defined; exit 1 if the build would refuse it |
83
+ | what a lesson's math binds | `bindings <lesson>` | every span: bindings, unresolved glyphs, redundant `\explain` wraps |
84
+ | does it render | `check <lesson \| deck>… \| --changed` | schema, references, MDX → KaTeX; exit 1 on an error |
85
+ | what an upgrade needs | `migrate [--from <v>] [--to <v>]` | the migration guide's sections between two versions, oldest first |
86
+ | a local entry becomes shared | `move <lesson>:<key> [--to <key>] [--write]` | the new card and every copy and reference that changes, as a diff |
87
+ | rename a shared card | `rename <old> <new> [--write]` | the file and every reference, as a diff |
88
+ | the mechanical repairs | `fix <lesson> [--write]` | redundant `\explain` wraps unwrapped, as a diff |
89
+ | a draft to start from | `new lesson <id>`, `new deck <lesson \| part>`, `new term <key>` | the files it created; never overwrites |
90
+ | install these procedures | `init-agent [--write]` | the skills and the `rules-reviewer` agent copied into `.claude/`, a baseline `AGENTS.md` when the course has none |
91
+ | is it ready to hand back | `verify [--e2e] [--force]` | one line per step — validate, check, `astro check`, tests, `astro build` — and a failed step's log tail; exit 1 if one failed |
92
+ | a verb's flags and `--json` | `help [<verb>]` | the usage; with a verb, its flags, what it writes, its exit code and its `--json` type |
93
+
94
+ The procedures — drafting a lesson, upgrading the engine, releasing the
95
+ course's content, reviewing a lesson against the guide-only rules — ship in
96
+ `node_modules/explico/docs/course-agent/`. `pnpm content init-agent --write`
97
+ installs them as project skills and a sub-agent; rerun it after an upgrade.
98
+ A course's own `AGENTS.md` should open by pointing here and then hold only
99
+ what is particular to the course; the shipped `docs/course-agent/AGENTS.md` is
100
+ that baseline, and `content status` says when the course file does not.
50
101
 
51
102
  ## Reading and running
52
103
 
@@ -150,8 +201,11 @@ then read the target package's
150
201
  `node_modules/explico/docs/authoring/migrating.md`; the migration instructions
151
202
  ship with the code they describe.
152
203
 
153
- Apply every version section after the old version through the installed version,
154
- oldest first. Follow required and conditional changes across `content/`,
204
+ `pnpm content migrate` prints exactly the sections to apply, from the version
205
+ `package.json` pinned at HEAD to the one now installed, oldest first (`--from`
206
+ and `--to` override either end), and refuses when a section is missing; it is
207
+ the one or two sections an upgrade needs, not the whole forty-thousand-token
208
+ guide. Apply every section it prints. Follow required and conditional changes across `content/`,
155
209
  `content/course.config.ts`, Astro/source wiring, course scripts, dependencies,
156
210
  and generated files. Do not use `CHANGELOG.md` alone as the procedure and do not
157
211
  assume that a release without a **Breaking** heading needs no work; each migration