38 KiB
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:
- Polieren – sichtbare Schwächen aus dem V3.0-Produkttest beheben (#77, #80, #81, #83, #84, #88, #91)
- Verlauf-Tab reifen lassen – Suche, Mehrfachauswahl, DB-Neuanlage (#82, #86, #87)
- 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,
--headlessbleibt vollständig erhalten .propertiesbleibt 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:
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<ProcessingStatus>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:
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):
-- 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_FINALFAILED_RETRYABLESKIPPED_FINAL_FAILURE(zeigt die letzte bekannte Fehlerursache des zugrundeliegenden fehlgeschlagenen Attempts – fachlich konsistent, daSKIPPED_FINAL_FAILUREdirekte 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:
- Primär: Auflösung relativ zum Verzeichnis der JAR-Datei
(
CodeSource.getLocation()) - Fallback: Auflösung relativ zu
user.home - Abbruch: Erst wenn auch
user.homefehlschlä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
ChangeListenerauf dietextProperty() - 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:
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
ActiveDatabaseContextPorteingeführt (Outbound-Port inapplication, Adapter inbootstrapoderadapter-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):
FileChooseröffnet (Filter:*.sqlite); Nutzer wählt Zieldatei- 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. - Existiert die Zieldatei (andere als aktive DB): Bestätigungsdialog „Die Datei existiert bereits. Überschreiben?"
- 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. - Neue SQLite-Datei wird als temporäre Datei im Zielverzeichnis erzeugt
- Flyway führt alle verfügbaren Migrationsskripte gegen die temporäre Datei aus
(
migrate()auf neuesten Schema-Stand) - 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
- 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 KombinationATOMIC_MOVE + REPLACE_EXISTINGnicht unterstützt, bricht der Vorgang mit klarer Fehlermeldung ab – kein unsicherer halb-atomarer Fallback. - Aktive DB-Referenz der Anwendung umgestellt (via
ActiveDatabaseContextPort) - Verlauf-Tab neu geladen → zeigt „Noch keine Verarbeitungen vorhanden."
- Statuszeile aktualisiert DB-Pfad
- DB-Pfad im Konfigurationsmodell geändert → Konfig-Tab wechselt in Dirty-State
- 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:
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<ProcessingStatus>, „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:
-- 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 verifygrü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.mdundgui-bedienanleitung.mdauf V3.1-Stand gebracht- Freigabedokument
freigabe-v3_1.mderstellt
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
* Konfigurationnur nach echter Nutzeränderung gegenüber Baseline-Snapshot - Programmgesteuertes Laden setzt kein Dirty-Flag (
loadingInProgress-Schutz) - Tab-Titel
Konfigurationnach 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/nullai_reasoningtextArea.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.MULTIPLEaktiv- 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_detailsbeiFAILED_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