Skip to content

Improve JetBrains session usability#11015

Merged
kirillk merged 32 commits into
mainfrom
tide-clipper
Jun 9, 2026
Merged

Improve JetBrains session usability#11015
kirillk merged 32 commits into
mainfrom
tide-clipper

Conversation

@kirillk

@kirillk kirillk commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR makes JetBrains chat sessions easier to read, scan, and interact with during normal use. It focuses on polishing transcript behavior rather than adding one large standalone feature.

Added Improvements

  • Added dedicated tool views for search, glob, and read output.
  • Added cleaner search target presentation with clipped, stacked labels.
  • Added repo-relative paths for search output when possible.
  • Added VS Code-style session view icons for clearer visual labels.
  • Added better reasoning behavior while streaming and after completion.
  • Added more compact reasoning and session card spacing.
  • Added softer card borders and hover behavior so the transcript feels less noisy.
  • Fixed mouse wheel and near-bottom scrolling issues so the transcript no longer snaps back, bounces, or unexpectedly resumes following when the user scrolls upward.
  • Fixed scroll-to-bottom recovery so users can rejoin auto-follow after moving away from the transcript tail.
  • Added heavy streaming scroll stress regression tests covering user-driven bottom recovery and the scroll-to-bottom button during large streamed updates.
  • Added markdown cleanup so separator lines do not distract from the message content.
  • Added stress and UI tests around scrolling, markdown rendering, reasoning, and tool views.

Verification

  • Ran focused JetBrains scroll tests, including the newly added heavy streaming scroll stress regressions.
  • Ran JetBrains focused tests during development.
  • Ran JetBrains typecheck successfully.
  • Added changesets for the user-facing polish included in this branch.

Screenshots

Light

Screen Shot 2026-06-09 at 1 00 45 PM

Dark

Screen Shot 2026-06-09 at 1 02 15 PM

@kirillk kirillk changed the title Improve JetBrains reasoning display Improve JetBrains session scrolling and reasoning display Jun 8, 2026
@kirillk kirillk changed the title Improve JetBrains session scrolling and reasoning display Improve JetBrains session usability Jun 9, 2026
@kirillk kirillk marked this pull request as ready for review June 9, 2026 16:58
@kilo-code-bot

kilo-code-bot Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
ToolSupport.kt 214 Potential double-release of editor: releaseEditor lambda registered after setDisposedWith fires first (LIFO), then EditorTextField.dispose() tries to release the same editor a second time.
MdViewHybrid.kt 503 Same double-release risk as above — releaseEditor lambda fires before EditorTextField.dispose() due to LIFO disposal order.
Previously Flagged — Resolved
  • SessionScroll.preserve() — The seq++ / followBottom re-entrancy issue from the previous review is now addressed: after preserve() computes tail, it calls seq++ again when tail == true, which restarts the follow chain correctly.
  • Screenshots — PR description now includes before/after light and dark screenshots.
Other Observations (not in diff)
  • SessionScroll.atBottom() now returns false immediately when !tail, delegating the "physically near bottom" check to near(). This is intentional: the pause flag allows the scroll to be near the bottom without treating it as at-bottom for follow purposes.
  • SessionSelection.clearSelection() — changing from select(pos, pos) to caretPosition = pos avoids firing a redundant selection-change event that previously triggered spurious caretUpdate callbacks; the test assertion update (assertNullassertTrue(isNullOrEmpty)) accommodates the caret-only move leaving an empty rather than null selectedText.
  • ToolSupport.kt / MdViewHybrid.kt editor leak fix — the intent is correct (editors held by EditorTextField can outlive the disposable when the component is never shown, because EditorTextField.dispose() only releases an editor that was already created). The explicit releaseEditor call in the lambda covers that gap. The LIFO ordering concern raised above should be verified; if EditorTextField.dispose() guards against double-release internally, the risk is benign.
  • SessionUiTestBase.tearDown() — adding Disposer.dispose(ui) before scope.cancel() ensures editor resources from MdViewHybrid and ToolBody code blocks are freed in tests, which is the root cause of the leak the stress tests were catching.
Files Reviewed (incremental, 11 changed files)
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/scroll/SessionScroll.kt — previous WARNING resolved; no new issues
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/tool/ToolSupport.kt — 1 WARNING
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/md/MdViewHybrid.kt — 1 WARNING
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/selection/SessionSelection.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/base/GenericView.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/SessionScrollTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/SessionUiTestBase.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/SessionSelectionCopyTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/views/ReasoningViewStressTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/views/permission/PermissionViewTest.kt
  • .changeset/fix-jetbrains-session-scroll.md

Fix these issues in Kilo Cloud


Reviewed by claude-4.6-sonnet-20260217 · 2,301,415 tokens

Review guidance: REVIEW.md from base branch main

@kirillk

kirillk commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the actionable scroll review warning:

  • Updated SessionScroll.preserve() so anchor-preserving operations that still leave the transcript at tail cancel stale in-flight follow passes while keeping tail-follow state active for the next content update.
  • Kept the existing anchor-preservation behavior intact so expanding a tool at the bottom does not immediately jump the clicked header.

Verification:

  • ./gradlew --no-parallel :frontend:test --tests 'ai.kilocode.client.session.SessionScrollTest.test user scrolling to bottom during massive stream resumes following' --tests 'ai.kilocode.client.session.SessionScrollTest.test scroll button resumes following during massive stream'
  • ./gradlew --no-parallel typecheck

I also reran the tool-expansion scroll assertion group; scroll assertions pass, but the known unrelated IntelliJ editor disposal failure still appears in that test path.

The screenshot / screen recording item is a PR process suggestion rather than a code change.

val field = ToolField(preview(tool), SessionEditorStyle.current()).also { ed ->
ed.setDisposedWith(disposable)
Disposer.register(disposable) {
ed.getEditor(false)?.let(EditorFactory.getInstance()::releaseEditor)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Potential double-release of the editor due to LIFO disposal ordering.

Disposer.register(disposable, this) (done internally by setDisposedWith) and Disposer.register(disposable) { releaseEditor } register two children on the same parent. IntelliJ's Disposer disposes children in LIFO order, so the explicit releaseEditor lambda fires before EditorTextField.dispose(). That means:

  1. Lambda: getEditor(false) → non-null → EditorFactory.releaseEditor(editor) called.
  2. Then EditorTextField.dispose(): calls releaseEditor(myEditor) a second time on the already-released editor.

To avoid this, register the lambda before setDisposedWith so it ends up later in the LIFO queue (i.e., fires after EditorTextField is already disposed and myEditor is null), or skip the explicit lambda and rely solely on setDisposedWith / EditorTextField.dispose() which already releases the editor internally.

CodeField(file, opts, text).also { ed ->
ed.setDisposedWith(disposable)
Disposer.register(disposable) {
ed.getEditor(false)?.let(EditorFactory.getInstance()::releaseEditor)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Same potential double-release as in ToolSupport.kt: the releaseEditor lambda is registered after setDisposedWith, so with LIFO disposal it fires first and releases the editor, then EditorTextField.dispose() (called via setDisposedWith) tries to release the same editor again. Register the lambda before setDisposedWith to ensure it runs after EditorTextField is already cleaned up.

@kirillk kirillk merged commit 08c0ef6 into main Jun 9, 2026
21 checks passed
@kirillk kirillk deleted the tide-clipper branch June 9, 2026 18:26
@kirillk

kirillk commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

reviews will be fixed in the next PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants