From c7a40298df049a0cd9f93ef509c0548fae75aeed Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 1 Jul 2026 18:53:46 +0200 Subject: [PATCH] fix: Codex-Review-Befunde zu Verlauf-Detailanzeige und Log-Umstellung MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - RuntimeLogDirectoryReconfigurer: Bei fehlgeschlagenem Log4j2-Reconfigure wird die System-Property log.directory auf den vorherigen Wert zurückgesetzt, damit ein späterer Versuch nicht faelschlich als "bereits aktiv" uebersprungen wird. Test ergaenzt. - GuiHistoryTab: Beim Detailwechsel wird die KI-Rohantwort zurückgesetzt; verspätet eintreffende Detail-Ergebnisse einer inzwischen gewechselten/abgewählten Selektion werden über einen Fingerprint-Abgleich verworfen (kein "A unter B", keine dauerhaft sichtbare fremde Rohantwort bei Ladefehler). - GuiHistoryTab: Der technische Fehler-Tooltip wird bei nicht-fehlerhaften Datensätzen auf den Standardtext zurückgesetzt (keine fremde Fehlermeldung mehr sichtbar). - AiFailureMessageTranslator: praezisere Meldung ("kein gueltiges JSON oder ein Pflichtfeld fehlt"). Co-Authored-By: Claude Opus 4.8 --- .../batchrun/AiFailureMessageTranslator.java | 2 +- .../adapter/in/gui/history/GuiHistoryTab.java | 30 +++++++++++++++++++ .../RuntimeLogDirectoryReconfigurer.java | 17 +++++++++-- .../RuntimeLogDirectoryReconfigurerTest.java | 18 +++++++++++ 4 files changed, 64 insertions(+), 3 deletions(-) diff --git a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/AiFailureMessageTranslator.java b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/AiFailureMessageTranslator.java index 37c0964..5b8b2cc 100644 --- a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/AiFailureMessageTranslator.java +++ b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/AiFailureMessageTranslator.java @@ -83,7 +83,7 @@ public final class AiFailureMessageTranslator { || lower.contains("empty_response") || lower.contains("missing_title") || lower.contains("blank_title") || lower.contains("missing_reasoning")) { - return "KI-Antwort war nicht im erwarteten Format (kein gültiges JSON). " + return "KI-Antwort war nicht im erwarteten Format (kein gültiges JSON oder ein Pflichtfeld fehlt). " + "Bitte erneut verarbeiten; hält der Fehler an, den Prompt anpassen."; } diff --git a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/history/GuiHistoryTab.java b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/history/GuiHistoryTab.java index 8a4d891..dd707f1 100644 --- a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/history/GuiHistoryTab.java +++ b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/history/GuiHistoryTab.java @@ -146,6 +146,13 @@ public final class GuiHistoryTab { private final TextArea reasoningArea = new TextArea(); private final TextArea rawResponseArea = new TextArea(); + /** + * SHA-256 des zuletzt angeforderten Detail-Dokuments. Dient dazu, verspätet + * eintreffende Detail-Ergebnisse einer inzwischen abgewählten oder gewechselten + * Selektion zu verwerfen. Zugriff ausschließlich auf dem JavaFX Application Thread. + */ + private String pendingDetailFingerprintHex; + private final Button resetButton = new Button("Status zurücksetzen"); private final Button deleteButton = new Button("Eintrag löschen"); @@ -715,7 +722,10 @@ public final class GuiHistoryTab { } private void loadDetails(DocumentFingerprint fingerprint) { + pendingDetailFingerprintHex = fingerprint.sha256Hex(); reasoningArea.setText(LOADING_TEXT); + rawResponseArea.setText(""); + rawResponseArea.setPromptText(NO_RAW_RESPONSE_TEXT); attemptsItems.clear(); clearDetailFields(); @@ -729,6 +739,9 @@ public final class GuiHistoryTab { try { Optional result = detailsPort.loadDetails(configPath, fingerprint); Platform.runLater(() -> { + if (isSupersededDetail(fingerprint)) { + return; + } if (result.isEmpty()) { clearDetailPane(); statusBarLabel.setText("Eintrag nicht mehr vorhanden."); @@ -740,6 +753,10 @@ public final class GuiHistoryTab { LOG.error("Fehler beim Laden der Dokumentdetails für {}: {}", fingerprint.sha256Hex(), ex.getMessage(), ex); Platform.runLater(() -> { + if (isSupersededDetail(fingerprint)) { + return; + } + clearDetailPane(); reasoningArea.setText("Fehler beim Laden der Details: " + ex.getMessage()); statusBarLabel.setText("Fehler beim Laden der Details."); }); @@ -957,6 +974,7 @@ public final class GuiHistoryTab { if (!failureRelevant || attempts.isEmpty()) { failureArea.setText(""); failureArea.setPromptText("Keine Fehlerdetails für diesen Status."); + failureArea.setTooltip(new Tooltip(GuiTooltipTexts.VERLAUF_FAILURE_AREA)); return; } @@ -1010,7 +1028,19 @@ public final class GuiHistoryTab { } } + /** + * Prüft, ob ein asynchron geladenes Detail-Ergebnis inzwischen überholt ist, weil + * mittlerweile ein anderes (oder kein) Dokument ausgewählt wurde. + * + * @param fingerprint der Fingerprint, für den das Ergebnis geladen wurde + * @return {@code true}, wenn das Ergebnis verworfen werden soll + */ + private boolean isSupersededDetail(DocumentFingerprint fingerprint) { + return !fingerprint.sha256Hex().equals(pendingDetailFingerprintHex); + } + private void clearDetailPane() { + pendingDetailFingerprintHex = null; clearDetailFields(); attemptsItems.clear(); failureArea.setText(""); diff --git a/pdf-umbenenner-bootstrap/src/main/java/de/gecheckt/pdf/umbenenner/bootstrap/startup/RuntimeLogDirectoryReconfigurer.java b/pdf-umbenenner-bootstrap/src/main/java/de/gecheckt/pdf/umbenenner/bootstrap/startup/RuntimeLogDirectoryReconfigurer.java index 41aa88b..22bd29d 100644 --- a/pdf-umbenenner-bootstrap/src/main/java/de/gecheckt/pdf/umbenenner/bootstrap/startup/RuntimeLogDirectoryReconfigurer.java +++ b/pdf-umbenenner-bootstrap/src/main/java/de/gecheckt/pdf/umbenenner/bootstrap/startup/RuntimeLogDirectoryReconfigurer.java @@ -92,12 +92,17 @@ public class RuntimeLogDirectoryReconfigurer { return LogDirectoryChange.unchanged(); } + String previous = System.getProperty(SYSTEM_PROPERTY_KEY); System.setProperty(SYSTEM_PROPERTY_KEY, target); try { reconfigure(); } catch (RuntimeException e) { - LOG.warn("Log4j2 konnte nicht auf Log-Verzeichnis '{}' umgestellt werden: {}", - target, e.getMessage()); + // Property auf den vorherigen Wert zurücksetzen: Bleibt sie auf dem neuen (nicht + // aktivierten) Ziel stehen, würde die Idempotenz-Prüfung oben jeden weiteren + // Umstellungsversuch fälschlich als „bereits aktiv" überspringen. + restoreProperty(previous); + LOG.warn("Log4j2 konnte nicht auf Log-Verzeichnis '{}' umgestellt werden: {} – " + + "bisheriges Log-Verzeichnis bleibt aktiv.", target, e.getMessage()); return LogDirectoryChange.unchanged(); } @@ -105,6 +110,14 @@ public class RuntimeLogDirectoryReconfigurer { return LogDirectoryChange.changedTo(target); } + private static void restoreProperty(String previous) { + if (previous == null) { + System.clearProperty(SYSTEM_PROPERTY_KEY); + } else { + System.setProperty(SYSTEM_PROPERTY_KEY, previous); + } + } + /** * Konfiguriert den laufenden Log4j2-{@link LoggerContext} neu, sodass * {@code ${sys:log.directory}} erneut aufgelöst wird. diff --git a/pdf-umbenenner-bootstrap/src/test/java/de/gecheckt/pdf/umbenenner/bootstrap/startup/RuntimeLogDirectoryReconfigurerTest.java b/pdf-umbenenner-bootstrap/src/test/java/de/gecheckt/pdf/umbenenner/bootstrap/startup/RuntimeLogDirectoryReconfigurerTest.java index d1885d4..268a34d 100644 --- a/pdf-umbenenner-bootstrap/src/test/java/de/gecheckt/pdf/umbenenner/bootstrap/startup/RuntimeLogDirectoryReconfigurerTest.java +++ b/pdf-umbenenner-bootstrap/src/test/java/de/gecheckt/pdf/umbenenner/bootstrap/startup/RuntimeLogDirectoryReconfigurerTest.java @@ -96,6 +96,24 @@ class RuntimeLogDirectoryReconfigurerTest { assertEquals(0, reconf.reconfigureCalls); } + @Test + void apply_reconfigureFails_restoresPreviousProperty() { + System.setProperty(KEY, "C:/vorher/logs"); + RuntimeLogDirectoryReconfigurer failing = new RuntimeLogDirectoryReconfigurer() { + @Override + protected void reconfigure() { + throw new IllegalStateException("reconfigure kaputt"); + } + }; + Path target = tempDir.resolve("neu"); + + LogDirectoryChange change = failing.apply(target); + + assertFalse(change.changed()); + assertEquals("C:/vorher/logs", System.getProperty(KEY), + "Nach fehlgeschlagener Neukonfiguration muss der vorherige Property-Wert wiederhergestellt sein"); + } + @Test void apply_nonCreatableDirectory_leavesPropertyUnchanged() throws IOException { RecordingReconfigurer reconf = new RecordingReconfigurer();