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/CODEOWNERS b/CODEOWNERS index 02da49f5..ef10ffeb 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 @@ -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. # 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 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() 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 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/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" 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")