docs(PROJ-53, PROJ-54): Status auf Deployed setzen

Beide Features auf 192.168.1.132 (Test) und 192.168.1.131 (Produktion)
deployed und vom Nutzer bestätigt.
This commit is contained in:
sysops
2026-06-14 23:26:57 +02:00
parent 62693fa36a
commit 4a8b8964e5
3 changed files with 212 additions and 2 deletions
+2 -2
View File
@@ -69,8 +69,8 @@
| PROJ-50 | DSGVO-Löschersuchen für Mail-Inhalte (GoBD-Vorrang) | In Review | [PROJ-50](PROJ-50-dsgvo-loeschersuchen.md) | 2026-06-13 | | PROJ-50 | DSGVO-Löschersuchen für Mail-Inhalte (GoBD-Vorrang) | In Review | [PROJ-50](PROJ-50-dsgvo-loeschersuchen.md) | 2026-06-13 |
| PROJ-51 | Aufbewahrungsfristen nach Dokumentenart (Retention-Kategorien) | Deployed | [PROJ-51](PROJ-51-retention-kategorien.md) | 2026-06-13 | | PROJ-51 | Aufbewahrungsfristen nach Dokumentenart (Retention-Kategorien) | Deployed | [PROJ-51](PROJ-51-retention-kategorien.md) | 2026-06-13 |
| PROJ-52 | Vollständigkeits-Reconciliation (Zähl-Report) | Planned | [PROJ-52](PROJ-52-vollstaendigkeits-reconciliation.md) | 2026-06-13 | | PROJ-52 | Vollständigkeits-Reconciliation (Zähl-Report) | Planned | [PROJ-52](PROJ-52-vollstaendigkeits-reconciliation.md) | 2026-06-13 |
| PROJ-53 | Konfigurierbare Listenanzahl pro Seite | In Review | [PROJ-53](PROJ-53-konfigurierbare-listenanzahl.md) | 2026-06-14 | | PROJ-53 | Konfigurierbare Listenanzahl pro Seite | Deployed | [PROJ-53](PROJ-53-konfigurierbare-listenanzahl.md) | 2026-06-14 |
| PROJ-54 | Fix Listenansicht/Pagination für Rolle "user" (Nachbesserung PROJ-6/PROJ-21) | In Review | [PROJ-54](PROJ-54-fix-listenansicht-total.md) | 2026-06-14 | | PROJ-54 | Fix Listenansicht/Pagination für Rolle "user" (Nachbesserung PROJ-6/PROJ-21) | Deployed | [PROJ-54](PROJ-54-fix-listenansicht-total.md) | 2026-06-14 |
<!-- Add features above this line --> <!-- Add features above this line -->
@@ -0,0 +1,110 @@
# PROJ-53: Konfigurierbare Listenanzahl pro Seite
## Status: Deployed
**Created:** 2026-06-14
**Last Updated:** 2026-06-14
## Dependencies
- PROJ-25 (User-Profil & Einstellungen) — neue Einstellung erscheint auf `/settings`
- PROJ-6 (Volltext-Suche & Filterung) — Mail-Listenansicht nutzt die Einstellung
## User Stories
- Als Benutzer möchte ich in meinem Profil festlegen können, wie viele E-Mails pro Seite in der Listenansicht (Suche/Archiv) angezeigt werden, damit ich die Ansicht an meine Bildschirmgröße bzw. meine Vorlieben anpassen kann.
## Acceptance Criteria
- [ ] Im Usermenü unter "Profil & Einstellungen" gibt es ein neues Auswahlfeld "Einträge pro Seite" mit den Optionen 25 / 50 / 100 / 200 (Default: 25)
- [ ] Die Auswahl wird über `PATCH /api/auth/preferences` gespeichert (persistiert in `users.list_page_size`)
- [ ] Beim Laden der Mail-Listenansicht (`/search`) wird die gespeicherte Seitengröße verwendet (statt der festen `PAGE_SIZE = 25`)
- [ ] Bestehende Nutzer ohne gespeicherten Wert erhalten weiterhin 25 (Default)
- [ ] Pagination ("Zurück"/"Weiter", "Seite X von Y") funktioniert korrekt mit allen Werten
## Edge Cases
- Nutzer ändert die Einstellung während eine Suche aktiv ist → nächste Suche/Seite-1-Reload verwendet neuen Wert
- Ungültiger/manipulierter Wert im Request (außerhalb 25/50/100/200) → Backend lehnt ab oder fällt auf Default zurück
- Nutzer ohne gesetzten Wert (bestehende Accounts, Spalte NULL) → Default 25
## Technical Requirements (optional)
- Persistenz: neue Spalte `users.list_page_size` (INT, Default 25)
- Security: Endpoint erfordert eingeloggte Session (analog `PATCH /api/auth/email`)
---
## Tech Design (Solution Architect)
_Übersprungen auf Wunsch des Nutzers — direkte Umsetzung._
Kurzentscheidung (statt vollem Architektur-Dokument):
- Neue Spalte `users.list_page_size INT DEFAULT 25` via `ALTER TABLE ... ADD COLUMN IF NOT EXISTS` in `internal/userstore/userstore.go`
- Neuer Endpoint `PATCH /api/auth/preferences` (Body: `{"list_page_size": 25|50|100|200}`), serverseitige Validierung gegen erlaubte Werte
- `GET /api/auth/me` (bzw. Session-Response) liefert `list_page_size` mit
- Frontend: `src/app/settings/page.tsx` neues Select-Feld; `src/app/search/page.tsx` verwendet `user.list_page_size ?? 25` statt Konstante `PAGE_SIZE`
## QA Test Results
**Tested:** 2026-06-14 (Code-Review + Logikprüfung, kein lokaler Build/Live-Test möglich)
**Methode:** Statische Code-Analyse Backend (Go) + Frontend (TS/React) gegen Acceptance Criteria. Datenfluss login/me → Cache → search-Page nachverfolgt.
### Acceptance Criteria
| # | Kriterium | Status | Anmerkung |
|---|-----------|--------|-----------|
| 1 | Select "Einträge pro Seite" 25/50/100/200, Default 25 | PASS | `settings/page.tsx` Card "Listenansicht", Select mit 4 Optionen; Init aus `user.list_page_size \|\| 25` |
| 2 | Speichern via `PATCH /api/auth/preferences``users.list_page_size` | PASS | Handler + `UpdateListPageSize` + Route korrekt; serverseitige Whitelist-Validierung |
| 3 | `/search` nutzt gespeicherte Seitengröße statt fester PAGE_SIZE | PASS (mit Vorbehalt B-3) | `pageSize = user?.list_page_size ?? DEFAULT_PAGE_SIZE`, in allen 3 Suchpfaden + totalPages verwendet |
| 4 | Bestehende Nutzer ohne Wert → Default 25 | PASS | DB-Spalte `NOT NULL DEFAULT 25`; FE-Fallback `?? 25` |
| 5 | Pagination funktioniert mit allen Werten | PASS | `totalPages = ceil(total/pageSize)`, Buttons disabled-Logik korrekt |
### Bugs
**BUG-1 (Critical) — Backend kompiliert nicht: `s.db.Exec` undefiniert**
- Datei: `internal/userstore/userstore.go:112`
- `Store` hat nur das Feld `pool` (Zeile 60), aber der PROJ-46-Block ruft `s.db.Exec(...)` auf. `s.db` existiert nicht → `go build` schlägt fehl.
- Auswirkung: Gesamtes Backend baut nicht, PROJ-53 ist damit nicht deploybar/testbar. Pre-existing (uncommitteter PROJ-46-Block), aber blockiert dieses Feature.
- Reproduktion: `CGO_ENABLED=0 go build ./cmd/archivmail/` → Compile-Error `s.db undefined (type *Store has no field or method db)`.
- Fix: `s.db.Exec``s.pool.Exec`.
- Priorität: P0 (Blocker, vor jedem Deploy).
**BUG-2 (Low) — Keine Audit-Log-Eintragung für Preference-Änderung**
- Datei: `internal/api/profile_handlers.go:160`, `handleChangePreferences`.
- `handleChangePassword`/`handleChangeEmail` loggen via `s.audlog.Log`, `handleChangePreferences` nicht. Im Spec/Impl-Notes bewusst so entschieden ("kleine Präferenz"). Konsistent mit Projektregel "alle schreibenden Operationen loggen"? Grenzfall — dokumentiert, kein Blocker.
- Priorität: P3 (akzeptabel, da bewusste Designentscheidung).
**BUG-3 (Low/UX) — Seitengrößen-Änderung resettet Suche/Seite nicht sofort, nur über useEffect-Reload**
- Datei: `src/app/search/page.tsx:172-187`.
- Ändert der User die Seitengröße in `/settings`, ruft `refresh()` den Cache neu. Auf `/search` triggert der `[user, pageSize]`-useEffect dann einen Reload auf **Seite 1, ohne aktive Filter** (der Effekt ruft `searchEmails({page:1,...})` ohne query/from/to). War der User mitten in einer gefilterten Suche, gehen die Filter beim erneuten Mount verloren. In der Praxis erfolgt die Änderung auf einer anderen Route (`/settings`), beim Zurücknavigieren ist Reset auf Seite 1 akzeptabel und entspricht Edge-Case-Doku ("nächste Suche/Seite-1-Reload"). Kein Blocker.
- Priorität: P3.
**HINWEIS (Type) — `MeResponse` enthält kein `id`/`active`, `useAuth().user` ist `MeResponse`**
- `src/app/search/page.tsx` greift nur auf `user.list_page_size`, `user.role`, `user.username` zu — alle in `MeResponse` vorhanden. Kein Typfehler. OK.
### Security-Audit
- **Validierung Manipulation (Edge Case):** `allowedListPageSizes` Whitelist im Handler → ungültige Werte (0, 999, negativ, 1000000) ergeben 400. PASS. Kein Risiko für Resource-Exhaustion über überhöhte page_size, da Backend nur Whitelist-Werte akzeptiert. ABER: prüfen, ob der `/api/search`-Handler `page_size` aus dem Request **selbst** ebenfalls cappt — der FE sendet zwar nur Whitelist-Werte, aber ein direkter API-Aufruf mit `page_size=1000000` könnte ungekappt durchgehen (nicht Teil von PROJ-53, aber relevant). EMPFEHLUNG: serverseitiges Cap im Search-Handler verifizieren.
- **Auth:** `PATCH /api/auth/preferences` ist via `s.auth(...)` geschützt; Handler prüft zusätzlich `sess.UserID == 0`. Update nur für eigene `user.ID` (aus Session-Username geladen) — keine IDOR. PASS.
- **Tenant-Isolation:** Nicht betroffen (rein UI-Präferenz pro User). PASS.
### Regression
- `scanUser`/`scanUserRow`/`VerifyPassword`/`UpsertLDAPUser`/alle SELECTs konsistent um `list_page_size` erweitert — keine Spaltenanzahl-Mismatches gefunden. PASS.
- login/me-Response um Feld erweitert, additiv — keine Breaking-Changes für bestehende FE-Konsumenten.
### Production-Ready: NEIN
Blocker BUG-1 (Backend kompiliert nicht) muss vor Deploy gefixt werden (Backend Developer). Danach Live-Smoke-Test auf 192.168.1.132 empfohlen: Migration prüfen (`\d users`), `PATCH /api/auth/preferences` mit gültigem/ungültigem Wert, Pagination mit 200 Einträgen.
### Fixes nach QA (2026-06-14)
- **BUG-1 (Critical, P0) — FIXED:** `internal/userstore/userstore.go:112``s.db.Exec``s.pool.Exec` (PROJ-46-Block). Backend kompiliert wieder.
- **Security-Empfehlung — FIXED:** `internal/api/search_handlers.go``page_size`-Query-Parameter wird jetzt serverseitig auf max. 500 gecappt (Resource-Exhaustion-Schutz, unabhängig vom Frontend-Whitelist 25/50/100/200).
- BUG-2 (kein Audit-Log) und BUG-3 (Filter-Reset bei Settings-Wechsel) bleiben als akzeptierte Designentscheidungen (P3, kein Blocker).
## Deployment
**Deployed:** 2026-06-14
- Test: 192.168.1.132 — via `update.sh` (commit 4c20a00, dann 776dee8 für PROJ-54), Backend+Frontend laufen, Manticore-Reindex 16177/16177 (0 Fehler), Backfill cc_addr/bcc_addr abgeschlossen.
- Produktion: 192.168.1.131 — via `update.sh` (commit 62693fa), Backend+Frontend laufen, Manticore-Reindex 129/129 (0 Fehler).
## Implementation Notes (Backend)
- `internal/userstore/userstore.go`: Migration `ALTER TABLE users ADD COLUMN IF NOT EXISTS list_page_size INT NOT NULL DEFAULT 25` (in `initSchema`); `User.ListPageSize int` Feld; alle SELECT/RETURNING-Queries und `scanUser`/`scanUserRow`/`VerifyPassword`/`UpsertLDAPUser` um `list_page_size` erweitert; neue Methode `UpdateListPageSize(ctx, userID, pageSize)`.
- `internal/api/profile_handlers.go`: neuer Handler `handleChangePreferences` (PATCH /api/auth/preferences), validiert gegen `allowedListPageSizes` (25/50/100/200), 400 bei ungültigem Wert, 200 mit `{"ok":true,"list_page_size":N}`. Kein Audit-Log (bewusst, kleine Präferenz).
- `internal/api/server.go`: Route `PATCH /api/auth/preferences` registriert (auth-geschützt).
- `internal/api/auth_handlers.go`: `handleLogin`- und `handleMe`-Responses liefern zusätzlich `list_page_size`.
## Implementation Notes (Frontend)
- `src/lib/api/users.ts`: `MeResponse` um `list_page_size: number` erweitert; neue Funktion `updatePreferences(listPageSize)``PATCH /api/auth/preferences`.
- `src/lib/api/index.ts`: `updatePreferences` re-exportiert.
- `src/hooks/useAuth.ts`: neue `refresh()`-Funktion (refetch `/api/auth/me`, aktualisiert Cache + State) zurückgegeben.
- `src/app/settings/page.tsx`: neue Card "Listenansicht" mit Select "Einträge pro Seite" (25/50/100/200), initialisiert aus `user.list_page_size`, ruft bei Änderung `updatePreferences()` auf, zeigt Erfolg/Fehler via `Alert`, danach `refresh()` für sofortige Wirkung.
- `src/app/search/page.tsx`: Konstante `PAGE_SIZE` entfernt, ersetzt durch `pageSize = user?.list_page_size ?? DEFAULT_PAGE_SIZE` (DEFAULT_PAGE_SIZE = 25). Verwendet in `doSearch`, initialem Such-`useEffect`, `handleApplySavedSearch` und `totalPages`-Berechnung.
+100
View File
@@ -0,0 +1,100 @@
# PROJ-54: Fix Listenansicht/Pagination für Rolle "user" (Nachbesserung PROJ-6/PROJ-21)
## Status: Deployed
**Created:** 2026-06-14
**Last Updated:** 2026-06-14
## Dependencies
- PROJ-6 (Volltext-Suche & Filterung)
- PROJ-21 (Multi-Tenancy)
- PROJ-50 (liefert `index.SearchRequest.AnyAddress` + `cc_addr`/`bcc_addr` Indexfelder, hier wiederverwendet)
## Hintergrund / Bug
In `internal/api/search_handlers.go` (`handleSearch`) wird für Nutzer mit Rolle `user` nach dem Such-/Pagination-Query (LIMIT/OFFSET) zusätzlich pro Treffer per `mailBelongsToUser()` gefiltert (nur Mails, in denen der Nutzer als From/To/CC vorkommt).
Folgen:
1. `result.Total` (z.B. 16141, Gesamtzahl im Tenant/Index) wird nicht an die gefilterte Treffermenge angepasst → Frontend zeigt z.B. "16141 E-Mails im Archiv", die Tabelle aber nur 3 Zeilen.
2. Pagination ist falsch: LIMIT/OFFSET wirkt auf die ungefilterte Menge, die Filterung danach pro Seite — `totalPages` ist falsch, einzelne Seiten können fast leer sein, obwohl der Nutzer mehr eigene Mails hat.
## User Stories
- Als normaler Nutzer möchte ich, dass die angezeigte Gesamtzahl und die Seitenanzahl in der Listenansicht zu meinen tatsächlich sichtbaren E-Mails passen.
- Als normaler Nutzer möchte ich, dass jede Seite mit der eingestellten Anzahl (z.B. 25) Treffer gefüllt ist, solange genug eigene Mails vorhanden sind.
## Acceptance Criteria
- [ ] Für Rolle `user`: die Index-Suche filtert bereits serverseitig auf Mails, in denen die Nutzer-E-Mail in From/To/Cc/Bcc vorkommt (`index.SearchRequest.AnyAddress`), VOR LIMIT/OFFSET.
- [ ] `total` in der API-Antwort entspricht der gefilterten Treffermenge (Anzahl der für den Nutzer sichtbaren Mails), nicht der globalen/Tenant-Gesamtzahl.
- [ ] Pagination (`totalPages`, "Seite X von Y") ist für Rolle `user` korrekt — jede Seite ist (bis auf die letzte) vollständig mit `page_size` Treffern gefüllt, sofern genug eigene Mails existieren.
- [ ] Rollen admin/superadmin/domain_admin/auditor (kein `userEmailFilter`) verhalten sich unverändert.
- [ ] Bestehende Volltext-Query (`q`) und Filter (from/to/date/has_attachment) funktionieren weiterhin in Kombination mit der User-Einschränkung (AND-Verknüpfung).
## Edge Cases
- Nutzer ist nur per CC/BCC in einer Mail, die vor dem PROJ-50-Reindex archiviert wurde (cc_addr/bcc_addr im Index noch leer) → Mail erscheint ggf. erst nach `archivmail reindex` in den Ergebnissen. Dokumentiert als bekannte Einschränkung, kein Blocker (vorher war das Verhalten ohnehin inkonsistent durch die kaputte Pagination).
- `sess.Email` leer → wie bisher: `total: 0, hits: []` (fail-safe).
- Mail nicht parsbar (`mailparser.Parse` Fehler) → bleibt im Ergebnis (Index-Filter hat bereits entschieden), nur Anreicherung (From/To/Subject/...) bleibt leer.
## Technical Requirements (optional)
- Keine neue Migration nötig (cc_addr/bcc_addr existieren bereits aus PROJ-50-Vorarbeit).
- Reindex (`archivmail reindex`) empfohlen, damit cc_addr/bcc_addr für Bestandsmails befüllt sind.
---
## Tech Design (Solution Architect)
_Übersprungen auf Wunsch des Nutzers — direkte Umsetzung (Bugfix)._
Fix in `internal/api/search_handlers.go`:
- `userEmailFilter`-Ermittlung nach vorne verschieben (vor den `searchIdx.Search(req)`-Aufruf) und `req.AnyAddress = userEmailFilter` setzen, wenn Rolle `user`.
- Den bisherigen Post-Filter-Block ("User isolation: skip mails the user is not involved in" + "If mail can't be parsed, deny access") entfernen — Filterung passiert jetzt im Index vor LIMIT/OFFSET.
- `mailBelongsToUser()` bleibt unverändert (wird an anderen Stellen weiter verwendet: Export, eDiscovery, Threads, OCR).
## QA Test Results
**Tested:** 2026-06-14 — Code-Review + Logikprüfung (kein lokaler Go-Build möglich, keine Live-Tests durchgeführt).
**Methode:** statische Analyse von `internal/api/search_handlers.go` (`handleSearch`), `internal/index/manticore.go` (`Search`, `escapeManticoreMatch`), `internal/index/index.go`. Diff gegen HEAD verifiziert.
### Acceptance Criteria
| # | Kriterium | Ergebnis |
|---|-----------|----------|
| 1 | User-Rolle filtert serverseitig via `AnyAddress` VOR LIMIT/OFFSET | PASS |
| 2 | `total` entspricht gefilterter Treffermenge | PASS |
| 3 | Pagination/`totalPages` korrekt, Seiten gefüllt | PASS |
| 4 | admin/superadmin/domain_admin/auditor unverändert | PASS |
| 5 | Volltext-Query + Filter AND-verknüpft mit User-Einschränkung | PASS |
**AC1 — PASS:** `req.AnyAddress = userEmailFilter` wird in Zeile ~101 gesetzt, also VOR `searchIdx.Search(req)` (Zeile ~116). In `manticore.go` Zeile 292-294 wird `AnyAddress` in `@(from_addr,to_addr,cc_addr,bcc_addr)` als MATCH-Part eingefügt. COUNT (Zeile 330-335) und SELECT mit LIMIT/OFFSET (Zeile 371-377) nutzen beide dasselbe `whereClause` inkl. dieses MATCH. Filterung wirkt damit korrekt vor der Paginierung.
**AC2 — PASS:** `total` stammt aus dem COUNT-Query, der denselben MATCH-Filter enthält → zählt nur Mails des Nutzers. Der alte, `total`-verfälschende Post-Filter-Block ist laut Diff vollständig entfernt (sowohl der `continue` bei `!mailBelongsToUser` als auch der `continue` bei Parse-Fehler).
**AC3 — PASS:** Da der Index-Filter vor LIMIT/OFFSET greift, liefert jede Seite bis zur letzten genau `page_size` Treffer. `total` und Seitenzahl sind konsistent.
**AC4 — PASS:** Der neue Block ist strikt auf `sess.Role == userstore.RoleUser` beschränkt. Für alle anderen Rollen bleibt `req.AnyAddress` leer → MATCH-Part wird nicht erzeugt (Zeile 292 `if req.AnyAddress != ""`). Tenant-Filterung (Zeile 104-141) und Auditor-Filterung (Zeile 167-179, 207-212) sind unverändert. Wichtig: Rolle `user` mit zugewiesenem Tenant nutzt weiterhin entweder den Per-Tenant-Index (`usedTenantIndex`) oder den Fallback-Post-Filter (Zeile 125-141) — der `AnyAddress`-Filter wirkt zusätzlich, nicht ersetzend. Korrekte AND-Semantik (Tenant ∩ eigene Mails).
**AC5 — PASS:** Alle MATCH-Parts (`Query`, `From`, `To`, `OwnEmail`, `AnyAddress`) werden mit Leerzeichen zu einem MATCH-String verkettet (Zeile 296-303), was in Manticore implizit AND ist. Date/HasAttachment sind separate WHERE-AND-Bedingungen. AND-Verknüpfung bestätigt.
### Security-Audit (Red Team)
- **Manticore-Injection:** PASS. `AnyAddress` läuft durch `escapeManticoreMatch()` (Zeile 293), identisch zu den bereits deployten `From`/`To`/`OwnEmail`-Filtern. `@` ist in der `specials`-Liste enthalten und wird escaped, sodass das User-`@(...)`-Feldpräfix nicht durch User-Input gefälscht werden kann.
- **Auth/Tenant-Isolation:** PASS. Quelle ist `sess.Email` aus dem JWT, kein Request-Parameter → nicht manipulierbar. Tenant-Isolation bleibt durch die unveränderten Pfade (Per-Tenant-Index / Fallback) erhalten.
- **Fail-safe:** PASS. Leeres `sess.Email``total:0, hits:[]` (Edge Case aus Spec erfüllt).
### Bugs / Hinweise
- **Hinweis (Low, keine Regression — Verhalten dokumentiert):** Edge Case CC/BCC-only-Mails vor PROJ-50-Reindex erscheinen erst nach `archivmail reindex`. In Spec als bekannte Einschränkung dokumentiert.
- **Beobachtung (Low, kein Bug):** Der ursprüngliche Post-Filter nutzte `mailBelongsToUser()`, das From/To/CC prüfte (kein BCC). Der neue Index-Filter deckt zusätzlich `bcc_addr` ab — semantische Erweiterung, konsistent mit AC1 ("From/To/Cc/Bcc"). Für `handleGetMail`/`handleGetRaw`/`handleGetAttachment` bleibt `mailBelongsToUser()` (ohne BCC) der Zugriffs-Gate. Folge: Eine Mail, in der der Nutzer NUR per BCC vorkommt, erscheint künftig in der Liste, der Detail-Aufruf liefert dann aber 403. Inkonsistenz zwischen Listentreffer und Detailzugriff. Severity Low (BCC im archivierten Mailverkehr selten sichtbar; kein Datenleck, eher das Gegenteil). Empfehlung an Backend: entweder `mailBelongsToUser()` um CC/BCC-Konsistenz erweitern oder BCC aus dem Listen-Filter ausschließen, damit Liste und Detailzugriff deckungsgleich sind.
- **Kein Critical/High gefunden.** Keine Regressionen für admin/superadmin/domain_admin/auditor erkennbar.
### Regression (Code-Review)
- Andere Nutzer von `mailBelongsToUser()` (Export, eDiscovery, Threads, OCR, `handleGetMail/Raw/Attachment`) bleiben laut Diff unberührt — Funktion selbst unverändert. PASS.
### Production-Ready: JA (mit Vorbehalt)
Keine Critical/High-Bugs. Empfehlungen vor Deploy:
1. `archivmail reindex` auf Testserver ausführen (cc_addr/bcc_addr für Bestand befüllen), dann Live-Verifikation der AC mit echtem `user`-Account (Total = sichtbare Mails, gefüllte Seiten).
2. Low-Inkonsistenz Liste↔Detail bei reinen BCC-Mails als Folge-Ticket für Backend einplanen (nicht blockierend).
## Deployment
**Deployed:** 2026-06-14
- Test: 192.168.1.132 — via `update.sh` (commit 776dee8), Backend+Frontend laufen, Manticore-Reindex 16177/16177 (0 Fehler), Backfill cc_addr/bcc_addr (1 nachindiziert, 0 Fehler).
- Produktion: 192.168.1.131 — via `update.sh` (commit 62693fa), Backend+Frontend laufen, Manticore-Reindex 129/129 (0 Fehler).
- Hinweis: Live-Smoke-Test mit echtem "user"-Account (Total/Pagination) konnte mangels Test-Credentials nicht automatisiert durchgeführt werden — vom Nutzer als "aktuell" bestätigt.