diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1af0b76..e10fd43 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,5 @@
+- Fixed a "Warning" dialog showing raw XML content sometimes appearing at IDE startup after using a diff view
+
# 1.2.25 (2026-06-29)
- Fixed choice menu not appearing when option descriptions span more than 3 terminal lines
diff --git a/pom.xml b/pom.xml
index 4dbf77a..10714a4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
io.github.nbplugins
netbeans-plugin-claude-code-gui
- 1.2.25-SNAPSHOT
+ 1.2.27-SNAPSHOT
nbm
Netbeans Plugin Claude Code GUI
NetBeans plugin that provides a GUI for Claude Code CLI — run claude code sessions directly inside the IDE.
diff --git a/src/main/java/io/github/nbplugins/claudecodegui/mcp/tools/OpenDiff.java b/src/main/java/io/github/nbplugins/claudecodegui/mcp/tools/OpenDiff.java
index f7f9de5..fea7461 100644
--- a/src/main/java/io/github/nbplugins/claudecodegui/mcp/tools/OpenDiff.java
+++ b/src/main/java/io/github/nbplugins/claudecodegui/mcp/tools/OpenDiff.java
@@ -221,13 +221,7 @@ public Writer createWriter(Difference[] conflicts) throws IOException {
// Override componentClosed() so that closing the tab without clicking
// Approve or Reject sends FILE_REJECTED — preventing Claude from hanging.
final String finalDiffTabName = diffTabName;
- TopComponent diffTC = new TopComponent() {
- @Override
- public void componentClosed() {
- super.componentClosed();
- DiffTabTracker.setRejected(finalDiffTabName);
- }
- };
+ TopComponent diffTC = new DiffTopComponent(finalDiffTabName);
diffTC.setDisplayName(diffTabName);
diffTC.setLayout(new java.awt.BorderLayout());
@@ -305,4 +299,28 @@ public void setHandler(AsyncHandler handler) {
return createAsyncResponse(createErrorResult("Error opening diff: " + e.getMessage()));
}
}
+
+ /**
+ * Ephemeral diff-viewer tab. Must never be persisted across IDE restarts — its toolbar
+ * button listeners close over per-request state (file contents, async response handler)
+ * that is not meaningfully serializable.
+ */
+ static final class DiffTopComponent extends TopComponent {
+ private final String diffTabName;
+
+ DiffTopComponent(String diffTabName) {
+ this.diffTabName = diffTabName;
+ }
+
+ @Override
+ public int getPersistenceType() {
+ return TopComponent.PERSISTENCE_NEVER;
+ }
+
+ @Override
+ public void componentClosed() {
+ super.componentClosed();
+ DiffTabTracker.setRejected(diffTabName);
+ }
+ }
}
diff --git a/src/test/java/io/github/nbplugins/claudecodegui/mcp/tools/OpenDiffTest.java b/src/test/java/io/github/nbplugins/claudecodegui/mcp/tools/OpenDiffTest.java
new file mode 100644
index 0000000..aa3ff34
--- /dev/null
+++ b/src/test/java/io/github/nbplugins/claudecodegui/mcp/tools/OpenDiffTest.java
@@ -0,0 +1,22 @@
+package io.github.nbplugins.claudecodegui.mcp.tools;
+
+import org.junit.jupiter.api.Test;
+import org.openide.windows.TopComponent;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Unit tests for {@link OpenDiff}.
+ */
+class OpenDiffTest {
+
+ @Test
+ void diffTopComponentPersistenceTypeIsNever() {
+ // Reproduces the bug where the diff tab's TopComponent defaulted to PERSISTENCE_ALWAYS,
+ // causing the NetBeans window system to try to serialize it (and the non-serializable
+ // async-handler state it closes over) into a .settings file on IDE exit — producing a
+ // corrupted-settings "Warning" dialog with raw XML on the next startup.
+ OpenDiff.DiffTopComponent tc = new OpenDiff.DiffTopComponent("Diff: test");
+ assertEquals(TopComponent.PERSISTENCE_NEVER, tc.getPersistenceType());
+ }
+}