From 5fc0829ee8e06bea60a533247007501cab84e7a3 Mon Sep 17 00:00:00 2001 From: kirillk Date: Tue, 19 May 2026 14:29:15 -0400 Subject: [PATCH] fix(jetbrains): hide account overlay when not logged in --- .../jetbrains-logged-out-account-panel.md | 5 + .../ai/kilocode/client/session/SessionUi.kt | 1 - .../ui/account/SessionAccountOverlay.kt | 95 ++----------------- .../ui/account/SessionAccountOverlayTest.kt | 35 ++++--- 4 files changed, 36 insertions(+), 100 deletions(-) create mode 100644 .changeset/jetbrains-logged-out-account-panel.md diff --git a/.changeset/jetbrains-logged-out-account-panel.md b/.changeset/jetbrains-logged-out-account-panel.md new file mode 100644 index 00000000000..67599a66745 --- /dev/null +++ b/.changeset/jetbrains-logged-out-account-panel.md @@ -0,0 +1,5 @@ +--- +"@kilocode/kilo-jetbrains": patch +--- + +Show the logged-out account status in the same rounded panel as the logged-in account overlay, with a "Not logged in" label, hidden picker/balance, and a profile icon to open settings. diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt index 0df19d7b054..d08170e3050 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt @@ -149,7 +149,6 @@ class SessionUi( account = SessionAccountOverlay( select = { org -> controller.selectOrganization(org) }, - login = { controller.openProfile() }, profile = { controller.openProfile() }, ) root.addOverlay(account) { pane, child -> diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/account/SessionAccountOverlay.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/account/SessionAccountOverlay.kt index ce2673d1ad8..b3f4cfe6d7d 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/account/SessionAccountOverlay.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/account/SessionAccountOverlay.kt @@ -18,17 +18,13 @@ import com.intellij.ui.components.JBList import com.intellij.util.ui.JBUI import com.intellij.util.ui.UIUtil import com.intellij.util.ui.components.BorderLayoutPanel -import java.awt.CardLayout import java.awt.Cursor -import java.awt.GridBagConstraints -import java.awt.GridBagLayout import java.awt.event.KeyEvent import java.awt.event.MouseAdapter import java.awt.event.MouseEvent import java.text.DecimalFormat import javax.swing.Box import javax.swing.BoxLayout -import javax.swing.JButton import javax.swing.JComponent import javax.swing.JPanel import javax.swing.KeyStroke @@ -38,38 +34,14 @@ import javax.swing.ScrollPaneConstants /** * Compact account overlay shown in the top-right of the empty session screen. * - * Displays logged-out prompt or logged-in account/balance info. + * Only visible when logged in. Hidden when not logged in or no profile is available. * Visibility is controlled entirely by [onEvent] — never set [isVisible] externally. */ internal class SessionAccountOverlay( private val select: (String?) -> Unit, - private val login: () -> Unit, private val profile: () -> Unit, ) : BorderLayoutPanel() { - companion object { - private const val CARD_OUT = "out" - private const val CARD_IN = "in" - } - - private val loginLabel = JBLabel(KiloBundle.message("profile.notLoggedIn")).apply { - foreground = UiStyle.Colors.weak() - } - private val loginBtn = JButton(KiloBundle.message("profile.action.login")).apply { - isOpaque = false - addActionListener { login() } - } - private val outCard = JPanel(GridBagLayout()).apply { - isOpaque = false - add(loginLabel, GridBagConstraints().apply { - gridx = 0; gridy = 0; anchor = GridBagConstraints.WEST - }) - add(loginBtn, GridBagConstraints().apply { - gridx = 0; gridy = 1; anchor = GridBagConstraints.CENTER - insets = JBUI.insetsTop(UiStyle.Gap.sm()) - }) - } - private val picker = PickerButton().apply { isEnabled = false text = " " @@ -110,27 +82,13 @@ internal class SessionAccountOverlay( addToCenter(row) } - private val inCard = JPanel(GridBagLayout()).apply { - isOpaque = false - add(panel, GridBagConstraints().apply { - gridx = 0; gridy = 0; fill = GridBagConstraints.HORIZONTAL - }) - } - - private val cardLayout = CardLayout() - private val cards = JPanel(cardLayout).apply { - isOpaque = false - add(outCard, CARD_OUT) - add(inCard, CARD_IN) - } - private var choices: List = emptyList() private var currentOrgId: String? = null init { isOpaque = false isVisible = false - addToCenter(cards) + addToCenter(panel) } fun onEvent(event: SessionControllerEvent.AccountOverlayChanged) { @@ -148,16 +106,13 @@ internal class SessionAccountOverlay( val snap = event.account val prof = snap.profile if (prof == null) { - if (!snap.transient) { - layout = showCard(CARD_OUT) || layout - if (!isVisible) { - isVisible = true - layout = true - } + if (!snap.transient && isVisible) { + isVisible = false + layout = true + paint = true } } else { layout = updateLoggedIn(prof, snap.switching, snap.targetOrgId) || layout - layout = showCard(CARD_IN) || layout if (!isVisible) { isVisible = true layout = true @@ -169,22 +124,9 @@ internal class SessionAccountOverlay( if (layout || paint) repaint() } - private fun activeCard(): String? { - for (i in 0 until cards.componentCount) { - val comp = cards.getComponent(i) - if (comp.isVisible) return if (comp === inCard) CARD_IN else CARD_OUT - } - return null - } - - private fun showCard(card: String): Boolean { - if (activeCard() == card) return false - cardLayout.show(cards, card) - return true - } - private fun updateLoggedIn(prof: ai.kilocode.rpc.dto.ProfileDto, switching: Boolean, target: String?): Boolean { var layout = false + val orgs = prof.organizations val next = listOf(AccountChoice(null, KiloBundle.message("profile.personalAccount"))) + orgs.map { org -> AccountChoice(org.id, org.name) } @@ -216,11 +158,6 @@ internal class SessionAccountOverlay( } if (picker.toolTipText != tip) picker.toolTipText = tip - if (!picker.isVisible) { - picker.isVisible = true - layout = true - } - layout = syncBalance(prof) || layout return layout } @@ -333,23 +270,7 @@ internal class SessionAccountOverlay( popup.showUnderneathOf(picker) } - internal fun loggedInVisible() = isVisible && cards.let { - var card = CARD_OUT - for (i in 0 until it.componentCount) { - val comp = it.getComponent(i) - if (comp.isVisible) card = if (comp === inCard) CARD_IN else CARD_OUT - } - card == CARD_IN - } - - internal fun loggedOutVisible() = isVisible && cards.let { - for (i in 0 until it.componentCount) { - val comp = it.getComponent(i) - if (comp.isVisible) return@let comp === outCard - } - false - } - + internal fun loggedInVisible() = isVisible internal fun accountTitle(): String? = picker.text?.removeSuffix(" ▾")?.ifBlank { null } internal fun pickerEnabled() = picker.isEnabled internal fun pickerVisible() = picker.isVisible diff --git a/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/account/SessionAccountOverlayTest.kt b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/account/SessionAccountOverlayTest.kt index 379e4222656..e6f5263cee0 100644 --- a/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/account/SessionAccountOverlayTest.kt +++ b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/account/SessionAccountOverlayTest.kt @@ -15,15 +15,12 @@ import com.intellij.icons.AllIcons class SessionAccountOverlayTest : SessionControllerTestBase() { private lateinit var panel: SessionAccountOverlay - private val selected = mutableListOf() - private var loginCalls = 0 private var profileCalls = 0 override fun setUp() { super.setUp() panel = SessionAccountOverlay( - select = { org -> selected.add(org) }, - login = { loginCalls++ }, + select = { }, profile = { profileCalls++ }, ) } @@ -56,13 +53,12 @@ class SessionAccountOverlayTest : SessionControllerTestBase() { private fun org(id: String, name: String, role: String = "MEMBER") = ProfileOrganizationDto(id = id, name = name, role = role) - // --- test 1: logged-out state shows login prompt --- + // --- test 1: logged-out state hides the overlay entirely --- - fun `test logged out state is visible with login button`() { + fun `test logged out state hides overlay`() { show(snap(null)) - assertTrue(panel.isVisible) - assertTrue(panel.loggedOutVisible()) + assertFalse(panel.isVisible) } // --- test 2: logged-in personal account shows picker title --- @@ -113,18 +109,22 @@ class SessionAccountOverlayTest : SessionControllerTestBase() { // --- test 4: programmatic update does not call select callback --- fun `test programmatic update does not call select callback`() { + val selected = mutableListOf() + val p = SessionAccountOverlay( + select = { org -> selected.add(org) }, + profile = {}, + ) val acme = org("org_1", "Acme") val prof = profile( email = "user@example.com", organizations = listOf(acme), currentOrgId = null, ) - // Show with personal account selected - show(snap(prof)) + edt { p.onEvent(SessionControllerEvent.AccountOverlayChanged.Show(snap(prof))) } selected.clear() // Show again with same profile - no user selection - show(snap(prof)) + edt { p.onEvent(SessionControllerEvent.AccountOverlayChanged.Show(snap(prof))) } assertEquals(0, selected.size) } @@ -226,7 +226,7 @@ class SessionAccountOverlayTest : SessionControllerTestBase() { ) show(transientSnap) - // Should remain visible and logged-in, not flash to logged-out + // Should remain visible and logged-in, not flash to hidden assertTrue(panel.isVisible) assertTrue(panel.loggedInVisible()) } @@ -317,4 +317,15 @@ class SessionAccountOverlayTest : SessionControllerTestBase() { assertTrue(panel.balanceVisible()) assertSame(icon, panel.balanceIcon()) } + + // --- test 16: non-transient null profile after login hides overlay --- + + fun `test non-transient null profile after login hides overlay`() { + show(snap(profile(email = "user@example.com"))) + assertTrue(panel.isVisible) + + show(snap(null)) + + assertFalse(panel.isVisible) + } }