# Changelog

Alle nennenswerten Änderungen an diesem Projekt werden in dieser Datei
dokumentiert.

Das Format orientiert sich an
[Keep a Changelog](https://keepachangelog.com/de/1.1.0/).
Eine Versionierung wird derzeit nicht geführt.

## [Unreleased]

### Hinzugefügt

- „Was ist neu" (nutzerseitige Release Notes): eigene Seite `/was-ist-neu` mit den
  Änderungen nach Release gruppiert (Akkordeon), gespeist aus `RELEASE-NOTES.md` –
  kuratiert und im Code gepflegt, getrennt von der technischen `CHANGELOG.md`,
  ohne Datenbank und ohne Oberflächen-Editor. Ein Band „N Neuerungen im LBM" auf
  der Startseite und ein Glocken-Badge im Header (alle Seiten) weisen auf
  ungelesene Einträge hin; der Lesestand wird pro Benutzer gemerkt
  (`tabUserPref` `updatesSeen`), sichtbar bleibt der Zugang zusätzlich über einen
  Link in den Einstellungen. Löst die frühere, fest verdrahtete „Update-Info"-
  Tabelle in den Einstellungen ab. Neu: `App\Content\UpdateLog`, `public/updates/`.
- „Neuer Brennpunkt" wird jetzt **inline auf der Startseite** erfasst (statt auf
  einer eigenen Seite): ein Klick öffnet das Formular als Karte im linken Bereich,
  die drei Übersichts-Karten und das Filterfeld weichen, die Termine-Spalte rechts
  bleibt. Eigene, deep-linkbare URL `/neuer-brennpunkt` (Brotkrume „Brennpunkte ›
  Neuer Brennpunkt", Zurück-Taste schliesst); nach dem Speichern wird der neue
  Eintrag geöffnet. Das Bearbeiten bleibt unverändert in der Detailansicht.
  Das Formular-Markup liegt nun einmalig als Fragment (`detail/erstellenForm.php`,
  genutzt von der Standalone-Seite und inline), die Logik in
  `detail/erstellenForm.js`. Damit das Inline-Formular neben der erweiterten Suche
  (gleiche Feldnamen) kollisionsfrei ist, tragen die Dropdown-Helfer und die
  Gemeinde-Kaskade jetzt eine optionale DOM-ID (Default unverändert); das
  Inline-Formular nutzt `bp…`-IDs.
- Die einfache Suche hat einen Umschalter **Titel / Volltext** (Feld und
  Umschalter bilden ein zusammenhängendes Bedienelement; **Titel = Standard**).
  **Titel** filtert weiterhin die gruppierte Übersicht clientseitig (Kanton ›
  Gemeinde, auf-/zuklappbar). **Volltext** sucht **serverseitig** über Titel,
  Gegenstand, Bemerkung und Notizen und ersetzt die Übersicht durch eine
  Trefferliste: je Treffer der Name mit Datum (rechts) und eine Zeile je Fundfeld
  mit Tag und einem Auszug, in dem der Begriff markiert ist (`<mark>`, XSS-sicher:
  erst escapen, dann markieren). Live beim Tippen (entprellt, Volltext ab 3
  Zeichen) mit Lade-Puls; leeres Feld zeigt wieder die Übersicht; der Zustand
  steht in der URL (`/suche?q=…&scope=`). Technik: `LIKE` (kein Schema-Eingriff)
  über `search/quickSearch.php` und
  `ProjektRepository::searchFulltext()`/`matchingNotes()`; `FULLTEXT` bleibt eine
  spätere, schnittstellengleiche Ausbaustufe.
- Brute-Force-Bremse am Login: Nach zu vielen Fehlversuchen aus derselben IP
  (10 in 15 Minuten) wird der Login vorübergehend verweigert und die Anmeldeseite
  zeigt einen Hinweis (`?locked=1`). Fehlversuche werden in der neuen Tabelle
  `tabLoginAttempt` mit Zeitstempel gezählt und beim Prüfen aufgeräumt; nach
  erfolgreicher Anmeldung gelöscht. Neu: `App\Repository\LoginAttemptRepository`.
- Mailserver-Box in den Einstellungen: zeigt die aktiven SMTP-Einstellungen
  (ohne Passwort) und verschickt auf Knopfdruck eine Testmail an eine frei
  wählbare, mit der eigenen Adresse vorausgefüllte Empfängeradresse. Der Knopf
  ist inaktiv, solange das Feld leer ist. Nutzt den vorhandenen `App\Mail\Mailer`
  (kein neues Paket); neue, CSRF-geschützte `settings/mailTest.php`, die die
  Adresse per `FILTER_VALIDATE_EMAIL` prüft. (Die Stile liegen im Frontend-Commit,
  `app.css`.)
- Favorit als Stern-Icon im Projektkopf (rechts auf der Reiterzeile), auf Detail-
  und Dokumente-Seite — projektweit, ohne Neuladen umschaltbar (`toggleFavorit`,
  Helfer `fav_star_html`). Damit ist der Favorit-Button aus der unteren
  Aktionszeile der Detailseite verschwunden; „Bearbeiten" und „Projekt
  abschliessen" bleiben dort.
- Cache-Busting für statische Assets: die in `partials/head.php` eingebundenen
  CSS-/JS-Dateien tragen jetzt ein `?v=<mtime>`-Suffix (Helfer `asset()` in
  `bootstrap.php`). Ändert sich eine Datei, lädt der Browser sie neu, statt eine
  veraltete Version aus dem Cache zu nehmen.
- Mint-Textmarker als CD-Signatur (Klasse `.mark`): ein Mint-Band im unteren
  Drittel eines Wortes. Bewusst sparsam gesetzt — auf der Login-Begrüssung, auf
  den kurzen, festen Seitentiteln (Brennpunkte, Einstellungen, Dokumentarchiv,
  Neuigkeiten, Erweiterte Suche, Neuer Brennpunkt) und im Termine-Leerzustand;
  nicht auf variablen Projektnamen (Detail/Projekt-Dokumente).
- Erweiterte Suche direkt auf der Startseite: „Erweiterte Suche" öffnet jetzt ein
  Panel unter der Filterzeile (statt auf eine eigene Seite zu wechseln). Dabei
  werden die drei Kacheln ausgeblendet und das normale Filterfeld gesperrt; die
  Suchfelder (voller Satz inkl. Kanton→Gemeinde-Kaskade) stehen als Kachel mit
  drei Aktionen — Suche beenden, Suchfelder leeren, Suchen (Mint-CTA). Die
  Ergebnisse erscheinen gruppiert nach Kanton › Gemeinde. Während der Suche
  signalisiert ein pulsierendes Lightblue-Leuchten um die Kachel den Ladevorgang.
- CD-Manual als gepflegte Datei im Repo (`docs/cd-manual.html`): das bisher nur
  als claude.ai-Artefakt bestehende Konzept „Weiss, Mint & Navy" lebt jetzt
  versioniert mit dem Code (das Artefakt bleibt die synchronisierte Teilkopie).
- Die (erweiterte) Suche zeigt ihre Ergebnisse jetzt nach Kanton > Gemeinde
  gruppiert, in derselben Hierarchie wie die Brennpunkt-Übersicht.
- Ordnerbaum in der Dokumentenverwaltung (Etappe 1): Die Ordner stehen jetzt als
  aufgeklappter Baum in einer linken Spalte (aktiver Ordner hervorgehoben,
  Unterordner mit Aktions-Icons Umbenennen/Verschieben/Löschen beim Hover); der
  Wurzelordner ohne Aktionen. „Neuer Ordner" wurde in diese linke Spalte verlegt
  und legt im aktuell geöffneten Ordner an. Rechts stehen nur noch die Dateien
  des aktuellen Ordners samt kompakter Toolbar (Ordnerinfo, Archiv, Upload).
  Nutzt die bereits vorhandene Ordner-Hierarchie (`tabOrdner.ordParent`) — keine
  DB-Änderung. Entfällt: die redundante Unterordner-Tabelle, die Breadcrumb (der
  Baum zeigt den Pfad) und der funktionslose Hinweis „Erstellen Sie Unterordner".
  Die Ordner-Mehrfachauswahl entfällt vorerst mit der Tabelle (Einzelaktionen
  bleiben); Drag&Drop zwischen Ordnern folgt in Etappe 2.
- Sprechende URLs für Brennpunkte: `/projekt/<id>-<slug>` (z.B.
  `/projekt/196-protokolle-brennpunkt`) statt `/detail/?p=196`. Die ID vorne ist
  massgeblich, der Slug (aus dem Projektnamen) ist Kosmetik — dadurch keine
  Eindeutigkeits- oder Umbenennungs-Probleme. Alte `/detail/?p=`-Links und
  fehlende/falsche Slugs werden per **301** auf die kanonische URL umgeleitet
  (`detail/index.php`), die Rewrite-Regel steht in `public/.htaccess`.
  Slug-Helfer (`slugify`, `projekt_path`, `projekt_url`) in `bootstrap.php`.
- Zuverlässiger E-Mail-Versand über SMTP (`App\Mail\Mailer`, PHPMailer) statt
  des nackten `mail()`: Mails laufen über das authentifizierte LSVV-Postfach
  (SPF/DKIM der Domain) und landen damit verlässlich zu. Konfiguration in der
  `.env` (`SMTP_*`, `MAIL_FROM*`); in der Entwicklung fängt ddev-Mailpit alle
  Mails ab. Genutzt von der Termin-Erinnerung und dem Passwort-Reset.
- „Passwort vergessen"-Funktion (Offerte Pos. 10): Über einen per E-Mail
  gesendeten, eine Stunde gültigen Link lässt sich ein neues Passwort setzen
  (mind. 12 Zeichen, mit Zahl und Sonderzeichen). Das Token liegt in der DB nur
  als SHA-256-Hash (neue Tabelle `tabPasswordReset`), ist einmalig verwendbar
  und wird nach Gebrauch samt weiteren offenen Links des Kontos entwertet.
  Schutz gegen Missbrauch: neutrale Rückmeldung (kein Ausspähen, ob eine Adresse
  existiert) und Rate-Limit (max. 5 Anfragen je Konto/Stunde). Neu:
  `login/forgot.php`, `login/reset.php`, `App\Repository\PasswordResetRepository`.
- Button „Projekt abschliessen" (Offerte Pos. 8): Auf der Detailseite schliesst
  ein Klick (mit Rückfrage) den Brennpunkt ab, indem er den Status auf den als
  Abschluss markierten Status setzt — fachlich identisch zu Bearbeiten → Status
  per Dropdown → Speichern, inkl. Journal-Eintrag. Welcher Status „Abschluss"
  ist, bestimmt das neue Flag `tabStatus.staIstAbschluss` (statt einer fest
  verdrahteten ID; die Status sind admin-editierbar). Der Button erscheint nur,
  wenn ein Abschluss-Status konfiguriert ist und der Brennpunkt noch nicht
  darauf steht. Neu: `detail/projektAbschliessen.php`,
  `ProjektRepository::setStatus()`, `StatusRepository::findAbschlussStatusId()`.
  In den Einstellungen (Status-Verwaltung) legt ein Radio-Button fest, welcher
  Status der Abschluss-Status ist — ein erneuter Klick hebt die Markierung wieder
  auf (kein Abschluss-Status). So bleibt das Flag sichtbar/änderbar statt
  Geheimwissen in der DB. Neu: `StatusRepository::setAbschlussStatus()`.
- Projektstatus-Journal (Offerte Pos. 9): Wird beim Bearbeiten eines
  Brennpunkts der Status geändert, hält ein automatischer Journal-Eintrag den
  Wechsel fest (z.B. «offen» -> «abgeschlossen»). Umgesetzt in
  `detail/projektUpdate.php` über `NotizRepository::add()`; die Status-Namen
  liefert `AbstractLookupRepository::findName()`.
- Composer als Abhängigkeitsverwaltung eingeführt (erste Abhängigkeit:
  `vlucas/phpdotenv`).
- Umgebungskonfiguration über eine `.env` (Vorlage: `.env.example`).
- ddev-Befehl `refresh-from-prod` zum Einspielen eines Produktions-Dumps in
  die lokale Umgebung (entfernt die DEFINER-Klauseln der Views und stellt die
  Stored Procedures sicher).
- Zentraler Prepared-Statement-Helfer `db_query()` in `db.php` als Grundlage
  für die Umstellung aller Abfragen auf gebundene Parameter.
- Golden-Master-Test unter `tests/` zum Absichern verhaltens-erhaltender
  Änderungen (Vergleich der Seiten-Ausgabe gegen eine Baseline).
- PSR-4-Autoloading (Namespace `App\` → Verzeichnis `app/`) als Fundament für
  die schrittweise Strukturierung des Codes.
- CSRF-Schutz für alle schreibenden Endpunkte nach dem Double-Submit-Cookie-
  Verfahren (`App\Security\Csrf`). `header.php` — der zentrale Engpass jeder
  Anfrage — stellt ein für JavaScript lesbares Token-Cookie (`SameSite=Lax`)
  sicher und weist schreibende Anfragen ohne gültigen Token mit HTTP 403 ab.
  Der Client sendet den Token über `assets/csrf.js` (jQuery-`ajaxSetup`) bzw.
  die Dropzone-Konfiguration automatisch mit. Schlanker Unit-Test unter
  `tests/csrf_test.php`.

### Geändert

- Erfassformular „Neuer Brennpunkt" optisch an das Bearbeiten-Formular angeglichen
  (gleiche Tabellen-/Label-Optik, Label „Zugewiesene Mitarbeitende"); mehr Luft
  über den Formular-Buttons (Erfassen/Bearbeiten) und über den
  Zwischenüberschriften der Einstellungen.
- Neuigkeiten-Listen über `App\Repository\NewsRepository` statt roher
  `mysqli_query` in der View; die Zeilen sind – wie in der Übersicht – klickbar
  mit echtem `<a>`-Link (barrierefrei) und das kaputte `<tr 'clickable'>`-Markup
  korrigiert. `htmlspecialchars` in mehreren Dateien auf den zentralen
  `e()`-Helfer vereinheitlicht (Termine-Sidebar, Passwort-vergessen/-Reset).
- Erweiterte Suche & Listen überarbeitet: Das Suchformular ist ein responsives
  Grid (zwei Spalten am Desktop, einspaltig auf schmalen Schirmen) statt einer
  Layout-`<table>`. Klickbare Zeilen (Startseite und Suchergebnisse) sind jetzt
  barrierefrei – echte `<a>`-Links für Tastatur/Screenreader, der ganze
  Zeilen-Klick per delegiertem `data-href`-Handler. Nach dem Ausführen scrollt
  die erweiterte Suche sanft zu den Treffern (mit `prefers-reduced-motion`).
  Intern: der ~215-zeilige Copy-Paste-WHERE-Block der Suche ist einem
  datengetriebenen Builder gewichen (Verhalten bit-genau erhalten, per
  Golden-Master belegt); `$_GET`-Zugriffe mit Defaults, keine Fehler-Leaks mehr.
  Enthält auch die Stile der Mailserver-Box (`app.css`).
- Der Live-Filter der Brennpunkt-Übersicht zeigt Treffer im „Alle"-Teil jetzt in
  ihrem Baum (Kanton › Gemeinde › passende Brennpunkte) statt flach — die
  Hierarchie bleibt beim Filtern erhalten.
- Die Termine-Sidebar der Startseite dockt früher an (Sticky-`top` an die jetzt
  flachere Navbar angeglichen), damit die Termine weniger mit den Brennpunkten
  mitwandern.
- Header-Menü: Die Benutzer-Menüpunkte (Name, Neuigkeiten, Doku-Archiv,
  Einstellungen, Abmelden) stehen ab Tablet nebeneinander statt in einem
  Dropdown; auf kleinen Screens klappen sie über ein Hamburger-Menü auf. Dezent
  gehalten (leises Petrol, Metagrösse, normal statt fett, mehr Luft dazwischen),
  kräftiges Petrol erst beim Hover; „Abmelden" bleibt rot.
- Header-Feinschliff: Der Benutzer-Button ist aufgelöst — nur noch der Name mit
  dezentem Aufklapp-Pfeil (kein gerahmter Button); das Logo etwas grösser, die
  Navbar vertikal flacher.
- Browser-/Bootstrap-Fokus in der CD-Farbe (Lightblue-Ring) statt Bootstrap-Blau;
  Eingabefelder zusätzlich mit Petrol-Rand.
- Login: das Logo in derselben Petrol-Einfärbung wie im Header, der Link
  „Passwort vergessen" in der CD-Linkfarbe (kontraststarkes Mint); die Zeile
  „Willkommen im Brennpunkt-Manager" entfernt.
- „Erweiterte Suche" schaltet jetzt um (derselbe Button öffnet und schliesst).
- Kopf „Kommentare & Termine" sieht aus wie der „Termine"-Kopf (Tableiste mit
  einem Punkt).
- Mehr Abstand zwischen Navbar und Brotkrume sowie Brotkrume und Titel; Titel und
  Brotkrume auf die Einrück-Linie von Reitern/Inhalt gezogen.
- Einheitlicher oberster Header auf allen Seiten (neuer `partials/navbar.php`):
  Logo links, Benutzer-Menü rechts, überall gleich (wie bisher in der
  Projektdetailansicht). Die seitenspezifischen Navbar-Titel und „Zurück"-Knöpfe
  entfallen — der Kontext steht im Seitenkopf (Brotkrume + Titel). Neuigkeiten,
  Erweiterte Suche und Neuer Brennpunkt haben nun ebenfalls Seitenkopf + Titel.
- Abschnittsüberschriften der Startseite („Termine", „Brennpunkte") sehen aus wie
  eine Tableiste mit einem Punkt: dünne Linie über die volle Breite, dicke Linie
  nur unter dem Wort.
- „Eine Hauptaktion" je Formular geschärft: In „Projekt bearbeiten" ist der
  Löschen-Button jetzt zurückhaltend (`btn-outline-danger`) statt Petrol-Outline,
  damit „Speichern" die einzige Hauptaktion ist.
- Formular-Labels in „Neuer Termin" und „Neuer Brennpunkt" im selben dezenten
  Stil wie die Labels der Projektdetails (klein, helles Petrol).
- Brotkrume beginnt jetzt überall mit „LBM" (verlinkt auf die Startseite/
  Brennpunkte), gefolgt vom Seitenkontext — so ist die Startseite auch aus
  Archiv, Einstellungen usw. immer erreichbar.
- Button-Hover vereinheitlicht und auf die Kundenfarben umgestellt: alle
  Aktions-Buttons (gerahmt wie gefüllter Mint-CTA) wechseln beim Hover auf Navy
  (#171b40) mit Lightblue-Text (#adedf9) — die CD-Paarung des Kunden an der
  Interaktion, ohne dunkler werdendes Mint und ohne vertikalen Sprung. Abbrechen
  und Löschen bleiben bewusst zurückhaltend/semantisch. Neue Tokens `--navy`/
  `--lightblue`.
- Jede Seite trägt jetzt einen einheitlichen Seitenkopf (`partials/page-header.php`,
  vorher der projektspezifische Kopf): Brotkrume plus Seitentitel, auf Detail-
  und Dokumente-Seite zusätzlich die Reiter. Titel je Seite: Brennpunkte
  (Startseite), Projektname (Detail/Dokumente), Dokumentarchiv (`p=-1`),
  Einstellungen. Navbar-Titel und „Zurück"-Knopf der Einstellungsseite entfallen;
  auf der Startseite entfällt der dadurch doppelte Spalten-Header. CSS-Klassen
  `.projekt-*` → `.page-*`.
- Brennpunkt-Übersicht optisch entschlackt: Kanton- und Gemeinde-Ebene ohne
  graue Hinterlegung (nur Fettung/Einrückung/Hover), die Trennlinie unter den
  Bereichsköpfen (Zuletzt besucht/Favoriten/Alle) als dünne Linie in der
  Mint-CTA-Farbe. „– keine –" in leeren Bereichen erscheint auf Eintragshöhe und
  -einrückung, ohne Hover.
- Brennpunkt-Übersicht der Startseite vereinfacht: Die drei Bereiche *Zuletzt
  besucht*, *Favoriten* und *Alle* stehen je in einer eigenen Karte mit
  Aufklapp-Kopf (erste Ebene beim Laden offen); die bisherigen doppelten
  Spaltenköpfe („Brennpunkt | Zuletzt bearbeitet") entfallen.
- Das frühere „Suche"-Feld in der Navbar ist ein Live-Filter geworden und sitzt
  jetzt zuoberst in der Brennpunkt-Liste: Er filtert schon bei der Eingabe (ohne
  Enter) über alle drei Bereiche (im „Alle"-Teil als flache Trefferliste), hat
  einen X-Knopf zum Leeren, und rechts daneben stehen „Erweiterte Suche" und
  „Neuer Brennpunkt". Die Navbar trägt dadurch nur noch Logo und Menü.
- Auf der Startseite stehen die Termine ohne umschliessende Card (wie die
  Brennpunkte) — nur Sticky-/Scroll-Verhalten bleibt.
- Projektkopf mit Reiter-Navigation statt des „Dokumente"-Knopfs in der Navbar:
  Detail- und Dokumente-Seite haben jetzt einen gemeinsamen Kopf (neuer Partial
  `partials/projekt-header.php`) mit Brotkrume „← Übersicht", Projekttitel und
  Reitern Details/Dokumente (der passende ist je Seite aktiv). Titel und Zurück
  wandern damit aus der Navbar. Der Dokumente-Reiter nutzt weiterhin
  `generateDocumentLink()` (legt bei Bedarf den Wurzelordner an). Toter Code
  `generateTitle()` entfernt.
- Abschnittsüberschriften „Brennpunkte", „Termine" (Startseite) und „Kommentare
  & Termine" (Journal) auf einen einheitlichen Stil gebracht (grau, 2px-Petrol-
  Unterlinie); „Brennpunkte" als Überschrift der linken Startseiten-Spalte
  ergänzt (Pendant zu „Termine").
- Seitenheader (Navbar) schliesst zum Inhalt mit einem weichen Schatten in der
  Mint-CTA-Farbe ab statt mit einer harten 2px-Linie.
- Ausrichtung der Dokumente-Seite korrigiert: Kopf und Inhalt fluchten nun. Die
  Kombination `.content.row` hatte über `margin:auto` die negativen Zeilenränder
  von Bootstrap ausgehebelt, wodurch das Spaltenpadding den Inhalt nach rechts
  schob; `.row` ist jetzt sauber in `.content` geschachtelt.
- Journal (Kommentare & Termine) in der Projekt-Detailansicht neu strukturiert,
  weil die Einträge optisch ineinanderflossen. Termine erscheinen jetzt als
  derselbe Karten-Baustein wie in der Startseiten-Sidebar („gleiche Dinge gleich
  behandeln") — dafür ist `.termin-card` aus `.page-home` zur gemeinsamen Klasse
  gelöst (Klickbarkeit/Hover bleiben startseiten-spezifisch). Der linke Rand
  zeigt die Kategorie: Frist rot, bevorstehend Mint, vergangen grau; Datum, Zeit
  und Ort stehen in einer Zeile. Kommentare stehen als leichter Eintrag mit dem
  (einzigen) Datum als eigener Spalte links statt inline, aufeinanderfolgende
  durch eine Haarlinie getrennt; der Kontrast Karte ↔ leichter Eintrag trennt
  die Typen zusätzlich. Die Abschnittsüberschrift „Kommentare & Termine" steht
  jetzt zuoberst über dem Eingabebereich (sie betrifft auch die Eingabe), die
  übergreifenden Aktionen (Neuer Termin, Kommentare exportieren) als Reihe
  darunter; „Absenden" heisst neu „Kommentar speichern". Ersetzt das bisherige
  tabellenbasierte Journal-Markup samt Badges.
- FontAwesome von 5.15.4 auf 6.7.2 gehoben (`public/library/fontawesome/`).
  Alle 27 im Projekt genutzten Icons lösen sich in FA6 auf (die umbenannten über
  die v5-Aliase), daher **kein Markup-Umbau** nötig. In FA6 entfallene
  Schriftformate (eot/svg/woff) entfernt; es bleiben woff2/ttf plus
  `fa-v4compatibility`. Bezug wie gehabt über npm (`@fortawesome/fontawesome-free`).
- Aktions-Icons (bearbeiten/umbenennen, verschieben, löschen) im Journal, in der
  Dokumentenverwaltung und in den Einstellungen vereinheitlicht: durchgehend
  solid (die bisher gemischten `far`-Outline-Icons für Kalender/Ordner ebenfalls
  auf solid), im Ruhezustand zurückhaltend (gedämpftes Petrol, halbe Deckkraft)
  und bei Hover kräftig — Löschen im gedämpften Marken-Rot als Warnsignal, die
  übrigen Aktionen in kräftigem Petrol. Ersetzt die alten, uneinheitlichen
  Einzelfarben (grau → rot/blau/grün) durch eine zentrale Klasse `.icon-action`
  (`custom-colors.css`); der bisher zweite Bearbeiten-Glyph (`fa-edit`) weicht
  dem einheitlichen `fa-pen`.
- Vereinheitlichtes Seitenlayout: Alle Seiten nutzen jetzt denselben Container
  (`max-width: 1250px`, 40px seitliches Padding), und die Navbar ist daran
  gekoppelt (`max-width: 1250px; width: 100%` statt der starren `1170px`), sodass
  Navigation und Inhalt seitengleich fluchten. Die Dokumentseite verliert ihre
  Sonderbreite (`max-width: 100%`) und zeigt den Ordner-/Dateibereich zunächst
  über die volle Breite; die Vorschauspalte erscheint erst beim Öffnen eines
  Dokuments (dann Aufteilung `col-lg-7 / col-lg-5`) statt dauerhaft die halbe
  Breite leer zu lassen. Auf der Startseite erhält die Termin-Spalte ab `lg` eine
  Mindestbreite (340px, linke Spalte gibt dafür nach), und in der Detailansicht
  brechen Formular und Journal bei zu wenig Platz sauber untereinander um, statt
  sich zu quetschen.
- Frontend-Feinschliff am hellen CD: Termin-Karten mit Datum/Zeit über dem Titel
  (Titel volle Breite); Detailansicht ohne Zebra (Trennlinien) und mit dezenten
  Labels (kleiner, hell-Petrol); Daten überall im selben Label-Format (Übersicht,
  Termin-Karten, Journal). Der Journal-Stream ist klar gegliedert — Inhalt oben,
  Erfassungsdatum unten hinter einer Trennlinie; beim Termin steht das
  Termindatum als „Termin: …" im Inhalt, das Erfassungsdatum (neu aus
  `tabTermin.terCreated`) in der Metazeile, sodass beide nicht mehr verwechselbar
  sind. Button-Rollen präzisiert (CTA nur bei einer Hauptaktion): „Absenden" ist
  CTA, „Kommentare exportieren"/„Projekt abschliessen" sind normale Buttons,
  Termin-Popup „Abbrechen" normal; Doku-Buttons in normaler Schriftgrösse. Kopf
  „Kommentare & Termine" und die Startseiten-Abschnittsköpfe hell/vereinheitlicht.
- Projektstruktur auf einen eigenen Document-Root `public/` umgestellt. Nur die
  öffentlichen Einstiegspunkte und Assets liegen in `public/`; die echte
  Infrastruktur (`bootstrap.php`/`db.php`/`header.php`), `config/`, `app/`,
  `vendor/`, `db/`, `docs/`, `tests/`, `cron/` und die `.env` liegen eine Ebene
  höher und sind damit nicht mehr über den Browser erreichbar (statt nur per
  `.htaccess` gesperrt). Drei einzeilige Shims in `public/` delegieren an die
  echten Infrastruktur-Dateien, sodass kein Include in den Seiten geändert werden
  musste. `uploads/`/`archives/` bleiben in `public/` (token-gated). Deployment:
  der Webserver-Docroot muss auf `public/` zeigen (ddev via `config.yaml`
  erledigt). Siehe [`docs/struktur.md`](docs/struktur.md).
- Oberfläche auf ein helles CD umgestellt (angelehnt an die LSVV-Kampagne):
  weisse Flächen statt farbiger, Navbar weiss mit Petrol-Ink und dünner
  Unterlinie, Logo als eingefärbte Maske in hellem Petrol (`--petrol-light`,
  zentral steuerbar). Buttons sind gerahmt mit kräftigem Mint-Hover; gefüllt ist
  nur der eine CTA je Hauptaktion (Speichern/Anmelden/Suchen/Abschliessen).
  Termin-Karten weiss mit farbigem Randstreifen und Kategorie-Label statt
  gefülltem Kopf; klickbare Karten und Zeilen bekommen einen klaren Hover
  (Mint-Signal). Favoriten-Kopf und Doku-Bereich hell. Neuer Token
  `--petrol-light` für den Logo-Ton.
- Die Termin-Erinnerung (`cron/termin_reminder.php`) versendet über die neue
  SMTP-Schicht (`App\Mail\Mailer`) statt über `mail()` — dieselbe verlässliche
  Zustellung wie beim Passwort-Reset, Absender zentral aus der Konfiguration.
- Die vier Stored Procedures (`procDeleteFolders`, `procDeleteProjekt`,
  `procDeleteSingleFile`, `procLastVisited`) durch sichtbare PHP-Logik in den
  Repositories ersetzt. Die App ruft damit keine Stored Procedure mehr auf — die
  zuvor unsichtbar in der Datenbank liegende, root-eigene Logik steht nun im Code
  und in git (`app/Repository/`). Der dev-Fallback `.ddev/scripts/ensure-procs.php`
  entfällt. `procLastVisited` (DELETE+INSERT) wurde zu einem Upsert
  (`INSERT … ON DUPLICATE KEY UPDATE`) über den Primärschlüssel.
- Frontend-Bibliotheken vereinfacht und lokalisiert. Der bisher rund zehnfach
  einkopierte `<head>` liegt jetzt zentral in `partials/head.php`; alle
  Bibliotheken (Bootstrap, jQuery, Popper, bootstrap-select, FontAwesome, PDF.js,
  Dropzone) kommen lokal aus `library/` statt von externen CDNs — besser für
  Datenschutz und Ausfallsicherheit, funktioniert offline/hinter Firewalls. Per
  npm bezogen (`package.json`), die `dist/`-Dateien mitversioniert (kein
  Build-Schritt). FontAwesome von drei parallelen Versionen (5.7/5.8/5.9) auf
  eine vereinheitlicht, jQuery auf 3.7.1 aktualisiert, Doctype auf HTML5. Sichtbar
  für Nutzer: die LSVV-Farben (`custom-colors.css`) gelten nun auf allen Seiten,
  bisher nur auf der Startseite. Die seiten-spezifischen Navbars (Titel,
  Zurück-Button) bleiben unverändert.
- Die neun verstreuten Stylesheets zu einer `css/app.css` konsolidiert, zentral
  über den Head-Partial geladen. Die seiten-eigenen Regeln sind unter einer
  Body-Kontextklasse (`page-*`) gescopt, sodass zuvor kollidierende Selektoren
  (z. B. `.col1` mit 70 % in Einstellungen vs. 15 % in Suche/News) sich nicht mehr
  gegenseitig überschreiben. Totes Alt-Layout-CSS entfernt (der ungenutzte
  `.termin-item`-Block, die Legacy-IDs `#logo/#titel/#menu1/#extra/#footer`),
  hartkodiertes `#4578A5` durch das Token `var(--primary)` ersetzt.
  Verhaltens-erhaltend (Werte bewahrt) und Grundlage für ein späteres Vite-Setup
  (`app.css` wird dort zum Entry-Point). Siehe `docs/css-strategie.md`.
- PDF.js von 3.11.174 auf 4.10.38 aktualisiert (Legacy-ESM-Build, lokal). Die
  Dokument-Vorschau lädt PDF.js jetzt als ES-Modul statt als globales
  UMD-Skript; der Worker liegt lokal als `.mjs`. Die Root-`.htaccess` liefert
  `.mjs` mit korrektem JavaScript-MIME-Typ aus, damit der Modul-Import auch auf
  dem Live-Server nicht blockiert wird.

- Konfiguration zentralisiert: DB-Zugang, App-Secret und alle umgebungs-
  abhängigen Werte (Basis-URL, Pfade, Superuser …) kommen aus der `.env` über
  `config/app.php` und den Helfer `config('app.…')`. Die Umgebung wird explizit
  über `APP_ENV` bestimmt (kein Raten mehr über den Hostnamen, gilt auch für den
  Cronjob). Die früher in `db.php`, `header.php` und `cron_header.php`
  dreifach duplizierte Host-Weiche und die `constants{Prod,Dev,Debug}.php`
  entfallen. Sämtliche Zugriffe im übrigen Code (Views, Funktionen, Cronjob)
  greifen nun direkt auf `config('app.…')` zu; die frühere Übergangs-Brücke
  über globale Variablen (`$BASE_URL`, `$UPLOAD_DIR` …) ist damit vollständig
  abgelöst.
- Datenzugriff für Benutzer in `App\Repository\UserRepository` gebündelt —
  erster Schritt der Repository-Einführung. Die zuvor über sieben Dateien
  verstreuten `tabUser`-Abfragen liegen nun zentral und testbar an einer Stelle
  und geben Arrays statt `mysqli_result` zurück (entkoppelt die Aufrufer von
  mysqli). Die DB-Verbindung wird per Konstruktor injiziert; `db_query()` nimmt
  dafür optional eine Verbindung entgegen (ohne Angabe unverändert das globale
  `$db`).
- Datenzugriff für Favoriten in `App\Repository\FavoritRepository` gebündelt —
  `add()`, `remove()`, `exists()`, `listForUser()` ersetzen den verstreuten
  SQL in `detail/setUnsetFavorit.php`, `detail/index.php` und `bpTable.php`
  (erste schreibende Repository-Methoden). IDs werden an der Grenze `(int)`
  gecastet.
- Datenzugriff für Termine in `App\Repository\TerminRepository` gebündelt —
  Anlegen/Bearbeiten/Löschen inklusive Benutzer-Zuweisung (`tabTerminUser`),
  die Sidebar-Liste und die beiden Cron-Abfragen (fällige Erinnerungen +
  „gesendet"-Flag) liegen nun zentral. Ersetzt den verstreuten SQL in
  `detail/comment/comments.php`, `termineSidebar.php` und
  `cron/termin_reminder.php`. Notizen (`tabNotiz`) und die Stream-Abfrage
  folgen als eigener Schritt.
- Datenzugriff für Notizen/Kommentare in `App\Repository\NotizRepository`
  gebündelt — Anlegen/Bearbeiten/Löschen, der Export und der Kommentar-/
  Termin-Verlauf (`streamForProjekt`, die `UNION` aus `tabNotiz` und
  `viewTermin`) liegen nun zentral. Ersetzt den restlichen rohen SQL in
  `detail/comment/comments.php`; damit ist der Kommentar-Bereich vollständig
  hinter Repositories gekapselt.
- Datenzugriff für Kantone in `App\Repository\KantonRepository` gebündelt
  (Beginn der Lookup-/Stammdaten-Migration). Dabei das „`$db`-als-Parameter"-
  Antipattern für Kanton aufgelöst: `kantonListSelected()` verliert den
  `$db`-Parameter (nutzt `global $db` + Repository), die Aufrufer in
  `detail/erstellen.php` und `detail/form_projekt_edit.php` ziehen mit.
- Datenzugriff für die uniformen Stammdaten Kategorie, Massnahme, Status und
  externe Organisation in `App\Repository\{Kategorie,Aktion,Status,OrgExtern}Repository`
  gebündelt, mit gemeinsamer Basisklasse `AbstractLookupRepository` (Lesen,
  Anlegen, Umbenennen, Aktiv-Schalten, Löschen mit Nutzungs-Guard). Ersetzt den
  verstreuten SQL in `detail/function.php`, `search/searchForm.php` und den
  Einstellungs-CRUD-Funktionen. Das `$db`-als-Parameter-Antipattern ist damit in
  `detail/function.php` vollständig aufgelöst (alle `*ListSelected` ohne `$db`).
  Nebenbei laufen die Einstellungs-Operationen `itemDelete`/`itemActivate` nun
  über Prepared Statements statt roher String-Interpolation.
- Datenzugriff für Gemeinden in `App\Repository\GemeindeRepository` gebündelt
  (letzter Lookup). Erbt die uniforme Mechanik aus `AbstractLookupRepository`,
  erweitert Anlegen/Umbenennen um den Kanton (`gemKanton`) und liefert mit
  `findByKanton()` die abhängigen Gemeinde-Dropdowns (`detail/` und
  `search/gemeindeProKanton.php`, inklusive „alle Gemeinden"). Damit läuft auch
  der Einstellungs-Typ 5 über `lookupRepository()`, und **alle sechs Lookup-
  Tabellen sind hinter Repositories gekapselt** — kein rohes Lookup-SQL mehr in
  Views oder Formularen.
- Datenzugriff für Brennpunkte in `App\Repository\ProjektRepository` gebündelt —
  Anzeige (`viewProjektFull`/`viewExtOrg`), Anlegen/Bearbeiten (über ein
  assoziatives Daten-Array), Löschen und „zuletzt besucht" via Stored Procedures
  (`procDeleteProjekt`/`procLastVisited`, weiter als direkte `CALL` mit
  `(int)`-Argumenten), die Mitarbeitenden- und Organisations-Zuordnungen
  (`tabProjektUser`/`tabProjektOrganisation`) und die Nummern-Sichten. Ersetzt
  den verstreuten SQL in `projektInsert`/`Update`/`Delete`,
  `form_projekt_show`/`edit`, `erstellen.php`, `detail/function.php` und
  `dokumente/index.php`. Der `tabOrdner`-INSERT (Wurzel-Ordner) bleibt für den
  Dokument-Schritt; die Projekt-Verknüpfung (`proFkOrdner`) läuft bereits über
  `setFolder()`.
- Datenzugriff für Ordner in `App\Repository\OrdnerRepository` gebündelt (erster
  Teil des Dokument-Bereichs). Umfasst das Anlegen von Wurzel- und Unterordnern
  (inklusive dem aus dem Projekt-Schritt ausgesparten `tabOrdner`-INSERT), die
  Baum-Navigation (Breadcrumb, Verschiebe-Baum, Unterordner-Tabelle über
  `viewOrdnerInfo`), Umbenennen/Verschieben sowie Löschen (`procDeleteFolders`
  als direkter `CALL` mit `(int)`; dessen Result-Set wird geleert, damit
  Folge-Queries nicht an „Commands out of sync" scheitern). Ersetzt den
  verstreuten SQL in `form_projekt_show`, `dokumente/function.php`,
  `singleAction`, `multiAction`, `addNewFolderTable` und `dokumente/index.php`.
  Die zuvor teils roh interpolierten Navigations-Abfragen (`WHERE ordID = $id`
  …) laufen nun über gebundene Parameter bzw. `(int)`-Casts (Härtung).
- Datenzugriff für Dokumente in `App\Repository\DokumentRepository` gebündelt —
  Anlegen, Namenskollisions-Prüfung, Anzeige-/ZIP-Listen (`viewDokument`),
  Umbenennen, Verschieben, Löschen (`procDeleteSingleFile`) und die Liste der
  gelöschten Dateien für das Aufräum-Skript. Ersetzt den restlichen SQL in
  `uploadFiles`, `singleAction`, `multiAction`, `dokumente/function.php` und
  `settings/deleteTest.php`. Die roh interpolierten ZIP-/Anzeige-Abfragen laufen
  nun über gebundene Parameter (Härtung). **Damit ist der gesamte Datenzugriff
  der Anwendung hinter Repositories gekapselt** (Benutzer, Favoriten, Termine,
  Notizen, sechs Lookups, Brennpunkte, Ordner, Dokumente).

### Entfernt

- Toter Code nach dem Dokumente-Umbau: die ungenutzten Funktionen
  `generateNavigation`, `getFolders` und `generateFolderTable` (samt `?folder`-
  Endpoint und dem funktionslosen „Erstellen Sie Unterordner"-Hinweis) in
  `dokumente/function.php`, sowie die verwaisten `goBack()`-Definitionen auf
  Detail-, Dokumente-, Einstellungs-, Suche- und Erstellen-Seite.
- Ungenutztes `public/library/darkBootstrap.min.css` (BS4-Dark-Theme, nirgends
  eingebunden — toter Ballast).
- Verwaister Duplikat-Code `slider/` (nahezu identische Kopie von `dokumente/`).
- Toter Code: `search/function.php`, `search/function_1.php` und
  `search/index_example.php` (die Beispiel-Seite war nirgends verlinkt und der
  einzige Nutzer der beiden anderen) sowie die nirgends aufgerufene
  `gemeindeListSelected()` in `detail/function.php`. Beseitigt die scheinbare
  Doppelung der Dropdown-Funktionen zwischen `detail/` und `search/` — die
  aktive Such-Maske nutzt ausschliesslich `search/searchForm.php`.

### Behoben

- Inline-Erfassformular: das Feld „Externe Organisation" bekam durch einen
  fehlenden Funktionsparameter (`organisationListSelected`) eine leere DOM-ID;
  behoben.
- Reflektiertes XSS in der Brennpunkt-Detailseite (`detail/index.php`): der
  Parameter `$_GET['p']` wurde bei nicht existierender ID roh in Inline-`<script>`
  ausgegeben. Es wird nun durchgängig die bereits sanitisierte Integer-Variable
  verwendet. Toter Favoriten-Code aus der Stern-Umstellung entfernt
  (`setUnsetFavorit`-JS, `setFavorit`-PHP); ebenso das SQL-Escaping als
  HTML-Escaper im Kommentar-Bereich durch Integer-Cast bzw. `e()` ersetzt.
- Keine SQL-/Fehlerdetails mehr an den Client in Detail- und Einstellungs-
  Endpunkten (`projektDelete/Update/Insert`, `form_projekt_show`, `comments.php`,
  `settings/function.php`): Details ins Server-Log, generische Meldung nach aussen.
  Redundante, fehler-leakende `if (!$db)`-Verbindungschecks entfernt (Prüfung
  liegt zentral in `db.php`). `$_POST['action']` in `comments.php` mit Default
  abgesichert.
- Datei-Upload prüft jetzt den tatsächlichen Inhaltstyp (finfo), nicht mehr nur
  die Dateiendung: eine Endung→MIME-Zuordnung, streng bei Bild/PDF/Text und
  tolerant gegenüber den generischen Container-Typen (ZIP/OLE), die finfo für
  Office- und `.msg`-Dateien liefert – so werden getarnte Inhalte abgewiesen,
  ohne gültige Uploads fälschlich zu blockieren. Zudem die vertauschte
  `isset`-Reihenfolge der Ordner-Prüfung korrigiert (`dokumente/uploadFiles.php`).
- Keine SQL-/Fehlerdetails mehr an den Client im Dokumenten-Bereich
  (`singleAction.php`, `addNewFolderTable.php`): `mysqli_error()` wandert ins
  Server-Log. Der Download-Name kommt über `DokumentRepository::findName()` statt
  über rohes `mysqli_query` (`uploads/download.php`).
- Der „Ausführen"-Button der Mehrfachaktion in der Dateiliste war links
  abgeschnitten: eine Bootstrap-`input-group` mit ungestyltem Select brach im
  schmaleren rechten Bereich. Jetzt eine schlichte Flex-Zeile (`.doc-multiaction`)
  mit Standard-Select und -Button.
- Projektkopf war auf der Detailseite sehr schmal: als Flex-Item in
  `#contentMain.vertical` schrumpfte der `.content`-Block mit `margin:auto` auf
  Inhaltsbreite. Mit `width:100%` füllt er nun (bis max-width) die Breite.
- Reiter und Brotkrume im Projektkopf wurden auf der Detailseite nicht vollständig
  gestaltet, weil `.page-detail a:not(.btn)` (schwarz/Padding) sie überschrieb;
  die Kopf-Links sind jetzt ausgenommen.
- XSS in den Suchergebnissen (`search/searchResult.php`): Projektname und
  Nutzername wurden in der Ergebnistabelle roh ausgegeben — jetzt über `e()`
  escaped (im XSS-Sweep zunächst übersehen, beim URL-Umbau entdeckt).
- Cross-Site-Scripting (XSS) in der Ausgabe: Nutzer-/DB-Daten werden beim
  Rendern in HTML über den neuen Helfer `e()` (`htmlspecialchars`,
  `ENT_QUOTES`/UTF-8) escaped — Projekt-, Kategorie-, Status-, Aktions- und
  Benutzernamen, Notiz-/Journaltext, Termine, Datei-/Ordnernamen, der
  Navbar-Benutzername, Formular-`value`-Attribute und -Textareas sowie
  News-Einträge. Damit lässt sich in kein Feld mehr ausführbares Markup
  einschleusen (mit einem `<script>`-Payload gegengeprüft). Die Eingabeseite war
  bereits über Prepared Statements abgesichert; dies schliesst die Ausgabe.
- Schriftfarbe der Navbar-Buttons auf der Detailseite: Die Buttons
  („Dokumente", „Zurück", Nutzermenü) erschienen mit schwarzer statt weisser
  Schrift. Ursache war die zu breit greifende Regel `.page-detail a { color:
  #000 }` (für Text-Links im Inhalt gedacht), die wegen höherer Spezifität die
  weisse Schrift von `.btn-outline-light` überschrieb. Regel auf
  `a:not(.btn)` eingegrenzt (`css/app.css`).
- SQL-Injection in der Brennpunkt-Suche (`search/searchResult.php`): die
  Filter- und Textparameter werden über Prepared Statements gebunden statt in
  den SQL-String verkettet. Dabei den doppelten Query-Aufruf entfernt.
- SQL-Injection im Brennpunkt-Detail (`detail/`): alle Abfragen mit
  Nutzereingaben — Anzeige, Anlegen, Bearbeiten, Löschen, Kommentare, Termine,
  Favoriten — auf gebundene Parameter umgestellt; Integer-IDs zusätzlich
  gecastet, `CALL`-Aufrufe mit geprüften Integer-Argumenten. Das Speichern von
  „kein Wert" (Dropdown `NULL`) bleibt korrekt als SQL `NULL` erhalten.
- SQL-Injection im Dokumenten-Bereich (`dokumente/`): Ordner- und
  Datei-Operationen (anlegen, umbenennen, verschieben, löschen, hochladen,
  ZIP-Export) auf gebundene Parameter bzw. geprüfte Integer-IDs umgestellt.
- SQL-Injection in den übrigen Bereichen (`bpTable.php`, `settings/`,
  `search/gemeindeProKanton.php`, `login/authenticate.php`): Such-, Formular-
  und ID-Parameter über Prepared Statements bzw. geprüfte Integer-IDs. Damit
  sind alle produktiven Abfragen mit Nutzereingaben abgesichert.
- Ausgabefehler auf der Doku-Archiv-Seite: kurzer `<?`-Tag in
  `dokumente/index.php` auf `<?php` korrigiert (erschien als Klartext, weil auf
  dem Server `short_open_tag` nicht aktiv ist).
- Neuen Brennpunkt anlegen brach mittendrin ab: die Auswahl „Externe
  Organisation" liess sich nicht befüllen, und alles darunter (Mitarbeitenden-
  Auswahl, Speichern-Knopf) fehlte. Ursache war ein `in_array()`-Aufruf mit dem
  Sentinel-String `'NULL'` statt eines Arrays — unter PHP 8 ein fataler
  TypeError. `organisationListSelected()` (in `detail/` und `search/`)
  normalisiert das Argument nun auf ein Array.
- Umbenennen von Ordnern und Dokumenten warf HTTP 500 und konnte fremde
  Datensätze verfälschen: In `dokumente/singleAction.php` waren die
  Verzweigungen `if ($type = "folder")` und `if ($type = "file")` Zuweisungen
  statt Vergleiche, wodurch bei jedem Umbenennen **beide** Zweige liefen. Der
  Datei-Zweig (`newFileName`) scheiterte dann am `json_decode` eines
  Nicht-JSON-Werts (→ 500), und der jeweils andere Zweig aktualisierte einen
  gleichnamigen Datensatz mit derselben ID in der anderen Tabelle (ein Ordner
  konnte so ein Dokument mit passender ID umbenennen und umgekehrt). Korrigiert
  zu `==`.
- Datei-Upload lieferte HTTP 500 statt eines Ergebnisses: In
  `dokumente/uploadFiles.php` wurde die JSON-Rückgabestruktur mit `$json = null`
  initialisiert und unmittelbar danach `$json->error = …` gesetzt — unter PHP 8
  ist das Setzen einer Eigenschaft auf `null` ein fataler Error. Nun als
  `new stdClass()` initialisiert.
- Fehlerbehandlung der Datenbank auf allen Abfragen wiederhergestellt: `db.php`
  setzt den mysqli-Fehlermodus jetzt explizit auf `MYSQLI_REPORT_OFF`. Seit PHP
  8.1 wirft mysqli standardmässig Exceptions statt `false` zurückzugeben; die
  bestehende Behandlung (`$db->connect_errno`, `db_query()`-Prüfung auf `false`)
  wäre dadurch wirkungslos geworden und jeder DB-Fehler hätte zu HTTP 500 statt
  zur graziösen Behandlung geführt.
- Anlegen des ersten Brennpunkts einer Periode konnte fatal enden: In
  `detail/function.php` (`newNumber()`) wurde die neue laufende Nummer über
  `$id + 1` berechnet, wobei `$id` aus `substr()` der letzten Nummer stammt. Bei
  leerer Tabelle oder kurzer Nummer liefert `substr()` `""`, und unter PHP 8 ist
  `"" + 1` ein fataler TypeError. Der Summand wird nun mit `(int)` gecastet.
- Sicherheit: browser-aufrufbaren Test-/Cron-Code entfernt. `cron/test_reminder.php`
  war ohne Login im Browser erreichbar, löste den Termin-Mailversand aus und gab
  das Log (E-Mail-Adressen, Termintitel) aus — gelöscht. Der Reminder-Cron
  `cron/termin_reminder.php` läuft nun ausschliesslich über die Kommandozeile
  (CLI-Guard), nicht mehr über HTTP. Toter Dev-Rest `detail/mailTest.php`
  (kaputter `require`, hartkodierte Fremdadresse) ebenfalls entfernt.
- Kaputte `<option value='NULL'</option>`-Tags (fehlendes schliessendes `>`) in
  den Status- und Gemeinde-Dropdowns korrigiert (`detail/function.php`,
  `search/searchForm.php`).
- Datei-Upload: Client-seitiges Grössenlimit von versehentlich 95 MB auf 200 MB
  korrigiert (`dokumente/function.js`) — deckt sich nun mit der Fehlermeldung und
  dem Server-Limit. Zuvor scheiterten Uploads zwischen 96 und 200 MB mit einer
  falschen Meldung.
- Sicherheit: Der Token für den Datei-Abruf ohne Session ist jetzt
  dokumentgebunden und kurzlebig. Bisher war `?token=` der Wert
  `sha256(secret + Datum)` — für alle Nutzer und alle Dokumente einen ganzen Tag
  identisch; er umging in `header.php` den Login vollständig, sodass ein einziger
  geleakter Vorschau-Link bis Mitternacht sämtliche Dateien öffnete. Neu signiert
  `App\Security\DownloadToken` pro Dokument-ID mit kurzer Gültigkeit (1 h);
  `uploads/download.php` autorisiert über Session ODER eine gültige Signatur für
  genau das angeforderte Dokument, und der generische Token-Bypass in `header.php`
  ist entfernt.
- Export-ZIPs sammelten sich unbegrenzt an: Der ZIP-Download eines Ordners
  (`dokumente/multiAction.php`) legte bei jedem Aufruf eine
  `archives/Archiv<zeit>.zip` an, die nie wieder gelöscht wurde. Beim Export
  werden nun ältere Export-ZIPs (älter als eine Stunde) aufgeräumt; der
  Zugriffsschutz (`archives/.htaccess`, `download.php`) bleibt unberührt.
- Ordner-Löschung liess Unterordner als Waisen zurück: `procDeleteFolders` (bzw.
  deren dev-Fallback) markierte nur den gewählten Ordner und dessen direkte
  Dokumente als gelöscht, nicht die Unterordner — obwohl Ordner hierarchisch
  verschachtelt sind. `OrdnerRepository::deleteTree()` löscht jetzt rekursiv den
  gesamten Teilbaum (jeder Ordner samt seiner Dokumente). Mit einem kontrollierten
  Test über drei Ebenen abgesichert.
- Sicherheit: sensible Dateien waren direkt über den Browser abrufbar — allen
  voran die `.env` mit **DB-Passwort und `APP_SECRET` im Klartext**. Die
  Root-`.htaccess` verweigert nun den Direktzugriff auf `.env` und alle anderen
  Punkt-Dateien (ausser `.well-known`), auf `composer.*`/`package*.json` sowie
  Markdown-/SQL-Dateien, auf die nur serverseitig genutzten Verzeichnisse (`app`,
  `config`, `vendor`, `db`, `tests`, `docs`, `partials`) und auf die reinen
  Include-Dateien (`bootstrap.php`, `db.php`, `header.php`, `cron_header.php`).
  Assets (`library/`, `css/`, `assets/`) und Seiten bleiben erreichbar.
