@lernapps/tooling 0.0.1 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +67 -2
- package/archetypes/shared/a11y.ts +38 -0
- package/archetypes/shared/hooks/pre-commit +22 -0
- package/archetypes/shared/hooks/pre-push +4 -0
- package/archetypes/shared/i18n.ts +25 -0
- package/archetypes/shared/install-hooks.ts +63 -0
- package/archetypes/shared/playwright.ts +54 -0
- package/archetypes/shared/preset.ts +81 -0
- package/archetypes/shared/storage.ts +167 -0
- package/archetypes/shared/tsconfig.json +27 -0
- package/check/validation-report.v1.schema.json +116 -0
- package/dist/a11y.mjs +30 -0
- package/dist/check/rules/accessible.mjs +18 -0
- package/dist/check/rules/app-content-guide.mjs +69 -0
- package/dist/check/rules/dependency-licence.mjs +82 -0
- package/dist/check/rules/entry-fitness-matches.mjs +22 -0
- package/dist/check/rules/entry-links-resolve.mjs +44 -0
- package/dist/check/rules/explainer-readable-without-js.mjs +66 -0
- package/dist/check/rules/links-resolve.mjs +10 -0
- package/dist/check/rules/lockfile-matches.mjs +57 -0
- package/dist/check/rules/no-request-before-click.mjs +17 -0
- package/dist/check/rules/no-tracking.mjs +17 -0
- package/dist/check/rules/own-data-only.mjs +66 -0
- package/dist/check/rules/pages-reachable.mjs +25 -0
- package/dist/check/rules/usable-at-360px.mjs +19 -0
- package/dist/check-cv4I6CCH.mjs +28 -0
- package/dist/cli.mjs +3118 -0
- package/dist/i18n.mjs +11 -0
- package/dist/lint.mjs +528 -0
- package/dist/plan-DWKayEnY.mjs +75 -0
- package/dist/playwright.mjs +47 -0
- package/dist/preset.mjs +60 -0
- package/dist/skills/lernapps-app/SKILL.md +49 -0
- package/dist/skills/lernapps-cli/SKILL.md +156 -0
- package/dist/skills/lernapps-cli/references/platform-rules.md +258 -0
- package/dist/skills/lernapps-commit/SKILL.md +39 -0
- package/dist/skills/lernapps-content-growth/SKILL.md +33 -0
- package/dist/skills/lernapps-content-questions/SKILL.md +133 -0
- package/dist/skills/lernapps-content-questions/references/question-types.md +69 -0
- package/dist/skills/lernapps-content-research/SKILL.md +50 -0
- package/dist/skills/lernapps-content-research/references/sources.md +16 -0
- package/dist/skills/lernapps-exercise-patterns/SKILL.md +74 -0
- package/dist/skills/lernapps-exercise-patterns/references/biology-geography.md +13 -0
- package/dist/skills/lernapps-exercise-patterns/references/chemistry.md +16 -0
- package/dist/skills/lernapps-exercise-patterns/references/computer-science.md +13 -0
- package/dist/skills/lernapps-exercise-patterns/references/figures.md +10 -0
- package/dist/skills/lernapps-exercise-patterns/references/history.md +13 -0
- package/dist/skills/lernapps-exercise-patterns/references/mathematics.md +19 -0
- package/dist/skills/lernapps-exercise-patterns/references/page-structures.md +8 -0
- package/dist/skills/lernapps-exercise-patterns/references/physics.md +18 -0
- package/dist/skills/lernapps-exercise-patterns/references/success.md +28 -0
- package/dist/skills/lernapps-explainer/SKILL.md +33 -0
- package/dist/skills/lernapps-explainer/references/blocks.md +83 -0
- package/dist/skills/lernapps-explainer/references/components.md +66 -0
- package/dist/skills/lernapps-explainer/references/exercises.md +115 -0
- package/dist/skills/lernapps-explainer/references/rules.md +285 -0
- package/dist/skills/lernapps-explainer/references/runtime.md +79 -0
- package/dist/skills/lernapps-explainer/references/structure.md +24 -0
- package/dist/skills/lernapps-explainer-design/SKILL.md +57 -0
- package/dist/skills/lernapps-explainer-design/references/rules.md +75 -0
- package/dist/skills/lernapps-explainer-self-assessment/SKILL.md +93 -0
- package/dist/skills/lernapps-github/SKILL.md +102 -0
- package/dist/skills/lernapps-interactive/SKILL.md +141 -0
- package/dist/skills/lernapps-interactive/references/frame.md +30 -0
- package/dist/skills/lernapps-interactive-design/SKILL.md +58 -0
- package/dist/skills/lernapps-listing/SKILL.md +44 -0
- package/dist/skills/lernapps-listing/references/keeping-updated.md +33 -0
- package/dist/skills/lernapps-quiz/SKILL.md +100 -0
- package/dist/skills/lernapps-quiz-design/SKILL.md +48 -0
- package/dist/status-CXoQBgfU.mjs +76 -0
- package/dist/storage.mjs +113 -0
- package/generator/app/.agents/skills/app-content/SKILL.md +63 -0
- package/generator/app/.github/ISSUE_TEMPLATE/inhaltsfehler.yml +25 -0
- package/generator/app/.github/workflows/pages.yml +61 -0
- package/generator/app/LICENSE +21 -0
- package/generator/app/renovate.json +4 -0
- package/generator/architecture.ts +212 -0
- package/generator/create.ts +462 -0
- package/generator/init.ts +186 -0
- package/generator/update.ts +429 -0
- package/guidance/AGENTS.md +26 -0
- package/guidance/increment-template.md +194 -0
- package/guidance/plan-front-matter.v1.schema.json +36 -0
- package/guidance/plan-template.md +233 -0
- package/guidance/subagent-brief.md +25 -0
- package/guidance/workflow-rules.md +109 -0
- package/package.json +133 -4
- package/review/verdict.v1.schema.json +134 -0
- package/skills/archetypes/lernapps-explainer/SKILL.md +33 -0
- package/skills/archetypes/lernapps-explainer/references/blocks.md +83 -0
- package/skills/archetypes/lernapps-explainer/references/components.md +66 -0
- package/skills/archetypes/lernapps-explainer/references/exercises.md +115 -0
- package/skills/archetypes/lernapps-explainer/references/rules.md +285 -0
- package/skills/archetypes/lernapps-explainer/references/runtime.md +79 -0
- package/skills/archetypes/lernapps-explainer/references/structure.md +24 -0
- package/skills/archetypes/lernapps-explainer-design/SKILL.md +57 -0
- package/skills/archetypes/lernapps-explainer-design/references/rules.md +75 -0
- package/skills/archetypes/lernapps-explainer-self-assessment/SKILL.md +93 -0
- package/skills/archetypes/lernapps-interactive/SKILL.md +141 -0
- package/skills/archetypes/lernapps-interactive/references/frame.md +30 -0
- package/skills/archetypes/lernapps-interactive-design/SKILL.md +58 -0
- package/skills/archetypes/lernapps-quiz/SKILL.md +100 -0
- package/skills/archetypes/lernapps-quiz-design/SKILL.md +48 -0
- package/skills/content/lernapps-content-questions/SKILL.md +133 -0
- package/skills/content/lernapps-content-questions/references/question-types.md +69 -0
- package/skills/content/lernapps-content-research/SKILL.md +50 -0
- package/skills/content/lernapps-content-research/references/sources.md +16 -0
- package/skills/content/lernapps-exercise-patterns/SKILL.md +74 -0
- package/skills/content/lernapps-exercise-patterns/references/biology-geography.md +13 -0
- package/skills/content/lernapps-exercise-patterns/references/chemistry.md +16 -0
- package/skills/content/lernapps-exercise-patterns/references/computer-science.md +13 -0
- package/skills/content/lernapps-exercise-patterns/references/figures.md +10 -0
- package/skills/content/lernapps-exercise-patterns/references/history.md +13 -0
- package/skills/content/lernapps-exercise-patterns/references/mathematics.md +19 -0
- package/skills/content/lernapps-exercise-patterns/references/page-structures.md +8 -0
- package/skills/content/lernapps-exercise-patterns/references/physics.md +18 -0
- package/skills/content/lernapps-exercise-patterns/references/success.md +28 -0
- package/skills/process/lernapps-app/SKILL.md +49 -0
- package/skills/process/lernapps-commit/SKILL.md +39 -0
- package/skills/process/lernapps-content-growth/SKILL.md +33 -0
- package/skills/process/lernapps-github/SKILL.md +102 -0
- package/skills/process/lernapps-listing/SKILL.md +44 -0
- package/skills/process/lernapps-listing/references/keeping-updated.md +33 -0
- package/skills/tooling/lernapps-cli/SKILL.md +156 -0
- package/skills/tooling/lernapps-cli/references/platform-rules.md +258 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Components
|
|
2
|
+
|
|
3
|
+
- Only for what no block or `generated` can express (label a figure, drag into place). Blocks are readable without
|
|
4
|
+
JS, accessible, tested by construction.
|
|
5
|
+
|
|
6
|
+
## Files (`src/components/`)
|
|
7
|
+
|
|
8
|
+
- Only `<Name>.vue`, `<Name>.ts`, `<Name>.test.ts`; PascalCase; no subfolders, no other files.
|
|
9
|
+
- Every `.vue`: `<script setup lang="ts">` and its `<Name>.test.ts` (run by `lernapps check`; `.vue` type-checked
|
|
10
|
+
with `vue-tsc`).
|
|
11
|
+
- Exercise: logic in `<Name>.ts`. Shared helpers: a `<Name>.ts` here, imported with extension (`./Circuit.ts`).
|
|
12
|
+
|
|
13
|
+
## Block and props
|
|
14
|
+
|
|
15
|
+
- `type: vue-component`, `component: <Name>`, `props`, `nojs` (not for an exercise).
|
|
16
|
+
- `defineProps<{ … }>()`: `string`, `number`, `boolean`, literals, unions, arrays, object types of them; declared in
|
|
17
|
+
the component or a local type alias. No `null`, no `undefined` union (make it optional), no imported alias.
|
|
18
|
+
- The build checks each block's `props`. A `string` prop is a learner text: a map with several languages.
|
|
19
|
+
- A list prop may be `{ data: <name> }`: the records of `src/data/<name>.yaml`, cut to the fields the item type declares
|
|
20
|
+
(`events: { title: string }[]`). Each record needs them; a missing file or field fails the build. One data file,
|
|
21
|
+
many blocks: never copy records into `props`. A field of a record may be a list or a record of values
|
|
22
|
+
(`compounds: [{ formula, text }]`) for this; exercises and timelines use the flat fields.
|
|
23
|
+
|
|
24
|
+
## `nojs`
|
|
25
|
+
|
|
26
|
+
- Restricted Markdown: what it shows plus its content (diagram described, or task with solution); carries the
|
|
27
|
+
meaning alone.
|
|
28
|
+
- Replaced once the component runs; `defineOptions({ nojs: "caption" })` keeps it folded as a description.
|
|
29
|
+
- Never in an exercise component (it would give the solution; build fails). Its text comes from `describe` for task 1.
|
|
30
|
+
|
|
31
|
+
## Exercise contract
|
|
32
|
+
|
|
33
|
+
- Emits `answer` → exercise. It is only the input: emit the learner's answer on every change (`undefined` while
|
|
34
|
+
none). No own "Prüfen", no own `<form>`, no call of `check`: the explainer draws "Prüfen" under it (Enter in a field
|
|
35
|
+
checks), calls `check`, `reason` and `show`, shows the feedback. Own controls ("Zurücksetzen", tools) stay, as
|
|
36
|
+
`type="button"`. Optional prop `checked: boolean`: true once the current answer is checked.
|
|
37
|
+
- `answered` is gone: the build fails on it.
|
|
38
|
+
- Does not count as the page's required exercise: the page also needs a question or a `generated` exercise.
|
|
39
|
+
- `<Name>.ts`, pure: `generate(seed, props)`, `check(task, answer)` (boolean), `solution(task)`, `wrong(task)`,
|
|
40
|
+
`reason(task, answer, props, lang)` (the app's message for a wrong answer from the props' texts, `undefined` for
|
|
41
|
+
none; what does not fit, never the solution). A component shows nothing after "Prüfen" that gives the solution away. Check the asked property, not equality with `solution`.
|
|
42
|
+
- Build tests 50 fixed seeds per block: same task per seed, solution passes, wrong fails, nothing throws.
|
|
43
|
+
- `about.mistakes` from the same wrong answers as `examples.rejected`; `describe` states the task exactly as shown
|
|
44
|
+
for that task number (no-JS text).
|
|
45
|
+
- For the tutor: `template(props, lang)` (task with `{name}`), `params(task, props, lang)` (inputs by name),
|
|
46
|
+
`mistakes(task, props)` (per `about.mistakes` its one fixed answer, else `undefined`: never an invented example),
|
|
47
|
+
`show(answer, task, props, lang)` (answer as text). An input of `params` that is no placeholder shows as
|
|
48
|
+
"(verdeckt)": the tutor knows it, the child does not. One `template` per block: prefer one kind of task per block;
|
|
49
|
+
else give the sentence that changes as an input (`{Aufgabe}`). `about.answer` names what the child does and "Prüfen". The build checks them against `check` for every number; rule `vue-component-tutor-table`. Model:
|
|
50
|
+
`ProzentFeld` of the template.
|
|
51
|
+
- More: rule `vue-component-exercise-contract`.
|
|
52
|
+
|
|
53
|
+
## Rendering
|
|
54
|
+
|
|
55
|
+
- Server-rendered into the page, hidden until hydrated. First render identical on server and browser:
|
|
56
|
+
`generate(1, props)`; randomness only in `onMounted`.
|
|
57
|
+
- No headings in its markup (also hidden parts). Texts via props, not strings in code.
|
|
58
|
+
|
|
59
|
+
## Limits (build and lint check, same ids)
|
|
60
|
+
|
|
61
|
+
- No network: no `fetch`, XMLHttpRequest, WebSocket, EventSource, `sendBeacon`, URL imports.
|
|
62
|
+
- No `eval`, `new Function`, `innerHTML`, `v-html`.
|
|
63
|
+
- Imports: `vue`, `@lernapps/app-templates`, `@lernapps/tooling/storage`, `i18n`, `a11y`, own folder.
|
|
64
|
+
- Browser globals only in `onMounted` or later unmount hooks.
|
|
65
|
+
- Storage only via `createStorage`.
|
|
66
|
+
- Max 20 kB gzip own code (Vue not counted); more → split, or ask for a block.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Exercises
|
|
2
|
+
|
|
3
|
+
- Every page at least one, best in a last section "Übung".
|
|
4
|
+
- What counts as right and each wrong answer's feedback: from "Content method" of the plan.
|
|
5
|
+
- Wrong answer gets the most specific reason: chosen option's `info`, a matching `mistakes` entry (`generated`;
|
|
6
|
+
`number` with `answer`, `message`), the `hint`, else "look again". Write `info` per wrong option, 2–3 mistakes,
|
|
7
|
+
a `hint`.
|
|
8
|
+
|
|
9
|
+
## Quiz types
|
|
10
|
+
|
|
11
|
+
- `single-choice`, `multiple-choice`, `true-false`, `number`, `ordering`, `matching`: blocks with the quiz fields
|
|
12
|
+
(`id`, `type`, `text`, `explanation`, `links`, type fields). Fields and how to write them: skill
|
|
13
|
+
`lernapps-content-questions`.
|
|
14
|
+
|
|
15
|
+
```yaml
|
|
16
|
+
- type: ordering
|
|
17
|
+
id: staende-ordnen
|
|
18
|
+
text: Bringe die Stände in ihre Rangfolge, den ersten Stand zuerst.
|
|
19
|
+
items: [die Geistlichen, der Adel, alle anderen]
|
|
20
|
+
explanation: Der erste Stand waren die Geistlichen, der zweite der Adel, der dritte alle anderen.
|
|
21
|
+
links:
|
|
22
|
+
- { title: Ständegesellschaft (Wikipedia), url: "https://de.wikipedia.org/wiki/Ständegesellschaft" }
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## `generated`
|
|
26
|
+
|
|
27
|
+
- New task per seed.
|
|
28
|
+
- `given`: `int: [min, max]` + `step`; `decimal: [min, max]` + `step`; `oneOf: [...]`; `pick: <n>` `from:` list or
|
|
29
|
+
`{ data: <name> }` (`distinct: true`; `pick: 1` = one record).
|
|
30
|
+
- `where`: boolean expressions; failing draws are redrawn.
|
|
31
|
+
- `task`: `text`. A figure that changes with the task: an exercise component (`components.md`).
|
|
32
|
+
- `answer`: `value` (expression) and `compare`: `number` or
|
|
33
|
+
`{ number: { format: integer|decimal|money|percent, decimals, tolerance, unit } }`, `{ choice: <options> }`,
|
|
34
|
+
`order`, `pairs` or `{ pairs: { left, right } }`.
|
|
35
|
+
- `mistakes`: `value` + `message` each. `hint`, `steps`: help, the way to the solution.
|
|
36
|
+
- Expressions (no eval): exact fractions, `+ - * /`, parentheses, `== != < <= > >=`, `and`, `or`, `not`, strings,
|
|
37
|
+
fields (`e.year`), index (`xs[0]`), lists, `round(x, n)`, `decimals`, `map(xs, field)`,
|
|
38
|
+
`sortBy(xs, field)`, `reverse`.
|
|
39
|
+
|
|
40
|
+
### Placeholders
|
|
41
|
+
|
|
42
|
+
- Always two braces around a declared name or field: `{{G}}`, `{{e.name}}`, `{{answer}}` in steps. Prose and
|
|
43
|
+
formulas alike.
|
|
44
|
+
- One brace is never a placeholder: `G`, `{G}`, `\frac{W}{G}` are letters. Group around a value stays:
|
|
45
|
+
`$\frac{{{W}}}{{{G}}} \cdot 100 = {{answer}}$`.
|
|
46
|
+
- Build fails on: undeclared name; `{G}` or `{{ G }}` in prose; a `given` variable nothing uses (`where` does not
|
|
47
|
+
count); a placeholder left after filling.
|
|
48
|
+
|
|
49
|
+
```yaml
|
|
50
|
+
- type: generated
|
|
51
|
+
id: prozentwert-ueben
|
|
52
|
+
given:
|
|
53
|
+
G: { int: [150, 1500], step: 5 }
|
|
54
|
+
p: { oneOf: [5, 10, 12, 15, 20, 25, 30, 40] }
|
|
55
|
+
where: ["decimals(G * p / 100) <= 2"]
|
|
56
|
+
task:
|
|
57
|
+
text: "Ein Fahrrad kostet {{G}} €. Im Angebot gibt es {{p}} % Rabatt. Wie viel Euro sparst du?"
|
|
58
|
+
answer: { value: "G * p / 100", compare: { number: { format: money } } }
|
|
59
|
+
mistakes:
|
|
60
|
+
- { value: "G * p", message: "Du hast vergessen, durch 100 zu teilen: {{p}} % sind {{p}} Hundertstel." }
|
|
61
|
+
- { value: "G - G * p / 100", message: "Das ist der neue Preis, nicht der Rabatt." }
|
|
62
|
+
hint: "Rechne erst 1 % aus: {{G}} € geteilt durch 100."
|
|
63
|
+
steps:
|
|
64
|
+
- '$W = \frac{G \cdot p}{100}$'
|
|
65
|
+
- '$W = \frac{{{G}} \cdot {{p}}}{100} = {{answer}}$, also {{answer}} € Rabatt.'
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
- Data-file example: `given: { events: { pick: 3, distinct: true, from: { data: ereignisse } } }`,
|
|
69
|
+
`where: ["events[0].date != events[1].date"]`, `answer: { value: "map(sortBy(events, date), title)", compare: order }`,
|
|
70
|
+
mistake `map(reverse(sortBy(events, date)), title)`.
|
|
71
|
+
|
|
72
|
+
### Property test (200 fixed seeds; first problem with its seed)
|
|
73
|
+
|
|
74
|
+
- `where` holds in at least 1 of 10 draws.
|
|
75
|
+
- Answer defined, in its format, passes its own comparator.
|
|
76
|
+
- No mistake equals the answer (`for seed 17 the mistake equals the answer (20); …`); each mistake is enterable.
|
|
77
|
+
- Same task for the same seed; no placeholder left.
|
|
78
|
+
- Mistake equal to the answer for some values = range too wide (`G - G * p / 100` = `G * p / 100` at 50 %: so `p`
|
|
79
|
+
≤ 40).
|
|
80
|
+
|
|
81
|
+
## `open-task`
|
|
82
|
+
|
|
83
|
+
- For what learners answer in their own words: describe, explain, compare, argue; a short text in German or a foreign
|
|
84
|
+
language. Not for what a quiz type can check.
|
|
85
|
+
- The learner answers first, typed or on paper with a photo (`answer: [text, photo]`, both by default), then compares:
|
|
86
|
+
the points show as tick boxes, each point of the content with its link "Nachlesen", and the model answer opens.
|
|
87
|
+
The ticks give right, partly right or wrong for the self-assessment and the tutor.
|
|
88
|
+
- `task`: what to write and how long ("in zwei bis vier Sätzen").
|
|
89
|
+
- `points`: 2 to 5, each one checkable sentence a good answer has, in its order. A point of the content has `source`,
|
|
90
|
+
the section of the app where its content is, `/<chapter>/<page>/#<section>` (the build checks it like a link); a
|
|
91
|
+
point of form (whole sentences, a thread, the words to use, the tense) has `kind: form` and no source. Only what the
|
|
92
|
+
material teaches.
|
|
93
|
+
- `solution`: the model answer, in the words a learner of the class would write, with every point. `hint` optional.
|
|
94
|
+
|
|
95
|
+
```yaml
|
|
96
|
+
- type: open-task
|
|
97
|
+
id: tauschen-erklaeren
|
|
98
|
+
task: Erkläre in zwei bis vier Sätzen, warum 20 % von 50 € genauso viel sind wie 50 % von 20 €.
|
|
99
|
+
points:
|
|
100
|
+
- text: Der Prozentwert ist Grundwert mal Prozentsatz, geteilt durch 100.
|
|
101
|
+
source: /prozent/prozentwert/#regel
|
|
102
|
+
- text: Beim Malnehmen darf man die Zahlen tauschen, darum ist das Ergebnis gleich.
|
|
103
|
+
source: /prozent/prozentwert/#regel
|
|
104
|
+
- text: Ganze Sätze mit einer Begründung (weil …, darum …).
|
|
105
|
+
kind: form
|
|
106
|
+
solution: |
|
|
107
|
+
Den Prozentwert rechnet man mit $W = \frac{G \cdot p}{100}$. Für 20 % von 50 € ist das
|
|
108
|
+
$\frac{50 \cdot 20}{100} = 10$, für 50 % von 20 € ebenso 10. Weil man beim Malnehmen tauschen darf, kommt beide
|
|
109
|
+
Male 10 € heraus.
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Examples
|
|
113
|
+
|
|
114
|
+
- Any exercise: `examples: { accepted, rejected }`, checked by the build, never shown. `generated`: expressions of
|
|
115
|
+
`given` and `answer`, checked per seed (e.g. `reverse(answer)`).
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
# Explainer rules
|
|
2
|
+
|
|
3
|
+
- Quiz-type exercises: the rules of skill `lernapps-content-questions`.
|
|
4
|
+
- The rules for planning pages, figures and numbers: skill `lernapps-explainer-design`.
|
|
5
|
+
|
|
6
|
+
## `explainer-only-content`: Write only the content
|
|
7
|
+
|
|
8
|
+
```rule
|
|
9
|
+
id: explainer-only-content
|
|
10
|
+
scope: archetype:explainer
|
|
11
|
+
severity: error
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
- Write: YAML of app, chapters, pages, data; images in `src/images/`; components with logic and tests in
|
|
15
|
+
`src/components/`.
|
|
16
|
+
- `src/main.ts`, `index.html`, `e2e/explainer.e2e.ts`, config as generated (`keep-the-preset`). Any other file in
|
|
17
|
+
`src/` fails the build.
|
|
18
|
+
- Not expressible → "Where I had to guess", never code around the runtime.
|
|
19
|
+
|
|
20
|
+
## `explainer-content-valid`: The content validates
|
|
21
|
+
|
|
22
|
+
```rule
|
|
23
|
+
id: explainer-content-valid
|
|
24
|
+
scope: archetype:explainer
|
|
25
|
+
severity: error
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
- Schema-valid; ids unique per level; references exist (data file, image, component, page or section link);
|
|
29
|
+
placeholders declared; formulas only from the command list.
|
|
30
|
+
- Build names each problem with file, line, block path.
|
|
31
|
+
|
|
32
|
+
## `explainer-structure-complete`: Every chapter and page listed once
|
|
33
|
+
|
|
34
|
+
```rule
|
|
35
|
+
id: explainer-structure-complete
|
|
36
|
+
scope: archetype:explainer
|
|
37
|
+
severity: error
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- At least one chapter.
|
|
41
|
+
- Every chapter folder in `chapters` of `src/app.yaml`, every page file in `pages` of its `chapter.yaml`, exactly
|
|
42
|
+
once, in reading order.
|
|
43
|
+
|
|
44
|
+
## `explainer-structure-depth`: Chapter, page, two section levels
|
|
45
|
+
|
|
46
|
+
```rule
|
|
47
|
+
id: explainer-structure-depth
|
|
48
|
+
scope: archetype:explainer
|
|
49
|
+
severity: error
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
- Sections at most two deep. No heading levels set: they follow from nesting. More levels = two apps.
|
|
53
|
+
|
|
54
|
+
## `explainer-structure-by-sections`: Structure only by sections
|
|
55
|
+
|
|
56
|
+
```rule
|
|
57
|
+
id: explainer-structure-by-sections
|
|
58
|
+
scope: archetype:explainer
|
|
59
|
+
severity: error
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
- Every heading = a `section` block with `id`, `title`.
|
|
63
|
+
- A Markdown heading (`#`, `##`, underlined) in a text fails the build.
|
|
64
|
+
|
|
65
|
+
## `explainer-stable-ids`: Keep the ids
|
|
66
|
+
|
|
67
|
+
```rule
|
|
68
|
+
id: explainer-stable-ids
|
|
69
|
+
scope: archetype:explainer
|
|
70
|
+
severity: warning
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
- Short, telling ids ("prozentwert", "regel") for chapters, pages, sections, exercises; keep them when texts
|
|
74
|
+
change (entry topics, teachers' links).
|
|
75
|
+
- New content, new ids; never reuse a removed one.
|
|
76
|
+
|
|
77
|
+
## `explainer-navigation`: Every page in one chain
|
|
78
|
+
|
|
79
|
+
```rule
|
|
80
|
+
id: explainer-navigation
|
|
81
|
+
scope: archetype:explainer
|
|
82
|
+
severity: error
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
- Navigation is the runtime's: from the order lists; every page in one chain, reachable from the start page.
|
|
86
|
+
- Prose links: paths from the app root.
|
|
87
|
+
- `lernapps check` finds unreachable pages (`pages-reachable`) and entry topics with a missing `#<section>`
|
|
88
|
+
(`entry-links-resolve`).
|
|
89
|
+
|
|
90
|
+
## `explainer-languages`: German first, every language complete
|
|
91
|
+
|
|
92
|
+
```rule
|
|
93
|
+
id: explainer-languages
|
|
94
|
+
scope: archetype:explainer
|
|
95
|
+
severity: error
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
- `languages`, German first.
|
|
99
|
+
- Every learner text (content, data files, component props) in each, no other. Build fails otherwise.
|
|
100
|
+
|
|
101
|
+
## `explainer-readable-without-js`: Every page readable without JavaScript
|
|
102
|
+
|
|
103
|
+
```rule
|
|
104
|
+
id: explainer-readable-without-js
|
|
105
|
+
scope: archetype:explainer
|
|
106
|
+
severity: error
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
- Every fact in a block, visible without JS. No meaning only inside an exercise or component.
|
|
110
|
+
- Every component a `nojs` text: what it shows, its content.
|
|
111
|
+
- `lernapps check` opens every explainer page again with JS off, bundle and deployed (listing and review too):
|
|
112
|
+
headings, formulas, figures visible, figures named; every "Lösung" and `nojs` text not empty.
|
|
113
|
+
|
|
114
|
+
## `explainer-formulas-static`: Formulas as formulas
|
|
115
|
+
|
|
116
|
+
```rule
|
|
117
|
+
id: explainer-formulas-static
|
|
118
|
+
scope: archetype:explainer
|
|
119
|
+
severity: error
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
- Every formula in a `formula` block or `$…$`; never plain text (`W = G * p / 100`) or a picture. MathML for
|
|
123
|
+
screen readers.
|
|
124
|
+
|
|
125
|
+
## `explainer-generated-sound`: The property test passes
|
|
126
|
+
|
|
127
|
+
```rule
|
|
128
|
+
id: explainer-generated-sound
|
|
129
|
+
scope: archetype:explainer
|
|
130
|
+
severity: error
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
- `given`, `where`: `where` holds often; answer defined and in format for every draw; no mistake can equal it.
|
|
134
|
+
- Failure names the seed: narrow `given` or change the mistake; never the comparator.
|
|
135
|
+
|
|
136
|
+
## `vue-component-props-valid`: Props as the component declares them
|
|
137
|
+
|
|
138
|
+
```rule
|
|
139
|
+
id: vue-component-props-valid
|
|
140
|
+
scope: archetype:explainer
|
|
141
|
+
severity: error
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
- `defineProps<{ … }>()` with types; each block passes exactly the declared props, of their types.
|
|
145
|
+
|
|
146
|
+
## `vue-component-nojs`: Every component block has its `nojs` text
|
|
147
|
+
|
|
148
|
+
```rule
|
|
149
|
+
id: vue-component-nojs
|
|
150
|
+
scope: archetype:explainer
|
|
151
|
+
severity: error
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
- Every non-exercise `vue-component` block: `nojs` in every language (exercise: from `describe`): what it shows
|
|
155
|
+
and does, with content; same meaning without JS.
|
|
156
|
+
- Schema requires it; `lernapps check` finds empty or hidden ones (`explainer-readable-without-js`).
|
|
157
|
+
|
|
158
|
+
## `vue-component-exercise-contract`: An exercise component has its logic module
|
|
159
|
+
|
|
160
|
+
```rule
|
|
161
|
+
id: vue-component-exercise-contract
|
|
162
|
+
scope: archetype:explainer
|
|
163
|
+
severity: error
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
- Emits `answer` → `src/components/<Name>.ts` with pure, deterministic `generate(seed, props)`,
|
|
167
|
+
`check(task, answer)`, `solution(task)`, `wrong(task)`, `reason(task, answer, props, lang)`; property test passes.
|
|
168
|
+
- Emits no `answered` (the old contract): the explainer draws "Prüfen" and checks; a component draws none.
|
|
169
|
+
- Block lists `examples` with at least one `accepted` and one `rejected`, `{ answer, seed? }` in the shape of
|
|
170
|
+
`check` (no seed: seeds 1–50), incl. another correct answer where the text allows.
|
|
171
|
+
- Declares prop `seed: number`, shows `generate(seed, props)`. The explainer gives the seed, shows "Aufgabe Nr.",
|
|
172
|
+
"Neue Aufgabe", keeps `?<id>=<n>`: never draw a seed or offer "next" inside.
|
|
173
|
+
- Also exports `describe(task, props, lang)` → `{ task, solution }` (restricted Markdown, task exactly as shown) and
|
|
174
|
+
`about = { task, answer, correct, mistakes }` (texts by language). The tutor reads only these (the tutor page, no-JS
|
|
175
|
+
text).
|
|
176
|
+
- Any exercise may carry `examples: { accepted, rejected }`; `generated`: expressions of `given` and `answer`, per
|
|
177
|
+
seed (e.g. `reverse(answer)`).
|
|
178
|
+
- With `template`, `params`, `mistakes` and `show` (all four or none, rule `vue-component-tutor-table`): `check`
|
|
179
|
+
accepts the solution and rejects each mistake's answer, `params` gives the same names for every task, each
|
|
180
|
+
placeholder of `template` is one of them.
|
|
181
|
+
|
|
182
|
+
## `vue-component-tutor-table`: An exercise component declares its exercise for the tutor
|
|
183
|
+
|
|
184
|
+
```rule
|
|
185
|
+
id: vue-component-tutor-table
|
|
186
|
+
scope: archetype:explainer
|
|
187
|
+
severity: hint
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
- The logic module also exports `template(props, lang)`: the task once, `{name}` for each input;
|
|
191
|
+
`params(task, props, lang)`: the inputs by name (`{ G: 1260, p: 15 }`), numbers or texts, the same names for every
|
|
192
|
+
task; `mistakes(task, props)`: per `about.mistakes`, in order, the answer it gives, `undefined` where none;
|
|
193
|
+
`show(answer, task, props, lang)`: an answer as text, short ("C", "3 Kästchen").
|
|
194
|
+
- The tutor page then has the task once, the mistakes as F1, F2 … and a table of every number (inputs, solution,
|
|
195
|
+
each mistake), like `generated`. Without them: each number's `describe` as far as 64 kB allow, and the build's hint
|
|
196
|
+
names the block.
|
|
197
|
+
- Example: `src/components/ProzentFeld.ts` of the template.
|
|
198
|
+
|
|
199
|
+
## `vue-component-tested`: A test per component
|
|
200
|
+
|
|
201
|
+
```rule
|
|
202
|
+
id: vue-component-tested
|
|
203
|
+
scope: archetype:explainer
|
|
204
|
+
severity: error
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
- `src/components/<Name>.test.ts` per component, testing its logic: other right answers the text allows, typical
|
|
208
|
+
wrong ones with reasons, edge cases. Run by `lernapps check`.
|
|
209
|
+
|
|
210
|
+
## `vue-component-no-network`: No network
|
|
211
|
+
|
|
212
|
+
```rule
|
|
213
|
+
id: vue-component-no-network
|
|
214
|
+
scope: [archetype:explainer, archetype:interactive]
|
|
215
|
+
severity: error
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
- No request, no URL import: props or bundle.
|
|
219
|
+
|
|
220
|
+
## `vue-component-no-html-injection`: No code or HTML from strings
|
|
221
|
+
|
|
222
|
+
```rule
|
|
223
|
+
id: vue-component-no-html-injection
|
|
224
|
+
scope: [archetype:explainer, archetype:interactive]
|
|
225
|
+
severity: error
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
- No `eval`, `new Function`, `innerHTML`, `v-html`: the template renders.
|
|
229
|
+
|
|
230
|
+
## `vue-component-imports`: Imports only from the allowed packages
|
|
231
|
+
|
|
232
|
+
```rule
|
|
233
|
+
id: vue-component-imports
|
|
234
|
+
scope: [archetype:explainer, archetype:interactive]
|
|
235
|
+
severity: error
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
- Only `vue`, `@lernapps/app-templates`, `@lernapps/tooling/storage`, `i18n`, `a11y`, own folder. Types from
|
|
239
|
+
anywhere.
|
|
240
|
+
- Interactive: also the app's `src/` files and its `dependencies`.
|
|
241
|
+
|
|
242
|
+
## `vue-component-browser-globals`: Browser globals only once mounted
|
|
243
|
+
|
|
244
|
+
```rule
|
|
245
|
+
id: vue-component-browser-globals
|
|
246
|
+
scope: [archetype:explainer, archetype:interactive]
|
|
247
|
+
severity: error
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
- `window`, `document`, `localStorage` etc. only in `onMounted` or later unmount hooks (server render has none).
|
|
251
|
+
|
|
252
|
+
## `vue-component-size`: Within 20 kB
|
|
253
|
+
|
|
254
|
+
```rule
|
|
255
|
+
id: vue-component-size
|
|
256
|
+
scope: archetype:explainer
|
|
257
|
+
severity: error
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
- Own code max 20 kB gzip. More → split, or a new block: write it into the retrospective.
|
|
261
|
+
|
|
262
|
+
## `explainer-open-points`: An open task checks what the material teaches
|
|
263
|
+
|
|
264
|
+
```rule
|
|
265
|
+
id: explainer-open-points
|
|
266
|
+
scope: archetype:explainer
|
|
267
|
+
severity: warning
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
- 2 to 5 points, each one sentence a learner can check with yes or no; together what a good answer needs.
|
|
271
|
+
- A point of the content names its section (`source`), and its content is there; a point of form has `kind: form`.
|
|
272
|
+
- The model answer has every point, in the words of the class.
|
|
273
|
+
|
|
274
|
+
## `explainer-scope-matches`: The scope says what the pages teach
|
|
275
|
+
|
|
276
|
+
```rule
|
|
277
|
+
id: explainer-scope-matches
|
|
278
|
+
scope: archetype:explainer
|
|
279
|
+
severity: warning
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
- `scope` of each page in your own words, never copied from the material; `scope` in `src/app.yaml` with what does not
|
|
283
|
+
belong to it.
|
|
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
|
+
might expect but the material does not teach.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Explainer runtime
|
|
2
|
+
|
|
3
|
+
## Build
|
|
4
|
+
|
|
5
|
+
- Reads, validates, renders every page to static HTML.
|
|
6
|
+
- Problem → one line `file:line: place (language): what to do`, e.g.
|
|
7
|
+
`src/prozent/prozentwert.yaml:12: blocks[0].blocks[1].text: a Markdown heading ("Regel"); use a section block with the title "Regel"`.
|
|
8
|
+
Fix where it points.
|
|
9
|
+
|
|
10
|
+
## Pages
|
|
11
|
+
|
|
12
|
+
- No JS: complete; text, formulas as MathML, figures, tables, navigation, sources. Exercise: task + `<details>`
|
|
13
|
+
"Lösung"; `generated`: three worked tasks; component: its `nojs` text; `open-task`: task, points with "Nachlesen",
|
|
14
|
+
model answer under "Lösung".
|
|
15
|
+
- JS: controls, "Prüfen", feedback with focus. Solution only after an answer; wrong → why, retry or "Lösung". An
|
|
16
|
+
exercise component gets "Prüfen" from the explainer too, under it.
|
|
17
|
+
`generated`: live task, "Neue Aufgabe", deep link `/<chapter>/<page>/?<exercise>=<n>#<exercise>`. Components
|
|
18
|
+
hydrate. `open-task`: "Deine Lösung" (type, or paper and photo), "Vergleichen" shows the points as tick boxes and
|
|
19
|
+
opens the model answer, "Antwort ändern".
|
|
20
|
+
- Video: no JS and before a click, title as a link to youtube.com, channel and a note that playing sends data to
|
|
21
|
+
YouTube/Google; nothing loads from another server. "Video abspielen" loads the player from youtube-nocookie.com.
|
|
22
|
+
- Stores on the device, per app, only the assistant the learner switched on for the tutor, once ticked, "Videos
|
|
23
|
+
immer direkt laden" (every player loads without the click until unticked), and the last result of each
|
|
24
|
+
self-assessment the learner did. While the tab is open (sessionStorage), the answer of each open task: way, text and
|
|
25
|
+
ticks, so that a reload loses nothing; never the photo.
|
|
26
|
+
- Self-assessment (`selfAssessment` per chapter, JS only): a chapter's on its page, the app's (every chapter's) on
|
|
27
|
+
the start page; its exercises without "Prüfen", checked together at "Auswerten"; the result per content page. With
|
|
28
|
+
`via=tutor` it keeps nothing and ends in "Zurück zum Tutor". Skill `lernapps-explainer-self-assessment`.
|
|
29
|
+
|
|
30
|
+
## Addresses
|
|
31
|
+
|
|
32
|
+
- Page `/<chapter>/<page>/`; section `#<section>`, subsection `#<section>/<sub>`; exercise `#<exercise>`.
|
|
33
|
+
- Glossary `/glossar/`. Other languages: same tree under `/<language>/`.
|
|
34
|
+
- Tutor page of a page: `/<chapter>/<page>/tutor/`, its Markdown beside it (`index.md`).
|
|
35
|
+
- Scope (from `scope` in the content): `/<chapter>/<page>/scope/` (the page's), `/<chapter>/scope/` (the chapter's),
|
|
36
|
+
`/scope/` (the whole); for the tutor only, linked from the tutor page. `scope` is no chapter id.
|
|
37
|
+
- Self-assessment: `/einschaetzung/` (the app's), `/<chapter>/einschaetzung/` (a chapter's), each with its tutor page
|
|
38
|
+
`tutor/`. `einschaetzung` is no chapter or page id.
|
|
39
|
+
- Unknown address → 404 page to the start page. Moving or removing a page: change the entry topics in the same
|
|
40
|
+
increment.
|
|
41
|
+
|
|
42
|
+
## Tests
|
|
43
|
+
|
|
44
|
+
- From the runtime (`e2e/explainer.e2e.ts`, `explainerTests()`), every page, every language, run by
|
|
45
|
+
`lernapps check`.
|
|
46
|
+
- No JS: headings vs nesting, sections, formulas, figures, anchors, navigation chain, languages, every solution,
|
|
47
|
+
every `nojs` text.
|
|
48
|
+
- JS (German, 360 px): one exercise per type and page accepts its answer, rejects a wrong one; components hydrate
|
|
49
|
+
without error; a video sends no request to another server before its click, and loads its player after it; each
|
|
50
|
+
self-assessment: a run with every answer right, one without an answer (kept, to the tutor, deleted), and one a tutor
|
|
51
|
+
opened.
|
|
52
|
+
- `lernapps check` also opens every page without JS (`explainer-readable-without-js`), bundle and deployed app.
|
|
53
|
+
|
|
54
|
+
## A runtime test fails
|
|
55
|
+
|
|
56
|
+
- Content validates (build passes) but a test fails = template bug: act as skill `lernapps-cli` says; go on with
|
|
57
|
+
content.
|
|
58
|
+
- Never change the runtime in `node_modules` (`explainer-only-content`). The fix arrives with `lernapps update`.
|
|
59
|
+
|
|
60
|
+
## Tutor
|
|
61
|
+
|
|
62
|
+
- Optional: the learner's own KI assistant helps with a page and with each exercise. The build writes a tutor page
|
|
63
|
+
per page from the content; the app sends nothing.
|
|
64
|
+
- The tutor reads the page's blocks as text (figures by `alt` and `caption`, a video as a link with title and channel)
|
|
65
|
+
and every exercise: task, answer format, solution, `hint` and typical mistakes with their messages. `generated`: template, `steps`, `mistakes` and each task
|
|
66
|
+
number's values; question: options with `info`, `explanation`; component: `about`, and with `template`, `params`, `mistakes`, `show`
|
|
67
|
+
the same table as `generated` (else `describe` per task number, as far as 64 kB allow).
|
|
68
|
+
`open-task`: task, points as P1, P2 … with their sources, model answer. It neither calculates nor invents: these
|
|
69
|
+
must be complete and right.
|
|
70
|
+
- The scope: the whole learning path in your own words, from the material, as continuous prose with the exact
|
|
71
|
+
content (facts, terms, reasons, examples), beside what it describes. `scope` in each page's YAML: what the page
|
|
72
|
+
teaches; optionally `scope` in `chapter.yaml`; and in `src/app.yaml`
|
|
73
|
+
`scope: { text, out: [...] }`: what the app is about and where its weight lies, and what does not belong to it (what
|
|
74
|
+
belongs to it is what the pages say). With
|
|
75
|
+
the app's scope the build requires a scope on every page. The tutor judges open answers by it, never from general
|
|
76
|
+
knowledge.
|
|
77
|
+
- `src/app.yaml`: `tutor.rules`, the app's rules for the tutor; never send it to another address.
|
|
78
|
+
- Task numbers 1 to N (max 67) are what learner, tutor and address (`?<id>=<n>`) share; changing a generator changes
|
|
79
|
+
what a number points to. `seed`, `via`, `uebung`, `abschnitt` are no exercise ids.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Structure
|
|
2
|
+
|
|
3
|
+
| Level | File | Holds |
|
|
4
|
+
|---|---|---|
|
|
5
|
+
| App | `src/app.yaml` | `title`, `intro`, `languages`, order of `chapters`, `sources` |
|
|
6
|
+
| Chapter | `src/<chapter>/chapter.yaml` | `title`, `summary`, order of its `pages` |
|
|
7
|
+
| Page | `src/<chapter>/<page>.yaml` | `title`, `description`, `blocks` |
|
|
8
|
+
| Section | `section` block in a page | `id`, `title`, `blocks`; one more section level inside |
|
|
9
|
+
| Scope | `scope` in app, chapter and page (optional) | the whole learning path for the tutor, beside each page (`runtime.md`) |
|
|
10
|
+
|
|
11
|
+
- Chapters mandatory. One chapter: hidden in navigation; addresses keep it (`/<chapter>/<page>/`).
|
|
12
|
+
- Order lists complete: `chapters`, `pages` list every folder or file exactly once. No sorting by name.
|
|
13
|
+
- Ids: lowercase, digits, hyphens; unique per level; stable once published. Not chapter ids: `data`, `images`,
|
|
14
|
+
`glossar`, `components`, `assets`, `scope`.
|
|
15
|
+
- Depth: page, section, subsection; headings follow from nesting (h1–h3). Boxes (`example`, `note`) take the next
|
|
16
|
+
level, hold no sections. More levels = two apps.
|
|
17
|
+
- Navigation generated: contents on start and chapter pages, breadcrumbs, previous/next across chapters, page
|
|
18
|
+
contents from three sections, glossary from `definition` blocks.
|
|
19
|
+
|
|
20
|
+
## Entry topics
|
|
21
|
+
|
|
22
|
+
- Path of a page or section relative to the app's `url`: `prozent/prozentwert/`, `prozent/prozentwert/#regel`
|
|
23
|
+
(`entry-links-resolve`; `lernapps check --entry` checks the section).
|
|
24
|
+
- From the stable ids (`explainer-stable-ids`).
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lernapps-explainer-design
|
|
3
|
+
description: Use in Explore right after choosing the `explainer` archetype - how the content of a lernapps.net explainer maps onto chapters, pages, blocks and exercises, which the plan then applies.
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Designing an explainer
|
|
8
|
+
|
|
9
|
+
## What an explainer is
|
|
10
|
+
|
|
11
|
+
- Chapters of pages that explain a topic, with exercises on every page. Every page is complete without JavaScript;
|
|
12
|
+
with it, exercises check answers and give feedback.
|
|
13
|
+
- Generated around your content: navigation, contents, glossary, a tutor prompt per page, other languages.
|
|
14
|
+
- You will write the content as YAML; a Vue component only where no block fits.
|
|
15
|
+
|
|
16
|
+
## Can and cannot
|
|
17
|
+
|
|
18
|
+
- The README of the template says it: <https://raw.githubusercontent.com/lernapps/app-templates/main/explainer/README.md>.
|
|
19
|
+
Read it before this skill's mapping, if you have not yet.
|
|
20
|
+
|
|
21
|
+
## Map the content
|
|
22
|
+
|
|
23
|
+
- A chapter of competencies → an app chapter. A competency → a page or a section.
|
|
24
|
+
- Depth: chapter, page, section, subsection. Need more levels → two apps.
|
|
25
|
+
- The content structure → the same sections on every page, with fixed ids and titles (e.g. "Regel", "Beispiel",
|
|
26
|
+
"Übung").
|
|
27
|
+
- Shared material → a data file (`src/data/<name>.yaml`), each fact once.
|
|
28
|
+
- A pattern → a block or an exercise with fixed parts, varied in its drawn values or the records it picks.
|
|
29
|
+
- One chapter only: it is hidden in the navigation; its pages keep their addresses.
|
|
30
|
+
|
|
31
|
+
## Blocks
|
|
32
|
+
|
|
33
|
+
- Text: prose (restricted Markdown), `definition` (also in the glossary), `example` and `note` boxes, `quote`,
|
|
34
|
+
`code`.
|
|
35
|
+
- 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
|
|
37
|
+
`labelled-figure` with numbered markers, a `table`, a `timeline`.
|
|
38
|
+
- Exercises: the six question types of the quiz; `generated` (a new task per seed, with drawn values, typical
|
|
39
|
+
mistakes, hint and steps); a component.
|
|
40
|
+
- Something no block can show or check → a component: read a value off a scale, label a figure, drag into place,
|
|
41
|
+
build a circuit. Logic in its own module, tested; a text for readers without JavaScript.
|
|
42
|
+
- Wanted, not only allowed: all that makes the content attractive - figures, drawings, labelled figures, tables,
|
|
43
|
+
timelines, videos; a component where no block fits.
|
|
44
|
+
|
|
45
|
+
## Decide
|
|
46
|
+
|
|
47
|
+
- Languages: German; English as a second language is supported. Each declared language complete.
|
|
48
|
+
- The tutor: the build writes it from the content. Plan only app-specific tutor rules, if any.
|
|
49
|
+
|
|
50
|
+
## Gotchas
|
|
51
|
+
|
|
52
|
+
- Each component costs a module, a test and a no-JS text: plan one only where it teaches what no block can.
|
|
53
|
+
|
|
54
|
+
## Read when
|
|
55
|
+
|
|
56
|
+
- [references/rules.md](references/rules.md): before you write "Content method"; it holds the rules for pages,
|
|
57
|
+
figures, numbers, mistakes and components.
|