diff --git a/.changeset/stellar-forest-feedback.md b/.changeset/stellar-forest-feedback.md
new file mode 100644
index 0000000000..98dd0d17c1
--- /dev/null
+++ b/.changeset/stellar-forest-feedback.md
@@ -0,0 +1,5 @@
+---
+"@kilocode/kilo-jetbrains": patch
+---
+
+Add Feedback & Support to the JetBrains empty session screen.
diff --git a/.kilo/plans/1780256109130-stellar-forest.md b/.kilo/plans/1780256109130-stellar-forest.md
new file mode 100644
index 0000000000..3aafa6c0dd
--- /dev/null
+++ b/.kilo/plans/1780256109130-stellar-forest.md
@@ -0,0 +1,109 @@
+# Plan: JetBrains Feedback & Support Button
+
+## Goal
+Add the VS Code empty-state `Feedback & Support` affordance to the JetBrains plugin, including a non-modal popup with the same three destinations:
+
+- GitHub issues: `https://github.com/Kilo-Org/kilocode/issues/new/choose`
+- Discord: `https://kilo.ai/discord`
+- Customer support: `https://kilo.ai/support`
+
+The popup must hide when clicking outside or pressing Escape.
+
+## Findings
+
+- The JetBrains empty state is `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/EmptySessionPanel.kt`.
+- It already renders the logo, welcome copy, recents, and `Show History`; this is the right place to add the button.
+- The VS Code implementation is:
+ - Button: `packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx`
+ - Popup content: `packages/kilo-vscode/webview-ui/src/components/chat/FeedbackDialog.tsx`
+ - Styling: `packages/kilo-vscode/webview-ui/src/styles/welcome.css`
+- Local IntelliJ API source is available at `$INTELLIJ_REPO=/Users/kirillk/products/intellij-community`.
+- IntelliJ popup API confirmed in local source:
+ - `JBPopupFactory.getInstance().createComponentPopupBuilder(content, focusComponent)`
+ - `setModalContext(false)` for non-modal context
+ - `setRequestFocus(false)` / `setFocusable(false)` if we want the popup not to steal focus
+ - `setCancelOnClickOutside(true)` for outside-click dismissal
+ - `setCancelKeyEnabled(true)` for Escape dismissal
+ - `setCancelOnWindowDeactivation(true)` and `setCancelOnOtherWindowOpen(true)` are available and appropriate for cleanup
+- IntelliJ platform icons found in `AllIcons`:
+ - Feedback button: `AllIcons.Ide.Feedback`
+ - GitHub action: `AllIcons.Vcs.Vendors.Github`
+ - Support/help action: `AllIcons.Actions.Help` or `AllIcons.General.ContextHelp`
+ - Discord: no platform icon found. Add a Discord icon by borrowing the existing VS Code/kilo-ui Discord SVG artwork and adapting it into the JetBrains plugin resources with IntelliJ-compatible SVG colors and dark variant if needed.
+
+## Implementation Steps
+
+1. Add localized JetBrains bundle keys.
+ - Add base English strings to `frontend/src/main/resources/messages/KiloBundle.properties`:
+ - `feedback.button=Feedback & Support`
+ - `feedback.dialog.message=We'd love to hear your feedback or help with any issues you're experiencing.`
+ - `feedback.dialog.github=Report an issue on GitHub`
+ - `feedback.dialog.discord=Join our Discord community`
+ - `feedback.dialog.support=Customer Support`
+ - Reuse existing cancel text if present; otherwise add `common.cancel=Cancel` only if needed.
+ - Add matching keys to localized `KiloBundle_*.properties` files. If accurate translations are not already available from the VS Code i18n files, copy the VS Code translations for matching locales where practical and leave English fallback only if the JetBrains bundle mechanism supports it cleanly.
+
+2. Extend `EmptySessionPanel` UI.
+ - Add a new retained `FeedbackButton`, styled to match VS Code conceptually:
+ - feedback icon + `Feedback & Support` text
+ - dashed link-colored border
+ - transparent background
+ - hand cursor
+ - hover state fills with link color and flips foreground/background for contrast
+ - Use IntelliJ theme APIs instead of raw colors:
+ - link color: `JBUI.CurrentTheme.Link.Foreground.ENABLED`
+ - hover link color: `JBUI.CurrentTheme.Link.Foreground.HOVERED` if useful
+ - editor/panel background from existing session style or `UiStyle.Colors.editorBackground()` / component background
+ - spacing via `UiStyle.Gap` / `JBUI.Borders`
+ - rounded arc via `UiStyle.Arc.component()` or `JBUI.getInt("Button.arc", 6)` at paint time
+ - Add the feedback button below the existing `Show History` button in the empty-state south area, keeping the current centered layout.
+ - Keep Swing mutations on EDT and preserve retained-component behavior.
+
+3. Implement the feedback popup in frontend Swing code.
+ - Add a small popup builder method/class inside `EmptySessionPanel.kt` or a new nearby UI file if the file would become too large.
+ - Content should mirror VS Code:
+ - Kilo logo at top using existing `/icons/kilo-content.svg`
+ - message text
+ - three action buttons/rows for GitHub, Discord, and Customer Support
+ - every action should have an icon: `AllIcons.Vcs.Vendors.Github` for GitHub, the borrowed Discord SVG for Discord, and `AllIcons.Actions.Help` or `AllIcons.General.ContextHelp` for Customer Support
+ - optional Cancel button if it fits the JetBrains UX; Escape/outside click already dismisses
+ - Add the Discord icon asset under `frontend/src/main/resources/icons/` using the plugin's existing icon naming pattern, for example `discord.svg` and `discord_dark.svg` if the borrowed asset needs separate theme variants.
+ - Load the Discord icon with `IconLoader.getIcon("/icons/discord.svg", EmptySessionPanel::class.java)` or a small Kilo-owned icon object if multiple call sites need it.
+ - Use `BrowserUtil.browse(url)` to open links.
+ - Close the popup after opening a URL.
+ - Use `JBPopupFactory.createComponentPopupBuilder(content, null)` and configure:
+ - `.setModalContext(false)`
+ - `.setRequestFocus(false)`
+ - `.setFocusable(false)` unless keyboard tabbing inside popup is desired; if buttons need keyboard focus, use `.setFocusable(true)` with `.setRequestFocus(false)`
+ - `.setCancelOnClickOutside(true)`
+ - `.setCancelKeyEnabled(true)`
+ - `.setCancelOnWindowDeactivation(true)`
+ - `.setCancelOnOtherWindowOpen(true)`
+ - `.setResizable(false)`
+ - `.setMovable(false)`
+ - Show with `popup.showUnderneathOf(feedbackButton)`.
+
+4. Add test coverage in `EmptySessionPanelTest.kt`.
+ - Assert feedback button text uses `KiloBundle.message("feedback.button")`.
+ - Assert feedback button has hand cursor and visible border semantics.
+ - Add an injectable browse callback or popup action callback only if needed for testing without launching browsers; keep it minimal and avoid production-only test hooks if component traversal can exercise enough behavior.
+ - Add a test that clicking the feedback button creates/shows popup content, if feasible in `BasePlatformTestCase`; otherwise test the retained popup content builder/action rows directly through package-internal methods.
+ - Assert the three action labels exist and URL callbacks map to the same URLs as VS Code.
+ - Assert the Discord action has an icon, so the plan cannot regress to a text-only Discord row.
+
+5. Add release note.
+ - This is user-facing for the JetBrains plugin, so add a patch changeset under `.changeset/` unless the repo has a JetBrains-specific release-note mechanism that supersedes changesets.
+ - Suggested text: `Add Feedback & Support to the JetBrains empty session screen.`
+
+6. Verify.
+ - Run the focused frontend/JetBrains tests first, preferably from `packages/kilo-jetbrains/`:
+ - `./gradlew :frontend:test --tests "ai.kilocode.client.session.ui.EmptySessionPanelTest"`
+ - Run JetBrains typecheck from `packages/kilo-jetbrains/`:
+ - `bun run typecheck` or `./gradlew typecheck`
+ - If touching only frontend UI and tests pass, no backend/SDK generation is needed.
+
+## Notes
+
+- Do not add Kotlin UI DSL, Compose, or JCEF.
+- Do not modify shared opencode files; this work is entirely under `packages/kilo-jetbrains/` plus a changeset.
+- Prefer platform icons where available, but explicitly borrow/add the Discord icon because `AllIcons` does not provide one and the popup should match the VS Code button set visually.
diff --git a/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/app/KiloBackendAppService.kt b/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/app/KiloBackendAppService.kt
index 240d562cdc..a3aa435dbb 100644
--- a/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/app/KiloBackendAppService.kt
+++ b/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/app/KiloBackendAppService.kt
@@ -25,6 +25,7 @@ import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
@@ -346,6 +347,7 @@ class KiloBackendAppService private constructor(
sessions.start(connection.api!!, connection.apiClient!!, connection.port, connection.events)
chat.start(connection.apiClient!!, connection.port, connection.events)
workspaces.start(connection.api!!, connection.apiClient!!, connection.port, connection.events)
+ startWatchingGlobalSseEvents()
setTelemetry(true)
captureBackend("Backend Connected", mapOf("portKnown" to "true"))
captureLoad("Backend Load Completed", start, mapOf(
@@ -362,7 +364,6 @@ class KiloBackendAppService private constructor(
)
)
log.info("Application started — config, profile, notifications loaded")
- startWatchingGlobalSseEvents()
} catch (e: TimeoutCancellationException) {
val err = LoadError(
resource = "app",
@@ -647,7 +648,7 @@ class KiloBackendAppService private constructor(
synchronized(loadLock) {
if (eventWatcher?.isActive == true) return
log.info("Started watching global SSE events (config.updated, disposed)")
- eventWatcher = cs.launch {
+ eventWatcher = cs.launch(start = CoroutineStart.UNDISPATCHED) {
connection.events.collect { event ->
when (event.type) {
"global.config.updated" -> {
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 c9a0ae2845..d78a843691 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
@@ -12,7 +12,7 @@ import ai.kilocode.client.session.model.SessionModelEvent
import ai.kilocode.client.session.model.SessionState
import ai.kilocode.client.session.scroll.SessionScroll
import ai.kilocode.client.session.ui.ConnectionPanel
-import ai.kilocode.client.session.ui.EmptySessionPanel
+import ai.kilocode.client.session.ui.empty.EmptySessionPanel
import ai.kilocode.client.session.ui.LoadingPanel
import ai.kilocode.client.session.ui.ReasoningPicker
import ai.kilocode.client.session.ui.mode.ModePicker
diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/empty/EmptySessionFeedback.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/empty/EmptySessionFeedback.kt
new file mode 100644
index 0000000000..ad6f79d05b
--- /dev/null
+++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/empty/EmptySessionFeedback.kt
@@ -0,0 +1,133 @@
+package ai.kilocode.client.session.ui.empty
+
+import ai.kilocode.client.plugin.KiloBundle
+import ai.kilocode.client.ui.UiStyle
+import ai.kilocode.client.ui.layout.HAlign
+import ai.kilocode.client.ui.layout.Stack
+import ai.kilocode.client.ui.layout.VAlign
+import ai.kilocode.client.ui.layout.align
+import com.intellij.icons.AllIcons
+import com.intellij.openapi.Disposable
+import com.intellij.openapi.ui.popup.Balloon
+import com.intellij.openapi.ui.popup.JBPopupFactory
+import com.intellij.openapi.util.Disposer
+import com.intellij.openapi.util.IconLoader
+import com.intellij.ui.awt.RelativePoint
+import com.intellij.ui.components.JBLabel
+import com.intellij.util.concurrency.annotations.RequiresEdt
+import com.intellij.util.ui.JBUI
+import com.intellij.util.ui.UIUtil
+import com.intellij.xml.util.XmlStringUtil
+import java.awt.Cursor
+import java.awt.Point
+import java.awt.event.MouseAdapter
+import java.awt.event.MouseEvent
+import javax.swing.JButton
+import javax.swing.JComponent
+
+internal class EmptySessionFeedback(
+ private val browse: (String) -> Unit,
+) : Disposable {
+ private var balloon: Balloon? = null
+
+ val button: JButton = FeedbackButton().apply {
+ addActionListener { popup() }
+ }
+
+ @RequiresEdt
+ private fun popup() {
+ balloon?.let {
+ it.hide()
+ return
+ }
+
+ val content = content { url ->
+ browse(url)
+ balloon?.hide()
+ }
+ val point = RelativePoint(button, Point(button.width / 2, button.height + JBUI.scale(1)))
+ val popup = JBPopupFactory.getInstance()
+ .createBalloonBuilder(content)
+ .setHideOnClickOutside(true)
+ .setHideOnKeyOutside(true)
+ .setHideOnAction(true)
+ .setHideOnFrameResize(true)
+ .setBorderColor(UiStyle.Balloon.border())
+ .setFillColor(UiStyle.Balloon.bg())
+ .setBorderInsets(UiStyle.Balloon.insets())
+ .setPointerSize(UiStyle.Balloon.pointer())
+ .setCornerRadius(UiStyle.Balloon.arc())
+ .createBalloon()
+
+ balloon = popup
+ popup.setAnimationEnabled(false)
+ Disposer.register(popup) { balloon = null }
+ popup.show(point, Balloon.Position.below)
+ }
+
+ private class FeedbackButton : EmptySessionPanel.ShowHistoryButton(buttonHtml(), AllIcons.Ide.Feedback)
+
+ override fun dispose() {
+ balloon?.hide()
+ }
+
+ private class ActionButton(text: String, icon: javax.swing.Icon, action: () -> Unit) : JButton(text, icon) {
+ init {
+ cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)
+ addActionListener { action() }
+ }
+ }
+
+ companion object {
+ @RequiresEdt
+ fun content(open: (String) -> Unit): JComponent {
+ val logo = JBLabel(IconLoader.getIcon("/icons/kilo-content.svg", EmptySessionPanel::class.java)).apply {
+ horizontalAlignment = JBLabel.CENTER
+ cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)
+ addMouseListener(object : MouseAdapter() {
+ override fun mouseClicked(e: MouseEvent) {
+ open(KILO_URL)
+ }
+ })
+ }
+ val msg = JBLabel(messageHtml()).apply {
+ foreground = UIUtil.getLabelForeground()
+ horizontalAlignment = JBLabel.CENTER
+ }
+ val actions = Stack.vertical(gap = UiStyle.Gap.sm())
+ .next(ActionButton(KiloBundle.message("feedback.dialog.github"), AllIcons.Vcs.Vendors.Github) {
+ open(GITHUB_ISSUES_URL)
+ }.align(HAlign.CENTER, VAlign.CENTER))
+ .next(ActionButton(KiloBundle.message("feedback.dialog.discord"), DISCORD_ICON) {
+ open(DISCORD_URL)
+ }.align(HAlign.CENTER, VAlign.CENTER))
+ .next(ActionButton(KiloBundle.message("feedback.dialog.support"), AllIcons.Actions.Help) {
+ open(SUPPORT_URL)
+ }.align(HAlign.CENTER, VAlign.CENTER))
+
+ return Stack.vertical(gap = UiStyle.Gap.lg())
+ .fill(UiStyle.Gap.sm())
+ .next(logo.align(HAlign.CENTER, VAlign.CENTER))
+ .next(msg.align(HAlign.CENTER, VAlign.CENTER))
+ .fill(UiStyle.Gap.xs())
+ .next(actions.align(HAlign.CENTER, VAlign.CENTER))
+ .fill(UiStyle.Gap.xs())
+ }
+
+ fun urls() = listOf(GITHUB_ISSUES_URL, DISCORD_URL, SUPPORT_URL)
+
+ private fun messageHtml() = XmlStringUtil.wrapInHtml(
+ "