# V3.1 – UX-Polish und Verlauf-Tab-Reife **Status:** Zur Implementierung freigegeben **Erstellt:** 2026-05-05 **Überarbeitet:** 2026-05-05 (nach ChatGPT-Review Runden 1, 2 und 3) **Autor:** Marcus (mit Claude als Mentor) --- ## Ziel V3.1 ist der konsequente Nachschlag zu V3.0: Was der Produkttest aufgedeckt hat, wird hier bereinigt. Kein großes Architektur-Feature, kein neues Maven-Modul – **gezielter UX-Schliff und Robustheit**. Schwerpunkte: 1. **Polieren** – sichtbare Schwächen aus dem V3.0-Produkttest beheben (#77, #80, #81, #83, #84, #88, #91) 2. **Verlauf-Tab reifen lassen** – Suche, Mehrfachauswahl, DB-Neuanlage (#82, #86, #87) 3. **Quick Win** – Mausrad-Zoom im PDF-Viewer als kleiner, wertvoller Gebrauchskomfort (#32) Die fachliche Kernverarbeitung bleibt vollständig unverändert. --- ## Einordnung V3.0 ist der abgeschlossene Ausgangspunkt. Hexagonale Architektur, Modulstruktur, headless-Betrieb, `.properties`-Konfigurationswahrheit und Flyway-DB-Evolution bleiben unangetastet. V3.1 fügt **kein neues Maven-Modul** hinzu. **Headless-Betrieb:** Der `adapter-in-cli`-Pfad erhält keine neue Bedienfunktion. Er ist jedoch von der globalen Lock-File-Pfadauflösung (#91) und einer ggf. notwendigen Flyway-Schemamigration (#88) betroffen – beide Änderungen wirken beim Programmstart, unabhängig von GUI oder CLI. --- ## Scope ### In V3.1 enthalten | # | Thema | Kategorie | |---|---|---| | #77 | Fehlende Tooltips | UX | | #80 | Dirty-Indikator für Konfigurations-Tab | UX | | #81 | Enum-Werte statt deutscher Bezeichnungen (Status-ComboBox + Versuche-Tabelle) | UX | | #82 | Verlauf-Tab: Live-Filter bei Suche | GUI | | #83 | KI-Begründung bei SUCCESS-Versuch verwirrend leer | UX | | #84 | Aktionsbuttons nach Laufende nicht sofort reaktiviert | Bug | | #86 | Mehrfachauswahl im Verlauf-Tab (Strg+A, Strg+Klick, Shift+Klick) | GUI | | #87 | Neue leere SQLite-Datenbank anlegen | GUI | | #88 | FAILED_FINAL-Einträge zeigen keine Fehlerursache im Verlauf-Tab | UX | | #91 | Lock-File relativer Pfad – Fallback wie Log-Verzeichnis | Robustheit | | #32 | Mausrad-Zoom in PDF-Vorschau | GUI | ### Explizit nicht in V3.1 - Automatischer Scheduler / Quellordner-Überwachung (#22) → V3.x - PDF-Viewer Render-DPI (#23) → V3.2 - F1-Hilfe (#69) → V3.2 - Dark Mode (#70) → V3.x - Log-Viewer in der GUI (#72) → V3.2 - Token- und Kosten-Tracking (#74) → V3.2 - Excel-Export (#75) → V3.2 - Automatische Update-Prüfung (#76) → V3.2 - Änderung der fachlichen Kernverarbeitung - Neue Maven-Module, neue KI-Provider, Architekturbrüche --- ## Unverrückbare Leitplanken (unverändert gegenüber V3.0) - Java 21, Maven Multi-Module, hexagonale Architektur - Shade-JAR als primäres Distributionsartefakt - GUI ist Standardstart, `--headless` bleibt vollständig erhalten - `.properties` bleibt die einzige Konfigurationswahrheit - Kein Webserver, kein Applikationsserver - GUI offiziell nur unter Windows; headless für Windows Server / Task Scheduler - JavaFX-Threading: I/O auf Worker-Thread, UI-Updates via `Platform.runLater()` - Kein JavaFX in Domain oder Application - JavaDoc-Standard für alle neuen öffentlichen Ports, Use-Cases, DTOs und Adapter-Methoden - Notwendige Code-Kommentare auf Deutsch; Logging auf Deutsch - Flyway ist die einzige Schema-Evolutionsquelle (kein manuelles DDL im Code) --- ## Status-Mapping-Tabelle (unverändert gegenüber V3.0) Diese Tabelle ist weiterhin die einzige autoritative Quelle für Status-Darstellung in der GUI. Sie gilt verbindlich für alle V3.1-Features, die Statuswerte anzeigen – insbesondere #81 (Status-ComboBox, Versuche-Tabelle). **Alle acht Statuswerte müssen vollständig unterstützt werden.** Kein Enum-Rohname darf für Endnutzer sichtbar sein. | Domain-Status (`ProcessingStatus`) | GUI-Icon | Farbe | GUI-Text (Tooltip) | Summary-Kategorie | |---|---|---|---|---| | `SUCCESS` | `✓` | Grün | „Erfolgreich verarbeitet und umbenannt." | erfolgreich | | `FAILED_RETRYABLE` | `↻` | Orange | „Temporärer Fehler – wird beim nächsten Lauf automatisch erneut versucht." | wird wiederholt | | `FAILED_FINAL` | `×` | Rot | „Dauerhaft nicht verarbeitbar – z. B. kein Textinhalt (Foto-PDF), Passwortschutz oder beschädigte Datei. Kein weiterer automatischer Versuch." | fehlgeschlagen | | `SKIPPED_ALREADY_PROCESSED` | `≡` | Grau | „Übersprungen – wurde bereits in einem früheren Lauf erfolgreich verarbeitet." | übersprungen | | `SKIPPED_FINAL_FAILURE` | `⊘` | Dunkelgrau | „Endgültig übersprungen nach wiederholten Fehlern." | endgültig übersprungen | | `READY_FOR_AI` | `⟳` | Blau | „Wartet auf Verarbeitung." | – | | `PROPOSAL_READY` | `◇` | Hellblau | „KI-Vorschlag liegt vor, wartet auf Bestätigung." | – | | `PROCESSING` | `▶` | Hellgrau | „Wird gerade verarbeitet." | – | **Wichtig:** Farbe ist niemals das einzige Unterscheidungsmerkmal. Icon und Tooltip-Text müssen den Status allein eindeutig beschreiben. --- ## UX-Polishing-Features ### #77 – Fehlende Tooltips #### Problem Der V3.0-Produkttest hat GUI-Elemente identifiziert, die noch keinen Tooltip tragen. Die Infrastruktur (`GuiTooltipTexts`, `setTooltip()`) existiert bereits aus #66 – es fehlt nur die konsequente Anwendung. #### Lösung Vor der Implementierung führt Claude Code eine **vollständige Bestandsaufnahme** durch: Alle interaktiven Elemente auf allen Tabs werden gegen vorhandene Tooltips geprüft. Maßgeblich ist die Bestandsaufnahme – die Zahl 16 stammt aus dem Produkttest und ist nicht bindend. Werden mehr fehlende Elemente gefunden, werden alle ergänzt. Fehlende Tooltips werden in `GuiTooltipTexts` als Konstanten ergänzt und im jeweiligen GUI-Tab via `element.setTooltip(new Tooltip(GuiTooltipTexts.XY))` gesetzt. Keine hartcodierten Strings. **Tooltips auf `TableColumn`-Headern (Sonderfall JavaFX):** `TableColumn` ist kein normaler JavaFX-Node; `setTooltip()` ist darauf nicht direkt anwendbar. **Kein Skin-/Lookup-Hack.** Falls Header-Tooltips benötigt werden, wird ein `Label` als Column-Graphic gesetzt: ```java Label headerLabel = new Label("Spaltenname"); headerLabel.setTooltip(new Tooltip("Erklärungstext")); column.setGraphic(headerLabel); column.setText(""); ``` Bei der Umsetzung muss geprüft werden, dass Sortierung, Header-Breite und bestehendes CSS durch das Column-Graphic-Pattern nicht sichtbar verschlechtert werden. Falls das Projekt bereits eine stabile eigene Lösung für Column-Tooltips besitzt, wird diese wiederverwendet. **Zu prüfende Tabs und Elemente (Anhaltspunkte):** | Tab | Verdächtige Elemente | |---|---| | Verlauf | Tabellenspalten-Header, Suchfeld, Such-Button, Aktions-Buttons (Reset, Löschen) | | Verlauf (Detail) | Status-Icon, Versuche-Tabelle Spalten, KI-Begründung-Bereich | | Prompt | Speichern-Button, Zurücksetzen-Button, TextArea | | Allgemein | Fortschrittsbalken, Summary-Banner-Elemente | **Technisch:** Ausschließlich `adapter-in-gui` und `GuiTooltipTexts`. Keine Architektur-Änderungen. --- ### #80 – Dirty-Indikator für Konfigurations-Tab #### Problem Der Prompt-Tab zeigt bereits einen `*`-Dirty-Indikator im Tab-Titel und warnt beim Verlassen mit ungespeicherten Änderungen. Der Konfigurations-Tab hat dieses Verhalten nicht – Nutzer verlieren versehentlich Änderungen. #### Lösung **Dirty-State-Tracking mit Baseline-Snapshot:** Beim Laden einer Konfiguration wird ein **Baseline-Snapshot** des geladenen Zustands gespeichert. Dirty-State entsteht durch Vergleich des aktuellen Formularinhalts mit dem Snapshot – nicht durch blindes „erster Listener feuert". Während programmgesteuertem Laden oder Normalisieren von Feldinhalten wird Dirty-Tracking temporär unterdrückt (Flag `loadingInProgress`), damit programmatische Feldänderungen keinen unechten Dirty-State auslösen. - Beim ersten echten Nutzerwechsel gegenüber dem Snapshot: Tab-Titel wechselt auf `* Konfiguration` - Dirty-Flag wird zurückgesetzt bei: Speichern, Speichern unter, Laden einer neuen Konfiguration (nach Bestätigungsdialog) **Bestätigungsdialog bei Navigation mit Dirty State:** Beim Laden einer neuen Konfiguration oder beim Schließen der Anwendung mit ungespeicherten Konfig-Änderungen: > „Die Konfiguration enthält ungespeicherte Änderungen. Jetzt speichern?" > [Speichern] [Verwerfen] [Abbrechen] **Kopplung mit #87 (Neue Datenbank):** Legt der Nutzer über „Neue Datenbank anlegen..." eine neue DB-Datei an, wird der DB-Pfad im Konfigurationsmodell geändert und der Konfig-Tab in den Dirty-State versetzt. Der bestehende Bestätigungsdialog greift beim nächsten Schließen oder Ladevorgang. **UX-Konsistenz mit Prompt-Tab:** Die UX muss identisch zum Prompt-Tab sein: Sternchen im Tab-Titel, Warn-/Speicherdialog beim Verlassen, Rücksetzen nach Speichern. Die **technische Umsetzung** darf im Konfig-Tab über Baseline-Snapshot und `loadingInProgress` erfolgen, wenn die komplexere Formularlogik das erfordert. **Technisch:** Ausschließlich `adapter-in-gui`. Kein neuer Port, kein Use-Case. --- ### #81 – Enum-Werte statt deutscher Bezeichnungen #### Problem Die Status-ComboBox im Verlauf-Tab zeigt rohe Enum-Namen (`READY_FOR_AI`, `FAILED_FINAL` etc.). Die Versuche-Tabelle im Detailbereich zeigt ebenfalls Enum-Rohnamen in der Status-Spalte. Das ist für Endnutzer unlesbar. #### Lösung **Anzeige-Mapping:** `ProcessingStatusPresentation` (existiert bereits aus #51) stellt die Mapping-Logik bereit. Dieses Mapping wird für alle Statusanzeigen im Verlauf-Tab verbindlich genutzt. **Alle acht Statuswerte der autoritativen Tabelle müssen abgedeckt sein:** | Enum-Wert | Angezeigter Text | |---|---| | `SUCCESS` | „✓ Erfolgreich" | | `FAILED_RETRYABLE` | „↻ Temporärer Fehler" | | `FAILED_FINAL` | „× Dauerhaft fehlgeschlagen" | | `SKIPPED_ALREADY_PROCESSED` | „≡ Bereits verarbeitet" | | `SKIPPED_FINAL_FAILURE` | „⊘ Endgültig übersprungen" | | `READY_FOR_AI` | „⟳ Wartet auf Verarbeitung" | | `PROPOSAL_READY` | „◇ Vorschlag vorhanden" | | `PROCESSING` | „▶ In Bearbeitung" | **Status-ComboBox:** - Erster Eintrag: „Alle Status" – GUI-intern als `Optional.empty()` bzw. `null`-Filter behandelt; kein Domain-Enum-Wert - Weitere Einträge: alle acht Statuswerte mit Displaytext - Intern wird für DB-Queries stets der Enum-Name verwendet - `StringConverter` implementieren **Versuche-Tabelle (Detailbereich):** - Status-Spalte: `ProcessingStatusPresentation`-Mapping anwenden - Kein Enum-Rohname darf für Endnutzer sichtbar sein **Technisch:** Ausschließlich `adapter-in-gui`. Kein neuer Port, kein Use-Case. --- ### #83 – KI-Begründung bei SUCCESS-Versuch verwirrend leer #### Problem Im Detailbereich wird bei einem Versuch mit Status `SUCCESS` die KI-Begründungs-TextArea leer angezeigt. Nutzer verstehen nicht, ob das ein Fehler ist oder ob tatsächlich keine Begründung vorliegt. #### Lösung **Platzhalter über JavaFX `promptText` (kein echter Textinhalt):** Bei leerem oder null `ai_reasoning` gilt: ```java textArea.setText(""); textArea.setPromptText("Keine KI-Begründung für diesen Versuch gespeichert."); ``` Der `promptText` wird von JavaFX automatisch gedimmt dargestellt und ist **nicht kopierbar, nicht speicherbar, nicht als Nutzdaten behandelbar**. Kein Vermischen von Daten und UI-Platzhaltertext. Die TextArea bleibt sichtbar – ein leeres Feld ohne Erklärung ist schlechter als ein erklärender Platzhalter. **Technisch:** Ausschließlich `adapter-in-gui`. Kein neuer Port, kein Use-Case, keine DB-Änderung. --- ### #84 – Aktionsbuttons nach Laufende nicht sofort reaktiviert #### Problem Nach Abschluss eines Verarbeitungslaufs bleiben die Aktionsbuttons im Verlauf-Tab („Status zurücksetzen", „Eintrag löschen") dauerhaft deaktiviert. #### Lösung **Ereignisgetriebene Button-State-Neuberechnung:** Der Button-State wird nach jedem Lauf-Terminierungsereignis neu berechnet – unabhängig vom Grund der Terminierung: - Erfolgreicher Laufabschluss - Fehlerabbruch (Exception im Worker) - Nutzerabbruch - Leerlauf (keine Dateien zu verarbeiten) Nach Terminierung wird, sofern eine Auswahl in der Verlauf-Tabelle besteht, der zugehörige Aktionsbutton-State **ereignisgetrieben** aktiviert – ohne dass der Nutzer die Auswahl erneuern oder den Tab wechseln muss. **Code-Analyse erforderlich:** Claude Code analysiert den genauen Signal-Pfad (Laufabschluss-Event → UI-Komponente) und korrigiert die fehlende `Platform.runLater()`-Kopplung. **Technisch:** Vermutlich `adapter-in-gui` und ggf. `bootstrap` (Bridge-Verdrahtung). Kein neuer Port, kein Use-Case. --- ### #88 – FAILED_FINAL ohne Fehlerursache im Verlauf-Tab #### Problem Der Detailbereich zeigt bei `FAILED_FINAL`-, `FAILED_RETRYABLE`- und `SKIPPED_FINAL_FAILURE`-Einträgen keine Fehlerursache an. Der Nutzer sieht nur den Status-Icon. #### Lösung **Schema-/Code-Analyse als blockierender erster Schritt:** Vor jeder weiteren Implementierung dokumentiert Claude Code verbindlich, welcher Fall vorliegt: **Fall A – geeignetes Fehlerfeld bereits vorhanden:** `processing_attempt` enthält bereits ein nutzbares Fehlerfeld. → Keine Migration. GUI und Abfrage werden um die Anzeige erweitert. **Fall B – kein geeignetes Fehlerfeld vorhanden:** → Flyway-Migration mit der **nächsten freien Versionsnummer** zum Zeitpunkt der Implementierung. Fehlerdetails können nur für ab V3.1 erzeugte Verarbeitungsversuche gespeichert werden. Bestehende Einträge bleiben unverändert und zeigen den Platzhalter „Keine Fehlerdetails gespeichert." **Fall C – Fehlerdetails werden bisher nur im Log gespeichert:** → Migration zwingend erforderlich. Zusätzlich muss der Fehlerpfad der Verarbeitungslogik um Persistierung der Fehlerdetails erweitert werden. **Domain-Modul-Einschränkung:** `pdf-umbenenner-domain` bleibt unverändert, sofern die benötigten Fehlerdetails ausschließlich über bestehende oder application-nahe History-DTOs transportiert werden können. Falls das fachliche Attempt-Modell im Domain-Modul liegt und für die Anzeige erweitert werden muss, ist eine **minimale Domain-Erweiterung zulässig**. Keine Änderung an der fachlichen Kernverarbeitung. **Datenmodell (bei Migration – Fall B oder C):** ```sql -- Versionsnummer = nächste freie Flyway-Version zum Zeitpunkt der Implementierung ALTER TABLE processing_attempt ADD COLUMN failure_details TEXT; ``` `failure_details` enthält eine **nutzerverständliche, gekürzte Fehlerbeschreibung**. Provider- oder Exception-Meldungen werden **nicht roh persistiert** – gespeichert wird eine kontrolliert erzeugte Kurzmeldung aus bekannten Fehlerkategorien oder eine bereinigte/gekürzte Message ohne Stacktrace, API-Keys oder vollständige Provider-Rohantworten. Die Begrenzung auf **1000 Zeichen wird spätestens vor Persistierung im DB-Adapter erzwungen**: Längere Texte werden gekürzt und mit „…" markiert. Falls bereits vorher ein zentrales Fehler-Mapping existiert, darf dort gekürzt werden. Entscheidend: in die DB gelangen nur gekürzte, bereinigte Fehlerdetails. Kein SQL-`CHECK`-Constraint (um Alt-/Importdaten nicht zu blockieren). **„Letzter Versuch" – Definition:** Die angezeigte Fehlerursache stammt aus dem Versuch mit dem höchsten `attempt_number`. Bei Gleichstand wird der mit dem jüngsten `ended_at` verwendet. Die Sortierung wird im Rahmen der Code-Analyse gegen das vorhandene Schema verifiziert. Falls `attempt_number` oder `ended_at` nicht existieren, wird die technisch eindeutige Sortierung des Attempt-Verlaufs verwendet und in der Implementierungsnotiz dokumentiert. **Anzuzeigende Status:** Fehlerursache wird angezeigt bei: - `FAILED_FINAL` - `FAILED_RETRYABLE` - `SKIPPED_FINAL_FAILURE` (zeigt die letzte bekannte Fehlerursache des zugrundeliegenden fehlgeschlagenen Attempts – fachlich konsistent, da `SKIPPED_FINAL_FAILURE` direkte Folge eines endgültigen Fehlschlags ist) Bei fehlendem `failure_details` (NULL oder leer): Platzhaltertext via `promptText` analog zu #83. **Technisch:** `adapter-in-gui` (Anzeige), ggf. `adapter-out-db` (Abfrage-Erweiterung), ggf. Flyway-Migration, ggf. minimale Domain-Erweiterung. --- ### #91 – Lock-File relativer Pfad #### Problem Der Lock-Mechanismus nutzt einen konfigurierten oder Standard-Pfad für die Lock-Datei. Bei relativem Pfad ist das Verzeichnis abhängig vom aktuellen Arbeitsverzeichnis. Liegt die JAR unter `C:\Program Files`, ist das Verzeichnis zudem nicht beschreibbar. #### Lösung **Verhalten abhängig vom Pfadtyp:** **Absolut konfigurierter Pfad:** Wird unverändert verwendet. Schlägt das Anlegen fehl, erfolgt **kein Fallback** – der Nutzer hat den Speicherort explizit vorgegeben. Start bricht mit klarer Fehlermeldung ab. **Relativer oder nicht konfigurierter (Default-)Pfad – zweistufige Fallback-Strategie:** 1. **Primär:** Auflösung relativ zum Verzeichnis der JAR-Datei (`CodeSource.getLocation()`) 2. **Fallback:** Auflösung relativ zu `user.home` 3. **Abbruch:** Erst wenn auch `user.home` fehlschlägt **Parent-Verzeichnisse** werden bei Bedarf automatisch angelegt (`Files.createDirectories()`). Der final verwendete **absolute Pfad wird beim Start geloggt** (INFO-Level): ``` Lock-Datei: C:\Users\Funny\Documents\pdf-umbenenner.lock ``` **Gilt für GUI- und Headless-Start.** **Code-Analyse erforderlich:** Claude Code ermittelt die aktuelle Lock-Implementierungslokation (`bootstrap` oder `adapter-out-db`). --- ## GUI-Features ### #82 – Verlauf-Tab: Live-Filter bei Suche #### Problem Die Suche im Verlauf-Tab wird nur durch expliziten Klick auf den Such-Button ausgelöst. Das erfordert unnötige Interaktion bei jeder Suchanpassung. #### Lösung **Live-Filter mit Debounce und Generation-Counter:** - Das Suchfeld erhält einen `ChangeListener` auf die `textProperty()` - Bei jeder Texteingabe startet ein JavaFX-`Timeline`-Debounce-Timer (300 ms) - Nach 300 ms ohne weitere Eingabe wird die DB-Abfrage auf einem Worker-Thread gestartet **Race-Condition-Schutz via Generation-Counter:** Jede gestartete Suchanfrage erhält eine aufsteigende Generations-ID (atomarer `long`-Counter). Der Worker-Thread trägt seine Generations-ID ins Ergebnis. Beim `Platform.runLater()`-Callback wird das Ergebnis nur in die UI übernommen, wenn die Generations-ID noch aktuell ist – veraltete Worker-Ergebnisse werden verworfen. **Such-Button und Enter-Taste:** - Klick auf Such-Button oder Enter im Suchfeld: Debounce-Timer sofort abgebrochen, Suche unverzüglich gestartet - Barrierefreiheit: Such-Button bleibt erhalten **Auswahlverhalten nach neuen Suchergebnissen:** Nach jeder Übernahme neuer Suchergebnisse wird die Tabellenauswahl **vollständig geleert**. Detailbereich und Aktionsbuttons werden entsprechend zurückgesetzt. Das ist robuster als ein Abgleich der alten Auswahl gegen die neue Ergebnisliste und vermeidet Wechselwirkungen mit #86. **Leeres Suchfeld:** Zeigt alle Einträge (bis LIMIT 501). **Technisch:** Ausschließlich `adapter-in-gui`. Die bestehende Suchabfrage via `GuiHistoryOverviewPort` wird unverändert wiederverwendet. --- ### #86 – Mehrfachauswahl im Verlauf-Tab #### Problem Der Verlauf-Tab erlaubt nur Einzelauswahl. Bulk-Operationen sind nicht möglich. #### Lösung **Multi-Select-Modus:** ```java tableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); ``` JavaFX stellt damit Strg+Klick und Shift+Klick automatisch bereit. **Strg+A – Fokusabhängig:** Strg+A selektiert alle sichtbaren Tabelleneinträge **nur, wenn die Verlauf-Tabelle den Fokus besitzt**. Liegt der Fokus im Suchfeld, bleibt Strg+A die normale Textauswahl im Suchfeld. **Detailbereich bei Mehrfachauswahl:** - Genau 1 Eintrag: Detailbereich wie bisher - Mehrere Einträge: Platzhaltertext „X Einträge ausgewählt." **Snapshot der fachlichen Schlüssel vor Worker-Thread-Start:** Vor dem Start einer Bulk-Operation wird ein **unveränderlicher Snapshot der fachlichen Schlüssel** erstellt, die die bestehenden Reset-/Delete-Use-Cases erwarten (typischerweise Fingerprints, sofern das die vorhandene Use-Case-Signatur erwartet). Der Worker-Thread arbeitet ausschließlich auf diesem Snapshot – nie auf einer Live-`ObservableList`, die sich während der Operation ändern könnte. **Aktionsbuttons bei Mehrfachauswahl:** | Aktion | Verhalten | |---|---| | „Status zurücksetzen" | Aktiv bei ≥ 1 Auswahl; Bestätigungsdialog: „X Einträge zurücksetzen?" | | „Eintrag löschen" | Aktiv bei ≥ 1 Auswahl; Bestätigungsdialog: „X Einträge unwiderruflich löschen?" | **Bulk-Fehlerstrategie (Partial Success):** Schlägt eine Operation bei einzelnen Einträgen fehl, werden die restlichen trotzdem abgearbeitet. Nach Abschluss erscheint ein **kompakter** Zusammenfassungsdialog: > „X von Y Einträgen erfolgreich verarbeitet. Z Einträge konnten nicht > verarbeitet werden." Detaillierte Einzelfehler werden geloggt, nicht in den Dialog gestopft. **Ausführung:** Bulk-Operationen rufen die bestehenden Use-Cases (`DefaultResetDocumentStatusUseCase`, `DefaultDeleteDocumentHistoryUseCase`) sequenziell auf dem Worker-Thread auf. Keine neuen Use-Cases erforderlich. **Sperren während Lauf:** Alle Aktions-Buttons deaktiviert während eines aktiven Verarbeitungslaufs. **Technisch:** Ausschließlich `adapter-in-gui`. Keine neuen Ports oder Use-Cases. --- ### #87 – Neue leere SQLite-Datenbank anlegen #### Problem Will der Nutzer mit einer frischen Datenbank starten, muss er die Datei manuell löschen. Das ist umständlich und fehleranfällig. #### Lösung **Neuer Menüpunkt:** `Datenbank → Neue Datenbank anlegen...` (Nur aktiv wenn kein Verarbeitungslauf läuft.) **Eigentümer des aktiven Datenbankkontexts:** Der Runtime-Wechsel der aktiven Datenbank erfordert eine zentrale Komponente, die den aktiven Datenbankkontext besitzt. Vor der Implementierung analysiert Claude Code, ob eine solche Komponente bereits existiert. - **Fall A – wechselbarer DB-Kontext vorhanden:** Vorhandene Komponente wird genutzt/erweitert. - **Fall B – kein wechselbarer DB-Kontext vorhanden:** Es wird ein minimaler `ActiveDatabaseContextPort` eingeführt (Outbound-Port in `application`, Adapter in `bootstrap` oder `adapter-out-db`). Dieser Port ist die einzige Stelle, an der die aktive DB-Referenz umgestellt wird. **Der DB-Wechsel darf nicht im JavaFX-Code versteckt werden.** Der Use-Case `DefaultCreateNewDatabaseUseCase` orchestriert den Wechsel; die physische Umstellung der Verbindung delegiert er über den Port. **Ablauf (atomar aus Anwendungssicht):** 1. `FileChooser` öffnet (Filter: `*.sqlite`); Nutzer wählt Zieldatei 2. **Pfad-Sicherheitsprüfung:** Die aktive DB und die gewählte Zieldatei werden über **normalisierte, absolut aufgelöste Pfade** verglichen – kein Rohstring-Vergleich. Für existierende Dateien wird `toRealPath()` verwendet; für noch nicht existierende Dateien wird der Parent-Pfad real aufgelöst und der Dateiname normalisiert verglichen. Unter Windows erfolgt der Vergleich case-insensitive. Bei Übereinstimmung: klare Fehlermeldung, kein Überschreiben. 3. Existiert die Zieldatei (andere als aktive DB): Bestätigungsdialog „Die Datei existiert bereits. Überschreiben?" 4. **GUI-Sperre:** Während Anlage und Wechsel befindet sich die GUI in einem `DB-Busy`-Zustand. Alle DB-lesenden und DB-schreibenden Aktionen (Live-Suche, Bulk-Reset, Bulk-Delete, Verlauf-Refresh, erneuter Klick auf „Neue Datenbank anlegen") sind deaktiviert. Der Zustand wird nach Erfolg oder Fehler zuverlässig zurückgesetzt. 5. Neue SQLite-Datei wird als **temporäre Datei im Zielverzeichnis** erzeugt 6. Flyway führt alle verfügbaren Migrationsskripte gegen die temporäre Datei aus (`migrate()` auf neuesten Schema-Stand) 7. Neue DB-Verbindung wird **testweise geöffnet und geprüft** (gegen Temp-Datei). Der Verbindungstest prüft mindestens: - SQLite-Verbindung kann geöffnet werden - Flyway-Schema-History ist vorhanden - Eine einfache Leseabfrage gegen Schema-Metadaten ist erfolgreich 8. Erst nach erfolgreichem Test: temporäre Datei zur Zieldatei verschoben. Bei bereits existierender, bestätigter Zieldatei wird `Files.move(tempFile, targetFile, ATOMIC_MOVE, REPLACE_EXISTING)` verwendet, sofern vom Dateisystem unterstützt. Die vorhandene Zieldatei wird vorher **nicht separat gelöscht**. Wird die Kombination `ATOMIC_MOVE + REPLACE_EXISTING` nicht unterstützt, bricht der Vorgang mit klarer Fehlermeldung ab – kein unsicherer halb-atomarer Fallback. 9. Aktive DB-Referenz der Anwendung umgestellt (via `ActiveDatabaseContextPort`) 10. Verlauf-Tab neu geladen → zeigt „Noch keine Verarbeitungen vorhanden." 11. Statuszeile aktualisiert DB-Pfad 12. DB-Pfad im Konfigurationsmodell geändert → Konfig-Tab wechselt in Dirty-State 13. Statuszeile oder Meldungsbereich zeigt: „Neue Datenbank ist aktiv. Konfiguration speichern, damit diese DB beim nächsten Start verwendet wird." **Fehlerfall ohne partielle Änderung:** Schlägt ein Schritt (Anlegen, Flyway, Verbindungstest, Move) fehl, bleibt die bisher aktive DB **vollständig unverändert in Betrieb**. Die temporäre Datei wird gelöscht. Fehlerdialog mit konkreter Meldung. **Headless:** Die Funktion ist ausschließlich GUI-seitig aufrufbar. `adapter-in-cli` ist nicht betroffen. **Architektur:** | Komponente | Typ | Modul | Zweck | |---|---|---|---| | `CreateNewDatabaseUseCase` | Inbound-Port-Interface | `application` | Vertrag: `createNewDatabase(Path)` | | `DefaultCreateNewDatabaseUseCase` | Use-Case-Impl. | `application` | Atomarer DB-Wechsel: Temp-Datei, Flyway, Test, Move, Kontext-Umstellung | | `DatabaseCreationPort` | Outbound-Port | `application` | `createAndInitialize(Path tempFile)` | | `ActiveDatabaseContextPort` | Outbound-Port | `application` | `switchActiveDatabase(Path newDbFile)` – Eigentümer des Laufzeitkontexts | | `GuiCreateNewDatabasePort` | Bridge-Interface | `adapter-in-gui` | Brücke zum Use-Case | | `SqliteDatabaseCreationAdapter` | Outbound-Adapter | `adapter-out-db` | SQLite-Temp-Datei erzeugen, Flyway migrate auf latest, Verbindung testen | | `SqliteActiveDatabaseContextAdapter` | Outbound-Adapter | `bootstrap` oder `adapter-out-db` | Umschalten der aktiven DB-Referenz (Analyse erforderlich) | --- ### #32 – Mausrad-Zoom in PDF-Vorschau #### Problem Die PDF-Vorschau lässt sich nur über die Zoom-Buttons skalieren. Ein Mausrad-Zoom fehlt. #### Lösung **Scroll-Event auf der PDF-Vorschau-Komponente:** ```java scrollPane.addEventFilter(ScrollEvent.SCROLL, event -> { if (event.isControlDown()) { accumulateAndApplyZoomDelta(event.getDeltaY()); event.consume(); // immer konsumieren bei Strg, kein paralleles Scrollen } // ohne Strg: normales Scrollen bleibt }); ``` **Bei gedrückter Strg-Taste werden ScrollEvents grundsätzlich konsumiert**, damit kein paralleles Scrollen im ScrollPane erfolgt – auch wenn der Delta zu klein für einen Zoomschritt ist. **Delta-Akkumulation für Trackpad-Kompatibilität:** Sehr kleine Trackpad-Deltas werden **intern akkumuliert**, bis die Mindestschwelle für einen Zoomschritt erreicht ist. Kein Verwerfen: akkumulierte Deltas ergeben bei genug Trackpad-Wischbewegung sauber einen Zoomschritt. Als Orientierungswert gilt ±10 % je „Notch" eines Standard-Mausrads. **Zoom-Verhalten:** | Parameter | Wert | |---|---| | Auslöser | Strg + Mausrad | | Schrittweite | Vorzeichenbasiert auf akkumuliertem `deltaY`, ca. 10 % je Notch | | Minimum | 10 % | | Maximum | 500 % | | Zurücksetzen bei neuem PDF | Ja (Zoom auf Fit-to-Width) | **Fit-to-Width-Modus:** Nach manuellem Strg+Mausrad-Zoom verlässt die Vorschau den Fit-to-Width-Modus. Fit-to-Width wird erst wieder aktiv, wenn ein neues PDF geladen oder der Fit-to-Width-Button explizit erneut betätigt wird. **Viewport-Stabilität:** Beim Zoom bleibt die sichtbare Viewport-Mitte möglichst erhalten. **Zoom-State-Konsistenz:** Der Zoom-State wird über dieselbe Variable geführt, die auch die Toolbar-Zoom-Buttons bedienen. **Technisch:** Ausschließlich `adapter-in-gui`. Kein neuer Port, kein Use-Case. --- ## Architektur-Zusammenfassung ### Neue Inbound-Port-Interfaces und Use-Cases | Komponente | Typ | Modul | Zweck | Issue | |---|---|---|---|---| | `CreateNewDatabaseUseCase` | Inbound-Port-Interface | `application` | Vertrag für DB-Anlage | #87 | | `DefaultCreateNewDatabaseUseCase` | Use-Case-Impl. | `application` | Atomarer DB-Wechsel via Temp-Datei + Port-Delegation | #87 | ### Neue Outbound-Ports | Komponente | Modul | Zweck | Issue | |---|---|---|---| | `DatabaseCreationPort` | `application` | Temp-Datei erzeugen, Flyway, Verbindungstest | #87 | | `ActiveDatabaseContextPort` | `application` | `switchActiveDatabase(Path)` – Laufzeit-DB-Kontext | #87 | ### Neue Bridge-Interfaces (adapter-in-gui) | Interface | Zweck | Issue | |---|---|---| | `GuiCreateNewDatabasePort` | Brücke zur DB-Anlage | #87 | ### Neue Adapter | Adapter | Modul | Zweck | Issue | |---|---|---|---| | `SqliteDatabaseCreationAdapter` | `adapter-out-db` | SQLite-Temp-Datei, Flyway migrate auf latest, Test | #87 | | `SqliteActiveDatabaseContextAdapter` | `bootstrap` oder `adapter-out-db` | Umschalten der aktiven DB-Referenz (Lokation via Code-Analyse) | #87 | ### Geänderte Komponenten (adapter-in-gui) | Komponente | Änderung | Issues | |---|---|---| | `GuiHistoryTab` | Multi-Select + Schlüssel-Snapshot, Live-Filter + Generation-Counter + Auswahl leeren, Fehlerursache, Platzhalter via promptText, Tooltips, DB-Busy-Sperre | #82, #83, #86, #88, #77, #87 | | `GuiConfigTab` | Dirty-State mit Baseline-Snapshot + loadingInProgress, Tab-Titel, Dialog, Kopplung mit #87 | #80 | | `GuiTooltipTexts` | Neue Tooltip-Konstanten; TableColumn-Header via Column-Graphic-Pattern | #77 | | Verlauf-Detailbereich | Enum-Displaytext (alle 8 Werte), Fehlerursache für FAILED/SKIPPED_FINAL | #81, #88 | | Status-ComboBox | `StringConverter`, „Alle Status" als GUI-interner Null-Filter | #81 | | PDF-Vorschau-Komponente | Delta-Akkumulation, Strg+Scroll konsumiert, Viewport-Stabilität, Fit-to-Width-Modus | #32 | | Lauf-Abschluss-Signalkette | Ereignisgetriebene Button-State-Neuberechnung für alle Terminierungsgründe | #84 | ### Geänderte Komponenten (sonstige) | Komponente | Modul | Änderung | Issue | |---|---|---|---| | Lock-File-Auflösung | `bootstrap` oder `adapter-out-db` | Absolut: direkt + Abbruch; Relativ: JAR-Dir → user.home → Abbruch; Parent-Dirs; Logging | #91 | ### Nicht geändert - `pdf-umbenenner-domain` – keine Änderungen, außer ggf. minimale Erweiterung für #88 falls Attempt-Modell dort liegt (zulässig, keine Kernverarbeitungslogik) - `pdf-umbenenner-adapter-in-cli` – keine neuen Funktionen - Headless-Verarbeitungslogik – vollständig unberührt - Kernverarbeitungslogik (PDF lesen → KI → umbenennen) --- ## Datenbankmigrationen Flyway ist die einzige Schema-Evolutionsquelle. ### Potenzielles Migrationsskript (abhängig von Code-Analyse #88) Vor der Implementierung von #88 dokumentiert Claude Code verbindlich, ob ein Fehlerfeld bereits im Schema existiert (Fall A / B / C – siehe #88). **Nur bei Fall B oder C:** ```sql -- Fehlerdetails in processing_attempt ergänzen -- Versionsnummer = nächste freie Flyway-Version zum Zeitpunkt der Implementierung ALTER TABLE processing_attempt ADD COLUMN failure_details TEXT; ``` - `failure_details`: nutzerverständliche, gekürzte Fehlerbeschreibung; Begrenzung auf 1000 Zeichen **vor Persistierung im Adapter** erzwungen, Kürzung mit „…"; kein SQL-`CHECK`-Constraint - Bestehende Zeilen erhalten automatisch `NULL` – kein Datenverlust - Alte Einträge ohne Fehlerdetails zeigen `promptText`-Platzhalter in der GUI --- ## Definition of Done (V3.1 gesamt) - [ ] Alle 11 Issues implementiert und einzeln getestet - [ ] `mvn clean verify` grün (alle Module, kein `-DskipTests`) - [ ] `mvn clean install -Drevision=3.1.0` – Build ohne Fehler - [ ] Manueller GUI-Produkttest durchgeführt (Green build ≠ fertige Software) - [ ] Keine Enum-Rohnamen in der GUI sichtbar (alle 8 Statuswerte mit Displaytext) - [ ] Alle fehlenden Tooltips vorhanden; TableColumn-Header via Column-Graphic-Pattern - [ ] Dirty-Indikator Konfig-Tab: kein programmgesteuertes Feuern, Baseline-Snapshot korrekt - [ ] Live-Filter: 300 ms Debounce, Generation-Counter, Auswahl nach Suche geleert - [ ] Mehrfachauswahl: Strg+A nur bei Tabellenfokus; Schlüssel-Snapshot; Partial-Success-Dialog - [ ] `FAILED_FINAL`/`FAILED_RETRYABLE`/`SKIPPED_FINAL_FAILURE`: Fehlerursache sichtbar (oder Platzhalter) - [ ] Leere `ai_reasoning`: `promptText`-Platzhalter (kein echter Text) - [ ] Aktionsbuttons ereignisgetrieben reaktiviert nach allen Terminierungsgründen - [ ] #87 Code-Analyse: DB-Kontext-Eigentümer dokumentiert (Fall A oder B) - [ ] #87: Atomarer Ablauf via Temp-Datei; Pfadvergleich normalisiert + case-insensitive - [ ] #87: Aktive DB bleibt bei Fehler unverändert; DB-Busy-Sperre korrekt zurückgesetzt - [ ] #87: Flyway auf neuesten Stand; Hinweismeldung nach Wechsel - [ ] Strg+Mausrad-Zoom: Delta-Akkumulation, immer konsumiert bei Strg, 10%–500% - [ ] Lock-File: Absolut direkt; Relativ zweistufig; Parent-Dirs; Pfad geloggt - [ ] Code-Kommentare auf Deutsch; Logging auf Deutsch - [ ] JavaDoc auf allen neuen öffentlichen Ports, Use-Cases und Adapter-Methoden - [ ] `betrieb.md` und `gui-bedienanleitung.md` auf V3.1-Stand gebracht - [ ] Freigabedokument `freigabe-v3_1.md` erstellt --- ## Abnahmekriterien je Feature ### #77 Fehlende Tooltips - [ ] Vollständige Bestandsaufnahme: Liste aller Elemente ohne Tooltip erstellt - [ ] Alle identifizierten Elemente haben Tooltips (Anzahl aus Bestandsaufnahme) - [ ] TableColumn-Header: Column-Graphic mit Label+Tooltip, kein Skin-/Lookup-Hack - [ ] Column-Graphic: Sortierung, Header-Breite und CSS nicht sichtbar verschlechtert - [ ] Neue Konstanten ausschließlich in `GuiTooltipTexts`, keine hartcodierten Strings ### #80 Dirty-Indikator Konfig-Tab - [ ] Tab-Titel `* Konfiguration` nur nach echter Nutzeränderung gegenüber Baseline-Snapshot - [ ] Programmgesteuertes Laden setzt kein Dirty-Flag (`loadingInProgress`-Schutz) - [ ] Tab-Titel `Konfiguration` nach Speichern - [ ] Bestätigungsdialog bei Laden neuer Konfig mit Dirty State - [ ] DB-Pfad-Wechsel via #87 setzt Konfig-Tab dirty - [ ] UX identisch zum Prompt-Tab (Sternchen, Dialog, Reset) ### #81 Enum-Bezeichnungen - [ ] Status-ComboBox: „Alle Status" als erster Eintrag (GUI-interner Null-Filter) - [ ] Status-ComboBox: alle 8 Statuswerte als Displaytext - [ ] Versuche-Tabelle: alle 8 Statuswerte als Displaytext - [ ] DB-Queries intern weiterhin mit Enum-Namen - [ ] Kein Enum-Rohname für Endnutzer sichtbar ### #82 Live-Filter - [ ] Suche startet nach 300 ms Tipp-Pause automatisch - [ ] Generation-Counter: veraltete Worker-Ergebnisse werden verworfen - [ ] Such-Button / Enter: sofortige Suche, Debounce abgebrochen - [ ] Auswahl nach neuen Suchergebnissen vollständig geleert - [ ] Leeres Suchfeld zeigt alle Einträge - [ ] Worker-Thread, UI via `Platform.runLater()` ### #83 KI-Begründung leer - [ ] `textArea.setPromptText(...)` bei leerem/null `ai_reasoning` - [ ] `textArea.setText("")` – kein Platzhaltertext als echter Inhalt - [ ] TextArea bleibt sichtbar ### #84 Buttons reaktivieren - [ ] Aktionsbuttons während Lauf deaktiviert - [ ] Reaktivierung ereignisgetrieben nach: Erfolg, Fehlerabbruch, Nutzerabbruch, Exception - [ ] Keine manuellen Workarounds notwendig ### #86 Mehrfachauswahl - [ ] `SelectionMode.MULTIPLE` aktiv - [ ] Strg+A nur bei Tabellenfokus (kein Konflikt mit Suchfeld) - [ ] Strg+Klick, Shift+Klick korrekt - [ ] Detailbereich: „X Einträge ausgewählt." bei Mehrfachauswahl - [ ] Schlüssel-Snapshot vor Worker-Thread-Start - [ ] Bulk-Reset: Bestätigungsdialog + Partial-Success-Dialog - [ ] Bulk-Delete: Bestätigungsdialog + Partial-Success-Dialog - [ ] Aktionen während Lauf gesperrt ### #87 Neue Datenbank anlegen - [ ] Code-Analyse: DB-Kontext-Eigentümer dokumentiert, Fall A oder B entschieden - [ ] Menüpunkt vorhanden, nur außerhalb von Läufen aktiv - [ ] Aktive DB über normalisierten Pfadvergleich (case-insensitive, toRealPath) erkannt - [ ] Bestehende Fremddatei: Überschreiben-Bestätigung - [ ] DB-Busy-Sperre während Anlage aktiv; nach Erfolg/Fehler zuverlässig zurückgesetzt - [ ] Neue DB als Temp-Datei; Flyway auf neuesten Stand - [ ] Verbindungstest: Verbindung öffnen, Flyway-History prüfen, Leseabfrage erfolgreich - [ ] Move mit `ATOMIC_MOVE + REPLACE_EXISTING`; vorhandene Datei nicht vorher separat löschen - [ ] Kein halb-atomarer Fallback bei nicht unterstützter Kombination - [ ] Fehlerfall: Temp-Datei gelöscht, aktive DB unverändert, Fehlerdialog - [ ] `ActiveDatabaseContextPort.switchActiveDatabase()` schaltet Referenz um - [ ] Verlauf-Tab: „Noch keine Verarbeitungen vorhanden." - [ ] Statuszeile aktualisiert DB-Pfad - [ ] Konfig-Tab wechselt in Dirty-State - [ ] Hinweismeldung: Konfiguration speichern nicht vergessen ### #88 Fehlerursache FAILED_FINAL - [ ] Schema-/Code-Analyse: Fall A/B/C dokumentiert vor Implementierung - [ ] Ggf. Flyway-Migration mit nächster freier Versionsnummer - [ ] Sortierung für „letzter Versuch" gegen Schema verifiziert - [ ] Detailbereich: `failure_details` bei `FAILED_FINAL`, `FAILED_RETRYABLE`, `SKIPPED_FINAL_FAILURE` - [ ] NULL/leer: `promptText`-Platzhalter - [ ] 1000-Zeichen-Grenze spätestens vor DB-Persistierung erzwungen, Kürzung mit „…" - [ ] Keine rohen Provider-/Exception-Meldungen persistiert ### #91 Lock-File Pfad - [ ] Absoluter Pfad: direkt verwendet, kein Fallback, Abbruch bei Fehler - [ ] Relativer Pfad: erst JAR-Verzeichnis, dann `user.home`, dann Abbruch - [ ] Parent-Verzeichnisse automatisch angelegt - [ ] Absoluter Pfad beim Start geloggt (INFO) - [ ] Gilt für GUI- und Headless-Start ### #32 Mausrad-Zoom - [ ] Strg+Scroll: Event grundsätzlich konsumiert (kein paralleles Scrollen) - [ ] Delta-Akkumulation für kleine Trackpad-Deltas - [ ] Zoom 10%–500%, ca. 10 % je Notch - [ ] Ohne Strg: normales Scrollen - [ ] Viewport-Mitte beim Zoom möglichst stabil - [ ] Fit-to-Width-Modus verlassen nach manuellem Zoom - [ ] Zoom-Reset bei neuem PDF (Fit-to-Width) - [ ] Zoom-State konsistent mit Toolbar-Zoom-Buttons