Commit Graph

328 Commits

Author SHA1 Message Date
marcus 4a40dee5cd Bugfix: Preferences-Knoten fuer lastConfigPath versionsunabhaengig 2026-05-07 14:57:20 +02:00
marcus 368cb81b56 Feature: Scheduler-Tick-Zaehlung korrigieren und Sitzungstotale einfuehren
Der Scheduler-Tab meldete nach erfolgreicher Verarbeitung faelschlich
"keine neuen Dokumente". Ursache war ein hartkodiertes RunSummary.noOp()
im BatchRunTrigger der Bootstrap; der echte Lauf-Summary wurde nie
gelesen.

- Bootstrap: BatchRunProgressObserver erfasst RunSummary aus onRunEnded
  und uebersetzt ihn in den ausgehenden RunSummary fuer das Tick-Ergebnis
- Neuer Wert-Typ SchedulerSessionTotals (success/failed) plus
  Optional-Feld in SchedulerStatus
- DefaultSchedulerControlUseCase setzt die Totale beim start() auf null
  zurueck, summiert pro Started-Tick auf, friert sie beim stop() ein
- GuiSchedulerTab zeigt pro Tick "X verarbeitet, Y Fehler" oder
  "keine neuen Dokumente" sowie ein zusaetzliches Label
  "Seit Scheduler-Start: X verarbeitet, Y Fehler"

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 14:51:36 +02:00
marcus ac5b74917f Bugfix: GUI-Methoden nutzen ApplicationRunContext statt erneuten Reload
Die GUI-Methoden fuer Historie und manuelle Datei-Aktionen haben Konfig
und Schema bei jedem Aufruf neu geladen. Bei aktivem Scheduler-Lock
schlug der Properties-Lesezugriff in loadAndValidateConfiguration mit
einer IOException fehl.

Zwei neue Helper bevorzugen den bereits stehenden ApplicationRunContext
und fallen nur ohne Kontext auf das alte Migrate-Load-Validate-
Schema-Init-Schema zurueck:
- resolveJdbcUrlForGui (nur JDBC-URL benoetigt)
- resolveStartConfigurationForGui (volle StartConfiguration benoetigt)

Refactoring betrifft:
- resolveHistoricalDocumentContextForGui
- loadHistoryOverviewForGui
- loadHistoryDetailsForGui
- resetHistoryDocumentStatusForGui
- deleteDocumentHistoryForGui
- performGuiManualFileRename
- performGuiManualFileCopy

Der kurzlebige Helper migrateConfigurationIfNeededForGui wurde durch
die beiden neuen Helper ueberfluessig und entfernt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 14:14:49 +02:00
marcus ef985fb6af Bugfix: alle GUI-Pfade ueberspringen Migration bei stehendem Run-Kontext
Konsequente Anwendung des Fix-Musters auf alle GUI-Methoden, die nach
dem Aufbau des ApplicationRunContext eine erneute, redundante
Legacy-Migration ausgeloest haetten. Bei aktivem Scheduler-Lock ist der
Lese-/Schreibzugriff auf die Konfigurationsdatei sonst blockiert.

Neue Helper-Methode migrateConfigurationIfNeededForGui kapselt den Check
auf den Run-Kontext und ersetzt den unbedingten Aufruf in:
- performGuiManualFileRename
- performGuiManualFileCopy
- resolveHistoricalDocumentContextForGui
- loadHistoryOverviewForGui (vorhandene inline-Variante zentralisiert)
- loadHistoryDetailsForGui
- resetHistoryDocumentStatusForGui
- deleteDocumentHistoryForGui

Die uebrigen Aufrufstellen bleiben unveraendert: der headless-Pfad,
initializeApplicationRunContext (die einzige zustaendige Stelle), die
GUI-Launch-Methoden mit bestehendem Early-Return bei vorhandenem Kontext
sowie die Stellen, die vor Aufbau des Kontexts laufen.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 13:58:51 +02:00
marcus fdfc36afb7 Bugfix: Historienuebersicht kollidiert nicht mehr mit Scheduler-Config-Lock
loadHistoryOverviewForGui rief migrateConfigurationIfNeeded unbedingt
auf. Bei aktivem Scheduler haelt der Use Case den OS-Lock auf der
Konfigurationsdatei; der Migrations-Lesezugriff lief dadurch in eine
IOException.

Die Migration ist nur einmal noetig; sie wurde bereits beim Aufbau des
ApplicationRunContext durchgefuehrt. Der erneute Aufruf wird daher
uebersprungen, sobald der Kontext steht.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 13:51:43 +02:00
marcus 8b963adb4f Bugfix: Scheduler-Close-Guard liest Use Case dynamisch
installSchedulerCloseGuard hat den Scheduler-Use-Case bisher nur einmalig
aus dem unveraenderlichen GuiStartupContext gelesen. Bei normalem
GUI-Start ohne --config war dieser Optional leer; der nach dem Auto-Load
verdrahtete Use Case wurde nicht erfasst und der Close-Guard griff nie.

Der Close-Handler wird jetzt unabhaengig vom Startup-Context installiert
und liest den Aktiv-Status zur Laufzeit ueber den Workspace, der den im
GuiSchedulerTab live verdrahteten Use Case kennt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 13:27:51 +02:00
marcus 1ea6465584 Bugfix: Stop-Button im Scheduler-Tab wird wieder aktiv
Die zentrale Status-Refresh-Timeline las den Scheduler-Use-Case aus dem
unveraenderlichen GuiStartupContext. Beim regulaeren GUI-Start ohne
--config ist dieser Optional leer; der via Auto-Load nachtraeglich
verdrahtete Use Case wurde dadurch nie sichtbar, updateStatus wurde nie
aufgerufen und der Stop-Button blieb dauerhaft deaktiviert.

Die Timeline liest den Status jetzt ueber den Workspace, der den live im
GuiSchedulerTab verdrahteten Use Case kennt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 13:21:41 +02:00
marcus 13141f9638 Scheduler: Autostart-Feature entfernen
Der Scheduler startet niemals automatisch beim Programmstart. Der Nutzer
startet ihn ausschliesslich bewusst ueber den Start-Button im
Scheduler-Tab. scheduler.enabled wird nicht mehr gelesen oder geschrieben;
das Property ist obsolet.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 12:57:54 +02:00
marcus 719cc50d16 Bugfix V3.2: RunLockPort-JavaDoc korrigiert und Backup-Fehler bei aktivem Scheduler behoben
BUG 1: RunLockPort-JavaDoc dokumentierte den Scheduler-Tick faelschlicherweise als
nicht-blockierenden Pfad mit tryAcquire(). Da execute() intern acquire() aufruft,
wuerde tryAcquire() vor execute() einen Double-Lock erzeugen. JavaDoc korrigiert:
Scheduler-Tick nutzt denselben blockierenden acquire()-Pfad wie der manuelle Lauf.

BUG 2: GuiConfigurationPropertiesWriter.copyFile() faengt jetzt AccessDeniedException
separat ab und liefert den klaren Hinweis "Konfiguration kann nicht gespeichert
werden - Scheduler laeuft." statt einer generischen Fehlermeldung.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 12:14:43 +02:00
marcus 4bc70dae75 GUI: ApplicationRunContext beim Datei-Öffnen proaktiv aufbauen
Bisher wurde der ApplicationRunContext nur beim --config-Startpfad
erzeugt. Der auto-load-Pfad (letzte Konfiguration aus Preferences)
baute keinen Kontext auf, was Scheduler und Batch-Vorinitialisierung
blockierte.

Neu: GuiApplicationContextInitializer-Callback, den Bootstrap für
jeden GUI-Startpfad bereitstellt. openConfigurationFile() ruft ihn
im Hintergrund-Thread auf; das Scheduler-Ergebnis wird via
Platform.runLater() an GuiSchedulerTab.onSchedulerAvailable()
übergeben.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 07:11:27 +02:00
marcus b7f9184344 SonarQube: fix alle BLOCKER- und CRITICAL-Issues (S3252, S2479, S1186, S1192, S2699, S5783, S3776)
- S3252: GuiStatusRefreshTimeline nutzt Animation.INDEFINITE statt Timeline.INDEFINITE
- S2479: Narrow-No-Break-Space (U+202F) in GuiTooltipTexts durch normales Leerzeichen ersetzt
- S1186: 134 leere Stub-Methoden in 18 Test- und Produktionsdateien kommentiert
- S1192: ~49 duplizierte String-Literale in ~25 Klassen als Konstanten extrahiert
- S2699: fehlende Assertions in SqliteSchemaInitializationAdapterTest und FilesystemTargetFolderAdapterTest ergaenzt
- S5783: Lambda-geprufte Ausnahme in SqliteSchemaInitializationAdapterTest in private Hilfsmethode extrahiert
- S3776: kognitive Komplexitaet in 8 Methoden durch Methodenextraktion auf unter 15 gesenkt
  (EarlyLogDirectoryInitializer, CliArgumentParser, GuiConfigurationEditorWorkspace,
   GuiHistoryTab x2, GuiBatchRunTab x2, DefaultManualFileCopyUseCase)
- Kompilierungsfehler behoben: private-Modifier in CorrectionOutcome-Interface entfernt,
  selbstreferenzielle Konstante in ModelCatalogResult korrigiert

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 21:27:59 +02:00
marcus 14da7ee789 Dokumentation V3.2: Scheduler-Ausnahme, Betriebsdoku und GUI-Bedienanleitung
CLAUDE.md:
- Unverrückbare Technikvorgaben: 'keine Dauerlauf-Anwendung' und 'kein interner
  Scheduler' mit Ausnahme-Hinweis auf GUI-Scheduler annotiert
- Modulstruktur: pdf-umbenenner-adapter-in-scheduler ergänzt
- Neuer Abschnitt 'Scheduler-Ausnahme (ab V3.2)' mit allen abweichenden Regeln
- Aktiver Implementierungsstand: V3.2 als abgeschlossen dokumentiert
- Konfigurationsparameter: scheduler.enabled und scheduler.interval.seconds ergänzt
- Nicht-Ziele: 'keine interne Scheduler-Logik' mit GUI-Scheduler-Ausnahme annotiert

docs/betrieb.md:
- 'Umfang der GUI': von drei auf fünf Tabs aktualisiert (Scheduler + Verlauf ergänzt)
- Neuer Abschnitt 'Automatischer Scheduler' mit Parametern, Autostart, Sperr-
  verhalten und Schließ-Verhalten
- Optionale Parameter: scheduler.enabled und scheduler.interval.seconds ergänzt
- Systemgrenzen: 'Kein interner Scheduler' auf headless-Kontext eingeschränkt

docs/gui-bedienanleitung.md:
- Abschnitt 1: fünf statt vier Tabs; Tab 3 Scheduler neu; Verlauf zu Tab 4,
  Prompt zu Tab 5; alle Abschnitt-Querverweise aktualisiert
- Abschnitte 14-20 zu 15-21 umnummeriert
- Neuer Abschnitt 14 'Tab Scheduler' mit Start/Stop, Statusanzeige, Countdown,
  letztem Lauf, Autostart-Fehler, Sperrbegründungen und Schließ-Dialog-Hinweis

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 17:04:23 +02:00
marcus 7aed0f3730 SonarQube: JaCoCo-Pfad-Mapping durch per-Modul-Reports lösen
Problem: Der Aggregate-Report enthält Klassen aller Module. SonarQube
analysiert jedes Modul isoliert und findet für Klassen anderer Module
keine Quellen → "File not found" für alle Einträge. Das Coverage-Modul
(kein Java-Code) lehnt beim Import alle Einträge ab.

Lösung:
- jacoco:report-Goal (verify-Phase) im Root-POM ergänzt → jedes Modul
  erzeugt target/site/jacoco/jacoco.xml nur für seine eigenen Klassen
- sonar.coverage.jacoco.xmlReportPaths auf relativen Pfad target/site/jacoco/jacoco.xml
  umgestellt → SonarQube löst pro Modul auf, liest ausschließlich dessen
  eigene Klassen, keine Cross-Modul-Kollisionen mehr
- sonar.skip=true in pdf-umbenenner-coverage und pdf-umbenenner-packaging
  gesetzt → Aggregator-/Packaging-Module ohne Java-Quellen werden von
  SonarQube nicht mehr analysiert

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 16:45:11 +02:00
marcus 62cab1ccc4 Schritte 11-13: Config-Tab-Sperre, Batch-Button-Sperre, Scheduler-Close-Guard
Schritt 11: updateLockState() implementiert in GuiConfigurationEditorWorkspace
- schedulerLockActive-Feld eingeführt
- applyBatchRunLockState() delegiert an neues applyConfigTabLockState()
- applyConfigTabLockState() vereint Batch-Run- und Scheduler-Sperre:
  Banner, sectionsBox, Neu/Öffnen/Speichern/Speichern-unter werden gesperrt
  wenn Scheduler aktiv oder Lauf aktiv

Schritt 12: updateSchedulerState() implementiert in GuiBatchRunTab
- schedulerActive-Feld eingeführt
- Starten-Button wird deaktiviert + Tooltip gesetzt wenn Scheduler läuft
- updateButtonStates() berücksichtigt schedulerActive damit Sperre beim
  Laufende nicht verloren geht

Schritt 13: Scheduler-Close-Guard in PdfUmbenennerGuiApplication
- installSchedulerCloseGuard() als äußerste Schicht des Close-Handlers
- Zeigt Informationsdialog und verhindert Beenden wenn Scheduler aktiv
- Bestehender Workspace-/Tray-Handler bleibt erhalten wenn Scheduler gestoppt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 16:28:52 +02:00
marcus 9f6c6f266b SonarQube: jacoco.xmlReportPaths auf absoluten Modulpfad umstellen
Der Glob **/jacoco-aggregate/jacoco.xml wird von SonarQube pro Modul relativ
zu dessen basedir aufgelöst und findet den Aggregate-Report im Geschwistermodul
pdf-umbenenner-coverage nicht. Maven löst ${project.basedir} hingegen vor der
Übergabe an das Sonar-Plugin zu einem absoluten Pfad auf, sodass
../pdf-umbenenner-coverage/... für alle Kind-Module korrekt zeigt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 16:10:00 +02:00
marcus 2af6d8d9bb SonarQube: jacoco.xmlReportPaths in Root-POM auslagern
sonar.coverage.jacoco.xmlReportPaths wird jetzt als Property im Root-POM
gepflegt statt als -D-Parameter im mvn sonar:sonar-Befehl des Jenkinsfile.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 16:06:50 +02:00
marcus fa4f327a3f Schritt 10: GuiSchedulerTab implementieren und in Workspace verdrahten
- SchedulerControlUseCase um getIntervalSeconds(), saveIntervalSeconds(), disableAutostart() erweitert
- DefaultSchedulerControlUseCase implementiert diese drei neuen Methoden
- GuiSchedulerTab neu eingeführt: Autostart-Fehler-Banner + Scheduler-Steuerung
  (Status, Start/Stopp, Countdown, letzter Lauf, Fehleranzeige, Intervall-Feld)
- GuiConfigurationEditorWorkspace: schedulerTab als 3. Tab (nach Verarbeitungslauf)
  eingehängt; onSchedulerStatusRefresh delegiert jetzt auch an schedulerTab.updateStatus()
- GuiAdapterSmokeTest: Tab-Anzahl und -Reihenfolge auf 5 Tabs aktualisiert

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 16:05:24 +02:00
marcus 0cec9347c1 Aufruf von SonarQube bzgl. Testabdeckung korrigiert 2026-05-06 15:45:10 +02:00
marcus e509160621 SonarQube Aufruf angepasst um Testergebnisse / -abdeckung 2026-05-06 15:24:44 +02:00
marcus 8c5d129439 Führe zentrale GuiStatusRefreshTimeline ein (1 Hz, alle Tabs)
PdfUmbenennerGuiApplication startet nach dem Anzeigen des Hauptfensters
eine GuiStatusRefreshTimeline, die im Sekundentakt refreshAllTabStates()
aufruft. Die Methode liest schedulerControlUseCase.getStatus() (falls
present) und delegiert an workspace.onSchedulerStatusRefresh(status).

GuiConfigurationEditorWorkspace.onSchedulerStatusRefresh() leitet den
Status an batchRunTab.updateSchedulerState() und updateLockState() weiter.
Beide Methoden sind vorerst leere Stubs; die Implementierung folgt in
späteren Schritten. Ebenso bleibt der zukünftige GuiSchedulerTab-Aufruf
ausgespart bis Schritt 10.

GuiStatusRefreshTimeline ist eine eigenständige Klasse im gui-Paket,
konsistent mit den bestehenden Coordinator-Klassen.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 15:22:39 +02:00
marcus 74e825d1f4 Erwirb Config-Lock vor manuellem Verarbeitungslauf in der GUI
GuiBatchRunCoordinator erwirbt vor jedem Verarbeitungslauf (regulär und
Mini-Lauf) einen exklusiven OS-Lock auf die Konfigurationsdatei via
ConfigurationFileLockPort. Bei ConfigurationFileLockException wird ein
deutscher Fehlerdialog angezeigt und der Lauf abgebrochen. In finally
wird der Lock immer freigegeben.

GuiStartupContext erhält das 27. Feld configurationFileLockPort;
BootstrapRunner befüllt es mit einem FileChannelConfigurationAccessAdapter
wenn eine Konfigurationsdatei geladen wurde. GuiBatchRunTab und
GuiConfigurationEditorWorkspace reichen den Port durch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 15:11:55 +02:00
marcus ce87b0bbec SonarQube-Aufruf angepasst. 2026-05-06 15:01:59 +02:00
marcus d66364e254 Bootstrap-Wiring: Scheduler in GUI-Startkontext verdrahten
- pdf-umbenenner-bootstrap/pom.xml: Abhängigkeit auf adapter-in-scheduler hinzugefügt
- GuiStartupContext: neues Feld schedulerControlUseCase (Optional<SchedulerControlUseCase>)
  als 26. Record-Komponente; 25-Parameter-Backward-Compat-Konstruktor sichert Abwärtskompatibilität
- DefaultSchedulerControlUseCase: öffentliche Methode markAutostartFailed() ergänzt
- BootstrapRunner: guiSchedulerUseCase-Feld, tryInitializeScheduler(), stopGuiSchedulerIfActive()
  sowie BatchRunTrigger-Lambda; Autostart gemäß scheduler.enabled-Konfiguration

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 14:42:35 +02:00
marcus 434c882d7d SonarQube zur Jenkins-Pipeline hinzugefügt 2026-05-06 14:15:47 +02:00
marcus 8bd25d06c0 Implementiere DefaultSchedulerControlUseCase für Scheduler-Orchestrierung
Implementiert SchedulerControlUseCase als zentralen Orchestrator:
- start()-Sequenz mit STARTING → RUNNING_IDLE und vollständigem Rollback
- stop()-Sequenz mit CAS-gesichertem STOPPING_BATCH_ACTIVE für laufende Batches
- executeWrappedTick() (package-private) setzt RUNNING_BATCH_ACTIVE vor dem Trigger
  und leitet Folgezustand aus BatchRunTriggerResult-Variante ab
- AtomicReference<SchedulerStatus> für threadsichere Zustandsverwaltung
- Intervall wird beim Start aus SchedulerSettingsPort geladen, Minimum 30 s

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 14:10:10 +02:00
marcus 3022a9a16f Implementiere ScheduledExecutorServiceSchedulerAdapter für SchedulerPort
Single-Thread-Executor mit scheduleWithFixedDelay (Initial Delay 0).
Thread-Name: pdf-umbenenner-scheduler, Non-Daemon, UncaughtExceptionHandler
auf ERROR. Alle Ausnahmen in onTick() werden abgefangen, damit der
Tick-Zyklus nicht still abbricht. currentTrigger und onTick() sind
package-private für direkte Testbarkeit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 13:24:45 +02:00
marcus aeb3323180 Implementiere FileChannelConfigurationAccessAdapter für ConfigurationFileLockPort und SchedulerSettingsPort
Der Adapter teilt intern einen FileChannel und ermöglicht so das Schreiben
von Scheduler-Einstellungen auch während eines aktiven OS-Locks. Schreibvorgänge
laufen ohne Lock über eine temporäre Datei (ATOMIC_MOVE); mit Lock direkt über
den Kanal (Truncate → Write → Force). Zeilenenden (CRLF/LF) und alle übrigen
Properties-Zeilen bleiben unverändert erhalten.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 13:14:40 +02:00
marcus c2a7921675 Scheduler-Ports und Typen im Application-Modul anlegen (Schritt 3)
Neue Typen (port/in):
- SchedulerControlUseCase – Inbound-Port: start(), stop(), getStatus()
- SchedulerState – Enum: STOPPED, STARTING, RUNNING_IDLE, RUNNING_BATCH_ACTIVE, STOPPING_BATCH_ACTIVE
- SchedulerStatus – Immutable Record mit AtomicReference-ready Snapshot
- SchedulerStartException – Unchecked Exception für Start-Fehler

Neue Typen (port/out):
- RunLockHandle – AutoCloseable für tryAcquire() in try-with-resources
- RunSummary – Aggregierte Lauf-Ergebniszähler (success/failed/skipped)
- BatchRunTrigger – @FunctionalInterface für synchronen Lauf-Trigger
- BatchRunTriggerResult – Sealed Interface: Started, SkippedBusy, Failed
- SchedulerConfig – Betriebskonfiguration (intervalSeconds >= 30)
- SchedulerSettings – Persistierte Properties-Werte mit Defaults
- SchedulerPort – startScheduler() / stopScheduler()
- ConfigurationFileLockPort – acquireLock() / releaseLock() / isLocked()
- ConfigurationFileLockException – Unchecked bei Lock-Erwerb-Fehler
- SchedulerSettingsPort – loadSettings() / saveEnabled() / saveIntervalSeconds()
- SchedulerSettingsWriteException – Unchecked bei Schreib-Fehler

Erweiterungen:
- RunLockPort: neue Methode tryAcquire() → Optional<RunLockHandle>
- FilesystemRunLockPortAdapter: implementiert tryAcquire() atomar via
  CREATE_NEW; idempotentes Handle via AtomicBoolean

Test-Fixes:
- 9 Mock-Klassen in application- und bootstrap-Tests um tryAcquire()
  ergänzt (liefern Optional.empty(), da nur blockierender Pfad getestet)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 12:42:42 +02:00
marcus 93a2473c36 Neues Maven-Modul pdf-umbenenner-adapter-in-scheduler anlegen
Erzeugt das Modul-Gerüst für den autonomen Scheduler-Adapter:
- pom.xml mit Abhängigkeit auf pdf-umbenenner-application (kein Bootstrap,
  kein JavaFX, kein Shade-Plugin); flatten-maven-plugin und PIT werden mit
  bewusstem Kommentar vom Parent geerbt; JaCoCo-Schwellwerte temporär auf 0
- package-info.java für das Paket de.gecheckt.pdf.umbenenner.adapter.in.scheduler
- SchedulerPlaceholder.java als temporäre Kompilierplatzhalter-Klasse
- Modul in der Parent-pom.xml zwischen adapter-in-gui und adapter-out registriert

Die Abhängigkeitsrichtung (adapter-in-scheduler → application → domain)
verhindert den zyklischen Bezug: Bootstrap wird in einem späteren Schritt
auf dieses Modul angewiesen sein, nicht umgekehrt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 12:21:05 +02:00
marcus 791499169f Spezifikation für V3.2 hinzugefügt 2026-05-06 12:11:39 +02:00
marcus 407f1e0422 Bootstrap-Refactoring: Init/Run-Trennung mit ApplicationRunContext
Führt ApplicationRunContext als package-private Record ein, der beim
GUI-Start einmalig aus der validierten Konfiguration gebaut wird
(migrate → load → validate → schema-init). Das Ergebnis wird in
guiApplicationRunContext gecacht und von launchGuiBatchRun,
launchGuiMiniBatchRun und resetDocumentStatusForGui wiederverwendet,
sodass die Init-Sequenz nicht bei jedem Lauf wiederholt wird.

GuiStartupContext erhält das neue Feld applicationContextError
(Optional<String>), das einen deutschen Fehlertext trägt, wenn der
Kontext bei Startup nicht initialisiert werden konnte. Alle bisherigen
Konstruktoren und die blank()-Fabrik wurden rückwärtskompatibel
ergänzt.

Der Test-Helfer runnerWithGuiFactory wirft jetzt
ConfigurationLoadingException statt AssertionError, damit
initializeApplicationRunContext() den Fehler gracefully abfangen
und in applicationContextError speichern kann.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 12:07:39 +02:00
marcus ca26d181f3 Freigabe-Doku für V3.1 um Buildnummer ergänzt 2026-05-06 07:55:28 +02:00
marcus eae2472b7e Abschluss-Dokumentation V3.1: betrieb.md, Bedienanleitung, Freigabedokument
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 07:45:32 +02:00
marcus 735b3af09f Erlaube .db-Endung im FileChooser „Neue Datenbank anlegen"
Der Filter akzeptiert jetzt *.db und *.sqlite. Der vorgeschlagene
Dateiname übernimmt die Endung der aktuell konfigurierten DB-Datei
(neue-datenbank.db bzw. neue-datenbank.sqlite).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 07:31:09 +02:00
marcus 3876e647b2 Lege neue leere SQLite-Datenbank atomar via Use-Case und GUI an
Neuer Menüpunkt „Datenbank → Neue Datenbank anlegen…" mit FileChooser,
normalisierter Pfadprüfung gegen die aktive DB, gesammelter Überschreib-
Bestätigung, DB-Busy-Sperre auf Verlauf-Tab, Flyway-Migration auf den
neuesten Stand gegen eine Temp-Datei, Verbindungstest, atomarem Move
(ATOMIC_MOVE + REPLACE_EXISTING) und Umstellen der aktiven DB-Referenz
über einen neuen ActiveDatabaseContextPort. Konfig-Tab wechselt nach
Wechsel automatisch in den Dirty-State; Hinweismeldung mit Speichern-
Aufforderung wird im zentralen Meldungsbereich angezeigt.

Architektur entspricht Fall B aus der Spezifikation: Bootstrap hält den
Override prozessweit und verwendet ihn in resolveActiveJdbcUrl statt
des Werts aus der .properties-Datei. Bei Fehlern wird die Temp-Datei
zuverlässig entfernt; die aktive DB bleibt unverändert in Betrieb.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 16:52:54 +02:00
marcus 90d95b9ff8 Zentriere PDF-Vorschau via viewStack-Mindestgröße statt Timing-Hacks
Korrekte Ursachenanalyse: Im Zoom-Modus schrumpft der viewStack auf
Inhalts-Größe (ImageView). Ist der Inhalt kleiner als der Viewport,
positioniert ScrollPane den viewStack links/oben – setHvalue(0.5) ist
wirkungslos, weil nichts zu scrollen ist. Alle bisherigen runLater/
ChangeListener/AnimationTimer-Ansätze haben am falschen Hebel gedreht.

Korrekter Fix: viewportBoundsProperty-Listener im Konstruktor zwingt
viewStack auf mindestens Viewport-Größe. Pos.CENTER zentriert dann
die ImageView automatisch, wenn sie kleiner ist; bei größerem Inhalt
bleibt die Mindestgröße wirkungslos und der ScrollPane scrollt normal.

Ersatzlos entfernt: AnimationTimer-Block in applyZoom (wasInFitMode-
Zweig), Folge-Schritt-runLater (else-Zweig), setHvalue(0.5)/setVvalue(0.5)
in resetToFitView. Bindings in resetToFitView bleiben unverändert.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 16:15:44 +02:00
marcus 661894f1ec Zentriere ersten Zoom-Schritt mittels AnimationTimer-Single-Shot
ChangeListener auf hvalueProperty feuert nicht zuverlässig: wenn hvalue
im Fit-Modus bereits 0.5 (oder identisch zum Reset-Wert) ist, gibt es
keine Wertänderung beim setFitToWidth(false), und der Listener läuft
nie an – der spätere JavaFX-eigene Reset auf 0.0 bleibt unkontrolliert.

AnimationTimer.handle() läuft einmal pro JavaFX-Frame, nach allen
Layout-, CSS- und Pulse-Passes des aktuellen Frames. Das ist der einzige
in JavaFX zuverlässige Mechanismus, um nach allem zu feuern, was JavaFX
in diesem Frame noch erledigt. stop() im ersten handle() macht den Timer
zum Single-Shot.

Folge-Zoom-Schritte (wasInFitMode == false) bleiben unverändert mit
einfachem Platform.runLater.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 16:03:29 +02:00
marcus 0651fcb6eb Fange JavaFX-Reset von hvalue mit ChangeListener ab statt per Timing
Beim Verlassen des Fit-Modus resettet JavaFX hvalue mehrfach auf 0.0,
auch nach unserem Platform.runLater-Aufruf. Verschachtelte runLater
können diesen Reset nicht zuverlässig überholen.

Lösung: Single-Shot-ChangeListener auf hvalueProperty. Er feuert beim
Reset, entfernt sich selbst und postet erst dann setHvalue(0.5)/
setVvalue(0.5) – garantiert nach dem Reset, ohne Timing-Annahmen.

Folge-Zoom-Schritte (wasInFitMode == false) bleiben unverändert mit
einfachem runLater.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 15:51:35 +02:00
marcus b62db18f0c Verschachtele runLater in applyZoom für zentriertes Verhalten beim ersten Zoom
Beim Verlassen des Fit-Modus (setFitToWidth(false)) löst JavaFX einen
H/V-Reset auf 0.0 aus, der innerhalb desselben Pulses passiert wie unser
setHvalue(0.5)-Aufruf im einfachen Platform.runLater. Resultat: Der Reset
überschreibt unseren Wert, die PDF springt links/oben bündig.

Lösung analog zu resetToFitView: doppelt verschachteltes runLater. Das
erste runLater stößt den Layout-Pass nach setFitToWidth(false) an; das
zweite feuert im darauffolgenden Pulse, wenn alle Layout-Folgen
abgeschlossen sind und setHvalue(0.5)/setVvalue(0.5) zuverlässig wirken.

Folge-Zoom-Schritte (wasInFitMode == false) bleiben mit einfachem
runLater und bewahren die aktuelle Scroll-Position.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 15:39:27 +02:00
marcus 3fb511601c Korrigiere Reihenfolge in resetToFitView für zuverlässige Zentrierung
Zwei zusammenwirkende Ursachen für die linksbündige Anzeige nach Zoom-Reset:

1. Die Property-Bindungen wurden vor setFitToWidth(true) gesetzt. Zu diesem
   Zeitpunkt sizet der viewStack noch nach der zoom-großen ImageView, sodass
   die Bindungen die imageView an die Zoom-Breite gekoppelt haben statt an
   die Viewport-Breite.

2. Verbleibende H/V-Werte aus Pan-/Zoom-Modus (insbesondere hvalue=0.0 nach
   Pan zum linken Rand) wurden nicht zurückgesetzt. Bei minimalsten
   Rounding-/Border-Differenzen wirkt hvalue auch im fit-aktiven Modus und
   richtet den Content links bündig aus.

Fix: setFitToWidth/Height(true) sofort; Bindings und setHvalue(0.5)/
setVvalue(0.5) im Platform.runLater nach abgeschlossenem Layout-Pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 15:26:31 +02:00
marcus a8d8a4a3c1 Kalibriere zoomLevel beim Verlassen des Fit-Modus auf visuellen Skalierungsfaktor
Beim ersten Zoom-Schritt sprang die ImageView abrupt von der visuell
sichtbaren Breite (durch fitHeight aspekt-erhaltend verkleinert) auf
naturalViewportWidth × zoomLevel, weil zoomLevel mit dem Wert 1.0 nicht
zur tatsächlich angezeigten Skalierung passte und gleichzeitig setFitHeight(0)
die Höhenrestriktion entfernte.

applyZoom() initialisiert nun beim Verlassen des Fit-Modus zoomLevel
auf currentVisualWidth / naturalImageWidth (= aktueller visueller
Skalierungsfaktor) und setzt naturalViewportWidth auf die natürliche
Bildbreite. Damit entspricht zoomLevel = 1.0 der pixel-genauen
Originaldarstellung. Der vom Caller intendierte Delta-Schritt wird vor
der Kalibrierung gesichert und nach der Kalibrierung auf den neuen
zoomLevel re-appliziert, damit applyZoom(zoomLevel + 0.10) nicht
unverändert auf den kalibrierten Wert feuert.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 15:16:25 +02:00
marcus 3ef8fd0dc3 Imports aufgeräumt 2026-05-05 14:56:16 +02:00
marcus 265b807263 Entferne wirkungslosen H/V-Workaround in resetToFitView
Bei aktivem fitToWidth/fitToHeight hat der ScrollPane keinen scrollbaren
Bereich – setHvalue(0.5)/setVvalue(0.5) sind in diesem Zustand wirkungslos.
Die Wiederherstellung der Property-Bindungen fitWidth/fitHeight an viewStack
versetzt den ImageView in exakt denselben Zustand wie nach der initialen
Konstruktor-Initialisierung. Der StackPane zentriert dann automatisch
über die bereits gesetzte Pos.CENTER-Ausrichtung.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 14:51:08 +02:00
marcus b4f2bf60c6 Verschachtele Platform.runLater in resetToFitView für zuverlässige Zentrierung
Ein einzelner Platform.runLater-Aufruf kann feuern, bevor JavaFX das Layout
nach setFitToWidth(true) vollständig abgeschlossen hat. Durch Verschachtelung
eines zweiten runLater werden setHvalue(0.5) und setVvalue(0.5) erst nach dem
nächsten vollständigen Layout-Pass gesetzt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 14:39:32 +02:00
marcus 15ff034a2b Behebe Zoom-Sprung und Zentrierung nach Rauszoomen
Bug 1: deltaY vor Akkumulation auf einen Notch-Wert begrenzen.
Plattformspezifische Scroll-Multiplikatoren (Windows-Mausgeschwindigkeit,
hohe DPI-Mäuse) können Werte wie 120 statt 40 liefern. Ohne Normierung
akkumuliert sich ein Überlaufwert, der Folge-Events sofort auslöst.

Bug 2: resetToFitView() setzt nach setFitToWidth(true) explizit
scrollPane.setHvalue(0.5) und setVvalue(0.5) (nach layout()-Aufruf),
damit vorherige Pan-Scroll-Werte die Zentrierung nicht nachwirken.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 14:31:29 +02:00
marcus 9c27e4df01 Implementiere PDF-Vorschau: Zoom-Verbesserungen und Grab & Pan
32c: ScrollPane.setPrefSize(0,0) und StackPane.setMinSize(0,0) verhindern,
dass der Vorschaubereich beim manuellen Zoom mitwächst.

32a: Zoom-Akkumulator nutzt if statt while – pro Mausrad-Raste wird genau
eine Zoom-Stufe (10 %) angewendet, auch bei großen deltaY-Werten.

32b: Beim ersten Zoom-Einstieg wird die Ansicht auf die Bildmitte
zentriert (H/V = 0.5). scrollPane.layout() vor der Scroll-Wert-
Restaurierung stellt sicher, dass die neuen Inhaltsgrenzen bekannt sind.

32d: Grab & Pan – im manuellen Zoom-Modus kann die Vorschau mit der Maus
verschoben werden. OPEN_HAND-Cursor signalisiert den Zoom-Modus,
CLOSED_HAND die aktive Pan-Geste.

32e: resetToFitView() setzt Pan-Zustand und Mauszeiger zurück, sodass
beim Laden einer neuen Datei der Fit-to-View-Modus vollständig
wiederhergestellt wird.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 14:09:44 +02:00
marcus 0412874f08 #88 + #77: Fehlerursache-Übersetzung und vollständige Tooltip-Abdeckung
Aufgabe 1 (#88): AiFailureMessageTranslator auf public gesetzt, damit der
Verlauf-Tab die technischen Fehlermeldungen in benutzerfreundliche deutsche
Texte übersetzen kann.

Aufgabe 2 (#77): Vollständige Bestandsaufnahme aller interaktiven GUI-Elemente.
13 neue Konstanten in GuiTooltipTexts ergänzt (Provider-Felder, Verarbeitungs-
limits, optionale Pfade, Vorschau-Navigation, Prompt-Buttons, Dateiname-Textfeld).
Alle fehlenden Tooltips in GuiConfigurationEditorWorkspace, GuiPromptEditorTab,
PdfPreviewPane und FileNameEditorPane gesetzt. Hartcodierte Strings in
GuiPromptEditorTab durch Konstantenreferenzen ersetzt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 13:25:56 +02:00
marcus 6c2e2efe22 #86: Mehrfachauswahl im Verlauf-Tab (SelectionMode.MULTIPLE)
Strg+Klick, Shift+Klick und Strg+A (alle sichtbaren Eintraege) werden durch
JavaFX natuerlich unterstuetzt. Aktionsbuttons (Reset, Loeschen) arbeiten nun
auf allen selektierten Eintraegen. Bei Status-Reset wird ein Hinweis angezeigt,
wenn SUCCESS-Eintraege in der Auswahl enthalten sind (Partial-Success-Dialog).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 12:45:23 +02:00
marcus 9f222208c0 #82: Live-Filter im Verlauf-Tab mit 300-ms-Debounce
Das Suchfeld löst loadOverview() nach 300 ms Tippinaktivität automatisch aus
(PauseTransition). Enter-Taste stoppt den Timer und sucht sofort. So wird die
Tabelle live gefiltert, ohne bei jedem Tastendruck eine DB-Anfrage zu starten.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 12:35:30 +02:00
marcus beade6ba2e #32: Mausrad-Zoom (Strg+Rad) in PDF-Vorschau ergänzt
Strg + Mausrad ändert den Zoomfaktor in 10-%-Stufen (Bereich 10–500 %).
Beim ersten Zoom verlässt die Vorschau den Fit-to-View-Modus; das ScrollPane
übernimmt dann die Scrollbarkeit. Laden einer neuen Datei setzt den Zoom
automatisch auf Fit-to-View zurück.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 12:31:12 +02:00