Files
pdf-umbenenner/docs/specs/V3_1_-_Spezifikation.md

38 KiB
Raw Permalink Blame History

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:

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_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:

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:

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 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