@lernapps/tooling 0.2.1 → 0.3.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/dist/cli.mjs CHANGED
@@ -1432,8 +1432,11 @@ const CREATE_USAGE = `Usage: lernapps create --archetype <name> [<dir>] [--plan
1432
1432
  Writes a new app from the archetype's template into <dir> (default: the current folder), which must be empty
1433
1433
  except for .git, the first version's plan .vibe/plans/01-<short name>.md and the skills that
1434
1434
  lernapps init synced (.agents/skills/, .claude/skills/, skills-lock.json, and the package.json, package-lock.json and
1435
- node_modules init wrote). The template is the folder <name>/ of the installed package @lernapps/app-templates, the
1436
- version @lernapps/tooling depends on; the app depends on the runtime of the same version.
1435
+ node_modules init wrote). From the installed package @lernapps/app-templates, the version @lernapps/tooling depends
1436
+ on, it writes the archetype's scaffold: the files of the template folder <name>/ that the package's lernapps.scaffold
1437
+ lists (the entry, the configuration, the end-to-end tests of the content) and a placeholder as start content
1438
+ (scaffold/<name>/), which the planned content replaces. The template folder itself is the example app: it stays in
1439
+ node_modules/@lernapps/app-templates/<name>/, to look up. The app depends on the runtime of the same version.
1437
1440
 
1438
1441
  It needs the plan of the first version, confirmed by the creator: started from the plan template as
1439
1442
  .vibe/plans/01-<short name>.md, the only plan there, worked through Explore and Plan, now in the phase code
@@ -1445,7 +1448,7 @@ Run it in the folder lernapps init prepared, from the tooling init installed:
1445
1448
  It needs git, and the npm registry for the npm install that follows. The app then depends on the version of
1446
1449
  @lernapps/tooling that created it, and its workflows use the actions of that version's tag.
1447
1450
 
1448
- It writes, besides the template: package.json named after the short name given to init (the folder's name without init) (its prepare script, lernapps hooks,
1451
+ It writes, besides the scaffold: package.json named after the short name given to init (the folder's name without init) (its prepare script, lernapps hooks,
1449
1452
  installs the git hooks; for an explainer and an interactive app also vue-tsc and typescript, with which lernapps
1450
1453
  check type-checks its Vue components; for an interactive app also @doctc/arc42, which checks its architecture),
1451
1454
  for an interactive app its architecture docs/arc42/ (the generic part and the interactive extension of app-templates,
@@ -1497,7 +1500,8 @@ function templatesPackage(dir) {
1497
1500
  return {
1498
1501
  root,
1499
1502
  version: manifest.version ?? "0.0.0",
1500
- archetypes: [...manifest.lernapps?.archetypes ?? []].sort()
1503
+ archetypes: [...manifest.lernapps?.archetypes ?? []].sort(),
1504
+ scaffold: manifest.lernapps?.scaffold ?? {}
1501
1505
  };
1502
1506
  }
1503
1507
  /** The version of this package. */
@@ -1545,6 +1549,27 @@ function git(args, cwd) {
1545
1549
  }
1546
1550
  /** A package name from the folder's name: lower case, letters, digits and hyphens. */
1547
1551
  const packageName = (dir) => basename(dir).toLowerCase().normalize("NFKD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "app";
1552
+ /**
1553
+ * The scaffold of the archetype into `dir` (decision dec-scaffold of app-templates): the paths of its template folder
1554
+ * that lernapps.scaffold.<archetype> of the package lists (the entry, the configuration, the e2e entry), then the start
1555
+ * content scaffold/<archetype>/, a placeholder the creator replaces. The rest of the template folder is the example
1556
+ * app; it stays in node_modules/@lernapps/app-templates/<archetype>/ for the creator's assistant to look up.
1557
+ */
1558
+ function writeScaffold(templates, archetype, dir) {
1559
+ const paths = templates.scaffold[archetype];
1560
+ if (paths === void 0 || paths.length === 0) throw new CreateError(`${templates.root} (version ${templates.version}) names no scaffold for ${archetype} (lernapps.scaffold in its package.json): use the version of ${RUNTIME} this tooling depends on`);
1561
+ const template = join(templates.root, archetype);
1562
+ for (const path of paths) {
1563
+ const shipped = basename(path) === ".gitignore" ? join(template, dirname(path), "_gitignore") : void 0;
1564
+ const source = shipped !== void 0 && existsSync(shipped) ? shipped : join(template, path);
1565
+ if (!existsSync(source)) throw new CreateError(`${template} has no ${path}, which its scaffold lists`);
1566
+ mkdirSync(dirname(join(dir, path)), { recursive: true });
1567
+ cpSync(source, join(dir, path));
1568
+ }
1569
+ const start = join(templates.root, "scaffold", archetype);
1570
+ if (!existsSync(start)) throw new CreateError(`${templates.root} has no start content scaffold/${archetype}/`);
1571
+ cpSync(start, dir, { recursive: true });
1572
+ }
1548
1573
  /** This package, as an app depends on it. */
1549
1574
  const TOOLING = "@lernapps/tooling";
1550
1575
  /**
@@ -1698,18 +1723,9 @@ function create(args) {
1698
1723
  const initialised = existsSync(join(dir, "package.json")) ? readJson(join(dir, "package.json")) : void 0;
1699
1724
  const initialLock = existsSync(join(dir, "package-lock.json")) ? readFileSync(join(dir, "package-lock.json"), "utf8") : void 0;
1700
1725
  mkdirSync(dir, { recursive: true });
1701
- cpSync(join(templates.root, archetype), dir, { recursive: true });
1702
- rmSync(join(dir, "package-lock.json"), { force: true });
1726
+ writeScaffold(templates, archetype, dir);
1703
1727
  if (initialLock !== void 0) writeFileSync(join(dir, "package-lock.json"), initialLock);
1704
- if (existsSync(join(dir, "_gitignore"))) renameSync(join(dir, "_gitignore"), join(dir, ".gitignore"));
1705
- rmSync(join(dir, "README.md"), { force: true });
1706
- rmSync(join(dir, ARCHITECTURE), {
1707
- recursive: true,
1708
- force: true
1709
- });
1710
- if (existsSync(join(dir, "docs")) && readdirSync(join(dir, "docs")).length === 0) rmSync(join(dir, "docs"), { recursive: true });
1711
1728
  if (architecture) writeArchitecture(dir, templates.root, archetype, templates.version);
1712
- else rmSync(join(dir, ".arc42ignore"), { force: true });
1713
1729
  const app = readJson(join(dir, "package.json"));
1714
1730
  const name = initialised?.lernapps?.init === true && typeof initialised.name === "string" ? initialised.name : packageName(dir);
1715
1731
  app.name = name;
@@ -1717,12 +1733,15 @@ function create(args) {
1717
1733
  ...app.scripts,
1718
1734
  prepare: "lernapps hooks"
1719
1735
  };
1720
- if (app.devDependencies?.["@lernapps/app-templates"] !== void 0) {
1721
- app.devDependencies = {
1722
- ...app.devDependencies,
1723
- [RUNTIME]: `^${templates.version}`
1724
- };
1725
- rmSync(join(dir, ".npmrc"), { force: true });
1736
+ if (app.devDependencies?.["@lernapps/app-templates"] !== void 0) app.devDependencies = {
1737
+ ...app.devDependencies,
1738
+ [RUNTIME]: `^${templates.version}`
1739
+ };
1740
+ const overrides = app["overrides"];
1741
+ if (typeof overrides === "object" && overrides !== null && "@lernapps/app-templates" in overrides) {
1742
+ const kept = Object.fromEntries(Object.entries(overrides).filter(([name]) => name !== RUNTIME));
1743
+ if (Object.keys(kept).length > 0) app["overrides"] = kept;
1744
+ else delete app["overrides"];
1726
1745
  }
1727
1746
  const tooling = values.tooling ?? `^${ownVersion()}`;
1728
1747
  app.devDependencies = {
@@ -147,6 +147,7 @@ severity: error
147
147
  ```
148
148
 
149
149
  - Texts for learners, teachers, parents: German, plain (short sentences, active voice, no jargon).
150
+ - Never English only: an app in English is German and English, German first.
150
151
  - More languages only where the archetype supports them (explainer, quiz: `languages`, German first); each declared
151
152
  language complete, no other.
152
153
  - Texts out of code: in the content (explainer YAML, quiz bank) or message files (`src/messages/de.json`, shown
@@ -8,7 +8,7 @@ license: MIT
8
8
 
9
9
  - You write YAML in `src/`; a Vue component only where the plan names one. Rendering, navigation, formulas, figures,
10
10
  exercises, accessibility, languages and the no-JS page come from the runtime `@lernapps/app-templates`.
11
- - `create` writes the example chapter "Prozent": replace it with the planned content.
11
+ - `create` writes a placeholder: one chapter with one page and one exercise. Replace it with the planned content. The complete example, the chapter "Prozent" with a component, is in `node_modules/@lernapps/app-templates/explainer/`: look things up there, never copy it.
12
12
  - Schema: `node_modules/@lernapps/app-templates/runtime/explainer/explainer.v1.schema.json`. Start every YAML file with
13
13
  `# yaml-language-server: $schema=../../node_modules/@lernapps/app-templates/runtime/explainer/explainer.v1.schema.json`
14
14
  (one `../` less in `src/app.yaml`).
@@ -69,6 +69,8 @@
69
69
  ## Languages
70
70
 
71
71
  - `languages` in `src/app.yaml`, German first: `[de]` or `[de, en]` (runtime texts: de, en).
72
+ - Never English only: with `[de]` the pages and the runtime's texts are German, so English strings mix both.
73
+ English wanted → `[de, en]`.
72
74
  - A string = German, enough only while German is the only language. Else every text a map:
73
75
  `title: { de: Die drei Stände, en: The three estates }`.
74
76
  - Build fails on a missing translation (`the translation is missing; write the text as a map: { de: …, en: … }`)
@@ -47,7 +47,7 @@
47
47
  `show(answer, task, props, lang)` (answer as text). An input of `params` that is no placeholder shows as
48
48
  "(verdeckt)": the tutor knows it, the child does not. One `template` per block: prefer one kind of task per block;
49
49
  else give the sentence that changes as an input (`{Aufgabe}`). `about.answer` names what the child does and "Prüfen". The build checks them against `check` for every number; rule `vue-component-tutor-table`. Model:
50
- `ProzentFeld` of the template.
50
+ `ProzentFeld` of the template, in `node_modules/@lernapps/app-templates/explainer/src/components/`.
51
51
  - More: rule `vue-component-exercise-contract`.
52
52
 
53
53
  ## Rendering
@@ -194,7 +194,7 @@ severity: hint
194
194
  - The tutor page then has the task once, the mistakes as F1, F2 … and a table of every number (inputs, solution,
195
195
  each mistake), like `generated`. Without them: each number's `describe` as far as 64 kB allow, and the build's hint
196
196
  names the block.
197
- - Example: `src/components/ProzentFeld.ts` of the template.
197
+ - Example: `node_modules/@lernapps/app-templates/explainer/src/components/ProzentFeld.ts`, the template's.
198
198
 
199
199
  ## `vue-component-tested`: A test per component
200
200
 
@@ -44,7 +44,8 @@ license: MIT
44
44
 
45
45
  ## Decide
46
46
 
47
- - Languages: German; English as a second language is supported. Each declared language complete.
47
+ - Languages: German; English as a second language is supported. Each declared language complete. Never English
48
+ only: without `languages` the page and the runtime's texts are German. English wanted → `[de, en]`.
48
49
  - The tutor: the build writes it from the content. Plan only app-specific tutor rules, if any.
49
50
 
50
51
  ## Gotchas
@@ -7,7 +7,7 @@ license: MIT
7
7
  # Interactive app
8
8
 
9
9
  - Runtime: `@lernapps/app-templates` (`runtime/interactive/`); leave it untouched.
10
- - `create` writes a small example app: replace it with the planned parts.
10
+ - `create` writes a placeholder: a counter as core, component and playthrough. Replace it with the planned parts. The complete example, a typing game, is in `node_modules/@lernapps/app-templates/interactive/`: look things up there, never copy it.
11
11
 
12
12
  ## What you write
13
13
 
@@ -39,7 +39,8 @@ license: MIT
39
39
  - Accessibility: WCAG 2.1 AA and 360 px are the default. A deviation the idea needs (a time limit) goes into the
40
40
  entry's `limits`, in German.
41
41
  - Look and feel: as the creator chose in Explore.
42
- - Languages: German; each declared language complete; learner texts in message files, never in code.
42
+ - Languages: German; each declared language complete; learner texts in message files, never in code. Never English
43
+ only: German comes first. English wanted → German and English.
43
44
 
44
45
  ## Gotchas
45
46
 
@@ -9,7 +9,7 @@ license: MIT
9
9
  - You write `src/quiz.json` and, for the content of its questions, the image files in `src/images/`. Engine, question
10
10
  types, feedback, scoring, order, accessibility and the no-JS page come from the runtime `@lernapps/app-templates`.
11
11
  - Schema: `node_modules/@lernapps/app-templates/runtime/quiz/quiz.v1.schema.json`.
12
- - `create` writes the example bank "Naturwunder der Welt": replace it with the planned questions.
12
+ - `create` writes a placeholder bank with one question. Replace it with the planned questions. The complete example, the bank "Naturwunder der Welt", is in `node_modules/@lernapps/app-templates/quiz/`: look things up there, never copy it.
13
13
  - The fields of each question type and how to write good questions: skill `lernapps-content-questions`.
14
14
 
15
15
  ## Gotchas
@@ -50,7 +50,8 @@ license: MIT
50
50
  - German; more with `"languages": ["de", "en"]`, German first (runtime texts: de, en).
51
51
  - Then every learner text is a map with each language: title, intro, source titles; per question text, options,
52
52
  `info`, items, pairs, unit, explanation, link titles.
53
- - Without `languages`: German strings.
53
+ - Without `languages`: German strings. The page is German then (`lang="de"`, the runtime's own texts such as
54
+ "Frage 1 von 5"); English strings give a page that mixes both. Never English only: English wanted → `[de, en]`.
54
55
  - Link `url`: one address, or a map per language (own Wikipedia).
55
56
  - Build errors, one line each, e.g.
56
57
  `questions[1].explanation (en) (question "totes-meer"): the translation is missing; write the text as a map: { de: …, en: … }`;
@@ -42,7 +42,8 @@ license: MIT
42
42
 
43
43
  ## Decide
44
44
 
45
- - Languages: German; English as a second language is supported. Each declared language complete.
45
+ - Languages: German; English as a second language is supported. Each declared language complete. Never English
46
+ only: without `languages` the page and the runtime's texts are German. English wanted → `[de, en]`.
46
47
  - Keep the last score on the device (`rememberLastResult`) or nothing. Keeping it → the entry says
47
48
  `fitness.storage: device`.
48
49
  - Order: shuffled per seed; a link with `?seed=42` gives every learner the same order.
@@ -1,8 +1,9 @@
1
1
  // The generator, `lernapps create --archetype <name> [<dir>]`: writes a new app into an empty folder, once the creator
2
2
  // has confirmed the first version's plan (.vibe/plans/01-<short name>.md, in the phase code, for this archetype). It
3
- // copies the archetype's folder out of the installed package @lernapps/app-templates (a dependency of this package, at
4
- // an exact version), and makes the app depend on that same version of the runtime, so template and runtime always
5
- // match. Then it writes what every app has: AGENTS.md, the
3
+ // writes the archetype's scaffold out of the installed package @lernapps/app-templates (a dependency of this package,
4
+ // at an exact version): the paths of the template folder its lernapps.scaffold lists and the placeholder in
5
+ // scaffold/<archetype>/, not the example app, which stays in node_modules for the assistant to look up. It makes the
6
+ // app depend on that same version of the runtime, so template and runtime always match. Then it writes what every app has: AGENTS.md, the
6
7
  // plan, the content guide (an app-local skill), the workflows, LICENSE, an issue form for content errors and the
7
8
  // Renovate configuration (generator/app/); an interactive app also its architecture (generator/architecture.ts). The
8
9
  // skills `lernapps init` synced into the folder stay, and are committed with the app; `lernapps update` replaces them
@@ -26,7 +27,7 @@ import { createRequire } from "node:module";
26
27
  import { basename, dirname, join, relative, resolve } from "node:path";
27
28
  import { fileURLToPath } from "node:url";
28
29
  import { parseArgs, type ParseArgsOptionsConfig } from "node:util";
29
- import { ARCHITECTURE, ARCHITECTURE_ARCHETYPES, writeArchitecture } from "./architecture.ts";
30
+ import { ARCHITECTURE_ARCHETYPES, writeArchitecture } from "./architecture.ts";
30
31
  import { LEGACY_PLAN, MIGRATED_PLAN, PLAN_NAME, PLANS, planFrontMatter, plans } from "../src/plan.ts";
31
32
  import { UsageError } from "../src/usage.ts";
32
33
 
@@ -44,8 +45,11 @@ export const CREATE_USAGE = `Usage: lernapps create --archetype <name> [<dir>] [
44
45
  Writes a new app from the archetype's template into <dir> (default: the current folder), which must be empty
45
46
  except for .git, the first version's plan .vibe/plans/01-<short name>.md and the skills that
46
47
  lernapps init synced (.agents/skills/, .claude/skills/, skills-lock.json, and the package.json, package-lock.json and
47
- node_modules init wrote). The template is the folder <name>/ of the installed package @lernapps/app-templates, the
48
- version @lernapps/tooling depends on; the app depends on the runtime of the same version.
48
+ node_modules init wrote). From the installed package @lernapps/app-templates, the version @lernapps/tooling depends
49
+ on, it writes the archetype's scaffold: the files of the template folder <name>/ that the package's lernapps.scaffold
50
+ lists (the entry, the configuration, the end-to-end tests of the content) and a placeholder as start content
51
+ (scaffold/<name>/), which the planned content replaces. The template folder itself is the example app: it stays in
52
+ node_modules/@lernapps/app-templates/<name>/, to look up. The app depends on the runtime of the same version.
49
53
 
50
54
  It needs the plan of the first version, confirmed by the creator: started from the plan template as
51
55
  .vibe/plans/01-<short name>.md, the only plan there, worked through Explore and Plan, now in the phase code
@@ -57,7 +61,7 @@ Run it in the folder lernapps init prepared, from the tooling init installed:
57
61
  It needs git, and the npm registry for the npm install that follows. The app then depends on the version of
58
62
  @lernapps/tooling that created it, and its workflows use the actions of that version's tag.
59
63
 
60
- It writes, besides the template: package.json named after the short name given to init (the folder's name without init) (its prepare script, lernapps hooks,
64
+ It writes, besides the scaffold: package.json named after the short name given to init (the folder's name without init) (its prepare script, lernapps hooks,
61
65
  installs the git hooks; for an explainer and an interactive app also vue-tsc and typescript, with which lernapps
62
66
  check type-checks its Vue components; for an interactive app also @doctc/arc42, which checks its architecture),
63
67
  for an interactive app its architecture docs/arc42/ (the generic part and the interactive extension of app-templates,
@@ -96,7 +100,7 @@ export interface Manifest {
96
100
  version?: string;
97
101
  devDependencies?: Record<string, string>;
98
102
  scripts?: Record<string, string>;
99
- lernapps?: { archetypes?: string[]; init?: boolean };
103
+ lernapps?: { archetypes?: string[]; scaffold?: Record<string, string[]>; init?: boolean };
100
104
  [key: string]: unknown;
101
105
  }
102
106
 
@@ -108,6 +112,8 @@ export interface Templates {
108
112
  root: string;
109
113
  version: string;
110
114
  archetypes: string[];
115
+ /** Per archetype, the paths of its template folder a new app gets (lernapps.scaffold of the package). */
116
+ scaffold: Record<string, string[]>;
111
117
  }
112
118
 
113
119
  /** The runtime package, which also carries the templates. */
@@ -133,7 +139,12 @@ export function templatesPackage(dir?: string): Templates {
133
139
  throw new CreateError(`${root} is no package ${RUNTIME}: it has no package.json`);
134
140
  }
135
141
  const manifest = readJson(join(root, "package.json"));
136
- return { root, version: manifest.version ?? "0.0.0", archetypes: [...(manifest.lernapps?.archetypes ?? [])].sort() };
142
+ return {
143
+ root,
144
+ version: manifest.version ?? "0.0.0",
145
+ archetypes: [...(manifest.lernapps?.archetypes ?? [])].sort(),
146
+ scaffold: manifest.lernapps?.scaffold ?? {},
147
+ };
137
148
  }
138
149
 
139
150
  /** The archetypes of the installed templates. */
@@ -202,6 +213,34 @@ const packageName = (dir: string) =>
202
213
  .replace(/[^a-z0-9]+/g, "-")
203
214
  .replace(/^-+|-+$/g, "") || "app";
204
215
 
216
+ /**
217
+ * The scaffold of the archetype into `dir` (decision dec-scaffold of app-templates): the paths of its template folder
218
+ * that lernapps.scaffold.<archetype> of the package lists (the entry, the configuration, the e2e entry), then the start
219
+ * content scaffold/<archetype>/, a placeholder the creator replaces. The rest of the template folder is the example
220
+ * app; it stays in node_modules/@lernapps/app-templates/<archetype>/ for the creator's assistant to look up.
221
+ */
222
+ export function writeScaffold(templates: Templates, archetype: string, dir: string): void {
223
+ const paths = templates.scaffold[archetype];
224
+ if (paths === undefined || paths.length === 0) {
225
+ throw new CreateError(
226
+ `${templates.root} (version ${templates.version}) names no scaffold for ${archetype} (lernapps.scaffold in its package.json): use the version of ${RUNTIME} this tooling depends on`,
227
+ );
228
+ }
229
+ const template = join(templates.root, archetype);
230
+ for (const path of paths) {
231
+ // npm never ships a .gitignore: the package carries it as _gitignore (app-templates, scripts/pack-dotfiles.ts);
232
+ // a clone of the repository has the .gitignore itself
233
+ const shipped = basename(path) === ".gitignore" ? join(template, dirname(path), "_gitignore") : undefined;
234
+ const source = shipped !== undefined && existsSync(shipped) ? shipped : join(template, path);
235
+ if (!existsSync(source)) throw new CreateError(`${template} has no ${path}, which its scaffold lists`);
236
+ mkdirSync(dirname(join(dir, path)), { recursive: true });
237
+ cpSync(source, join(dir, path));
238
+ }
239
+ const start = join(templates.root, "scaffold", archetype);
240
+ if (!existsSync(start)) throw new CreateError(`${templates.root} has no start content scaffold/${archetype}/`);
241
+ cpSync(start, dir, { recursive: true });
242
+ }
243
+
205
244
  /** This package, as an app depends on it. */
206
245
  export const TOOLING = "@lernapps/tooling";
207
246
 
@@ -399,21 +438,9 @@ export function create(args: string[]): number {
399
438
  ? readFileSync(join(dir, "package-lock.json"), "utf8")
400
439
  : undefined;
401
440
  mkdirSync(dir, { recursive: true });
402
- cpSync(join(templates.root, archetype), dir, { recursive: true });
403
- rmSync(join(dir, "package-lock.json"), { force: true }); // the template's lock, of another name and tooling
441
+ writeScaffold(templates, archetype, dir);
404
442
  if (initialLock !== undefined) writeFileSync(join(dir, "package-lock.json"), initialLock);
405
- // npm never ships a .gitignore: the package carries it as _gitignore (app-templates, scripts/pack-dotfiles.ts)
406
- if (existsSync(join(dir, "_gitignore"))) renameSync(join(dir, "_gitignore"), join(dir, ".gitignore"));
407
- // the template's README says what the archetype can do, for choosing one; it is no part of the app
408
- rmSync(join(dir, "README.md"), { force: true });
409
- // the template folder's architecture is the extension of app-templates' own: an interactive app gets the
410
- // combination as its reference, the other archetypes none
411
- rmSync(join(dir, ARCHITECTURE), { recursive: true, force: true });
412
- if (existsSync(join(dir, "docs")) && readdirSync(join(dir, "docs")).length === 0) {
413
- rmSync(join(dir, "docs"), { recursive: true });
414
- }
415
443
  if (architecture) writeArchitecture(dir, templates.root, archetype, templates.version);
416
- else rmSync(join(dir, ".arc42ignore"), { force: true });
417
444
 
418
445
  const app = readJson(join(dir, "package.json"));
419
446
  // the short name the creator gave `lernapps init` stays the app's name; without init, the folder names it
@@ -427,7 +454,14 @@ export function create(args: string[]): number {
427
454
  // version of the runtime it was copied from
428
455
  if (app.devDependencies?.[RUNTIME] !== undefined) {
429
456
  app.devDependencies = { ...app.devDependencies, [RUNTIME]: `^${templates.version}` };
430
- rmSync(join(dir, ".npmrc"), { force: true });
457
+ }
458
+ // the template keeps its checkout's runtime with an override (file:..), which npm would otherwise swap for the
459
+ // version this tooling depends on; the app has no checkout to point at
460
+ const overrides = app["overrides"];
461
+ if (typeof overrides === "object" && overrides !== null && RUNTIME in overrides) {
462
+ const kept = Object.fromEntries(Object.entries(overrides).filter(([name]) => name !== RUNTIME));
463
+ if (Object.keys(kept).length > 0) app["overrides"] = kept;
464
+ else delete app["overrides"];
431
465
  }
432
466
  // the tooling the app depends on: the one named, else this version (init may have installed it already)
433
467
  const tooling = values.tooling ?? `^${ownVersion()}`;
@@ -101,7 +101,9 @@ questions. -->
101
101
  research, a data format).
102
102
  - Amount: how much content per level or chapter in the first version (e.g. "one text per school year"); what comes
103
103
  later.
104
- - Languages: German only, or more (texts, code).
104
+ - Languages: German only, or German and English (texts, code). Never English only: an app without German still has
105
+ German pages and runtime texts, so it mixes both. The creator wants English → offer German and English,
106
+ both complete, and say why.
105
107
  - Look and feel: calm like a worksheet, or like a game (colours, praise, effects); dark mode.
106
108
  - Terms: the creator's words for the main things in the app, used everywhere.
107
109
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lernapps/tooling",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Shared tooling of the lernapps.net apps and sites: the CLI lernapps, site actions, Renovate preset, guidance for building apps; documentation at https://lernapps.net/tooling/",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -107,7 +107,7 @@
107
107
  "arc42": "arc42 --dir docs/arc42"
108
108
  },
109
109
  "dependencies": {
110
- "@lernapps/app-templates": "0.1.0",
110
+ "@lernapps/app-templates": "0.2.1",
111
111
  "axe-core": "4.14.0",
112
112
  "playwright": "1.64.1",
113
113
  "skills": "1.7.4",
@@ -115,7 +115,7 @@
115
115
  },
116
116
  "devDependencies": {
117
117
  "@doctc/arc42": "0.38.0",
118
- "@lernapps/site": "github:lernapps/lernapps.github.io#15473cb8e782c69b2a38d45208bbe7eaf8a6c698",
118
+ "@lernapps/site": "github:lernapps/lernapps.github.io#8b6c98f76170c313519d04b38937a48747c05b09",
119
119
  "@types/node": "26.6.5",
120
120
  "ajv": "8.20.0",
121
121
  "vite-plus": "1.4.0"
@@ -8,7 +8,7 @@ license: MIT
8
8
 
9
9
  - You write YAML in `src/`; a Vue component only where the plan names one. Rendering, navigation, formulas, figures,
10
10
  exercises, accessibility, languages and the no-JS page come from the runtime `@lernapps/app-templates`.
11
- - `create` writes the example chapter "Prozent": replace it with the planned content.
11
+ - `create` writes a placeholder: one chapter with one page and one exercise. Replace it with the planned content. The complete example, the chapter "Prozent" with a component, is in `node_modules/@lernapps/app-templates/explainer/`: look things up there, never copy it.
12
12
  - Schema: `node_modules/@lernapps/app-templates/runtime/explainer/explainer.v1.schema.json`. Start every YAML file with
13
13
  `# yaml-language-server: $schema=../../node_modules/@lernapps/app-templates/runtime/explainer/explainer.v1.schema.json`
14
14
  (one `../` less in `src/app.yaml`).
@@ -69,6 +69,8 @@
69
69
  ## Languages
70
70
 
71
71
  - `languages` in `src/app.yaml`, German first: `[de]` or `[de, en]` (runtime texts: de, en).
72
+ - Never English only: with `[de]` the pages and the runtime's texts are German, so English strings mix both.
73
+ English wanted → `[de, en]`.
72
74
  - A string = German, enough only while German is the only language. Else every text a map:
73
75
  `title: { de: Die drei Stände, en: The three estates }`.
74
76
  - Build fails on a missing translation (`the translation is missing; write the text as a map: { de: …, en: … }`)
@@ -47,7 +47,7 @@
47
47
  `show(answer, task, props, lang)` (answer as text). An input of `params` that is no placeholder shows as
48
48
  "(verdeckt)": the tutor knows it, the child does not. One `template` per block: prefer one kind of task per block;
49
49
  else give the sentence that changes as an input (`{Aufgabe}`). `about.answer` names what the child does and "Prüfen". The build checks them against `check` for every number; rule `vue-component-tutor-table`. Model:
50
- `ProzentFeld` of the template.
50
+ `ProzentFeld` of the template, in `node_modules/@lernapps/app-templates/explainer/src/components/`.
51
51
  - More: rule `vue-component-exercise-contract`.
52
52
 
53
53
  ## Rendering
@@ -194,7 +194,7 @@ severity: hint
194
194
  - The tutor page then has the task once, the mistakes as F1, F2 … and a table of every number (inputs, solution,
195
195
  each mistake), like `generated`. Without them: each number's `describe` as far as 64 kB allow, and the build's hint
196
196
  names the block.
197
- - Example: `src/components/ProzentFeld.ts` of the template.
197
+ - Example: `node_modules/@lernapps/app-templates/explainer/src/components/ProzentFeld.ts`, the template's.
198
198
 
199
199
  ## `vue-component-tested`: A test per component
200
200
 
@@ -44,7 +44,8 @@ license: MIT
44
44
 
45
45
  ## Decide
46
46
 
47
- - Languages: German; English as a second language is supported. Each declared language complete.
47
+ - Languages: German; English as a second language is supported. Each declared language complete. Never English
48
+ only: without `languages` the page and the runtime's texts are German. English wanted → `[de, en]`.
48
49
  - The tutor: the build writes it from the content. Plan only app-specific tutor rules, if any.
49
50
 
50
51
  ## Gotchas
@@ -7,7 +7,7 @@ license: MIT
7
7
  # Interactive app
8
8
 
9
9
  - Runtime: `@lernapps/app-templates` (`runtime/interactive/`); leave it untouched.
10
- - `create` writes a small example app: replace it with the planned parts.
10
+ - `create` writes a placeholder: a counter as core, component and playthrough. Replace it with the planned parts. The complete example, a typing game, is in `node_modules/@lernapps/app-templates/interactive/`: look things up there, never copy it.
11
11
 
12
12
  ## What you write
13
13
 
@@ -39,7 +39,8 @@ license: MIT
39
39
  - Accessibility: WCAG 2.1 AA and 360 px are the default. A deviation the idea needs (a time limit) goes into the
40
40
  entry's `limits`, in German.
41
41
  - Look and feel: as the creator chose in Explore.
42
- - Languages: German; each declared language complete; learner texts in message files, never in code.
42
+ - Languages: German; each declared language complete; learner texts in message files, never in code. Never English
43
+ only: German comes first. English wanted → German and English.
43
44
 
44
45
  ## Gotchas
45
46
 
@@ -9,7 +9,7 @@ license: MIT
9
9
  - You write `src/quiz.json` and, for the content of its questions, the image files in `src/images/`. Engine, question
10
10
  types, feedback, scoring, order, accessibility and the no-JS page come from the runtime `@lernapps/app-templates`.
11
11
  - Schema: `node_modules/@lernapps/app-templates/runtime/quiz/quiz.v1.schema.json`.
12
- - `create` writes the example bank "Naturwunder der Welt": replace it with the planned questions.
12
+ - `create` writes a placeholder bank with one question. Replace it with the planned questions. The complete example, the bank "Naturwunder der Welt", is in `node_modules/@lernapps/app-templates/quiz/`: look things up there, never copy it.
13
13
  - The fields of each question type and how to write good questions: skill `lernapps-content-questions`.
14
14
 
15
15
  ## Gotchas
@@ -50,7 +50,8 @@ license: MIT
50
50
  - German; more with `"languages": ["de", "en"]`, German first (runtime texts: de, en).
51
51
  - Then every learner text is a map with each language: title, intro, source titles; per question text, options,
52
52
  `info`, items, pairs, unit, explanation, link titles.
53
- - Without `languages`: German strings.
53
+ - Without `languages`: German strings. The page is German then (`lang="de"`, the runtime's own texts such as
54
+ "Frage 1 von 5"); English strings give a page that mixes both. Never English only: English wanted → `[de, en]`.
54
55
  - Link `url`: one address, or a map per language (own Wikipedia).
55
56
  - Build errors, one line each, e.g.
56
57
  `questions[1].explanation (en) (question "totes-meer"): the translation is missing; write the text as a map: { de: …, en: … }`;
@@ -42,7 +42,8 @@ license: MIT
42
42
 
43
43
  ## Decide
44
44
 
45
- - Languages: German; English as a second language is supported. Each declared language complete.
45
+ - Languages: German; English as a second language is supported. Each declared language complete. Never English
46
+ only: without `languages` the page and the runtime's texts are German. English wanted → `[de, en]`.
46
47
  - Keep the last score on the device (`rememberLastResult`) or nothing. Keeping it → the entry says
47
48
  `fitness.storage: device`.
48
49
  - Order: shuffled per seed; a link with `?seed=42` gives every learner the same order.
@@ -147,6 +147,7 @@ severity: error
147
147
  ```
148
148
 
149
149
  - Texts for learners, teachers, parents: German, plain (short sentences, active voice, no jargon).
150
+ - Never English only: an app in English is German and English, German first.
150
151
  - More languages only where the archetype supports them (explainer, quiz: `languages`, German first); each declared
151
152
  language complete, no other.
152
153
  - Texts out of code: in the content (explainer YAML, quiz bank) or message files (`src/messages/de.json`, shown