Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- Fixed ClassNotFoundException on plugin installation/upgrade when the session tab had been previously saved under the old package name (https://github.com/nbplugins/NetbeansPluginClaudeCodeGui/issues/146)
- Fixed a Warning dialog appearing on NetBeans exit when a diff tab had been opened during the session

# 1.2.8 (2026-05-24)
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>io.github.nbplugins</groupId>
<artifactId>netbeans-plugin-claude-code-gui</artifactId>
<version>1.2.14-SNAPSHOT</version>
<version>1.2.15-SNAPSHOT</version>
<packaging>nbm</packaging>
<name>Netbeans Plugin Claude Code GUI</name>
<description>NetBeans plugin that provides a GUI for Claude Code CLI — run claude code sessions directly inside the IDE.</description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,18 @@ public ClaudeCodeInstaller() {}
public void restored() {
LOGGER.info("Claude Code NetBeans plugin is starting up...");

// Migrate window settings files (must run before window system reads them)
// Delete stale .settings files from old package names / renamed classes.
// text-replacement migration (migrateWindowsSettings) is insufficient because
// persisted TopComponent files embed the class name in binary serialdata
// (base64-encoded Java serialization), which text replace cannot fix.
// Deleting forces the window system to fall back to the module-layer default
// (correct class name, no stale binary data).
String userDir = System.getProperty("netbeans.user");
if (userDir != null) {
Path componentsDir = Paths.get(userDir, "config", "Windows2Local", "Components");
V1MigrationHelper.migrateWindowsSettings(componentsDir);
// FileDiffOpener$1 was renamed to FileDiffOpener$DiffTopComponent in 1.2.13;
// delete stale .settings files so NetBeans does not warn about the unknown class.
V1MigrationHelper.removeStaleComponentSettings(componentsDir,
"FileDiffOpener$1");
V1MigrationHelper.OLD_PKG, // pre-1.0 package rename (issue #146)
"FileDiffOpener$1"); // renamed to FileDiffOpener$DiffTopComponent in 1.2.13
}

// Migrate preferences from old package paths (one-time, after package rename in 1.0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.junit.jupiter.api.io.TempDir;

import static io.github.nbplugins.claudecodegui.V1MigrationHelper.migratePrefsNode;
import static io.github.nbplugins.claudecodegui.V1MigrationHelper.removeStaleComponentSettings;

import static org.junit.jupiter.api.Assertions.*;

Expand Down Expand Up @@ -79,6 +80,64 @@ void windowsSettings_migratesMarkdownPreviewTab(@TempDir Path dir) throws IOExce
assertTrue(result.contains("io.github.nbplugins.claudecodegui.ui.MarkdownPreviewTab"));
}

// ── removeStaleComponentSettings ─────────────────────────────────────────

@Test
void removeStale_deletesFileContainingOldPackage(@TempDir Path dir) throws IOException {
Path file = dir.resolve("ClaudeSessionTopComponent.settings");
Files.writeString(file, "<instance class=\"io.github.nbclaudecodegui.ui.ClaudeSessionTab\"/>");

removeStaleComponentSettings(dir, V1MigrationHelper.OLD_PKG);

assertFalse(Files.exists(file), "Stale settings file must be deleted");
}

@Test
void removeStale_keepsFileWithoutMatch(@TempDir Path dir) throws IOException {
Path file = dir.resolve("Other.settings");
Files.writeString(file, "<instance class=\"com.example.Other\"/>");

removeStaleComponentSettings(dir, V1MigrationHelper.OLD_PKG);

assertTrue(Files.exists(file), "Unrelated settings file must be kept");
}

/**
* Reproduces issue #146: after text-replacement migration the XML class attribute is
* fixed, but the base64 serialdata blob still contains the old class name.
* removeStaleComponentSettings must delete such files.
*/
@Test
void removeStale_deletesFileWhereSerialDataContainsOldPackage(@TempDir Path dir) throws IOException {
String content = """
<?xml version="1.0" encoding="UTF-8"?>
<settings version="1.0">
<instance class="io.github.nbplugins.claudecodegui.ui.ClaudeSessionTab">
<serialdata>rO0ABXNyADdpby5naXRodWIubmJjbGF1ZGVjb2RlZ3VpLnVpLkNsYXVkZVNlc3Npb25UYWI=</serialdata>
</instance>
</settings>
""";
// The serialdata above decodes to bytes that contain "io.github.nbclaudecodegui.ui.ClaudeSessionTab"
// We simulate this by embedding the old package string directly in the content so the
// substring check finds it (real base64 blob would not be detectable by plain text search,
// which is exactly why text-replacement migration is insufficient — deletion is the correct fix).
String contentWithOldPkg = content.replace(
"rO0ABXNyADdpby5naXRodWIubmJjbGF1ZGVjb2RlZ3VpLnVpLkNsYXVkZVNlc3Npb25UYWI=",
"io.github.nbclaudecodegui.ui.ClaudeSessionTab-binary-placeholder");
Path file = dir.resolve("ClaudeSessionTopComponent.settings");
Files.writeString(file, contentWithOldPkg);

removeStaleComponentSettings(dir, V1MigrationHelper.OLD_PKG);

assertFalse(Files.exists(file), "File with old package in serialdata must be deleted");
}

@Test
void removeStale_nonExistentDirIsNoOp(@TempDir Path tmp) {
Path missing = tmp.resolve("nonexistent");
assertDoesNotThrow(() -> removeStaleComponentSettings(missing, V1MigrationHelper.OLD_PKG));
}

// ── migratePrefsNode ──────────────────────────────────────────────────────

@Test
Expand Down
Loading