@lernapps/tooling 0.0.0-stage → 0.1.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 (126) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +67 -2
  3. package/archetypes/shared/a11y.ts +38 -0
  4. package/archetypes/shared/hooks/pre-commit +22 -0
  5. package/archetypes/shared/hooks/pre-push +4 -0
  6. package/archetypes/shared/i18n.ts +25 -0
  7. package/archetypes/shared/install-hooks.ts +63 -0
  8. package/archetypes/shared/playwright.ts +54 -0
  9. package/archetypes/shared/preset.ts +81 -0
  10. package/archetypes/shared/storage.ts +167 -0
  11. package/archetypes/shared/tsconfig.json +27 -0
  12. package/check/validation-report.v1.schema.json +116 -0
  13. package/dist/a11y.mjs +30 -0
  14. package/dist/check/rules/accessible.mjs +18 -0
  15. package/dist/check/rules/app-content-guide.mjs +69 -0
  16. package/dist/check/rules/dependency-licence.mjs +82 -0
  17. package/dist/check/rules/entry-fitness-matches.mjs +22 -0
  18. package/dist/check/rules/entry-links-resolve.mjs +44 -0
  19. package/dist/check/rules/explainer-readable-without-js.mjs +66 -0
  20. package/dist/check/rules/links-resolve.mjs +10 -0
  21. package/dist/check/rules/lockfile-matches.mjs +57 -0
  22. package/dist/check/rules/no-request-before-click.mjs +17 -0
  23. package/dist/check/rules/no-tracking.mjs +17 -0
  24. package/dist/check/rules/own-data-only.mjs +66 -0
  25. package/dist/check/rules/pages-reachable.mjs +25 -0
  26. package/dist/check/rules/usable-at-360px.mjs +19 -0
  27. package/dist/check-cv4I6CCH.mjs +28 -0
  28. package/dist/cli.mjs +3118 -0
  29. package/dist/i18n.mjs +11 -0
  30. package/dist/lint.mjs +528 -0
  31. package/dist/plan-DWKayEnY.mjs +75 -0
  32. package/dist/playwright.mjs +47 -0
  33. package/dist/preset.mjs +60 -0
  34. package/dist/skills/lernapps-app/SKILL.md +49 -0
  35. package/dist/skills/lernapps-cli/SKILL.md +156 -0
  36. package/dist/skills/lernapps-cli/references/platform-rules.md +258 -0
  37. package/dist/skills/lernapps-commit/SKILL.md +39 -0
  38. package/dist/skills/lernapps-content-growth/SKILL.md +33 -0
  39. package/dist/skills/lernapps-content-questions/SKILL.md +133 -0
  40. package/dist/skills/lernapps-content-questions/references/question-types.md +69 -0
  41. package/dist/skills/lernapps-content-research/SKILL.md +50 -0
  42. package/dist/skills/lernapps-content-research/references/sources.md +16 -0
  43. package/dist/skills/lernapps-exercise-patterns/SKILL.md +74 -0
  44. package/dist/skills/lernapps-exercise-patterns/references/biology-geography.md +13 -0
  45. package/dist/skills/lernapps-exercise-patterns/references/chemistry.md +16 -0
  46. package/dist/skills/lernapps-exercise-patterns/references/computer-science.md +13 -0
  47. package/dist/skills/lernapps-exercise-patterns/references/figures.md +10 -0
  48. package/dist/skills/lernapps-exercise-patterns/references/history.md +13 -0
  49. package/dist/skills/lernapps-exercise-patterns/references/mathematics.md +19 -0
  50. package/dist/skills/lernapps-exercise-patterns/references/page-structures.md +8 -0
  51. package/dist/skills/lernapps-exercise-patterns/references/physics.md +18 -0
  52. package/dist/skills/lernapps-exercise-patterns/references/success.md +28 -0
  53. package/dist/skills/lernapps-explainer/SKILL.md +33 -0
  54. package/dist/skills/lernapps-explainer/references/blocks.md +83 -0
  55. package/dist/skills/lernapps-explainer/references/components.md +66 -0
  56. package/dist/skills/lernapps-explainer/references/exercises.md +115 -0
  57. package/dist/skills/lernapps-explainer/references/rules.md +285 -0
  58. package/dist/skills/lernapps-explainer/references/runtime.md +79 -0
  59. package/dist/skills/lernapps-explainer/references/structure.md +24 -0
  60. package/dist/skills/lernapps-explainer-design/SKILL.md +57 -0
  61. package/dist/skills/lernapps-explainer-design/references/rules.md +75 -0
  62. package/dist/skills/lernapps-explainer-self-assessment/SKILL.md +93 -0
  63. package/dist/skills/lernapps-github/SKILL.md +102 -0
  64. package/dist/skills/lernapps-interactive/SKILL.md +141 -0
  65. package/dist/skills/lernapps-interactive/references/frame.md +30 -0
  66. package/dist/skills/lernapps-interactive-design/SKILL.md +58 -0
  67. package/dist/skills/lernapps-listing/SKILL.md +44 -0
  68. package/dist/skills/lernapps-listing/references/keeping-updated.md +33 -0
  69. package/dist/skills/lernapps-quiz/SKILL.md +100 -0
  70. package/dist/skills/lernapps-quiz-design/SKILL.md +48 -0
  71. package/dist/status-CXoQBgfU.mjs +76 -0
  72. package/dist/storage.mjs +113 -0
  73. package/generator/app/.agents/skills/app-content/SKILL.md +63 -0
  74. package/generator/app/.github/ISSUE_TEMPLATE/inhaltsfehler.yml +25 -0
  75. package/generator/app/.github/workflows/pages.yml +61 -0
  76. package/generator/app/LICENSE +21 -0
  77. package/generator/app/renovate.json +4 -0
  78. package/generator/architecture.ts +212 -0
  79. package/generator/create.ts +462 -0
  80. package/generator/init.ts +186 -0
  81. package/generator/update.ts +429 -0
  82. package/guidance/AGENTS.md +26 -0
  83. package/guidance/increment-template.md +194 -0
  84. package/guidance/plan-front-matter.v1.schema.json +36 -0
  85. package/guidance/plan-template.md +233 -0
  86. package/guidance/subagent-brief.md +25 -0
  87. package/guidance/workflow-rules.md +109 -0
  88. package/package.json +135 -4
  89. package/review/verdict.v1.schema.json +134 -0
  90. package/skills/archetypes/lernapps-explainer/SKILL.md +33 -0
  91. package/skills/archetypes/lernapps-explainer/references/blocks.md +83 -0
  92. package/skills/archetypes/lernapps-explainer/references/components.md +66 -0
  93. package/skills/archetypes/lernapps-explainer/references/exercises.md +115 -0
  94. package/skills/archetypes/lernapps-explainer/references/rules.md +285 -0
  95. package/skills/archetypes/lernapps-explainer/references/runtime.md +79 -0
  96. package/skills/archetypes/lernapps-explainer/references/structure.md +24 -0
  97. package/skills/archetypes/lernapps-explainer-design/SKILL.md +57 -0
  98. package/skills/archetypes/lernapps-explainer-design/references/rules.md +75 -0
  99. package/skills/archetypes/lernapps-explainer-self-assessment/SKILL.md +93 -0
  100. package/skills/archetypes/lernapps-interactive/SKILL.md +141 -0
  101. package/skills/archetypes/lernapps-interactive/references/frame.md +30 -0
  102. package/skills/archetypes/lernapps-interactive-design/SKILL.md +58 -0
  103. package/skills/archetypes/lernapps-quiz/SKILL.md +100 -0
  104. package/skills/archetypes/lernapps-quiz-design/SKILL.md +48 -0
  105. package/skills/content/lernapps-content-questions/SKILL.md +133 -0
  106. package/skills/content/lernapps-content-questions/references/question-types.md +69 -0
  107. package/skills/content/lernapps-content-research/SKILL.md +50 -0
  108. package/skills/content/lernapps-content-research/references/sources.md +16 -0
  109. package/skills/content/lernapps-exercise-patterns/SKILL.md +74 -0
  110. package/skills/content/lernapps-exercise-patterns/references/biology-geography.md +13 -0
  111. package/skills/content/lernapps-exercise-patterns/references/chemistry.md +16 -0
  112. package/skills/content/lernapps-exercise-patterns/references/computer-science.md +13 -0
  113. package/skills/content/lernapps-exercise-patterns/references/figures.md +10 -0
  114. package/skills/content/lernapps-exercise-patterns/references/history.md +13 -0
  115. package/skills/content/lernapps-exercise-patterns/references/mathematics.md +19 -0
  116. package/skills/content/lernapps-exercise-patterns/references/page-structures.md +8 -0
  117. package/skills/content/lernapps-exercise-patterns/references/physics.md +18 -0
  118. package/skills/content/lernapps-exercise-patterns/references/success.md +28 -0
  119. package/skills/process/lernapps-app/SKILL.md +49 -0
  120. package/skills/process/lernapps-commit/SKILL.md +39 -0
  121. package/skills/process/lernapps-content-growth/SKILL.md +33 -0
  122. package/skills/process/lernapps-github/SKILL.md +102 -0
  123. package/skills/process/lernapps-listing/SKILL.md +44 -0
  124. package/skills/process/lernapps-listing/references/keeping-updated.md +33 -0
  125. package/skills/tooling/lernapps-cli/SKILL.md +156 -0
  126. 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.