@lernapps/app-templates 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.
Files changed (232) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +195 -2
  3. package/dist/appname-Bh23cUuz.mjs +12 -0
  4. package/dist/assessment-Dy359O1O.mjs +1696 -0
  5. package/dist/block-BK_ohsH1.mjs +82 -0
  6. package/dist/blocks-CxtCPAa5.mjs +149 -0
  7. package/dist/blocks.mjs +4 -0
  8. package/dist/check-CteIFN96.mjs +254 -0
  9. package/dist/content-DO6oEJxj.mjs +140 -0
  10. package/dist/contract-PONpxJtd.mjs +52 -0
  11. package/dist/e2e-CU0n_unV.mjs +89 -0
  12. package/dist/engine-CArxqizW.mjs +136 -0
  13. package/dist/explainer-e2e.mjs +1311 -0
  14. package/dist/explainer-islands.mjs +197 -0
  15. package/dist/explainer-plugin.mjs +2132 -0
  16. package/dist/explainer.mjs +1246 -0
  17. package/dist/html-mffkHGHh.mjs +89 -0
  18. package/dist/image-CspJVfyQ.mjs +21 -0
  19. package/dist/interactive-e2e.mjs +140 -0
  20. package/dist/interactive-plugin.mjs +242 -0
  21. package/dist/interactive.mjs +75 -0
  22. package/dist/load-RosvaPZf.mjs +16976 -0
  23. package/dist/needs-BzEahJ6X.mjs +97 -0
  24. package/dist/pages-9RaaomoO.mjs +81 -0
  25. package/dist/questions-e2e.mjs +1 -0
  26. package/dist/questions-engine.mjs +3 -0
  27. package/dist/questions.mjs +6 -0
  28. package/dist/quiz-DnW-xtCh.mjs +70 -0
  29. package/dist/quiz-e2e.mjs +174 -0
  30. package/dist/quiz-plugin.mjs +266 -0
  31. package/dist/quiz.mjs +246 -0
  32. package/dist/render-D5kwkNt1.mjs +507 -0
  33. package/dist/render-DqHOJyHK.mjs +146 -0
  34. package/dist/render-GS1Htlu8.mjs +545 -0
  35. package/dist/result-D_mmCVwN.mjs +180 -0
  36. package/dist/tasks-DADUYy52.mjs +74 -0
  37. package/dist/validate-BDwL7Su3.mjs +1076 -0
  38. package/dist/validate-Bxy9NfB8.mjs +146 -0
  39. package/dist/vue-DO1m6q1R.mjs +40 -0
  40. package/dist/vue-code-BkJjW_I_.mjs +320 -0
  41. package/docs/arc42/01-introduction-and-goals.arc42.md +82 -0
  42. package/docs/arc42/02-constraints.arc42.md +228 -0
  43. package/docs/arc42/03-system-context.arc42.md +118 -0
  44. package/docs/arc42/04-solution-strategy.arc42.md +47 -0
  45. package/docs/arc42/05-building-blocks.arc42.md +307 -0
  46. package/docs/arc42/06-runtime-view.arc42.md +98 -0
  47. package/docs/arc42/07-deployment-view.arc42.md +160 -0
  48. package/docs/arc42/08-concepts.arc42.md +123 -0
  49. package/docs/arc42/09-decisions.arc42.md +246 -0
  50. package/docs/arc42/10-quality-requirements.arc42.md +155 -0
  51. package/docs/arc42/11-risks.arc42.md +113 -0
  52. package/docs/arc42/12-glossary.arc42.md +162 -0
  53. package/docs/arc42/architecture-evidence.md +395 -0
  54. package/explainer/.arc42ignore +3 -0
  55. package/explainer/README.md +54 -0
  56. package/explainer/_gitignore +5 -0
  57. package/explainer/docs/arc42/03-explainer-system-context.arc42.md +35 -0
  58. package/explainer/docs/arc42/05-explainer-building-blocks.arc42.md +659 -0
  59. package/explainer/docs/arc42/06-explainer-runtime-view.arc42.md +105 -0
  60. package/explainer/docs/arc42/07-explainer-deployment-view.arc42.md +34 -0
  61. package/explainer/docs/arc42/08-explainer-concepts.arc42.md +17 -0
  62. package/explainer/docs/arc42/09-explainer-decisions.arc42.md +443 -0
  63. package/explainer/docs/arc42/10-explainer-quality-requirements.arc42.md +54 -0
  64. package/explainer/docs/arc42/11-explainer-risks.arc42.md +17 -0
  65. package/explainer/docs/arc42/12-explainer-glossary.arc42.md +185 -0
  66. package/explainer/e2e/explainer.e2e.ts +3 -0
  67. package/explainer/index.html +11 -0
  68. package/explainer/package.json +18 -0
  69. package/explainer/playwright.config.ts +1 -0
  70. package/explainer/src/app.yaml +20 -0
  71. package/explainer/src/components/ProzentFeld.test.ts +30 -0
  72. package/explainer/src/components/ProzentFeld.ts +121 -0
  73. package/explainer/src/components/ProzentFeld.vue +84 -0
  74. package/explainer/src/images/hunderterfeld-klammern.svg +105 -0
  75. package/explainer/src/main.ts +4 -0
  76. package/explainer/src/prozent/chapter.yaml +18 -0
  77. package/explainer/src/prozent/grundwert.yaml +115 -0
  78. package/explainer/src/prozent/prozentsatz.yaml +99 -0
  79. package/explainer/src/prozent/prozentwert.yaml +119 -0
  80. package/explainer/tsconfig.json +1 -0
  81. package/explainer/vite.config.ts +4 -0
  82. package/interactive/.arc42ignore +5 -0
  83. package/interactive/README.md +28 -0
  84. package/interactive/_gitignore +4 -0
  85. package/interactive/docs/arc42/05-interactive-building-blocks.arc42.md +518 -0
  86. package/interactive/docs/arc42/06-interactive-runtime-view.arc42.md +58 -0
  87. package/interactive/docs/arc42/07-interactive-deployment-view.arc42.md +35 -0
  88. package/interactive/docs/arc42/08-interactive-concepts.arc42.md +52 -0
  89. package/interactive/docs/arc42/09-interactive-decisions.arc42.md +130 -0
  90. package/interactive/docs/arc42/10-interactive-quality-requirements.arc42.md +45 -0
  91. package/interactive/docs/arc42/11-interactive-risks.arc42.md +20 -0
  92. package/interactive/docs/arc42/12-interactive-glossary.arc42.md +80 -0
  93. package/interactive/e2e/interactive.e2e.ts +3 -0
  94. package/interactive/index.html +11 -0
  95. package/interactive/package.json +18 -0
  96. package/interactive/playwright.config.ts +1 -0
  97. package/interactive/src/app.yaml +28 -0
  98. package/interactive/src/core/index.ts +101 -0
  99. package/interactive/src/data/lessons.json +4 -0
  100. package/interactive/src/env.d.ts +6 -0
  101. package/interactive/src/main.ts +6 -0
  102. package/interactive/src/messages/de.json +12 -0
  103. package/interactive/src/playthroughs/grundreihe.yaml +33 -0
  104. package/interactive/src/playthroughs/obere-reihe.yaml +14 -0
  105. package/interactive/src/ui/App.vue +85 -0
  106. package/interactive/tsconfig.json +1 -0
  107. package/interactive/vite.config.ts +4 -0
  108. package/package.json +143 -4
  109. package/quiz/README.md +31 -0
  110. package/quiz/_gitignore +5 -0
  111. package/quiz/docs/arc42/05-quiz-building-blocks.arc42.md +283 -0
  112. package/quiz/docs/arc42/06-quiz-runtime-view.arc42.md +21 -0
  113. package/quiz/docs/arc42/07-quiz-deployment-view.arc42.md +33 -0
  114. package/quiz/docs/arc42/09-quiz-decisions.arc42.md +36 -0
  115. package/quiz/docs/arc42/10-quiz-quality-requirements.arc42.md +53 -0
  116. package/quiz/docs/arc42/11-quiz-risks.arc42.md +19 -0
  117. package/quiz/docs/arc42/12-quiz-glossary.arc42.md +27 -0
  118. package/quiz/e2e/quiz.e2e.ts +3 -0
  119. package/quiz/index.html +11 -0
  120. package/quiz/package.json +18 -0
  121. package/quiz/playwright.config.ts +1 -0
  122. package/quiz/src/images/berge-ordnen.jpg +0 -0
  123. package/quiz/src/images/everest-hoehe.jpg +0 -0
  124. package/quiz/src/images/galapagos.jpg +0 -0
  125. package/quiz/src/images/grand-canyon.jpg +0 -0
  126. package/quiz/src/images/totes-meer.jpg +0 -0
  127. package/quiz/src/images/uluru.jpg +0 -0
  128. package/quiz/src/images/wasserfaelle-laender.jpg +0 -0
  129. package/quiz/src/main.ts +4 -0
  130. package/quiz/src/quiz.json +312 -0
  131. package/quiz/tsconfig.json +1 -0
  132. package/quiz/vite.config.ts +4 -0
  133. package/runtime/explainer/assessment-client.ts +478 -0
  134. package/runtime/explainer/assessment-page.ts +448 -0
  135. package/runtime/explainer/assessment.ts +393 -0
  136. package/runtime/explainer/check-page.ts +520 -0
  137. package/runtime/explainer/components/exercise.ts +409 -0
  138. package/runtime/explainer/components/props.ts +230 -0
  139. package/runtime/explainer/components/read.ts +428 -0
  140. package/runtime/explainer/content.ts +277 -0
  141. package/runtime/explainer/e2e.ts +1488 -0
  142. package/runtime/explainer/exercises.ts +326 -0
  143. package/runtime/explainer/explainer.css +1283 -0
  144. package/runtime/explainer/explainer.ts +838 -0
  145. package/runtime/explainer/explainer.v1.schema.json +1165 -0
  146. package/runtime/explainer/expression.ts +407 -0
  147. package/runtime/explainer/fraction.ts +107 -0
  148. package/runtime/explainer/generated/comparator.ts +31 -0
  149. package/runtime/explainer/generated/question.ts +165 -0
  150. package/runtime/explainer/generated/soundness.ts +280 -0
  151. package/runtime/explainer/generated/texts.ts +257 -0
  152. package/runtime/explainer/generated/types.ts +103 -0
  153. package/runtime/explainer/generated/values.ts +136 -0
  154. package/runtime/explainer/islands.ts +281 -0
  155. package/runtime/explainer/load.ts +457 -0
  156. package/runtime/explainer/messages/de.json +200 -0
  157. package/runtime/explainer/messages/en.json +200 -0
  158. package/runtime/explainer/messages.ts +13 -0
  159. package/runtime/explainer/needs.ts +122 -0
  160. package/runtime/explainer/open-task.ts +235 -0
  161. package/runtime/explainer/outline.ts +306 -0
  162. package/runtime/explainer/plugin.ts +488 -0
  163. package/runtime/explainer/position.ts +62 -0
  164. package/runtime/explainer/prompt.ts +75 -0
  165. package/runtime/explainer/render.ts +686 -0
  166. package/runtime/explainer/result.ts +238 -0
  167. package/runtime/explainer/routes.ts +119 -0
  168. package/runtime/explainer/schema.ts +140 -0
  169. package/runtime/explainer/scope.ts +87 -0
  170. package/runtime/explainer/stuck.ts +57 -0
  171. package/runtime/explainer/tasks.ts +109 -0
  172. package/runtime/explainer/tutor.ts +469 -0
  173. package/runtime/explainer/tutorpage.ts +669 -0
  174. package/runtime/explainer/video.ts +123 -0
  175. package/runtime/interactive/contract.ts +121 -0
  176. package/runtime/interactive/e2e.ts +190 -0
  177. package/runtime/interactive/interactive.css +82 -0
  178. package/runtime/interactive/interactive.ts +115 -0
  179. package/runtime/interactive/interactive.v1.schema.json +146 -0
  180. package/runtime/interactive/load.ts +188 -0
  181. package/runtime/interactive/messages/de.json +14 -0
  182. package/runtime/interactive/messages/en.json +14 -0
  183. package/runtime/interactive/messages.ts +11 -0
  184. package/runtime/interactive/playthrough.v1.schema.json +143 -0
  185. package/runtime/interactive/plugin.ts +266 -0
  186. package/runtime/interactive/render.ts +126 -0
  187. package/runtime/quiz/e2e.ts +233 -0
  188. package/runtime/quiz/engine.ts +242 -0
  189. package/runtime/quiz/messages/de.json +23 -0
  190. package/runtime/quiz/messages/en.json +23 -0
  191. package/runtime/quiz/plugin.ts +150 -0
  192. package/runtime/quiz/quiz.css +154 -0
  193. package/runtime/quiz/quiz.ts +56 -0
  194. package/runtime/quiz/quiz.v1.schema.json +72 -0
  195. package/runtime/quiz/render.ts +173 -0
  196. package/runtime/quiz/validate.ts +108 -0
  197. package/runtime/shared/appname.ts +16 -0
  198. package/runtime/shared/blocks/block.ts +162 -0
  199. package/runtime/shared/blocks/blocks.css +190 -0
  200. package/runtime/shared/blocks/blocks.v1.schema.json +432 -0
  201. package/runtime/shared/blocks/code.ts +26 -0
  202. package/runtime/shared/blocks/figures.ts +49 -0
  203. package/runtime/shared/blocks/image.ts +25 -0
  204. package/runtime/shared/blocks/index.ts +13 -0
  205. package/runtime/shared/blocks/messages/de.json +4 -0
  206. package/runtime/shared/blocks/messages/en.json +4 -0
  207. package/runtime/shared/blocks/render.ts +134 -0
  208. package/runtime/shared/blocks/validate.ts +165 -0
  209. package/runtime/shared/content.ts +197 -0
  210. package/runtime/shared/examples.ts +20 -0
  211. package/runtime/shared/html.ts +10 -0
  212. package/runtime/shared/languages.ts +94 -0
  213. package/runtime/shared/page.css +101 -0
  214. package/runtime/shared/pages.ts +114 -0
  215. package/runtime/shared/questions/e2e.ts +115 -0
  216. package/runtime/shared/questions/engine.ts +209 -0
  217. package/runtime/shared/questions/index.ts +39 -0
  218. package/runtime/shared/questions/messages/de.json +26 -0
  219. package/runtime/shared/questions/messages/en.json +26 -0
  220. package/runtime/shared/questions/question.ts +433 -0
  221. package/runtime/shared/questions/question.v1.schema.json +346 -0
  222. package/runtime/shared/questions/questions.css +192 -0
  223. package/runtime/shared/questions/render.ts +255 -0
  224. package/runtime/shared/questions/validate.ts +518 -0
  225. package/runtime/shared/schema.ts +44 -0
  226. package/runtime/shared/text/check.ts +68 -0
  227. package/runtime/shared/text/formula.ts +69 -0
  228. package/runtime/shared/text/markdown.ts +185 -0
  229. package/runtime/shared/text/speech.ts +39 -0
  230. package/runtime/shared/text/text.v1.schema.json +34 -0
  231. package/runtime/shared/vue-code.ts +395 -0
  232. package/runtime/shared/vue.ts +38 -0
@@ -0,0 +1,105 @@
1
+ # Runtime View
2
+
3
+ The explainer's own scenarios, besides the generic ones.
4
+
5
+ ## Practising on a page
6
+
7
+ The learner opens a page, possibly with `?<id>=<n>` for the task number of an exercise. Without JavaScript the page
8
+ is complete: the text, formulas, figures, each exercise with its solution in a `<details>` "Lösung", a generated one
9
+ with three worked tasks, each component's `nojs` text. With JavaScript `startExplainer()` makes the exercises live: a
10
+ quiz-type exercise gets the quiz's controls and checker, a generated exercise a live task from its task number and
11
+ "Neue Aufgabe", which writes the next number into the address; the islands hydrate their components. A wrong answer
12
+ gets its reason, the solution shows after the right answer or "Lösung", the feedback in view with the focus.
13
+ The worked tasks stay hidden with JavaScript; the sentence above the live
14
+ task is the exercise's `practice`, else a neutral default.
15
+
16
+ ```arc42
17
+ :::runtime-scenario
18
+ id: scenario-explainer-practise
19
+ title: Practising on a page of the explainer
20
+ trigger: The learner opens a page or answers one of its exercises
21
+ involves: bb-render, bb-shell, bb-islands, bb-generated-exercises, bb-questions, bb-task-numbers, bb-explainer-content, bb-explainer-data, bb-explainer-components, bb-runtime-texts, bb-tooling
22
+ :::
23
+ ```
24
+
25
+ ## Building the explainer
26
+
27
+ The `explainer()` plugin validates the content, its data files and its components, runs the component rules and the
28
+ property test of each generated exercise and exercise component over fixed seeds, and writes the pages and, where the
29
+ tutor is on, the tutor page of each page from its content, each exercise's tasks by number and a component's
30
+ `describe` and `about`; it warns when a component's numbers do not all fit on its tutor page.
31
+
32
+ ```arc42
33
+ :::runtime-scenario
34
+ id: scenario-explainer-build
35
+ title: Building the explainer
36
+ trigger: The creator, a git hook or CI runs the build
37
+ involves: bb-archetype-runtime, bb-build, bb-validation, bb-component-rules, bb-generated-exercises, bb-tutor-handoff, bb-task-numbers, bb-questions, bb-blocks, bb-explainer-content, bb-explainer-data, bb-explainer-components
38
+ :::
39
+ ```
40
+
41
+ ## Asking the tutor
42
+
43
+ The learner switches an assistant on once, on the start page or beside the title of a page; the choice stays on the
44
+ device. Way A: the bar beside the title of a page opens the learner's own assistant with one sentence that names the
45
+ page's tutor page with the page's task numbers, or copies that sentence with the tutor page's Markdown. The assistant
46
+ fetches the tutor page, looks the numbers up in its tables, asks what the child does not understand, explains with
47
+ the page's own example and sends links to tasks of the page with `via=tutor`. On such a page the learner solves the
48
+ task and presses "Zurück zum Tutor": the page copies the tutor page with the page's task numbers and how each task of
49
+ the session went (the try, the input, the app's message), the button says where to paste them, and the learner switches back and pastes them into the
50
+ chat; the assistant knows the task's solution and
51
+ mistakes from the tutor page and talks about the mistake. Way B: stuck at a task, the learner clicks "Hilfe"
52
+ ("Warum?" after a wrong answer) in the exercise. Without a chat the assistant opens with one sentence that names the
53
+ tutor page and the task, then the input and the app's message word for word; on a page a tutor opened the page copies
54
+ the task, the same lines and the tutor page, and the learner pastes them into the running chat.
55
+ The assistant starts from the app's message and helps without giving the solution away, as the tutor page's guide
56
+ says. Nothing leaves the device before a click, and the app reports nothing back. No model runs in the browser.
57
+
58
+ ```arc42
59
+ :::runtime-scenario
60
+ id: scenario-tutor
61
+ title: Asking the tutor
62
+ trigger: The learner wants to learn a page with a tutor, or is stuck at a task
63
+ involves: bb-shell, bb-tutor-handoff, bb-task-numbers, bb-explainer-components
64
+ :::
65
+ ```
66
+
67
+ ## Watching a video
68
+
69
+ Without JavaScript, and with it before a click, a video is its title as a link to youtube.com, its channel and the
70
+ note that playing it sends data, among them the IP address, to YouTube/Google; the page requests nothing from another
71
+ host. The learner clicks "Video abspielen": the player of youtube-nocookie.com loads into the figure and starts, and
72
+ from then on YouTube gets the learner's IP address and the app's origin. Ticking "Videos immer direkt laden" keeps that
73
+ choice on the device, so every page of the app loads its players without the click, until the learner unticks it.
74
+
75
+ ```arc42
76
+ :::runtime-scenario
77
+ id: scenario-explainer-video
78
+ title: Watching a video on a page
79
+ trigger: The learner clicks "Video abspielen", or opens a page after choosing to load videos directly
80
+ involves: bb-render, bb-shell, bb-video, bb-runtime-texts, bb-tooling
81
+ :::
82
+ ```
83
+
84
+ ## Doing a self-assessment
85
+
86
+ The start page, or a chapter's page, offers the self-assessment once the client module runs; it names the last result
87
+ when the device keeps one. The learner starts a run: each generated exercise gets a new task, and the pages follow one
88
+ by one with "Weiter" and "Zurück", the exercises without "Prüfen" and without feedback. "Auswerten" checks all of them
89
+ together, keeps the result on the device (replacing the one before) and shows the score and, for each content page in
90
+ order, whether the learner probably knows it, should look at it again or should read it, each with its link. "Mit
91
+ <name> besprechen" opens the learner's assistant with one sentence that names the self-assessment's tutor page with the
92
+ result in its address, the result per page and a line per exercise. The tutor guides chapter by chapter: it sends the
93
+ learner to the pages that do not stick, each with `via=tutor`, whose "Zurück zum Tutor" copies the result line for the
94
+ chat, and then into a new round of the chapter's self-assessment with `via=tutor`: that run keeps nothing, and its
95
+ "Zurück zum Tutor" copies the result and the tutor page for the running chat. After each round the tutor decides on the
96
+ next chapter or another round. "Neu starten" starts a new run, "Ergebnis löschen" removes the result.
97
+
98
+ ```arc42
99
+ :::runtime-scenario
100
+ id: scenario-explainer-self-assessment
101
+ title: Doing a self-assessment
102
+ trigger: The learner starts the self-assessment offered on the start page or a chapter's page
103
+ involves: bb-render, bb-shell, bb-self-assessment, bb-generated-exercises, bb-questions, bb-task-numbers, bb-tutor-handoff, bb-runtime-texts, bb-tooling
104
+ :::
105
+ ```
@@ -0,0 +1,34 @@
1
+ # Deployment View
2
+
3
+ Where the explainer's blocks run, below the generic nodes.
4
+
5
+ ## Explainer build
6
+
7
+ The explainer's component rules, property tests, tutor pages and self-assessments run in CI and, through the same hooks, on the
8
+ creator's machine; the content, data and components are built into the pages and the bundle there.
9
+
10
+ ```arc42
11
+ :::deployment-node
12
+ id: dn-explainer-build
13
+ title: Explainer build
14
+ type: container
15
+ parent: dn-ci-runner
16
+ hosts: bb-generated-exercises, bb-tutor-handoff, bb-scope, bb-task-numbers, bb-self-assessment, bb-explainer-content, bb-explainer-data, bb-explainer-components
17
+ :::
18
+ ```
19
+
20
+ ## Explainer page
21
+
22
+ The client module, the islands with the components, the generated exercises' live tasks with the quiz's question
23
+ types, the task numbers, the videos, a self-assessment's run and the explainer's texts run in the learner's browser on the pages the build
24
+ rendered.
25
+
26
+ ```arc42
27
+ :::deployment-node
28
+ id: dn-explainer-page
29
+ title: Explainer page
30
+ type: container
31
+ parent: dn-learner-browser
32
+ hosts: bb-islands, bb-generated-exercises, bb-open-tasks, bb-task-numbers, bb-video, bb-self-assessment, bb-runtime-texts, bb-explainer-components
33
+ :::
34
+ ```
@@ -0,0 +1,17 @@
1
+ # Cross-cutting Concepts
2
+
3
+ The explainer's concept, besides the generic ones.
4
+
5
+ ## Tutor
6
+
7
+ Optional per app: no model runs in the browser; the tutor is a handoff of the current task to the learner's own
8
+ assistant, after a click, through a prompt and a static tutor page per page (`dec-tutor-variant`, `dec-tutor-page`). Built for the explainer, not
9
+ built for the quiz and interactive apps (`risk-tutor-not-built`).
10
+
11
+ ```arc42
12
+ :::concept
13
+ id: concept-tutor
14
+ title: Tutor as a handoff to the learner's own assistant
15
+ category: learning
16
+ :::
17
+ ```
@@ -0,0 +1,443 @@
1
+ # Architecture Decisions
2
+
3
+ The explainer's decisions, besides the generic ones.
4
+
5
+ ## The Creator Writes Only Content
6
+
7
+ The explainer is a deep scaffold: rendering, navigation, formulas, figures, exercises with feedback, accessibility,
8
+ languages and the page without JavaScript come from the runtime; the creator writes YAML in `src/` and code only as a
9
+ Vue component where no block can say what the page needs. The build fails on any other file in `src/`. So the schema
10
+ fixes the explainer's structure, and an explainer app writes no architecture of its own. Rejected: free code in an
11
+ explainer.
12
+
13
+ ```arc42
14
+ :::decision
15
+ id: dec-explainer-only-content
16
+ title: An explainer is its content, components only where no block fits
17
+ status: accepted
18
+ date: 2026-10-16
19
+ addresses: qg-checkable, qg-understandable, con-runtime-unchanged, con-component-rules
20
+ :::
21
+ ```
22
+
23
+ ## Every Page Complete Without JavaScript
24
+
25
+ Every fact is in a block, rendered at build; an exercise shows its solution folded, a component its `nojs` text (an
26
+ exercise component's written from its `describe`), so the page is complete without JavaScript and the client module
27
+ only adds. `lernapps check` opens every explainer page a second time with JavaScript off. Rejected: meaning only
28
+ inside a component or a live exercise. The one exception is the optional self-assessment (`dec-self-assessment`),
29
+ which needs a run in the browser and is no content of its own.
30
+
31
+ ```arc42
32
+ :::decision
33
+ id: dec-explainer-complete-without-js
34
+ title: Every explainer page is complete without JavaScript
35
+ status: accepted
36
+ date: 2026-10-16
37
+ addresses: qg-explainer-readable-without-js, qg-accessibility
38
+ :::
39
+ ```
40
+
41
+ ## A Fixed Size per Component
42
+
43
+ A component stays within a fixed size of its own code, with Vue loaded once per page, so that a text-heavy page stays
44
+ quick to load; a component that needs more is split or becomes a built-in block. The figure is an implementation
45
+ detail of the runtime's build check. Rejected: no limit, which suits interactive apps but not pages read like text.
46
+
47
+ ```arc42
48
+ :::decision
49
+ id: dec-explainer-component-size
50
+ title: A fixed size per component
51
+ status: accepted
52
+ date: 2026-10-16
53
+ addresses: con-component-rules, qg-explainer-readable-without-js
54
+ :::
55
+ ```
56
+
57
+ ## Task Numbers in the Address
58
+
59
+ Every seeded exercise numbers its distinct tasks, and the address holds the number of each exercise of the page, so a
60
+ link shared in class, a deep link and the tutor name the same task; the build ships each exercise's table of seeds.
61
+
62
+ ```arc42
63
+ :::decision
64
+ id: dec-explainer-task-numbers
65
+ title: The task number in the address is the one identifier of a task
66
+ status: accepted
67
+ date: 2026-10-16
68
+ addresses: qg-checkable
69
+ :::
70
+ ```
71
+
72
+ ## One Tutor Page per Page, the One Source, Two Ways to It
73
+
74
+ A chat assistant such as claude.ai fetches only addresses that stand word for word in a message of its user; an
75
+ address in a fetched file does not count, absolute or relative. A static host serves the same file for any query.
76
+ So the tutor page of a content page `<chapter>/<page>/tutor/` holds everything the tutor needs, and every way to the
77
+ tutor names just this address, with where the learner is in its query: the page's task numbers as the page's own
78
+ address has them (`?rr-strom=16&rr-teilspannung=7`), and for way B the exercise (`uebung`) and its section
79
+ (`abschnitt`). The tutor reads the query from the message and looks the numbers up on the page. The tutor page is lean HTML with its
80
+ guide visible, so that parents and teachers see what the tutor gets: who the tutor helps (a child at an exercise of
81
+ this page), a few rules for guiding (short answers, the page's own tasks, nothing worked out by itself, explaining in
82
+ the chat and solving in the app), how to start each way, where the child is, then the page's content as
83
+ context and its exercises with every task number. A `generated` exercise is there once as its template, with hint,
84
+ steps and its typical mistakes as F1, F2 … with the app's messages, and below one line per task number with its
85
+ inputs and outputs, all worked out: the values of the placeholders, the solution, what each mistake gives. So the
86
+ tutor fills in and calculates nothing, can give any further task with its link, and never asks the learner to copy
87
+ one. An exercise component has its `about` and the same table from its exercise logic (`dec-component-table`), or
88
+ without one each number's task and solution from its `describe`, as many as fit into 64 kB. The guide stands on the tutor page alone: the prompt of way A, the lines
89
+ of way B and the result line speak in the learner's voice and carry no rule, and the app writes no `llms.txt`, so that
90
+ no two texts can tell the tutor different things. Learners come with one of two needs, and each gets its own way. A, "Mit
91
+ <name> lernen", a bar beside the title of every content page, where the learner is at work, once an assistant is on
92
+ (`dec-tutor-assistant`; a chapter page starts none): one sentence that asks for help with the page, with the page's
93
+ task numbers alone; the tutor asks what the child knows, leads through the page section by section, checks each with
94
+ a question on its content, and only then sends the child to its task. With the exercise in the query of way A, the
95
+ tutor went straight for the task (hand test on claude.ai, 2026-10-26). B, "Hilfe" in
96
+ every exercise, before the first input too, "Warum?" after a wrong one: one sentence that names the task, then the
97
+ input with its tries and the app's message word for word, so the tutor starts from what the learner was told: the
98
+ tutor page tells it to go first, briefly, into exactly that input and the task, which mistake of the table it is;
99
+ without a chat they open a new one, and on a page a tutor opened (`via=tutor`) they are copied for the running chat,
100
+ which a new chat would lose. "Zurück zum Tutor", at the top and the end of the page, copies the page's tutor page with
101
+ the page's task numbers and a line for each task of the session (how far the learner got, a wrong one with its input
102
+ and the app's message), so that the tutor loads the tutor page and coaches on exactly these tasks; "Zurück" at one
103
+ task copies that task's result line. Copying
104
+ instead of an assistant adds the tutor page's Markdown, which lies beside it. Input and message leave the page only
105
+ with the learner's click. Measured on lernapps/elektrizitaet and Mathe-Karte (2026-10-26): with every number, a
106
+ page with two `generated` exercises and a component has 39 kB, the most with `generated` ones alone 54 kB; two
107
+ pages whose components describe each task at length would have 80 kB.
108
+ Rejected: one tutor page per chapter (long, and the tutor answered at length); a task file per task number, which the
109
+ tutor may fetch only from a line the learner pastes, and a tutor file per page beside the tutor page (each a second
110
+ source; the task files were not fetched from a link on the tutor page); a static page per task number (the numbers
111
+ of several exercises on one page multiply); the template with only the values per number (the tutor calculated the
112
+ solution itself); rules in the prompt, the lines, `llms.txt` or a whole-app prompt (they contradicted the tutor
113
+ page); an `llms.txt` with the pages alone (the tutor page and the pages take its part); a prompt that lists every file (one address per page, unreadable as a first message); absolute links in a
114
+ fetched file (not fetched); a Markdown tutor page alone (many browsers download `text/markdown`); hidden instructions
115
+ (look like prompt injection).
116
+
117
+ ```arc42
118
+ :::decision
119
+ id: dec-tutor-page
120
+ title: One short tutor page per page, the one source of what the tutor gets, two ways to it
121
+ status: accepted
122
+ date: 2026-10-26
123
+ addresses: qg-privacy, qg-understandable, con-no-request-before-click
124
+ :::
125
+ ```
126
+
127
+ ## The Scope Gives the Tutor the Whole Learning Path
128
+
129
+ To judge an open answer, a tutor needs the exact content of the whole learning path, not only of the page at hand: for
130
+ a test, what the class learnt and what it did not is what counts (lernapps/practice#19). The material, photos of
131
+ worksheets for a made-to-measure app, is seen only while building. So the creator's assistant writes the scope: long
132
+ continuous prose in its own words, beside the content it describes: `scope` in each page's YAML, optionally in a
133
+ chapter's, and in `src/app.yaml` what the app is about and what does not belong to it (what belongs to it is what the pages
134
+ say); with the app's, every
135
+ page has its own, which the build checks. A scope beside its page is kept in step with it and needs no paths that
136
+ could go wrong (owner, 2026-10-30; first built as one file `src/scope.md`). It is no page for the learner: the build writes a scope beside each page
137
+ (`<chapter>/<page>/scope/`, the page's part with what does not belong to the scope), combines all of a
138
+ chapter beside the chapter and the whole beside the app, each linking one level up, for no menu and no search engine;
139
+ each tutor page links its page's scope (`scope/` in place of `tutor/`) with a guide: read it before judging an open
140
+ answer, go up when a point's source is on another page;
141
+ if it cannot be fetched (security settings may block it, which an assistant notices itself), give the learner its full
142
+ address and ask them to allow it, and until then judge no open answer and answer nothing about the content from
143
+ general knowledge. This revises `dec-tutor-page`: the tutor page stays the one source of the task, the scope is the
144
+ source of the content. Rejected: a summary page for the learner (a second content page to keep in step); one file for
145
+ the whole scope (it drifts from the pages, and each part has to name its page by a path); a list instead of prose (the
146
+ exact content is missing).
147
+
148
+ ```arc42
149
+ :::decision
150
+ id: dec-scope
151
+ title: The scope, prose of the whole learning path in the creator's own words, gives the tutor the content
152
+ status: accepted
153
+ date: 2026-10-30
154
+ addresses: qg-understandable
155
+ :::
156
+ ```
157
+
158
+ ## An Exercise Component Computes Its Table of Tasks
159
+
160
+ An exercise component's values and results are in its code (`generate`, `check`, `solution`), not in a declaration
161
+ the build could read, and its `describe` gives each number's task as text of about 1 kB on Mathe-Karte, so two pages
162
+ showed only 49 to 54 of 67 numbers within 64 kB. So the component gives the tutor what a `generated` exercise gives:
163
+ its logic module exports `template(props, lang)`, the task once with `{name}` for each input; `params(task, props,
164
+ lang)`, the inputs of each task; `mistakes(task, props)`, the answer of each typical mistake of `about.mistakes`; and
165
+ `show(answer, task, props, lang)`, an answer as text. The build computes the outputs itself, the solution as
166
+ `show(solution(task))` and each mistake as `show` of its answer, and checks them for every task number: `check`
167
+ accepts the solution and rejects each mistake's answer, the inputs have the same names for every task, and each
168
+ placeholder is one of them. So the table cannot drift from the code. The four exports are optional, all or none; a
169
+ component without them keeps the lines from `describe` within the budget, and the build gives a hint
170
+ (`vue-component-tutor-table`), to become a warning once the apps have them. Measured on Mathe-Karte (`feat/explainer`,
171
+ 2026-10-26) with example declarations of `ProzentGrundbegriffe`, `ZufallPfade` and `ZufallTerm`: `grundbegriffe`
172
+ 23 kB instead of 64 kB, `pfadregel-2` 35 kB instead of 63 kB, every number on the page. Rejected: the outputs as
173
+ expressions in the language of `generated` (`about.formula`, the first proposal of #80; terms, trees, sets and
174
+ choices have no such expression); shorter texts from `describe` (still one text per number, and the tutor would
175
+ read the values out of prose); a task file per number (fetched only from a line the learner pastes,
176
+ `dec-tutor-page`).
177
+
178
+ ```arc42
179
+ :::decision
180
+ id: dec-component-table
181
+ title: An exercise component computes its table of tasks for the tutor, checked against check
182
+ status: accepted
183
+ date: 2026-10-26
184
+ addresses: qg-checkable, qg-understandable
185
+ :::
186
+ ```
187
+
188
+ ## The Explainer Checks an Exercise Component's Answer
189
+
190
+ An exercise component drew its own "Prüfen", called `check` and reported the result with `answered`: every app built
191
+ the same button and its style anew (the questions style only buttons inside `.question`, so it was small and plain),
192
+ the answer reached the tutor as text only when the component sent it, and a component could report a result other
193
+ than its logic's. So a component is the input of its task and nothing more: it emits `answer` with the learner's
194
+ answer each time it changes, and may declare `checked: boolean`, true once its answer is checked. The explainer
195
+ renders its island in a form with "Prüfen" in the row of the questions (Enter in a field checks), enabled while there
196
+ is an answer not checked yet; it calls `check(task, answer)`, `reason(task, answer, props, lang)` for the app's message
197
+ of a wrong answer and, when there, `show` for the answer as text, gives the feedback and passes all three on to "Hilfe"
198
+ and "Zurück". Its logic module gets one export more, `reason`, which the property test calls on the wrong answer.
199
+ `answered` is gone, without a transition: the build names it as an error, with what to do instead. Kept out, until an
200
+ app needs them: texts of the task and the messages in the block (the props hold them, so the component and `reason`
201
+ fill them in), the explainer's own "Lösung" and "Zurücksetzen" for a component, and finding the mistake by comparing
202
+ answers (most components' mistakes are kinds of answers, "Amperemeter parallel", not single values, so `reason` stays
203
+ code). Tried on all exercise components of elektrizitaet, anorganische-chemie, mechanik-explainer and Mathe-Karte
204
+ (2026-10-26). Rejected: the component's own button with shared CSS (#85, a stopgap); "Prüfen" inside the island,
205
+ whose DOM Vue owns (#83); `v-model`, as nothing flows back into the component while there is no "Lösung".
206
+
207
+ ```arc42
208
+ :::decision
209
+ id: dec-explainer-checks
210
+ title: The explainer draws "Prüfen" and checks an exercise component's answer with its logic module
211
+ status: accepted
212
+ date: 2026-10-26
213
+ addresses: qg-checkable, qg-accessibility
214
+ :::
215
+ ```
216
+
217
+ ## The Learner Switches an Assistant On Once
218
+
219
+ Not every learner wants a KI tutor, and one who does wants it at hand on every page. So the learner chooses once:
220
+ on the start page ("Dein KI-Assistent", "Keiner" first) or beside the title of a content page, where "Mit einem
221
+ KI-Tutor lernen" stays folded while none is on. The choice is one of the KI assistants the runtime offers, the same for every app,
222
+ or copying the text, and it is kept on the device, per app, as the
223
+ only thing the app keeps; nothing is sent. Once an assistant is on, each content page shows a small bar beside its
224
+ title with one button, "Mit <name> lernen" (way A; changed or switched off on the start page, as it is seldom changed) and one help button in the row of "Prüfen" and "Lösung" of every exercise (way B), with the assistant's
225
+ first letter as its symbol, so that the learner sees where a click leads: "Hilfe", "Warum?" after a wrong answer,
226
+ and "Zurück" on a page a tutor opened once the task is done; one button that changes its text, not two. While none
227
+ is on, an exercise has no help button. Both places say that the learner uses an account of their own and that the
228
+ assistant is no offer of lernapps.net, which only provides the content and the exercises. Rejected: the way chosen
229
+ anew on every page (a box above every page's content); a help button for learners without a tutor (it leads
230
+ nowhere); the providers' logos (their marks; a letter says enough); assistants named per app (`tutor.providers`; no
231
+ app named others, and a learner meets the same choice in every app); one choice for all apps of lernapps.net (each
232
+ app keeps what it stores apart).
233
+
234
+ Mistral Vibe loads no address from a message that arrives through its own address (`?q=`): it counts as not written by the
235
+ learner. So an assistant can carry `paste`: the click copies the start prompt, the one sentence with the address of the
236
+ tutor page, at once, and opens the chat with a short placeholder message ("Ich brauche Hilfe bei einer Lern-Einheit.
237
+ Ich schreibe Dir gleich, worum es geht."), which holds no address. The learner pastes the sentence; it is now their own
238
+ message, and the tutor loads the tutor page from it. The copy starts in the click, as a browser refuses it once the new
239
+ window has the focus (Edge did, after a fetch of the tutor page's Markdown). Choosing such an assistant shows a short
240
+ note on how it works. Rejected: the prompt alone in the address (Mistral Vibe fetches nothing from it); a plain link to Le
241
+ Chat (the learner would write the prompt by hand); copying the Markdown as well (a fetch first, and the copy came too
242
+ late).
243
+
244
+ ```arc42
245
+ :::decision
246
+ id: dec-tutor-assistant
247
+ title: The learner switches an assistant on once, kept on the device; help only while one is on
248
+ status: accepted
249
+ date: 2026-10-26
250
+ addresses: qg-privacy, qg-understandable, con-no-request-before-click
251
+ :::
252
+ ```
253
+
254
+ ## The Learner Names What Helps; the Tutor Page Holds a Short Section for Each
255
+
256
+ Some learners learn better when the tutor talks to them in a particular way: in short steps, with
257
+ little to read, with deeper questions, in simple German for another first language, literally, or with numbers as
258
+ amounts. So beside the assistant the learner chooses none, one or several of six needs, each named by what helps,
259
+ never by a diagnosis: `kurze-schritte`, `wenig-lesen`, `mehr-tiefe`, `andere-muttersprache`, `klar-und-woertlich`,
260
+ `rechnen-anschaulich`; with `andere-muttersprache` also the first language, from a list. Beside each need one or two
261
+ sentences say what it does. Unlike the assistant, which each app keeps apart (`dec-tutor-assistant`), the needs are
262
+ the learner's own and the same in every explainer: the choice holds for every explainer of lernapps.net
263
+ (`lernapps:settings:explainer:`) or for this app only (`lernapps:<id>:`, which wins); nothing is sent before a click. Every address the learner hands to a
264
+ tutor that starts a chat, the start prompt of way A, the lines of way B1 and the prompt of a self-assessment, carries the needs in the tutor page's
265
+ query (`bedarf=kurze-schritte,andere-muttersprache`); with `andere-muttersprache` the prompt adds one sentence in the
266
+ learner's voice, "Meine Muttersprache ist Türkisch." A running chat (B2, "Zurück zum Tutor") has them already. The
267
+ tutor page is static, so it holds the section of every need, after its guide under "Besondere Anforderungen": they
268
+ hold when the query names them and go before the guide; each says where it departs from it ("Im Gegensatz zu oben
269
+ …") and has at most two sentences, about 200 characters, so that the six together do not drown the guide. Of two needs that differ, the stricter one holds. The tutor reads the
270
+ query as it reads the task numbers. The sections draw on research on teaching, support and feedback; none of it is
271
+ on tutoring in a chat, so each section is checked by a hand test like the guide.
272
+ Rejected: a page per need or a central page of all needs on the GitHub Pages of lernapps/app-templates (a second
273
+ address to fetch, untested; several needs multiply the addresses); the needs as rules in the prompt (they would
274
+ contradict the tutor page); free text (the same, and nothing to check); names of diagnoses (health data about a child
275
+ in an address sent to the assistant's provider, and a label); learning styles (no evidence); a need for
276
+ encouragement and one for learners who cannot see the figures, not now.
277
+
278
+ ```arc42
279
+ :::decision
280
+ id: dec-tutor-needs
281
+ title: The learner chooses needs named by what helps; the tutor page holds a short section for each, chosen by its query
282
+ status: accepted
283
+ date: 2026-10-30
284
+ addresses: qg-privacy, qg-understandable, qg-accessibility, con-no-request-before-click, con-state-on-device
285
+ :::
286
+ ```
287
+
288
+ ## A Video Loads Only After a Click
289
+
290
+ A video block is a link to YouTube until the learner asks for the player. Before the click the page shows the title,
291
+ the channel and a note that playing sends data, among them the IP address, to YouTube/Google, and it requests nothing
292
+ from another host: `con-no-request-before-click` holds, and the page needs no consent banner. The click on "Video
293
+ abspielen" is the learner's consent for this one video, given with the note in view; only then does the player load,
294
+ from YouTube's privacy-enhanced host youtube-nocookie.com, with the app's origin as its only referrer, so YouTube never
295
+ sees the task numbers or `via=tutor` in the address. "Videos immer direkt laden" is a standing consent: kept on the
296
+ device under the app's prefix (`con-state-on-device`), shown ticked on every video, and withdrawn by unticking it. No
297
+ thumbnail, as YouTube's image would load before the click and an own one needs the rights to it. An app with a video
298
+ loads from another server after a click, so its catalog entry says `fitness.thirdParty: on-click`. Rejected: a direct
299
+ embed (requests to Google on opening the page, a consent banner); only a link (the learner leaves the page for
300
+ YouTube's recommendations); the video in the bundle (rights, size).
301
+
302
+ ```arc42
303
+ :::decision
304
+ id: dec-explainer-video-two-click
305
+ title: A video is a link until the learner's click loads its player from youtube-nocookie.com
306
+ status: accepted
307
+ date: 2026-10-26
308
+ addresses: qg-privacy, qg-explainer-readable-without-js, con-no-request-before-click, con-state-on-device, risk-explainer-video-after-click
309
+ :::
310
+ ```
311
+
312
+ ## A Self-Assessment Checked Only as a Whole
313
+
314
+ A learner may want to know before learning which pages they can skim. So an explainer may offer a self-assessment. A
315
+ chapter declares its own, the unit a learner works through; the app's is every chapter's together, so that one run
316
+ covers all and a tutor can go on chapter by chapter with the same exercises. It shows exercises of the content pages without the page around
317
+ them, a few on each of its pages (best two to five), and checks no single one: only "Auswerten" checks them all
318
+ together, and the result is per content page, the level at which the learner goes on: probably known (all right),
319
+ look at it again (at least half), read it (less); a page none of whose exercises is asked is "not asked", as some
320
+ content, an open activity for one, cannot be checked. One task alone is too little to call a page known: right, it is
321
+ "look at it again". A generated exercise is preferred, as each run draws a new task, and it may be listed twice for
322
+ two tasks of its own; the options of a choice are mixed anew for each run, so that a second run asks the question,
323
+ not the place of its answer. It is optional for the learner and for the app: the content declares it, and an agent adds it when the content
324
+ and its forms of teaching are known, at the end (tooling skill `lernapps-explainer-self-assessment`). It needs
325
+ JavaScript; without it the offer stays hidden and its page says so in one line. Rejected: feedback after each
326
+ exercise (it would teach, not assess); a result per chapter (too coarse to say where to start); a self-assessment of
327
+ the app declared apart from the chapters' (two lists of the same exercises to keep in step); exercises of its own
328
+ (they would duplicate the pages' and miss the tutor's tables). An exercise component may be in it as well, and may be
329
+ listed twice like a generated exercise: its island shows the task the run draws, without "Prüfen", and judges each
330
+ answer silently for "Auswerten"; its typical mistake comes from its logic's mistakes. Its tasks often check a page
331
+ best, so leaving components out left whole chapters unasked.
332
+
333
+ ```arc42
334
+ :::decision
335
+ id: dec-self-assessment
336
+ title: An optional self-assessment checks its exercises only as a whole and says per content page where to start
337
+ status: accepted
338
+ date: 2026-10-28
339
+ addresses: qg-understandable, qg-explainer-readable-without-js
340
+ :::
341
+ ```
342
+
343
+ ## A Partly Right Answer Counts Half
344
+
345
+ An answer may be partly right: an open answer has some, not all, points of a good answer (lernapps/practice#19). It
346
+ counts half, so that a page with one right and one partly right of three is "look at it again", and its result
347
+ `teilweise` names the points it missed (`P2`) as a wrong one names its mistake (`F1`). An exercise component may say so
348
+ as well (`partly`, `missed`). Rejected: counting it as wrong (writing half an answer would count as writing none) or
349
+ as right (a page would be "known" with points missing).
350
+
351
+ ```arc42
352
+ :::decision
353
+ id: dec-self-assessment-partly
354
+ title: A partly right answer counts half and names the points it missed
355
+ status: accepted
356
+ date: 2026-10-30
357
+ addresses: qg-understandable
358
+ :::
359
+ ```
360
+
361
+ ## An Open Task Is Answered First, Then Compared With Points
362
+
363
+ Some subjects need answers in the learner's own words: German, foreign languages, religion (lernapps/practice#19). An
364
+ example with a model answer beside it was only read, tied to nothing in the material, and gave the self-assessment and
365
+ the tutor nothing to work with. So an open task asks for the answer first, typed or written on paper and photographed
366
+ (the choice "Deine Lösung"), and only then shows the points of a good answer, 2 to 5, each about the content with its
367
+ source in the app, linked to read up, or about the form; the learner ticks those the answer has, which makes them read
368
+ each point; the ticks give right, partly right (`dec-self-assessment-partly`) or wrong at once, without a step of
369
+ their own, and comparing opens the model answer.
370
+ Without JavaScript the page shows the task, the points and the model answer under "Lösung", as every exercise has one.
371
+ The tutor page gives the tutor the points as P1, P2 … with their sources, the model answer and how to help; the result
372
+ line carries the ticked points and the answer, a photo only as a note that the learner attaches it. The answer stays on
373
+ the device: the text, the way chosen and the ticks are kept while the tab is open (sessionStorage through the tooling's
374
+ storage, under the app's prefix), so that a reload loses nothing and a new day starts afresh; the photo is shown from
375
+ memory and never stored or sent; a self-assessment keeps nothing of its own. Rejected: grading by a model (no model runs in
376
+ the browser, `dec-tutor-variant`; a server would collect what learners write); a model answer without the learner's own
377
+ answer first (it is only read); a word search over the answer (easy to fool; at most a hint).
378
+
379
+ ```arc42
380
+ :::decision
381
+ id: dec-open-task
382
+ title: An open task is answered first, then compared with points that name their sources
383
+ status: accepted
384
+ date: 2026-10-30
385
+ addresses: qg-understandable, qg-privacy, qg-explainer-readable-without-js
386
+ :::
387
+ ```
388
+
389
+ ## The Last Result of a Self-Assessment Stays on the Device
390
+
391
+ The learner should find their result again, and a tutor should be able to accompany them from it. So the result of
392
+ the last run stays on the device, under the app's prefix (`con-state-on-device`), one per self-assessment, so the app's
393
+ and a chapter's do not mix; a new run replaces it, and "Ergebnis löschen" removes it. A run a tutor opened
394
+ (`via=tutor`) is no self-assessment of the learner's: it keeps nothing. It holds per exercise its
395
+ reference, its task number, right, wrong or not answered, and the input and typical mistake of a wrong one: nothing
396
+ about the learner, and nothing is sent. The learner takes it to a tutor with a click: the tutor page of the
397
+ self-assessment, its address holding the result (`?a1=17-falsch-F1`), in one sentence with a line per exercise, in the
398
+ address of the assistant chosen or copied with the tutor page's Markdown, as on the content pages
399
+ (`dec-tutor-assistant`). Rejected: keeping every run (a history about the learner); a result only in memory (lost on the way to the
400
+ tutor); the result sent anywhere (`qg-privacy`).
401
+
402
+ ```arc42
403
+ :::decision
404
+ id: dec-self-assessment-result
405
+ title: The last result of a self-assessment stays on the device and goes to a tutor only with a click
406
+ status: accepted
407
+ date: 2026-10-28
408
+ addresses: qg-privacy, con-state-on-device, con-no-request-before-click
409
+ :::
410
+ ```
411
+
412
+ ## The Tutor Guides Through Chapters and Rounds
413
+
414
+ After a self-assessment the tutor is a guide, not a teacher: explaining and practising happen on the pages, with their
415
+ "Hilfe" and their own tutor pages. The self-assessment's tutor page tells the tutor to go chapter by chapter: the pages
416
+ that do not stick, each opened with `via=tutor` and ending in "Zurück zum Tutor", then a new round of the chapter's
417
+ self-assessment, also with `via=tutor`, which draws new tasks; after each round the tutor decides on the next chapter
418
+ or another round, and sends a page that does not stick after two rounds to "Mit einem KI-Tutor lernen" on the page.
419
+ When the learner comes back from a page with a wrong task, the guide loads the page's tutor page and coaches on exactly
420
+ those tasks, then guides on. Each round's "Zurück zum Tutor" ends with the link of the next round, which names the task
421
+ numbers of this one (`vorher=a1.28`): the next round draws others where it can. On
422
+ a page a tutor opened, the clipboard is always the way back: the self-assessment's "Zurück zum Tutor" copies the result
423
+ per page, a line per exercise and the tutor page with the result in its address, as the result
424
+ line of a content page does. The tutor page holds every chapter with its pages and the link to its self-assessment, and
425
+ the exercise behind each key; no tables of solutions, which the pages' tutor pages hold. It holds no address: a static
426
+ page does not know where the app is served, and an assistant resolving relative links led the learner to tutor pages
427
+ (ChatGPT, 2026-10-28). The message the learner sends, written by the browser, ends with the full address of every
428
+ page and every self-assessment of the app with `via=tutor`, and the guide takes links only from there. The
429
+ parameter is `via=tutor`, without the old `von=tutor`. A copy for the running chat keeps the tab open: closing it let the browser show
430
+ some other tab, and the learner did not see that something was copied. The button says "✓ Kopiert" for a while, as the tutor
431
+ bar does after copying, and a line below it where to paste. Rejected: the tutor explaining
432
+ the tasks of the self-assessment itself (a second place to teach, and solutions in view); a new chat per round (the
433
+ tutor would lose the course of the learner).
434
+
435
+ ```arc42
436
+ :::decision
437
+ id: dec-self-assessment-tutor
438
+ title: After a self-assessment the tutor guides chapter by chapter, with rounds whose result comes back through the clipboard
439
+ status: accepted
440
+ date: 2026-10-28
441
+ addresses: qg-understandable, qg-privacy, con-no-request-before-click
442
+ :::
443
+ ```
@@ -0,0 +1,54 @@
1
+ # Quality Requirements
2
+
3
+ The explainer's goal and scenarios, besides the generic ones.
4
+
5
+ ## Readable without JavaScript (explainer)
6
+
7
+ An explainer is text-heavy, so every page is complete without JavaScript: text, formulas, figures, the navigation,
8
+ every exercise with its solution and every component's `nojs` text (`dec-explainer-complete-without-js`); realised by
9
+ the page rendering and checked by `lernapps check` and the explainer's end-to-end tests.
10
+
11
+ High priority, because the archetype is text-heavy (owner, 2026-10-16).
12
+
13
+ ```arc42
14
+ :::ignore W014 the extension's goal follows the generic goals in the combined chapter; the generic part cannot hold it
15
+ :::
16
+ :::ignore H010 the generic solution strategy cannot name an archetype's goal; the explainer's rendering realises it, see the decisions that address it
17
+ :::
18
+ :::quality-goal
19
+ id: qg-explainer-readable-without-js
20
+ title: Readable without JavaScript (explainer)
21
+ priority: high
22
+ :::
23
+ ```
24
+
25
+ ## Every page complete without JavaScript
26
+
27
+ `lernapps check` opens every page the runtime marks as an explainer page a second time with JavaScript off, on the
28
+ bundle and on the deployed app (rule `explainer-readable-without-js`).
29
+
30
+ ```arc42
31
+ :::quality-scenario
32
+ id: qs-explainer-without-js
33
+ title: Every explainer page is complete without JavaScript
34
+ quality: qg-explainer-readable-without-js
35
+ stimulus: lernapps check visits every explainer page of the built or deployed app with JavaScript off
36
+ response: A page without its h1, a hidden heading, formula or figure, an empty "Lösung" or an empty nojs text is an error
37
+ metric: 0 findings of explainer-readable-without-js on every page, in every language
38
+ :::
39
+ ```
40
+
41
+ ## Generated exercises are sound
42
+
43
+ The build runs each generated exercise and each exercise component's logic over fixed seeds.
44
+
45
+ ```arc42
46
+ :::quality-scenario
47
+ id: qs-explainer-property-test
48
+ title: A generated exercise passes its property test
49
+ quality: qg-checkable
50
+ stimulus: The build draws the tasks of fixed seeds of each generated exercise and exercise component
51
+ response: A task that differs for the same seed, a solution the check rejects, a wrong answer it accepts or a throw fails the build
52
+ metric: 50 fixed seeds per exercise component block, every one passes
53
+ :::
54
+ ```