Commit Graph
76 Commits
Author SHA1 Message Date
adminandClaude Sonnet 5 c5a1157a7d feat: unbenutzte Sitzplätze ausblenden können
Manche Räume sind unregelmäßig bestückt (z.B. eine Klasse mit 3 Reihen, in zwei davon fehlt ein
Einzeltisch) - das feste Rows x Columns-Raster kennt aber keine per-Reihe abweichende Spaltenzahl.

Neues Feld SeatingPlan.HiddenSeats für Plätze ohne physischen Tisch. Ein ausgeblendeter Platz
bleibt Teil des Rasters (für die Spalten-/Reihenausrichtung von SeatingPlanPanel), wird im
Ansichtsmodus aber nicht gerendert; im Bearbeitungsmodus bleibt er sichtbar (abgeblendet) mit
einem Umschalt-Button direkt auf dem Platz. Nur leere Plätze lassen sich ausblenden; Zuweisen/
Verschieben eines Schülers auf einen ausgeblendeten Platz wird sowohl im ViewModel als auch im
Drag&Drop-Ziel-Check verhindert. SeatingPlanRepository.Save filtert HiddenSeats zusätzlich auf
das gültige Raster und blendet einen Platz automatisch wieder ein, falls er trotzdem belegt wird.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 12:06:26 +02:00
adminandClaude Sonnet 5 391628ae36 Vernetzung Stundenplan ↔ Unterrichtsplanung: Terminvorschlag, Lesson-Viewer-Sprung, Verzweigung (4.5.1–4.5.3)
4.5.1: Terminvorschlag für neue Stunden schlägt jetzt auch die Stundennummer
vor (bei Doppelstunden die erste Periode), nicht nur den Wochentag —
zieht über den bestehenden LessonNumber-Changed-Hook automatisch auch den
Stundenbeginn nach.

4.5.2 (teilweise): Klick auf eine Stunde im Stundenplan ("Heute"-Liste und
read-only Wochenraster) springt bei bereits vorhandener Lesson direkt in
den Verlaufsplan-Viewer statt nur grob zum Planung-Tab. Das Anlegen einer
neuen Lesson direkt aus dem Stundenplan bleibt bewusst offen (ungeklärte
Unit-Zuordnung, siehe TODO.md).

4.5.3: Der Lesson-Viewer hat jetzt Buttons "Zur Mitarbeit"/"Zu den Noten"
zum Weiterverzweigen in die Lerngruppe.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 23:39:27 +02:00
adminandClaude Sonnet 5 52a1d45539 Kurs-Übersicht: fehlende Hausaufgaben; Erinnerungen als eigene Aufgabenart; Kalender-/Sitzplan-Bugfixes
Kurs-Übersicht: neue Karte "Fehlende Hausaufgaben" listet Schüler mit nicht
gemachter Hausaufgabe aus der letzten Mitarbeitssitzung.

Aufgabenverwaltung: WorkTask.Kind unterscheidet jetzt Arbeitsaufträge von
reinen Erinnerungen ohne Zeitbezug (z.B. "morgen Ansage an die Klasse
machen") - eigener Button "🔔 Erinnerung", eigenes Icon in Liste/Dashboard,
läuft nicht in die Zeitauswertung ein.

Drei Bugfixes aus Nutzer-Feedback:
- Dashboard-Kalender zeigte eine Stunde doppelt, wenn sie über "Sitzung
  erzeugen" mit einer Mitarbeitssitzung verknüpft war.
- Der Sitzplan-Tab legte beim bloßen Öffnen einer Gruppe (nicht erst bei
  echter Nutzung) eine leere "Sitzplan"-Sitzung für heute an.
- Der "Meine Klasse"-Ring im Kalender erschien erst, sobald für den Tag
  eine Lesson existierte, statt schon laut Stundenplan.

Außerdem: Drawer-Icons pinnen jetzt explizit auf die farbige Emoji-Schrift
(Windows kann sonst je nach Font-Fallback auf eine einfarbige Variante
ausweichen).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 23:17:18 +02:00
adminandClaude Sonnet 5 4d2a89b14b Kurs-Übersicht gefüllt: nächste Termine, Mitarbeitshinweis, offene Kontrollen/Dokumentationen, auffällige Fehlzeiten
Der bislang leere "Übersicht"-Tab je Kurs zeigt jetzt einen kompakten
"Was steht an"-Überblick aus bereits vorhandenen Daten mit Klick-Durchsprung
in den jeweiligen Tab: nächste Stunde/Klausur, Hinweis auf die letzte
Mitarbeitssitzung, offene Hausaufgaben-Kontrolle, offene Entschuldigungen,
offene Dokumentations-Entwürfe und auffällige Fehlzeiten.

Die Fehlzeiten-Karte ist bewusst kein 1:1-Abbild der Dashboard-Warnung: erst
ab einer Mindeststichprobe von 8 erfassten Terminen im Schuljahr gemeldet,
damit ein einzelner Fehltag zu Schuljahresbeginn nicht sofort als auffällig
gilt. Das Hauptdashboard hat dasselbe Problem noch, wird dort separat
nachgezogen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 22:39:31 +02:00
adminandClaude Sonnet 5 f73bd6c9ec Sitzungen (3.3): aus Stunde erzeugen, löschen, Kalenderüberblick; Doppelstunden-Bugfix Serienerzeugung
Kapitel 3.3: neuer Button erzeugt eine Mitarbeitssitzung aus einer geplanten
Stunde (Datum/Thema, dupl.-sicher über Lesson.LessonId), Sitzungen lassen
sich jetzt mit Rückfrage bei vorhandenen Bewertungen löschen, und der
Dashboard-Kalender zeigt Sitzungen als dritten Termintyp neben Stunden/
Klausuren. Das Bearbeiten von Sitzungen war bereits vorhanden (undokumentiert
aus früherer Ad-hoc-Arbeit) und wird hier nur nachgezogen.

Bugfix: "Serie erzeugen" legte für Doppelstunden zwei Lessons mit gleichem
Datum an statt einer, da jede Stundenplan-Periode einzeln behandelt wurde.
Folgeperioden werden jetzt der Lesson der ersten Periode zugerechnet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 22:17:46 +02:00
adminandClaude Sonnet 5 f868b3a259 feat: sichtbare Seite nach eingehendem Sync automatisch neu laden
EventApplier schreibt eingehende Sync-Ereignisse absichtlich direkt auf die rohe LiteDB-Collection
(Ping-Pong-Vermeidung), benachrichtigt dabei aber kein ViewModel - eine per Sync empfangene neue
Stunde erschien bisher erst nach manuellem Neuladen (z.B. Tab-Wechsel).

Neues SyncEngine.DataChanged-Ereignis, gefeuert nach jedem Pull mit Ereignissen sowie bei einer
RemoteWon-Konfliktauflösung; SyncStatusViewModel reicht es durch. MainWindowViewModel abonniert es
und lädt ausschließlich die gerade sichtbare Seite über ihren eigenen, längst vorhandenen
Lade-Einstieg neu - keine Navigation weg von offenen Detailansichten, keine neue Lade-Logik, und
eine laufende Inline-Bearbeitung (Schülerdetail) wird nicht überschrieben.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 00:18:56 +02:00
adminandClaude Sonnet 5 979511ab6a fix: Kontowechsel ließ Push dauerhaft mit 404 scheitern
Nach einem Login mit korrigierter Groß-/Kleinschreibung (siehe vorheriger Fix zur userId-
Stabilität) bezog sich die lokale Versionsverfolgung je Entität (BasedOnServerSeq-Cache) und der
Pull-Cursor weiterhin auf das alte Konto - ServerSeq-Werte sind aber nur innerhalb des Event-Logs
EINES Kontos gültig. Der Push wurde zu Recht abgelehnt, der Server kannte die Entität unter der
neuen userId aber gar nicht (404 beim Nachladen), und HandleRejectedAsync gab bei einem 404
bisher einfach auf, ohne den veralteten Cache-Eintrag zu bereinigen - derselbe Fehlschlag bei
jedem weiteren Sync-Versuch.

Dreiteiliger Fix: (1) ein 404 beim Nachladen löscht jetzt den stale Cache-Eintrag, sodass der
nächste Push die Entität korrekt als neu behandelt und selbstheilend durchgeht; (2) der
"Vollständigen Sync erzwingen"-Button setzt jetzt auch die Push-Versionsverfolgung zurück, nicht
nur den Pull-Cursor; (3) SyncLogin erkennt einen echten Kontowechsel künftig proaktiv anhand der
kanonischen userId aus der Server-Antwort und resettet automatisch, bevor der Folgefehler
überhaupt auftreten kann.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 00:05:24 +02:00
adminandClaude Sonnet 5 ba8786906a fix: Login-userId war nicht stabil gegenüber Groß-/Kleinschreibung
/api/auth/login mintete die JWT-userId bisher aus dem roh eingegebenen Benutzernamen. LiteDBs
Standard-Collation vergleicht den eindeutigen Index auf Username aber case-insensitive - ein
Login mit nur einmal abweichender Schreibweise auf einem zweiten Gerät authentifiziert
erfolgreich, mintet aber eine andere userId. Da EventStore.GetCol(userId) diese direkt als
Dateiname für den Server-seitigen Event-Speicher nutzt, entstanden zwei komplett getrennte
Datenbestände für ein und dasselbe, aus Nutzersicht einzige Konto - Ursache dafür, dass ein
zweites Gerät trotz "since=0" durchgängig 0 Ereignisse erhielt.

UserStore.VerifyPassword(bool) durch Authenticate(UserEntry?) ersetzt, das bei Erfolg den
kanonisch gespeicherten Nutzereintrag liefert; /api/auth/login mintet Token und userId daraus
statt aus der Roheingabe.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 23:51:31 +02:00
adminandClaude Sonnet 5 e7e5faeba8 fix: eigener Push ließ Client seinen Pull-Cursor über fremde Ereignisse springen
SyncEngine.PushAsync setzte den lokalen Pull-Cursor bisher aus PushResponse.ServerSequenceNr -
dem globalen Zähler über alle Geräte nach dem eigenen Push, nicht dem tatsächlich zugestellten
Stand. War beim Server zu diesem Zeitpunkt bereits ein noch nicht abgeholtes Ereignis eines
anderen Geräts mit niedrigerer ServerSeq vorhanden, sprang der Cursor darüber hinweg und der
direkt folgende Pull bekam 0 Ereignisse, ohne es je angewendet zu haben - ein zweiter,
unabhängiger Cursor-Bug mit demselben Symptom wie der vorherige Wasserzeichen-Fix, diesmal
client- statt serverseitig.

Ergänzt außerdem einen "Vollständigen Sync erzwingen"-Button in den Sync-Einstellungen, damit
bereits durch diesen Bug zu weit vorgerückte Geräte ihren Fortschritt manuell zurücksetzen und
alle Ereignisse erneut laden können.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 23:38:46 +02:00
adminandClaude Sonnet 5 5bd6967421 fix: Pull-Wasserzeichen sprang über fremde Ereignisse + schärfere Push-Kollisionskontrolle
EventStore.Pull gab bisher den globalen ServerSeq-Höchststand als neuen Cursor zurück statt
den höchsten unter den tatsächlich gelieferten Ereignissen - hatte ein Gerät selbst kurz zuvor
etwas gepusht, sprang sein Pull-Cursor über noch nicht abgeholte Ereignisse anderer Geräte
hinweg und verpasste sie dauerhaft, ohne jeden Fehler.

Ersetzt außerdem die bisherige 30-Sekunden-Heuristik zur Konflikterkennung beim Push durch
exakte BasedOnServerSeq-Prüfung: jedes SyncEvent trägt die ServerSeq, auf der es aufbaut: der
Server lehnt ab, wenn der aktuelle Stand nicht mehr passt. Bei Ablehnung lädt der Client sofort
den neuen Server-Stand nach, löst den Konflikt nach der bestehenden Desktop-vs-Companion/
Timestamp-Politik auf und macht ihn immer in der Konflikt-Review-UI sichtbar, statt die
verworfene Änderung stillschweigend zu verlieren.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 23:25:04 +02:00
adminandClaude Sonnet 5 270c4d40cd feat: Erfolgs-Protokollierung für die gesamte Sync-Kette
SyncEventPublisher/SyncEngine/EventApplier protokollierten bisher
ausschließlich Fehlschläge - ein sauberes Log bewies nur "nichts ist
abgestürzt", nicht ob eine Änderung tatsächlich hoch-/heruntergeladen
wurde. Jetzt wird auch der Erfolgspfad geloggt: Einreihen in die Outbox
(mit SequenceNr), Push/Pull mit Anzahl und Entitätstypen sowie der vom
Server bestätigten ServerSequenceNr, und jedes tatsächlich angewendete
Ereignis. Damit lässt sich anhand der Log-Dateien beider Geräte
nachvollziehen, an welcher Stelle der Kette eine Änderung verloren geht.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 22:20:33 +02:00
adminandClaude Sonnet 5 dea7da8cca fix: Rate-Limit hinter Reverse Proxy wirkte wie ein globales statt Pro-IP-Limit
RemoteIpAddress zeigte hinter Dokploy/Traefik ohne ForwardedHeaders-Middleware
für jede Anfrage auf dieselbe interne Proxy-IP - das Pro-IP-Limit (120/Minute)
wurde dadurch faktisch zu einem einzigen globalen Limit für die gesamte
Bereitstellung, geteilt von allen Geräten und Endpunkten zusammen.

ForwardedHeadersOptions (X-Forwarded-For/X-Forwarded-Proto) registriert,
KnownIPNetworks/KnownProxies geleert (Container nur über den Reverse Proxy
erreichbar). API-only-Fix, Server-Redeploy nötig.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 22:09:59 +02:00
adminandClaude Sonnet 5 722b7f87ff fix: Sync blockierte dauerhaft und unsichtbar bei fehlerhaftem Ereignis
EventApplier.ApplyAsync fing bisher nur LiteException ab - jede andere
Ausnahme (z.B. eine fehlerhafte Entschlüsselung/Deserialisierung eines
einzelnen Ereignisses) fiel unbehandelt aus der Pull-Schleife in
SyncEngine.PullAsync heraus, bevor der Fortschritt (SetLastServerSeq)
gespeichert wurde. Der nächste Sync-Versuch lud denselben Batch erneut
und scheiterte am selben Ereignis wieder - ein dauerhaft blockierter
Sync, bei dem selbst bereits erfolgreich angewendete Ereignisse im
selben Batch nie als erledigt markiert wurden. Zusätzlich protokollierte
weder SyncEngine noch EventApplier irgendetwas, ein Fehlschlag zeigte
sich höchstens als knapper Text in der Sync-Statusleiste.

EventApplier fängt jetzt jede Ausnahme pro Ereignis ab (geloggt über
AppLogger, übersprungen statt den Batch zu blockieren); SyncEngine
loggt jeden Sync-Fehlschlag vollständig. Betrifft nur den Desktop-Client,
kein API-Redeploy nötig.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 21:53:30 +02:00
adminandClaude Sonnet 5 dc21cb2319 fix: Geräte-Pairing - Schlüssel-Code stimmte nie mit dem angezeigten Code überein
SnapshotService.CreateAndUploadAsync lädt in zwei Schritten hoch: Schritt 1
holt einen Code vom Server, Schritt 2 verschlüsselt den Sync-Schlüssel mit
diesem Code und lädt erneut hoch. SnapshotStore.Store() vergab bei jedem
Aufruf bedingungslos einen neuen Zufallscode - der dem Nutzer am Ende
angezeigte Code war dadurch nie derselbe, mit dem der Schlüssel tatsächlich
verschlüsselt wurde. Jede Kopplung musste deterministisch an der
Schlüssel-Entschlüsselung scheitern.

SnapshotUploadRequest bekommt ein optionales Code-Feld; Store() aktualisiert
bei vorhandenem, passendem Code denselben Eintrag statt einen neuen mit
neuem Code anzulegen. Betrifft LehrerApp.Api - der Server muss neu deployt
werden.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 21:34:43 +02:00
admin 26461bd2f2 Add shared export infrastructure 2026-08-18 21:21:56 +02:00
adminandClaude Sonnet 5 41bf2c0756 feat: Stundenplan-Badge für unkontrollierte Hausaufgaben (4.5.4)
Lesson bekommt HomeworkChecked/HomeworkCheckDismissed. Der Stundenplan
(Tagesliste und Wochenraster im "Heute"-Tab) zeigt ein 📓-Badge, wenn die
letzte Stunde einer Gruppe eine Hausaufgabe hatte, die noch nicht
kontrolliert oder bewusst übersprungen wurde. Umschaltbar über zwei sich
gegenseitig ausschließende Checkboxen ("Kontrolliert"/"Nicht
kontrollieren") im Verlaufsplan-Editor, direkt neben dem
Hausaufgabenfeld.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 20:52:22 +02:00
adminandClaude Sonnet 5 a0f44f1b25 feat: Abwesenheits-Hinweise, Fehlquote bei Zeugnisnoten, Gruppen-Dokumentation
- Schnellbewerten-Dialog: abwesende Schüler werden gedimmt und mit ihrem
  Anwesenheitsstatus statt der Aspektbeschriftung angezeigt, damit keine
  Mitarbeitsnote für nicht anwesende Schüler vergeben wird.
- Zeugnisnoten-Dialog: zeigt je Schüler die Fehlquote im gewählten
  Zeitraum, ab 50 % hervorgehoben (informativ, keine automatische
  Notenänderung).
- Gruppen-Tab "Dokumentation" (bisher Platzhalter) implementiert: listet
  alle Dokumentationseinträge der Gruppen-Schüler, mit Schüler-Filter und
  optionalem "Nur dieser Unterricht"-Schalter. Einträge aus anderen
  Lerngruppen werden standardmäßig mitangezeigt, aber gedimmt. Der
  Dokumentationsdialog bekommt dafür einen optionalen Schüler-Picker.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 19:45:01 +02:00
adminandClaude Sonnet 5 f2cb3c98c6 fix: Geräte-Pairing - Fehlschlag beim Einlösen war unsichtbar
RedeemPairingCode() rief nach einem Fehlschlag sofort RestartApplication()
auf, direkt nach dem Setzen von PairingStatus - die Fehlermeldung konnte
nie gerendert werden, und wurde auch nirgends geloggt. Ein Fehlschlag
(falscher/abgelaufener Code, Server nicht erreichbar) wirkte dadurch wie
ein kommentarloser Absturz ohne jede Spur.

- SettingsViewModel loggt den Fehler jetzt über AppLogger und zeigt vor
  dem Neustart einen Bestätigungsdialog mit der echten Fehlermeldung an.
- AppBootstrapper.RestartApplication() gibt unter dotnet run/IDE-Debug
  (wo ProcessPath auf den dotnet-Host statt die App zeigt) die
  ursprünglichen Kommandozeilenargumente beim Neustart mit, statt nur
  die dotnet-CLI-Hilfe zu öffnen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 16:35:11 +02:00
adminandClaude Sonnet 5 fd509fc921 feat: Komfort-Features für Unterrichtsplanung, Zeiterfassung und Dashboard
- Neue-Stunde-Dialog: Datum wird beim Anlegen anhand des Stundenplans
  und der letzten Stunde der Einheit vorbelegt statt auf "heute"
  (behebt eine falsch erkannte Doppelstunde, wenn "heute" nicht auf
  den passenden Wochentag fiel).
- Zeiterfassung: Button "Unterrichtszeit heute übernehmen" schlägt
  Start/Ende aus dem heutigen Stundenplan inkl. Puffer davor/danach vor.
- Dashboard: neue Kachel "Ungeplante Stunden" erinnert an Stunden ohne
  Thema für heute/morgen, mit Opt-out je Gruppe ("Benötigt
  Unterrichtsplanung"), Doppelstunden-Erkennung (keine doppelte Meldung
  für die zweite Periode) und Berücksichtigung von Stundenausfall.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 11:30:50 +02:00
adminandClaude Sonnet 5 762c8236fe fix: Sync-Statusanzeige zeigt nach Neustart fälschlich "nicht konfiguriert"
SyncEngine feuert sein erstes StatusChanged bereits im eigenen
Konstruktor. Da SyncEngine und SyncStatusViewModel beide DI-Singletons
sind und Letzterer Ersteren erst innerhalb der eigenen Factory aus dem
Container holt, lief dieser erste Broadcast ab, bevor
SyncStatusViewModel überhaupt abonniert hatte - der Status ging
verloren, StatusText blieb bis zum nächsten Auto-Sync oder manuellen
Sync beim hartcodierten Default "Kein Server konfiguriert".

Fix: Konstruktor ruft nach dem Abonnieren zusätzlich einmal
OnStatus(engine.Status) mit dem bereits vorhandenen aktuellen Zustand
auf. Regressionstest ergänzt - dabei fehlte LehrerApp.Desktop.Tests
das DisableTestParallelization-Attribut (gleicher bekannter
LiteDB-BsonMapper.Global-Bug wie in den anderen Testprojekten, sobald
zwei Testklassen parallel LiteDbContext konstruieren).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 01:14:22 +02:00
adminandClaude Sonnet 5 f8058b537b feat: Geräte-Pairing-UI + Einstellungen-Neustrukturierung
- Sync-Settings-Tab: neuer Bereich "Gerät koppeln" (10.3.1), nutzt den
  bereits vorhandenen, bisher nirgends verdrahteten SnapshotService.
  Ein Gerät erzeugt per CreatePairingCode einen verschlüsselten
  DB-Snapshot + Einmal-Code, das zweite Gerät übernimmt per
  RedeemPairingCode Datenbank und Sync-Schlüssel. Bestätigungsdialog
  vor dem Einlösen, da die lokale Datenbank dabei vollständig ersetzt
  wird (alter Stand wird automatisch als Backup gesichert).
- Einstellungen: TabPlacement von Top auf Left umgestellt (vertikale
  Liste statt langem horizontalem Balken) und die 13 Tabs in drei
  logische Gruppen sortiert (Fachliches / Zeitplanung / System).
- Neuer SettingsTab-Enum ersetzt rohe int-Tab-Indizes bei
  MainWindowViewModel.NavigateToSettings/
  TimetableViewModel.OnNavigateToSettings. Dabei einen bestehenden Bug
  gefunden und mitbehoben: das Zahnrad im Stundenplan öffnete über den
  hartcodierten Index 7 tatsächlich "Datenschutz" statt des laut
  Kommentar/Tooltip beabsichtigten "Ferien & Feiertage".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 01:09:25 +02:00
adminandClaude Sonnet 5 cdac335ad1 fix: Server-Daten in Named Volume statt Bind-Mount
./data war ein Bind-Mount relativ zum Git-Checkout - bei Dokploy wird
das Repo pro Deploy frisch geklont, ein per CLI angelegter Nutzer war
nach einem reinen Routing-Redeploy spurlos verschwunden. Betraf nicht
nur Nutzer, sondern denselben Api:DataPath für Ereignis-Log/Snapshots/
Anhänge - also jeden Server-Datenbestand bei jedem Redeploy.

- docker-compose.yml: Named Volume "api-data" statt Bind-Mount, lebt
  unabhängig vom Checkout im Docker-Daemon.
- backup.sh: auf volume-basiertes Backup umgeschrieben (Alpine-
  Hilfscontainer statt direktem Host-Pfad).
- docker/README.md, TODO.md 10.2.4: Vorfall dokumentiert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 00:02:56 +02:00
adminandClaude Sonnet 5 788eb42fd2 fix: Docker-SDK-Image auf global.json-Version pinnen
docker compose Build schlug bei Dokploy fehl: "A compatible .NET SDK
was not found". global.json pinnt exakt 10.0.301 mit
rollForward: latestPatch (erlaubt nur neuere Patches im selben
Feature-Band), der treibende Tag mcr.microsoft.com/dotnet/sdk:10.0
zeigte inzwischen aber auf 10.0.400 (neues Feature-Band) - latestPatch
lehnt das ab. Build-Stage in Dockerfile.api auf den exakten Tag
10.0.301 gepinnt, TODO.md 10.2.4 um den Vorfall ergänzt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 23:27:35 +02:00
adminandClaude Sonnet 5 858499e8ec fix: personenbezogene Daten aus Companion-Klartextkanal ausschließen
- PlainEventStore.Allowed: Grade/ExamResult entfernt (beide tragen
  StudentId + Notenwert/-kommentar, PlainSyncEvent.Payload läuft aber
  als Klartext-JSON über den Server, anders als der AES-256-GCM-
  verschlüsselte Desktop-Kanal). Nur noch WorkTask/Lesson erlaubt.
- Regressionstest PlainEventStoreTests ergänzt.
- TODO.md 10.3.3 abgehakt, inkl. deutlichem Warnhinweis: die geplante
  Mitarbeitsnoten-Erfassung per Companion-App ist damit bewusst
  blockiert, bis 10.3.1 (Schlüsselaustausch) + eine echte Payload-
  Verschlüsselung für PlainSyncEvent existieren.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 23:27:08 +02:00
adminandClaude Sonnet 5 9286bfa3b5 Baustein 11: Kapitel 10 in TODO.md final abgleichen
- Einleitungstext von "Grundgerüst existiert..." auf den finalen Stand
  aktualisiert: Outbound-Publishing, Inbound-Apply, Anhang-Sync,
  Client-UI und Server-Härtung sind umgesetzt und nur aktiv, wenn
  Server-URL + Login konfiguriert sind.
- Abschließenden Verifikations-Absatz zu 10.3 ergänzt: Build/Test-Stand
  (591 Tests) sowie Live-Rauchtest gegen einen laufenden
  LehrerApp.Api-Prozess dokumentiert, inkl. der zwei in dieser Umgebung
  nicht verifizierbaren Szenarien (Zwei-Geräte-Sync, docker compose up).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 11:42:05 +02:00
adminandClaude Sonnet 5 6774123270 Baustein 10: Deployment-Haertung (Kapitel 10)
- Rate Limiting ueber ASP.NET Cores eingebautes
  Microsoft.AspNetCore.RateLimiting (keine neue Paketabhaengigkeit):
  /api/auth/login auf 5 Versuche/Minute begrenzt (Brute-Force-Schutz),
  alle Endpunkte zusaetzlich global auf 120 Anfragen/Minute je IP
- Kestrel MaxRequestBodySize auf 15 MB gedeckelt (Anhaenge sind
  clientseitig ohnehin auf 10 MB begrenzt)
- Neu docker/backup.sh: Tar-Archiv von ./data (Ereignis-Logs,
  Snapshots, Anhaenge, Nutzer), raeumt Archive aelter als 30 Tage auf,
  laeuft direkt auf dem Host
- docker/README.md um Backup- und Rate-Limit-Dokumentation ergaenzt

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 11:38:42 +02:00
adminandClaude Sonnet 5 fc2d7aea3e Baustein 9: Konflikt-Review-UI (Kapitel 10)
Minimale Liste im Tab "Synchronisation" - Entitaet, Zeitpunkt, welche
Seite ConflictResolver gewaehlt hat, mit "Gesehen"-Aktion. Kein
Feld-Diff fuer v1: die Payloads sind clientseitig verschluesselt, ein
Diff wuerde ohnehin nur rohes JSON zeigen.

Neu EventQueue.MarkReviewed(id) - bisher gab es AddConflict/
GetUnreviewed/ConflictCount, aber keinen Weg, einen Konflikt als
gesehen zu markieren.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 11:37:20 +02:00
adminandClaude Sonnet 5 de98b4ed54 Baustein 8: Settings-Tab "Synchronisation" (Kapitel 10)
Neuer Tab in den Einstellungen: Server-URL, Login, Verbindungstest,
Logout.

- Neu SyncSettingsService (Muster AiSettingsService): Token
  AES-256-verschluesselt ueber SyncCrypto mit eigenem, rein lokalem
  Schluessel - ersetzt die bisherigen Klartext-Helfer
  AppBootstrapper.LoadServerUrl/SaveServerUrl und die unverschluesselte
  auth.token-Datei
- Neu SyncAuthService fuer den Login-HTTP-Aufruf gegen /api/auth/login
  und den Verbindungstest (unterscheidet erreichbar & angemeldet /
  erreichbar aber nicht angemeldet / nicht erreichbar)
- Speichern/Anmelden startet die App neu (AppBootstrapper.
  RestartApplication, gleiches Muster wie bei DB-Passwort/AppLock-
  Aenderungen) - SyncEngine/SnapshotService werden nur einmalig beim
  Start registriert, es gibt keinen Live-Re-Registrierungspfad

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 11:34:49 +02:00
adminandClaude Sonnet 5 95345f9c46 Baustein 7: TODO.md-Korrektur (Kapitel 10)
- 10.1.4/10.1.5 abgehakt (SyncStatusViewModel/SyncStatusBar waren
  bereits vorhanden, aber in der Checkliste noch nicht als erledigt
  markiert)
- 10.1.6 abgehakt mit Umsetzungsnotiz (Baustein 2-4)
- Neuer Punkt 10.1.7 fuer den zuvor komplett fehlenden, in der
  Checkliste nicht erfassten Inbound-Apply-Baustein (Baustein 5)
- Neuer Punkt 10.1.8 fuer Anhang-Sync (Baustein 6)
- 10.2.1 abgehakt mit Umsetzungsnotiz (Baustein 1)
- Neuer Punkt 10.3.4 fuer die bewusst akzeptierte v1-Einschraenkung
  beim Regel-Bypass im Event-Applier

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 11:31:09 +02:00
admin 50f3b2d6a9 feat: add seating plan drag and quick assessment 2026-08-17 01:49:05 +02:00
admin ad40d11d6e feat: complete dashboard chapter 2026-08-17 01:14:31 +02:00
admin fe36f51886 feat: add seating plans for learning groups 2026-08-17 01:01:40 +02:00
adminandClaude Sonnet 5 fbf70df439 Stunde/Einheit-Dialog: Gruppe + Fach anzeigen, nicht nur Fach
Bei einer Klasse, die in mehreren Fächern unterrichtet wird (mehrere
LearningGroup-Datensätze mit gleichem Namen, z.B. zwei "10c"), zeigte
UnitDialog bisher nur das Fach ("Fach: Chemie"), LessonDialog gar keinen
Gruppen-/Fach-Hinweis. Beide zeigen jetzt "Gruppe · Fach" (z.B. "10c ·
Chemie") direkt unter dem Dialogtitel, nicht editierbar, nur zur Einordnung.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 00:47:07 +02:00
adminandClaude Sonnet 5 888df39f37 Fix: ArgumentException im Stundenplan bei gleichnamigen Lerngruppen
TimetableSlotDialogViewModel baute die Gruppenauswahl als Dictionary,
geschlüsselt nach LearningGroup.Name. Bei zwei Lerngruppen mit demselben
Namen (dieselbe Klasse in zwei Fächern unterrichtet, z.B. zwei "10c") warf
ToDictionary eine ArgumentException, der Zuweisen-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 nummeriertem Fallback für den Restfall gleicher Name
und gleiches Fach.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 00:27:14 +02:00
adminandClaude Sonnet 5 16ff4875d8 3.1.3: Aspekt-Typen Scale3/Binary/Points in Raster und Dialog unterstützt
Neue Klasse ParticipationRatingScale (Core) ist die einzige Quelle für
Rohwerte je Aspekt-Typ. Scale3/Binary liegen bewusst direkt auf derselben
-2..+2-Achse wie Scale5 (nur mit weniger Zwischenschritten), damit die
bestehende Gewichtung/Mittelwertbildung zur Mitarbeitsnote unverändert
kompatibel bleibt. Points ist grundverschieden (echter Zählwert 0..MaxPoints,
neues Feld auf ParticipationAspect) und wird nur zur Aggregation linear auf
dieselbe Achse normiert.

Ohne diese Normierung hätte ein Punkte-Aspekt die Mitarbeitsnote verfälscht:
drei Stellen summierten bisher den Rohwert direkt (ParticipationGradeDialog-
ViewModel.Recompute, ParticipationWizardViewModels.WeightedRating und
.ComputeSuggestion) - alle drei sind jetzt auf die Normierung umgestellt.

Raster: Punkte-Aspekte zeigen ein NumericUpDown statt fester Stufen-Buttons.
Schnelleingabe-Dialog: Zifferntasten/+/- sind jetzt typabhängig, Legende
zeigt live die für den aktuellen Aspekt gültigen Tasten. Aspekt-Verwaltung
um "Max. Punkte"-Feld ergänzt (nur bei Typ "Punkte" sichtbar).

Bewusst nicht angefasst: die Trendlinien-Visualisierung im
Mitarbeits-Assistenten bleibt fest auf die drei Standardaspekte
zugeschnitten - eigener, größerer Umbau.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 00:12:16 +02:00
adminandClaude Sonnet 5 f8dd567ef8 Dashboard 9.1/9.2: Uhrzeit/Raum bei heutigen Stunden + Absprung zur Mitarbeit
TodaysLessons löst je Stunde den Raum über den passenden TimetableSlot auf
und die Uhrzeit aus Lesson.StartTime bzw. dem Stundenraster - gleiche
Quellen wie im Verlaufsplan-Editor und Stundenplan. Bewusst nicht dupliziert:
die Vertretung/Ausfall-Logik der Stundenplan-eigenen "Heute"-Ansicht bleibt
dort, das Dashboard zeigt nur die einfache geplante Stunde.

Klick auf eine Stunde springt in die Mitarbeitserfassung der Gruppe -
bewusst anderes Ziel als der bestehende Stundenplan-Sprung (dort "Planung"),
da vom Dashboard aus morgens eher die Mitarbeitserfassung naheliegt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 23:54:52 +02:00
adminandClaude Sonnet 5 e40585801c Kapitel 3.1: Mitarbeit-Aspekte pro Gruppe verwalten (3.1.1/3.1.2/3.1.4)
Neuer Button "Aspekte verwalten" im Mitarbeit-Tab öffnet
ParticipationAspectsDialog: Anlegen, Bezeichnung/Typ/Gewichtung/Aktiv-Status
bearbeiten (speichert sofort, gleiches Muster wie die bestehende
Gewichtungs-Bearbeitung in 3.2.1), Hoch/Runter-Reihenfolge, Löschen mit
Rückfrage. Verwaltet bewusst nur die gruppenspezifischen Aspekte, nicht den
bislang nirgends befüllten globalen Standardkatalog.

Schlüssel ist nur beim Neuanlegen editierbar - er verknüpft AspectRating mit
dem Aspekt per Key, ein nachträgliches Umbenennen würde historische
Bewertungen unauffindbar machen.

IParticipationAspectRepository.Save validiert jetzt Pflichtfelder und
Schlüssel-Eindeutigkeit gegen globale Standards UND eigene Gruppen-Aspekte
zusammen, da beide im Bewertungsraster kombiniert verwendet werden. Neue
GetAllByGroup-Methode (inkl. inaktiver) für die Verwaltungsansicht.

3.1.3 (Scale3/Binary/Points im Bewertungsraster selbst) bewusst nicht
angefasst - eigener, größerer Eingriff in die Bewertungs-UI.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 23:39:44 +02:00
adminandClaude Sonnet 5 10d82e5e2c KI-Feature 4.5.22: KI-Unterstützung direkt aus dem Stunden-Editor starten
Bisher musste man aus dem Stunden-Editor raus, die KI-Hilfe für die ganze
Einheit aufrufen und die gewünschte Stunde in der freien Anweisung erst
benennen. Neuer Button "KI-Unterstützung für diese Stunde" im LessonDialog
öffnet denselben AiAssistDialog im neuen Fokus-Modus.

AiPlanningRequest.FocusLessonId weist die KI im Systemprompt an, sich auf
genau diese Stunde zu beschränken; zusätzlich wie beim Umfangs-Umschalter
client-seitig hart durchgesetzt in Send() und ApplyResponse, statt der
KI-Antwort blind zu vertrauen.

Da "Übernehmen" direkt ins Repository speichert, wären die noch offenen
Feldwerte des ursprünglichen LessonDialog danach veraltet gewesen - der
Dialog schließt sich deshalb nach einer angewendeten KI-Änderung automatisch
mit dem frischen Stand statt ihn mit alten Werten zu überschreiben.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 22:48:00 +02:00
adminandClaude Sonnet 5 364df378f0 KI-Feature 4.5.21: didaktischer Hintergrund je Stunde, nur auf Nachfrage
Neuer Endpunkt ai-backend/explain.php mit eigenem statischen Systemprompt
(Begründung des Phasenaufbaus, mögliche Stolpersteine, Differenzierungsideen).
Bewusst als separater Endpunkt statt Zusatzfeld in jeder plan.php-Antwort,
damit die Erklärung nur bei tatsächlicher Nutzung abgerechnet wird statt bei
jeder Planungsanfrage mitgeneriert zu werden.

Die Guthaben-Abrechnung (SELECT-FOR-UPDATE, Transaktions-Insert) wurde aus
plan.php nach ai_backend_call_and_charge in db.php ausgelagert, damit sie
nicht an zwei Stellen gepflegt werden muss. Kein neues DB-Schema nötig.

Im AiAssistDialog erscheint je Stunde ein Button "Didaktischen Hintergrund
erklären", der nach dem Laden durch den Text ersetzt wird.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 22:32:36 +02:00
adminandClaude Sonnet 5 f703fe7b66 KI-Feature 4.5.20: Medienvorschlag + Zwischenablage-Prompt für externe Erzeugung
Die KI liefert je Verlaufsplan-Phase optional einen kurzen Vorschlag, was ein
Medium/Material (z.B. Tafelbild) zeigen sollte (AiPhaseStep.MaterialSuggestion),
nur wenn das über das ohnehin genannte Material hinaus einen Mehrwert hätte.
Bewusst nicht ins Domänenmodell übernommen (keine Migration nötig) — nur für
die Review-Anzeige relevant.

AiPlanningService.BuildMaterialPrompt baut daraus rein lokal (kein weiterer
KI-Aufruf) einen vollständigen Prompt aus Fach/Stufe/Gruppe, Einheit, Stunde
und Phase. Im AiAssistDialog landet er per Klick in der Zwischenablage, zum
Einfügen in eine separate Claude-Sitzung für die eigentliche Materialerzeugung.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 21:22:12 +02:00
adminandClaude Sonnet 5 da62a08437 TODO.md: Aktualität geprüft — 4.5.19-Reihenfolge, Stand-Datum, Zusammenfassung
4.5.19 stand versehentlich zwischen 4.5.14 und 4.5.15 statt aufsteigend am
Ende von Kapitel 4.5. Stand-Datum war seit mehreren Sessions nicht mehr
aktualisiert. "Empfohlene Reihenfolge" erwähnte den gesamten KI-Feature-Ausbau
(4.5.9-4.5.19) noch nicht und empfahl veraltet Kapitel 10/11 als Nächstes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 16:56:35 +02:00
adminandClaude Sonnet 5 8f2be89133 KI-Feature 4.5.14: Feld-Diff für geänderte Stunden im AiAssistDialog
Statt Vorschläge nur pauschal als "Neu"/"Geändert" zu markieren, zeigt der
Dialog jetzt je geänderter Stunde, was sich konkret unterscheidet (Thema,
Datum, Beginn, Stundennummer, Hausaufgabe/Reflexion, Verlaufsplan) —
AiPlanningService.DescribeChanges vergleicht bestehende Lesson und
KI-Vorschlag feldweise und listet nur echte Unterschiede auf.

Granulare Übernahme einzelner Phasen und Schutz vor Überschreiben eigener
zwischenzeitlicher Änderungen als 4.5.19 zurückgestellt (größerer Umbau).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 16:50:35 +02:00
adminandClaude Sonnet 5 2005b73a16 KI-Backend: deutsches Datumsformat fixen, Fortschrittsanzeige + Nachfassen im Dialog
Live-Test lieferte JsonException: DateOnly/TimeOnly hatten keinen Converter
fürs im Systemprompt dokumentierte deutsche Format (TT.MM.JJJJ/HH:mm), .NET
nutzte stattdessen ISO 8601 in beide Richtungen. Neue Converter mit Fallback
aufs allgemeine Parsen.

AiAssistDialog: indeterminierter ProgressBar statt nur Text während der
Anfrage. Neuer Button "Erneut anfragen" erlaubt Nachfassen mit geänderter
Anweisung, ohne den Dialog neu zu starten — schickt die aktuell angehakten
Vorschläge als Entwurfskontext mit (AiPlanningService.MergeDraft), damit die
KI auf dem noch ungespeicherten Stand aufbaut. Side-by-side-Vergleich beider
Entwürfe als 4.5.18 zurückgestellt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 16:39:46 +02:00
admin 1928916eac KI-Backend: Umfangs-Umschalter + Prompt Caching (4.5.15/4.5.16)
Umfangs-Umschalter: Checkbox "Auch bestehende Stundeninhalte anpassen" im
AiAssistDialog steuert, ob die KI bestehende Stunden inhaltlich ändern darf oder
die Einheit nur um neue Stunden erweitern soll. Zweifach durchgesetzt (Systemprompt
+ hartes client-seitiges Verwerfen in ApplyResponse), nicht nur der KI-Antwort
vertraut. Dabei auch einen Bug gefixt: eine bereits "Durchgeführt" markierte Stunde
wurde durch eine übernommene KI-Änderung stillschweigend auf "Geplant" zurückgesetzt.

Prompt Caching: der Systemprompt ist jetzt vollständig statisch (Voraussetzung für
Caching) und wird von AnthropicProvider.php als "cache_control: ephemeral" markiert
— wiederholte Anfragen innerhalb der 5-Minuten-TTL zahlen nur den reduzierten
Cache-Read-Preis. transactions-Tabelle und Preistabelle um Cache-Token-Spalten
erweitert, Migration für bereits deployte Installationen beigelegt.
2026-08-16 16:06:52 +02:00
admin 4437f951d9 Kompetenzen fast fertig 2026-08-16 02:32:12 +02:00
admin 7c7fe6dfcb KI-Backend: Authorization-Header-Fix + TODO-Ergänzungen (4.5.11-4.5.14)
Login funktionierte, aber jede authentifizierte Folgeanfrage (Guthaben, KI-Anfrage)
meldete fälschlich eine abgelaufene Anmeldung — viele Apache/PHP-FPM-Hosting-Setups
reichen den Authorization-Header standardmäßig nicht an PHP durch. .htaccess erzwingt
jetzt die Weiterleitung, db.php liest zusätzlich REDIRECT_HTTP_AUTHORIZATION/
getallheaders() als Fallback. AiBackendUrl auf die echte deployte Domain gesetzt.

TODO.md: KI-Unterstützung auch für Stundenplanung (4.5.11), Frage zu einem
console.claude.ai-Agent für Standardkontext vs. app-internem Standard-Prompt
(4.5.12/4.5.13), sowie ein Planungsdiff für KI-Vorschläge auf Feldebene statt
grober Neu/Geändert-Markierung (4.5.14).
2026-08-16 02:09:07 +02:00
admin 8546eabbdd KI-Backend: Nutzer-Anlage ohne PHP-CLI (setup-user.php)
scripts/create-user.php setzt SSH/CLI-Zugriff voraus, den viele Shared-Hosting-Pakete
nicht haben. setup-user.php ist die web-erreichbare Alternative (Geheimnis in
config.php, per curl von lokal aufrufbar), muss nach Gebrauch aber wieder vom
Server gelöscht werden — sonst bleibt ein Endpunkt zum Anlegen beliebiger Nutzer
erreichbar.
2026-08-16 01:48:51 +02:00
admin 8495e1b8d0 KI-gestützte Planungsunterstützung (4.5.9) + Kompetenzkatalog-Import (8.1.2)
KI-Unterstützung: neuer Einstellungen-Tab (Anmeldung, Guthaben) und Button im
Planungs-Tab, der Einheiten+Stunden als JSON an ein neues PHP-Backend (ai-backend/)
sendet und die Antwort als prüfbare Vorschlagsliste zurückbringt. Provider-Aufruf,
Guthabenverwaltung und Abrechnung nach echten Token-Kosten laufen serverseitig, der
Desktop-Client sieht nie einen LLM-API-Key. Zentral abgesichert: eine von der KI
zurückgegebene Stunden-Id, die zu keiner echten Lesson der Einheit passt, wird nie
als Update übernommen, sondern immer als neue Stunde behandelt.

Kompetenzkatalog-Import (8.1.2): JSON-Export/Import für Kompetenzkataloge.
2026-08-16 01:31:14 +02:00
admin 2b4fda7bb3 Arbeitszeit: wiederkehrende Aufgaben + Auto-Aufgabe bei Klausurkorrektur (6.1.4/6.1.5)
Damit ist Kapitel 6 (Arbeitszeit & Aufgaben) vollständig abgeschlossen (bis auf den
Export der Auswertung, der an das noch fehlende Kapitel 11 hängt).

- WorkTask.Recurrence (None/Weekly/Monthly): beim Abschließen einer wiederkehrenden
  Aufgabe wird automatisch die nächste Instanz mit verschobenem Fälligkeitsdatum erzeugt.
- GroupDetailViewModel.SetExamStatus legt beim ersten Wechsel einer Klausur von
  "Geplant" auf "Durchgeführt" automatisch eine Korrektur-Aufgabe an.
2026-08-16 00:39:18 +02:00
admin f9e398b225 Kapitel 7 abgeschlossen. 2026-08-16 00:29:01 +02:00