# Waffensachkunde – Lernsoftware (Anwendungsgerüst)

Barrierefreie Desktop-Lernsoftware zur Vorbereitung auf die Waffensachkundeprüfung.
Läuft vollständig offline; es werden keine Daten übertragen.

Dieses Verzeichnis enthält **nur das Anwendungsgerüst**. Die Lerninhalte und das
Datenbankschema kommen aus einem anderen Arbeitsstrang (`content/`, `data-pipeline/`).

---

## Technischer Stack

| Baustein          | Version        | Zweck                                    |
| ----------------- | -------------- | ---------------------------------------- |
| Electron          | 43.4.1         | Desktop-Laufzeit                         |
| React             | 19.2.8         | Oberfläche im Renderer-Prozess           |
| Vite              | 7.3.6          | Bundler                                  |
| electron-vite     | 5.0.0          | Bindeglied Vite ↔ Electron               |
| TypeScript        | 6.0.3          | `strict` plus zusätzliche Verschärfungen |
| better-sqlite3    | 13.0.3         | Lokale Datenbank des Lernstands          |
| electron-builder  | 26.15.3        | Paketierung (NSIS, DMG)                  |
| Vitest            | 4.1.11         | Unit-Tests                               |
| Playwright        | 1.62.1         | E2E-Tests gegen die echte Anwendung      |
| axe-core          | 4.13.0         | Automatisierte Barrierefreiheitsprüfung  |
| ESLint / Prettier | 9.39.5 / 3.9.6 | Statische Analyse und Formatierung       |

---

## Verzeichnisstruktur

```
app/
├── src/
│   ├── main/                  Main-Prozess (Node, voller Systemzugriff) – 31 Dateien
│   │   ├── index.ts             Einstiegspunkt, Lebenszyklus der Anwendung
│   │   ├── fenster.ts           Hauptfenster inkl. gehärteter webPreferences
│   │   ├── sicherheit.ts        CSP-Header, Berechtigungssperren, Navigationsschutz
│   │   ├── menue.ts             Deutschsprachiges Anwendungsmenü
│   │   ├── ipc.ts               Registrierung der typisierten IPC-Handler
│   │   ├── einstellungen.ts     Nutzereinstellungen (JSON in userData)
│   │   ├── datenbank.ts         Öffnen der SQLite-Datei
│   │   ├── schema.ts            Grundschema und die zehn Migrationsschritte
│   │   ├── lernstand.ts         Der Anwendungskern: Profile, Antworten, Sitzung, Plan
│   │   ├── pruefung.ts          Prüfungssimulation: Bogen, Auswertung, Verlauf
│   │   ├── katalog.ts,          Laden und Prüfen der Inhalte aus content/
│   │   │   erklaerungen.ts,
│   │   │   glossar.ts,
│   │   │   normtexte.ts,
│   │   │   themen.ts
│   │   ├── sicherung.ts,        Sichern und Einspielen des Lernstands
│   │   │   sicherung-dialoge.ts,
│   │   │   selbstsicherung.ts,
│   │   │   profil-uebernehmen.ts
│   │   └── druck.ts,            PDF-Ausgabe: Lernbericht, Fehlerprotokoll, Fragenliste
│   │       dokument-export.ts,
│   │       fragendruck-export.ts,
│   │       lernbericht-export.ts
│   │
│   ├── preload/               Die EINZIGE Brücke zwischen Main und Renderer
│   │   ├── index.ts             contextBridge-Freigabe der typisierten API
│   │   └── index.d.ts           Deklaration von `window.lernApp`
│   │
│   ├── renderer/              React-Oberfläche (Sandbox, kein Node-Zugriff)
│   │   ├── index.html           lang="de", CSP-Platzhalter, Titel
│   │   └── src/
│   │       ├── main.tsx           React-Einstiegspunkt
│   │       ├── App.tsx            Ansichtswahl und die Tore davor
│   │       ├── bridge.ts          Typisierter Zugriff auf window.lernApp
│   │       ├── components/        Bausteine, nach Bereich gegliedert:
│   │       │                        lernen/ (38), pruefung/ (13), profil/ (3),
│   │       │                        suche/ (2), ueber/ (3), hilfe/ (1)
│   │       ├── hooks/             26 Haken – Katalog, Lernstand, Sitzung,
│   │       │                        Prüfung, Thema, Textabstand, Tastenkürzel
│   │       ├── lernen/            Bewertung, Sitzungsaufbau, Sprachausgabe
│   │       ├── pruefung/          Auftrag, Bogen, Bewertung, Zeit
│   │       └── styles/            tokens.css (3 Themes) und sechs Bereichsdateien
│   │
│   └── shared/                Von allen drei Prozessen genutzte Typen und Logik
│       ├── ipc.ts               IPC-Vertrag (41 Kanäle, Anfrage-/Antworttypen)
│       ├── fsrs.ts              Das Wiedervorlageverfahren
│       ├── lernstand.ts,        Verträge des Kerns
│       │   lernplan.ts, reife.ts
│       ├── katalog.ts,          Verträge der Inhalte
│       │   erklaerungen.ts, glossar.ts, normtexte.ts, themen.ts
│       ├── druck/               Die Dokumentbausteine, ohne Electron
│       ├── theme.ts             Theme-Typen und Auflösungslogik
│       └── csp.ts               Content-Security-Policy (eine Quelle der Wahrheit)
│
├── tests/                     Unit-Tests (Vitest, jsdom) – 72 Dateien
│   ├── setup.ts                 Test-Setup, matchMedia-Stub
│   ├── lernstand.test.ts        Der Kern: Profile, Wiedervorlage, Übersicht, Plan
│   ├── pruefung.test.ts         Bogen, Auswertung, unterbrochener Lauf
│   ├── renderer-*.test.tsx      Die Oberfläche je Bereich, mit axe-Lauf
│   ├── dokumentation.test.ts    Fassungsnummern, Changelog, tote Verweise
│   └── …                        Druck, Sicherung, Suche, Glossar, Normtexte, Symbol
│
├── e2e/                       E2E-Tests (Playwright gegen echtes Electron) – 23 Dateien
│   ├── electron-hilfe.ts        Start der gebauten App mit frischem Profil
│   ├── axe-hilfe.ts             axe-core-Injektion für Electron
│   ├── konsolenwache.ts         Macht Konsolenfehler der Anwendung rot
│   ├── gepackt.spec.ts          Rauchtest gegen das gebaute Installationspaket
│   └── barrierefreiheit*.spec.ts axe je Ansicht und Farbschema, Landmarken, Fokus
│
├── build-resources/           Programmsymbol und die Grafiken des Store-Pakets
├── tools/                     Symbol bauen, Paketstand messen, Gate-Stempel
├── electron.vite.config.ts    Build für main / preload / renderer
├── electron-builder.yml       Paketierung Windows (NSIS, AppX) und macOS (DMG)
├── eslint.config.mjs          ESLint Flat Config
├── playwright.config.ts       E2E-Konfiguration
├── vitest.config.ts           Unit-Test-Konfiguration samt Abdeckungsschwellen
└── tsconfig.*.json            Getrennte Projekte für node / web / tests / e2e
```

Nach `npm run build` entsteht zusätzlich `out/` (`out/main`, `out/preload`, `out/renderer`),
nach `npm run dist:*` das Verzeichnis `release/`. Beides ist nicht versioniert.

---

## npm-Skripte

| Skript                 | Wirkung                                                                          |
| ---------------------- | -------------------------------------------------------------------------------- |
| `npm run dev`          | Startet Vite-Dev-Server und Electron mit Hot Reload.                             |
| `npm run build`        | Typprüfung, danach Produktionsbundles nach `out/`.                               |
| `npm start`            | Zeigt einen bereits gebauten Stand (`electron-vite preview`).                    |
| `npm test`             | Unit-Tests einmalig (Vitest).                                                    |
| `npm run test:watch`   | Unit-Tests im Beobachtungsmodus.                                                 |
| `npm run test:e2e`     | Baut die App und führt danach die Playwright-Tests aus.                          |
| `npm run lint`         | ESLint über das gesamte Projekt.                                                 |
| `npm run lint:fix`     | ESLint mit automatischer Korrektur.                                              |
| `npm run typecheck`    | TypeScript für alle vier Teilprojekte (node, web, tests, e2e).                   |
| `npm run format`       | Prettier schreibt alle Dateien.                                                  |
| `npm run format:check` | Prettier prüft ohne zu schreiben (für CI).                                       |
| `npm run dist:win`     | Windows-Installer (NSIS) nach `release/`.                                        |
| `npm run dist:mac`     | macOS-Abbild (DMG) nach `release/` – muss auf macOS laufen.                      |
| `npm run rebuild`      | Baut native Module für Electron neu. Normalerweise **nicht** nötig, siehe unten. |

---

## Sicherheitsmodell

Diese Schalter sind nicht verhandelbar und werden von `tests/sicherheit.test.ts`
(Quellprüfung) und `e2e/anwendung.spec.ts` (Verhaltensprüfung) abgesichert:

- `contextIsolation: true` – Renderer und Preload laufen in getrennten JS-Kontexten.
- `nodeIntegration: false` – im Renderer gibt es kein `require`, `process`, `Buffer`.
- `sandbox: true` – der Renderer-Prozess läuft in der Chromium-Sandbox.
- `webviewTag: false`, `nodeIntegrationInWorker/-InSubFrames: false`, `webSecurity: true`.

Weitere Härtung in `src/main/sicherheit.ts`: alle Berechtigungsanfragen werden
abgelehnt, Navigation nach außen wird blockiert, neue Fenster werden nicht in
Electron geöffnet, sondern an den Systembrowser übergeben.

**Genau ein Kanal führt nach außen**, und er wird immer von Hand angestoßen:
`system:unterstuetzung` öffnet die Unterstützungsseite im Standardbrowser
(`shell.openExternal`). Der Renderer schickt die Adresse mit, damit prüfbar
bleibt, dass die Oberfläche genau die anfordert, die sie anzeigt – geöffnet
wird aber ausschließlich die eine Adresse aus `src/shared/unterstuetzung.ts`.
Ist dort nichts eingetragen, öffnet dieser Weg nichts, und die Oberfläche zeigt
das Angebot gar nicht erst an. Die Anwendung lädt nach wie vor von sich aus
nichts: kein `fetch`, kein `net.request`, kein `https.get`.

**IPC ausschließlich über `contextBridge`.** Kanäle werden an genau einer Stelle
definiert (`src/shared/ipc.ts`, Interface `IpcVertrag`); Anfrage- und Antworttyp
werden auf beiden Seiten daraus abgeleitet. Einen neuen Kanal ergänzt man so:

1. Eintrag in `IpcVertrag` und in `IPC_KANAELE` (Allowlist).
2. Handler in `src/main/ipc.ts` über die Hilfsfunktion `behandeln()`.
3. Methode in `LernAppBridge` und Umsetzung in `src/preload/index.ts`.

**Content-Security-Policy** steht in `src/shared/csp.ts` und wird zweifach
ausgeliefert: als `<meta>`-Element im HTML und als HTTP-Kopf aus dem
Main-Prozess. Beide greifen auch bei `file://` im gepackten Build, und beide
gelten gleichzeitig – ausgewertet wird die Schnittmenge. Im Produktionsbuild
gilt `default-src 'none'` ohne jedes `unsafe-*`.

Die `<meta>`-Fassung lässt `frame-ancestors` bewusst weg: Diese Direktive
wirkt ausschließlich im HTTP-Kopf, im `<meta>`-Element verwirft Chromium sie
mit einem Konsolenfehler bei jedem Start. Dass sie über den Kopf ankommt, misst
`e2e/anwendung.spec.ts` im echten Fenster.

---

## Barrierefreiheit

Barrierefreiheit ist **Definition of Done**, nicht Nacharbeit.

### Verbindliche Regeln

| Bereich      | Regel                                                                   |
| ------------ | ----------------------------------------------------------------------- |
| Zielniveau   | WCAG 2.2 Stufe AA vollständig; AAA als Zielwert, wo erreichbar.         |
| Textkontrast | Minimum 4,5:1, Zielwert 7:1.                                            |
| UI-Kontrast  | Ränder, Bedienelemente und Fokusring mindestens 3:1.                    |
| Sprache      | `lang="de"` am `<html>`; alle sichtbaren Texte auf Deutsch.             |
| Tastatur     | Jede Funktion ohne Maus bedienbar; sichtbarer `:focus-visible`-Ring.    |
| Skip-Link    | Erster Tabstopp springt zum Hauptinhalt.                                |
| Struktur     | Genau eine `<h1>`, keine übersprungenen Ebenen, Landmarken mit Namen.   |
| Farbe allein | Zustände nie nur über Farbe (Radiogruppe nutzt zusätzlich einen Glyph). |
| Klickflächen | Mindestens 44 × 44 px (`--klickflaeche-min`).                           |
| Bewegung     | `prefers-reduced-motion: reduce` schaltet alle Übergänge praktisch ab.  |
| Schriften    | Nur systemseitig vorhandene Schriften – keine Webfont-Downloads.        |
| Widgets      | ARIA-Muster strikt nach den ARIA Authoring Practices umsetzen.          |

### Themes

Drei Themes, definiert als CSS Custom Properties in `src/renderer/src/styles/tokens.css`:
**Hell**, **Dunkel** und **Hoher Kontrast**.

- Ohne manuelle Auswahl folgt die Oberfläche dem Betriebssystem
  (`prefers-color-scheme`, `prefers-contrast`).
- `forced-colors: active` (Windows-Kontrastdesign) überstimmt alles; die
  Farbwahl geht dann vollständig an das Betriebssystem (CSS-Systemfarben).
- Die manuelle Auswahl (`<html data-thema="…">`) wird in `userData` gespeichert
  und beim nächsten Start wiederhergestellt.

Die Auflösungslogik liegt bewusst als reine Funktion in `src/shared/theme.ts`
(`themeAufloesen`) und ist damit direkt testbar.

Gestaltungsrichtung: ruhig und kontrastreich – zurückhaltende Farben, große
klare Typografie (Basisgröße 18 px), viel Weißraum, eine dezente Akzentfarbe
(gedämpftes Petrol), ausgelegt auf lange Lernsitzungen.

### Wie geprüft wird

Drei Ebenen, die sich ergänzen:

1. **Statisch – `npm run lint`**
   `eslint-plugin-jsx-a11y` läuft im **strikten** Regelsatz über alle `.tsx`-Dateien.
   Verstöße sind Fehler, keine Warnungen.

2. **Unit – `npm test`**
   `tests/ThemaWahl.test.tsx` prüft die ARIA-Struktur der Radiogruppe, die
   vollständige Tastaturbedienung nach APG (Pfeiltasten, Pos1/Ende, Leertaste,
   Umlauf, Roving Tabindex) und lässt axe-core über das gerenderte Markup laufen.
   Farbkontraste sind hier abgeschaltet: jsdom rendert kein CSS.

3. **E2E – `npm run test:e2e`**
   `e2e/barrierefreiheit.spec.ts` startet die echte, gebaute Electron-App und
   lässt axe-core gegen WCAG 2.0/2.1/2.2 A und AA laufen – **einmal pro Theme**,
   damit alle drei Paletten gemessen werden. Zusätzlich geprüft: Landmarken,
   zugängliche Namen der Sektionen und der sichtbare Fokusring bei echter
   Tastaturnavigation. Der AAA-Zielwert (7:1) wird erhoben und berichtet, aber
   nicht erzwungen.

Automatisierte Tests decken erfahrungsgemäß etwa ein Drittel der Kriterien ab.
Vor jeder Auslieferung zusätzlich manuell prüfen: vollständiger Durchlauf nur
mit der Tastatur, ein Screenreader-Durchgang (NVDA unter Windows, VoiceOver
unter macOS) und ein Blick auf die Anwendung bei 200 % Systemzoom.

---

## Paketierung

`npm run dist:win` erzeugt einen NSIS-Installer, `npm run dist:mac` ein DMG.
Beides landet in `release/`.

**Code-Signierung ist bewusst deaktiviert** (`identity: null` für macOS, keine
Zertifikatsangaben für Windows), weil die Anwendung privat weitergegeben wird.
Das ist den Nutzenden zu erklären:

- **Windows:** SmartScreen meldet beim ersten Start „Windows hat Ihren PC
  geschützt“ → „Weitere Informationen“ → „Trotzdem ausführen“.
- **macOS:** Gatekeeper blockiert die App. Rechtsklick auf die App → „Öffnen“ →
  im Dialog erneut „Öffnen“. Alternativ Systemeinstellungen → Datenschutz &
  Sicherheit → „Dennoch öffnen“.

Anwendungssymbole gehören nach `build-resources/` (`icon.ico` für Windows,
`icon.icns` oder `icon.png` ab 512 × 512 px für macOS). Ohne eigene Symbole
verwendet electron-builder das Electron-Standardsymbol.

`npm run dist:mac` muss auf einem Mac laufen – electron-builder kann DMGs nicht
unter Windows erzeugen.

### Native Module

`better-sqlite3` ab Version 12 liefert **N-API-Prebuilds** mit
(`node_modules/better-sqlite3/prebuilds/`). Die sind ABI-stabil und laufen ohne
Neubau sowohl unter Node als auch unter Electron. Deshalb steht in
`electron-builder.yml` `npmRebuild: false`, und `npm run rebuild` wird im
Normalfall nicht gebraucht. Sollte der Startbildschirm dennoch „Datenbank:
Fehler“ melden, hilft `npm run rebuild` (setzt Build-Werkzeuge voraus).

---

## Bekannte Einschränkungen

- **`@axe-core/playwright` wird nicht verwendet.** Das Paket ruft intern
  `browserContext.newPage()` auf; Electron beantwortet das mit
  `Protocol error (Target.createTarget): Not supported`. Stattdessen injiziert
  `e2e/axe-hilfe.ts` die axe-core-Quelle direkt über `page.evaluate()`. Das ist
  gleichwertig und lässt die strenge Produktions-CSP unangetastet.
- **ESLint ist auf 9.x festgelegt.** `eslint-plugin-jsx-a11y` unterstützt
  ESLint 10 noch nicht. Da die a11y-Regeln hier verbindlich sind, hat der
  vollständige Regelsatz Vorrang vor der neuesten ESLint-Hauptversion.
- Die E2E-Tests brauchen einen vorherigen `npm run build`. Fehlt `out/` **oder
  ist der Bau älter als der Quelltext**, bricht `bauPruefen()` mit einer
  verständlichen Meldung ab — es wird nicht übersprungen. Das ist Absicht: Ein
  übersprungener Test meldet grün, und diese Suite würde dann die vorige
  Fassung prüfen. Übersprungen wird nur, was ausdrücklich einen Grund nennt
  (die beiden Prüfungen zum Store-Paket, die ein registriertes MSIX brauchen).
