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 keinrequire,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:
- Eintrag in
IpcVertragund inIPC_KANAELE(Allowlist). - Handler in
src/main/ipc.tsüber die Hilfsfunktionbehandeln(). - Methode in
LernAppBridgeund Umsetzung insrc/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 inuserDatagespeichert 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:
- Statisch –
npm run linteslint-plugin-jsx-a11yläuft im strikten Regelsatz über alle.tsx-Dateien. Verstöße sind Fehler, keine Warnungen.
- Unit –
npm testtests/ThemaWahl.test.tsxprü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.
- E2E –
npm run test:e2ee2e/barrierefreiheit.spec.tsstartet 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/playwrightwird nicht verwendet. Das Paket ruft internbrowserContext.newPage()auf; Electron beantwortet das mitProtocol error (Target.createTarget): Not supported. Stattdessen injizierte2e/axe-hilfe.tsdie axe-core-Quelle direkt überpage.evaluate(). Das ist gleichwertig und lässt die strenge Produktions-CSP unangetastet.- ESLint ist auf 9.x festgelegt.
eslint-plugin-jsx-a11yunterstü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. Fehltout/oder ist der Bau älter als der Quelltext, brichtbauPruefen()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).