Back to Kilocode

Plan: JetBrains Question-Style Scroll Button

.kilo/plans/1779990397226-quiet-canyon.md

7.3.187.9 KB
Original Source

Plan: JetBrains Question-Style Scroll Button

Goal

Fix JetBrains session scrolling so that when any inline prompt card appears while the scroll/jump button is already visible, the transcript scroll position is preserved and only the scroll button state changes. This applies to all current BaseQuestionView-backed session cards: questions, permissions, and login-required prompts. Preserve existing behavior that question navigation, review/back/next, and bottom-following content changes auto-scroll when they are supposed to.

Findings

  • The relevant files are packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt, .../scroll/SessionScroll.kt, .../views/question/QuestionView.kt, .../views/permission/PermissionView.kt, .../views/LoginRequiredView.kt, .../views/base/BaseQuestionView.kt, and frontend/src/test/kotlin/ai/kilocode/client/session/SessionScrollTest.kt.
  • QuestionView, PermissionView, and LoginRequiredView do not inherit from BaseQuestionView; they compose it as the shared card shell. Scroll behavior should stay outside BaseQuestionView because it is a pure visual component with no session scroll context.
  • The current uncommitted SessionUi.kt attempt changes QuestionView from follow = { scroll.following() } to follow = { scroll.atBottom() }.
  • That fixes initial question appearance when the user is visibly away from bottom, but it is too broad because QuestionView.follow() is also used after custom editor growth, selection refresh, reply/reject, and other internal updates.
  • QuestionView already intentionally calls scroll(true) in goBack, goForward, and goReview; those forced calls are the behavior that preserves auto-scroll when switching question pages.
  • PermissionView.show() and LoginRequiredView.show() do not call scroll directly. They rely on SessionController.beforeUpdate / afterUpdate, which samples scroll.atBottom() before the state change and calls scroll.followBottom(follow) afterward. With the stale-tail fix, this path can preserve scroll for all non-question inline prompt cards.
  • The uncommitted SessionScroll.followBottom(false) change that clears stale tail when the transcript is visible and not actually at bottom is directionally correct. It prevents later following() calls from resurrecting auto-follow after a non-follow decision.
  • The current scroll button pending state is named question / setQuestionPending and is only enabled for non-plan AwaitingQuestion. To cover all BaseQuestionView-backed prompts, this should become a generic pending-input/attention state and include AwaitingPermission and LoginRequired.
  • Existing behavior intentionally keeps plan follow-up questions using the normal scroll icon (test plan followup question keeps scroll icon), so preserve that exception unless product intent changes separately.
  • The uncommitted build.gradle.kts and script/build-sign-check.sh changes are unrelated to this bug and should not be touched as part of this fix.

Implementation Plan

  1. In QuestionView.kt, split the scroll-follow decision into two callbacks.
  • Keep the existing follow callback for question-internal updates.
  • Add a second callback for initial question appearance, with a default that delegates to follow so existing tests/call sites remain compatible.
  • In show(q), sample the new initial callback before making the question visible or rebuilding the card, then call scroll(tail) after syncPage() as today.
  • Leave scroll(true) in goBack, goForward, and goReview unchanged.
  • Leave existing scroll(follow()) calls in custom editor changes, option selection, reply, and reject paths unchanged.
  1. In SessionUi.kt, wire the callbacks with distinct meanings.
  • Use follow = { scroll.following() } for question-internal updates.
  • Use the new initial callback as { scroll.atBottom() } for initial question appearance.
  • Keep scroll = { scroll.followBottom(it) }.
  1. Generalize the scroll button pending state for all inline prompt cards.
  • Rename SessionScroll.setQuestionPending and the backing question boolean to a generic name such as setInputPending / pending or setAttentionPending / pending.
  • Rename questionPending(state) in SessionUi.kt to match the broader meaning.
  • Return true for non-plan SessionState.AwaitingQuestion, SessionState.AwaitingPermission, and SessionState.LoginRequired.
  • Keep returning false for plan follow-up questions so the existing plan-specific icon behavior is preserved.
  • Reuse the existing question-style icon and tooltip for now, unless a separate product decision asks for a different permission/login-specific icon or string.
  1. In SessionScroll.kt, keep the stale-tail reconciliation in followBottom(false).
  • When follow is false, increment seq, and if the active view is the transcript and !atBottom(), set tail = false before updateJump().
  • Do not change following() to use atBottom(), because tail is needed to preserve bottom-follow behavior across deferred layout/height growth.
  1. Do not add scroll callbacks to PermissionView, LoginRequiredView, or BaseQuestionView unless tests prove the controller-level path is insufficient.
  • PermissionView and LoginRequiredView should remain retained visual views that show/hide themselves and refresh their parent.
  • BaseQuestionView should stay a scroll-unaware card shell.
  • The shared behavior for those cards should come from the controller-level state-change follow decision plus the generalized pending-button state.
  1. In SessionScrollTest.kt, update tests around question-style prompt appearance and preserved behavior.
  • Keep or add an assertion that the jump button is visible before emitting a question in the middle-scroll test.
  • Keep a regression for stale tail plus visible jump button if no public-only scenario can reproduce the failed state; keep the reflective helper local to this test file and avoid adding production test hooks.
  • Keep existing behavior tests for question navigation from the middle, forced navigation resuming follow, custom question answer growth at bottom, custom question answer growth in the middle, and option selection not resuming follow.
  • Add or keep permission bottom/middle tests to prove AwaitingPermission preserves scroll when away from bottom and follows when at bottom.
  • Add or keep login-required bottom/middle tests to prove LoginRequired preserves scroll when away from bottom and follows when at bottom.
  • Add explicit button-state assertions for permission and login-required while away from bottom: the button remains visible, switches to the pending-input icon/tooltip, and clicking it still scrolls to bottom.
  • Keep the plan follow-up test proving plan prompts do not switch to the pending-input icon.
  1. Add a patch changeset for the JetBrains plugin unless an existing branch changeset is intentionally covering this exact fix.
  • Suggested release note: Preserve JetBrains chat scroll position when inline prompts appear while the scroll button is visible.

Verification

  • From packages/kilo-jetbrains/, run ./gradlew test --tests "ai.kilocode.client.session.SessionScrollTest".
  • From packages/kilo-jetbrains/, run ./gradlew typecheck.
  • Do not run root bun test.

Expected Outcome

  • If the transcript is away from bottom and the jump button is visible, a newly appearing question, permission prompt, or login-required prompt changes the button state without moving bar.value.
  • If the transcript is at bottom, a newly appearing question, permission prompt, or login-required prompt follows to the new bottom.
  • Clicking the visible pending-input scroll button still jumps to the bottom for every inline prompt card.
  • Question carousel next/back/review still jumps to the bottom even if the transcript was previously in the middle.
  • Custom question editor growth still follows when the transcript was following and preserves position when it was not.