@lernapps/tooling 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/check/rules/competencies-on-map.mjs +85 -0
  2. package/dist/check/rules/one-competency-per-page.mjs +18 -0
  3. package/dist/check/rules/prerequisites-linked.mjs +38 -0
  4. package/dist/cli.mjs +191 -52
  5. package/dist/explainer-map-DW4XCcY0.mjs +124 -0
  6. package/dist/skills/lernapps-app/SKILL.md +2 -1
  7. package/dist/skills/lernapps-cli/SKILL.md +13 -4
  8. package/dist/skills/lernapps-content-questions/SKILL.md +2 -0
  9. package/dist/skills/lernapps-content-questions/references/question-types.md +2 -1
  10. package/dist/skills/lernapps-content-research/SKILL.md +41 -5
  11. package/dist/skills/lernapps-exercise-patterns/references/mathematics.md +25 -1
  12. package/dist/skills/lernapps-exercise-patterns/references/physics.md +1 -0
  13. package/dist/skills/lernapps-explainer/SKILL.md +10 -4
  14. package/dist/skills/lernapps-explainer/references/blocks.md +7 -3
  15. package/dist/skills/lernapps-explainer/references/exercises.md +6 -0
  16. package/dist/skills/lernapps-explainer/references/rules.md +29 -0
  17. package/dist/skills/lernapps-explainer/references/structure.md +62 -0
  18. package/dist/skills/lernapps-explainer-design/SKILL.md +46 -2
  19. package/dist/skills/lernapps-explainer-design/references/rules.md +1 -0
  20. package/dist/skills/lernapps-explainer-self-assessment/SKILL.md +1 -0
  21. package/dist/skills/lernapps-listing/SKILL.md +9 -0
  22. package/generator/create.ts +39 -10
  23. package/generator/init.ts +89 -19
  24. package/generator/update.ts +53 -8
  25. package/guidance/increment-template.md +13 -5
  26. package/guidance/plan-template.md +25 -11
  27. package/guidance/workflow-rules.md +3 -1
  28. package/package.json +2 -2
  29. package/skills/archetypes/lernapps-explainer/SKILL.md +10 -4
  30. package/skills/archetypes/lernapps-explainer/references/blocks.md +7 -3
  31. package/skills/archetypes/lernapps-explainer/references/exercises.md +6 -0
  32. package/skills/archetypes/lernapps-explainer/references/rules.md +29 -0
  33. package/skills/archetypes/lernapps-explainer/references/structure.md +62 -0
  34. package/skills/archetypes/lernapps-explainer-design/SKILL.md +46 -2
  35. package/skills/archetypes/lernapps-explainer-design/references/rules.md +1 -0
  36. package/skills/archetypes/lernapps-explainer-self-assessment/SKILL.md +1 -0
  37. package/skills/content/lernapps-content-questions/SKILL.md +2 -0
  38. package/skills/content/lernapps-content-questions/references/question-types.md +2 -1
  39. package/skills/content/lernapps-content-research/SKILL.md +41 -5
  40. package/skills/content/lernapps-exercise-patterns/references/mathematics.md +25 -1
  41. package/skills/content/lernapps-exercise-patterns/references/physics.md +1 -0
  42. package/skills/process/lernapps-app/SKILL.md +2 -1
  43. package/skills/process/lernapps-listing/SKILL.md +9 -0
  44. package/skills/tooling/lernapps-cli/SKILL.md +13 -4
@@ -15,11 +15,18 @@ license: MIT
15
15
 
16
16
  1. Only after the creator's yes to the plan (`explore-before-code`). In the plan's folder:
17
17
  `npx --no -- lernapps create --archetype <archetype>`.
18
- 2. No git repo yet: `git init --initial-branch=work`. Then `npm install` (dependencies, git hooks), and commit
19
- everything on `work`.
18
+ 2. `create` installs nothing and commits nothing; its last lines say what follows, in this order: no git repo yet
19
+ (`lernapps init` makes one): `git init --initial-branch=work`; then `npm install` (runtime, git hooks); then commit
20
+ everything on `work` (`git add -A`, `git commit`; the hooks check it).
20
21
  3. Needs git, Node ≥ 22.18, registry.npmjs.org. Blocked → ask the creator to allow it, run again. Never write the
21
22
  app by hand.
22
23
  4. It keeps the plan and the installed skills, and writes the content guide's skeleton.
24
+ 5. Maintainers only, to try a tooling that is not on the registry (a local build has version `0.0.0`): give it to
25
+ both, `lernapps init <short name> --tooling <spec>` and `create --tooling <spec>`, e.g.
26
+ `file:/path/to/lernapps-tooling-0.0.0.tgz` (from `npm pack`) or `github:lernapps/tooling#<commit>`. Run from a
27
+ checkout of the tooling, `init` packs it into a temporary folder and installs that tarball by itself; `create`
28
+ takes the same from `package.json`. A sandbox without a server for the map: `LERNAPPS_TEST_MAP_URL=<folder>` (or a
29
+ `file:` URL) makes `lernapps check` read `<folder>/<subject>.json`.
23
30
 
24
31
  ## Update
25
32
 
@@ -38,8 +45,10 @@ license: MIT
38
45
  A check says the rules hold; only a look says what a learner sees. Show the creator the pages before a push, and before
39
46
  a submission they declare something about.
40
47
 
41
- - Explainer or quiz: build (`npx vp build`), then `npx --no -- lernapps screenshots`. It writes one image of every page, one of
42
- every figure, and an `index.html` that shows them in order to `screenshots/` (not part of the app: do not commit it).
48
+ - Explainer or quiz: build (`npx vp build` writes `dist/`; `npm run build` writes `_site/`), then
49
+ `npx --no -- lernapps screenshots`: it takes the newer of `dist/` and `_site/`, or the folder you name. It writes
50
+ one image of every page, one of every figure, and an `index.html` that shows them in order to `screenshots/` (not
51
+ part of the app: do not commit it).
43
52
  Show the images, or the `index.html` where you can show HTML. Check every figure in its own image
44
53
  (`…-figure-<n>.png`): a page image is scaled down, its details are not readable. `--desktop` adds 1280 px images.
45
54
  Options: `npx --no -- lernapps screenshots --help`. The folder ignores itself in git, so a later check never sees it.
@@ -81,6 +81,8 @@ severity: warning
81
81
 
82
82
  - At least one link per question, best the German Wikipedia (`https://de.wikipedia.org/wiki/...`).
83
83
  - `title` names the page ("Uluru (Wikipedia)").
84
+ - No page of its own (a term like "Wortgleichung"): the page that fits best, preferably the one the question's facts
85
+ came from. Never no link (the schema needs one).
84
86
  - HTTP 429 → tell the creator which; mark the facts "not checked" in the plan; go on.
85
87
 
86
88
  ## `quiz-own-words`: Write in your own words
@@ -9,7 +9,8 @@
9
9
  | `ordering` | `items` in the right order; the page mixes them |
10
10
  | `matching` | `pairs` as they belong, each pair an object `{ left, right }`; the page mixes the right side. `distractors` (1 to 4): wrong right entries that match no left one. `pairsFrom: material` instead of `pairs` takes them from the `labelled-figure` in `material` (marker left, text right; the figure needs `legend: false`) |
11
11
 
12
- - Every question: `id`, `text`, `explanation`, at least one link in `links`. Optional quiz `sources`.
12
+ - Every question: `id`, `text`, `explanation`, at least one link in `links` (no fitting page: `quiz-further-reading`).
13
+ Optional quiz `sources`.
13
14
  - Types without options carry their background in `explanation`.
14
15
  - Optional `material`: what the question shows between its text and its answers, a list of blocks: `text` (restricted
15
16
  Markdown), `formula`, `figure`, `labelled-figure`, `table`, `quote`, `code`. A `figure` is `image: images/<name>.svg`
@@ -20,22 +20,58 @@ license: MIT
20
20
  - Cite it under "Notes": title, issuer, year, address; page and unit (Inhaltsfeld, Lernbereich, Kompetenz).
21
21
  - The creator's material differs → follow the material; say so in one sentence.
22
22
 
23
- ## 2. Competencies
23
+ ## 2. The learning map
24
24
 
25
- - One per line: "Ich kann …" with one observable verb (berechnen, zuordnen, erklären, unterscheiden, ablesen,
25
+ Before you write competencies, look at the learning map of the subject. A strand is recommended, not required.
26
+
27
+ - Open `https://lernapps.net/apps/map/<subject>.json` (for you); people see it at `https://lernapps.net/apps/map/<subject>/`.
28
+ `<subject>` is the file name in the catalog, e.g. `mathematik`. Read it from the file or the page, in full.
29
+ - A strand fits the app → recommend it to the creator: one app, one strand. A strand runs over several school years;
30
+ a topic that spans two strands is two apps. Show the strand and its competencies (Bausteine), with the ones still
31
+ no app practises.
32
+ - The creator takes the strand → the app practises only competencies of that strand. Write each with its id from the
33
+ map. The app's "Ich kann …" competencies are those of the map, in its words: do not reword or invent them. The
34
+ map's `requires` shows what learners need first; a prerequisite the app does not practise, of any strand, gets a
35
+ recap page or a link to the map (skill `lernapps-explainer-design`).
36
+ - The map lacks a competency the creator wants → do not invent it in the app. Propose it to the catalog: an issue or a
37
+ pull request in `lernapps/apps` (`map/<subject>.yaml`, the strand's list). Say so to the creator; go on with the
38
+ ones the map has, or wait for the answer.
39
+ - No strand fits, the creator wants none, or the subject has no map (the address answers "not found") →
40
+ `strand: none`. The app is listed as usual, only not on the map; write its competencies as in the next section.
41
+ - Write the strand (`<subject>/<strand>`, or `none`) and the competency ids under "Notes".
42
+ - Tell the creator now how the content will be structured: a competency is one teachable thing (one or two lessons);
43
+ in an explainer as a rule one page per competency, chapters of competencies that build on each other (skill
44
+ `lernapps-explainer-design`).
45
+
46
+ ### `strand-from-map`: Recommend a strand of the learning map
47
+
48
+ ```rule
49
+ id: strand-from-map
50
+ scope: tooling
51
+ severity: hint
52
+ ```
53
+
54
+ - The subject has a learning map → check its strands and recommend the one that fits the app.
55
+ - `strand: none` only when no strand fits or the creator wants none; say why under "Notes".
56
+ - With a strand: only that strand's competencies, by their ids; a missing one is proposed to `lernapps/apps`.
57
+
58
+ ## 3. Competencies
59
+
60
+ - With a strand: those of the map, see above. Without: one per line: "Ich kann …" with one observable verb (berechnen, zuordnen, erklären, unterscheiden, ablesen,
26
61
  zeichnen). Never "verstehen" or "kennen".
27
62
  - Yes: "Ich kann die Stromstärke in einem Reihenstromkreis berechnen."
28
63
  - No: "Ich verstehe den elektrischen Strom."
29
- - Group them into chapters, in the order of the Lehrplan or the material. Many chapters are still one app.
64
+ - Group them into chapters of competencies that build on each other, in the order of the Lehrplan, the material
65
+ or the map's `requires`. Many chapters are still one app.
30
66
  - Write them under "Notes". Show them at the checkpoint "research".
31
67
 
32
- ## 3. Second sources
68
+ ## 4. Second sources
33
69
 
34
70
  - Per competency one article or video (serlo.org, a channel the creator trusts, Wikipedia).
35
71
  - Read [references/sources.md](references/sources.md) before you choose, cite or rely on a source.
36
72
  - A text source (Wikipedia, a schoolbook site): take facts, never sentences. Copying a sentence needs the licence the page states; if you cannot read it, do not copy.
37
73
 
38
- ## 4. The creator's material and privacy
74
+ ## 5. The creator's material and privacy
39
75
 
40
76
  - The creator's material stays local: never commit or quote it. Only the competencies derived from it go into the
41
77
  repo.
@@ -10,10 +10,34 @@
10
10
  | Idea | Learners see and do | Expressed with |
11
11
  |---|---|---|
12
12
  | Part of a whole (percent, fractions) | a 10×10 grid with the part filled; compute the part, the whole or the rate | `figure` `hundred-grid` on the page; `generated` with `compare: { number: { format: percent } }`; a figure that changes with the task: an exercise component |
13
- | Before and after a change | two bars, old and new value; compute the change or the new value | `figure` `bar` (`part`, `whole`) on the page; `generated` with the typical mistake "added the rate instead of the amount" |
13
+ | Before and after a change | old and new value; compute the change or the new value | a decrease: `figure` `bar` (`part` the new value, `whole` the old; it draws only a part of a whole); an increase: a `table` or an `image` of two bars; `generated` with the typical mistake "added the rate instead of the amount" |
14
14
  | Product of brackets (binomial formulas) | a rectangle split into a², ab, ab, b², lengths from the task | `image` (SVG) for the rule; `generated` for the task; a component only for lengths that follow the task |
15
15
  | Difference of squares | a² − b² as an L-shape, rearranged into (a + b)(a − b) | two `image`s side by side with captions; `ordering` of the steps |
16
16
  | Outcome set (Laplace) | die faces, wheel sectors or balls, the favourable ones marked | `table` or `image`; `generated` drawing from a data file of experiments, answer as a fraction |
17
17
  | Multi-stage experiment | a tree from top to bottom with branch probabilities; multiply along a path | `image` (SVG) of the tree; `generated` with the path rule; a clickable tree as a component |
18
18
  | Drawing without replacement | an urn before and after one ball is taken | two `image`s; `generated` with `pick: 2, distinct: true` from a data file of balls |
19
19
  | Unit conversion | a value with one unit; give it in another | `generated` with `compare: { number: { unit } }` and the mistake "factor in the wrong direction" |
20
+ | Sides and angles in a right triangle (trigonometry) | a right triangle, the angle α marked, its sides named from α (Gegenkathete, Ankathete, Hypotenuse); compute a side from an angle and a side, or an angle from two sides | `image` (SVG) of the triangle with the right angle and α marked, the sides labelled; `formula` with `\sin`, `\cos`, `\tan`, `\arcsin`, Greek letters and `^\circ`; `generated` with `sin`/`cos`/`tan`, `asin`/`acos`/`atan` (degrees) and `sqrt`, answer rounded (`round`, `decimals`, `tolerance`); mistakes: opposite and adjacent side swapped (`cos` instead of `sin`), calculator in radians (the value of `sin` of α taken as radians), sin⁻¹ confused with 1/sin; a triangle that changes with the task: an exercise component |
21
+
22
+ ## Example: a side in a right triangle
23
+
24
+ ```yaml
25
+ - type: generated
26
+ id: gegenkathete-berechnen
27
+ given:
28
+ c: { int: [5, 20] }
29
+ alpha: { int: [20, 70], step: 5 }
30
+ task:
31
+ text: "In einem rechtwinkligen Dreieck ist die Hypotenuse c = {{c}} cm lang, der Winkel α = {{alpha}}°. Wie lang ist die Gegenkathete a von α? Runde auf eine Stelle nach dem Komma."
32
+ answer:
33
+ value: "round(c * sin(alpha), 1)"
34
+ compare: { number: { format: decimal, decimals: 1, tolerance: 0.1, unit: cm } }
35
+ mistakes:
36
+ - { value: "round(c * cos(alpha), 1)", message: "Das ist die Ankathete: die Gegenkathete liegt α gegenüber, also Sinus." }
37
+ - { value: "round(c * sin(alpha * 57.29578), 1)", message: "Dein Taschenrechner rechnet im Bogenmaß (RAD): stelle ihn auf Grad (DEG)." }
38
+ - { value: "round(c / sin(alpha), 1)", message: "sin α = a : c, also a = c · sin α: mal, nicht geteilt." }
39
+ ```
40
+
41
+ An angle from two sides: `answer: { value: "round(asin(a / c), 1)" }`, in degrees. Mistakes: opposite and adjacent
42
+ swapped (`round(acos(a / c), 1)`, the other angle); sin⁻¹ taken as 1 : sin (`round(c / a, 1)`: "sin⁻¹ ist die
43
+ Umkehrfunktion, nicht 1 : sin"). A mistake value must have a value: `asin(c / a)` with c > a has none.
@@ -16,3 +16,4 @@
16
16
  | Lever and moment | a beam with weights at distances; does it balance | `image` of the beam; `generated` for F₁ · a₁ = F₂ · a₂; a component with draggable weights |
17
17
  | Optics | a ray diagram of a convex lens; where the image forms | `labelled-figure` of the rays; `true-false` on image properties; a component with a draggable object |
18
18
  | Density | blocks of equal mass and different volumes; float or sink | `table` from a data file of materials; `generated` with `compare: { choice: [schwimmt, sinkt] }` |
19
+ | Energy and efficiency | forms of energy before and after; how much is useful | `figure` `bar` only for a part of a whole (useful energy of the energy put in, the efficiency); energies side by side or a transfer chain → `table` or an `image` (SVG); `generated` with the values from a data file (`from: { data: … }`, records with numbers never inline) |
@@ -9,9 +9,10 @@ license: MIT
9
9
  - You write YAML in `src/`; a Vue component only where the plan names one. Rendering, navigation, formulas, figures,
10
10
  exercises, accessibility, languages and the no-JS page come from the runtime `@lernapps/app-templates`.
11
11
  - `create` writes a placeholder: one chapter with one page and one exercise. Replace it with the planned content. The complete example, the chapter "Prozent" with a component, is in `node_modules/@lernapps/app-templates/explainer/`: look things up there, never copy it.
12
- - Schema: `node_modules/@lernapps/app-templates/runtime/explainer/explainer.v1.schema.json`. Start every YAML file with
13
- `# yaml-language-server: $schema=../../node_modules/@lernapps/app-templates/runtime/explainer/explainer.v1.schema.json`
14
- (one `../` less in `src/app.yaml`).
12
+ - Schema: `node_modules/@lernapps/app-templates/runtime/explainer/explainer.v2.schema.json` (format 2: `format: 2`
13
+ in `src/app.yaml`, as `create` writes it). Start every YAML file with
14
+ `# yaml-language-server: $schema=../../node_modules/@lernapps/app-templates/runtime/explainer/explainer.v2.schema.json`
15
+ (one `../` less in `src/app.yaml`). Only an app that has not moved to format 2 keeps `explainer.v1.schema.json`.
15
16
  - Questions of the quiz's types: also skill `lernapps-content-questions`.
16
17
 
17
18
  ## Read when
@@ -22,7 +23,8 @@ license: MIT
22
23
  - [references/components.md](references/components.md): only when the plan names a component.
23
24
  - [references/runtime.md](references/runtime.md): when the build or a runtime test fails, and before you set up the
24
25
  tutor.
25
- - [references/rules.md](references/rules.md): when a check names an `explainer-*` or `vue-component-*` rule.
26
+ - [references/rules.md](references/rules.md): when a check names an `explainer-*`, `vue-component-*` or
27
+ `competencies-on-map` rule, and before you write a page with `competencies`.
26
28
 
27
29
  ## Gotchas
28
30
 
@@ -30,6 +32,10 @@ license: MIT
30
32
  - Headings are `section` blocks, never Markdown headings.
31
33
  - YAML: quote a text that has `: ` in it or looks like a number (`"8."`, `"01000001"`, `"0"`). Unquoted, YAML reads a map or a
32
34
  number: the build stops, or the digits change. A table cell, a header cell too, is a text and never empty.
35
+ - A page with `competencies` teaches what their "Ich kann …" in the map says, not clearly more or other
36
+ (`content-matches-competencies`).
37
+ - A page with formulas written through the shell: a heredoc with an unquoted delimiter replaces `$…` and `\`
38
+ (`<<EOF`). Quote the delimiter (`<<'EOF'`), or write the file with the file tool.
33
39
  - A new chapter: a folder with `chapter.yaml`, listed in `src/app.yaml`, each page listed in `chapter.yaml`.
34
40
  - `selfAssessment` in a `chapter.yaml`: written in Commit, skill
35
41
  `lernapps-explainer-self-assessment`. An exercise it names renamed or moved → change the reference too.
@@ -10,7 +10,7 @@
10
10
  | `section` | `id`, `title`, `blocks` | heading and blocks |
11
11
  | `text` | `text` | prose |
12
12
  | `formula` | `formula` (LaTeX subset, no `$`) | formula on its own line, MathML |
13
- | `figure` | `draw` (`hundred-grid` with `filled`, `bar` with `part`, `whole`) or `image` (`images/<name>.svg`, `source`, `licence`); `alt`, `caption` | inline SVG or bundled image |
13
+ | `figure` | `draw` (`hundred-grid` with `filled`, `bar` with `part`, `whole`) or `image` (`images/<name>.svg`, `source`, `licence`); `alt`, `caption` | inline SVG or bundled image; `bar` is one strip, the whole, with the part shaded and labelled with its percentage: a part of a whole only, never two quantities side by side or a value above the whole (an `image` or a `table` instead) |
14
14
  | `labelled-figure` | `image`, `alt`, `caption`, `labels` (`x`, `y` in %, `text`, optional `marker` of 1 to 3 characters, e.g. the material's `A` to `E`, else the number), `legend` (default `true`; `false` shows only the markers, then `alt` names every marker), `source`, `licence` | image with markers (letters or numbers) and a legend of the texts |
15
15
  | `table` | `caption`, `header`, `rowHeaders`, `rows` | table with caption |
16
16
  | `timeline` | `events` (`date`, `title`, `label`, `text`) or `data` | dated events, earliest first |
@@ -62,6 +62,10 @@
62
62
  - LaTeX subset → MathML; `formula` block or inline `$…$`.
63
63
  - Commands: `\frac`, `\cdot`, `\times`, `\div`, `\%`, `\text`, `^`, `_`, `\sqrt`, `\approx`, `\pm`, `\le`, `\ge`,
64
64
  `\neq`, `\,`, parentheses, braces (`\{`, `\}`).
65
+ - Trigonometry and more: `\sin`, `\cos`, `\tan`, `\arcsin` (or `\sin^{-1}`), `\arccos`, `\arctan`, Greek letters
66
+ (`\alpha`, `\varphi`, `\pi`, `\Delta`, …), degrees (`30^\circ`), spaces (`\ `, `\quad`, `\qquad`), `\Rightarrow`,
67
+ `\Leftrightarrow`, `\left(` … `\right)`:
68
+ `'\sin \alpha = \frac{a}{c} \quad \Rightarrow \quad a = c \cdot \sin \alpha'`.
65
69
  - Chemistry (mhchem): `\ce{2H2 + O2 -> 2H2O}`, `\ce{CO2 + H2O <=> H2CO3}`, units `\pu{123 kJ/mol}`.
66
70
  - Other command or parse error → build fails with the place.
67
71
  - YAML formulas in single quotes. Same in every language unless `\text{…}` differs (then a map).
@@ -80,6 +84,6 @@
80
84
 
81
85
  ## Data files
82
86
 
83
- - `src/data/<name>.yaml`: list of records, same lowercase fields; values number, boolean, text (map if several
84
- languages).
87
+ - `src/data/<name>.yaml`: list of records, same fields; a field name starts with a lower-case letter, then letters
88
+ and digits (`masse`, `hoeheM`; not `Masse`, `h_m`); values number, boolean, text (map if several languages).
85
89
  - Used by `timeline` (`data: ereignisse`) and `generated` (`from: { data: elemente }`). Each fact once, there.
@@ -27,6 +27,9 @@
27
27
  - New task per seed.
28
28
  - `given`: `int: [min, max]` + `step`; `decimal: [min, max]` + `step`; `oneOf: [...]`; `pick: <n>` `from:` list or
29
29
  `{ data: <name> }` (`distinct: true`; `pick: 1` = one record).
30
+ - An inline `from:` list holds numbers or texts only: a record in it (`{ de: …, en: … }`) is read as a text by
31
+ language and takes text values only. Records with numbers (`{ name: Stein, masse: 2 }`) → a data file
32
+ (`src/data/<name>.yaml`, `blocks.md`) and `from: { data: <name> }`.
30
33
  - `where`: boolean expressions; failing draws are redrawn.
31
34
  - `task`: `text`. A figure that changes with the task: an exercise component (`components.md`).
32
35
  - `answer`: `value` (expression) and `compare`: `number` or
@@ -36,6 +39,9 @@
36
39
  - Expressions (no eval): exact fractions, `+ - * /`, parentheses, `== != < <= > >=`, `and`, `or`, `not`, strings,
37
40
  fields (`e.year`), index (`xs[0]`), lists, `round(x, n)`, `decimals`, `map(xs, field)`,
38
41
  `sortBy(xs, field)`, `reverse`.
42
+ - `sqrt(x)`, `pow(x, n)`, `sin(x)`, `cos(x)`, `tan(x)`, `asin(x)`, `acos(x)`, `atan(x)`, angles in degrees
43
+ (`sin(30)` is `1/2`, `asin(0.5)` is `30`). A rational result is exact; any other (`sqrt(2)`, `sin(10)`) has 12
44
+ decimal places: give the answer with `round(…, n)` and `compare: { number: { decimals: n, tolerance } }`.
39
45
 
40
46
  ### Placeholders
41
47
 
@@ -283,3 +283,32 @@ severity: warning
283
283
  belong to it.
284
284
  - Each page's scope says what the page teaches, no more and no less; "Gehört nicht dazu" names what a learner or tutor
285
285
  might expect but the material does not teach.
286
+
287
+ ## `content-matches-competencies`: Each page teaches its competencies
288
+
289
+ ```rule
290
+ id: content-matches-competencies
291
+ scope: tooling
292
+ severity: hint
293
+ ```
294
+
295
+ - A page of a format-2 explainer with a strand teaches what the "Ich kann …" (`iCan`) of its competencies says in
296
+ `https://lernapps.net/apps/map/<subject>.json`: not clearly more, not something else.
297
+ - More to teach → a page of the competency that says it; none in the map → propose it to `lernapps/apps`
298
+ (`map/<subject>.yaml`), never invent it in the app.
299
+ - The review reads each page with the `iCan` of its competencies.
300
+
301
+ ## `competencies-on-map`: The strand and the page competencies are on the map
302
+
303
+ ```rule
304
+ id: competencies-on-map
305
+ scope: tooling
306
+ severity: warning
307
+ ```
308
+
309
+ - `strand` in `src/app.yaml` is a strand of the subject's map; each page's `competencies` are ids of that strand.
310
+ - A recap page's `recap` (`<subject>/<competency>`) is on the map, of any strand, and the app does not practise it
311
+ on a page of its own.
312
+ - `lernapps check` loads `https://lernapps.net/apps/map/<subject>.json` and warns before the push. Not reachable or
313
+ no map yet → one hint that it could not check.
314
+ - At the listing an unknown id or one of another strand is an error of the catalog (`entry-learning-map`).
@@ -17,8 +17,70 @@
17
17
  - Navigation generated: contents on start and chapter pages, breadcrumbs, previous/next across chapters, page
18
18
  contents from three sections, glossary from `definition` blocks.
19
19
 
20
+ ### When `scope` is needed
21
+
22
+ - All or nothing: `scope` in `src/app.yaml` → every page needs its own `scope`, else the build stops; none in
23
+ `src/app.yaml` → no page has one.
24
+ - Needed when the app has an `open-task` (the tutor judges open answers by the scope, never from general knowledge),
25
+ or follows the creator's material (the scope keeps the tutor to it). Else optional; the plan says which.
26
+ - Each page's `scope`: what the page teaches, in your own words; `src/app.yaml`: `scope: { text, out: [...] }` with
27
+ what does not belong (`explainer-scope-matches`, form in `runtime.md`).
28
+
29
+ ## Format 2: strand and competencies
30
+
31
+ An explainer on the learning map is in format 2. `src/app.yaml` starts with:
32
+
33
+ ```yaml
34
+ # yaml-language-server: $schema=../node_modules/@lernapps/app-templates/runtime/explainer/explainer.v2.schema.json
35
+ format: 2
36
+ strand: mathematik/stochastik # "<subject>/<strand>" of the map, or none
37
+ title: Stochastik
38
+ ```
39
+
40
+ | Field | Where | Rule |
41
+ |---|---|---|
42
+ | `format` | `src/app.yaml` | `2`; absent means format 1 (an app that has not moved) |
43
+ | `strand` | `src/app.yaml` | required in format 2: `<subject>/<strand>` (two ids of the map) or `none` |
44
+ | `competencies` | every page | strand set: required, ids of the app's strand; as a rule one. `none`: not allowed |
45
+
46
+ - A page: `competencies: [laplace]` beside `title` and `blocks`. One page per competency; a large one may take
47
+ several pages; several on one page are not recommended (`one-competency-per-page`).
48
+ - Chapters group competencies that build on each other; pages follow the map's `requires`.
49
+ - One app, one strand. The ids are those of `https://lernapps.net/apps/map/<subject>.json`; the build cannot know
50
+ them. `lernapps check` compares them with the map before the push (`competencies-on-map`); the listing checks
51
+ them again.
52
+ - The build writes `lernapps-pages.json` (path, title, competencies or recap per page, in the app's order) beside
53
+ `index.html`; the catalog lists the pages from it.
54
+
55
+ ### A recap page
56
+
57
+ A competency of the app may require one the app does not practise: of another strand, or of the app's strand outside
58
+ the app's scope. The app may repeat it on a recap page, placed before the page that needs it:
59
+
60
+ ```yaml
61
+ title: Brüche – kurz wiederholt
62
+ recap: mathematik/brueche-anteile # "<subject>/<competency>", one the app does not practise
63
+ blocks: [...] # descriptive blocks only: no exercise
64
+ ```
65
+
66
+ | Rule | Checked by |
67
+ |---|---|
68
+ | A page has either `competencies` or `recap`, never both | schema v2 |
69
+ | A recap page has no exercise; `topic-has-exercises` does not apply to it | schema v2, build |
70
+ | The link to the competency's page on the map is written by the runtime, so it cannot be missing | runtime |
71
+ | The recapped competency exists, and the app does not practise it on a page of its own | `lernapps check` (`competencies-on-map`, warning), listing (error) |
72
+
73
+ - A recap page counts as nothing on the map and is left out of the self-assessment.
74
+ - A prerequisite the app does not practise (of any strand) with neither a recap page nor a link in the page's text to
75
+ `https://lernapps.net/apps/map/<subject>/c/<id>/`: a hint of `lernapps check` (`prerequisites-linked`).
76
+ - Format 1 stays valid. `lernapps update` adds `format: 2` and `strand: none` to a format-1 app when its runtime
77
+ knows format 2.
78
+
20
79
  ## Entry topics
21
80
 
81
+ - An explainer in format 2 has no hand-written topics: the catalog takes its pages from `lernapps-pages.json`. The
82
+ rest of this section holds for the other apps.
83
+
22
84
  - Path of a page or section relative to the app's `url`: `prozent/prozentwert/`, `prozent/prozentwert/#regel`
23
85
  (`entry-links-resolve`; `lernapps check --entry` checks the section).
24
86
  - From the stable ids (`explainer-stable-ids`).
@@ -20,7 +20,20 @@ license: MIT
20
20
 
21
21
  ## Map the content
22
22
 
23
- - A chapter of competencies → an app chapter. A competency → a page or a section.
23
+ - A competency (one teachable thing, one or two lessons) → a page. A large competency may take several pages.
24
+ - A chapter → competencies that build on each other. Order chapters and pages along the map's `requires`: what
25
+ learners need first comes first.
26
+ - Prerequisites: for each page's competency, read its `requires` in the map.
27
+ - The app practises it (a page of its own) → it orders the pages; the text may link the page that teaches it.
28
+ - The app does not practise it → of another strand (one app, one strand), or of the app's strand outside the
29
+ app's scope. The page names it. The app may repeat it on a recap page (`recap: <subject>/<competency>`):
30
+ descriptive blocks only, no exercise; the runtime writes the link to its page on the map. Place the recap page
31
+ before the page that needs it ([structure](../lernapps-explainer/references/structure.md)). Never both: a
32
+ competency the app practises gets no recap page.
33
+ - The app has one strand of the subject's learning map (recommended, skill `lernapps-content-research`), or `none`.
34
+ Every page names the competency of the map it practises (`competencies`, ids of the app's strand). A competency
35
+ the map lacks is proposed to the catalog, not invented here. With `strand: none` pages name none.
36
+ - Show the creator this structure in Explore: which competency on which page, which pages in which chapter.
24
37
  - Depth: chapter, page, section, subsection. Need more levels → two apps.
25
38
  - The content structure → the same sections on every page, with fixed ids and titles (e.g. "Regel", "Beispiel",
26
39
  "Übung").
@@ -33,7 +46,7 @@ license: MIT
33
46
  - Text: prose (restricted Markdown), `definition` (also in the glossary), `example` and `note` boxes, `quote`,
34
47
  `code`.
35
48
  - Formulas: `formula` and inline `$…$`, LaTeX subset incl. chemistry (`\ce`), rendered as MathML.
36
- - Figures: drawn (`hundred-grid`, `bar`), an image (`images/<name>.svg`, with source and licence), a
49
+ - Figures: drawn (`hundred-grid`, `bar`: both a part of a whole), an image (`images/<name>.svg`, with source and licence), a
37
50
  `labelled-figure` with numbered markers, a `table`, a `timeline`.
38
51
  - Exercises: the six question types of the quiz; `generated` (a new task per seed, with drawn values, typical
39
52
  mistakes, hint and steps); a component.
@@ -44,10 +57,41 @@ license: MIT
44
57
 
45
58
  ## Decide
46
59
 
60
+ - Strand: `<subject>/<strand>` from the map where one fits, or `none`. The plan records it, the competency ids of
61
+ each page, each page's prerequisites the app does not practise and which of them get a recap page (format 2,
62
+ [structure](../lernapps-explainer/references/structure.md)).
47
63
  - Languages: German; English as a second language is supported. Each declared language complete. Never English
48
64
  only: without `languages` the page and the runtime's texts are German. English wanted → `[de, en]`.
49
65
  - The tutor: the build writes it from the content. Plan only app-specific tutor rules, if any.
50
66
 
67
+ ## `one-competency-per-page`: One page per competency
68
+
69
+ ```rule
70
+ id: one-competency-per-page
71
+ scope: tooling
72
+ severity: hint
73
+ ```
74
+
75
+ - Each page teaches one competency of the map: `competencies` with one id. Several pages for one large competency
76
+ are fine.
77
+ - Several competencies on one page: allowed by the schema, not recommended. Split the page, one per competency.
78
+ - `lernapps check` gives a hint for every page of a format-2 explainer with more than one competency.
79
+
80
+ ## `prerequisites-linked`: Prerequisites the app does not practise are recapped or linked
81
+
82
+ ```rule
83
+ id: prerequisites-linked
84
+ scope: tooling
85
+ severity: hint
86
+ ```
87
+
88
+ - A competency of a page requires one the app does not practise (`requires` in the map), of another strand or of
89
+ the app's own: learners find it from the app.
90
+ - Either a recap page of the app repeats it (`recap: <subject>/<competency>`), or the page's text names it with a link
91
+ to its page on the map: `https://lernapps.net/apps/map/<subject>/c/<id>/`.
92
+ - `lernapps check` gives a hint for each such prerequisite with neither, naming the page and the prerequisite. Without
93
+ the map it finds nothing (`competencies-on-map` says it could not check).
94
+
51
95
  ## Gotchas
52
96
 
53
97
  - Each component costs a module, a test and a no-JS text: plan one only where it teaches what no block can.
@@ -11,6 +11,7 @@ severity: error
11
11
  - Every page has at least one exercise that practises what the page explains, with solution or feedback; best in a
12
12
  last section "Übung".
13
13
  - The schema requires it; start, chapter and glossary pages are generated. The review judges the fit.
14
+ - Except a recap page (`recap`, format 2): it repeats a prerequisite the app does not practise and has no exercise.
14
15
 
15
16
  ## `prefer-built-in-block`: A component only where no block fits
16
17
 
@@ -89,5 +89,6 @@ selfAssessment:
89
89
 
90
90
  - An exercise renamed or moved → change its reference in the same commit.
91
91
  - `einschaetzung` is reserved: no chapter or page may have that id.
92
+ - A recap page (`recap`) has no exercise: it is never part of a self-assessment.
92
93
  - Never `selfAssessment` in `src/app.yaml` (build problem): the app's comes from the chapters.
93
94
  - Nothing about a real learner in any file (`no-learner-data-in-texts`).
@@ -19,6 +19,15 @@ At the checkpoint "list", without asking first: the pull request publishes nothi
19
19
  check it, how to open the pull request.
20
20
  2. Fill the entry from the plan's entry draft and <https://lernapps.net/apps/schemas/entry.v1.schema.json>, fetched
21
21
  now. The schema describes every field.
22
+ - An explainer in format 2: its pages come from `lernapps-pages.json` of the build, not from a hand-written list. The
23
+ listing checks the competency ids against the map and that they belong to the app's `strand`. A `strand` in
24
+ the entry, if given, equals the one in that file.
25
+ - Any other app may give `strand` (`<subject>/<strand>`) and `competencies` (ids of that subject) in the entry;
26
+ `competencies` needs a `strand`. An app without a strand is not on the map; that is valid.
27
+ - An unknown id or one of another strand is an error of the listing (`entry-learning-map`, lernapps/apps).
28
+ The review adds two hints, never errors: a strand that could fit an app with `strand: none`
29
+ (`strand-from-map`), a page that teaches more or other than its competencies say
30
+ (`content-matches-competencies`). `lernapps check` warns about unknown ids before (`competencies-on-map`).
22
31
  3. Run the check the guide names. Fix its findings first: the pull request runs the same check.
23
32
  4. Open the pull request from the creator's fork as skill `lernapps-github` says.
24
33
 
@@ -8,8 +8,9 @@
8
8
  // Renovate configuration (generator/app/); an interactive app also its architecture (generator/architecture.ts). The
9
9
  // skills `lernapps init` synced into the folder stay, and are committed with the app; `lernapps update` replaces them
10
10
  // with those of the tooling it moves the app to.
11
- // To move to a new app-templates: set the exact version of @lernapps/app-templates in package.json; the generator's
12
- // tests then run on that version.
11
+ // To move to a new app-templates: set the exact version of @lernapps/app-templates in package.json, or a commit
12
+ // (github:lernapps/app-templates#<commit>, which Renovate moves) until a release has what the tooling needs; the
13
+ // generator's tests then run on that version. An app gets the same: ^ the version, or the same commit (runtimeSpec).
13
14
  import { spawnSync } from "node:child_process";
14
15
  import {
15
16
  cpSync,
@@ -49,7 +50,8 @@ node_modules init wrote). From the installed package @lernapps/app-templates, th
49
50
  on, it writes the archetype's scaffold: the files of the template folder <name>/ that the package's lernapps.scaffold
50
51
  lists (the entry, the configuration, the end-to-end tests of the content) and a placeholder as start content
51
52
  (scaffold/<name>/), which the planned content replaces. The template folder itself is the example app: it stays in
52
- node_modules/@lernapps/app-templates/<name>/, to look up. The app depends on the runtime of the same version.
53
+ node_modules/@lernapps/app-templates/<name>/, to look up. The app depends on the runtime of the same version (or
54
+ the same commit, while @lernapps/tooling depends on an unreleased one).
53
55
 
54
56
  It needs the plan of the first version, confirmed by the creator: started from the plan template as
55
57
  .vibe/plans/01-<short name>.md, the only plan there, worked through Explore and Plan, now in the phase code
@@ -74,7 +76,10 @@ publish action to the repo's GitHub Pages), an issue form for content errors, LI
74
76
  Renovate preset of lernapps). The installed skills and skills-lock.json are committed with the app, like the
75
77
  content guide; lernapps update replaces the lernapps-* skills.
76
78
 
77
- Then: git init (if <dir> is not a repository yet), npm install (installs the git hooks), and the first commit.
79
+ It installs nothing and commits nothing. Its last lines say what to do next, in this order: git init
80
+ --initial-branch=work (only if <dir> is not a repository yet; lernapps init made one), npm install (installs the
81
+ runtime and the git hooks), git add -A and git commit (the first commit, on work; the hooks check it), then follow
82
+ the plan.
78
83
 
79
84
  Exit codes: 0 when the app is written, 1 when it cannot be (the message says why), 2 on a usage error.
80
85
 
@@ -84,8 +89,8 @@ Options:
84
89
  its file name <NN>-<short name>.md, else 01-plan.md (default: the one in <dir>)
85
90
  --templates <dir> the package @lernapps/app-templates to copy from: its folder, or a clone of its repository
86
91
  (default: the environment variable LERNAPPS_TEMPLATES, else the installed package)
87
- --tooling <spec> the npm spec of @lernapps/tooling the app depends on (default: ^ the version of this
88
- @lernapps/tooling)
92
+ --tooling <spec> the npm spec of @lernapps/tooling the app depends on (default: the one lernapps init
93
+ installed, else ^ the version of this @lernapps/tooling)
89
94
  -h, --help print this help
90
95
  `;
91
96
 
@@ -98,6 +103,7 @@ export const fromPackage = (...path: string[]) => join(packageRoot, ...path);
98
103
  export interface Manifest {
99
104
  name?: string;
100
105
  version?: string;
106
+ dependencies?: Record<string, string>;
101
107
  devDependencies?: Record<string, string>;
102
108
  scripts?: Record<string, string>;
103
109
  lernapps?: { archetypes?: string[]; scaffold?: Record<string, string[]>; init?: boolean };
@@ -147,6 +153,19 @@ export function templatesPackage(dir?: string): Templates {
147
153
  };
148
154
  }
149
155
 
156
+ /** The version of a package built from a commit, not released: its package.json in the repository says 0.0.0. */
157
+ const UNRELEASED = "0.0.0";
158
+
159
+ /**
160
+ * The spec of the runtime an app depends on, for templates of `version`: ^ that version; for templates built from a
161
+ * commit (version 0.0.0, which the registry does not have) the spec this tooling depends on, the same commit
162
+ * (github:lernapps/app-templates#<commit>), so that template and runtime match.
163
+ */
164
+ export function runtimeSpec(version: string): string {
165
+ if (version !== UNRELEASED) return `^${version}`;
166
+ return readJson(fromPackage("package.json")).dependencies?.[RUNTIME] ?? `^${version}`;
167
+ }
168
+
150
169
  /** The archetypes of the installed templates. */
151
170
  export const archetypes = (): string[] => templatesPackage().archetypes;
152
171
 
@@ -241,6 +260,9 @@ export function writeScaffold(templates: Templates, archetype: string, dir: stri
241
260
  cpSync(start, dir, { recursive: true });
242
261
  }
243
262
 
263
+ /** The branch the plan works on (rule 8 of the plan template). */
264
+ export const WORK_BRANCH = "work";
265
+
244
266
  /** This package, as an app depends on it. */
245
267
  export const TOOLING = "@lernapps/tooling";
246
268
 
@@ -453,7 +475,7 @@ export function create(args: string[]): number {
453
475
  // the template uses the runtime of its own checkout (file:.., installed as a copy through .npmrc); the app the
454
476
  // version of the runtime it was copied from
455
477
  if (app.devDependencies?.[RUNTIME] !== undefined) {
456
- app.devDependencies = { ...app.devDependencies, [RUNTIME]: `^${templates.version}` };
478
+ app.devDependencies = { ...app.devDependencies, [RUNTIME]: runtimeSpec(templates.version) };
457
479
  }
458
480
  // the template keeps its checkout's runtime with an override (file:..), which npm would otherwise swap for the
459
481
  // version this tooling depends on; the app has no checkout to point at
@@ -463,8 +485,9 @@ export function create(args: string[]): number {
463
485
  if (Object.keys(kept).length > 0) app["overrides"] = kept;
464
486
  else delete app["overrides"];
465
487
  }
466
- // the tooling the app depends on: the one named, else this version (init may have installed it already)
467
- const tooling = values.tooling ?? `^${ownVersion()}`;
488
+ // the tooling the app depends on: the one named, else the one init installed (maybe a local one, --tooling of
489
+ // init), else this version
490
+ const tooling = values.tooling ?? initialised?.devDependencies?.[TOOLING] ?? `^${ownVersion()}`;
468
491
  app.devDependencies = {
469
492
  ...app.devDependencies,
470
493
  ...ARCHETYPE_DEV_DEPENDENCIES[archetype],
@@ -490,7 +513,13 @@ export function create(args: string[]): number {
490
513
  `lernapps create: the ${archetype} app ${name} in ${dir} (app-templates ${templates.version})`,
491
514
  ...guide.map((line) => `Content guide: ${line}`),
492
515
  ...(moved ? [`Plan: moved ${LEGACY_PLAN} to ${relative(dir, plan.target)}`] : []),
493
- `Next: ${existsSync(join(dir, ".git")) ? "" : "git init, "}npm install, then follow ${relative(dir, plan.target)}`,
516
+ "Next, in this order (create installs and commits nothing):",
517
+ ...[
518
+ ...(existsSync(join(dir, ".git")) ? [] : [`git init --initial-branch=${WORK_BRANCH}`]),
519
+ "npm install (the runtime and the git hooks)",
520
+ "git add -A, then git commit (the first commit; the hooks check it)",
521
+ `follow ${relative(dir, plan.target)}`,
522
+ ].map((step, index) => ` ${index + 1}. ${step}`),
494
523
  "",
495
524
  ].join("\n"),
496
525
  );