feat(174): Mail-Review-Session #189

Merged
admin-mrrm merged 17 commits from feat-174-mail-review-session into main 2026-05-04 23:11:13 +02:00
Owner

Summary

  • Neuer Endpoint GET /mail/review-queue liefert Mails mit unbestätigten (suggested) Tags aus der DB (kein IMAP)
  • Web + Mobile: Session-Screen mit Batch-Auswahl, Karte-für-Karte-Review, Fortschrittsanzeige
  • Aktionen: Bestätigen / Anpassen (Freitext oder bestehende Tags wählen) / Überspringen
  • Review-Button mit offener Anzahl in der Mail-Übersicht (Web + Mobile)

Test plan

  • Mails mit vorgeschlagenen Tags öffnen → Review starten
  • Bestätigen: Tag wird confirmed, verschwindet aus der Queue
  • Anpassen (gleicher Name): verhält sich wie Bestätigen
  • Anpassen (anderer Name): alten Tag entfernen, neuen als confirmed anlegen
  • Überspringen: Mail kommt beim nächsten Start wieder in die Queue
  • Batch-Auswahl (10 / 50 / Alle) begrenzt korrekt
  • Abschlusscreen zeigt korrekte Statistik

Closes #174

🤖 Generated with Claude Code

## Summary - Neuer Endpoint `GET /mail/review-queue` liefert Mails mit unbestätigten (`suggested`) Tags aus der DB (kein IMAP) - Web + Mobile: Session-Screen mit Batch-Auswahl, Karte-für-Karte-Review, Fortschrittsanzeige - Aktionen: **Bestätigen** / **Anpassen** (Freitext oder bestehende Tags wählen) / **Überspringen** - Review-Button mit offener Anzahl in der Mail-Übersicht (Web + Mobile) ## Test plan - [ ] Mails mit vorgeschlagenen Tags öffnen → Review starten - [ ] Bestätigen: Tag wird `confirmed`, verschwindet aus der Queue - [ ] Anpassen (gleicher Name): verhält sich wie Bestätigen - [ ] Anpassen (anderer Name): alten Tag entfernen, neuen als `confirmed` anlegen - [ ] Überspringen: Mail kommt beim nächsten Start wieder in die Queue - [ ] Batch-Auswahl (10 / 50 / Alle) begrenzt korrekt - [ ] Abschlusscreen zeigt korrekte Statistik Closes #174 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(174): Mail-Review-Session — interaktiver Tag-Bestätigungs-Flow
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
72be471661
- API: GET /mail/review-queue gibt Mails mit suggested-Tags zurück (alle
  Accounts des Users, kein IMAP nötig, nur DB-Join)
- api-client: ReviewQueueItem/Tag/Queue-Typen + getReviewQueue()-Methode
- Web + Mobile: Review-Screen mit Start → Karte-für-Karte → Abschluss
  · Batch-Auswahl (10 / 50 / Alle)
  · Bestätigen / Anpassen (Freitext + vorhandene Tags) / Überspringen
  · Fortschrittsanzeige (n / gesamt)
- Mail-Übersicht (Web + Mobile): Review-Button mit Anzahl offener Items

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(mobile): Batch-Categorizer nutzte msg.id statt msg.uid
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
5a99e6ab0f
listMessages gibt MessageEnvelope zurück (uid: number, kein DB-UUID id).
Der Categorizer hat msg.id aufgerufen was immer undefined war und alle
assignTag-Calls lautlos fehlschlagen ließ.

Fix:
- listMessageTagsByUids prüft zuerst welche UIDs schon Tags haben (nur DB)
- getMessage holt für ungetaggte UIDs Body + DB-UUID via IMAP (cacht in DB)
- assignTag bekommt jetzt die korrekte msg.id (UUID)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat(174): Ordner-Kategorisierung — manueller Trigger pro Ordner
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
90fec2ec49
- categorizeFolderItems() als reine Funktion exportiert (nimmt suggest als
  Parameter, nutzbar von Batch-Job und manuellem Hook)
- useCategorizeFolder()-Hook (web + mobile): ruft Model + API, tracked
  Fortschritt und Ergebnis
- Mobile Ordnerliste: "Kategorisieren"-Button pro Zeile mit Spinner,
  Fortschrittsanzeige und Ergebnis ("X von Y neu getaggt")
- Web Ordnerliste: identisches UI

Kategorisiert bis zu 30 Mails pro Lauf (erste Seite IMAP). Mails die
bereits Tags haben werden übersprungen (DB-only-Check, kein IMAP).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(174): Spinner nur beim angeklickten Ordner anzeigen
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
03d365d13c
activeFolder-State trackt welcher Ordner gerade läuft; isRunning/isDisabled
werden pro Zeile separat berechnet statt global weitergegeben.
Ergebnisse werden per Ordner-Name in results-Map gespeichert.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(174): Kategorisierungs-Limit auf 50 Mails erhöhen
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
5ad7068e75
fix(174): Kategorisierung lädt Seiten bis 50 wirklich neue Tags vergeben
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
36cfed505a
Statt fix 50 Mails zu laden, werden nun seitenweise (je 30) Mails geholt
und nur ungtaggte verarbeitet — so lange bis 50 Mails erfolgreich
kategorisiert wurden oder keine weiteren Mails vorhanden sind.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat(174): Hang-Erkennung + Abbrechen für Ordner-Kategorisierung
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
87ba2a51d3
- withTimeout(): 30s für IMAP-Fetch, 90s für LLM-Inference — hängende
  Mails werden übersprungen statt alles zu blockieren
- cancelRef: Abbrechen-Button stoppt den Loop nach der aktuellen Mail
- lastActivityAt + Sekundentimer: zeigt wie lange keine Aktivität
  - ab 5s: "Letzte Aktivität vor Xs"
  - ab 20s: orangener Spinner + "möglicherweise aufgehängt"
- Fortschrittsanzeige: "X / 50 kategorisiert…"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(174): Kategorisierung vor Start auf Modell-Bereitschaft prüfen
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
817a98d937
suggest() wartet intern auf Download + Init des Llama-Modells (1,8 GB).
Ohne Check lief der 90s-Timeout für jede Mail während des Downloads
→ bei 50 Mails bis zu 75 Min. scheinbares Hängen.

Fix: isReady-Check am Anfang von run(). Klare Fehlermeldung:
- "Modell wird noch heruntergeladen" wenn Download läuft
- "Bitte zuerst eine Mail im Reader öffnen" sonst

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(174): Fortschrittsanzeige auch bei blockierten Tags + Logging
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
99cb6b4bb9
onProgress feuert jetzt bei jeder geprüften Mail (nicht nur bei
erfolgreich kategorisierten), sodass der Aktivitäts-Timer nicht fälschlich
'aufgehängt' anzeigt wenn alle Vorschläge geblockt sind.

Anzeige: 'X kategorisiert, Y geprüft' statt 'X / 50'.
Logging: zeigt welche Tags durch den Rejection-Filter geblockt wurden.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Neue Seite /mail/blocked-tags listet alle abgelehnten Kategorien mit
Ablehnungszähler und ermöglicht einzelnes oder vollständiges Zurücksetzen.
Link am Ende jeder Ordnerliste + im Fehlerfall sichtbar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Chip antippen ersetzt den vorgeschlagenen Tag sofort, ohne danach noch ✓
drücken zu müssen. Bei freigetipptem Namen der noch nicht existiert erscheint
ein "neu erstellen"-Button (web + mobile).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Mail mit mehreren vorgeschlagenen Tags zeigt alle anderen als gelbe Chips
  im Anpassen-Modus (z.B. Newsletter wenn Werbung der Haupt-Tag ist)
- Tippen auf solche Chips bestätigt den gewünschten Tag direkt
- assignTag im Backend nutzt onConflictDoUpdate statt plain INSERT
  (verhindert unique-constraint-Fehler wenn Tag bereits zugewiesen ist)
- Fehler werden jetzt rot unterhalb des Eingabefelds angezeigt
- "neu erstellen"-Button blendet sich aus wenn der Name bereits in item.tags

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Statt nur item.tags[0] werden jetzt alle Tags als gelbe Chips angezeigt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Kein view/adjust-Modus mehr. Alle vorgeschlagenen Tags als Chips:
- Grün ✓ = wird bestätigt (Standard)
- Rot ✕ = wird entfernt (antippen zum Umschalten)
- Blau = manuell hinzugefügt (antippen zum Entfernen)
Separates Eingabefeld mit Autovervollständigung für neue Tags.
"Übernehmen" verarbeitet alle Entscheidungen auf einmal.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend wendet cleanSnippet auf bodyText an bevor es im Review ausgegeben
wird — URLs, HTML-Entities und Trennzeichen werden gefiltert.
Frontend zeigt den vollen Text ohne Zeilenlimit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat(174): Review zeigt Absender + Body (HTML-Fallback wenn kein plain text)
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
314d15a30c
- fromAddr wird jetzt im Review-Item mitgeliefert und angezeigt
- Wenn bodyText null ist, wird bodyHtml zu Plain-Text konvertiert (Tags
  und Style/Script-Blöcke entfernt) und als Snippet verwendet
- "Kein Textinhalt verfügbar" wenn beides fehlt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(test): onConflictDoUpdate im DB-Mock ergänzen
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
0d5961620e
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
admin-mrrm deleted branch feat-174-mail-review-session 2026-05-04 23:11:13 +02:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
admin-mrrm/mrrmlabapp!189
No description provided.