# LehrerApp — Entwicklungs-Roadmap Strukturierte ToDo-Liste aller offenen Entwicklungsschritte. Jeder Punkt ist so formuliert, dass er einzeln an einen Agenten übergeben werden kann. **Stand:** 2026-08-16 **Legende:** `[ ]` offen · `[~]` teilweise umgesetzt · `[x]` fertig --- ## 0. Konventionen für alle Aufgaben Diese Regeln gelten für jede Aufgabe unten und müssen nicht wiederholt werden. - **MVVM:** Logik in ViewModels (`CommunityToolkit.Mvvm`, `[ObservableProperty]` / `[RelayCommand]`), Views enthalten nur Binding. Kein Code-Behind außer Dialog-Verdrahtung. - **DI:** Neue Repositories/Services/ViewModels in `LehrerApp.Desktop/AppBootstrapper.cs` registrieren. Repositories + Listen-VMs = Singleton, Detail-/Dialog-VMs = Transient. - **Daten:** Neue Collections in `LehrerApp.Data/LiteDbContext.cs` anlegen, Indexe in `EnsureIndexes()`. Interface nach `LehrerApp.Core/Interfaces/IRepositories.cs`, Implementierung nach `LehrerApp.Data/Repositories/AllRepositories.cs`. - **Dialoge:** `Window` mit `Close(true/false)`, DataContext manuell gesetzt (Muster siehe [AddGroupDialog.axaml.cs](LehrerApp.Desktop/Views/Groups/AddGroupDialog.axaml.cs)). - **Sprache:** Alle UI-Texte auf Deutsch. Datumsformat `dd.MM.yyyy`, Kultur `de-DE`. - **Modelle:** `UpdatedAt` bei jedem Speichern setzen (Grundlage für Sync-Konfliktauflösung). --- ## 1. Klausuren & Leistungsüberprüfungen Modelle `Exam`, `ExamTask`, `GradingKeyEntry`, `ExamResult` existieren bereits in [Exam.cs](LehrerApp.Core/Models/Exam.cs), Repositories ebenfalls. Die komplette UI fehlt. **Niveaudifferenzierung (E/G/Förder) ist bereits umgesetzt** — nicht aus dieser Liste, sondern ein separater Bedarf (Nachteilsausgleich/Binnendifferenzierung): `LearningGroup.IsDifferentiated` (Checkbox in den Stammdaten) blendet eine Niveau-Zuordnung je Schüler ein (`GroupMembership.Niveau`, Schüler-Tab). Klausuren bekommen optional ein `Niveau`; Punkteeingabe und Auswertung filtern dann automatisch auf die passenden Schüler. Workflow: G-Klausur normal anlegen, per "Duplizieren" (1.1.4) die E-Variante mit eigenen Aufgaben/Punkten ableiten (Niveau wird beim Duplizieren bewusst zurückgesetzt). Vollständig individuelle Förderklausuren (nur für einen einzelnen Schüler) laufen pragmatisch über "Abwesend" bei den übrigen Schülern statt über eine eigene Schüler-Zuordnung. ### 1.1 Klausur anlegen und verwalten - [x] **1.1.1** Dialog `AddExamDialog` — Titel, Datum, Fach, Klausurnummer, Notizen. Ersetzt den TODO-Stub `AddExam()` in [GroupViewModels.cs](LehrerApp.Desktop/ViewModels/Groups/GroupViewModels.cs). Umgesetzt als `ExamDialog`/`ExamDialogViewModel`. - [x] **1.1.2** Klausur bearbeiten/löschen (Kontextmenü im Klausuren-Tab, Löschen mit Rückfrage). - [x] **1.1.3** Statuswechsel `Planned → Conducted → Graded → Returned` per Button, inkl. Farbcodierung des Status im DataGrid. - [x] **1.1.4** Klausur aus bestehender Klausur duplizieren (Aufgaben + Notenschlüssel übernehmen, neues Datum) — für Parallelkurse. ### 1.2 Aufgabenstruktur (`ExamTask`) - [x] **1.2.1** Editor für die Aufgabenliste: Nr., Titel, Maximalpunkte, Gewichtung. Zeilen hinzufügen/entfernen/umsortieren. Umgesetzt im `ExamDialog` (Teil von 1.1.1). - [x] **1.2.2** Automatische Anzeige der Gesamtpunktzahl, Warnung bei 0 Punkten. - [ ] **1.2.3** Optional: Teilaufgaben (a/b/c) — erfordert Modellerweiterung, vorher entscheiden. - [x] **1.2.4** Zuordnung von Kompetenzen (`CompetencyItem`) zu einzelnen Aufgaben — Voraussetzung für die Kompetenzauswertung in 8.3. ### 1.3 Notenschlüssel - [x] **1.3.1** Editor für `GradingKey` mit Vorbelegung aus `GradingService.DefaultKey1To6()` / `DefaultKey0To15()` je nach `GradingSystem` der Gruppe. Umgesetzt im `ExamDialog` (Teil von 1.1.1), analog zum Aufgaben-Editor. - [x] **1.3.2** Notenschlüssel als wiederverwendbare Vorlage speichern (neues Modell `GradingKeyTemplate` + Repository) und in Einstellungen verwalten. - [x] **1.3.3** Live-Vorschau: Punktegrenzen absolut anzeigen (z.B. "Note 2 ab 45 von 60 P."). - [x] **1.3.4** Validierung: lückenlose, absteigende Prozentgrenzen, keine Dopplungen (`GradingService.ValidateGradingKey`). ### 1.4 Punkteeingabe & Korrektur - [x] **1.4.1** Eingaberaster: Zeilen = Schüler, Spalten = Aufgaben, letzte Spalte Summe + Note. Note wird live über `GradingService.CalculateGrade()` berechnet. Umgesetzt als eigener Dialog `ExamGradingDialog`, erreichbar über "Punkte eingeben" im Klausuren-Tab (Button/Kontextmenü bei ausgewählter Klausur). - [x] **1.4.2** Tastaturnavigation: Tab bewegt sich nativ zur nächsten Zelle, Enter/Pfeil-Hoch/ Pfeil-Runter springen zur gleichen Spalte in der Nachbarzeile, direkte Zifferneingabe und halbe Punkte (Komma oder Punkt) erlaubt. - [x] **1.4.3** Kennzeichnung "Abwesend" (`ExamResult.Absent`) per Checkbox — Note zeigt "abwesend" statt einer berechneten Note. Ausschluss aus Statistiken über 1.5 umgesetzt. - [x] **1.4.4** Kommentarfeld pro Schüler (`ExamResult.Comment`). - [x] **1.4.5** Validierung: Punkte < 0 oder > Maximalpunkte der Aufgabe wird rot markiert und nicht gespeichert (`PointsCell.TrySetValue`). - [x] **1.4.6** Autosave nach jeder Zelle (kein expliziter Speichern-Button nötig). ### 1.5 Klausurauswertung - [x] **1.5.1** Notenspiegel (Häufigkeitsverteilung als Balken), Durchschnitt, Median, Anteil unter 4 / unter 5 Punkten. Umgesetzt als neuer Dialog `ExamEvaluationDialog`, erreichbar über "Auswertung" im Klausuren-Tab. Bei Notensystem 1–6 zeigt der Schwellenwert sinngemäß "Anteil nicht ausreichend (Note 5/6)" statt der Punktegrenzen. Abwesende werden aus allen Statistiken ausgeschlossen (löst damit auch den Hinweis aus 1.4.3 ein). - [x] **1.5.2** Aufgabenanalyse: durchschnittlicher Erfüllungsgrad pro Aufgabe in Prozent, Kennzeichnung auffällig schwacher Aufgaben (Ø < 50 %, rot markiert). - [x] **1.5.3** Notenschlüssel nachträglich verschieben und Auswirkung sofort im Notenspiegel sehen — Änderungen wirken sich live aus, erst "Übernehmen" schreibt sie in die Klausur zurück. - [x] **1.5.4** Export der Auswertung — als eigenständiger CSV-Export direkt im Dialog umgesetzt (analog zum bestehenden JSON-Export der Kompetenzkataloge), nicht über eine gemeinsame Export-Infrastruktur, da Kapitel 11 ("Bisher nicht vorhanden — komplett neu") noch aussteht. --- ## 2. Noten & Zeugnisnoten Modell `Grade` existiert in [Planning.cs](LehrerApp.Core/Models/Planning.cs), `GradeRepository` ebenfalls. Der Tab "Noten" in [GroupDetailView.axaml](LehrerApp.Desktop/Views/Groups/GroupDetailView.axaml) ist jetzt vollständig umgesetzt (siehe unten), ebenso der gleichnamige Tab im Schülerdetail (2.5). ### 2.1 Notenübersicht der Gruppe - [x] **2.1.1** Matrix: Zeilen = Schüler, Spalten = alle Leistungen (Klausuren, Mitarbeit je Halbjahr, sonstige Noten). Zelle zeigt Note/Punkte. - [x] **2.1.2** Spalte "Gesamt" mit gewichtetem Durchschnitt über `GradingService.WeightedAverage()`. - [x] **2.1.3** Sortierung nach Name / Gesamtnote, Umschalten Noten ↔ Punkte. - [x] **2.1.4** Halbjahresfilter (H1 / H2 / Gesamtjahr), berücksichtigt `GroupMembership.Period`. ### 2.2 Einzelnoten pflegen - [x] **2.2.1** Dialog "Note hinzufügen": Kategorie (`GradeCategory`), Wert, Datum, Gewichtung, Notiz. - [x] **2.2.2** Note bearbeiten / löschen mit Historie (wer/wann geändert) — mindestens `CreatedAt` sichtbar. - [x] **2.2.3** Sammelerfassung: eine Note (z.B. Hausaufgabenkontrolle) für die ganze Gruppe auf einmal. ### 2.3 Gewichtungsschema - [x] **2.3.1** Neues Modell `GradingScheme` je Gruppe: prozentuale Anteile von Klausuren / Mitarbeit / sonstige Leistungen (z.B. 50/40/10). - [x] **2.3.2** UI zur Bearbeitung, Validierung auf Summe 100 %. - [x] **2.3.3** Voreinstellung je Gruppentyp (`Class` vs. `Course`) in den Einstellungen. ### 2.4 Zeugnisnote - [x] **2.4.1** Berechnung der Zeugnisnote aus Schema (2.3) + allen Teilnoten, Rundungsregel konfigurierbar (kaufmännisch / pädagogisch abweichbar). - [x] **2.4.2** Manuelles Übersteuern mit Pflicht-Begründung (pädagogischer Spielraum). - [x] **2.4.3** Zeugnisnoten-Ansicht mit Sperren/Festschreiben zum Konferenztermin. - [x] **2.4.4** Export der Zeugnisnotenliste (siehe 11.2). ### 2.5 Notenentwicklung - [x] **2.5.1** Verlaufsdiagramm pro Schüler über das Schuljahr (im Schülerdetail). - [x] **2.5.2** Auffälligkeiten markieren: Abfall um ≥ 1 Note, Versetzungsgefährdung (Note 5/6). Umgesetzt über [GradeOverviewViewModels.cs](LehrerApp.Desktop/ViewModels/Groups/GradeOverviewViewModels.cs) (Matrix-Tab, Einzelnoten-Dialoge), [ReportGradeViewModels.cs](LehrerApp.Desktop/ViewModels/Groups/ReportGradeViewModels.cs) (Zeugnisnoten-Dialog) und die neuen Modelle `GradingScheme`/`ReportGrade` in [Planning.cs](LehrerApp.Core/Models/Planning.cs). Das Gewichtungsschema wird pro Gruppe gesucht, sonst die Voreinstellung des Gruppentyps (Einstellungen → Notenschema), sonst ein Fallback 50/40/10 verwendet; Bereiche ohne Werte werden bei der Berechnung ausgelassen und die verbleibenden Prozentanteile neu normiert (`GradingService.CalculateReportGrade()`). Notenentwicklung im Schülerdetail zeigt ein einfaches Balken-Sparkline je Lerngruppe über alle Klausur- und Einzelnoten-Einträge chronologisch — **jeder Balken ist ein einzelner `Grade`- bzw. Klausurergebnis-Eintrag, keine Mitarbeit-"Sitzung"**. Die Herkunft (Kategorie wie "Mündlich"/"Mitarbeit" oder Klausurtitel) stand ursprünglich nur im Tooltip und nicht sichtbar auf der Kachel — das führte zu Verwirrung, welche Zahl wofür steht, und wurde ergänzt (`GradeHistoryPoint.Label` jetzt auch unter dem Balken sichtbar, nicht nur im Tooltip), siehe [StudentDetailView.axaml](LehrerApp.Desktop/Views/Students/StudentDetailView.axaml). --- ## 3. Mündliche Mitarbeit — offene Punkte Grundfunktion ist umgesetzt (Sitzungen, Raster, Schnelleingabe-Dialog). Siehe [ParticipationViewModels.cs](LehrerApp.Desktop/ViewModels/Groups/ParticipationViewModels.cs). ### 3.1 Aspekte konfigurieren - [x] **3.1.1** UI zum Anlegen/Bearbeiten/Löschen von `ParticipationAspect` **pro Gruppe** (Fach Chemie: z.B. "Experiment", "Protokoll") — `GroupId = null` bleibt global. **Umsetzung:** neuer Button "Aspekte verwalten" im Mitarbeit-Tab öffnet `ParticipationAspectsDialog`. Verwaltet bewusst nur die gruppenspezifischen Aspekte (`GroupId` = diese Gruppe), nicht den globalen Standardkatalog (`GroupId = null`) — der wird bislang nirgends befüllt (`DefaultParticipationAspects` ist reiner In-Memory-Fallback ohne UI) und eine Änderung dort würde sofort alle Gruppen betreffen; das wäre ein eigener, separat zu entscheidender Schritt. Label/Typ/Gewichtung/Aktiv-Status speichern sofort bei Änderung (gleiches Muster wie die bestehende Gewichtungs-Bearbeitung in 3.2.1), mit Rollback + Fehlermeldung an der Zeile, falls das Repository ablehnt (leerer Wert, doppelter Schlüssel). "Schlüssel" ist bewusst nur beim Neuanlegen editierbar: er verknüpft `AspectRating` mit dem Aspekt (per Key, nicht per Id) — ein nachträgliches Umbenennen würde historische Bewertungen unauffindbar machen. `IParticipationAspectRepository.Save` prüft jetzt zusätzlich Pflichtfelder und Schlüssel-Eindeutigkeit (gegen die globalen Standards UND die eigenen Gruppen-Aspekte zusammen, da beide in der Bewertungsübersicht kombiniert verwendet werden — siehe `ParticipationTabViewModel.LoadAspects`). - [x] **3.1.2** Reihenfolge der Aspekte per Hoch/Runter-Buttons festlegen (bestimmt auch die Q/W/E/R/T-Belegung im Schnelleingabe-Dialog) — gleiches Muster wie die Kompetenzbereiche in 8.1.1, `SortOrder` wird bei jeder Verschiebung sofort und lückenlos gespeichert. **Drag & Drop nicht umgesetzt:** Hoch/Runter deckt die vollständige Bedienung bereits ab, analog zur Drag-Entscheidung bei 4.3.3. - [x] **3.1.3** Aspekt-Typen `Scale3`, `Binary`, `Points` in Raster und Dialog vollständig unterstützen (bisher primär `Scale5`) — Nutzer-Nachtrag, damit das Bewertungsfeature mit selbst angelegten Aspekt-Typen produktiv nutzbar wird. **Umsetzung:** Neue statische Klasse `ParticipationRatingScale` (Core) ist die einzige Quelle für Rohwert/Label-Stufen je Typ und für die Normierung zur Aggregation. Bewusste Design-Entscheidung: `Scale3`/`Binary` bekommen **keine eigene Wertemenge**, sondern liegen direkt auf derselben -2..+2-Achse wie `Scale5` (Scale3: -2/0/+2, Binary: -2/+2 für Nein/Ja) — dadurch bleibt die bestehende Gewichtung/Mittelwertbildung zur Mitarbeitsnote (3.2) unverändert kompatibel, ohne pro Bewertung erst umrechnen zu müssen. Nur `Points` ist grundverschieden (ein echter Zählwert 0..`MaxPoints`, neues Feld auf `ParticipationAspect`) und wird ausschließlich zum Aggregationszeitpunkt linear auf dieselbe Achse normiert (`ParticipationRatingScale.Normalize`, 0 → -2, MaxPoints → +2) — dafür mussten die drei Stellen, die bisher `r.Value * gewicht` direkt aufsummierten (`ParticipationGradeDialogViewModel.Recompute`, `ParticipationWizardViewModels.WeightedRating` und `.ComputeSuggestion`), auf die Normierung umgestellt werden; ohne diese Korrektur hätte ein Punkte-Aspekt die Mitarbeitsnote grob verfälscht (z.B. "4 Punkte" als Rohwert weit außerhalb der -2..+2-Skala). Raster (`ParticipationTabView`): Zellen für `Points`-Aspekte sind jetzt ein `NumericUpDown` (0..MaxPoints) statt der fünf Stufen-Buttons, die anderen Typen zeigen die jeweils passende Buttonzahl (2/3/5) aus `ParticipationRatingScale.Steps`. Schnelleingabe-Dialog: Zifferntasten und +/− sind jetzt typabhängig (Scale3: Tasten 1–3, Binary: 1/2, Points: Tasten 0–9 als direkte Zahleneingabe bis `MaxPoints`, sonst Cycling mit +/−); die Tastenkürzel-Legende im Dialog zeigt dafür jetzt dynamisch die für den aktuell gewählten Aspekt gültigen Tasten statt eines festen Textes. Aspekt-Verwaltung (3.1.1) um ein "Max. Punkte"-Feld ergänzt, nur sichtbar bei Typ "Punkte". **Bewusst nicht angefasst:** die spezialisierte Trendlinien-Visualisierung im Mitarbeits-Assistenten (Kapitel 3.2-Erweiterung) ist weiterhin fest auf die drei Standardaspekte (Qualität/Quantität/Arbeitsphase) zugeschnitten — eigene Trendlinien für beliebige, selbst angelegte Aspekte wäre ein eigener, größerer Umbau dieser Ansicht. - [x] **3.1.4** Aspekt deaktivieren statt löschen, damit alte Einträge gültig bleiben. **Umsetzung:** Checkbox "Aktiv" je Zeile in der neuen Verwaltung (3.1.1); die bestehenden Abfragen (`GetByGroup`/`GetDefaults`, für Bewertungsraster und -aggregation) filterten inaktive Aspekte bereits vorher heraus, das war schon vor dieser Aufgabe so gebaut. Neu ist nur `GetAllByGroup` (inkl. inaktiver), damit die Verwaltungsansicht deaktivierte Aspekte weiterhin anzeigt und wieder aktivierbar macht. "Löschen" bleibt zusätzlich verfügbar (mit Rückfrage über den bestehenden `ConfirmDialog`, Warnhinweis auf mögliche bereits erfasste Bewertungen), aber "Deaktivieren" ist der empfohlene Weg — ein echter Verwendungs-Check vor dem Löschen (durchsucht alle `ParticipationEntry.Ratings` nach dem Key) ist nicht umgesetzt, da `AspectRating` nicht nach Aspekt-Key indiziert ist. ### 3.2 Aggregation zur Mitarbeitsnote - [x] **3.2.1** Gewichtung je Aspekt konfigurierbar (z.B. Qualität 50 %, Quantität 30 %, Experiment 20 %). - [x] **3.2.2** Berechnung einer Mitarbeitsnote je Halbjahr aus allen Sitzungen, Ausgabe als Note bzw. Punkte je nach `GradingSystem` der Gruppe. - [x] **3.2.3** Umgang mit fehlenden Werten festlegen (nicht bewertet ≠ schlecht bewertet). - [x] **3.2.4** Übernahme der berechneten Mitarbeitsnote als `Grade` mit `Category = Participation` (Anbindung an 2.1). - [x] **3.2.5** Trendanzeige pro Schüler (Entwicklung über die Sitzungen hinweg). Umgesetzt über den neuen Dialog "Ø Mitarbeitsnote" im Mitarbeit-Tab ([ParticipationGradeViewModels.cs](LehrerApp.Desktop/ViewModels/Groups/ParticipationGradeViewModels.cs), [ParticipationGradeDialog.axaml](LehrerApp.Desktop/Views/Groups/ParticipationGradeDialog.axaml)): Zeitraum wählbar (Gesamtjahr/H1/H2), Aspekt-Gewichtung live editierbar (`ParticipationAspect.Weight`, da die eigentliche Aspekt-Verwaltung aus 3.1 noch fehlt), Mapping der Bewertungsskala (-2..+2) auf Note/Punkte über `GradingService.ParticipationGrade()`, einfache Trendanzeige (↑/↓/→) durch Vergleich der ersten mit der zweiten Hälfte der Sitzungen. "Übernehmen" aktualisiert eine bestehende Mitarbeit-Note für denselben Zeitraum statt sie zu duplizieren (erkannt über den `Grade.Note`-Tag). ### 3.3 Sitzungen - [ ] **3.3.1** Sitzung automatisch aus einer geplanten `Lesson` erzeugen (Datum + Thema übernehmen) — Abhängigkeit zu 4.2. - [x] **3.3.2** Anwesenheit in der Sitzung erfassen — als `ParticipationEntry.Attendance` (krank: Entschuldigung offen/entschuldigt/unentschuldigt), nicht als "verspätet"; siehe unten. Vorgriff auf 5.2, ersetzt dessen 5.2.1/5.2.4 aber nicht vollständig (kein Fehlzeitenmodul über alle Stundentypen hinweg, nur innerhalb von Mitarbeit-Sitzungen). - [ ] **3.3.3** Sitzung bearbeiten/löschen inkl. Rückfrage bei vorhandenen Bewertungen. - [ ] **3.3.4** Sitzungen mehrerer Gruppen im Kalenderüberblick. ### Abschnittsnoten & Mitarbeits-Assistent (nicht aus dieser Liste, eigener Workflow-Bedarf) Ergänzt den Workflow "regelmäßig (alle 4–7 Wochen) mündliche Noten zu einer Abschnittsnote zusammenziehen, daraus die Halbjahresnote bilden": - `ParticipationEntry.HomeworkMissing` (bool) und `.Attendance` (`AttendanceStatus?`: `ExcusePending`/`Excused`/`Unexcused`) — editierbar direkt im Bewertungsraster ([ParticipationTabView.axaml](LehrerApp.Desktop/Views/Groups/ParticipationTabView.axaml), Spalten "HA"/"Anwesenheit") und im neuen Mitarbeits-Assistenten. `ExcusePending` ist bewusst ein Zwischenzustand, da die Entschuldigung meist erst später eintrifft. - Neues Modell `ParticipationSection` (Abschnitt: Start/Ende/Bezeichnung je Gruppe) — nur abgeschlossene Abschnitte werden gespeichert, der laufende Zeitraum ergibt sich aus dem Ende des letzten Abschnitts bis heute. - Neues Fenster **Mitarbeits-Assistent** ([ParticipationWizardViewModels.cs](LehrerApp.Desktop/ViewModels/Groups/ParticipationWizardViewModels.cs), [ParticipationWizardDialog.axaml](LehrerApp.Desktop/Views/Groups/ParticipationWizardDialog.axaml)): Zeitleiste je Schüler (Sitzungen als Bewertungs-Badges, Klausurtermine, Symbole für Hausaufgaben/ Bemerkung/Anwesenheit), gruppiert nach Abschnitten. "Abschnitt abschließen" berechnet für alle Schüler einen Notenvorschlag aus den Sitzungen im Zeitraum und speichert ihn als `Grade` (`Category = Participation`, `Note = "Abschnitt: "`), individuell nachjustierbar. "Halbjahresnote aus Abschnitten übernehmen" mittelt die Abschnittsnoten eines Zeitraums und speichert sie über denselben `Grade.Note`-Tag wie der bestehende "Ø Mitarbeitsnote"-Dialog (3.2) — beide Wege sind kompatibel, der Assistent baut auf 3.2 auf statt es zu ersetzen. - Dashboard-Kachel "Offene Entschuldigungen": listet alle `ExcusePending`-Einträge der letzten 21 Tage gruppenübergreifend mit Direktauflösung; ältere Einträge werden ausgeblendet statt automatisch entschieden (pädagogische Entscheidung bleibt bei der Lehrkraft). --- ## 4. Unterrichtsplanung Modelle `Unit` und `Lesson` existieren, `UnitRepository`/`LessonRepository` ebenfalls. Navigationspunkt "Unterrichtsplanung" zeigt seit 4.3 den Stundenplan (`TimetableView`, Standardtab "Heute" + Bearbeiten-Raster) statt eines `PlaceholderViewModel`. Eine gruppenübergreifende Wochenansicht fehlt noch, siehe 4.4. 4.1/4.2 leben unabhängig davon im gruppenspezifischen Tab "Planung" (`GroupDetailView`), siehe unten. ### 4.1 Unterrichtseinheiten (`Unit`) - [x] **4.1.1** Listenansicht der Einheiten je Gruppe mit Status und Zeitraum (ersetzt den Platzhalter im Tab "Planung"). - [x] **4.1.2** Dialog Einheit anlegen/bearbeiten: Titel, Fach, Zeitraum, Status, Notizen. **Nachtrag (Nutzer-Feedback, im Zuge des 4.3.3-Bugfixes):** Bei einer Klasse, die in mehreren Fächern unterrichtet wird (mehrere `LearningGroup`-Datensätze mit gleichem Namen, z.B. zwei "10c"), zeigte der Dialog bisher nur das Fach ("Fach: Chemie"), nicht welche Klasse gemeint ist — beides zusammen war nirgends auf einen Blick zu sehen. Jetzt zeigen sowohl `UnitDialog` (Einheit) als auch `LessonDialog` (Stunde) direkt unter dem Dialogtitel "Gruppe · Fach" (z.B. "10c · Chemie"), nicht editierbar, nur zur Einordnung. - [x] **4.1.3** Kompetenzen aus dem Katalog (siehe 8) einer Einheit zuordnen — Mehrfachauswahl. - [x] **4.1.4** Einheit als Vorlage speichern und in eine andere Gruppe kopieren (inkl. Stunden, ohne Datumsbezug). - [x] **4.1.5** Fortschrittsanzeige: gehaltene / geplante Stunden der Einheit. ### 4.2 Einzelstunden (`Lesson`) - [x] **4.2.1** Stundenliste innerhalb einer Einheit, sortiert nach Datum/Stundennummer. - [x] **4.2.2** Stundeneditor als tabellarischer Verlaufsplan (Vorbild: vom Nutzer bereitgestelltes Beispiel eines realen Stundenverlaufsplans) statt einzelnem Phase-Textfeld + Methoden-/ Materialien-Chips: mehrere Phasen-Zeilen (Einstieg, Erarbeitung 1, Sicherung 1, ...), je Zeile Name, Dauer (Minuten), Tätigkeit, Material (mit Autovervollständigung aus bisherigen Einträgen) und ein Kurzsymbol, das den Materialfluss kodiert (z.B. `Tb->SH` = Tafelbild wird ins Schülerheft übertragen). - [x] **4.2.3** Status `Planned → Conducted` setzen, Reflexionsfeld nach der Stunde. - [x] **4.2.4** Stunden verschieben (z.B. bei Ausfall) — Folgestunden automatisch nachrücken. - [x] **4.2.5** Stunden serienweise aus dem Stundenplan (4.3) erzeugen — neuer Button "Serie erzeugen" in der Stunden-Toolbar der Planung ([GenerateLessonSeriesDialog.axaml](LehrerApp.Desktop/Views/Groups/GenerateLessonSeriesDialog.axaml), `GenerateLessonSeriesDialogViewModel` in [PlanningViewModels.cs](LehrerApp.Desktop/ViewModels/Groups/PlanningViewModels.cs)). Legt für jeden Wochentag/Stunde, den die Gruppe laut `TimetableSlot` (4.3) hat, im gewählten Zeitraum eine `Lesson` an; Zeitraum ist standardmäßig `Unit.StartDate`–`Unit.EndDate`, falls gesetzt. Schulferien/Feiertage werden übersprungen (dieselbe Prüfung wie im Stundenplan-Wochenraster), bereits vorhandene Termine (gleiches Datum + gleiche Stundennummer der Gruppe, unabhängig von der Einheit) nicht doppelt angelegt. Ergebnis ("3 Stunde(n) angelegt, 1 durch Ferien/Feiertage übersprungen, ...") kommt als Toast (erste tatsächliche Nutzung von `NotificationService.ShowSuccess`, bis dahin nur `ShowError` im Einsatz). Neue Stunden haben bewusst kein Thema (Platzhalter zum Ausfüllen) — die "Thema erforderlich"-Pflicht des manuellen "+ Stunde"-Dialogs gilt hier nicht. Umgesetzt über den neuen Tab "Planung" in [GroupDetailView.axaml](LehrerApp.Desktop/Views/Groups/GroupDetailView.axaml) (ersetzt den bisherigen Platzhalter), analog zu Mitarbeit/Noten als eigenes UserControl [PlanningTabView.axaml](LehrerApp.Desktop/Views/Groups/PlanningTabView.axaml) mit eigenem [PlanningTabViewModel](LehrerApp.Desktop/ViewModels/Groups/PlanningViewModels.cs) (Einheiten-Tabelle oben, Stunden der ausgewählten Einheit darunter). Modelle, Repositories und DI-Registrierung existierten bereits; neu sind nur die UI-Schicht sowie zwei Verhaltensfragen: - **Fach** wird wie bei `Exam` nicht auf `Unit` gespeichert, sondern read-only aus `LearningGroup.SubjectId` anzeigt (jede Gruppe unterrichtet ein Fach). - **Verschieben mit Nachrücken (4.2.4):** `MoveLessonDialog` fragt neues Datum + Checkbox "Folgestunden automatisch verschieben" (Standard an) ab. Verschoben werden dabei nur noch geplante (`Planned`) Stunden derselben Einheit, die ursprünglich nach der verschobenen Stunde lagen; bereits durchgeführte (`Conducted`) Stunden bleiben unangetastet. - **Vorlage-Kopie (4.1.4):** `CopyUnitDialog` fragt Zielgruppe + neues Startdatum ab. "Ohne Datumsbezug" bedeutet konkret: die relativen Tages-Abstände der Stunden zueinander bleiben erhalten, werden aber auf das neue Startdatum re-verankert statt die alten Kalendertage zu übernehmen. Reflexion wird geleert, Status auf "Geplant" zurückgesetzt, `Lesson.GroupId` wird auf jeder Kopie explizit auf die Zielgruppe gesetzt (siehe [Datenmodell.md](docs/Datenmodell.md), Abschnitt "Bewusste Denormalisierung"). `LessonRepository.GetByUnit` sortiert jetzt zusätzlich nach `LessonNumber` bei gleichem Datum (Doppelstunden am selben Tag). **Nachtrag zu 4.2.2 (Verlaufsplan-Redesign):** `Lesson.Phase`/`Methods`/`Materials` wurden durch `Lesson.Phases: List` (Name/Dauer/Tätigkeit/Material/Kurzsymbol je Zeile) sowie ein optionales `Lesson.StartTime` ersetzt — Modelle, Migration und Editor in [Planning.cs](LehrerApp.Core/Models/Planning.cs), [LiteDbContext.cs](LehrerApp.Data/LiteDbContext.cs) (Schema-Version 3, `MigrateLessonPhases()` fasst alte Lessons verlustfrei in eine synthetisierte Phasen-Zeile zusammen) und [PlanningViewModels.cs](LehrerApp.Desktop/ViewModels/Groups/PlanningViewModels.cs). Design-Entscheidungen: - **Dauer ist die primäre Eingabe** je Phase (man plant in Zeitblöcken); die Uhrzeit je Phase wird im Editor nur zur Anzeige daraus abgeleitet (`LessonDialogViewModel.RecomputeTimes()`), sofern das optionale `StartTime`-Feld ("Beginn") gesetzt ist. Der Stundenplan (4.3) existiert noch nicht — bis dahin wird `StartTime` manuell gepflegt, könnte später automatisch befüllt werden. - **Kurzsymbol** (`LessonPhaseStep.Shorthand`) ist bewusst ein einzelnes Freitextfeld statt einer erzwungenen Von/Nach-Struktur (Nutzer-Feedback: manchmal ein Materialfluss-Pfeil wie "AB001->S", manchmal nur eine Sozialform ohne Pfeil wie "Plenum" oder "LDE"). Vorschläge kombinieren den in den Einstellungen gepflegten Kürzel-Katalog (neues Modell `ShorthandCode`, CRUD-Muster 1:1 von der Fach-Verwaltung übernommen; Startwerte `DefaultShorthandCodes` L/S/Tb/SH/AB/GA, solange der Katalog leer ist — kein DB-Seed) mit bereits in anderen Stunden der Gruppe frei getippten Werten (`PlanningTabViewModel.KnownShorthands`, analog zur Material-Autovervollständigung) — ein Kurzsymbol muss also nicht vorab im Katalog stehen, um beim nächsten Mal wieder vorgeschlagen zu werden. - **Tabellenlayout statt Karten** (Nutzer-Feedback: gestapelte Bordered-Cards pro Phase wirkten träge): der Verlaufsplan-Editor zeigt eine Kopfzeile mit Spaltentiteln und je Phase eine flache, einzeilige Zeile mit denselben Spaltenbreiten (Name/Dauer/Zeit/Tätigkeit/Material/Kurzsymbol/ Auf-Ab-Entfernen) statt mehrerer intern gestapelter Unterzeilen — nur die Tätigkeit wächst bei Bedarf mehrzeilig, alles andere bleibt einzeilig nebeneinander wie in der realen Vorlage. - Die Migration liest die alte Struktur bewusst über rohe `BsonDocument`s statt über die typisierte `Lessons`-Collection, da die alten Felder nach der Modelländerung beim typisierten Deserialisieren sonst bereits verworfen wären, bevor sie gelesen werden können. **Nachtrag zu 4.2.2 (Stundenraster + Zeitbedarf-Rückmeldung):** Der Verlaufsplan-Editor kannte bis dahin nur die geplante Gesamtdauer, nicht wie viel Zeit die Stunde laut Stundenplan tatsächlich hat. Neuer Tab "Stundenraster" in den Einstellungen pflegt Beginn/Ende je Stundennummer (1.–10. Stunde, `PeriodScheduleService` — JSON-Datei, gleiches Muster wie `SchoolCalendarSettingsService`/`PrivacySettingsService`). Nicht jede Stunde muss eingetragen sein. - **Doppelstunden-Erkennung:** Ausgehend von der eingetragenen Stundennummer wird so lange die jeweils nächste Periode addiert, wie der Stundenplan (4.3, `TimetableSlot`) für dieselbe Gruppe am selben Wochentag *dort ebenfalls* einen Slot hat — eine Lesson mit Stundennummer 3 bekommt bei einer Doppelstunde 3./4. also automatisch 90 statt 45 Minuten als Vergleichsbasis, ohne dass das irgendwo separat markiert werden muss. Gehört die Folgeperiode einer anderen Gruppe, wird sie korrekt nicht mitgezählt. - **Farbskala** (`LessonDialogViewModel.TimeBudgetColor`, Nutzer-Vorgabe): 93–96 % Auslastung ist der Zielbereich (grün) — ein kleiner Puffer, da 100 % laut Nutzer "meist schon knapp" ist. Von dort Richtung 100 % wird es zunehmend orange, darüber (überplant) kräftiger rot. Für "deutlich zu wenig geplant" (unter 70 %) hatte der Nutzer noch keine feste Vorstellung — hier bewusst ein neutrales Blaugrau statt Rot gewählt (kein Fehler, nur "hier geht noch was"); Grenzwerte/Farben sind über die switch-Ausdrücke leicht nachjustierbar. - **Beginn wird beim Setzen der Stundennummer automatisch aus dem Stundenraster übernommen**, sofern noch keiner eingetragen ist (überschreibt nie einen bereits vorhandenen Wert) — damit entfällt die bisher manuelle Pflege des "Beginn"-Felds für Stunden, die im Stundenraster hinterlegt sind, ganz von selbst. - Ohne Stundennummer/gültiges Datum oder ohne im Stundenraster hinterlegte Zeiten für die betroffene(n) Periode(n) bleibt die Rückmeldung schlicht ausgeblendet statt eine erfundene Dauer vorzutäuschen. **Ideensammlung "Live-Unterrichtsmodus" (noch nicht geplant, nicht Teil von 4.2):** beim Besprechen des Verlaufsplan-Redesigns kamen weitergehende Wünsche auf, die bewusst zurückgestellt wurden, da sie eigene Datenmodelle (Live-Session-Zustand, Phasen-Verschiebung zwischen Stunden) brauchen: - Fortschrittslinie/Zeitanzeige während des Haltens der Stunde, die zeigt, wo man gerade stehen müsste. - "Fertig"/"Überziehen" je Phase anklickbar, nachfolgende Phasenzeiten passen sich automatisch an; Visualisierung, wie weit man dem Plan hinterherhängt. - Live-Sprung zwischen Wegen während des Haltens der Stunde (per Klick direkt in den alternativen Ablauf springen) — die Planungs-/Anzeigeseite (Kennzeichnen, gruppierte Darstellung) ist bereits umgesetzt, siehe Nachtrag unten; nur die Live-Interaktion beim Halten fehlt noch. - "Phase in nächste Stunde schieben"-Aktion, mit Hinweis-Symbol an der Folgestunde, dass dort noch offene Phasen/Arbeitsaufträge der Vorstunde einzuplanen sind. - Phasen-"Parkplatz": beim Planen Phasen ablegen können, ohne sie sofort einer Stunde zuzuordnen, und später flexibel in eine beliebige Stunde einfügen — Werkzeug, um Einheiten während des Schuljahres an die Realität anzupassen, wenn der Unterricht nicht wie geplant läuft. **Nachtrag — schreibgeschützte Verlaufsplan-Ansicht:** Um den Verlaufsplan-Editor überhaupt im Unterricht benutzen zu können, ohne versehentlich etwas zu verändern, gibt es jetzt `LessonViewerDialog` (erreichbar über "Anzeigen" im Stunden-Toolbar, sichtbar bei ausgewählter Stunde) — reine, größer geschriebene Anzeige des Verlaufsplans inkl. abgeleiteter Uhrzeit je Phase, ohne jede Bearbeitungs- oder Verlängern-/Verschieben-Funktion. Das ist bewusst nur ein Ausschnitt aus der "Fortschrittslinie"-Idee oben (rein statische Anzeige, keine Live-Aktualisierung nach Uhrzeit, kein "fertig"/"überziehen") — der volle Live-Modus bleibt Ideensammlung. **Nachtrag — Alternative Unterrichtsabläufe, zweite Iteration (echter Katalog statt Freitext):** Die erste Umsetzung (`LessonPhaseStep.AlternativePath: string?`, freier Bezeichner, Zuweisung über eine durchgehend sichtbare `AutoCompleteBox`-Spalte) wirkte im Alltag zu prominent für einen Fall, der selten vorkommt. Nutzer-Feedback: eine unauffällige Checkbox reicht für den Normalfall, die eigentliche Zuordnung gehört in einen separaten Dialog, und ein alternativer Ablauf sollte eine Erklärung tragen können (wann/wofür er gedacht ist, z.B. "bei Zeitmangel"). Redesign: - Neues Modell [`AlternativeLessonPath`](LehrerApp.Core/Models/Planning.cs) (Name + optionale `Description`) als eigener, wiederverwendbarer Katalog — `LessonPhaseStep.AlternativePathId: Guid?` referenziert einen Eintrag statt Freitext direkt zu speichern (`null` = Hauptweg). `IAlternativeLessonPathRepository`/`AlternativeLessonPathRepository`, eindeutig auf `Name`, kein eigenes Verwaltungs-UI in den Einstellungen — Einträge entstehen inline beim Zuweisen. - **Editor**: Die "Pfad"-Spalte ist jetzt eine einzelne `CheckBox` (`PhaseStepEditItem. HasAlternativePath`), deren `Content` bei Zuordnung den Namen des Ablaufs zeigt, `Foreground` deterministisch aus dem Namen eingefärbt (kleines, farbig unterstütztes Label statt breiter Eingabespalte). Ankreuzen öffnet `AlternativePathDialog` (Combobox über vorhandene Abläufe + Formular zum Anlegen eines neuen inkl. Beschreibung); Abwählen entfernt die Zuordnung direkt ohne Dialog. `LessonDialogViewModel.OnPickAlternativePath` (vom Code-Behind von `LessonDialog` selbst verdrahtet, nicht vom übergeordneten `PlanningTabView` — der Dialog braucht das `LessonDialog`-Fenster als Owner) kapselt diesen Ablauf. - **Viewer**: `LessonViewerViewModel` löst `AlternativePathId` über den Katalog auf und zeigt die hinterlegte Beschreibung unter der Gruppenüberschrift. - **Migration v3→v4** (`MigrateLessonAlternativePaths`): alte `AlternativePath`-Freitextwerte werden pro distinktem Namen zu einem `AlternativeLessonPath`-Katalogeintrag zusammengeführt (gleicher Name über mehrere Stunden hinweg → derselbe Eintrag, wiederverwendet) und referenziert. - Weiterhin bewusst **kein** Bedingungsfeld ("wenn an Stelle X nur noch weniger als Y Minuten") — das setzt Live-Zeiterfassung während des Unterrichtens voraus (siehe Live-Unterrichtsmodus oben) und war ausdrücklich als Idee, nicht als Anforderung formuliert. `AlternativeLessonPath. Description` deckt die Erklärung als Freitext ab; eine echte Bedingung bliebe für später. - Bewusst weiterhin **kein** Fork-Punkt-Modell: jede Gruppe berechnet ihre abgeleitete Uhrzeit im Viewer unabhängig ab `Lesson.StartTime`, nicht ab einer gemeinsamen Verzweigungsstelle im Hauptweg — deutlich einfacher und für den schnellen Überblick ausreichend. ### 4.3 Stundenplan - [x] **4.3.1** Neues Modell `TimetableSlot` (Gruppe, Wochentag, Stunde, Raum) + Repository — [Planning.cs](LehrerApp.Core/Models/Planning.cs), [TimetableSlotRepository](LehrerApp.Data/Repositories/AllRepositories.cs). Wiederkehrendes Muster (kein Datum) — für konkret gehaltene Stunden bleibt `Lesson` (4.2) zuständig. Pro Wochentag/Stunde höchstens eine Gruppe (eindeutiger Index + freundliche Fehlermeldung im Repository selbst, nicht erst über eine rohe `LiteException`): ein Lehrer kann nicht gleichzeitig an zwei Orten unterrichten. - [x] **4.3.2** Wochenstundenplan-Ansicht als Raster mit Farbcodierung je Gruppe — neuer Navigationspunkt "Unterrichtsplanung" (bisher `PlaceholderViewModel`) zeigt jetzt [TimetableView.axaml](LehrerApp.Desktop/Views/Planning/TimetableView.axaml). Mo–Fr × 1.–10. Stunde, Farbe deterministisch aus dem Gruppennamen (gleiches Hash-in-Palette-Muster wie bei den alternativen Unterrichtsabläufen in 4.2.2). - [x] **4.3.3** Bearbeitung per Klick im Raster — Klick auf eine leere Zelle öffnet `TimetableSlotDialog` zum Zuweisen (Gruppenauswahl + optionaler Raum), Klick auf eine belegte Zelle öffnet denselben Dialog zum Ändern/Entfernen. **Drag bewusst nicht umgesetzt:** Klick deckt die vollständige Bearbeitung (Zuweisen/Ändern/Entfernen) bereits ab: Drag wäre nur eine schnellere Geste für "Zuordnung an eine andere Zelle verschieben", kein zusätzlicher Funktionsumfang — bei Bedarf später ergänzbar. **Nachtrag (Bugfix, aus echtem Nutzungsfall):** `TimetableSlotDialogViewModel` baute die Gruppenauswahl für den Zuweisen-Dialog als `Dictionary`, geschlüsselt nach `LearningGroup.Name` — bei zwei Lerngruppen mit demselben Namen (dieselbe Klasse in zwei Fächern unterrichtet, z.B. zwei Datensätze "10c") warf `ToDictionary` eine `ArgumentException` ("An item with the same key has already been added"), der Dialog ließ sich gar nicht mehr öffnen. Behoben durch eindeutige Anzeige-Labels statt des rohen Namens: bei einem Namenskonflikt wird das Fach angehängt ("10c (Chemie)" vs. "10c (Mathematik)"), mit einem nummerierten Fallback-Suffix für den theoretischen Restfall gleicher Name UND gleiches Fach. Zwei Regressionstests in [TimetableSlotDialogViewModelTests.cs](LehrerApp.Desktop.Tests/TimetableSlotDialogViewModelTests.cs) bilden genau dieses Szenario nach. - [x] **4.3.4** Abgleich mit `LearningGroup.HoursPerWeek` — Seitenleiste "Abweichende Wochenstunden" listet jede Gruppe der aktuellen Schuljahres, deren eingetragene Slot-Anzahl nicht der hinterlegten Wochenstundenzahl entspricht. - [x] **4.3.5** Schulferien und Feiertage — gesetzliche Feiertage werden je Bundesland berechnet ([PublicHolidayService.cs](LehrerApp.Core/Services/PublicHolidayService.cs), Gauß'sche Osterformel + bundeslandspezifische Zusatzfeiertage, empirisch gegen bekannte Ostersonntage 2023–2027 verifiziert) statt gespeichert — anders als Schulferien sind sie algorithmisch herleitbar. Schulferien selbst sind **nicht** herleitbar (jährlich neu von den Bundesländern festgelegt) und werden deshalb manuell gepflegt (`SchoolHoliday`-Repository, CRUD in der Seitenleiste). Bundesland als persistente Einstellung ([SchoolCalendarSettingsService.cs](LehrerApp.Core/Services/SchoolCalendarSettingsService.cs)). "Aus der Planung ausnehmen" ist für dieses Kapitel als "nächste unterrichtsfreie Tage" sichtbar (kombinierte, sortierte Liste aus beiden Quellen) — die eigentliche Ausnahme aus generierten Stunden ist erst mit der (bewusst zurückgestellten) Serienerzeugung 4.2.5 relevant und verwendet dieselben zwei Datenquellen. **Nachtrag zu 4.3 (Nutzer-Feedback nach Erstumsetzung):** - **Ferien/Feiertage-Pflege verschoben:** Die Bundesland-Auswahl und das Schulferien-CRUD standen ursprünglich in der Seitenleiste des Stundenplans selbst — das wirkte dort deplatziert, da es eine einmalige Einstellung statt einer täglich genutzten Ansicht ist. Beides ist jetzt ein eigener Tab "Ferien & Feiertage" in den Einstellungen ([SettingsViewModel.cs](LehrerApp.Desktop/ViewModels/Settings/SettingsViewModel.cs), [SettingsView.axaml](LehrerApp.Desktop/Views/Settings/SettingsView.axaml)). Der Stundenplan selbst zeigt Bundesland und nächste unterrichtsfreie Tage nur noch lesend an ([TimetableViewModel.cs](LehrerApp.Desktop/ViewModels/Planning/TimetableViewModel.cs)). - **Badge "1"/"2" vor Ferien:** Die letzte bzw. vorletzte Stunde eines Wochentags vor den nächsten Schulferien wird im Bearbeiten-Raster mit einem kleinen Badge markiert — Erinnerung, rechtzeitig abzuschließen. Berechnet pro (Wochentag, Gruppe) statt pro einzelnem `TimetableSlot`: eine Doppelstunde besteht aus zwei Slots mit demselben Wochentag/derselben Gruppe und bekommt dadurch automatisch dasselbe Badge, ohne gesonderte Blockerkennung. Gesetzliche Feiertage werden beim Abzählen der verbleibenden Termine übersprungen. Bewusst nur an `SchoolHoliday` geknüpft, nicht an einzelne `PublicHoliday`-Tage — es geht um das Vorbereiten auf eine längere Unterbrechung, nicht um einen einzelnen freien Tag. - **"Heute" als neue Standardansicht:** Der Stundenplan öffnet jetzt auf einem nicht-editierenden Tab "Heute" (heutige Stunden mit Gruppe, Raum, Thema aus `Lesson.Topic` falls vorhanden, sowie ein Hinweis, wenn an dem Tag eine Klausur ansteht). Klick auf eine Stunde navigiert in die Lerngruppe (Tab "Planung"). Das bisherige Bearbeiten-Raster aus 4.3.2/4.3.3 ist über einen Button ("Stundenplan bearbeiten") als zweiter Tab weiterhin erreichbar. **Nachtrag zu 4.3, zweite Iteration (Wochenraster in der "Heute"-Ansicht):** Die Tagesliste allein beantwortete nicht "was steht in der Woche noch an" bzw. "was macht der parallele Kurs" — beides Fragen, die nur ein Blick über alle Gruppen hinweg beantwortet. Der Tab "Heute" zeigt deshalb jetzt zusätzlich ein schreibgeschütztes Wochenraster (Mo–Fr × 1.–10. Stunde, wie das Bearbeiten-Raster, aber ohne Klick-zum-Bearbeiten). Jede Kachel zeigt Fach (`Subject.ShortName`), Klasse, Raum und – falls für den Tag hinterlegt – das Thema der Stunde, dazu Symbole für: Ferien-Badge ("1"/"2", wie im Bearbeiten-Raster), Klausur an dem Tag (📝), letzte Stunde vor der nächsten Klausur dieser Gruppe (⏰) und ein geplantes Experiment (🧪). Die Tagesliste ist per `DockPanel` unten an die Seite angedockt, das Wochenraster füllt den verbleibenden Platz darüber ([TimetableViewModel.cs](LehrerApp.Desktop/ViewModels/Planning/TimetableViewModel.cs), [TimetableView.axaml](LehrerApp.Desktop/Views/Planning/TimetableView.axaml)). Klick auf eine Kachel navigiert wie in der Tagesliste zur Lerngruppe. - **"Letzte Stunde vor Klausur"** wird wie das Ferien-Badge pro (Wochentag, Gruppe) berechnet, aber am nächsten `Exam.Date` der jeweiligen Gruppe statt an einem gemeinsamen Ferientermin ausgerichtet — beide Badges teilen sich denselben Zähl-Helfer (`CountOccurrences`). - **"Experiment geplant"** hat kein eigenes Datenfeld — es ist eine Texterkennung über die bereits im Verlaufsplan-Editor (4.2.2) gepflegten `LessonPhaseStep`-Felder (Name/Aktivität/ Material enthält "Experiment", ohne Groß-/Kleinschreibung). Bewusst kein neues Modellfeld: die Information steckt in den meisten Fällen schon in der ohnehin gepflegten Phasenplanung. - Das Wochenraster zeigt bewusst nur die laufende Kalenderwoche ohne Vor-/Zurück-Navigation — für einen Blick auf zukünftige Wochen bleibt vorerst das Bearbeiten-Raster (zeigt das wiederkehrende Muster unabhängig vom Datum). **Nachtrag zu 4.3, dritte Iteration (Ferientage im Wochenraster ausgegraut statt eigener Liste):** Die Box "Nächste unterrichtsfreie Tage" wirkte neben dem neuen Wochenraster redundant — welche Tage frei sind, sieht man dort jetzt direkt an den betroffenen Stunden. Die Box (samt `UpcomingFreeDayItem`, `UpcomingFreeDays`, `BuildUpcomingFreeDays`, `BundeslandLabel`) wurde entfernt. Stattdessen werden Wochenkacheln, deren Datum in Schulferien fällt oder ein gesetzlicher Feiertag ist, grau eingefärbt (`WeekCellItem.IsHoliday`, Prüfung gegen `SchoolHoliday`-Zeitraum und die bereits für die Badges berechneten `PublicHoliday`-Daten) und zeigen statt der Klausur-/ Experiment-Symbole nur noch die Aufschrift "Ferien" — die Symbole wären an einem unterrichtsfreien Tag ohnehin nicht sinnvoll interpretierbar. **Nachtrag zu 4.3, vierte Iteration (Wochennavigation):** Schalter "‹"/"›" plus "Diese Woche" erlauben jetzt, im Wochenraster vor und zurück zu blättern (`TimetableViewModel.WeekOffset`, `PreviousWeekCommand`/`NextWeekCommand`/`CurrentWeekCommand`). Die Ferien-/Klausur-Nähe-Badges ("1"/"2" vor Ferien, ⏰ vor Klausur) waren bis dahin an "heute" verankert und pro (Wochentag, Gruppe) einmalig berechnet — das wäre beim Blättern falsch geworden (ein Badge hätte in jeder angezeigten Woche geklebt, nicht nur in der einen Woche, zu der es gehört). Für das Wochenraster berechnen `HolidayBadgeFor`/`IsLastBeforeExamFor` die Badges deshalb jetzt je Zelle am dort gezeigten Datum statt an "heute" — das Bearbeiten-Raster (zeigt ohnehin nur das wiederkehrende Muster ohne Datum) behält die alte, "heute"-verankerte Berechnung. Beim erneuten Navigieren in den Stundenplan (Sidebar-Klick) springt die Ansicht wieder auf die laufende Woche zurück. **Nachtrag zu 4.3, fünfte Iteration (Aufsichten + Vertretung):** Nutzer-Feedback: zwischen manchen Stunden ist auch Pausenaufsicht zu erledigen, und gelegentlich kommen Sonderfälle vor — eine Vertretungsaufsicht für einen erkrankten Kollegen, oder eine Vertretungsstunde in einer eigenen oder fremden Lerngruppe. - **Neue Modelle** ([Planning.cs](LehrerApp.Core/Models/Planning.cs)): `SupervisionDuty` (wiederkehrend, Wochentag + "Pause nach Stunde X" + Ort — `AfterPeriod = 0` heißt Frühaufsicht vor der 1. Stunde) und `SubstitutionEntry` (einmalig, an einem konkreten Datum — entweder `Kind = Supervision` oder `Kind = Lesson`). Bewusst getrennt von `TimetableSlot`/`Lesson`: die wiederkehrende Aufsicht hat keinen Gruppenbezug, und die meisten Vertretungsstunden sind keine durchgeplanten Einheiten-Stunden. - **Wiederkehrende Aufsicht wird nur in den Einstellungen gepflegt** (neuer Tab "Aufsichten", analog zum Kürzel-Katalog: Liste + Formular, kein Klick-Dialog im Stundenplan-Raster selbst — anders als bei `TimetableSlot`, weil eine kleine, seltene Liste hier ergonomischer ist als ein Klick durchs ganze Raster). Pro Wochentag/Pause höchstens eine Aufsicht (eindeutiger Index, freundliche Fehlermeldung wie bei `TimetableSlot`). - **Anzeige im Stundenplan:** Aufsicht-Zeilen werden zwischen den betroffenen Stundenzeilen sowohl im Bearbeiten-Raster als auch im Wochenraster eingefügt (nur wenn für die jeweilige Pause tatsächlich etwas hinterlegt ist, sonst bleibt die Zeile weg) — `TimetableCellItem`/`WeekCellItem` bekamen dafür `IsSupervisionRow`/`IsSupervisionCell` als weitere, sich gegenseitig ausschließende Zellenarten (gleiches Muster wie die bestehenden `IsHeader`/`IsPeriodLabel`/`IsSlotCell`), statt eine zweite `ItemsControl` neben das bestehende `UniformGrid` zu setzen. - **"Vertretung eintragen"**-Dialog (neuer Button neben "Stundenplan bearbeiten" im "Heute"-Tab, [SubstitutionEntryDialogViewModel.cs](LehrerApp.Desktop/ViewModels/Planning/SubstitutionEntryDialogViewModel.cs)) deckt beide Sonderfälle ab. Eine Vertretungsaufsicht/-stunde für ein konkretes Datum überschreibt in Wochenraster, Bearbeiten-Zeile bzw. Tagesliste die sonst dort angezeigte reguläre Information für diese eine Stunde/Pause — sie beschreibt ja, was an dem Tag tatsächlich passiert. Vertretungsstunden ohne passenden `TimetableSlot` (z.B. fremde Gruppe zu einer Zeit, zu der man sonst frei hat) werden trotzdem ergänzt, nicht verworfen. - **Entscheidung zur eigenen-Gruppe-Frage** (mit dem Nutzer abgestimmt): Standard bleibt der einfache Weg — nur ein `SubstitutionEntry` mit Thema, wie bei einer fremden Gruppe. Nur wenn die gewählte eigene Gruppe mindestens eine `Unit` hat, erscheint zusätzlich eine Checkbox "Direkt als Stunde in der Einheit übernehmen" (mit Einheiten-Auswahl) — dann entsteht *zusätzlich* eine echte `Lesson` in dieser Einheit. Der `SubstitutionEntry` bleibt in beiden Fällen bestehen (er ist die Anzeige-Quelle für den Plan), die `Lesson` ist rein für die Fortschritts-/Reihenfolge- Bilanz der Einheit gedacht und wird im Stundenplan nicht separat angezeigt. **Nachtrag zu 4.3, sechste Iteration (Sondereinsätze):** Nutzer-Feedback: neben Vertretung gibt es auch Sondereinsätze wie Ausflüge oder Berufsmessen, die einen Teil des Tages oder den ganzen Tag blockieren, ohne dass jemand vertreten wird. - `SubstitutionKind` um `SpecialAssignment` erweitert; `SubstitutionEntry` bekam `FromPeriod`/ `ToPeriod` (Stundenbereich) und `IsAllDay` (ganztägig statt Stundenbereich). Dieselbe dritte Art im "Vertretung eintragen"-Dialog (`SubstitutionKindDisplay` jetzt mit drei statt zwei Optionen). Bewusst **kein** "Als Stunde in der Einheit übernehmen" für Sondereinsätze — ein Ausflug ist inhaltlich kein Verlaufsplan-Eintrag, anders als eine Vertretungsstunde. - **Anzeige im Wochenraster:** ein Sondereinsatz überdeckt für seinen Wochentag jede Perioden-Zelle im belegten Bereich (bei `IsAllDay` alle 10 Stunden) mit derselben Kachel (Farbe `#00838F`, deutlich von Vertretung-Lila unterscheidbar) — dieselbe Überschreiben-Logik wie bei Vertretungsstunden, nur über mehrere Perioden statt einer einzelnen. - **Anzeige in der Tagesliste:** eigener Abschnitt `TodaySpecialAssignments` (wie bei den Aufsichten) statt Wiederholung über mehrere Zeilen — ein ganztägiger Sondereinsatz würde sonst zehnmal in der Liste auftauchen. - **Mehrtägige Sondereinsätze (Klassenfahrt) bewusst nicht als Datumsbereich modelliert** — mit dem Nutzer abgestimmt: `SubstitutionEntry.Date` bleibt ein einzelnes Datum. Klassenfahrten dauern laut Nutzer höchstens ~5 Tage und kommen alle ein bis zwei Jahre vor — dafür lohnt sich kein eigener Datumsbereich mit den nötigen Änderungen an `GetByDate`/Wochenraster-Abfragen; ein Sondereinsatz über mehrere Tage wird einfach als mehrere Einzeleinträge (einer pro Tag) erfasst. **Nachtrag zu 4.3, siebte Iteration (Stundenausfall):** Nutzer-Feedback: es kann auch sein, dass eine Stunde schlicht ausfällt, ohne dass die eigene Abwesenheit der Grund ist — z.B. fällt der NAT-Unterricht der 6a aus, weil die 6a selbst auf Klassenfahrt ist. Kein Vertretungsfall (niemand übernimmt), kein Sondereinsatz (die eigene Zeit ist nicht belegt). - `SubstitutionKind` um `Cancelled` erweitert, vierte Option im "Vertretung eintragen"-Dialog. Braucht nur Datum + Stundennummer + optionalen Grund — Fach und Gruppe werden beim Anzeigen aus dem an der Stelle regulär eingetragenen `TimetableSlot` abgeleitet, nicht beim Anlegen manuell erfasst (die Information steht ja schon im Stundenplan). Bewusst kein "Als Stunde übernehmen" — ein Ausfall ist per Definition keine gehaltene Stunde. - **Anzeige:** ersetzt im Wochenraster und in der Tagesliste die normale Stunden-Kachel/-Zeile für die betroffene Stunde (Grau `#757575`, Aufschrift "Ausfall") — dieselbe Überschreiben-Logik wie bei Vertretungsstunden und Sondereinsätzen, nur dass hier nichts an die Stelle tritt. **Nachtrag zu 4.3, achte Iteration (Button-Umbenennung + Zahnrad statt "bearbeiten"):** Nutzer-Feedback: Der Button "Vertretung eintragen" deckt nach der fünften bis siebten Iteration auch Sondereinsatz und Ausfall ab, der Name passt nicht mehr. Der Button "Stundenplan bearbeiten" war zudem redundant (das Bearbeiten-Tab ist direkt anklickbar) und fühlte sich unzuverlässig an. - "Vertretung eintragen" → "Ausnahme eintragen" (Button und Dialogtitel in `SubstitutionEntryDialog.axaml`) als neutraler Oberbegriff für alle vier `SubstitutionKind`-Fälle. - "Stundenplan bearbeiten" ersetzt durch einen kleinen Zahnrad-Button ("⚙️"), der in die Einstellungen springt, direkt auf den Tab "Ferien & Feiertage" (Nachbar-Tabs "Stundenraster" und "Aufsichten" sind von dort ein Klick entfernt) — dieselbe Navigate-mit-Zieltab-Konvention wie `NavigateToGroupDetail`. Dafür `SettingsViewModel.ActiveTabIndex` (an `TabbedPage.SelectedIndex` gebunden) sowie `MainWindowViewModel.NavigateToSettings(int initialTab = 0)` neu eingeführt. - `TimetableViewModel.ShowEditorCommand` entfernt (kein Aufrufer mehr) statt umbenannt — es tat ohnehin nur `ActiveTabIndex = 1`, was jetzt nirgends mehr gebraucht wird. ### 4.4 Wochen-/Tagesansicht - [x] **4.4.1** Kalenderansicht über alle Gruppen: Woche und Tag — siehe Nachtrag zu 4.3 ("Heute"-Tab: Tagesliste unten angedockt, gruppenübergreifendes Wochenraster darüber, inkl. Vor-/Zurück-Navigation zwischen Kalenderwochen seit der vierten Iteration). **Nicht enthalten:** ein eigenständiger Kalendermonat-Überblick — bei Bedarf später ergänzbar. - [x] **4.4.2** Sprung von einer Stunde direkt in die Lerngruppe — siehe Nachtrag zu 4.3 ("Heute"-Tab, Klick navigiert zur Planung der Gruppe statt direkt in die Mitarbeitserfassung, da das für den Vorbereitungs-Kontext des Tages passender ist). - [x] **4.4.3** Anzeige anstehender Klausurtermine im Kalender — neue Box "ANSTEHENDE KLAUSUREN" im "Heute"-Tab, gruppenübergreifend, nächste 21 Tage (max. 10 Einträge), unabhängig vom `WeekOffset` des Wochenrasters. `IExamRepository` brauchte dafür ein `GetAll()` (bisher nur `GetByGroup`). **Abgabefristen bewusst nicht enthalten:** es gibt im Datenmodell noch kein Frist-/Deadline-Konzept (nur `Exam.Date`) — das bräuchte ein neues Modell, siehe auch 9.8 ("ggf. einem neuen Termine-Modell"). Kein Aufschub aus Unklarheit, sondern weil das über den angefragten Umfang hinausginge. ### 4.5 Vernetzung Stundenplan ↔ Unterrichtsplanung (zurückgestellt) Nutzer-Feedback nach der 4.3-Iterationen: Stundenplan (4.3, wiederkehrendes Muster) und Unterrichtsplanung (4.1/4.2, `Unit`/`Lesson` je Gruppe) laufen bisher zu getrennt nebeneinander. Ausdrücklich als Idee für später festgehalten, **nicht** jetzt umsetzen: - [ ] **4.5.1** Terminvorschlag beim Anlegen einer `Lesson`: Tag/Stunde anhand des Stundenplans vorschlagen, an dem die betroffene Gruppe laut `TimetableSlot` tatsächlich Unterricht hat, statt Datum/Stunde komplett frei einzutragen. - [ ] **4.5.2** Vom Stundenplan (Wochenraster oder Bearbeiten-Raster) direkt in einen "Viewer" der zugehörigen `Lesson` springen können — und von dort auch eine neue `Lesson` anlegen können. **Offene Frage, vor Umsetzung zu klären:** `Lesson` hängt an einer übergeordneten `Unit` (4.1) — beim Anlegen direkt aus dem Stundenplan heraus ist unklar, welcher `Unit` die neue Lesson zugeordnet werden soll (aktuellste offene Einheit der Gruppe? Rückfrage an die Lehrkraft?). Muss überdacht werden, bevor das gebaut wird. - [ ] **4.5.3** Aus diesem Lesson-Viewer heraus weiter verzweigen können: in die Zeugnisnote/ Bewertung der Gruppe, und in die Schnelldialoge für Mitarbeit sowie Anwesenheit/Hausaufgaben. - [ ] **4.5.4** Badges am Stundenplan/Lesson-Viewer, die anzeigen, ob eine Stunde bereits "kontrolliert" ist — u.a. ob in der vorherigen Stunde Hausaufgaben erteilt wurden und ob diese noch nicht kontrolliert sind. Muss sich pro Fall abschalten lassen: manchmal wird eine Hausaufgabe bewusst nicht kontrolliert, ohne dass bis Schuljahresende ständig daran erinnert werden soll. - [ ] **4.5.5** Stunden aus dieser Ansicht heraus verschieben können, wenn kurzfristig etwas dazwischenkommt. - [ ] **4.5.6** Bei Doppelstunden (90-Minuten-Planung laut Verlaufsplan, 4.2.2) den Inhalt sinnvoll auf die beiden Kacheln/Perioden aufteilen, damit z.B. gezielt nur die zweite Stunde eines Blocks verschoben werden kann, ohne den ganzen Block anzufassen. **Architekturentscheidung (Nachtrag, Konzeptgespräch):** Diskutiert wurde, ob eine eigene Multiplattform-App für die Einheiten-/Stundenplanung sinnvoll ist — analog zum separaten Tafelbilder-Vorhaben (eigene App, nur eine Schnittstelle zur LehrerApp, weil dort die Interaktionsform — Zeichnen/Präsentationsmodus — grundsätzlich anders ist als CRUD). Für die Unterrichtsplanung gilt das **nicht**: Planung findet zu 99,9 % am Desktop-PC oder MacBook statt (Avalonia läuft dort bereits nativ), es besteht also kein Plattformzwang für eine separate App. Entscheidung: **kein** eigenes Domainmodell/eigene App für Einheiten-/Stundenplanung — die folgenden Punkte gehören direkt in `LehrerApp.Desktop`: - [ ] **4.5.7** Graph über Aktivitätsphasen und Anspruchsniveau im zeitlichen Verlauf einer Lesson/Einheit (baut auf `LessonPhaseStep`/`Niveau` auf), um die Stundenverteilung besser einschätzen zu können. - [ ] **4.5.8** Kompetenzen je Aufgabe/Phase verknüpfen — nutzt den bestehenden `CompetencyDomain`/`CompetencyItem`-Katalog (siehe Kompetenzen-Tab in den Einstellungen), bisher nur für Klausuraufgaben (`ExamTask.CompetencyCodes`) verknüpft, nicht für Verlaufsplan-Phasen. - [x] **4.5.9** KI-gestützte Planungsunterstützung über eine Schnittstelle zu einer LLM-API, um Einheiten/Stunden mit Hilfe vorzuschlagen und weiterzuentwickeln. Bedarf eines abgesicherten Zwischenelements auf dem eigenen Server (Ablösung/Verbesserung des bisherigen PHP-Zwischenelements für Elternbriefe) mit interner Abrechnung/Nutzungskontrolle, damit der API-Schlüssel nicht im Client landet. **Umsetzung:** Neuer Button "🤖 KI-Unterstützung" im Planungs-Tab (nur bei ausgewählter Einheit und aktivierter KI-Unterstützung) öffnet `AiAssistDialog`: Freitext-Anweisung an die KI, Antwort wird als Liste vorgeschlagener Stunden mit Checkbox zur Übernahme angezeigt (nichts wird automatisch übernommen). Export/Import-Schema als eigene DTOs in `LehrerApp.Core/AiPlanning/AiPlanningDtos.cs`, bewusst getrennt von den internen Domänenmodellen — ein Wire-Vertrag mit einem externen System muss unabhängig von internen Refactors abwärtskompatibel bleiben. Zentrale Absicherung in `AiPlanningService.ApplyResponse`: eine von der KI zurückgegebene Lesson-Id, die zu keiner tatsächlich zur Einheit gehörenden Lesson passt, wird NIE als Update interpretiert (sonst könnte eine halluzinierte Id im schlimmsten Fall eine fremde Lesson überschreiben), sondern immer als neue Lesson mit frischer Id behandelt — mit dediziertem Test abgesichert. Neue Einstellungen-Tab "KI-Unterstützung": Aktivieren-Schalter, Benutzername/Passwort → Anmelden tauscht das Passwort gegen ein Bearer-Token (nie das Passwort selbst persistiert). Das Token liegt lokal AES-256-verschlüsselt über das bestehende `SyncCrypto` (gleiches Verfahren wie beim Sync-Schlüssel) — besser als die bisherige Klartext-Ablage der Sync-Server-URL, aber mangels Betriebssystem-Schlüsselbund kein vollständiger Schutz gegen jemanden mit Zugriff auf den App-Datenordner; bewusste Abwägung, kein Aufschub aus Unklarheit. Neues PHP-Backend in `ai-backend/` (im Repo, aber nicht Teil von `LehrerApp.sln` — ein separat deploytes System, dessen Schema aber mit den DTOs synchron bleiben soll). MySQL-Schema mit `users`/`tokens`/`transactions`, mehrnutzerfähig von Anfang an. Abrechnung nach echten Token-Kosten (Input-/Output-Token × Preistabelle) statt Pauschalpreis, mit `SELECT ... FOR UPDATE` gegen Race Conditions beim Guthabenabzug. Provider-Schnittstelle vorbereitet für mehrere LLM-Anbieter, in dieser Runde aber **nur Anthropic konkret implementiert** — OpenAI bewusst zurückgestellt, da aktuelle Preise/API-Version zum Zeitpunkt der Implementierung nicht verifiziert werden konnten (kein Aufschub aus Unklarheit über den Bedarf, sondern um keine falsch berechneten Kosten zu riskieren). Kein Admin-UI für Guthaben-Aufladung — bei der aktuellen Nutzerzahl reicht ein dokumentierter manueller SQL-Befehl (`ai-backend/README.md`). Nutzeranlage über `scripts/create-user.php` setzt PHP-CLI/SSH voraus — auf typischem Shared Hosting ohne CLI-Zugriff gibt es mit `ai-backend/setup-user.php` eine web-erreichbare Alternative (durch ein Geheimnis in `config.php` geschützt, nach Gebrauch vom Server zu löschen). **Nicht ohne echtes Deployment + echten API-Key verifizierbar** (siehe `ai-backend/README.md`): ob Anthropic zuverlässig valides JSON im erwarteten Schema liefert, ob die berechneten Kosten exakt mit der echten Abrechnung übereinstimmen, sowie die komplette Kette Desktop → Backend → Anthropic unter echten Netzwerkbedingungen. **Nachtrag (Bugfix, aus echtem Live-Test):** Erste echte Planungsanfrage lieferte beim Verarbeiten der Antwort eine `JsonException`. Ursache: `AiPlanningService.JsonOptions` hatte keinen Converter für `DateOnly`/`TimeOnly` registriert, sodass .NETs Standardformat (ISO 8601, z.B. "2026-12-24") verwendet wurde — der Systemprompt in `plan.php` dokumentiert aber durchgängig deutsches Format ("TT.MM.JJJJ"/"HH:mm"). Das betraf beide Richtungen: die ausgehende Anfrage wurde entgegen der Prompt-Dokumentation im ISO-Format gesendet, und eine dem Prompt folgende KI-Antwort im deutschen Format scheiterte beim Deserialisieren. Behoben durch `GermanDateOnlyJsonConverter`/`GermanTimeOnlyJsonConverter` (in `AiPlanningService.cs`), die deutsches Format schreiben und beim Lesen zuerst deutsches Format, dann defensiv allgemeines Parsen (u.a. ISO) versuchen — LLMs weichen erfahrungsgemäß gelegentlich vom dokumentierten Format ab, ein harter Fehlschlag bei kleinen Abweichungen wäre unnötig fragil. Abgesichert durch `GermanDateTimeJsonConverterTests.cs`, u.a. mit dem konkreten nullable-Feld-Fall (`AiLesson.Date`/`StartTime`), der den ursprünglichen Bug ausgelöst hat. - [ ] **4.5.10** Falls doch ein schlanker Companion-/WebApp-Client entstehen soll: bewusst **minimaler** Funktionsumfang — nur Wochenraster ansehen, eine Stunde verschieben, oder eine Stunde als "Umplanung nötig" flaggen. Kein Editor für Einheiten/Kompetenzen/KI-Planung dort. Technisch schon vorbereitet: `LehrerApp.Sync`/`LehrerApp.Api` haben mit `PlainSyncEvent` und `DeviceType.Companion` bereits eine Klartext-Sync-Schiene für genau diese Art von leichtem, nicht-Desktop-Client (siehe `PlainEventStore`, `MapPlainSyncEndpoints`) — ein "Umplanung nötig"-Flag käme darüber rein und würde nach dem Sync als Hinweis/Badge an der betroffenen Stunde bzw. im Dashboard erscheinen, bis es am Desktop bearbeitet oder bewusst abgehakt wird. - [ ] **4.5.11** KI-Unterstützung (4.5.9) auch für die Stundenplanung (Kapitel 4.3, `TimetableSlot`) anbieten, nicht nur für Einheiten/Stunden im Verlaufsplan — z.B. Vorschläge beim Aufbau eines neuen Stundenplans oder beim Ausgleich nach Änderungen. Gleiches Backend/gleiche Abrechnung wie 4.5.9, aber eigenes Export/Import-Schema für die Stundenplan-Daten. - [x] **4.5.12** Offene Frage: Kann ein Agent/Project bei console.claude.ai eingerichtet werden, um Standardinformationen (JSON-Schema, grundlegender Auftragskontext) dort dauerhaft zu hinterlegen, statt sie bei jeder Anfrage über das eigene PHP-Backend mitschicken zu müssen? Würde die Nutzer-Anweisung auf das eigentlich Fachliche reduzieren. **Antwort: nein, technisch nicht wie gedacht umsetzbar.** Projects/Agents bei console.claude.ai sind eine Funktion der claude.ai-Chat-Oberfläche, nicht der Messages-API, die `ai-backend/plan.php` serverseitig aufruft — es gibt keine Möglichkeit, von dort programmatisch auf einen dort hinterlegten Kontext zuzugreifen (nutzbar wäre das nur bei manueller Bedienung im Browser, ohne App/Abrechnung). Stattdessen wie in 4.5.13 umgesetzt. - [x] **4.5.13** Alternative zu 4.5.12: statt eines extern gepflegten Agents ein app-interner Standard-Prompt neben dem freien Nutzer-Prompt, der den grundlegenden Kontext (Schema, Auftragsbeschreibung) automatisch mitliefert — der Nutzer muss ihn dann nicht jedes Mal selbst formulieren. **Umsetzung:** `$systemPrompt` in `ai-backend/plan.php` zeigt der KI jetzt zuerst das vollständige, literale Eingabeschema (nicht nur Fließtext-Beschreibungen der Felder) und stellt explizit klar, dass die Einheiten-Ebene reiner Lesekontext ist — nur `lessons[]` wird vorgeschlagen/verändert, für die Einheit selbst gibt es in der Antwort kein Feld (Nutzer-Nachtrag, nachdem die erste Fassung Felder nur prosaisch statt strukturell beschrieb). Danach folgen Feldbedeutungen (Fach/Stufe/Gruppe, Kompetenz-/Alternativpfad-Katalog), deutsche Verlaufsplan-Konventionen (typische Phasennamen, übliche Sozialform-Kürzel wie "EA"/"GA"/"UG") und Zeitrichtwerte (≈45 Min./Einzelstunde) — die freie Nutzer-Anweisung muss dadurch nur noch das fachlich Konkrete enthalten. Zusätzlich die Anweisung, bei unklarer Nutzer-Anweisung eine begründete Annahme zu treffen statt zu blockieren (Rückfragen sind über diese Schnittstelle nicht möglich), mit kurzer Begründung im `summary`-Feld. Bewusst nicht angefasst in dieser Runde: der Kürzel-Katalog (`IShorthandCodeRepository`) selbst wird der KI noch nicht als Kontext mitgegeben (nur die Konvention allgemein erklärt) — wäre der nächste sinnvolle Schritt, analog zu `CompetencyCatalog`/`AlternativePathCatalog`. - [x] **4.5.14** Planungsdiff: KI-Vorschläge (4.5.9) mit dem bereits Geplanten auf Feldebene vergleichbar machen, nicht nur pauschal als "Neu"/"Geändert" markieren wie bisher im `AiAssistDialog`. **Umsetzung (erster, bewusst begrenzter Schritt — Teil 2/3 der ursprünglichen Idee zurückgestellt, siehe 4.5.19):** `AiPlanningService.DescribeChanges` vergleicht eine bestehende Lesson mit dem KI-Vorschlag für dieselbe Id und liefert nur die tatsächlich unterschiedlichen Felder als kurze deutsche Sätze (Thema, Datum, Beginn, Stundennummer, "Hausaufgabe/Reflexion geändert", Verlaufsplan-Änderung inkl. Phasenanzahl bei unterschiedlicher Phasenzahl). `AiAssistDialogViewModel.Send` ruft das je geänderter Lesson auf und reicht das Ergebnis an `AiLessonReviewItem.DiffText` durch, im Dialog als kleine Aufzählung unter der jeweiligen Checkbox angezeigt. Rein/ohne I/O, mit dedizierten Tests (u.a. dass identische Lessons keine Diffs liefern und nur wirklich geänderte Felder auftauchen). Rest der ursprünglichen Idee (granulare Übernahme, Konflikterkennung) zurückgestellt, siehe 4.5.19 am Ende dieses Kapitels. - [x] **4.5.15** Umfangs-Umschalter im `AiAssistDialog`: "Einheit umplanen ohne Stunden zu ändern" vs. "mit Stunden ändern" (Nutzer-Nachtrag zum Konzeptgespräch nach 4.5.9). Neue Checkbox "Auch bestehende Stundeninhalte anpassen" (Default: an, entspricht dem bisherigen Verhalten). Zweifach durchgesetzt statt der KI-Antwort blind vertraut: der (bewusst statisch gehaltene, siehe 4.5.16) Systemprompt erklärt im Abschnitt "Umfang dieser Anfrage", dass die KI das mitgesendete Feld `allowModifyingExistingLessons` selbst auswerten und befolgen muss, UND `AiPlanningService.ApplyResponse`/`AiAssistDialogViewModel.Send` verwerfen client-seitig hart jede zurückgegebene Änderung an einer bestehenden Lesson, falls der Umschalter das verbietet — auch wenn die KI die Anweisung ignoriert. Noch nicht umgesetzt: eine interaktive Rückfrage der KI selbst wäre über diese Schnittstelle (einzelne, separat abgerechnete Anfragen) nicht sinnvoll möglich — der Umschalter muss vorher vom Nutzer gesetzt werden, keine Rückfrage während der Anfrage. - [x] **4.5.16** Prompt Caching für wiederkehrende Kontexte (Nutzer-Nachtrag zur Guthabenfrage): der Systemprompt in `ai-backend/plan.php` ist bei jeder Anfrage identisch — dafür extra der zuvor dynamisch interpolierte Umfangs-Abschnitt (4.5.15) wieder statisch gemacht, die KI liest `allowModifyingExistingLessons` jetzt selbst aus der Eingabe statt einer variablen Formulierung im Prompt. `AnthropicProvider.php` markiert den Systemprompt jetzt als "cache_control: ephemeral"; wiederholte Anfragen innerhalb der Anthropic-Cache-TTL (Standard 5 Min.) zahlen dafür nur den reduzierten Cache-Read-Preis statt des vollen Input-Preises. `transactions` um `cache_creation_input_tokens`/`cache_read_input_tokens` erweitert (`ai-backend/migrations/2026-08-add-cache-tokens.sql` für bereits deployte Installationen), Preistabelle in `config.example.php` um `cache_write`/`cache_read` je Modell ergänzt. Provider-Interface entsprechend erweitert (`FakeProvider` liefert dafür einfach 0 zurück). **Nicht ohne echten API-Key verifizierbar**, ob Caching tatsächlich greift — nur an `cache_read_input_tokens > 0` in `transactions` bei einer zweiten Anfrage innerhalb der TTL beobachtbar (siehe `ai-backend/README.md`). - [x] **4.5.17** Fortschrittsanzeige + einfaches Nachfassen im `AiAssistDialog` (Nutzer-Feedback nach erstem echten Live-Test: Dialog wirkte während der Anfrage "aufgehängt", und eine geänderte Anweisung erforderte bisher komplettes Abbrechen/Neustarten). **Umsetzung:** indeterminierter `ProgressBar` unter "Anfrage läuft…". Das Anweisungsfeld bleibt nach der ersten Antwort sichtbar (statt zu verschwinden); ein zweiter Button "Erneut anfragen" sendet eine geänderte/ergänzte Anweisung erneut, wobei die aktuell angehakten Vorschläge der letzten Runde als Entwurfskontext mitgeschickt werden (`AiPlanningService.MergeDraft`: ersetzt Lessons mit passender Id im tatsächlichen Datenbankstand durch die Entwurfsversion, hängt neue Vorschläge ohne Id an) — die KI baut so auf dem bereits gezeigten, noch nicht gespeicherten Stand auf, ohne dass dafür etwas in der Datenbank landen muss. Ergebnisliste wird bei jeder Runde einfach ersetzt, **kein** Seite-an-Seite-Vergleich zwischen den Runden (das wäre 4.5.18). Bewusst einfacherer erster Schritt statt des größeren Umbaus. - [ ] **4.5.18** (Zurückgestellt, Nutzer-Idee zu 4.5.17) Statt die Ergebnisliste beim Nachfassen einfach zu ersetzen: alten und neuen KI-Entwurf gegenüberstellen und den Nutzer pro Stunde (oder insgesamt) wählen lassen, mit welchem Stand weitergemacht wird. Größerer Umbau als 4.5.17 (zwei Entwürfe parallel im Speicher halten, Seite-an-Seite-UI, Entscheidung je Stunde statt nur global) — deshalb erst mal zurückgestellt, bis sich zeigt, ob das einfache Nachfassen aus 4.5.17 in der Praxis schon ausreicht. - [ ] **4.5.19** (Zurückgestellt, Rest von 4.5.14) Granularere Übernahme statt nur ganzer Stunden — z.B. nur einzelne Verlaufsplan-Phasen einer Stunde übernehmen statt zwingend die komplette Stunde. Außerdem: eigene zwischenzeitliche Änderungen an einer Lesson nicht versehentlich durch eine übernommene KI-Antwort überschreiben, falls sich die Einheit seit dem Absenden der Anfrage schon geändert hat (aktuell nicht erkannt/abgesichert). Größerer Umbau als 4.5.14 (Teil-Merge-UI je Phase, Änderungszeitpunkt-Vergleich) — deshalb zurückgestellt. - [x] **4.5.20** Material-/Medienvorschlag mit Zwischenablage-Prompt für externe Erzeugung (Nutzer-Feedback nach erstem echten Einsatz: eine KI-Stunde ist "nicht die eigene Idee", Material/Medien wie Tafelbilder sollen die KI zumindest vorschlagen können, ohne dass die eigentliche Erzeugung über das eigene, kostenpflichtige Backend laufen muss). **Umsetzung:** `AiPhaseStep` bekommt ein neues optionales Feld `MaterialSuggestion` (kurzer, konkreter Vorschlag, was ein Medium zu dieser Phase zeigen/enthalten sollte, z.B. Aufbau eines Tafelbilds) — der Systemprompt in `plan.php` weist die KI an, das nur zu füllen, wenn ein Medium über das ohnehin genannte `material`/`shorthand` hinaus wirklich einen Mehrwert hätte. Bewusst **kein** neues Feld auf dem Domänenmodell `LessonPhaseStep`: der Vorschlag ist nur für die Review-Anzeige/Prompt-Erzeugung relevant, keine Migration nötig, geht beim "Übernehmen" bewusst nicht in die gespeicherte Lesson ein (siehe 4.5.21 für dauerhaftes Speichern als mögliche spätere Erweiterung). `AiPlanningService.BuildMaterialPrompt` baut daraus rein lokal (kein weiterer KI-Aufruf) einen vollständigen, eigenständigen Prompt aus Fach/Stufe/Gruppe, Einheit, Stunde und Phase plus dem KI-Vorschlag; im `AiAssistDialog` erscheint dieser als Kasten unter der jeweiligen Stunde mit Button "📋 Prompt kopieren (extern erzeugen)" (Avalonia-`IClipboard`, erste Zwischenablage-Nutzung in der App), den die Lehrkraft in eine eigene Claude-Sitzung einfügt. Kein neuer Server-Roundtrip: nur ein kurzer zusätzlicher Textabschnitt je Phase in der ohnehin laufenden Planungsantwort. - [x] **4.5.21** "Schattenfeld" mit didaktischem Hintergrund/Begründung je KI-Stunde, auf Nachfrage sichtbar (Nutzer-Idee neben 4.5.20) — bei einer KI-generierten Stunde fehlt anders als bei einer selbst geschriebenen das eigene Vorwissen zum "Warum", das für einen sicheren Unterrichtseinsatz hilft. **Umsetzung:** entschieden für "nur auf Klick nachgeladen" statt immer mitgeneriert — passt zum bestehenden Guthabenmodell (keine Kosten für ungenutzte Erklärungen). Neuer, eigener Endpunkt `ai-backend/explain.php` statt Zusatzfeld in jeder `plan.php`-Antwort: eigener statischer Systemprompt (Begründung des Phasenaufbaus, mögliche Stolpersteine/Schülermissverständnisse, Differenzierungsideen — kurz gehalten, kein Roman), eigene Abrechnung. Die Guthaben-Abzugslogik aus `plan.php` (SELECT-FOR-UPDATE gegen Race Conditions, Transaktions-Insert) wurde dafür nach `ai_backend_call_and_charge` in `db.php` ausgelagert, damit sie nicht doppelt gepflegt werden muss. Neue DTOs `AiExplainRequest`/ `AiExplainResponse`, `AiPlanningService.RequestExplanationAsync`. Im `AiAssistDialog` erscheint je Stunde ein Button "💡 Didaktischen Hintergrund erklären" (`AiLessonReviewItem.RequestExplanationCommand`), der nach Laden durch den Text ersetzt wird — dieselbe Anfrage wird nicht zweimal angeboten. Kein neues DB-Schema nötig (nutzt dieselben `users`/`tokens`/`transactions`-Tabellen wie `plan.php`). - [x] **4.5.22** KI-Unterstützung direkt aus dem Editor einer einzelnen Stunde heraus statt nur aus der Einheiten-Übersicht (Nutzer-Feedback: bisher musste man aus der Stunde raus, die Einheiten-KI aufrufen und die gewünschte Stunde in der freien Anweisung erst benennen). **Umsetzung:** neuer "Fokus-Modus" im `AiAssistDialog` statt eines separaten Dialogs — Button "🤖 KI-Unterstützung für diese Stunde" im `LessonDialog` (nur bei bereits gespeicherten Stunden, eine gerade erst angelegte hat noch keine Id zum Fokussieren) öffnet denselben `AiAssistDialog`, aber mit `focusLesson` gesetzt. Neues Wire-Feld `AiPlanningRequest.FocusLessonId`: der Systemprompt in `plan.php` weist die KI an, ausschließlich diese eine Stunde zu bearbeiten, keine neuen Stunden vorzuschlagen und keine andere Stunde anzufassen — zusätzlich (wie beim Umfangs-Umschalter 4.5.15) client-seitig hart durchgesetzt in `AiAssistDialogViewModel.Send` (Antwort wird auf die Fokus-Id gefiltert, bevor sie überhaupt angezeigt wird) und `AiPlanningService.ApplyResponse`. Der Umfangs-Umschalter ("Auch bestehende Stundeninhalte anpassen") ergibt im Fokus-Modus keinen Sinn und wird ausgeblendet (immer erzwungen an). **Datenkonsistenz-Falle vermieden:** Da `AiAssistDialog` bei "Übernehmen" direkt ins Repository speichert, wären die noch offenen Feldwerte des ursprünglichen `LessonDialog` danach veraltet — ein anschließendes normales "Speichern" hätte die KI-Änderung stillschweigend wieder überschrieben. Der `LessonDialog` schließt sich deshalb nach einer angewendeten KI-Änderung automatisch (`LessonDialogViewModel.MarkAppliedExternally`, lädt den frischen Stand statt der eigenen Felder), die aufrufende Liste aktualisiert sich wie nach einem normalen Speichern. --- ## 5. Schülerdokumentation **Wichtige Abweichung von der ursprünglichen Planung (5.2):** Vor der Umsetzung zeigte sich, dass 5.2 wie ursprünglich beschrieben eine zweite, parallele Fehlzeiten-Erfassung neben dem bereits bestehenden Anwesenheits-Tracking aus Kapitel 3 (`ParticipationEntry.Attendance`, `AttendanceStatus`) ergeben hätte — zwei potenziell widersprüchliche Datenquellen für dieselbe Frage ("war der Schüler da?"). Auf Rückfrage entschieden: 5.2 wird als **Auswertung** der bestehenden Anwesenheitsdaten umgesetzt, keine zweite Erfassung. 5.2.1 und 5.2.4 existierten dadurch faktisch schon (Quick-Input-Dialog bzw. "Offene Entschuldigungen" im Dashboard). ### 5.1 Einträge erfassen - [x] **5.1.1** Dialog "Dokumentation hinzufügen" mit Typwahl (`Conversation`, `Incident`, `SupportPlan`, `Absence`) und typabhängigen Feldern — [DocumentationDialog.axaml](LehrerApp.Desktop/Views/Students/DocumentationDialog.axaml), [DocumentationViewModels.cs](LehrerApp.Desktop/ViewModels/Students/DocumentationViewModels.cs). Deutsche Anzeige über `DocumentationTypeDisplay`/`SupportStatusDisplay`, analog zum `NiveauDisplay`-Muster. Feldbezogene Validierung wie in 13.2.4. - [x] **5.1.2** Teilnehmerliste (`Participants`) bei Gesprächen — Chip-Liste mit Hinzufügen/ Entfernen im Dialog, nur sichtbar bei Typ "Gespräch". - [x] **5.1.3** Kennzeichen "vertraulich" — vertrauliche Einträge zeigen in der Übersicht nur Datum/Typ/Schloss-Symbol, Titel und Inhalt erst nach Klick auf "Anzeigen" (`DocumentationItem.IsRevealed`, session-lokal, keine erneute Passwortabfrage — dafür gibt es bereits die App-Sperre aus 13.3.5). - [x] **5.1.4** Bearbeiten/Löschen mit Rückfrage (neuer generischer `ConfirmDialog`); Löschen markiert nur `IsDeleted`/`DeletedAt` statt hart zu entfernen — `IDocumentationRepository.Delete`. Ein echtes `HardDelete` existiert separat, nur für die Löschfristen-Bereinigung in 5.4.2. - [x] **5.1.5** Zwei weitere Dokumentationstypen ergänzt (Nutzer-Feedback nach Erstauslieferung): - **Elternanruf** (`DocumentationType.ParentCall`): Gesprächspunkte werden im normalen Dialog vorab geplant (`ParentCallData.Points`); ein separater [ParentCallSessionDialog](LehrerApp.Desktop/Views/Students/ParentCallSessionDialog.axaml) ("Gespräch begleiten", Button an jedem Elternanruf-Eintrag) hakt sie während des Telefonats ab und hält Eindrücke/Ergänzungen als Protokoll fest (`ParentCallData.Impressions`, `IsConducted`, `ConductedDate`). Punkte mit unverändertem Text behalten beim erneuten Bearbeiten ihren Abhak-Status (Zuordnung über `ParentCallPoint.Id`, sonst über Textabgleich — ein umbenannter Punkt gilt als neu und startet offen; bewusste Vereinfachung). - **Elternbrief** (`DocumentationType.ParentLetter`): Entwurf/Inhalt, Absendedatum, Rückmeldung erhalten (ja/nein), Rückmeldedatum und -notiz (`ParentLetterData`). - **Datei-Anhänge** (an allen Dokumentationstypen, nicht nur Elternbrief): über LiteDBs eingebauten Dateispeicher (`ILiteStorage`, per Skript verifiziert — funktioniert zuverlässig, im Gegensatz zum defekten `Rebuild`-mit-Passwort aus 13.3.4) — [LiteAttachmentStorage.cs](LehrerApp.Data/LiteAttachmentStorage.cs). Größe strikt auf 10 MB je Datei begrenzt (`IAttachmentStorage.MaxSizeBytes`), damit die Datenbankdatei (und jedes Backup, 13.3.1) nicht durch Anhänge aufgebläht wird. Neu hochgeladene, aber nie gespeicherte Anhänge werden beim Abbrechen des Dialogs wieder gelöscht, damit keine verwaisten Blobs zurückbleiben; beim endgültigen Löschen eines Eintrags (5.4.2) werden auch dessen Anhänge mit entfernt. **Randnotiz:** Beim Schreiben der Tests für den Datei-Speicher fiel eine bereits vorher latent vorhandene Testinfrastruktur-Schwäche auf: `LiteDbContext` nutzt LiteDBs statischen, geteilten `BsonMapper.Global` für die Index-Auflösung — bei paralleler Testausführung über mehrere Testklassen hinweg (xUnit-Standard) führte das sporadisch zu "Member X not found on BsonMapper"-Fehlern in völlig unbeteiligten Tests. Behoben durch `[assembly: CollectionBehavior(DisableTestParallelization = true)]` in [AssemblyInfo.cs](LehrerApp.Data.Tests/AssemblyInfo.cs) — Data-Tests laufen jetzt sequenziell (bei elementaren In-Memory-Tests kein spürbarer Zeitverlust). - [x] **5.1.6** Labels zur Nachverfolgung (Nutzer-Feedback): freie Text-Labels an jedem Dokumentationseintrag (`Documentation.Tags`), mit AutoCompleteBox-Vorschlägen (`DocumentationTagDisplay.Suggestions`: Kritisch, Nacharbeiten, Mit JGL abklären, Erkundigung einholen, Elterngespräch nötig, Mit Schulleitung abklären, Klassenkonferenz, Frist beachten, Beobachten, Erledigt — eigene Labels bleiben trotzdem frei möglich). Farbcodierung nach Dringlichkeit statt nach Label-Identität (`DocumentationTagDisplay.ColorHex`): rot = Priorität, orange = Handlungsbedarf, blau = im Blick behalten, grün = abgeschlossen, grau = freies Label. Als farbige Chips in der Dokumentationsliste sichtbar (`DocumentationItem.TagChips`). Dabei außerdem behoben: die Farblegende der Notenentwicklung-Balken (2.5) fehlte sichtbar im UI (nur im Tooltip) — wirkte dadurch wie zufällige/abwechselnde Farbgebung statt wie das eigentliche Signal "Auffälligkeit". Jetzt als kleine Legende über dem Diagramm sichtbar, siehe [StudentDetailView.axaml](LehrerApp.Desktop/Views/Students/StudentDetailView.axaml). ### 5.2 Fehlzeiten (als Auswertung des bestehenden Anwesenheits-Trackings, siehe oben) - [x] **5.2.1** Schnelle Abwesenheitserfassung je Stunde — bereits vorhanden über `AttendanceHomeworkQuickInputDialog` und den Mitarbeits-Assistenten (Kapitel 3). - [x] **5.2.2** Fehlzeitenbilanz je Schüler und laufendes Schuljahr — [AttendanceBalanceService.cs](LehrerApp.Core/Services/AttendanceBalanceService.cs), reine Auswertungslogik (kontrollierte Stunden, entschuldigt/unentschuldigt/offen, Fehlquote in %), angezeigt im Schülerdetail-Tab "Dokumentation". Schulisch veranlasste Abwesenheit (`OtherSchoolEvent`) zählt bewusst nicht als Fehlzeit des Schülers. - [x] **5.2.3** Schwellenwert-Warnung (> 20 %) — `AttendanceBalance.ExceedsThreshold` im Schülerdetail sowie eine neue "Fehlzeiten-Warnung"-Karte im Dashboard (alle Schüler über dem Schwellenwert, sortiert nach Fehlquote). - [x] **5.2.4** Nachträgliches Entschuldigen mit Frist-Hinweis — bereits vorhanden über die "Offene Entschuldigungen"-Karte im Dashboard (21-Tage-Grenze, aus Kapitel 3). ### 5.3 Förderpläne - [x] **5.3.1** Förderplan anlegen (Maßnahmenliste, Überprüfungsdatum, Status) — über den 5.1-Dialog mit Typ "Förderplan" (`SupportData`: `Measures`, `ReviewDate`, `Status`). - [x] **5.3.2** Wiedervorlage: fällige Überprüfungen (Status Aktiv, Überprüfungsdatum in den nächsten 14 Tagen oder überfällig) erscheinen als eigene Dashboard-Karte "Förderplan-Wiedervorlage", überfällige rot hervorgehoben. - [x] **5.3.3** Verlaufsdokumentation — die Dokumentationsliste im Schülerdetail zeigt alle Förderplan-Einträge chronologisch; bewusst keine zusätzliche Gruppierung über eine Plan-ID, da das bestehende flache `Documentation`-Modell dafür ausreicht. ### 5.4 Datenschutz - [x] **5.4.1** Vertrauliche Einträge nur nach zusätzlicher Bestätigung anzeigen — siehe 5.1.3 (zusammen umgesetzt, da es sich um dieselbe UI-Stelle handelt). - [x] **5.4.2** Löschfristen — neuer Tab "Datenschutz" in den Einstellungen: konfigurierbare Aufbewahrungsfrist in Jahren ([PrivacySettingsService.cs](LehrerApp.Core/Services/PrivacySettingsService.cs), Standard 3 Jahre), Liste abgelaufener Einträge zur manuellen Prüfung mit "Endgültig löschen" (`IDocumentationRepository.HardDelete`). Löscht nie automatisch. - [x] **5.4.3** Export einzelner Schülerdaten für Auskunftsersuchen (Art. 15 DSGVO) — [PersonalDataExportService.cs](LehrerApp.Core/Services/PersonalDataExportService.cs), JSON-Export mit Stammdaten, Gruppenzuordnungen, Noten, Klausurergebnissen, Mitarbeit und Dokumentation (auch als vertraulich markierte Einträge — das Vertraulich-Kennzeichen blendet nur die laufende Ansicht aus, ist aber keine pauschale rechtliche Ausnahme vom Auskunftsanspruch der betroffenen Person selbst). **Hinweis:** ob im Einzelfall eine Ausnahme greift (z.B. schutzwürdige Belange Dritter nach Landes-Schulrecht), muss die verantwortliche Lehrkraft/Schule selbst prüfen — das ist keine Rechtsberatung. --- ## 6. Arbeitszeit & Aufgaben Modelle `WorkTask` und `TimeEntry` existieren, Repositories ebenfalls. Navigationspunkt "Arbeitszeit" zeigt Aufgabenverwaltung (6.1), Zeiterfassung (6.2) und Auswertung (6.3) als drei Tabs (`WorkloadViewModel`/`WorkloadView`, gleiches Container-Tab-Muster wie `GroupDetailViewModel`). **Kapitel 6 ist damit vollständig abgeschlossen** bis auf den Export der Auswertung (6.3.3), der am noch nicht existierenden Kapitel 11 (Export-Infrastruktur) hängt. ### 6.1 Aufgabenverwaltung - [x] **6.1.1** Aufgabenliste mit Filter nach Status, Kategorie, Gruppe und Fälligkeit — `WorkTaskListViewModel`/`WorkTaskListView`. Drei Filter-ComboBoxen (Status/Kategorie/Gruppe); "Fälligkeit" als Sortierung (fällige zuerst, unbefristete ans Ende) statt eigenem Datumsbereich-Filter umgesetzt, da eine Liste mit wenigen Dutzend Aufgaben davon mehr profitiert als von einer zusätzlichen Filter-UI. - [x] **6.1.2** Aufgabe anlegen/bearbeiten: Titel, Kategorie, Gruppe, Fälligkeit, geschätzte Dauer, Notizen — `AddEditWorkTaskDialog`, gleiches Feld-Fehler-Muster wie die übrigen Dialoge (`{Field}Error` je Feld, Validierung sammelt statt beim ersten Fehler abzubrechen). - [x] **6.1.3** Status per Klick wechseln (`Open → InProgress → Done → Open`) über eine farbige Status-Kachel je Zeile; "Offene Aufgaben" (alles außer `Done`) ist der Default-Filter, damit Erledigtes automatisch ausgeblendet ist, ohne separate Sichtbarkeits-Logik. - [x] **6.1.4** Wiederkehrende Aufgaben (wöchentlich/monatlich) — `WorkTask.Recurrence` (`TaskRecurrence`: None/Weekly/Monthly). Kein eigenes Serien-/Vorlagen-Modell: Beim Abschließen (Status → `Done` in 6.1.3) wird einfach eine neue `WorkTask` mit gleichem Titel/Kategorie/Gruppe und um 7 Tage bzw. 1 Monat verschobenem Fälligkeitsdatum erzeugt — genau das Verhalten, das ein Lehrer von "wöchentlich wiederkehrend" erwartet, ohne Hintergrundjob oder Kalenderlogik. Braucht deshalb ein Fälligkeitsdatum als Ausgangspunkt (im Dialog erzwungen, sobald eine Wiederholung gewählt ist). - [x] **6.1.5** Automatische Aufgabe "Klausur korrigieren" beim Statuswechsel einer Klausur auf `Conducted` (Anbindung an 1.1.3) — Hook in `GroupDetailViewModel.SetExamStatus`, feuert nur beim *erstmaligen* Wechsel von `Planned` auf `Conducted` (nicht bei jedem erneuten Klick), Fälligkeit 14 Tage nach dem Klausurtermin, Kategorie `Correction`. ### 6.2 Zeiterfassung - [x] **6.2.1** Timer starten/stoppen mit Zuordnung zu Aufgabe oder Kategorie — `TimeTrackingViewModel`. Bewusst ohne live mitlaufende Sekundenanzeige (keine `DispatcherTimer`-Kopplung in die ViewModel-Schicht); während der Timer läuft, zeigt "Läuft seit HH:mm Uhr" den Startzeitpunkt, die Dauer wird beim Stoppen aus der Differenz berechnet (mind. 1 Minute). - [x] **6.2.2** Manuelle Nacherfassung eines Zeitblocks (Datum, Von–Bis oder Dauer) — `AddTimeEntryDialog`: entweder Dauer direkt eintragen oder Von/Bis angeben (daraus wird die Dauer berechnet); mindestens eines von beidem ist Pflicht. - [x] **6.2.3** Wochenübersicht der erfassten Zeit, Summen je Kategorie — Balken je Kategorie (`CategoryTimeSummary`, `TimeEntry.Category` ist bewusst ein freier String statt `TaskCategory`-Enum wie bei `WorkTask`, da das Domänenmodell das schon so vorgab; die ComboBox schlägt trotzdem dieselben Kategorienamen vor). Nur die aktuelle Kalenderwoche, keine Wochennavigation wie im Stundenplan — bei Bedarf später ergänzbar. - [x] **6.2.4** Schätzung vs. tatsächliche Dauer je Aufgabe vergleichen — direkt in der Aufgabenliste (6.1.1) als "X / Y min erfasst", sobald mindestens ein verknüpfter Zeiteintrag existiert; dafür braucht `WorkTaskListViewModel` jetzt zusätzlich `ITimeEntryRepository`. ### 6.3 Auswertung - [x] **6.3.1** Monats-/Jahresauswertung nach Kategorie und Gruppe (Diagramm + Tabelle) — dritter Tab "Auswertung" (`WorkloadEvaluationViewModel`). Zeitraum wahlweise Monat (Jahr+Monat) oder Schuljahr (nutzt `SchoolYearService`, 1.8.–31.7. wie überall sonst in der App — bewusst kein Kalenderjahr, um konsistent zur restlichen Schuljahres-Logik zu bleiben); "Diagramm" als Balken je Kategorie/Gruppe (`ProgressBar`, gleiches Muster wie die Wochenübersicht in 6.2.3), keine eigene Chart-Bibliothek. - [x] **6.3.2** Abgleich mit der Pflichtstundenzahl — neuer `WorkloadSettingsService` (`workloadsettings.json`, gleiches Muster wie `PeriodScheduleService`/ `SchoolCalendarSettingsService`). Bewusst **nicht** in den Einstellungen (Kapitel 12) hinterlegt, sondern direkt auf dem Auswertungs-Tab editierbar: das Feld wird ausschließlich dort verwendet, ein Sprung in eine andere Seite für eine einzelne Zahl wäre reine Indirektion (siehe auch das Nutzer-Feedback zum "Stundenplan bearbeiten"-Knopf oben). Die Pflichtstunden pro Woche werden auf die Wochenzahl des gewählten Zeitraums hochgerechnet und der erfassten Ist-Zeit gegenübergestellt. - [ ] **6.3.3** Export der Arbeitszeitauswertung (siehe 11.2) — noch offen, da Kapitel 11 (Export-Infrastruktur) noch nicht existiert. --- ## 7. Schüler & Gruppen — Verfeinerungen Grundfunktionen sind vorhanden ([StudentViewModels.cs](LehrerApp.Desktop/ViewModels/Students/StudentViewModels.cs), [GroupViewModels.cs](LehrerApp.Desktop/ViewModels/Groups/GroupViewModels.cs)). Niveau-Zuordnung je Schüler (E/G/Förder, `GroupMembership.Niveau`) ist bereits umgesetzt, siehe Hinweis in Kapitel 1 — betrifft auch Kurse, nicht nur Klassen. ### 7.1 Schüler - [x] **7.1.1** Schülerverwaltung im Schülerdetail: Deaktivieren blendet den Schüler aus der normalen Schülerliste aus und bewahrt alle historischen Daten; deaktivierte Schüler können wieder aktiviert werden. Ein Verwaltungsdialog zeigt die Anzahl verknüpfter Gruppenzuordnungen, Klausurergebnisse, Einzel-/Zeugnisnoten, Mitarbeit- und Dokumentationseinträge. Endgültiges Löschen ist nur ohne solche Verknüpfungen möglich und zusätzlich im `StudentRepository` abgesichert, damit kein anderer Aufrufer verwaiste Daten erzeugen kann. - [x] **7.1.2** Suche in der Schülerliste mit Sofortfilter über ein gemeinsames Suchfeld für Vorname, Nachname, vollständigen Namen oder Lerngruppe. Passende Gruppen werden einmalig auf ihre Mitgliedschaften abgebildet, statt pro Schüler separate Repository-Abfragen auszuführen. - [x] **7.1.3a** Datenschutzneutrale Standardavatare aus dem bereits gespeicherten Geschlecht: männlich/weiblich klar unterscheidbar, neutraler Fallback für divers oder nicht angegeben. Anzeige in Schülerliste, Gruppenschülerliste und Schülerdetail; das Geschlecht kann dort nachträglich korrigiert werden. Keine Bilddateien und keine zusätzliche Einwilligung nötig. - [ ] **7.1.3b** Echtes Foto je Schüler (optional, lokal gespeichert) — bewusst ganz nach hinten gestellt, da die nötigen Foto-Einwilligungen im schulischen Alltag voraussichtlich nur in seltenen Sonderfällen vorliegen. - [x] **7.1.4** Serienbrieffelder aus Kontaktdaten für Elternbriefe: Kontakte speichern eine ausdrückliche Briefanrede (keine fehleranfällige Ableitung aus Beziehung oder Name) sowie die bereits vorhandenen getrennten Adressfelder. DOCX-Vorlagen verwenden Word- Inhaltssteuerelemente mit dokumentierten Tags wie `Letter.Salutation`, `Contact.Address`, `Student.FirstName` und `Group.Name`. - [x] **7.1.5** Sitzplan je Gruppe (Raster mit Drag & Drop), Sprung von Sitzplatz zur Bewertung. Mehrere benannte Sitzpläne je Lerngruppe und Raum besitzen ein konfigurierbares Raster. Schüler werden aus „Nicht zugeordnet“ auf Plätze gezogen, zwischen belegten Plätzen getauscht oder durch Ablegen im freien Bereich wieder entfernt. Ein Klick auf einen belegten Sitz öffnet die kompakte Sitzplatz-Schnelleingabe für alle aktiven Mitarbeitsaspekte, Anwesenheit und Hausaufgaben. Die Eingabe unterstützt Maus sowie Tastenkürzel und schreibt in die heutige Mitarbeitssitzung; falls noch keine existiert, wird einmalig eine Sitzung „Sitzplan“ für heute angelegt. ### 7.2 Gruppen - [x] **7.2.1** Gruppe bearbeiten und löschen — bereits vorhanden (`EditGroupCommand`/`DeleteGroupCommand`/ `ToggleArchiveCommand` in `GroupListViewModel`), nicht Teil der aktuellen Klausuren-Arbeit, beim Review aber bestätigt. Die Löschung läuft transaktional und entfernt alle fachlich abhängigen Datensätze (inkl. Zeugnisnoten, gruppenspezifischem Notenschema und Mitarbeitsabschnitten). Dokumentation, Aufgaben und Zeiteinträge bleiben als historische Nachweise erhalten; ihr `GroupId` wird auf `null` gesetzt. - [x] **7.2.2** Gruppe ins neue Schuljahr übernehmen — eigener Dialog im Verwaltungsmenü mit vorgeschlagenem Folgeschuljahr, editierbarem Gruppennamen und erhöhter Klassenstufe. Fach, Gruppentyp, Bewertungssystem, Wochenstunden, Eigene-Klasse-Markierung und Niveaudifferenzierung werden übernommen; ein gruppenspezifisches Notenschema optional. Die Schülerliste bleibt korrigierbar, vorausgewählt sind nur aktive Schüler, deren Mitgliedschaft bis zum Ende des Ausgangsschuljahres läuft. Frühere Austritte und reine H1-Mitglieder sind sichtbar, aber abgewählt; deaktivierte Schüler nicht übernehmbar. Neue Mitgliedschaften beginnen am 1. August als Ganzjahresmitgliedschaft, behalten das Niveau und erhalten kein Austrittsdatum. Leistungs-, Mitarbeit-, Planungs- und Stundenplandaten bleiben ausschließlich an der alten Gruppe; diese kann nach erfolgreicher Übernahme automatisch archiviert werden. - [x] **7.2.3** Schüler aus einer Gruppe austragen, ohne die Mitgliedschaft zu löschen — eigener Dialog mit `CalendarDatePicker`, aktuellem Datum als Vorbelegung und frei wählbarem Austrittsdatum. Setzt `GroupMembership.LeftAt`; Eintrittsdatum, Niveau und historische Leistungen bleiben erhalten. Ehemalige Schüler können in der Gruppenansicht eingeblendet und ihre Austragung zurückgenommen werden. Ein bereits gesetztes Austrittsdatum lässt sich über denselben Dialog ändern. - [x] **7.2.4** `JoinedAt`/`LeftAt` und `MembershipPeriod` werden über den gemeinsamen `GroupMembershipService` in allen gruppenbezogenen Auswertungen berücksichtigt: Mitarbeit, Mitarbeits-Assistent/-Aggregation, Klausur-Punkteeingabe, Sammelnoten, Notenübersicht und Zeugnisnoten. Ein Schüler zählt nur an Tagen bzw. in Zeiträumen, in denen die Mitgliedschaft tatsächlich aktiv war; der Austrittstag selbst zählt noch als zugehörig. - [x] **7.2.5** Archivansicht abgeschlossener Schuljahre mit konsequentem Schreibschutz. Archivierte Gruppen und sämtliche historischen Schüler-, Noten-, Klausur-, Mitarbeit- und Planungsdaten bleiben sichtbar. Verändernde Bedienelemente sind deaktiviert und ein deutliches Banner bietet die ausdrückliche Wiederaktivierung mit Bestätigung an. Zusätzlich erzwingt die Datenschicht den Schutz für Mitgliedschaften, Klausuren/-ergebnisse, Einzel- und Zeugnisnoten, Notenschema, Mitarbeitssitzungen/-einträge/-abschnitte, Einheiten, Stunden und Stundenplan-Slots; ein übersehener Dialog kann den UI-Schutz daher nicht umgehen. Auch die Gruppeneigenschaften können erst in einem getrennten Schritt nach der Reaktivierung geändert oder gelöscht werden. Reine Ansichten und das Kopieren einer Unterrichtseinheit als Vorlage in eine aktive Gruppe bleiben möglich. ### 7.3 Import - [ ] **7.3.1** CSV-Import von Schülerlisten mit Spaltenzuordnung im Dialog. - [ ] **7.3.2** Dublettenerkennung beim Import (Name + Geburtsdatum), Zusammenführen anbieten. - [ ] **7.3.3** Import-Vorschau mit Fehlerliste vor dem endgültigen Übernehmen. --- ## 8. Kompetenzen Katalogverwaltung und JSON-Import existieren in [SettingsViewModel.cs](LehrerApp.Desktop/ViewModels/Settings/SettingsViewModel.cs), Format dokumentiert in [Kompetenzkatalog-KI-Prompt.md](docs/Kompetenzkatalog-KI-Prompt.md). ### 8.1 Katalogverwaltung - [x] **8.1.1** Kompetenzen innerhalb eines Bereichs umsortieren (`SortOrder` bearbeitbar machen). Bereiche und einzelne Kompetenzen lassen sich über Hoch-/Runter-Schaltflächen verschieben; die Reihenfolge wird unmittelbar und lückenlos in `SortOrder` gespeichert. - [x] **8.1.2** Katalog exportieren (JSON) — Gegenstück zum vorhandenen Import. Bereits über „JSON exportieren“ in den Einstellungen umgesetzt; Fach, Klassenstufe, Bereiche und Kompetenzen werden vollständig ausgegeben. - [x] **8.1.3** Katalog von einer Jahrgangsstufe in eine andere kopieren. Zielklassenstufe wird in einem Dialog gewählt; anschließend nutzt die Kopie dieselbe sichere Vorschau, Konfliktauswahl und Merge-/Ersetzen-Logik wie der JSON-Import. - [x] **8.1.4** Import-Konflikte behandeln: Merge statt Ersetzen anbieten. Umgesetzt mit vollständiger Validierung vor dem Schreiben, Importvorschau, sicherem Merge als Vorauswahl, Einzelentscheidung je Konflikt und bestätigungspflichtigem atomarem Ersetzen. ### 8.2 Verwendung im Unterricht - [x] **8.2.1** Kompetenzen einer Unterrichtseinheit zuordnen (siehe 4.1.3). Bereits im Unterrichtseinheiten-Dialog über die Kompetenz-Auswahl umgesetzt und in `Unit.Competencies` gespeichert. - [x] **8.2.2** Kompetenzen einzelnen Klausuraufgaben zuordnen — umgesetzt mit 1.2.4. - [x] **8.2.3** Abdeckungsübersicht: welche Kompetenzen wurden im Schuljahr behandelt/geprüft? Eigener Tab je Lerngruppe unterscheidet geplante und behandelte Unterrichtseinheiten sowie geplante und tatsächlich durchgeführte Klausuren pro Kompetenz. ### 8.3 Kompetenzorientierte Auswertung - [x] **8.3.1** Kompetenzprofil je Schüler aus Klausuraufgaben-Ergebnissen berechnen. Aufgabenpunkte korrigierter/zurückgegebener Klausuren werden nach zugeordneten Kompetenzcodes aggregiert; abwesende Schüler und Aufgaben ohne positive Maximalpunktzahl werden nicht gewertet. - [x] **8.3.2** Gruppenübersicht: durchschnittlicher Erfüllungsgrad je Kompetenz, Identifikation von Wiederholungsbedarf. Gruppenmittel, Zahl der beteiligten Schüler und Aufgabenwerte werden angezeigt; die Schwelle für Wiederholungsbedarf ist frei einstellbar. - [ ] **8.3.3** Kompetenzbericht je Schüler als Ausdruck/Export. --- ## 9. Dashboard Basis vorhanden in [DashboardViewModel.cs](LehrerApp.Desktop/ViewModels/DashboardViewModel.cs). Kleiner Monatskalender ist bereits umgesetzt (Farbcodierung Unterricht/Klausur, Hervorhebung "eigene Klasse" über `LearningGroup.IsOwnClass`, feste Kartenbreite/-position). - [x] **9.1** Heutige Stunden mit Uhrzeit und Raum anzeigen (Abhängigkeit zu 4.3, jetzt erfüllt). **Umsetzung:** `DashboardViewModel.TodaysLessons` löst je Stunde den Raum über den zur Stundennummer und dem heutigen Wochentag passenden `TimetableSlot` auf, die Uhrzeit aus `Lesson.StartTime` (falls manuell gesetzt) oder sonst aus dem Stundenraster (`PeriodScheduleService.GetTimes`) — gleiche Quellen, wie sie auch im Verlaufsplan-Editor (4.2.2) und im Stundenplan verwendet werden. **Bewusst nicht dupliziert:** die reichhaltigere Logik der "Heute"-Ansicht im Stundenplan selbst (Vertretung/Ausfall/Sondereinsatz-bewusst, siehe 4.3 Nachträge) — das Dashboard zeigt hier nur die einfache, tatsächlich geplante Stunde, für den vollständigen Tagesüberblick bleibt der Stundenplan zuständig. - [x] **9.2** Direkter Absprung von einer Stunde in Mitarbeitserfassung bzw. Stundenplanung. **Umsetzung:** Klick auf eine Stunde in "Heute" springt in die Lerngruppe, Tab "Mitarbeit" (`DashboardViewModel.OnNavigateToLesson`, `NavigateToGroupDetail(id, 3)`) — bewusst anderes Sprungziel als der bereits bestehende Klick in der Stundenplan-eigenen "Heute"-Ansicht (springt dort auf Tab "Planung", siehe 4.4.2): vom Dashboard aus ist der naheliegende nächste Schritt morgens eher die Mitarbeitserfassung als die Planung. - [x] **9.3** Kachel "Anstehende Termine": Klausuren, Förderplan-Überprüfungen, Abgabefristen. Bündelt geplante Klausuren, aktive Förderplan-Wiedervorlagen und offene Aufgaben mit Fälligkeitsdatum für die nächsten 30 Tage; überfällige Einträge bleiben sichtbar und alle Einträge springen zur passenden Lerngruppe bzw. zum Schüler. - [x] **9.4** Kachel "Offene Korrekturen" mit Fortschritt (x von y Klausuren bewertet). Durchgeführte und noch nicht zurückgegebene Klausuren zeigen den Fortschritt als Zahl und Balken (`bewertete Arbeiten / am Klausurtag aktive Gruppenmitglieder`) und führen direkt in den Klausuren-Tab der Lerngruppe. - [x] **9.5** Kachel "Auffälligkeiten": Fehlzeitenüberschreitungen, Notenabfall, Versetzungsgefährdung. Fehlzeiten nutzen den bestehenden konfigurierten Schwellenwert. Ein Notenabfall wird beim Vergleich der letzten zwei mit den beiden vorherigen Einzelnoten erkannt (mindestens eine Notenstufe bzw. drei Punkte); Versetzungsgefährdung basiert auf dem jüngsten gespeicherten Zeugnisnotenstand (Note 5/6 bzw. höchstens 4 Punkte). Klick öffnet den betroffenen Schüler. - [x] **9.6** Dashboard-Kacheln ein-/ausblendbar und in der Reihenfolge konfigurierbar. „Dashboard anpassen“ bietet für jede Kachel Sichtbarkeit sowie Hoch-/Runter-Sortierung; die Konfiguration wird lokal in `dashboardsettings.json` gespeichert und das Raster ohne Lücken neu angeordnet. - [x] **9.7** Automatische Aktualisierung beim Zurücknavigieren. `MainWindowViewModel.GetDashboard()` führt beim Wechsel zurück zum Dashboard bereits den `RefreshCommand` aus; damit werden Termine, Korrekturstände und Auffälligkeiten neu geladen. - [x] **9.8** Kalender-Detailansicht: Tag im Monatskalender anklickbar/auswählbar, zeigt in einem angrenzenden Feld die Termine dieses Tages (Unterricht, Klausuren, perspektivisch Konferenzen/Sondertermine) mit Details und Sprungmöglichkeit in die jeweilige Ansicht. Unterricht und Klausuren werden jetzt im Detailbereich der Kalenderkachel angezeigt und verlinkt. Weitere Terminarten können später über das vorhandene `CalendarEventItem` ergänzt werden, sobald dafür ein eigenes Termine-Modell existiert. --- ## 10. Sync & Server Vollständig funktionsfähig: Outbound-Publishing (10.1.6), Inbound-Apply (10.1.7), Anhang-Sync (10.1.8), Client-UI (10.1.1–10.1.5) und Server-Härtung (10.2) sind umgesetzt. Nur aktiv, wenn in den Einstellungen eine Server-URL konfiguriert und ein Login erfolgt ist. Offen bleiben bewusst die Schlüsselverwaltungs-Punkte unter 10.3 (Gerätewechsel/-verlust) und die Docker-Verifikation unter 10.2.4 (kein Docker im Entwicklungsstand verfügbar). ### 10.1 Client - [x] **10.1.1** Sync-Einrichtung in den Einstellungen: Server-URL, Login, Token speichern. **Umsetzung:** Neuer Tab "Synchronisation" in den Einstellungen. Neu `LehrerApp.Desktop/Services/SyncSettingsService.cs` (Muster `AiSettingsService`: Token AES-256-verschlüsselt über `SyncCrypto`, eigener rein lokaler Schlüssel — ersetzt die bisherigen Klartext-Helfer `AppBootstrapper.LoadServerUrl`/`SaveServerUrl` und die unverschlüsselte `auth.token`-Datei). Neu `SyncAuthService` für den Login-HTTP-Aufruf gegen `/api/auth/login`. Speichern/Anmelden startet die App neu (`AppBootstrapper. RestartApplication`, gleiches Muster wie bei DB-Passwort/AppLock-Änderungen) — `SyncEngine`/ `SnapshotService` werden nur einmalig beim Start registriert, es gibt keinen Live-Re-Registrierungspfad. - [x] **10.1.2** Verbindungstest mit klarer Fehlermeldung (nicht erreichbar / Token ungültig). **Umsetzung:** `SyncAuthService.TestConnectionAsync` unterscheidet drei Zustände (erreichbar & angemeldet / erreichbar aber nicht angemeldet bzw. Token ungültig / nicht erreichbar) über einen GET auf `/api/sync/status` mit optionalem Bearer-Token. - [x] **10.1.3** Konfliktanzeige in der UI — was `ConflictResolver` entscheidet, muss sichtbar sein. **Umsetzung:** Minimale Liste im Tab "Synchronisation" (kein Feld-Diff für v1 — die Payloads sind clientseitig verschlüsselt, ein Diff würde ohnehin nur rohes JSON zeigen). Zeigt Entität, Zeitpunkt und welche Seite gewonnen hat, mit "Gesehen"-Aktion. Neu `EventQueue.MarkReviewed(id)` (bisher nur `AddConflict`/`GetUnreviewed`/`ConflictCount`, kein Weg, einen Konflikt als gesehen zu markieren). - [x] **10.1.4** Manuelles Auslösen einer vollständigen Synchronisation. **Umsetzung:** War bereits vorhanden (`SyncStatusViewModel.SyncNowCommand`, `SyncStatusBar` in `MainWindow.axaml`), nur hier noch nicht abgehakt. - [x] **10.1.5** Statusanzeige erweitern: letzter Sync, Anzahl wartender Events, Fehlerzustand. **Umsetzung:** War bereits vorhanden (`SyncStatusViewModel`/`SyncStatusBar`), nur hier noch nicht abgehakt. - [x] **10.1.6** Lokale Schreibvorgänge atomar an die Outbox (`EventQueue`) anbinden — aktuell ist das Sync-Grundgerüst registriert, die Repositories erzeugen aber noch keine Sync-Ereignisse. **Umsetzung:** `LiteDbContext.OnChange`-Hook (Sync-agnostisch, kein Verweis auf `LehrerApp.Sync` aus `LehrerApp.Data`) — alle 27 Repositories rufen ihn nach Save/Delete auf, Kaskaden (`GroupRepository.Delete` u.a.) und Batch-Methoden (`SaveMany`/`DeleteBySession`) feuern genau ein Ereignis pro betroffener Entität statt pro Collection-Zugriff. In `AppBootstrapper` an `SyncEventPublisher.Publish` gehängt, das den Hook in ein AES-verschlüsseltes `EventQueue.Enqueue` übersetzt. - [x] **10.1.7** Eingehende Sync-Ereignisse tatsächlich auf die lokale Datenbank anwenden. Bisher komplett fehlender, in dieser Checkliste nicht erfasster Baustein: selbst mit 10.1.6 hätte `SyncEngine.PullAsync` empfangene Ereignisse nur zur Konflikterkennung genutzt, nie in die lokale LiteDB geschrieben — ankommende Änderungen von anderen Geräten wären nirgends sichtbar geworden. **Umsetzung:** Neu `LehrerApp.Sync/EventApplier.cs` — entschlüsselt, dispatcht über eine explizite `EntityType`-Tabelle, schreibt **immer direkt auf die rohe LiteDB-Collection**, nie über eine Repository-Save/Delete-Methode (sonst würde der 10.1.6-Hook die gerade angewendete Änderung als neues ausgehendes Ereignis re-enqueuen — Sync-Ping-Pong). Ein gemeinsames Suppress-Flag wurde geprüft und verworfen (Timer-Thread vs. UI-Thread — ein Flag könnte einen echten Nutzer-Save währenddessen verschlucken); der direkte Collection-Zugriff ist zustandslos und dadurch korrekt. Kaskaden-Fälle nutzen dieselben internen `LiteDbContext`-Hilfsmethoden wie die Repositories. Mit dediziertem Loop-Prevention-Test abgesichert (`EventApplierTests`). **Bekannte v1-Einschränkung:** weiche Geschäftsregeln (`ArchivedGroupWriteGuard`, Namens-Eindeutigkeit bei Aspekten u.ä.) werden auf diesem Pfad nicht geprüft — nur harte LiteDB-Unique-Constraints greifen noch und führen zum Überspringen des einzelnen Ereignisses. Für Einzel-/Wenig-Geräte-Nutzung akzeptiert, siehe 10.3.4. - [x] **10.1.8** Datei-Anhänge (Dokumentation) über den laufenden Sync mitschicken. **Umsetzung:** Eigener, unverschlüsselt im JSON-Ereigniskanal nicht mitgeführter Binärkanal (würde ihn für Fotos/Scans stark aufblähen) — neue Endpunkte `POST/GET /api/sync/attachments/{storageId}` in `LehrerApp.Api`, neue `EventQueue`-Warteliste für ausstehende Uploads, `AttachmentSyncer` (Upload, in `SyncEngine.SyncNowAsync` nach dem Event-Push) und `EventApplier` (Download fehlender Anhänge nach Anwenden eines `Documentation`-Ereignisses). Original-`StorageId` bleibt beim Download erhalten (roher `db.Attachments.Upload`-Aufruf statt `IAttachmentStorage.Upload`, das immer eine neue Id vergäbe). ### 10.2 Server - [x] **10.2.1** Benutzerverwaltung/Registrierung prüfen und absichern ([Endpoints.cs](LehrerApp.Api/Endpoints/Endpoints.cs)). **Umsetzung:** `/api/auth/login` und `/api/auth/register` akzeptierten zuvor jeden beliebigen Nutzernamen/Passwort und stellten ein gültiges 30-Tage-JWT aus (unadressierte `// TODO`-Kommentare im Code) — konkrete, ausnutzbare Lücke bei echtem Deployment. Neu `PasswordHasher` (PBKDF2, Salt pro Nutzer — kein neues NuGet-Paket, gleiche BCL-Technik wie `SyncCrypto`) und `UserStore` (LiteDB-Collection `users`). `/api/auth/register` ersatzlos entfernt (kein offener Registrierungs-Endpunkt für ein Einzel-/Familien-Deployment); neue Nutzer werden per CLI angelegt (`dotnet LehrerApp.Api.dll create-user `, dokumentiert in `docker/README.md`), damit keine zusätzliche unauthentifizierte Angriffsfläche entsteht. - [x] **10.2.2** Rate Limiting und Request-Größenbegrenzung. **Umsetzung:** ASP.NET Cores eingebautes `Microsoft.AspNetCore.RateLimiting` (keine neue Paketabhängigkeit). `/api/auth/login` speziell auf 5 Versuche/Minute begrenzt (Brute-Force-Schutz), alle Endpunkte zusätzlich global auf 120 Anfragen/Minute je IP. Kestrel `MaxRequestBodySize` auf 15 MB gedeckelt (Anhänge sind clientseitig ohnehin auf 10 MB begrenzt, siehe `IAttachmentStorage.MaxSizeBytes`). - [x] **10.2.3** Serverseitiges Backup der Event-/Snapshot-Dateien. **Umsetzung:** `docker/backup.sh` — Tar-Archiv von `./data` (Ereignis-Logs, Snapshots, Anhänge, Nutzer), räumt Archive älter als 30 Tage auf, läuft direkt auf dem Host (kein Container-Zugriff nötig), dokumentiert samt Cron-Beispiel in `docker/README.md`. - [ ] **10.2.4** Docker-Setup in [docker/](docker/) verifizieren und dokumentieren. **Teilweise:** `docker/README.md` um `create-user`-Flow, Backup und Rate-Limits ergänzt. Ein tatsächlicher `docker compose up`-Durchlauf konnte in dieser Umgebung nicht verifiziert werden (kein Docker verfügbar) — steht vor dem ersten echten Deployment noch aus. **Nachtrag (echter Dokploy-Deploy-Versuch):** Build schlug fehl mit "A compatible .NET SDK was not found" — `global.json` pinnt exakt `10.0.301` mit `rollForward: latestPatch`, das erlaubt nur neuere Patches im selben Feature-Band (300er). Der treibende Docker-Image-Tag `mcr.microsoft.com/dotnet/sdk:10.0` zeigte inzwischen auf `10.0.400` (Feature-Band 400) — `latestPatch` lehnt das ab. Fix in `docker/Dockerfile.api`: Build-Stage exakt auf `mcr.microsoft.com/dotnet/sdk:10.0.301` gepinnt. **Merke:** wird `global.json` je auf eine neuere SDK-Version gehoben, muss dieser Tag mitgezogen werden, sonst bricht der Docker-Build auf die gleiche Art wieder ab. ### 10.3 Verschlüsselung - [ ] **10.3.1** Schlüsselübertragung auf ein zweites Gerät (QR-Code oder Passphrase). - [ ] **10.3.2** Warnung und Wiederherstellungspfad bei verlorenem Schlüssel. - [x] **10.3.3** Prüfen, welche Daten unverschlüsselt über `PlainEventStore` laufen — personenbezogene Daten dürfen das nicht. `Grade`/`ExamResult` (beide mit `StudentId` plus Notenwert/-kommentar) trugen bisher personenbezogene Daten im Klartext über den Companion/WebApp-Kanal (`PlainSyncEvent.Payload` ist unverschlüsseltes JSON, anders als der AES-256-GCM-verschlüsselte Desktop-Kanal — der Server sieht den Klartext also nicht nur während der Übertragung, sondern speichert ihn auch dauerhaft unverschlüsselt in der EventStore-Collection). Aus `PlainEventStore.Allowed` entfernt — nur noch `WorkTask`/`Lesson` (reine Lehrer-Planungsdaten ohne `StudentId`) laufen über diesen Kanal. > ⚠️ **BEWUSSTE BLOCKADE, gehört zu geplanter Companion-App-Funktion:** Mitarbeitsnoten > sollen laut Plan später über eine Companion-App erfassbar sein — das ist genau `Grade` mit > `Category = Participation`. **Das geht mit dem aktuellen Stand nicht**, und zwar absichtlich: > `PlainEventStore.Push` lehnt `Grade`/`ExamResult`-Events NICHT mit einem Fehler/4xx ab, > sondern **schluckt sie still** — `Success = true`, das Event landet nur in > `RejectedEventIds` statt gespeichert zu werden. Wer das nicht kennt, sucht stundenlang, > warum Mitarbeitsnoten aus der Companion-App im Server nie ankommen. Bevor eine > Companion-App Noten schreiben soll, muss zuerst 10.3.1 (Schlüsselaustausch) + eine echte > Payload-Verschlüsselung für `PlainSyncEvent` gebaut werden — erst danach `Grade` wieder in > `PlainEventStore.Allowed` aufnehmen. - [ ] **10.3.4** Bekannte v1-Einschränkung aus 10.1.7: weiche Geschäftsregeln greifen beim Anwenden eingehender Sync-Ereignisse nicht, nur harte LiteDB-Unique-Constraints. Bei mehreren eigenen Geräten in Randfällen möglich, dass sich Datenstände leicht unterscheiden. Für v1 bewusst akzeptiert (Einzel-/Wenig-Geräte-Nutzung) — falls das je zum echten Problem wird, müsste der Event-Applier dieselben Validierungen wie die Repository-Save-Methoden durchlaufen, ohne dabei erneut ein Sync-Ereignis auszulösen. **Verifikation:** `dotnet build LehrerApp.sln && dotnet test LehrerApp.sln` grün (591 Tests, inkl. `EventApplierTests`, `ChangeHookCascadeTests`, `AttachmentSyncerTests`, `AttachmentStoreTests`). Zusätzlich Live-Rauchtest gegen einen tatsächlich laufenden `LehrerApp.Api`-Prozess (nicht gemockt): `create-user`-CLI, Login (richtig/falsch), Zugriffsschutz auf `/api/sync/status`, entferntes `/api/auth/register` (404), Push/Pull-Roundtrip zwischen zwei Geräte-Ids, Anhang-Upload/Download byteidentisch, Rate-Limit auf `/api/auth/login` löst nach 5 Versuchen tatsächlich 429 aus. **Nicht verifizierbar in dieser Umgebung:** zwei echte Desktop-Instanzen gegeneinander synchronisieren (kein Mehrfach-AppData-Mechanismus vorhanden, GUI nicht headless steuerbar) und ein echter `docker compose up`-Durchlauf (kein Docker verfügbar) — beides vor dem ersten produktiven Zwei-Geräte-Einsatz empfehlenswert nachzuholen. --- ## 11. Export, Druck & Berichte Bisher nicht vorhanden — komplett neu. - [ ] **11.1** Basisinfrastruktur: Export-Service mit Formatwahl und Speicherdialog. - [ ] **11.2** CSV-Export für Notenlisten, Klausurauswertung, Arbeitszeit, Fehlzeiten. - [ ] **11.3** PDF-Erzeugung (Bibliothek auswählen — z.B. QuestPDF) mit einheitlichem Layout. - [ ] **11.4** Druckvorlagen: Notenliste, Klausur-Notenspiegel, Sitzplan, Kompetenzbericht, Schülerdokumentation. - [x] **11.5a** Einzelner Elternbrief als bearbeitbare DOCX-Kopie aus einer in Word gestalteten Vorlage. Vorlagenverwaltung in den Einstellungen; das Original bleibt unverändert. Vor Import und erneut vor jeder Erzeugung werden Inhaltssteuerelemente auch in Kopf-/Fußzeilen geprüft. Kein Steuerelement, fehlender Tag und unbekannte Tags erzeugen Hinweise; wahrscheinliche Tippfehler werden per Ähnlichkeitsprüfung deutlich mit einem konkreten Korrekturvorschlag gemeldet. Fehlende verwendete Kontakt-/Gruppenwerte blockieren die Erzeugung, damit fehlerhafte Briefe nicht unbemerkt vervielfältigt werden. - [ ] **11.5b** Stapelerzeugung für eine ganze Lerngruppe mit Kontaktauswahl je Schüler und Ergebnisübersicht. Bewusst nachgelagert; der validierte Einzelbrief bildet die Grundlage. - [ ] **11.6** Vollständiger Datenexport eines Schuljahres (Archivierung). --- ## 12. Einstellungen & Stammdaten Fächer- und Kompetenzverwaltung existiert bereits in [SettingsView.axaml](LehrerApp.Desktop/Views/Settings/SettingsView.axaml), ebenso (neu, im Zuge von 4.2.2) der Tab "Kürzel-Katalog" für die Von/Nach-Kürzel des Stundenverlaufsplans. - [ ] **12.1** Lehrerprofil: Name, Kürzel, Schule, Pflichtstundenzahl. - [ ] **12.2** Schuljahr-Einstellungen: Beginn/Ende, Halbjahresgrenze, Ferien (`SchoolYearService` erweitern). - [ ] **12.3** Standard-Notenschlüssel und Standard-Gewichtungsschema hinterlegen (2.3.3, 1.3.2). - [ ] **12.4** Darstellung: Hell/Dunkel-Modus, Schriftgröße. - [ ] **12.5** Speicherort der Datenbank anzeigen und ändern. - [ ] **12.6** Backup-Verwaltung (siehe 13.3) in den Einstellungen zugänglich machen. --- ## 13. Technische Basis ### 13.1 Tests - [x] **13.1.1** Testprojekt `LehrerApp.Tests` angelegt (referenziert `LehrerApp.Core`, analog zu [LehrerApp.Data.Tests](LehrerApp.Data.Tests/LehrerApp.Data.Tests.csproj) für die Datenschicht). Noch ohne Testfälle — folgt mit 13.1.2ff. - [x] **13.1.2** Unit-Tests für `GradingService` (Notenschlüssel-Grenzfälle, gewichteter Schnitt) — 41 Tests in [GradingServiceTests.cs](LehrerApp.Tests/GradingServiceTests.cs), deckt `CalculateGrade`, `ValidateGradingKey`, `WeightedAverage`, `RoundToGrade`, `ParticipationGrade`, `ValidateGradingScheme` und `CalculateReportGrade` ab. - [x] **13.1.3** Unit-Tests für `SchoolYearService` (Jahreswechsel, Schuljahresgrenze) — 13 Tests in [SchoolYearServiceTests.cs](LehrerApp.Tests/SchoolYearServiceTests.cs). Dafür `CurrentSchoolYear`/`RecentSchoolYears` um ein optionales `today`-Parameter erweitert (Default weiterhin `DateTime.Today`), um den 1.-August-Wechsel deterministisch zu testen, ohne bestehende Aufrufer zu ändern. - [x] **13.1.4** Repository-Tests gegen eine In-Memory-LiteDB — `LiteDbContext` um einen `Stream`-Konstruktor erweitert (`new LiteDatabase(stream)`, kein Datei-I/O nötig), 19 Tests in [RepositoryTests.cs](LehrerApp.Data.Tests/RepositoryTests.cs): u.a. Kaskadenlöschung bei `GroupRepository.Delete`/`ExamRepository.Delete`/ `ParticipationSessionRepository.Delete`, Eindeutigkeitsprüfungen (`GroupMembership`, `ParticipationEntry`), Fach-Validierung (Trimmen, Duplikate, Löschsperre bei Verwendung), `GradingScheme`-Auflösung gruppenspezifisch vs. Voreinstellung. - [x] **13.1.5** Tests für die Mitarbeits-Aggregation (3.2) und Zeugnisnotenberechnung (2.4) — neues Testprojekt `LehrerApp.Desktop.Tests` (referenziert `LehrerApp.Desktop`, damit ViewModel-Logik ohne echtes UI getestet werden kann), gemeinsame Fakes in [Fakes.cs](LehrerApp.Desktop.Tests/Fakes.cs). 9 Tests für [ParticipationGradeAggregationTests.cs](LehrerApp.Desktop.Tests/ParticipationGradeAggregationTests.cs) (gewichtetes Mittel, "nicht bewertet" ≠ schlechte Note, Trend, Aspektgewicht 0, Halbjahresfilter, Übernehmen-Update-statt-Duplikat) und 9 für [ReportGradeCalculationTests.cs](LehrerApp.Desktop.Tests/ReportGradeCalculationTests.cs) (Schema-Auflösung gruppenspezifisch/Voreinstellung/Fallback, Pflicht-Begründung beim Übersteuern, Festschreiben friert den Stand ein, Rundungsregel). - [x] **13.1.6** Tests für `ConflictResolver` — neues Testprojekt `LehrerApp.Sync.Tests`, 10 Tests in [ConflictResolverTests.cs](LehrerApp.Sync.Tests/ConflictResolverTests.cs) (Desktop schlägt Companion immer, Gleichstand nach Zeitstempel, kein Konflikt bei unterschiedlicher Entität/gleichem Gerät). Dabei zwei echte Fehler gefunden und behoben: (1) `SyncEvent` hatte keine `[BsonId]`-Markierung auf `EventId` — LiteDB vergab dadurch einen eigenen, unabhängigen `_id`, wodurch `EventQueue.Acknowledge()` nie etwas aus der Queue löschte (verlorene Konflikte blieben für immer offen). (2) `ConflictResolver` verglich `local.Timestamp` (aus LiteDB, `Kind=Local` mit verschobenen Ticks) direkt mit `remote.Timestamp` (`Kind=Utc`) — `DateTime`-Vergleiche ignorieren `Kind` und vergleichen nur rohe Ticks, wodurch die Gleichstand-Regel außerhalb von UTC+0 falsch entschied. Fix: `.ToUniversalTime()` auf beiden Seiten vor dem Vergleich. ### 13.2 Fehlerbehandlung & Logging - [x] **13.2.1** Zentrale Exception-Behandlung mit verständlicher Fehlermeldung statt Absturz — [GlobalExceptionHandler.cs](LehrerApp.Desktop/Services/GlobalExceptionHandler.cs). `Dispatcher.UIThread.UnhandledException` protokolliert Fehler aus Befehlen/Ereignis-Handlern. Nur als wiederherstellbar eingestufte I/O-, Netzwerk-, Timeout- und Abbruchfehler werden mit `Handled = true` behandelt und als Toast angezeigt. Unbekannte Programmier-/Zustandsfehler dürfen die Anwendung kontrolliert beenden, statt mit möglicherweise beschädigtem Zustand weiterzulaufen. `AppDomain.UnhandledException` und `TaskScheduler.UnobservedTaskException` sind Sicherheitsnetze für Fehler außerhalb des UI-Threads — dort kann ein bereits "IsTerminating"-Fehler nicht mehr verhindert werden, wird aber vollständig protokolliert. - [x] **13.2.2** Logging in Datei im App-Datenverzeichnis, mit Rotation — [AppLogger.cs](LehrerApp.Core/Services/AppLogger.cs), tägliche Datei unter `/LehrerApp/logs/`, Dateien älter als 14 Tage werden beim Start gelöscht. 6 Tests in [AppLoggerTests.cs](LehrerApp.Tests/AppLoggerTests.cs). - [x] **13.2.3** Einheitliche Benachrichtigungen (Toast/Snackbar) für Erfolg und Fehler — [NotificationService.cs](LehrerApp.Desktop/Services/NotificationService.cs), Overlay unten rechts in [MainWindow.axaml](LehrerApp.Desktop/Views/MainWindow.axaml), blendet sich nach 3 s (Erfolg) bzw. 6 s (Fehler) automatisch aus. Aktuell an den globalen Exception-Handler angebunden; bestehende Dialoge zeigen Erfolg/Fehler weiterhin über ihre eigenen Statuszeilen (`StatusMessage`/`ValidationMessage`) — eine Umstellung dieser bestehenden Stellen auf Toasts wäre ein eigener, größerer Umbau über viele Dateien hinweg und ist bewusst nicht Teil dieser Aufgabe. - [x] **13.2.4** Validierungsmeldungen einheitlich an den Eingabefeldern statt in Sammel-Labels. Alle Dialoge mit Formularfeldern umgestellt: `AddStudentDialog`, `ContactEditDialog`, `AddGroupDialog`, `StudentGradesDialog` (`GradeEditItem`), `CollectiveGradeDialog`, `ExamDialog`, `AddSessionDialog`, `ParticipationWizardDialog` (Abschnitt-Formular), Einstellungen (Fach anlegen, Notenschlüssel-Vorlage anlegen, Notenschlüssel-Editor). Muster: pro Feld eine `{Feld}Error`-Property, `Save()` leert alle Fehler-Properties am Anfang, sammelt alle Verstöße über eine lokale `valid`-Variable statt beim ersten Fehler zurückzukehren, und bricht erst am Ende mit `if (!valid) return;` ab — so werden mehrere Fehler gleichzeitig angezeigt. Bewusst als Sammel-Meldung belassen, wo die Meldung sich auf eine Kombination mehrerer Felder bezieht statt auf ein einzelnes Feld: `GradingKeyValidation` (Notenschlüssel-Tabelle als Ganzes), `GradingSchemeEditItem.ValidationMessage` (Summe der drei Prozent-Felder muss 100 ergeben), `ReportGradeRow.ValidationMessage` (Override-Wert + Begründung gehören zusammen), `CatalogValidation` (Kompetenzkatalog-Panel: Fachauswahl, Bereichsname und JSON-Import teilen sich einen Status-Bereich), `AddStudentToGroupDialogViewModel.ValidationMessage` (einzige Prüfung "Schüler ausgewählt?", kein Textfeld zum Andocken vorhanden). ### 13.3 Datensicherheit - [x] **13.3.1** Automatisches lokales Backup der LiteDB beim Start (rollierend, letzte 10) — [BackupService.cs](LehrerApp.Core/Services/BackupService.cs). Reine Dateikopie nach `/LehrerApp/backups/`, läuft in `AppBootstrapper.BuildServices()` **vor** dem Öffnen der Datenbank (unabhängig von Verschlüsselung, kein LiteDB-Handle nötig). Behält standardmäßig die letzten 10 Backups, ältere werden entfernt. 6 Tests in [BackupServiceTests.cs](LehrerApp.Tests/BackupServiceTests.cs). - [x] **13.3.2** Wiederherstellung aus einem Backup über die Einstellungen — neuer Tab "Sicherheit" in [SettingsView.axaml](LehrerApp.Desktop/Views/Settings/SettingsView.axaml) listet vorhandene Backups mit Zeitstempel/Größe, "Wiederherstellen" fragt über einen neuen generischen [ConfirmDialog](LehrerApp.Desktop/Views/Shared/ConfirmDialog.axaml) nach, kopiert das Backup über die aktive Datenbankdatei und startet die App danach automatisch neu (`AppBootstrapper.RestartApplication()`) — ein laufender LiteDB-Verbindung kann nicht sicher "heiß" auf eine andere Datei umgehängt werden. - [x] **13.3.3** Schema-Migration mit Versionsnummer — `LiteDbContext` schreibt die Schema-Version in eine `meta`-Collection und führt Migrationsschritte nur noch aus, wenn die gespeicherte Version dahinter liegt (`RunVersionedMigrations`), statt wie bisher bei jedem Start erneut über alle Daten zu laufen. Die bestehenden (bereits idempotenten) Migrationsschritte wurden als Version-1-Schritt gebündelt, für zukünftige Schema-Änderungen ergänzt man einen weiteren `if (version < N)`-Block. 2 Tests in [LiteDbContextTests.cs](LehrerApp.Data.Tests/LiteDbContextTests.cs). - [x] **13.3.4** Optionale Verschlüsselung der lokalen Datenbank — [DatabaseEncryptionService.cs](LehrerApp.Data/DatabaseEncryptionService.cs). Passwort setzen/ändern/entfernen läuft über eine Kopie (neue Datei mit Zielpasswort anlegen, alle Collections umkopieren, Datei austauschen) statt über `LiteDatabase.Rebuild(...)` mit Passwort — das ist in LiteDB 5.0.21 nachweislich fehlerhaft (per Skript verifiziert: wirft "this data file is encrypted" beim Rebuild einer unverschlüsselten Datei). Ist die Datenbank verschlüsselt, fragt ein eigenes Fenster ([DbPasswordPromptWindow](LehrerApp.Desktop/Views/DbPasswordPromptWindow.axaml)) das Passwort ab, **bevor** `AppBootstrapper.BuildServices()` (und damit das Öffnen der Datenbank) läuft — siehe `App.axaml.cs`. Ändern/Entfernen des Passworts läuft über Einstellungen → Sicherheit, jeweils mit Neustart der App danach. 4 Tests in [DatabaseEncryptionServiceTests.cs](LehrerApp.Data.Tests/DatabaseEncryptionServiceTests.cs). - [x] **13.3.5** App-Sperre nach Inaktivität — [AppLockService.cs](LehrerApp.Core/Services/AppLockService.cs) (eigenes, gesalzenes PBKDF2-Passwort, unabhängig von einer eventuellen Datenbank-Verschlüsselung) + [AppLockViewModel.cs](LehrerApp.Desktop/ViewModels/AppLockViewModel.cs) (Inaktivitäts-Timer, Sperrbildschirm-Logik). Maus-/Tastatureingaben im Hauptfenster setzen den Timer zurück ([MainWindow.axaml.cs](LehrerApp.Desktop/Views/MainWindow.axaml.cs)); läuft er ab, blendet ein Overlay in [MainWindow.axaml](LehrerApp.Desktop/Views/MainWindow.axaml) die gesamte Oberfläche aus, bis das Passwort stimmt. Einstellbar (aktiv/inaktiv, Zeit, Passwort) über Einstellungen → Sicherheit, wirkt sofort ohne Neustart. 2 Tests in [AppLockViewModelTests.cs](LehrerApp.Desktop.Tests/AppLockViewModelTests.cs) (reine Unlock-Logik; der zeitbasierte Inaktivitäts-Timer selbst ist nicht automatisiert getestet). **Hinweis:** `RestartApplication()` startet den Prozess über `Environment.ProcessPath` neu — im veröffentlichten Programm korrekt, im Entwicklungsbetrieb über `dotnet run` zeigt `ProcessPath` auf den `dotnet`-Host statt auf die App, ein Neustart über die Einstellungen ist dort also nur in einer veröffentlichten Build (`dotnet publish`) sinnvoll zu testen. - [x] **13.3.6** Sauberer Desktop-Lifecycle — beim Beenden wird ein LiteDB-Checkpoint ausgeführt und anschließend der DI-Container samt Singleton-Datenbankverbindung entsorgt. Das initiale Hauptfenster wird vom Avalonia-Desktop-Lifetime angezeigt; ein explizites `Show()` erfolgt nur beim späteren Wechsel vom Passwortfenster zur Hauptansicht. ### 13.4 Codepflege - [x] **13.4.1** `CLAUDE.md` mit Projektkonventionen anlegen (`/init`) — [CLAUDE.md](CLAUDE.md). - [ ] **13.4.2** `AllRepositories.cs` und `IRepositories.cs` in Themendateien aufteilen, sobald weitere Repositories dazukommen. Aktuell (319 bzw. 154 Zeilen für ~20 Repositories) noch nicht unübersichtlich genug, um die Bedingung auszulösen — bewusst zurückgestellt. - [x] **13.4.3** Wiederkehrende UI-Muster als wiederverwendbare Controls/Styles. Bestandsaufnahme vor der Umsetzung ergab drei echte Duplikate (Suchleiste dagegen nur 3× und jedes Mal eine simple `TextBox` ohne gemeinsames Chrome — Extraktion lohnt sich dort nicht): - Seiten-Kopfzeile (Titel 22px SemiBold + optionaler Untertitel) war identisch in `GroupListView`, `StudentListView`, `GroupDetailView`, `StudentDetailView`, `SettingsView` nachgebaut → neue [PageHeader](LehrerApp.Desktop/Views/Shared/PageHeader.axaml)-Control, dort eingesetzt. - Dialog-Titel (`FontSize="18" FontWeight="SemiBold"` als erstes Element) kam in 16 Dialogen jeweils inline vor → globale Style-Klasse `TextBlock.dialogtitle` ([App.axaml](LehrerApp.Desktop/App.axaml)). - Leerlisten-Hinweis ("Noch keine …") kam an 10 Stellen vor, dabei war Opacity/FontSize bereits leicht auseinandergedriftet (0.35 vs. 0.4, 12 vs. 13) → globale Style-Klasse `TextBlock.emptyhint`, normalisiert alle Stellen auf einen Wert. Absichtlich nicht angefasst: die vier Views mit eigenen lokalen `