From 8ea3f10495e28c8a131b805d51f8f7524895148b Mon Sep 17 00:00:00 2001 From: kirillk Date: Thu, 9 Jul 2026 13:38:47 -0400 Subject: [PATCH] fix(jetbrains): increase user prompt transcript gap --- .changeset/jetbrains-user-prompt-gap.md | 5 +++ .../client/session/ui/SessionLayout.kt | 13 +++++-- .../kilocode/client/session/ui/SessionView.kt | 1 + .../client/session/ui/style/SessionUiStyle.kt | 1 + .../kilocode/client/session/views/TurnView.kt | 8 ++++- .../client/session/SessionUiLayoutTest.kt | 26 ++++++++++++++ .../client/session/ui/SessionLayoutTest.kt | 34 +++++++++++++++++++ .../session/ui/SessionMessageListPanelTest.kt | 20 +++++++++++ 8 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 .changeset/jetbrains-user-prompt-gap.md diff --git a/.changeset/jetbrains-user-prompt-gap.md b/.changeset/jetbrains-user-prompt-gap.md new file mode 100644 index 00000000000..41a2ac21b1b --- /dev/null +++ b/.changeset/jetbrains-user-prompt-gap.md @@ -0,0 +1,5 @@ +--- +"@kilocode/kilo-jetbrains": patch +--- + +Increase spacing before non-initial user prompts in the JetBrains session transcript. diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/SessionLayout.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/SessionLayout.kt index 296a87ef9e7..62e9d4805cb 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/SessionLayout.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/SessionLayout.kt @@ -44,7 +44,7 @@ class SessionLayout( var first = true for (comp in parent.components) { if (!comp.isVisible) continue - if (!first) h += gap + if (!first) h += gap(comp) first = false val child = bounds(ins, w, comp) // Pre-size to available width so HTML panes reflow before we measure @@ -63,7 +63,7 @@ class SessionLayout( var first = true for (comp in parent.components) { if (!comp.isVisible) continue - if (!first) y += gap + if (!first) y += gap(comp) first = false val child = bounds(ins, w, comp) // Fix width first so HTML reflows, then read the resulting height @@ -75,7 +75,7 @@ class SessionLayout( } private fun bounds(ins: Insets, width: Int, comp: Component): Bounds { - val view = comp as? SessionView ?: return Bounds(ins.left, width) + val view = view(comp) ?: return Bounds(ins.left, width) if (view.sessionViewKind != SessionView.Kind.UserPrompt) return Bounds(ins.left, width) val shift = JBUI.scale(SessionUiStyle.SessionLayout.USER_PROMPT_INDENT) val next = width - shift @@ -93,6 +93,13 @@ class SessionLayout( ) } + private fun gap(comp: Component): Int { + if (view(comp)?.sessionGapKind == SessionView.Kind.UserPrompt) return JBUI.scale(SessionUiStyle.SessionLayout.USER_PROMPT_GAP) + return gap + } + + private fun view(comp: Component): SessionView? = comp as? SessionView + private data class Bounds(val left: Int, val width: Int) } diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/SessionView.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/SessionView.kt index 500d228a0b7..99b6b0023fe 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/SessionView.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/SessionView.kt @@ -2,6 +2,7 @@ package ai.kilocode.client.session.ui interface SessionView { val sessionViewKind: Kind + val sessionGapKind: Kind get() = sessionViewKind enum class Kind { Default, diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/style/SessionUiStyle.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/style/SessionUiStyle.kt index 95e3b494af3..846832903c4 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/style/SessionUiStyle.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/style/SessionUiStyle.kt @@ -16,6 +16,7 @@ object SessionUiStyle { /** Geometry for the transcript list and its scroll behavior. */ object SessionLayout { const val GAP = 3 + const val USER_PROMPT_GAP = 10 const val TRANSCRIPT_SCROLLBAR_PADDING = 10 val InnerInsets = Insets( UiStyle.Gap.md(), diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/TurnView.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/TurnView.kt index 40bbdf51894..afdad313b20 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/TurnView.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/TurnView.kt @@ -4,6 +4,7 @@ import ai.kilocode.client.session.SessionFileOpener import ai.kilocode.client.session.model.FileAttachment import ai.kilocode.client.session.model.Message import ai.kilocode.client.session.ui.SessionLayoutPanel +import ai.kilocode.client.session.ui.SessionView import ai.kilocode.client.session.ui.style.SessionEditorStyle import ai.kilocode.client.session.ui.selection.SessionSelection import ai.kilocode.client.session.ui.style.SessionEditorStyleTarget @@ -35,10 +36,15 @@ class TurnView( private val repo: String? = null, private val hover: ((PartView, Boolean) -> Unit)? = null, private val revert: ((String) -> Unit)? = null, -) : SessionLayoutPanel(JBUI.scale(SessionUiStyle.SessionLayout.GAP)), Disposable, SessionEditorStyleTarget { +) : SessionLayoutPanel(JBUI.scale(SessionUiStyle.SessionLayout.GAP)), Disposable, SessionEditorStyleTarget, SessionView { private val messages = LinkedHashMap() + override val sessionViewKind = SessionView.Kind.Default + + override val sessionGapKind: SessionView.Kind + get() = messages.values.firstOrNull { it.isVisible }?.sessionViewKind ?: SessionView.Kind.Default + init { isOpaque = false } diff --git a/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/SessionUiLayoutTest.kt b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/SessionUiLayoutTest.kt index 099f99e5f37..7f5df79dbbc 100644 --- a/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/SessionUiLayoutTest.kt +++ b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/SessionUiLayoutTest.kt @@ -11,10 +11,12 @@ import ai.kilocode.client.session.ui.ConnectionPanel import ai.kilocode.client.session.ui.empty.EmptySessionPanel import ai.kilocode.client.session.ui.LoadingPanel import ai.kilocode.client.session.ui.SessionDropOverlay +import ai.kilocode.client.session.ui.SessionLayoutPanel import ai.kilocode.client.session.ui.prompt.PromptPanel import ai.kilocode.client.session.ui.account.SessionAccountOverlay import ai.kilocode.client.session.ui.SessionMessageListPanel import ai.kilocode.client.session.ui.SessionRootPanel +import ai.kilocode.client.session.ui.SessionView import ai.kilocode.client.session.ui.style.SessionEditorStyle import ai.kilocode.client.session.ui.header.SessionHeaderPanel import ai.kilocode.client.session.ui.style.SessionUiStyle @@ -29,7 +31,9 @@ import ai.kilocode.client.session.views.permission.PermissionView import ai.kilocode.client.session.views.question.QuestionView import ai.kilocode.rpc.dto.MessageWithPartsDto import com.intellij.ui.components.JBScrollPane +import java.awt.Dimension import javax.swing.JLayeredPane +import javax.swing.JPanel @Suppress("UnstableApiUsage") class SessionUiLayoutTest : SessionUiTestBase() { @@ -73,6 +77,24 @@ class SessionUiLayoutTest : SessionUiTestBase() { assertFalse(root.content.components.contains(connection)) } + fun `test transcript uses larger standard gap before prompts after first item`() { + val panel = SessionLayoutPanel(gap = SessionUiStyle.SessionLayout.GAP).apply { + setSize(400, 300) + add(Row(SessionView.Kind.UserPrompt)) + add(Row(SessionView.Kind.Default)) + add(Row(SessionView.Kind.UserPrompt)) + } + + panel.doLayout() + + assertEquals(0, panel.getComponent(0).y) + assertEquals(SessionUiStyle.SessionLayout.GAP, panel.getComponent(1).y - panel.getComponent(0).bounds.maxY.toInt()) + assertEquals( + JBUI.scale(SessionUiStyle.SessionLayout.USER_PROMPT_GAP), + panel.getComponent(2).y - panel.getComponent(1).bounds.maxY.toInt(), + ) + } + fun `test drop overlay is attached under root overlay layer`() { val root = find(ui) val drop = find(ui) @@ -640,4 +662,8 @@ class SessionUiLayoutTest : SessionUiTestBase() { .components .single() .let { it as javax.swing.JComponent } + + private class Row(override val sessionViewKind: SessionView.Kind) : JPanel(), SessionView { + override fun getPreferredSize() = Dimension(100, 10) + } } diff --git a/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/SessionLayoutTest.kt b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/SessionLayoutTest.kt index f76cfcc03d5..9abefa2f210 100644 --- a/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/SessionLayoutTest.kt +++ b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/SessionLayoutTest.kt @@ -1,5 +1,6 @@ package ai.kilocode.client.session.ui +import ai.kilocode.client.session.ui.style.SessionUiStyle import com.intellij.testFramework.fixtures.BasePlatformTestCase import com.intellij.util.ui.JBUI import com.intellij.util.ui.components.BorderLayoutPanel @@ -68,6 +69,29 @@ class SessionLayoutTest : BasePlatformTestCase() { assertEquals(33, c3.y) // 10 + 4 + 15 + 4 } + fun `test user prompt after first component uses prompt gap`() { + val p = panel(gap = 4, width = 300) + val c1 = label(height = 20) + val c2 = view(height = 30, kind = SessionView.Kind.UserPrompt) + p.add(c1) + p.add(c2) + p.doLayout() + + assertEquals(20 + JBUI.scale(SessionUiStyle.SessionLayout.USER_PROMPT_GAP), c2.y) + } + + fun `test first user prompt uses standard gap`() { + val p = panel(gap = 4, width = 300) + val c1 = view(height = 20, kind = SessionView.Kind.UserPrompt) + val c2 = label(height = 30) + p.add(c1) + p.add(c2) + p.doLayout() + + assertEquals(0, c1.y) + assertEquals(20 + 4, c2.y) + } + fun `test all children receive full available width`() { val p = panel(width = 500) val c1 = label(height = 20) @@ -199,6 +223,16 @@ class SessionLayoutTest : BasePlatformTestCase() { assertEquals(10 + 4 + 15 + 4 + 20, size.height) } + fun `test preferredLayoutSize uses prompt gap before non-first user prompt`() { + val p = panel(gap = 4, width = 300) + p.add(label(height = 10)) + p.add(view(height = 15, kind = SessionView.Kind.UserPrompt)) + p.doLayout() + + val size = p.layout.preferredLayoutSize(p) + assertEquals(10 + JBUI.scale(SessionUiStyle.SessionLayout.USER_PROMPT_GAP) + 15, size.height) + } + fun `test preferredLayoutSize with no children is zero`() { val p = panel(width = 300) val size = p.layout.preferredLayoutSize(p) diff --git a/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/SessionMessageListPanelTest.kt b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/SessionMessageListPanelTest.kt index b409f2ab393..bcb77d038d8 100644 --- a/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/SessionMessageListPanelTest.kt +++ b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/SessionMessageListPanelTest.kt @@ -42,6 +42,7 @@ import com.intellij.openapi.util.Disposer import com.intellij.testFramework.fixtures.BasePlatformTestCase import com.intellij.ui.components.JBLabel import com.intellij.ui.components.JBScrollPane +import com.intellij.util.ui.JBUI import com.intellij.util.ui.UIUtil import java.awt.BorderLayout import java.awt.Color @@ -105,6 +106,25 @@ class SessionMessageListPanelTest : BasePlatformTestCase() { assertEquals(right, left) } + fun `test top level user turns use prompt gap after previous turn`() { + model.upsertMessage(msg("u1", "user")) + model.updateContent("u1", part("p1", "u1", "text", text = "first")) + model.upsertMessage(msg("a1", "assistant")) + model.updateContent("a1", part("p2", "a1", "text", text = "answer")) + model.upsertMessage(msg("u2", "user")) + model.updateContent("u2", part("p3", "u2", "text", text = "second")) + + panel.setSize(600, 1000) + layout(panel) + val first = panel.findTurn("u1")!! + val second = panel.findTurn("u2")!! + + assertEquals( + JBUI.scale(SessionUiStyle.SessionLayout.USER_PROMPT_GAP), + second.y - first.bounds.maxY.toInt(), + ) + } + // ------ TurnAdded ------ fun `test user message creates turn and is findable by message id`() {