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("empty_response")
|
||||||
|| lower.contains("missing_title") || lower.contains("blank_title")
|
|| lower.contains("missing_title") || lower.contains("blank_title")
|
||||||
|| lower.contains("missing_reasoning")) {
|
|| 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.";
|
+ "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 reasoningArea = new TextArea();
|
||||||
private final TextArea rawResponseArea = 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 resetButton = new Button("Status zurücksetzen");
|
||||||
private final Button deleteButton = new Button("Eintrag löschen");
|
private final Button deleteButton = new Button("Eintrag löschen");
|
||||||
|
|
||||||
@@ -715,7 +722,10 @@ public final class GuiHistoryTab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void loadDetails(DocumentFingerprint fingerprint) {
|
private void loadDetails(DocumentFingerprint fingerprint) {
|
||||||
|
pendingDetailFingerprintHex = fingerprint.sha256Hex();
|
||||||
reasoningArea.setText(LOADING_TEXT);
|
reasoningArea.setText(LOADING_TEXT);
|
||||||
|
rawResponseArea.setText("");
|
||||||
|
rawResponseArea.setPromptText(NO_RAW_RESPONSE_TEXT);
|
||||||
attemptsItems.clear();
|
attemptsItems.clear();
|
||||||
clearDetailFields();
|
clearDetailFields();
|
||||||
|
|
||||||
@@ -729,6 +739,9 @@ public final class GuiHistoryTab {
|
|||||||
try {
|
try {
|
||||||
Optional<HistoryDetailsResult> result = detailsPort.loadDetails(configPath, fingerprint);
|
Optional<HistoryDetailsResult> result = detailsPort.loadDetails(configPath, fingerprint);
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
|
if (isSupersededDetail(fingerprint)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (result.isEmpty()) {
|
if (result.isEmpty()) {
|
||||||
clearDetailPane();
|
clearDetailPane();
|
||||||
statusBarLabel.setText("Eintrag nicht mehr vorhanden.");
|
statusBarLabel.setText("Eintrag nicht mehr vorhanden.");
|
||||||
@@ -740,6 +753,10 @@ public final class GuiHistoryTab {
|
|||||||
LOG.error("Fehler beim Laden der Dokumentdetails für {}: {}",
|
LOG.error("Fehler beim Laden der Dokumentdetails für {}: {}",
|
||||||
fingerprint.sha256Hex(), ex.getMessage(), ex);
|
fingerprint.sha256Hex(), ex.getMessage(), ex);
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
|
if (isSupersededDetail(fingerprint)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clearDetailPane();
|
||||||
reasoningArea.setText("Fehler beim Laden der Details: " + ex.getMessage());
|
reasoningArea.setText("Fehler beim Laden der Details: " + ex.getMessage());
|
||||||
statusBarLabel.setText("Fehler beim Laden der Details.");
|
statusBarLabel.setText("Fehler beim Laden der Details.");
|
||||||
});
|
});
|
||||||
@@ -957,6 +974,7 @@ public final class GuiHistoryTab {
|
|||||||
if (!failureRelevant || attempts.isEmpty()) {
|
if (!failureRelevant || attempts.isEmpty()) {
|
||||||
failureArea.setText("");
|
failureArea.setText("");
|
||||||
failureArea.setPromptText("Keine Fehlerdetails für diesen Status.");
|
failureArea.setPromptText("Keine Fehlerdetails für diesen Status.");
|
||||||
|
failureArea.setTooltip(new Tooltip(GuiTooltipTexts.VERLAUF_FAILURE_AREA));
|
||||||
return;
|
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() {
|
private void clearDetailPane() {
|
||||||
|
pendingDetailFingerprintHex = null;
|
||||||
clearDetailFields();
|
clearDetailFields();
|
||||||
attemptsItems.clear();
|
attemptsItems.clear();
|
||||||
failureArea.setText("");
|
failureArea.setText("");
|
||||||
|
|||||||
+15
-2
@@ -92,12 +92,17 @@ public class RuntimeLogDirectoryReconfigurer {
|
|||||||
return LogDirectoryChange.unchanged();
|
return LogDirectoryChange.unchanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String previous = System.getProperty(SYSTEM_PROPERTY_KEY);
|
||||||
System.setProperty(SYSTEM_PROPERTY_KEY, target);
|
System.setProperty(SYSTEM_PROPERTY_KEY, target);
|
||||||
try {
|
try {
|
||||||
reconfigure();
|
reconfigure();
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
LOG.warn("Log4j2 konnte nicht auf Log-Verzeichnis '{}' umgestellt werden: {}",
|
// Property auf den vorherigen Wert zurücksetzen: Bleibt sie auf dem neuen (nicht
|
||||||
target, e.getMessage());
|
// 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();
|
return LogDirectoryChange.unchanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,6 +110,14 @@ public class RuntimeLogDirectoryReconfigurer {
|
|||||||
return LogDirectoryChange.changedTo(target);
|
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
|
* Konfiguriert den laufenden Log4j2-{@link LoggerContext} neu, sodass
|
||||||
* {@code ${sys:log.directory}} erneut aufgelöst wird.
|
* {@code ${sys:log.directory}} erneut aufgelöst wird.
|
||||||
|
|||||||
+18
@@ -96,6 +96,24 @@ class RuntimeLogDirectoryReconfigurerTest {
|
|||||||
assertEquals(0, reconf.reconfigureCalls);
|
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
|
@Test
|
||||||
void apply_nonCreatableDirectory_leavesPropertyUnchanged() throws IOException {
|
void apply_nonCreatableDirectory_leavesPropertyUnchanged() throws IOException {
|
||||||
RecordingReconfigurer reconf = new RecordingReconfigurer();
|
RecordingReconfigurer reconf = new RecordingReconfigurer();
|
||||||
|
|||||||
Reference in New Issue
Block a user