@lernapps/app-templates 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.
- package/LICENSE +22 -0
- package/README.md +195 -2
- package/dist/appname-Bh23cUuz.mjs +12 -0
- package/dist/assessment-By2GGTJC.mjs +1696 -0
- package/dist/block-BK_ohsH1.mjs +82 -0
- package/dist/blocks-BmRrD7UY.mjs +149 -0
- package/dist/blocks.mjs +4 -0
- package/dist/check-BkEl-m-F.mjs +254 -0
- package/dist/content-cYBPYg_J.mjs +140 -0
- package/dist/contract-PONpxJtd.mjs +52 -0
- package/dist/e2e-BiMI-pCL.mjs +89 -0
- package/dist/engine-BqF2Vffn.mjs +136 -0
- package/dist/explainer-e2e.mjs +1297 -0
- package/dist/explainer-islands.mjs +197 -0
- package/dist/explainer-plugin.mjs +2131 -0
- package/dist/explainer.mjs +1246 -0
- package/dist/html-mffkHGHh.mjs +89 -0
- package/dist/image-CspJVfyQ.mjs +21 -0
- package/dist/interactive-e2e.mjs +140 -0
- package/dist/interactive-plugin.mjs +242 -0
- package/dist/interactive.mjs +75 -0
- package/dist/load-C-0hUaSC.mjs +16947 -0
- package/dist/needs-BzEahJ6X.mjs +97 -0
- package/dist/pages-9RaaomoO.mjs +81 -0
- package/dist/questions-e2e.mjs +1 -0
- package/dist/questions-engine.mjs +3 -0
- package/dist/questions.mjs +6 -0
- package/dist/quiz-BKc8JYtC.mjs +70 -0
- package/dist/quiz-e2e.mjs +174 -0
- package/dist/quiz-plugin.mjs +266 -0
- package/dist/quiz.mjs +246 -0
- package/dist/render-BJey0bwl.mjs +540 -0
- package/dist/render-BL6fV_NI.mjs +507 -0
- package/dist/render-DpRO_RKm.mjs +146 -0
- package/dist/result-_eQcamoz.mjs +180 -0
- package/dist/tasks-DADUYy52.mjs +74 -0
- package/dist/validate-COaZso7u.mjs +1072 -0
- package/dist/validate-CxtdeQ5S.mjs +146 -0
- package/dist/vue-DO1m6q1R.mjs +40 -0
- package/dist/vue-code-BkJjW_I_.mjs +320 -0
- package/docs/arc42/01-introduction-and-goals.arc42.md +82 -0
- package/docs/arc42/02-constraints.arc42.md +228 -0
- package/docs/arc42/03-system-context.arc42.md +118 -0
- package/docs/arc42/04-solution-strategy.arc42.md +47 -0
- package/docs/arc42/05-building-blocks.arc42.md +307 -0
- package/docs/arc42/06-runtime-view.arc42.md +98 -0
- package/docs/arc42/07-deployment-view.arc42.md +160 -0
- package/docs/arc42/08-concepts.arc42.md +123 -0
- package/docs/arc42/09-decisions.arc42.md +246 -0
- package/docs/arc42/10-quality-requirements.arc42.md +155 -0
- package/docs/arc42/11-risks.arc42.md +113 -0
- package/docs/arc42/12-glossary.arc42.md +162 -0
- package/docs/arc42/architecture-evidence.md +395 -0
- package/explainer/.arc42ignore +3 -0
- package/explainer/README.md +54 -0
- package/explainer/_gitignore +5 -0
- package/explainer/docs/arc42/03-explainer-system-context.arc42.md +35 -0
- package/explainer/docs/arc42/05-explainer-building-blocks.arc42.md +659 -0
- package/explainer/docs/arc42/06-explainer-runtime-view.arc42.md +105 -0
- package/explainer/docs/arc42/07-explainer-deployment-view.arc42.md +34 -0
- package/explainer/docs/arc42/08-explainer-concepts.arc42.md +17 -0
- package/explainer/docs/arc42/09-explainer-decisions.arc42.md +443 -0
- package/explainer/docs/arc42/10-explainer-quality-requirements.arc42.md +54 -0
- package/explainer/docs/arc42/11-explainer-risks.arc42.md +17 -0
- package/explainer/docs/arc42/12-explainer-glossary.arc42.md +185 -0
- package/explainer/e2e/explainer.e2e.ts +3 -0
- package/explainer/index.html +11 -0
- package/explainer/package.json +18 -0
- package/explainer/playwright.config.ts +1 -0
- package/explainer/src/app.yaml +20 -0
- package/explainer/src/components/ProzentFeld.test.ts +30 -0
- package/explainer/src/components/ProzentFeld.ts +121 -0
- package/explainer/src/components/ProzentFeld.vue +84 -0
- package/explainer/src/images/hunderterfeld-klammern.svg +105 -0
- package/explainer/src/main.ts +4 -0
- package/explainer/src/prozent/chapter.yaml +18 -0
- package/explainer/src/prozent/grundwert.yaml +115 -0
- package/explainer/src/prozent/prozentsatz.yaml +99 -0
- package/explainer/src/prozent/prozentwert.yaml +119 -0
- package/explainer/tsconfig.json +1 -0
- package/explainer/vite.config.ts +4 -0
- package/interactive/.arc42ignore +5 -0
- package/interactive/README.md +28 -0
- package/interactive/_gitignore +4 -0
- package/interactive/docs/arc42/05-interactive-building-blocks.arc42.md +518 -0
- package/interactive/docs/arc42/06-interactive-runtime-view.arc42.md +58 -0
- package/interactive/docs/arc42/07-interactive-deployment-view.arc42.md +35 -0
- package/interactive/docs/arc42/08-interactive-concepts.arc42.md +52 -0
- package/interactive/docs/arc42/09-interactive-decisions.arc42.md +130 -0
- package/interactive/docs/arc42/10-interactive-quality-requirements.arc42.md +45 -0
- package/interactive/docs/arc42/11-interactive-risks.arc42.md +20 -0
- package/interactive/docs/arc42/12-interactive-glossary.arc42.md +80 -0
- package/interactive/e2e/interactive.e2e.ts +3 -0
- package/interactive/index.html +11 -0
- package/interactive/package.json +18 -0
- package/interactive/playwright.config.ts +1 -0
- package/interactive/src/app.yaml +28 -0
- package/interactive/src/core/index.ts +101 -0
- package/interactive/src/data/lessons.json +4 -0
- package/interactive/src/env.d.ts +6 -0
- package/interactive/src/main.ts +6 -0
- package/interactive/src/messages/de.json +12 -0
- package/interactive/src/playthroughs/grundreihe.yaml +33 -0
- package/interactive/src/playthroughs/obere-reihe.yaml +14 -0
- package/interactive/src/ui/App.vue +85 -0
- package/interactive/tsconfig.json +1 -0
- package/interactive/vite.config.ts +4 -0
- package/package.json +144 -4
- package/quiz/README.md +31 -0
- package/quiz/_gitignore +5 -0
- package/quiz/docs/arc42/05-quiz-building-blocks.arc42.md +283 -0
- package/quiz/docs/arc42/06-quiz-runtime-view.arc42.md +21 -0
- package/quiz/docs/arc42/07-quiz-deployment-view.arc42.md +33 -0
- package/quiz/docs/arc42/09-quiz-decisions.arc42.md +36 -0
- package/quiz/docs/arc42/10-quiz-quality-requirements.arc42.md +53 -0
- package/quiz/docs/arc42/11-quiz-risks.arc42.md +19 -0
- package/quiz/docs/arc42/12-quiz-glossary.arc42.md +27 -0
- package/quiz/e2e/quiz.e2e.ts +3 -0
- package/quiz/index.html +11 -0
- package/quiz/package.json +18 -0
- package/quiz/playwright.config.ts +1 -0
- package/quiz/src/images/berge-ordnen.jpg +0 -0
- package/quiz/src/images/everest-hoehe.jpg +0 -0
- package/quiz/src/images/galapagos.jpg +0 -0
- package/quiz/src/images/grand-canyon.jpg +0 -0
- package/quiz/src/images/totes-meer.jpg +0 -0
- package/quiz/src/images/uluru.jpg +0 -0
- package/quiz/src/images/wasserfaelle-laender.jpg +0 -0
- package/quiz/src/main.ts +4 -0
- package/quiz/src/quiz.json +312 -0
- package/quiz/tsconfig.json +1 -0
- package/quiz/vite.config.ts +4 -0
- package/runtime/explainer/assessment-client.ts +478 -0
- package/runtime/explainer/assessment-page.ts +446 -0
- package/runtime/explainer/assessment.ts +393 -0
- package/runtime/explainer/check-page.ts +520 -0
- package/runtime/explainer/components/exercise.ts +409 -0
- package/runtime/explainer/components/props.ts +230 -0
- package/runtime/explainer/components/read.ts +428 -0
- package/runtime/explainer/content.ts +277 -0
- package/runtime/explainer/e2e.ts +1474 -0
- package/runtime/explainer/exercises.ts +324 -0
- package/runtime/explainer/explainer.css +1283 -0
- package/runtime/explainer/explainer.ts +838 -0
- package/runtime/explainer/explainer.v1.schema.json +1165 -0
- package/runtime/explainer/expression.ts +407 -0
- package/runtime/explainer/fraction.ts +107 -0
- package/runtime/explainer/generated/comparator.ts +31 -0
- package/runtime/explainer/generated/question.ts +165 -0
- package/runtime/explainer/generated/soundness.ts +280 -0
- package/runtime/explainer/generated/texts.ts +257 -0
- package/runtime/explainer/generated/types.ts +103 -0
- package/runtime/explainer/generated/values.ts +136 -0
- package/runtime/explainer/islands.ts +281 -0
- package/runtime/explainer/load.ts +457 -0
- package/runtime/explainer/messages/de.json +200 -0
- package/runtime/explainer/messages/en.json +200 -0
- package/runtime/explainer/messages.ts +13 -0
- package/runtime/explainer/needs.ts +122 -0
- package/runtime/explainer/open-task.ts +235 -0
- package/runtime/explainer/outline.ts +299 -0
- package/runtime/explainer/plugin.ts +488 -0
- package/runtime/explainer/position.ts +62 -0
- package/runtime/explainer/prompt.ts +75 -0
- package/runtime/explainer/render.ts +686 -0
- package/runtime/explainer/result.ts +238 -0
- package/runtime/explainer/routes.ts +119 -0
- package/runtime/explainer/schema.ts +140 -0
- package/runtime/explainer/scope.ts +87 -0
- package/runtime/explainer/stuck.ts +57 -0
- package/runtime/explainer/tasks.ts +109 -0
- package/runtime/explainer/tutor.ts +469 -0
- package/runtime/explainer/tutorpage.ts +669 -0
- package/runtime/explainer/video.ts +123 -0
- package/runtime/interactive/contract.ts +121 -0
- package/runtime/interactive/e2e.ts +190 -0
- package/runtime/interactive/interactive.css +82 -0
- package/runtime/interactive/interactive.ts +115 -0
- package/runtime/interactive/interactive.v1.schema.json +146 -0
- package/runtime/interactive/load.ts +188 -0
- package/runtime/interactive/messages/de.json +14 -0
- package/runtime/interactive/messages/en.json +14 -0
- package/runtime/interactive/messages.ts +11 -0
- package/runtime/interactive/playthrough.v1.schema.json +143 -0
- package/runtime/interactive/plugin.ts +266 -0
- package/runtime/interactive/render.ts +126 -0
- package/runtime/quiz/e2e.ts +233 -0
- package/runtime/quiz/engine.ts +242 -0
- package/runtime/quiz/messages/de.json +23 -0
- package/runtime/quiz/messages/en.json +23 -0
- package/runtime/quiz/plugin.ts +150 -0
- package/runtime/quiz/quiz.css +154 -0
- package/runtime/quiz/quiz.ts +56 -0
- package/runtime/quiz/quiz.v1.schema.json +72 -0
- package/runtime/quiz/render.ts +173 -0
- package/runtime/quiz/validate.ts +108 -0
- package/runtime/shared/appname.ts +16 -0
- package/runtime/shared/blocks/block.ts +162 -0
- package/runtime/shared/blocks/blocks.css +190 -0
- package/runtime/shared/blocks/blocks.v1.schema.json +432 -0
- package/runtime/shared/blocks/code.ts +26 -0
- package/runtime/shared/blocks/figures.ts +49 -0
- package/runtime/shared/blocks/image.ts +25 -0
- package/runtime/shared/blocks/index.ts +13 -0
- package/runtime/shared/blocks/messages/de.json +4 -0
- package/runtime/shared/blocks/messages/en.json +4 -0
- package/runtime/shared/blocks/render.ts +134 -0
- package/runtime/shared/blocks/validate.ts +165 -0
- package/runtime/shared/content.ts +197 -0
- package/runtime/shared/examples.ts +20 -0
- package/runtime/shared/html.ts +10 -0
- package/runtime/shared/languages.ts +94 -0
- package/runtime/shared/page.css +101 -0
- package/runtime/shared/pages.ts +114 -0
- package/runtime/shared/questions/e2e.ts +115 -0
- package/runtime/shared/questions/engine.ts +209 -0
- package/runtime/shared/questions/index.ts +39 -0
- package/runtime/shared/questions/messages/de.json +26 -0
- package/runtime/shared/questions/messages/en.json +26 -0
- package/runtime/shared/questions/question.ts +430 -0
- package/runtime/shared/questions/question.v1.schema.json +342 -0
- package/runtime/shared/questions/questions.css +192 -0
- package/runtime/shared/questions/render.ts +247 -0
- package/runtime/shared/questions/validate.ts +518 -0
- package/runtime/shared/schema.ts +44 -0
- package/runtime/shared/text/check.ts +68 -0
- package/runtime/shared/text/formula.ts +69 -0
- package/runtime/shared/text/markdown.ts +185 -0
- package/runtime/shared/text/text.v1.schema.json +34 -0
- package/runtime/shared/vue-code.ts +395 -0
- 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
|
+
```
|