@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.
Files changed (231) 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-By2GGTJC.mjs +1696 -0
  5. package/dist/block-BK_ohsH1.mjs +82 -0
  6. package/dist/blocks-BmRrD7UY.mjs +149 -0
  7. package/dist/blocks.mjs +4 -0
  8. package/dist/check-BkEl-m-F.mjs +254 -0
  9. package/dist/content-cYBPYg_J.mjs +140 -0
  10. package/dist/contract-PONpxJtd.mjs +52 -0
  11. package/dist/e2e-BiMI-pCL.mjs +89 -0
  12. package/dist/engine-BqF2Vffn.mjs +136 -0
  13. package/dist/explainer-e2e.mjs +1297 -0
  14. package/dist/explainer-islands.mjs +197 -0
  15. package/dist/explainer-plugin.mjs +2131 -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-C-0hUaSC.mjs +16947 -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-BKc8JYtC.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-BJey0bwl.mjs +540 -0
  33. package/dist/render-BL6fV_NI.mjs +507 -0
  34. package/dist/render-DpRO_RKm.mjs +146 -0
  35. package/dist/result-_eQcamoz.mjs +180 -0
  36. package/dist/tasks-DADUYy52.mjs +74 -0
  37. package/dist/validate-COaZso7u.mjs +1072 -0
  38. package/dist/validate-CxtdeQ5S.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 +144 -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 +446 -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 +1474 -0
  142. package/runtime/explainer/exercises.ts +324 -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 +299 -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 +430 -0
  221. package/runtime/shared/questions/question.v1.schema.json +342 -0
  222. package/runtime/shared/questions/questions.css +192 -0
  223. package/runtime/shared/questions/render.ts +247 -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/text.v1.schema.json +34 -0
  230. package/runtime/shared/vue-code.ts +395 -0
  231. package/runtime/shared/vue.ts +38 -0
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 the lernapps.net contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md CHANGED
@@ -1,3 +1,196 @@
1
- # Temporary Holding Version
1
+ # app-templates
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The archetypes of [lernapps.net](https://lernapps.net/) apps: one template folder per archetype, and at the root the
4
+ package `@lernapps/app-templates` with the runtime of each archetype that apps depend on. It is part of the tooling
5
+ with which a creator and their AI assistant build a learning app.
6
+
7
+ ## Who it is for
8
+
9
+ - **Creators and their assistants** never open this repo: the generator of
10
+ [lernapps/tooling](https://github.com/lernapps/tooling) copies a template folder into a new app. Start at
11
+ [lernapps.net/apps/eintragen/](https://lernapps.net/apps/eintragen/), or, as an assistant, at
12
+ [lernapps.net/tooling/llms.txt](https://lernapps.net/tooling/llms.txt).
13
+ - **Maintainers of an archetype** change its template and its runtime here.
14
+
15
+ ## What is in it
16
+
17
+ What each archetype can and cannot do is in the README of its folder: [quiz](quiz/README.md),
18
+ [explainer](explainer/README.md), [interactive](interactive/README.md). Change it in the same pull request as the
19
+ feature: assistants read it before they choose an archetype.
20
+
21
+ | Folder | Archetype | What the app supplies |
22
+ |---|---|---|
23
+ | [`quiz/`](quiz/) | a quiz about things to know: questions with options, feedback on every option, score and solutions, in one or more languages (a page per language) | the question bank `src/quiz.json` ([schema](runtime/quiz/quiz.v1.schema.json)) |
24
+ | [`explainer/`](explainer/) | an explainer: chapters of pages with sections, formulas, figures, tables, timelines, definitions with a glossary, quotes, code and YouTube videos behind two clicks, and an exercise on every page (the quiz's question types, and `generated` exercises drawn from a seed and property-tested at build), readable without JavaScript, in one or more languages; an optional self-assessment of the app or a chapter, checked as a whole; Vue components for what no block can express | the content as YAML in `src/`: `app.yaml`, a folder per chapter with `chapter.yaml` and its pages, `data/` and `images/`, and `components/` ([schema](runtime/explainer/explainer.v1.schema.json)) |
25
+ | [`interactive/`](interactive/) | an interactive app whose doing is the content (a game, a trainer): a static start page, readable without JavaScript, and an app page with the app's own Vue components, its routes as `app/#<id>`; the build plays its playthroughs against its core | `src/app.yaml` ([schema](runtime/interactive/interactive.v1.schema.json)), the core `src/core/index.ts` (`start(seed, route)`, `step(state, input)` with a key or an action of its root component, optional `tick(state, dt)`), its components in `src/ui/`, data in `src/data/`, and `src/playthroughs/*.yaml` ([schema](runtime/interactive/playthrough.v1.schema.json)) |
26
+
27
+ - [`runtime/`](runtime/): the runtime of each archetype, published to npm as `@lernapps/app-templates` (with the template folders, which `lernapps create` of `@lernapps/tooling` copies out of the installed package); its exports
28
+ are in [`package.json`](package.json), what each does in the comments of its source. The quiz's questions also work
29
+ one by one, for other archetypes that embed them as exercises: `@lernapps/app-templates/quiz/question` (schema,
30
+ validation, rendering, checker, messages; a question's texts may be maps by language), `startQuestion` of `@lernapps/app-templates/quiz` and the helpers of
31
+ `@lernapps/app-templates/quiz/e2e`; [`test/fixtures/question-page/`](test/fixtures/question-page/) is a page built so.
32
+ - The placeholders of the explainer's `generated` exercises are always two braces around a declared name or its
33
+ field, nothing else inside: `{{G}}`, `{{e.name}}`, `{{answer}}`, in prose and in formulas alike
34
+ (`$\frac{{{W}}}{{{G}}} \cdot 100 = {{answer}}$`). One brace is never a placeholder, so in a formula `G`, `{G}` and
35
+ `\frac{W}{G}` are letters; the only LaTeX form this reserves, a double group around a lone name, means the same as
36
+ the name alone. The build fails on a placeholder whose name `given` does not declare, on one brace or spaces where a
37
+ placeholder was meant in prose (`{G}`, `{{ G }}`), on a variable of `given` that nothing uses (no placeholder, and
38
+ no expression of the figure, answer or mistakes; `where` does not count), and on a placeholder left after filling in
39
+ ([`generated.ts`](runtime/explainer/generated.ts)).
40
+ - An explainer's components are Vue single-file components in `src/components/<Name>.vue`, each with
41
+ `<script setup lang="ts">`, typed props and its test `<Name>.test.ts`, used by a `vue-component` block with its
42
+ `props` and a `nojs` text. The build renders each with Vue's server renderer into its island, `hidden` until it
43
+ hydrates; only a page with a component loads Vue. It checks each block's props against the component's prop types,
44
+ and on the SFC compiler's output: no network, no `eval`, `new Function`, `innerHTML` or `v-html`, imports only from
45
+ `vue`, this runtime, `@lernapps/tooling/storage`, `i18n` and `a11y` and the folder itself, browser globals only in
46
+ `onMounted`, and 20 kB gzip of the component's own code. Once it runs, it replaces its `nojs` text, or keeps it
47
+ folded as a text description under it with `defineOptions({ nojs: "caption" })`. A component that emits
48
+ `answer` is an exercise, and only the input of its task: it emits the learner's answer each time it changes
49
+ (`undefined` while there is none) and draws no "Prüfen" of its own; the explainer renders its island in a form with
50
+ "Prüfen" under it (Enter in a field checks too), calls `check`, `reason` and `show` of its logic module and gives the
51
+ feedback, and sets the prop `checked: boolean`, when the component declares it, until the answer changes. It
52
+ declares the prop `seed: number`, the seed of its task number, which the explainer gives it with "Aufgabe Nr."
53
+ and "Neue Aufgabe" and keeps in the address (`?<id>=<n>`, the id of its block or its name in kebab case), and
54
+ its logic module `<Name>.ts` exports `generate(seed, props)`, `check(task, answer)`, `solution(task)` and
55
+ `wrong(task)`, which the build tests over 50 fixed seeds per block, `reason(task, answer, props, lang)`, the app's
56
+ message for a wrong answer (`undefined` for none), `describe(task, props, lang)`, the task and its
57
+ solution as a learner reads them (`{ task, solution }`, restricted Markdown), and `about`, what the exercise is for a
58
+ tutor (`{ task, answer, correct, mistakes }`). The build checks `describe` for every task number in every
59
+ language and writes the no-JS text of an exercise component from it: the task of number 1 with its solution folded,
60
+ so it needs no `nojs` ([`components.ts`](runtime/explainer/components.ts)). For its table of tasks on the tutor
61
+ page it also exports, all four or none, `template(props, lang)`, the task once with `{name}` for each input,
62
+ `params(task, props, lang)`, the inputs of a task by name (`{ G: 1260, p: 15 }`, the same names for every task),
63
+ `mistakes(task, props)`, the answer each typical mistake of `about.mistakes` gives (`undefined` for one without a
64
+ single answer), and `show(answer, task, props, lang)`, an answer as text. The build checks them for every task
65
+ number: `check` accepts the solution and rejects each mistake's answer, and each placeholder is an input. The
66
+ table leaves out the column of a mistake without an answer for any number, and calls an input that is no
67
+ placeholder of the template "(verdeckt)", a value the child does not see. Without them the build gives a hint
68
+ (`vue-component-tutor-table`).
69
+ - Every seeded exercise (generated, exercise component) numbers its distinct tasks: the build walks the seeds 1, 2,
70
+ 3, … through its generator, keeps each seed whose task differs from all before, up to 67, and the page shows
71
+ "Aufgabe Nr. <n>" for them ([`tasks.ts`](runtime/explainer/tasks.ts)). The number is the one identifier of a task:
72
+ `?<id>=<n>` opens task number n of the exercise `<id>` (a number above N wraps around). The address holds one
73
+ parameter per exercise (`?a=3&b=12`), written on load and at every "Neue Aufgabe", so it is the page's state: a link
74
+ to share in class, a deep link, and what a tutor reads; `seed`, `via`, `uebung` and `abschnitt` are no exercise id, and `?seed=<n>#<id>`
75
+ still opens that exercise's task. The build ships each exercise's table of seeds. An exercise with fewer than 67 distinct tasks is
76
+ noted in the build output.
77
+ - For a KI tutor, an assistant the learner brings and controls, the explainer writes static files only (a chat
78
+ assistant that fetches a URL runs no JavaScript, and fetches only addresses its user wrote into the conversation,
79
+ not those it finds in a fetched file). Per language tree and content page it writes the page's tutor page
80
+ (`<chapter>/<page>/tutor/`, [`tutorpage.ts`](runtime/explainer/tutorpage.ts)), the one source of what a tutor gets:
81
+ short, lean HTML without script or menu, which parents and teachers can open too, and its Markdown beside it
82
+ (`index.md`). It tells the tutor that it helps a child who is at an exercise on this page of a lernapps.net app; how
83
+ to guide (short answers without headings or lists, the page's own examples and tasks, nothing worked out by
84
+ itself, explaining in the chat and solving in the app, no solution, never asking to copy a task); how to start: to
85
+ learn the page, ask what the child knows, then lead through the page section by section, check each with a question
86
+ on its content, and only then send the child to its task; with "Hilfe", start at the task, and after a wrong input
87
+ with the app's message first go briefly into exactly that input and the task (which mistake it is); where the child is (the
88
+ task numbers in its address, with "Hilfe" also `uebung` and `abschnitt`, the app's message) and how to give a
89
+ further task; the app's own rules from `tutor.rules` in `app.yaml`. Then the page's content as context (texts,
90
+ formulas as `$…$`, examples, definitions, notes, figures by alt text and caption) and its exercises with every task
91
+ number. A `generated` one is there once as its template (`{G}`), with answer format, hint, steps, its typical
92
+ mistakes as F1, F2 … with the app's messages and the link pattern `../?<id>=n&via=tutor#<id>`, and below a table of
93
+ each task number's inputs and outputs, all worked out: `Nr. | p | G | Lösung | F1`, `27 | 15 | 1260 | 189 | 18900`.
94
+ An exercise component has its `about`, and with its table of tasks its task once as its template, its typical
95
+ mistakes as F1, F2 … and the same table as a generated one, `Lösung` and `F1` as `show` gives them, with every
96
+ task number. Without the table it has each task number's task and solution from its `describe`; where these would
97
+ make the page larger than 64 kB, the components show as many numbers as fit, the page says so, and the build warns. A question is there in
98
+ full. There is no `llms.txt`: the tutor page and the pages themselves are what an assistant reads. The build checks
99
+ every link and anchor of the tutor pages. Two ways lead to the tutor. The learner switches an assistant on once: on the
100
+ start page ("Dein KI-Assistent", "Keiner" first) or beside the title of a content page ("Mit einem KI-Tutor
101
+ lernen", folded while none is on: a select and "Einschalten"). The choice is one of the KI assistants the runtime
102
+ offers, the same for every app, or "Ein anderer: Text kopieren"; it is kept on the
103
+ device, per app (`@lernapps/tooling/storage`), and both places say that the learner uses an account of their own
104
+ and that the assistant is no offer of lernapps.net, which only provides the content and the exercises. A chapter
105
+ page starts no tutor. Way A: once an assistant is on, a bar beside the title of every content page with one button,
106
+ "Mit <name> lernen", with the assistant's first letter as its symbol; the start page changes the assistant or
107
+ switches it off. An assistant opens with the start prompt, one sentence in the learner's voice: "Lade <tutor
108
+ page>?<id>=<n>&… und sei mein Tutor.", with the page's task numbers alone, so that the tutor
109
+ leads through the page before it comes to a task. ([`prompt.ts`](runtime/explainer/prompt.ts),
110
+ [`position.ts`](runtime/explainer/position.ts)). Copying the text instead adds the tutor page's Markdown, for an
111
+ assistant that fetches nothing. Without JavaScript the section has a plain link per assistant. Way B: while an
112
+ assistant is on, every exercise (generated, question, exercise component) has one help button with its symbol, in
113
+ the row of "Prüfen" and "Lösung" (below an exercise component): "Hilfe" before an answer and after the right one,
114
+ "Warum?", standing out, after a wrong one. It gives the lines of that task ([`stuck.ts`](runtime/explainer/stuck.ts)),
115
+ in the learner's voice too: without a chat (B1) "Lade <tutor page>?… und hilf mir bei Übung <id>, Nr. <n>. Ich komme
116
+ nicht weiter.", then `Ich habe <input> eingegeben (<n> Versuche).`, `Die App sagt: „<message>“` (word for word
117
+ what the feedback said: a generated exercise's message of its mistake or the general one, a question's reasons, an
118
+ exercise component's `message`, else its `reason`) and the hint or solution opened; on a page opened with
119
+ `via=tutor` (B2), where the button is there without an assistant switched on, it copies `Übung <id>, Nr. <n>:`,
120
+ the same lines and `Tutor-Seite: <tutor page>?…` for the running chat, as "Zurück zum Tutor" does, and once the task
121
+ is right or its solution shown it is "Zurück", which copies the result line. No prompt and no line carries a rule
122
+ for the tutor: the tutor page has them. After a wrong answer a sentence under the row says that input and message
123
+ leave the page only with the click. "Neue Aufgabe" is a circular arrow beside the task number. A page opened with
124
+ `via=tutor` shows "Zurück zum Tutor" at its top and end: it copies the page's tutor page with its task numbers and a
125
+ line per task of the session (the exercise, its task number, "richtig im 2. Versuch", "Lösung angesehen" or "noch
126
+ nicht gelöst (1 Versuch, Eingabe …)" with the app's message), then the count of the session. The tab stays open, and
127
+ the button says "✓ Kopiert" and a line below it where to paste; when copying fails, the lines are selected in a
128
+ field. The line goes to the clipboard only
129
+ ([`result.ts`](runtime/explainer/result.ts)).
130
+ - An explainer's `video` block (`youtube`, `title`, `channel`) is a YouTube video behind two clicks: the page, with or
131
+ without JavaScript, shows the title as a link to youtube.com, the channel and a note that playing it sends data,
132
+ among them the IP address, to YouTube/Google, and requests nothing from another host. With JavaScript, "Video
133
+ abspielen" puts the player of youtube-nocookie.com into the page, and "Videos immer direkt laden" is kept on the
134
+ device (`@lernapps/tooling/storage`) and loads every player without the click until the learner unticks it.
135
+ The page's tutor page names the video in its section as a link with title and channel, one source among the
136
+ page's content ([`video.ts`](runtime/explainer/video.ts)).
137
+ - Every exercise may carry `examples: { accepted, rejected }`, test cases that learners never see: the other correct
138
+ answers its text allows, and typical wrong ones. The build runs each through the same check the page uses (the
139
+ quiz's checker, a generated exercise's comparator for every seed, an exercise component's `check`) and fails on an
140
+ accepted example the check rejects or a rejected one it accepts. An exercise component's block must have at least
141
+ one of each, as its check is the app's own code; for every other exercise they are optional
142
+ ([`examples.ts`](runtime/shared/examples.ts)).
143
+ - Both archetypes write `404.html` at the bundle's root: GitHub Pages serves it for any address the app does not
144
+ have, and its script finds the app's root (the nearest folder upwards with a `404.html`, so a preview below
145
+ `pr-preview/pr-<n>/` finds its own) and moves on to its start page
146
+ ([`pages.ts`](runtime/shared/pages.ts)).
147
+ - Which version of this package a new app gets and what the generator adds to the template:
148
+ `npx --yes @lernapps/tooling create --help`.
149
+ - What an archetype is and how the parts fit: the [architecture of the tooling](https://lernapps.net/tooling/architecture/).
150
+ - A template has no `prepare` script of its own, so `npm install` in its folder leaves this repo's git hooks alone;
151
+ the generator gives the app the one that installs them.
152
+
153
+ ## Architecture
154
+
155
+ Every archetype follows one architecture, because they share its quality goals: the generic part in
156
+ [`docs/arc42/`](docs/arc42/) (the shared runtime with page trees, 404 page and examples check, and the tooling
157
+ helpers), and one extension per archetype in its folder, which adds the archetype's runtime (build step, validation,
158
+ rendering, shell, end-to-end tests) and the parts its creator writes. An app's architecture is both together.
159
+
160
+ - **Quiz** ([`quiz/docs/arc42/`](quiz/docs/arc42/)): the quiz rendering writes the bank into a worksheet page, the
161
+ bank validation checks `src/quiz.json`, the quiz engine checks answers with the question types' checker and keeps
162
+ only the last result on the device when the bank asks; no tutor.
163
+ - **Explainer** ([`explainer/docs/arc42/`](explainer/docs/arc42/)): the page rendering writes the YAML pages and the
164
+ component islands at build, the content validation guards the content, the client module and the islands check
165
+ answers in the browser and keep only the chosen way to the tutor, the choice to load videos directly and the last
166
+ result of a self-assessment; the tutor handoff, task numbers, the two-click video and the self-assessment are built
167
+ for it.
168
+ - **Interactive** ([`interactive/docs/arc42/`](interactive/docs/arc42/)): the start page and app page render from
169
+ `src/app.yaml`, the build checks the core and plays the playthroughs, the shell mounts the root component and feeds
170
+ the core inputs (today keys), ticks, route and seed; no tutor yet. Only an interactive app adds an architecture part
171
+ of its own.
172
+
173
+ `node scripts/arc42.ts` validates the generic part with each extension (`arc42 validate --strict`, `--root` at the
174
+ repo root) and fails on any finding; the pre-commit hook runs it when an `*.arc42.md` or `.arc42ignore` file is staged,
175
+ and CI on every change.
176
+
177
+ ## Contributing
178
+
179
+ `npm ci`, then the steps of the workflow [`check.yml`](.github/workflows/check.yml): the runtime's checks at the root
180
+ (with `npm run check:vue`, the type check of the fixtures' `.vue` files) and `lernapps check` in every template folder
181
+ and in `test/fixtures/question-page/`. `npm ci` also installs this repo's git hooks ([`.vite-hooks/`](.vite-hooks/)):
182
+ the pre-commit hook formats and fixes the staged files and checks a staged architecture (`vp staged`); the pre-push hook
183
+ ([`scripts/pre-push.ts`](scripts/pre-push.ts)) checks nothing for a push of only the plan (`.vibe/**`) or only docs
184
+ (`docs/**/*.md`, the change scope of `lernapps changes`, whose classifier the hook fetches once at the commit check.yml
185
+ uses), and otherwise runs the root checks and `lernapps check --pre-push` in each template folder the push affects.
186
+ Install a template's folder (`npm install` in it) to push a change to it. Which paths affect which template is a
187
+ positive list, `TEMPLATE_PATHS`: a path on no list runs the root checks only, and CI, which gates every job on the
188
+ change scope and runs all of them for any code change, catches the rest; when it catches what the hook let through, add
189
+ the path to the list. The fixture jobs run only in CI. The job `check` reports the overall result and is the check to
190
+ require. A change reaches new apps once it is released ([`release.yml`](.github/workflows/release.yml): after a merge to `main`,
191
+ semantic-release takes the version from the conventional commits, tags `v<version>` and publishes to npm with provenance) and lernapps/tooling depends on that version; Renovate moves existing apps. The package leaves out
192
+ each template's `.gitignore`, which npm never ships: `prepack` stages it as `_gitignore` ([`scripts/pack-dotfiles.ts`](scripts/pack-dotfiles.ts)), and `lernapps create` writes it back.
193
+
194
+ ## License
195
+
196
+ [MIT](LICENSE). Apps created from these templates start with MIT as well.
@@ -0,0 +1,12 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ // runtime/shared/appname.ts
4
+ /** The name in the package.json of the app in `root`, or `fallback` when there is none. */
5
+ function appName(root, fallback) {
6
+ try {
7
+ const manifest = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
8
+ if (typeof manifest === "object" && manifest !== null && "name" in manifest && typeof manifest.name === "string") return manifest.name;
9
+ } catch {}
10
+ return fallback;
11
+ }
12
+ export { appName as t };