fix: Codex-Review-Befunde zu Verlauf-Detailanzeige und Log-Umstellung
- 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 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -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.";
|
||||
}
|
||||
|
||||
|
||||
+30
@@ -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<HistoryDetailsResult> 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("");
|
||||
|
||||
+15
-2
@@ -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.
|
||||
|
||||
+18
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user