From dfe0545c7938be898d87f9001fb1e66572ea385d Mon Sep 17 00:00:00 2001 From: Lain Date: Fri, 28 Aug 2026 17:34:41 +0200 Subject: [PATCH 1/7] docs(readme): fill the two gaps Remote Control left behind MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit What's new skipped 5.7.0 entirely — it went from 5.8.0 straight back to 5.5.0, so the release that made the guard visible was missing from the one place a reader looks for it. The comparison table gained a Remote Control row. The middle column says what follows from a fact the table already states two rows down: Anthropic's plugin runs the CLI in the integrated terminal, and there the command is the CLI's own. Their plugin was not installed to check it behaves as documented. --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index ab053f49..1cc11f51 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ Three different things are often confused. All of them are legitimate; they solv | Permissions | An inline card per call, plus a deterministic lock that runs before any auto-approval | Handled by the CLI in the terminal | JetBrains' own approvals | | Account | Your `claude` subscription or API key | Your `claude` subscription or API key | JetBrains AI credits, your own Anthropic API key, or a Claude Console account | | Agents / background tasks | A tab and a transcript per agent; background tasks keep their output | Visible as terminal output | Not applicable | +| [Remote Control](#remote-control--pick-a-chat-up-from-your-phone) | A button in the chat and a row in the ⚙ menu; the chat you are looking at is the one that goes to your phone | The CLI's own `/remote-control`, typed in the terminal | Not applicable — it does not run your local CLI | | Needs the `claude` CLI | Yes — and installs it for you if you do not have it | Yes | No | Anthropic's [Claude Code [Beta]](https://plugins.jetbrains.com/plugin/27310-claude-code-beta-) is not @@ -640,6 +641,13 @@ phone button in the chat's button row and a row in the ⚙ menu, neither of whic has accepted; a refusal names its reason on the button and in the chat. Permission cards answered from the phone close themselves here. +**5.7.0** — the [Sensitive Guard](#security) stops being invisible: it keeps a log of every alert it +raises, per project, and a **Guard** view in the chat's view row to read it — what matched, what the rule +saw, the verdict, and what let the call through if anything did. Every rule gets a mode (Enforcing, +Permissive, Allow All), as does the guard as a whole; **Settings ▸ Claude Code Security** becomes its own +page, with the three whitelist reaches and the extra credential globs and blocked domains. A shield in +the chat's button row suspends the guard for a duration you pick. Most settings became per project. + **5.5.0** — a tab and a transcript per agent, with the whole tree one hover away; a single **Workloads** diagram of everything running across every chat; background tasks that keep their output after they end and survive a restart; a [Git integration](#git) whose write actions are asked of Claude rather than From 5ae4c361006aabacc5822af9a9bb43f202adce7b Mon Sep 17 00:00:00 2001 From: Lain Date: Sun, 30 Aug 2026 21:18:31 +0200 Subject: [PATCH 2/7] fix(tabs): hide the closing chat before it leaves the card deck MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closing the last chat logged "Already disposed" from JBCefOsrComponent's resize alarm. Container.remove runs removeNotify() on the card first, then hands it to CardLayout.removeLayoutComponent, which calls next() when the card is still visible. With one card left, next() picks that same card, makes it visible again and validates, so the layout reshapes a browser component whose alarm the removeNotify had already disposed. With two or more chats the survivor's select() had already hidden the dying card, so next() never ran — which is why only the last tab, on a session whose browser had really been realised, hit it. --- .../dev/lain/claudejb/ui/ChatTabsPanel.kt | 1 + .../headless/ChatTabsCloseHeadlessTest.kt | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/main/kotlin/dev/lain/claudejb/ui/ChatTabsPanel.kt b/src/main/kotlin/dev/lain/claudejb/ui/ChatTabsPanel.kt index 9a470ca8..943568e4 100644 --- a/src/main/kotlin/dev/lain/claudejb/ui/ChatTabsPanel.kt +++ b/src/main/kotlin/dev/lain/claudejb/ui/ChatTabsPanel.kt @@ -116,6 +116,7 @@ internal class ChatTabsPanel : JBPanel(BorderLayout()), Disposabl } else { pushChats() } + tab.component.isVisible = false onClosed(tab) content.remove(tab.component) tab.disposer?.let { Disposer.dispose(it) } diff --git a/src/test/kotlin/dev/lain/claudejb/headless/ChatTabsCloseHeadlessTest.kt b/src/test/kotlin/dev/lain/claudejb/headless/ChatTabsCloseHeadlessTest.kt index ea1d7412..ae56df00 100644 --- a/src/test/kotlin/dev/lain/claudejb/headless/ChatTabsCloseHeadlessTest.kt +++ b/src/test/kotlin/dev/lain/claudejb/headless/ChatTabsCloseHeadlessTest.kt @@ -108,6 +108,30 @@ class ChatTabsCloseHeadlessTest : BasePlatformTestCase() { assertEquals(listOf("A"), closed) } + fun `test the last tab is hidden before it leaves the deck, never shown again`() { + val a = open("A") + tabs.select(a) + + tabs.close(a) + + assertFalse( + "closing the only card must not leave the card layout re-showing the torn down chat", + a.component.isVisible, + ) + assertNull("and the card must be out of the deck", a.component.parent) + } + + fun `test closing the selected tab hides it and shows the survivor`() { + val a = open("A") + val b = open("B") + tabs.select(a) + + tabs.close(a) + + assertFalse(a.component.isVisible) + assertTrue(b.component.isVisible) + } + fun `test the closed tab is out of the model when close returns`() { val a = open("A") open("B") From 80d3a4a490a84817048411d92992e490fb0020d6 Mon Sep 17 00:00:00 2001 From: Lain Date: Sun, 30 Aug 2026 21:19:35 +0200 Subject: [PATCH 3/7] chore(codeowners): correct the maintainer's contact address The listed address was wrong; the maintainer reads lain@digitalexperiments.dev. --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 02da49f5..6da71dc4 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,7 +1,7 @@ # CODEOWNERS — Claude Code Native # # GitHub uses this file to auto-request reviews on pull requests. -# Maintainer: @serialexperimentslainnnn (the local author "Lain" / lain.agent604@passmail.com). +# Maintainer: @serialexperimentslainnnn (the local author "Lain" / lain@digitalexperiments.dev). # Default owner for everything in the repo. * @serialexperimentslainnnn From 5f66829a5f4bc0e9412803e8318581aa9e77df7a Mon Sep 17 00:00:00 2001 From: Lain Date: Sun, 30 Aug 2026 21:20:26 +0200 Subject: [PATCH 4/7] chore(codeowners): own CLAUDE.md and .gitignore outright Neither is a chore file. CLAUDE.md holds the standing directive over the security code and its tests, and .gitignore is what keeps credentials and local state out of the history; an edit to either changes the rules, so both are listed rather than left to the default glob. --- CODEOWNERS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CODEOWNERS b/CODEOWNERS index 6da71dc4..ef10ffeb 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -15,6 +15,13 @@ /src/main/kotlin/dev/lain/claudejb/permission/ @serialexperimentslainnnn /src/main/kotlin/dev/lain/claudejb/protocol/ @serialexperimentslainnnn +# Two files nothing may edit in passing. CLAUDE.md carries the standing directive over the +# guard and its tests, and .gitignore is what keeps credentials and local state out of the +# history in the first place — a quiet line added to either is a change to the rules, not a +# chore, so both are owned outright. +/CLAUDE.md @serialexperimentslainnnn +/.gitignore @serialexperimentslainnnn + # A workflow is privileged code: it runs with a token, and it can publish to the Marketplace. A ruleset is # the gate that decides what reaches main at all. Both are listed LAST so they win over every earlier glob. # From 25e741fbf4fbae8f18f37604817da8d4b8869fc6 Mon Sep 17 00:00:00 2001 From: Lain Date: Sun, 30 Aug 2026 21:23:30 +0200 Subject: [PATCH 5/7] build: bump the version to 5.8.1 --- CHANGELOG.md | 9 +++++++++ RELEASE_NOTES.md | 8 ++++++++ build.gradle.kts | 2 +- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6035e951..8ed782cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [5.8.1] — 2026-08-30 + +### Fixed +- **Closing the last chat no longer reports an internal error.** Closing a chat that was the only one + open, and that had been used, left the IDE logging `Already disposed` from the browser component the + chat had just torn down. The chat's card is now hidden before it leaves the tool window, so the layout + never lays out a browser that is already gone. Nothing about closing a chat changes for you: the + replacement chat still opens as before. + ## [5.8.0] — 2026-08-28 **A chat in the IDE can now be driven from claude.ai or the Claude app**, and keeps running on your own diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 9feca591..6846e9a2 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,11 @@ +## v5.8.1 — 2026-08-30 + +**Closing your last chat no longer raises an internal error.** If the chat you closed was the +only one open and you had actually used it, the IDE logged an `Already disposed` error while +tearing the chat's browser down. The chat is now hidden before it is removed, so nothing tries +to lay out a browser that has already gone. Closing a chat behaves exactly as before otherwise, +replacement chat included. + ## v5.8.0 — 2026-08-28 **Pick a chat up from your phone.** Remote Control connects a chat running in your IDE to diff --git a/build.gradle.kts b/build.gradle.kts index 595a0329..dc9ebd28 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -28,7 +28,7 @@ plugins { } group = "dev.lain" -version = "5.8.0" +version = "5.8.1" repositories { mavenCentral() From 4da08a69eea99ebe63b10d68c6a5d79cf043e81a Mon Sep 17 00:00:00 2001 From: Lain Date: Sun, 30 Aug 2026 21:29:53 +0200 Subject: [PATCH 6/7] docs: stop claiming settings are global MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FAQ.md and TROUBLESHOOTING.md still described the 5.5.0 scope. Since 5.7.0 settings are one document per IDE installation, per project, with the old global document kept as the seed — which README.md already says. --- docs/FAQ.md | 7 +++++-- docs/TROUBLESHOOTING.md | 8 ++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/FAQ.md b/docs/FAQ.md index 46e6f10b..f357802e 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -89,8 +89,11 @@ Because that is where its settings and credentials live. Since 5.5.0 the whole settings document is in the IDE's PasswordSafe rather than in `.idea/claude-code.xml` — they were per-project plaintext in a file people commit, and they include an env block, which is where an API key or a -credentialed proxy URL ends up. One consequence worth knowing: **the settings are -now global, not per project.** +credentialed proxy URL ends up. The scope worth knowing: **since 5.7.0 the +settings are one document per IDE installation, per project** — two IDEs on the +same machine keep separate settings, and so do two projects in the same IDE. +(Between 5.5.0 and 5.7.0 there was a single global document; it is kept as the +seed a project without settings of its own starts from.) If the safe cannot be read (a locked KWallet, say), the plugin treats that as a failure and refuses to save over it — a failed read is not an empty diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index c985e95e..109e53ed 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -73,8 +73,12 @@ Since **5.5.0** the settings live in the IDE's **PasswordSafe**, not in a file. The old `.idea/claude-code.xml` is read once, copied into the safe, and deleted only after the safe has accepted the copy. Two things follow: -- **Settings are now global**, not per project. The safe is application-wide, - which is also the scope these settings actually had. +- **Settings are kept per IDE installation, per project** since 5.7.0. The safe + is application-wide, but each entry is keyed by the IDE's configuration + directory and the project, so two IDEs — or two checkouts — do not share a + document. The single global document that 5.5.0–5.7.0 wrote is still read as + the seed for a project that has none of its own, which is why it is never + deleted. - **If the safe cannot be read**, the plugin refuses to save over it rather than treating a failed read as an empty configuration. On Linux that usually means a locked KWallet/keyring — unlock it and restart the IDE. A read that failed From 45184a6c259d4b9455b55b5d633a6471f66efb04 Mon Sep 17 00:00:00 2001 From: Lain Date: Sun, 30 Aug 2026 21:40:43 +0200 Subject: [PATCH 7/7] build: carry the 5.8.1 bump into PluginIdentity --- src/main/kotlin/dev/lain/claudejb/util/PluginIdentity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/dev/lain/claudejb/util/PluginIdentity.kt b/src/main/kotlin/dev/lain/claudejb/util/PluginIdentity.kt index 73b40a02..f1e5d9e3 100644 --- a/src/main/kotlin/dev/lain/claudejb/util/PluginIdentity.kt +++ b/src/main/kotlin/dev/lain/claudejb/util/PluginIdentity.kt @@ -2,7 +2,7 @@ package dev.lain.claudejb.util object PluginIdentity { - const val PLUGIN_VERSION = "5.8.0" + const val PLUGIN_VERSION = "5.8.1" private const val PROJECT_URL = "https://github.com/serialexperimentslainnnn/claude-code-for-jetbrains"