diff --git a/CHANGELOG.md b/CHANGELOG.md index b8c6c4aa..07a38e42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/pom.xml b/pom.xml index 951ccbf8..ae7485d2 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ io.github.nbplugins netbeans-plugin-claude-code-gui - 1.2.14-SNAPSHOT + 1.2.15-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/ClaudeCodeInstaller.java b/src/main/java/io/github/nbplugins/claudecodegui/ClaudeCodeInstaller.java index 20ae26b1..12048b70 100644 --- a/src/main/java/io/github/nbplugins/claudecodegui/ClaudeCodeInstaller.java +++ b/src/main/java/io/github/nbplugins/claudecodegui/ClaudeCodeInstaller.java @@ -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) diff --git a/src/test/java/io/github/nbplugins/claudecodegui/V1MigrationHelperTest.java b/src/test/java/io/github/nbplugins/claudecodegui/V1MigrationHelperTest.java index 87a10f96..53a01265 100644 --- a/src/test/java/io/github/nbplugins/claudecodegui/V1MigrationHelperTest.java +++ b/src/test/java/io/github/nbplugins/claudecodegui/V1MigrationHelperTest.java @@ -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.*; @@ -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, ""); + + 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, ""); + + 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 = """ + + + + rO0ABXNyADdpby5naXRodWIubmJjbGF1ZGVjb2RlZ3VpLnVpLkNsYXVkZVNlc3Npb25UYWI= + + + """; + // 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