refactor(jetbrains): move Permission views to views.permission, Align to ui.layout

This commit is contained in:
kirillk
2026-05-22 13:53:43 -04:00
parent 20214bbfe9
commit c4d85ca5ff
18 changed files with 2287 additions and 16 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,88 @@
# Plan: Bubble Subagent Permission Requests in JetBrains
## Goal
Make the JetBrains plugin show subagent permission requests in the main/root session UI so the child session does not hang waiting for a reply. Use the VS Code approach only as a reference pattern; do not change VS Code.
## Findings
- CLI permission requests are session-scoped. `packages/opencode/src/session/prompt.ts` sends `Permission.ask()` with the active session ID, and `packages/opencode/src/permission/index.ts` publishes `permission.asked` then waits indefinitely for a reply.
- The task tool creates a child session for each subagent in `packages/opencode/src/tool/task.ts`, writes the child `sessionId` into the parent task tool metadata, then starts the child prompt. Subagent tool permissions therefore arrive as `permission.asked` for the child session ID.
- JetBrains parses the required data already:
- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/cli/KiloCliDataParser.kt` parses `permission.asked`, `permission.replied`, and task part metadata.
- `packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/dto/ChatDto.kt` has `PermissionRequestDto.sessionID`, `PartDto.metadata`, and `SessionDto.parentID`.
- JetBrains currently filters too narrowly:
- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/rpc/KiloSessionRpcApiImpl.kt` exposes `events(id, directory)` filtered to `sid == id`.
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/controller/SessionController.kt` subscribes only to the root session and `recoverPending(id)` filters pending permissions to `it.sessionID == id`.
- The UI does not need a major change. `SessionState.AwaitingPermission` already renders through `PermissionView`; `replyPermission()` replies by request ID and directory, so it can reply to child permission requests once the controller surfaces them.
- VS Code’s relevant reference is client-side: it detects task child session IDs from task part metadata, starts tracking the child, then recovers pending prompts to close the race where a child permission event arrived before the UI tracked the child.
## Implementation Plan
1. Keep the fix JetBrains-only and frontend-focused.
- Do not modify `packages/kilo-vscode/`.
- Avoid shared `packages/opencode/` changes; the CLI already exposes child session metadata and pending permissions.
- Prefer changing `SessionController.kt` plus controller tests. Backend/RPC DTO changes should not be necessary.
2. Add child-session tracking to `SessionController`.
- Replace the single `eventJob: Job?` with a map such as `eventJobs: MutableMap<String, Job>`.
- Track known child IDs in a `MutableSet<String>`.
- Keep one root subscription for the active root session and add child subscriptions only when a task part reveals a subagent session ID.
- On disposal or when switching root sessions, cancel all jobs and clear the child set.
3. Discover subagent child IDs from task tool metadata.
- Add a helper similar to VS Code’s `childID(part)`:
- require `part.type == "tool"`,
- require `part.tool == "task"`,
- read `part.metadata["sessionId"]`.
- Call it from `handle(ChatEventDto.PartUpdated)` after the root model receives the task part.
- Also scan loaded history (`List<MessageWithPartsDto>`) after `loadHistory()` and cloud import so reopening an existing session discovers already-created child sessions.
4. Subscribe to child permission events without polluting the root transcript.
- Add `subscribeEvents(id: String, root: Boolean)` or equivalent.
- Root subscription keeps existing behavior and enqueues all root-matching events.
- Child subscriptions collect `sessions.events(childId, directory)` but enqueue only permission events:
- `ChatEventDto.PermissionAsked`,
- `ChatEventDto.PermissionReplied`.
- Ignore child message, part, status, idle, diff, and todo events so child transcript/status cannot overwrite the root session UI.
5. Recover pending child permissions immediately after tracking a child.
- After subscribing to a child, call a new recovery helper for that child ID.
- Query `sessions.pendingPermissions(directory)` and filter `it.sessionID == childId`.
- If auto-approve is active, reply `once` to each pending child permission.
- Otherwise set the root model state to `SessionState.AwaitingPermission(toPermission(lastPendingChildPermission))` on the EDT.
- This closes the race where the child `permission.asked` event was already emitted before the child subscription started.
6. Keep root recovery intact, but add child-aware hooks.
- Leave `recoverPending(rootId)` responsible for root permissions/questions/status seeding after history load.
- After history-based child discovery, run child permission recovery for each discovered child.
- Update `drainPermissions()` so toggling auto-approve drains pending permissions for the root plus tracked child IDs, not just the root.
7. Keep replies unchanged.
- `SessionController.replyPermission(requestId, reply, rules)` should continue calling `sessions.replyPermission(requestId, directory, reply)`.
- `Permission.sessionId` should remain the child ID for model/debug visibility.
- `PermissionReplied` from the child subscription can clear the root prompt by matching the current request ID, just like root permission replies do today.
8. Add focused tests.
- In `PromptLifecycleTest.kt`:
- task part with `metadata["sessionId"] = "ses_child"` causes the controller to track child permission events,
- child `PermissionAsked("ses_child", ...)` moves the root model to `AwaitingPermission`,
- replying sends the child request ID through `FakeSessionRpcApi.permissionReplies`,
- child `PermissionReplied` moves the root model back to busy,
- child non-permission events do not change root transcript/status.
- In `SessionRecoveryTest.kt`:
- loaded history containing a task part with child metadata plus a pending child permission recovers into `AwaitingPermission`,
- auto-approve recovery replies to pending child permissions without showing a prompt,
- pending permissions from unrelated sessions remain ignored.
- Use direct `PartDto(...)` construction or extend the test helper to include metadata.
9. Verify with JetBrains checks.
- Run a targeted Gradle test for the touched controller tests if available.
- Run `bun run typecheck` or `./gradlew typecheck` from `packages/kilo-jetbrains/`.
- No VS Code checks are required because VS Code is reference-only and remains unchanged.
## Non-Goals
- Do not add a CLI permission timeout as the primary fix; that would stop the hang by failing the request, not by showing the user the approval prompt.
- Do not render the child transcript in the root session.
- Do not expand scope to child questions unless a follow-up asks for it. The same subscription/recovery pattern can be reused later for `QuestionAsked`/`QuestionReplied` if needed.
+66
View File
@@ -0,0 +1,66 @@
# Refactor JetBrains Permission View
## Goal
Refactor the JetBrains session permission view so it uses the same shared inline question-card shell as question/login-required views, including internal button styling and alignment. Render bash/command permissions as a Markdown code block with `MdView`, capped to three visible lines with vertical scrolling inside the command block.
## Current Context
- `PermissionView` currently builds its own `BorderLayoutPanel` card, manually applies `SessionUiStyle.View` borders/backgrounds, and uses raw `JButton` actions in `frontend/src/main/kotlin/ai/kilocode/client/session/views/PermissionView.kt`.
- `QuestionView` and `LoginRequiredView` use `BaseSessionQuestionPanel` plus `applyButton`/`dismissButton` from `frontend/src/main/kotlin/ai/kilocode/client/session/ui/shared/`.
- `BaseSessionQuestionPanel` owns the shared rounded surface, header/description text areas, body slot, footer slot, editor-font propagation, and left alignment conventions.
- `MdView.html()` is the existing Markdown renderer. Tests already inspect markdown/html and style overrides through its component.
- `SessionMessageListPanel` already calls `permission.applyStyle(style)`, so the refactor can stay inside the frontend session UI without controller/model/RPC changes.
## Implementation Plan
1. Refactor `PermissionView` shell
- Replace the custom `card = BorderLayoutPanel()` card setup with `BaseSessionQuestionPanel()`.
- Set `card.headerText.text` to `session.permission.title` and use `card.descriptionText` for `permission.message` when present; hide/clear the description when blank.
- Keep a retained `details`/`body` `JPanel` with `BoxLayout.Y_AXIS`, transparent background, and `Component.LEFT_ALIGNMENT`, then attach it via `card.setBody(body)`.
- Add a retained `footer` panel via `card.setFooter(footer)` with the same alignment pattern as `QuestionView`/`LoginRequiredView`.
2. Use shared internal buttons
- Replace raw `JButton` instances with `applyButton(KiloBundle.message("session.permission.run")) { decide("once") }` and `dismissButton(KiloBundle.message("session.permission.deny")) { decide("reject") }`.
- Lay out `deny` on the left and `run` on the right using the same `BorderLayout` footer convention as the other `BaseSessionQuestionPanel` users.
- Preserve the existing enabled/disabled behavior for `RESPONDING` and `RESOLVED`, and disable both buttons immediately after a decision.
- Keep existing `runButtonForTest()` and `denyButtonForTest()` helpers, returning the new `SessionQuestionButton` instances.
3. Render command permissions with `MdView`
- Replace `addCommandBlock`'s `JBTextArea` command renderer with an `MdView.html()` instance.
- Set the Markdown source to a fenced code block, escaping embedded triple backticks safely by choosing a fence length longer than any backtick run in the command.
- Apply session/editor style to the Markdown view: `md.font = style.transcriptFont`, `md.codeFont = style.editorFamily`, and keep it visually integrated with the question-card surface.
- Wrap `md.component` in a `JBScrollPane` with `VERTICAL_SCROLLBAR_AS_NEEDED`; use a capped preferred/maximum height equivalent to three editor text lines plus scroll/chrome padding so longer commands scroll inside the code block.
- Add `SessionUiStyle.View.Permission.COMMAND_LINES = 3` (or similarly named session-specific token) rather than scattering a magic number in the view.
4. Preserve non-command permission details
- Keep `addPatternBlock`, tool label mapping, diff title/summary rendering, fallback diff handling, and no-rule-controls behavior intact.
- Keep existing diff previews as plain text areas unless implementation naturally benefits from a small helper; the requested Markdown/code-block behavior applies specifically to permission commands.
- Ensure every dynamic detail component aligns left under the shared card body and no custom outer card background/border code remains in `PermissionView`.
5. Update style propagation
- Replace the old `textAreas` tracking with separate tracking for text areas and command Markdown views, or a small helper that updates both.
- In `applyStyle`, call `card.applyStyle(style)`, update labels/text areas, and update all command `MdView` instances without rebuilding the component tree.
- Avoid raw colors/fonts and keep theme-derived values via `SessionEditorStyle`, `UiStyle`, `SessionUiStyle`, and `UIUtil` as the existing file does.
6. Update tests in `PermissionViewTest`
- Assert the view contains a `BaseSessionQuestionPanel` after `show`.
- Assert Run/Deny are `SessionQuestionButton`s, Run is primary/default styled, Deny is secondary, and both use the shared question surface background.
- Keep existing reply/visibility/pattern/diff/no-rule/responding tests passing.
- Add a command-specific test that verifies a bash command renders through Markdown as a fenced code block (for example by inspecting the `JBHtmlPane`/`MdView` content for `<pre>` and the command text).
- Add a long-command test that verifies the command scroll pane uses vertical scrolling and its preferred/maximum height is capped at the three-line limit.
## Files To Change
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/PermissionView.kt`
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/style/SessionUiStyle.kt`
- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/views/PermissionViewTest.kt`
## Verification
- Run targeted tests from `packages/kilo-jetbrains/`:
- `./gradlew :frontend:test --tests 'ai.kilocode.client.session.views.PermissionViewTest'`
- Run the package typecheck from `packages/kilo-jetbrains/`:
- `./gradlew typecheck`
- If the targeted command test surfaces layout-sensitive failures, also run:
- `./gradlew :frontend:test --tests 'ai.kilocode.client.session.views.QuestionViewTest'`
## Risks / Notes
- `MdView` stores rendered HTML in a `JBHtmlPane`; tests should avoid depending on exact full HTML output and assert stable signals such as source text, `<pre>` presence, button types, and scroll policies.
- The command height cap should account for line height plus scrollpane chrome, not a raw pixel height, so it remains correct across editor font changes and HiDPI scaling.
- This is a frontend-only Swing refactor; no shared RPC/model/backend changes are expected.
+91
View File
@@ -0,0 +1,91 @@
# Refactor BaseQuestionView and Session Fonts
## Goal
Refactor the JetBrains session question card UI so callers configure text/actions through `BaseQuestionView` instead of reaching into its Swing internals, and move session UI font choices to explicit standard `UiStyle.Fonts`/`SessionEditorStyle` tokens.
## Design Decisions
- Treat `SessionEditorStyle` editor fonts/colors as-is for editor-backed content, but replace current UI font sizing/derivation with explicit UI font fields sourced from `UiStyle.Fonts`.
- Proposed UI font mapping:
- `headerFont` -> `UiStyle.Fonts.header()` -> `JBFont.h3().asBold()`
- `hintFont` -> `UiStyle.Fonts.hint()` -> `JBFont.regular()`
- `regularFont` -> `UiStyle.Fonts.regular()` -> `JBFont.regular()`
- `boldFont` -> `UiStyle.Fonts.bold()` -> `JBFont.regular().asBold()`
- `smallFont` -> `UiStyle.Fonts.small()` -> `JBFont.small()`
- Remove manual UI font size derivation (`JBUI.Fonts.label().deriveFont(...)`, `JBFont.small().deriveFont(...)`, `deriveFont(size + 1)`); only style helpers such as `.asBold()` are used for UI fonts.
- Keep `BaseQuestionView.setTopPanel(...)`, keep/add a content setter (`setContent(...)`, with `setBody(...)` retained only if useful as a compatibility alias), and replace external footer/button construction with a Base-owned action API.
- Make `SessionQuestionButton` an implementation detail or remove it from public APIs. Callers and tests should see generic `JButton`/`AbstractButton` behavior only.
## Implementation Plan
1. Update shared font tokens in `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/UiStyle.kt`.
- Extend `UiStyle.Fonts` with `header()`, `hint()`, `regular()`, `bold()`, and `small()`.
- Preserve existing `display()`, `heading()`, and `large()` helpers.
- Use only standard `JBFont` helpers and `.asBold()`; do not derive sizes.
2. Refactor `SessionEditorStyle` in `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/style/SessionEditorStyle.kt`.
- Add/replace UI fields with `headerFont`, `hintFont`, `regularFont`, `boldFont`, `smallFont`.
- Populate those fields directly from `UiStyle.Fonts`.
- Remove current UI-font manual scaling and no longer couple UI font sizes to editor font size.
- Keep editor-specific fields (`transcriptFont`, `smallEditorFont`, `boldEditorFont`, editor colors/family/size) for code/editor-rendered content.
3. Refactor `BaseQuestionView` in `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/base/BaseQuestionView.kt`.
- Make header/description text areas private implementation details.
- Add EDT methods such as `setHeader(header: String, description: String? = null)` and `setDescription(description: String?)`.
- Hide the description row when the description is null/blank, instead of requiring clients to set visibility directly.
- Apply `style.headerFont` to the header and `style.hintFont` to the description.
- Add a Base-owned action API, for example a public nested `Action(id, text, primary, enabled, handler)` plus `setActions(List<Action>)` and `setActionEnabled(id, enabled)`.
- Build the right-aligned footer and internal buttons in `BaseQuestionView`, preserving primary buttons via `DarculaButtonUI.DEFAULT_STYLE_KEY` and question-card background styling.
- Keep `setHeaderIcon(...)` and `setTopPanel(...)`; keep the top slot for progress/navigation and the content slot for view-specific body components.
- Add minimal internal test helpers only if necessary, returning generic `JButton` or `Font`, not `SessionQuestionButton`.
4. Migrate question card callers.
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/question/QuestionView.kt`:
- Replace direct `card.headerText`/`card.descriptionText` mutation with `card.setHeader(...)`/`card.setDescription(...)`.
- Replace `SessionQuestionButton`, `applyButton`, `dismissButton`, and manual footer panels with `card.setActions(...)`.
- Use stable action ids and `card.setActionEnabled(...)` so selection updates do not rebuild the existing Next/Submit button.
- Update option/review text fonts to `style.regularFont` and `style.boldFont`.
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/PermissionView.kt`:
- Use Base setters for header/description/content/actions.
- Return generic buttons from test helpers if helpers remain.
- Keep editor-derived `transcriptFont`/`editorFamily` for command markdown/code rendering.
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/LoginRequiredView.kt`:
- Use Base setters for title/description/actions.
- Remove public exposure of `SessionQuestionButton`; tests can locate generic buttons by text or use generic helper accessors.
5. Rename/update remaining `SessionEditorStyle` UI-font consumers.
- Update all `style.uiFont`, `style.boldUiFont`, and `style.smallUiFont` references to the new fields.
- Expected mappings:
- ordinary labels/body text -> `regularFont`
- emphasized labels/body answers -> `boldFont`
- compact metadata/subtitles -> `smallFont`
- card question titles -> `headerFont`
- card hints/descriptions -> `hintFont`
- Files likely affected include `QuestionResultView.kt`, `CompactionView.kt`, `GenericView.kt`, `SessionHeaderPanel.kt`, `ContextBar.kt`, `ProgressPanel.kt`, `LoadingPanel.kt`, and `EmptySessionPanel.kt`.
6. Update tests.
- `SessionEditorStyleTest.kt`: assert UI font fields equal `UiStyle.Fonts.*()` and do not inherit editor family/size.
- `BaseQuestionViewTest.kt`: test the new text/content/action APIs, description visibility behavior, retained top/content ordering, primary button default-style key, enabled-state updates, and callbacks without referencing `SessionQuestionButton`.
- `QuestionViewTest.kt`, `PermissionViewTest.kt`, `LoginRequiredViewTest.kt`, and `QuestionResultViewTest.kt`: remove `SessionQuestionButton` imports/type assertions; keep behavior/style assertions through generic Swing buttons and new font fields.
- Preserve retained-component tests such as “selection updates existing footer controls” by using `setActionEnabled(...)` rather than rebuilding buttons on selection changes.
## Verification
Run from `packages/kilo-jetbrains/` after implementation:
```bash
./gradlew :frontend:test --tests "ai.kilocode.client.session.ui.SessionEditorStyleTest" --tests "ai.kilocode.client.session.views.base.BaseQuestionViewTest" --tests "ai.kilocode.client.session.views.QuestionViewTest" --tests "ai.kilocode.client.session.views.PermissionViewTest" --tests "ai.kilocode.client.session.views.LoginRequiredViewTest" --tests "ai.kilocode.client.session.views.QuestionResultViewTest"
```
Then run the package compile/typecheck:
```bash
./gradlew typecheck
```
## Risks / Notes
- The new standard UI fonts intentionally stop following the editor font size, so several font-size assertions must change.
- `SessionQuestionButton` may remain as a private implementation class in `BaseQuestionView.kt`, but no caller should import it or rely on its concrete type.
- This is a JetBrains frontend-only refactor; no split-mode RPC or module descriptor changes are expected.
+88
View File
@@ -0,0 +1,88 @@
# Add Custom Question Responses In JetBrains
## Findings
- The backend/DTO contract is already ready for custom answers.
- `packages/opencode/src/question/index.ts` exposes `Question.Info.custom?: boolean` and `Question.Reply.answers: string[][]`.
- Both legacy and HttpApi question reply routes accept arbitrary strings in `answers`, so no server shape change is needed.
- The generated SDK also has `QuestionInfo.custom?: boolean` and `QuestionAnswer = string[]`.
- JetBrains shared DTOs already mirror this: `QuestionInfoDto.custom: Boolean = true` and `QuestionReplyDto.answers: List<List<String>>` in `packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/dto/ChatDto.kt`.
- JetBrains backend parsing already preserves `custom`, defaulting to true unless the server sends `false`, in `KiloCliDataParser.parseQuestionRequest`.
- JetBrains frontend model already carries `QuestionItem.custom`, and `SessionController.toQuestion` maps the DTO field into the model.
- Current missing piece is only `QuestionView`: it ignores `QuestionItem.custom` and only renders option labels.
- VS Code renders a custom row when `question.custom !== false`, labels it as a typed-answer option, stores typed custom answers as ordinary answer strings, and sends the same `answers: string[][]` payload.
## Implementation Plan
1. Reuse the prompt editor component for question custom input.
- Extract the current prompt editor text field behavior from `PromptEditorTextField` into a reusable session editor text field, likely under `frontend/src/main/kotlin/ai/kilocode/client/session/ui/editor/`.
- Keep the prompt using the same reusable editor so prompt behavior remains unchanged.
- Let `QuestionView` create the same editor type with plain-text file type, soft wraps, editor color scheme, editor font, empty borders, and `SessionEditorStyle.applyToEditor`.
- Ensure editor instances for both `PromptPanel` and `QuestionView` are created from the required IntelliJ read context/read thread, rather than directly from arbitrary Swing construction code.
- If IntelliJ APIs require it, wrap editor creation in a small helper such as `runReadAction` or the platform's current read-thread equivalent, then attach/mutate the resulting component only on the EDT.
- Add an optional `SendPromptContext` hook to the reusable editor so the existing prompt shortcuts still work; for question custom input, implement a small question-local context that submits through the question footer flow rather than sending a normal prompt.
2. Pass `Project` into `QuestionView`.
- `EditorTextField` needs a project, and `SessionUi` already has one.
- Update `QuestionView(project, reply, reject, scroll)` construction in `SessionUi`.
- Update `QuestionViewTest` setup to pass the test fixture `project`.
3. Extend `QuestionView` state to separate option selections from custom text.
- Keep selected option labels in the existing per-question sets.
- Add per-question custom text and custom editor/open state.
- Compute effective answers from options plus the trimmed custom text.
- For single-select questions, activating custom should clear option selection and make the custom answer the only answer once non-blank.
- For multi-select questions, the custom answer should be included alongside checked options once non-blank.
- Preserve typed custom text across back/next/review navigation.
4. Render the custom row when `item.custom` is true.
- Add an extra radio/checkbox-style row labeled `Add your own response` or the final localized copy chosen in bundle keys.
- Keep the input/editor initially invisible.
- Clicking the custom row shows the editor, focuses it, selects the custom answer mode for single-select, and revalidates/repaints the question view.
- If custom text already exists and the editor is hidden, show a short preview in the row description.
- Do not render the row when `custom = false`.
- Ensure optionless questions with `custom = true` are answerable.
5. Make the custom editor grow and validate live.
- Add a document listener to the question custom editor.
- On each edit, update stored text, recompute answer readiness, call `syncControls`, and revalidate/repaint the editor, card, and parent container.
- Size the editor from the current newline count, starting at one visible line and expanding as the user types newlines.
- Continue using the main footer buttons for `Next`, `Review`, and `Submit`; no backend-specific custom-submit action is needed.
- Keep blank/whitespace-only custom input from enabling submit or next.
6. Update review and reply behavior.
- Change review rows to display effective answers, including custom typed answers.
- Change `doReply` to send effective answers for all question items.
- Keep the reply payload unchanged: `QuestionReplyDto(listOf(listOf("typed custom answer")))`.
- Keep reject/dismiss behavior unchanged.
7. Add localized strings.
- Add bundle keys in `KiloBundle.properties`, such as `session.question.custom.label` and `session.question.custom.placeholder`.
- Rely on base-bundle fallback for other locale files unless the project requires updating every translation file.
8. Add tests.
- `QuestionViewTest`: custom row renders when `custom = true` and is absent when `custom = false`.
- `QuestionViewTest`: clicking the custom row reveals/focuses the editor.
- `QuestionViewTest`: blank custom input does not enable submit.
- `QuestionViewTest`: single-select custom answer submits as `QuestionReplyDto(listOf(listOf(text)))`.
- `QuestionViewTest`: selecting a normal single option after custom input sends the option, not stale custom text.
- `QuestionViewTest`: multi-select custom answer combines with selected options.
- `QuestionViewTest`: custom text appears in review and is preserved across navigation.
- `QuestionViewTest`: optionless custom question is answerable.
- `QuestionViewTest`: editor preferred height increases after typing a newline and triggers revalidation.
- `KiloCliDataParserTest`: only add coverage if existing parser tests do not already prove `custom` parsing and arbitrary string reply escaping.
9. Add release note metadata.
- This is a user-facing JetBrains plugin enhancement, so add a patch changeset for `@kilocode/kilo-jetbrains` describing typed custom responses to question prompts.
10. Verify.
- Run targeted JetBrains tests for `QuestionViewTest` and any parser tests touched.
- Run `./gradlew typecheck` from `packages/kilo-jetbrains/`.
- If only frontend tests changed, also run the smallest relevant Gradle test target for the frontend module if available; otherwise run the package test task that includes `QuestionViewTest`.
## Non-Goals
- Do not change the CLI/server question API shape.
- Do not add a separate `custom` discriminator to replies.
- Do not route custom typed answers through the normal prompt path.
- Do not use Compose, JCEF, or Kotlin UI DSL.
+557
View File
@@ -0,0 +1,557 @@
# JetBrains Permission Panel Rendering Parity Plan
## Goal
Bring the JetBrains permission panel content rendering to parity with the VS Code permission dock, specifically for the contents/details shown inside the permission panel. Do not implement auto-approve rule controls or saved-rule management yet.
The priority user-visible gaps are edit/write/patch details, especially file diff previews. The JetBrains frontend already receives the relevant metadata; most of the work is Swing rendering, styling, and tests.
## Scope
Implement content rendering parity for:
- Permission title/header, including subagent title if the request belongs to another session and the current session can be determined safely.
- Bash/command details.
- Non-command tool pattern details.
- Fallback tool description/details when patterns are empty or only `*`.
- Edit/write/patch file details and diff previews.
- Per-file additions/deletions summary.
- Missing patch fallback text.
- Permission response state details where already modeled, especially error messages.
Explicitly exclude:
- Auto-approve rule controls.
- Persisting rule decisions.
- Any new permission response semantics beyond existing `Run`/`Deny`.
- Repositioning the permission panel inline next to the originating tool call.
- Changes to CLI permission generation unless a parser bug is discovered.
## Key Findings From Analysis
### VS Code implementation
Reference files:
- `packages/kilo-vscode/webview-ui/src/components/chat/PermissionDock.tsx`
- `packages/kilo-vscode/webview-ui/src/components/chat/PermissionCommand.tsx`
- `packages/kilo-vscode/webview-ui/src/components/chat/PermissionDiff.tsx`
- `packages/kilo-vscode/webview-ui/src/components/chat/permission-diff-utils.ts`
- `packages/kilo-vscode/webview-ui/src/components/chat/permission-dock-utils.ts`
- `packages/kilo-vscode/webview-ui/src/types/messages/permissions.ts`
VS Code renders:
- Header: `Permission required`, or `Permission required (subagent)` when `request.sessionID !== currentSessionID`.
- Command block when `request.args.command` is a string.
- Pattern description for non-command permissions:
- filters out `*`
- one pattern: `<Tool Label> <pattern>`
- multiple patterns: `<Tool Label>:` then code-like rows
- no meaningful patterns: fallback to localized tool description if available
- Diffs from `permissionDiffs(request)`:
- `args.filediff` first
- `args.files[]` second
- fallback to `args.diff` plus `args.filepath ?? "patch"`
- Each diff displays:
- file icon
- directory and filename split
- `+N -N` via `DiffChanges`
- inline unified diff via `Diff`
- fallback text: `Diff preview unavailable for this file.` when no patch exists
- expand/open-in-tab button (do not implement for JetBrains in this pass unless trivial and explicitly desired later)
- Auto-approve rule controls live in the footer and should be ignored for this task.
### JetBrains implementation
Reference files:
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/PermissionView.kt`
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/model/Permission.kt`
- `packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/dto/ChatDto.kt`
- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/cli/KiloCliDataParser.kt`
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/controller/SessionController.kt`
- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/views/PermissionViewTest.kt`
- `packages/kilo-jetbrains/frontend/src/main/resources/messages/KiloBundle.properties`
Current JetBrains behavior:
- `PermissionView.show(permission)` rebuilds a simple body.
- If `permission.meta.command != null` or tool name is `bash`, it renders a fenced Markdown code block.
- Otherwise it renders `patternText(toolName, permission.patterns)` as a fenced Markdown code block.
- It disables buttons for `RESPONDING` and `RESOLVED`.
- It does not render diff previews, `filePath`, `fileDiffs`, error messages, or rich status text.
- Existing tests currently assert that diff previews are not rendered; these must be inverted/updated.
JetBrains already receives and maps rich permission data:
- `PermissionRequestDto.fileDiffs: List<PermissionFileDiffDto>`
- `PermissionFileDiffDto.file`, `patch`, `before`, `after`, `additions`, `deletions`
- `PermissionMeta.fileDiffs`, `fileDiff`, `diff`, `filePath`, `raw`
- Parser handles:
- `metadata.filediff`
- `metadata.files[]`
- fallback `metadata.diff`
- Controller maps DTOs to frontend model correctly.
Important existing bundle keys:
- `session.permission.title=Permission required`
- `session.permission.title.subagent=Permission required (subagent)`
- `session.permission.run=Run`
- `session.permission.deny=Deny`
- `session.permission.patterns={0}:`
- `session.permission.diff=Changes`
- `session.permission.diff.summary=+{0} -{1}`
- `session.permission.no.details={0} requires permission.`
- `session.permission.responding=Sending response...`
- `session.permission.error=Failed to send permission response`
- `session.permission.tool.*` mappings already exist.
Likely missing bundle key:
- `session.permission.diff.unavailable=Diff preview unavailable for this file.`
If adding this key, add it to all localized `KiloBundle_*.properties` files. It is acceptable to use English fallback text in localized files if this repository pattern does that elsewhere; otherwise follow the existing localization convention.
## Implementation Strategy
### 1. Keep changes localized to JetBrains frontend first
Most changes should be in:
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/PermissionView.kt`
- possibly a new helper/component file such as:
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/PermissionDiffView.kt`
- or `PermissionDetailsView.kt` if extracting all detail sections is cleaner
- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/views/PermissionViewTest.kt`
- resource bundle files under `packages/kilo-jetbrains/frontend/src/main/resources/messages/`
- possibly `SessionUiStyle.kt` only if a reusable permission diff height/spacing token is necessary
Avoid backend/shared changes unless tests reveal the parser/model does not expose a field needed by rendering.
### 2. Prefer a small extracted diff component
Recommended structure:
- Keep `PermissionView` responsible for card shell, actions, command/pattern section selection, and body composition.
- Add `PermissionDiffView` for one file diff.
- Optionally add `PermissionDiffListView` only if it materially reduces complexity.
Why:
- `PermissionView.kt` is already 241 lines and will grow quickly.
- Diff rendering has distinct styling, tests, and style application concerns.
- A dedicated component is easier for a faster model to modify safely.
Proposed `PermissionDiffView` responsibilities:
- Accept a `PermissionFileDiff`.
- Implement `SessionEditorStyleTarget` if it renders code with editor style.
- Build header row:
- file icon: prefer `AllIcons.FileTypes.Text` or another standard IntelliJ icon if appropriate
- file path display, preferably directory muted + filename regular/bold if simple
- summary label using `KiloBundle.message("session.permission.diff.summary", additions, deletions)`
- Build content area:
- if `patch` is non-null/non-blank: scrollable code/Markdown block with patch
- else: label with unavailable preview text
- Use platform components:
- `JBLabel`
- `JBScrollPane`
- `BorderLayoutPanel` / `JBUI.Panels.simplePanel`
- `JBUI.Borders.empty(...)`
- Style with `SessionEditorStyle` for patch content and `SessionUiStyle`/`UiStyle` for spacing/colors.
Avoid:
- Compose, JCEF, UI DSL.
- Raw hardcoded colors.
- Raw unscaled dimensions or `EmptyBorder`.
- Complex custom diff parsing/syntax highlighting. VS Code already receives unified patches and renders them; for JetBrains parity, a readable scrollable unified patch is enough unless there is an existing diff widget readily available.
### 3. Update `PermissionView.show(permission)` rendering flow
Current flow:
```kotlin
val toolName = permission.name
val cmd = permission.meta.command
val command = cmd != null || toolName == "bash"
if (command) {
addCodeBlock(cmd ?: "")
} else {
addCodeBlock(patternText(toolName, permission.patterns))
}
```
Recommended flow:
```kotlin
val tool = permission.name
val cmd = permission.meta.command
if (cmd != null || tool == "bash") {
addCodeBlock(cmd ?: "")
} else {
addPatternDetails(tool, permission.patterns)
}
addDiffs(permission.meta.fileDiffs)
addStateMessage(permission)
```
Notes:
- Keep bash with missing command showing an empty command block only if this preserves current behavior. Alternatively show fallback details for missing command, but that is a behavior change; preserve current behavior unless tests or product expectations say otherwise.
- Do not render `permission.message` by default for bash because an existing test explicitly asserts it is not shown and VS Code does not show `message` as content.
- For non-command permissions, pattern/details rendering should be closer to VS Code. It can still use a code-like block, but consider rendering multiple patterns as separate labels/code rows rather than one fenced block if easy.
- Always add diffs after the command/pattern section. This matches VS Code, where a permission can show command/details and then diffs.
### 4. Implement pattern rendering parity
Current `patternText()` already mostly works but renders everything in a fenced block and uses double space for single pattern.
Recommended helper behavior:
```kotlin
private fun patternDescription(tool: String, patterns: List<String>): PatternDescription?
```
Where `PatternDescription` can be a small private sealed class/data class inside `PermissionView.kt`, or avoid an explicit type and keep simple helpers.
Exact behavior to mirror:
- Filter `patterns.filter { it != "*" }`.
- If no filtered patterns: render fallback `session.permission.no.details` with `toolLabel(tool)`.
- If one: render `<label> <pattern>`.
- If multiple:
- first line `session.permission.patterns(label)`
- then one row per pattern.
Do not include auto-approve `always` or `rules` here.
### 5. Render file diff details
Source data: `permission.meta.fileDiffs`.
For each diff:
- Header:
- show `diff.file`
- if splitting path is easy, display directory separately from filename, but this is not required for first parity pass if full path is clear
- show `+{additions} -{deletions}` using `session.permission.diff.summary`
- Body:
- if `patch` exists: render patch in scrollable code area
- else: render unavailable fallback
Height cap:
- Existing command cap uses `SessionUiStyle.View.Permission.COMMAND_LINES` and `CARD_BODY_EXTRA_HEIGHT`.
- For diff previews, add a token if needed, for example `SessionUiStyle.View.Permission.DIFF_LINES`, or reuse `COMMAND_LINES` for simplicity if no new token is needed.
- If adding tokens, put them in `SessionUiStyle.View.Permission`, not in local constants.
Possible patch rendering implementation options:
Option A, quickest and consistent with current command rendering:
- Use `MdView.html()` with `fencedBlock(patch)` and existing `applyMd/applyScroll` logic.
- Pros: minimal new rendering code, already tested for code blocks.
- Cons: patch is not syntax-highlighted by additions/deletions colors.
Option B, slightly more purpose-built:
- Use `JBTextArea` or editor-like component in a scroll pane, set monospaced/editor style, non-editable, line wrap off or on depending UX.
- Pros: simpler text collection in tests; avoids Markdown HTML quirks.
- Cons: need to style manually and ensure platform component usage.
Recommended: Option A for faster implementation and lower risk, unless `MdView` makes tests too brittle.
### 6. Style application
Current `PermissionView` tracks:
- `cmdViews: MutableList<MdView>`
- `cmdScrolls: MutableList<JBScrollPane>`
If using `MdView` for diffs too:
- Rename lists to a more generic name, e.g. `codeViews` and `codeScrolls`, or keep existing names only if used exclusively for command/pattern blocks.
- Update `applyStyle()` to apply style to all command/pattern/diff code views.
- Review naming rules: prefer single-word locals where clear, but do not sacrifice clarity for exported or test-visible names.
Be careful with tests that currently call `firstCmdViewForTest()`. Either preserve that test helper or add a more generic helper while adjusting tests.
### 7. Permission state messages
Current model can represent:
- `PENDING`
- `RESPONDING`
- `RESOLVED`
- `ERROR`
Current UI only disables buttons for `RESPONDING` and `RESOLVED`.
Recommended content parity improvement:
- For `ERROR`, render `permission.message ?: KiloBundle.message("session.permission.error")` visibly in the card body, probably below details and above actions.
- For `RESPONDING`, optionally render `session.permission.responding` if desired. This is already a resource key and helps users understand disabled buttons.
- Do not render anything for `RESOLVED` because the state usually transitions away from permission.
If this increases scope too much, prioritize `ERROR` because the message is currently stored but invisible.
### 8. Subagent title
VS Code title changes when the pending permission belongs to another session.
JetBrains `PermissionView` currently only receives `Permission`, not the current session id. Options:
- Minimal: keep current title and do not implement subagent title yet; note as parity limitation.
- Better: pass a predicate or current session id into `PermissionView` constructor from `SessionUi`/controller context, then use `permission.sessionId` to select `session.permission.title.subagent`.
Recommended for faster model: defer subagent title unless the current session id is already easily available at the construction site. The main user complaint is missing edit/details, not title. If implementing, keep it tiny and test it.
### 9. Do not implement auto-approve rules
Important: `PermissionMeta.rules` and `Permission.always` should remain unused for UI controls in this pass.
Keep/adjust the existing test that verifies no rule controls are rendered:
- It should continue to assert only Run and Deny buttons are present.
- It should not assert absence of text that may appear in unrelated details unless needed.
## Detailed File-by-File Plan
### `PermissionView.kt`
Tasks:
1. Rename command-specific collections if they will hold all code blocks:
- `cmdViews` -> `codeViews`
- `cmdScrolls` -> `codeScrolls`
- Preserve or update test helper names carefully.
2. In `show(permission)`:
- clear body and code view lists
- set title, potentially subagent title if implemented
- add command or pattern details
- add diffs from `permission.meta.fileDiffs`
- add state message for `RESPONDING`/`ERROR` if implemented
- update button enabled state as today
3. Add `addPatternDetails(tool, patterns)` or adjust `patternText()`:
- use VS Code-compatible filtering and text.
4. Add `addDiffs(diffs)`:
- no-op when empty
- optionally add `session.permission.diff` label/header
- add a `PermissionDiffView` per diff
5. Add `addStateMessage(permission)` if included.
6. Ensure `applyStyle()` updates all retained code/diff children.
7. Add/adjust test helpers:
- expose code views count if needed
- expose diff components if needed
- keep existing helpers unless tests are updated.
Caution:
- Current `show()` rebuilds body with `removeAll()`. This is acceptable for a discrete permission request. Do not introduce a complex retained diff update system unless necessary.
- Existing style guide says avoid extra helpers unless they improve clarity. Here helpers are justified to keep `show()` readable.
### New `PermissionDiffView.kt` (recommended)
Tasks:
1. Create class under `ai.kilocode.client.session.views`.
2. Constructor accepts `PermissionFileDiff` and possibly a function to register code views, or the component manages its own code view internally.
3. Implement `SessionEditorStyleTarget` if it owns an `MdView` or styled text component.
4. Use platform components and existing style tokens.
5. Add test helpers if direct component testing is easier.
Possible API:
```kotlin
class PermissionDiffView(
private val diff: PermissionFileDiff,
) : BorderLayoutPanel(), SessionEditorStyleTarget {
fun applyStyle(style: SessionEditorStyle) { ... }
}
```
Then `PermissionView` keeps a list of `SessionEditorStyleTarget` child targets and calls `applyStyle(style)`.
Alternative: no new class; add private `addDiff(diff)` inside `PermissionView`. This is acceptable if the resulting file remains manageable, but extraction is recommended.
### `SessionUiStyle.kt`
Only touch if needed.
Possible additions under `SessionUiStyle.View.Permission`:
- `DIFF_LINES`
- maybe `DIFF_GAP` only if existing gap tokens are insufficient
Before adding constants, check existing `SessionUiStyle.View.Permission` object. Reuse existing values where practical.
### `KiloBundle.properties` and localized bundles
Likely add:
```properties
session.permission.diff.unavailable=Diff preview unavailable for this file.
```
Maybe add if rendering state messages needs distinct text:
```properties
session.permission.diff.file={0}
```
Avoid adding keys unless needed. Existing keys cover most content.
If adding keys:
- Add to every localized bundle under `frontend/src/main/resources/messages/`.
- Follow existing formatting/order near other `session.permission.*` keys.
### `PermissionViewTest.kt`
Update/add tests:
1. Replace `test diff preview is not rendered` with positive rendering test:
- construct edit permission with `fileDiffs = listOf(PermissionFileDiff(file = "src/A.kt", patch = "@@ -1 +1 @@\n-old\n+new", additions = 1, deletions = 2))`
- assert all text contains `src/A.kt` or split path parts
- assert contains `@@`
- assert contains `+1 -2` or the exact localized summary split
2. Add missing patch fallback test:
- diff with `patch = null`, `additions`, `deletions`
- assert file appears
- assert unavailable fallback appears
3. Add multiple diffs test:
- `src/A.kt` and `src/B.kt`
- assert both appear and summaries appear
4. Keep command behavior tests:
- bash still renders command
- bash does not show `permission.message`
5. Keep no rule controls test:
- only two buttons Run/Deny
- no Manage Auto-Approve Rules text
6. Add error state test if implemented:
- state `ERROR`, message `Boom`
- assert text contains `Boom`
- buttons enabled or disabled? Current code disables only responding/resolved, not error. Decide desired behavior:
- Recommended: ERROR should re-enable buttons so user can retry Deny/Run. Current logic already enables for ERROR. Test that if useful.
7. Adjust tests that assume first `JBHtmlPane` is command/pattern if diff code blocks add more panes.
### Backend/parser tests
Probably no changes needed because parser tests already cover:
- command metadata
- fallback `diff` + `filepath`
- `filediff` object
- `files[]` array
- malformed files
- old JSON defaults
Only update parser/backend tests if implementation discovers parser mismatch.
## Suggested Implementation Order
1. Add/adjust tests in `PermissionViewTest.kt` first, especially diff-positive tests.
2. Add missing bundle key(s).
3. Implement diff rendering with the simplest viable component.
4. Update `PermissionView.show()` to append diffs after command/pattern content.
5. Update pattern rendering if tests show current output is not close enough to VS Code.
6. Add state/error message rendering if included.
7. Run targeted tests and fix compile/test failures.
8. Run package typecheck.
## Verification Commands
Run from `packages/kilo-jetbrains/`:
```bash
./gradlew test --tests "ai.kilocode.client.session.views.PermissionViewTest"
```
Then run:
```bash
./gradlew typecheck
```
If the targeted test command is not supported by this Gradle setup, run:
```bash
./gradlew test
```
If Java 21 is missing, follow repo instructions before running Gradle:
```bash
java -version
sdk install java 21-tem
sdk use java 21-tem
```
Only install/use SDKMAN if available and needed.
## Acceptance Criteria
A reviewer should be able to verify:
- Bash permissions still show the command and Run/Deny buttons.
- Read/glob/grep/list/etc. permissions still show meaningful tool/pattern details.
- Edit/write/patch permissions with `fileDiffs` show file names, additions/deletions, and inline unified patches.
- Edit/write/patch permissions with no patch show a clear unavailable-preview fallback.
- Multiple-file patch permissions show each file separately.
- Auto-approve rule controls are not rendered.
- Existing permission replies still send `PermissionReplyDto(reply = "once")` for Run and `"reject"` for Deny.
- UI uses Swing/IntelliJ platform components only.
- `PermissionViewTest` and JetBrains typecheck pass.
## Risks and Mitigations
### Risk: `MdView` HTML makes text assertions brittle
Mitigation:
- Existing tests already inspect `JBHtmlPane.text`; follow that pattern.
- Assert broad substrings such as filename, `@@`, and command text.
- If patch text is escaped or transformed, use a plain text component for diff patches instead.
### Risk: File grows too large/complex
Mitigation:
- Extract `PermissionDiffView.kt`.
- Keep parser/model unchanged.
### Risk: Styling is too custom
Mitigation:
- Use existing `SessionEditorStyle`, `SessionUiStyle`, `UiStyle.Gap`, `JBUI.Borders`, and platform colors.
- Avoid raw `Color(...)`, hardcoded font families, and unscaled dimensions.
### Risk: Subagent title requires plumbing
Mitigation:
- Treat as optional lower-priority parity item.
- Do not block edit/detail parity on it.
### Risk: Auto-approve metadata accidentally appears
Mitigation:
- Do not render `permission.meta.rules` or `permission.always`.
- Keep/update tests asserting no extra rule controls.
## Notes for Faster Model
- The main implementation target is `PermissionView.kt`; do not start in backend/parser unless a compile error points there.
- The data is already present in `permission.meta.fileDiffs`.
- Do not implement auto-approve rules even though VS Code has a footer for them.
- Prefer a readable unified patch block over sophisticated diff coloring.
- Keep the current Run/Deny actions unchanged.
- If time is limited, prioritize: file name + `+N -N` + patch text + unavailable fallback.
@@ -0,0 +1,51 @@
# Improve JetBrains Permission View
## Goal
Update the JetBrains permission prompt so permission details read as a compact summary line instead of block content:
- Show the action/tool label (for example `Read`, `Edit`, `Shell`) as the row header.
- Show the target path, pattern, or command as an inline code fragment on the same row.
- Show file diffs as compact colored badges with `+N -M lines` and no diff body/content for now.
## Current State
- `PermissionView.kt` renders bash commands and non-bash targets through fenced Markdown code blocks.
- `patternText()` combines action and target into the same Markdown block.
- `PermissionDiffView.kt` renders the file name plus summary and then either a scrollable patch preview or an unavailable fallback label.
- Tests in `PermissionViewTest.kt` currently assert the old Markdown/pre-block and diff-content behavior.
## Implementation Plan
1. Replace permission detail block rendering in `PermissionView.kt`.
- Introduce a compact detail row builder that uses Swing components, not `MdView`, for action + target.
- Resolve the row action with existing `toolLabel(tool)`.
- Resolve the target in priority order: explicit command for bash/command permissions, `permission.meta.filePath`, then `permission.patterns` excluding `*`.
- Render each target as an inline code fragment with editor font/colors and a subtle code background, keeping it on the same horizontal row as the action label.
- Preserve a reasonable no-target fallback for `*`/empty patterns, but avoid rendering it as a code block.
- Remove or stop using `patternText()` for the new UI.
2. Simplify `PermissionDiffView.kt` to badge-only rendering.
- Render each diff as one horizontal row: file path label plus a colored pill/badge.
- Badge text should include additions, deletions, and line wording, e.g. `+3 -1 lines`.
- Use theme-derived colors from existing style helpers where possible; add a small session-specific color helper only if needed.
- Do not render `diff.patch`, `before`, `after`, or unavailable fallback content.
- Drop `MdView`/`JBScrollPane` state from the diff view if no longer needed.
3. Keep style changes local and native to Swing.
- Use IntelliJ platform components (`JBLabel`, platform borders, `JBUI` spacing).
- Use existing `SessionEditorStyle` propagation for inline code fragments that depend on editor fonts/colors.
- Avoid hardcoded runtime colors unless centralized as semantic style helpers.
4. Update tests in `PermissionViewTest.kt`.
- Replace assertions that expect fenced `<pre>` blocks for non-bash patterns with assertions for action text plus inline target text.
- Adjust bash command tests to expect same-line action + command, not a command code block.
- Update diff tests to assert file names and badge text are shown while patch markers/unavailable fallback text are not shown.
- Add or update tests for multiple patterns/diffs and style propagation for inline code fragments.
5. Add a patch changeset.
- Create `.changeset/<slug>.md` for `"@kilocode/kilo-jetbrains": patch` with a user-facing note such as: `Improve JetBrains permission prompts with compact action rows and diff badges.`
## Verification
- Run the targeted JetBrains frontend test class if Gradle supports it, otherwise run `./gradlew test --tests ai.kilocode.client.session.views.PermissionViewTest` from `packages/kilo-jetbrains/`.
- Run `./gradlew typecheck` from `packages/kilo-jetbrains/` to catch Kotlin/UI compile errors.
## Risks / Notes
- Long paths may exceed the available width in a single horizontal row. Prefer a layout that keeps the action and first target together while allowing the row to wrap naturally if needed.
- The diff badge has no dedicated data field for total changed lines, so use `additions + deletions` for the `lines` count if a separate total is needed, or use wording around the existing `+N -M` values only.
+163
View File
@@ -0,0 +1,163 @@
# Plan: Update JetBrains Align Wrapper Sizing Semantics
## Goal
Update the existing `Align` wrapper so every non-track alignment mode respects the wrapped component's minimum, preferred, and maximum sizes. Add a new `TRACK` mode on both axes that always fills the available wrapper space, ignores the child's min/preferred/max size on that axis, and does not contribute the child size to the wrapper's min/preferred/max size on that axis.
Also replace the existing `CenterShrinkPanel` utility with `Align` if the new semantics fully cover its behavior.
## Feasibility: Replacing `CenterShrinkPanel`
`CenterShrinkPanel` currently has only two real usages, both in `EmptySessionPanel.kt`:
- `val view: CenterShrinkPanel = CenterShrinkPanel(this)` wraps the whole empty-state content and centers it.
- `add(CenterShrinkPanel(description), BorderLayout.CENTER)` centers the bounded-width welcome description.
`CenterShrinkPanel` behavior is: transparent wrapper, one child, center child, size = `min(preferred, maximum, available)` on both axes, preferred size = child preferred + insets. The new `Align` with `HAlign.CENTER` / `VAlign.CENTER` and min/preferred/max-aware sizing can cover this behavior and improve it by respecting minimum size where space allows.
Replacement is feasible:
- Use `alignCenter()` for both current `CenterShrinkPanel` call sites.
- Change `EmptySessionPanel.view` type from `CenterShrinkPanel` to `Align` (or `JComponent` if we want to hide the wrapper implementation); prefer `Align` to keep it explicit and testable.
- Update the class KDoc to reference `Align` instead of `CenterShrinkPanel`.
- Remove the `CenterShrinkPanel` import.
- Delete `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/CenterShrinkPanel.kt` after confirming no remaining references.
- Update `AGENTS.md` to remove the guidance about keeping `CenterShrinkPanel`; instead state that `Align` replaces one-child center/shrink wrappers.
Risk is low because `SessionUi.kt` only passes `panel.view` into `scroll.show(...)`, and no production code depends on `CenterShrinkPanel` APIs beyond it being a Swing component. Existing `EmptySessionPanelTest` assertions are mostly content/layout independent; add a focused assertion if needed to prove `view` is now an `Align` wrapper and still exposes nonzero preferred size.
## Target Files
- Update `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/Align.kt`
- Update `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/ui/AlignTest.kt`
- Update `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/EmptySessionPanel.kt` to replace `CenterShrinkPanel` with `Align` / `alignCenter()`.
- Update `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/EmptySessionPanelTest.kt` only if needed for changed type exposure or to preserve coverage.
- Delete `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/CenterShrinkPanel.kt` if all usages are removed.
- Update `packages/kilo-jetbrains/AGENTS.md` with the revised `TRACK` behavior, min/preferred/max-size guidance, and the fact that `Align` replaces old one-child center/shrink wrappers.
- Optionally update `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/PermissionView.kt` to use `Align` only if it simplifies current alignment code without broadening the change.
## API Shape
- Package remains `ai.kilocode.client.ui`.
- Keep `Align(child: Component, h: HAlign = HAlign.FIT, v: VAlign = VAlign.FIT) : JPanel(null)`.
- Extend enums:
- `enum class HAlign { TRACK, FIT, LEFT, CENTER, RIGHT }`
- `enum class VAlign { TRACK, FIT, TOP, CENTER, BOTTOM }`
- Constructor behavior stays the same:
- Set `isOpaque = false`
- Add the wrapped child as the only child
- Do not introduce colors, borders, fonts, or hardcoded spacing
## Layout Semantics
- Account for panel insets in all sizing and positioning.
- Clamp available inner width/height to `0` before setting child bounds.
- Use child sizes per axis:
- `min`: `child.minimumSize`
- `pref`: `child.preferredSize`, coerced into `[min, max]` when the axis is not `TRACK`
- `max`: `child.maximumSize`
- If child max is smaller than child min, treat the effective max as at least min so calculations remain stable.
- If available space is smaller than the effective minimum, shrink to available space to avoid drawing outside the wrapper. This is the only case where layout may go below minimum size.
### Horizontal Modes
- `TRACK`: child width equals available inner width, ignoring child min/preferred/max width.
- `FIT`: child width is available inner width constrained by effective min/max; if max prevents full width, place at left inset.
- `LEFT`: child width is bounded preferred width, shrunk to available if needed; x at left inset.
- `CENTER`: child width is bounded preferred width, shrunk to available if needed; x centered in available space.
- `RIGHT`: child width is bounded preferred width, shrunk to available if needed; x aligned to right edge.
### Vertical Modes
- `TRACK`: child height equals available inner height, ignoring child min/preferred/max height.
- `FIT`: child height is available inner height constrained by effective min/max; if max prevents full height, place at top inset.
- `TOP`: child height is bounded preferred height, shrunk to available if needed; y at top inset.
- `CENTER`: child height is bounded preferred height, shrunk to available if needed; y centered in available space.
- `BOTTOM`: child height is bounded preferred height, shrunk to available if needed; y aligned to bottom edge.
## Wrapper Min/Preferred/Max Size Semantics
Compute wrapper sizing independently per axis, then add insets for that axis.
- For non-`TRACK` axes (`FIT`, edge, and `CENTER`):
- `minimumSize` contribution is child minimum size.
- `preferredSize` contribution is child preferred size coerced into the effective min/max range.
- `maximumSize` contribution is child effective maximum size.
- For `TRACK` axes:
- `minimumSize` contribution is `0` because the child should not request space on that axis.
- `preferredSize` contribution is `0` because the child should not request space on that axis.
- `maximumSize` should not be constrained by the child's max size; use the panel/default maximum for that axis so parents can still allocate extra space for tracking.
- Include wrapper insets in all returned min/preferred/max dimensions.
- If there is no child, keep existing fallback behavior: no-op layout and delegate size methods to `super`.
## Kotlin-Style Factories
Update factory helpers in `Align.kt` so call sites stay concise and can opt into tracking.
Keep existing helpers:
- `fun Component.align(h: HAlign = HAlign.FIT, v: VAlign = VAlign.FIT) = Align(this, h, v)`
- `fun Component.alignCenter() = Align(this, HAlign.CENTER, VAlign.CENTER)`
- `fun Component.alignLeft(v: VAlign = VAlign.FIT) = Align(this, HAlign.LEFT, v)`
- `fun Component.alignRight(v: VAlign = VAlign.FIT) = Align(this, HAlign.RIGHT, v)`
- `fun Component.alignTop(h: HAlign = HAlign.FIT) = Align(this, h, VAlign.TOP)`
- `fun Component.alignBottom(h: HAlign = HAlign.FIT) = Align(this, h, VAlign.BOTTOM)`
Add tracking helpers:
- `fun Component.track() = Align(this, HAlign.TRACK, VAlign.TRACK)`
- `fun Component.trackX(v: VAlign = VAlign.FIT) = Align(this, HAlign.TRACK, v)`
- `fun Component.trackY(h: HAlign = HAlign.FIT) = Align(this, h, VAlign.TRACK)`
Keep helpers minimal; do not add every possible alignment combination unless it improves a real call site.
## EmptySessionPanel Replacement Steps
- Replace `import ai.kilocode.client.ui.CenterShrinkPanel` with `import ai.kilocode.client.ui.Align` and `import ai.kilocode.client.ui.alignCenter`.
- Change `val view: CenterShrinkPanel = CenterShrinkPanel(this)` to `val view: Align = alignCenter()`.
- Change `add(CenterShrinkPanel(description), BorderLayout.CENTER)` to `add(description.alignCenter(), BorderLayout.CENTER)`.
- Update KDoc from `[CenterShrinkPanel]` to `[Align]`.
- Grep for `CenterShrinkPanel` after edits; if only the class file remains, delete `CenterShrinkPanel.kt`.
## AGENTS.md Guidance
Update the existing `Align — Single-Component Alignment Wrapper` section:
- Add `TRACK` to the mode table.
- Explain `TRACK` as: fill all available space on that axis, ignore child min/preferred/max on that axis, and do not let the child contribute to wrapper min/preferred/max size on that axis.
- Explain `FIT` as: fill available space while respecting child min/max; use `TRACK` if full-size tracking must ignore constraints.
- Explain edge/center modes as: use bounded preferred size, respect min/max where space allows, shrink to available space only when necessary.
- Add factory examples for `child.track()`, `child.trackX(...)`, and `child.trackY(...)`.
- Keep the warning that `Align` is only for single-child alignment, not spacing, padding, borders, colors, or multi-child layout.
- Replace the `CenterShrinkPanel` note with: `CenterShrinkPanel` has been superseded by `Align`; use `child.alignCenter()` for the old center-and-shrink behavior.
## Tests
Update `AlignTest` using `BasePlatformTestCase` with real Swing components.
Keep existing coverage:
- Wrapper is non-opaque and contains exactly the wrapped child.
- Insets are honored.
- `FIT/FIT`, edge, and center modes place the child correctly.
- Existing factory helpers produce expected wrappers/layout.
Add coverage for min/max behavior:
- Non-track `CENTER/CENTER` uses preferred size coerced up to minimum when preferred is smaller than minimum and space is sufficient.
- Non-track `CENTER/CENTER` caps preferred size to maximum when preferred is larger than maximum and space is sufficient.
- Non-track `FIT/FIT` fills available space only up to maximum size when available is larger than maximum.
- Non-track `FIT/FIT` expands to at least minimum size when available is larger than minimum but smaller than preferred.
- Non-track modes shrink to available space when available is smaller than minimum, avoiding overflow.
- Wrapper `minimumSize`, `preferredSize`, and `maximumSize` include child min/bounded-pref/max contributions plus insets for non-track axes.
- `Align(HAlign.CENTER, VAlign.CENTER)` matches old `CenterShrinkPanel` center-and-shrink behavior for a child with preferred size larger than maximum size.
Add coverage for `TRACK` behavior:
- `TRACK/TRACK` lays child out to the full available inner bounds even when child's min/preferred/max sizes are smaller or larger.
- `TRACK/TRACK` wrapper `minimumSize` and `preferredSize` are only the wrapper insets, not child sizes.
- `TRACK/TRACK` wrapper `maximumSize` is not capped by the child maximum size; assert it is larger than the child's max or equals the panel/default maximum used by implementation.
- Mixed `TRACK` and non-track axes compute size independently, e.g. `HAlign.TRACK` + `VAlign.CENTER` has preferred width equal to horizontal insets only and preferred height based on child bounded preferred height plus vertical insets.
- `track()`, `trackX(...)`, and `trackY(...)` factory helpers produce expected layout and size behavior.
Update or add `EmptySessionPanelTest` coverage if needed:
- Verify `panel.view` remains non-opaque and has a visible preferred height.
- If exposing `Align` is acceptable, add a direct assertion that `panel.view is Align` only if that does not over-specify internals. Prefer behavior assertions over type assertions.
Test helper updates:
- Replace `fixedChild(w, h)` with a helper that can set min/preferred/max sizes independently, using an actual Swing component and overriding size getters.
- Keep assertions exact where layout is deterministic; for maximum size under `TRACK`, assert the child max is not used rather than relying on an arbitrary Swing default if needed.
## Verification
Run from `packages/kilo-jetbrains/`:
- `./gradlew :frontend:test --tests ai.kilocode.client.ui.AlignTest`
- `./gradlew :frontend:test --tests ai.kilocode.client.session.ui.EmptySessionPanelTest`
- `./gradlew :frontend:compileKotlin`
- If `AGENTS.md` table formatting changes are extensive, optionally run the repo markdown table padding check from root: `bun run script/check-md-table-padding.ts --fix`
## Notes
- No changeset is needed unless `Align` is applied to a user-visible UI path beyond replacing behavior-equivalent internal wrappers.
- Keep the implementation local to generic JetBrains UI code; this does not touch shared upstream-owned files.
@@ -25,7 +25,7 @@ import ai.kilocode.client.session.controller.SessionController
import ai.kilocode.client.session.controller.SessionControllerEvent
import ai.kilocode.client.session.ui.style.SessionUiStyle
import ai.kilocode.client.session.views.LoginRequiredView
import ai.kilocode.client.session.views.PermissionView
import ai.kilocode.client.session.views.permission.PermissionView
import ai.kilocode.client.session.views.question.QuestionView
import ai.kilocode.client.settings.profile.UserProfileConfigurable
import ai.kilocode.log.ChatLogSummary
@@ -10,11 +10,11 @@ import ai.kilocode.client.session.ui.style.SessionEditorStyle
import ai.kilocode.client.session.ui.style.SessionEditorStyleTarget
import ai.kilocode.client.session.ui.style.SessionUiStyle
import ai.kilocode.client.session.controller.SessionController
import ai.kilocode.client.ui.Align
import ai.kilocode.client.ui.HAlign
import ai.kilocode.client.ui.UiStyle
import ai.kilocode.client.ui.VAlign
import ai.kilocode.client.ui.align
import ai.kilocode.client.ui.layout.Align
import ai.kilocode.client.ui.layout.HAlign
import ai.kilocode.client.ui.layout.VAlign
import ai.kilocode.client.ui.layout.align
import ai.kilocode.rpc.dto.SessionDto
import com.intellij.icons.AllIcons
import com.intellij.openapi.Disposable
@@ -9,7 +9,7 @@ import ai.kilocode.client.session.ui.style.SessionEditorStyleTarget
import ai.kilocode.client.session.ui.style.SessionUiStyle
import ai.kilocode.client.session.views.LoginRequiredView
import ai.kilocode.client.session.views.MessageView
import ai.kilocode.client.session.views.PermissionView
import ai.kilocode.client.session.views.permission.PermissionView
import ai.kilocode.client.session.views.question.QuestionView
import ai.kilocode.client.session.views.TurnView
import com.intellij.openapi.Disposable
@@ -1,4 +1,4 @@
package ai.kilocode.client.session.views
package ai.kilocode.client.session.views.permission
import ai.kilocode.client.session.model.PermissionFileDiff
import ai.kilocode.client.session.ui.style.SessionEditorStyle
@@ -1,4 +1,4 @@
package ai.kilocode.client.session.views
package ai.kilocode.client.session.views.permission
import ai.kilocode.client.plugin.KiloBundle
import ai.kilocode.client.session.model.Permission
@@ -10,10 +10,10 @@ import ai.kilocode.client.session.ui.style.SessionEditorStyle
import ai.kilocode.client.session.ui.style.SessionEditorStyleTarget
import ai.kilocode.client.session.ui.style.SessionUiStyle
import ai.kilocode.client.session.ui.style.SessionUiStyle.View.CARD_LAYOUT_GAP
import ai.kilocode.client.ui.HAlign
import ai.kilocode.client.ui.UiStyle
import ai.kilocode.client.ui.VAlign
import ai.kilocode.client.ui.align
import ai.kilocode.client.ui.layout.HAlign
import ai.kilocode.client.ui.layout.VAlign
import ai.kilocode.client.ui.layout.align
import ai.kilocode.rpc.dto.PermissionReplyDto
import com.intellij.icons.AllIcons
import com.intellij.ui.ColorUtil
@@ -1,4 +1,4 @@
package ai.kilocode.client.ui
package ai.kilocode.client.ui.layout
import java.awt.Component
import java.awt.Dimension
@@ -22,7 +22,7 @@ import ai.kilocode.rpc.dto.KiloAppStateDto
import ai.kilocode.rpc.dto.KiloAppStatusDto
import ai.kilocode.rpc.dto.ProfileDto
import com.intellij.util.ui.JBUI
import ai.kilocode.client.session.views.PermissionView
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
@@ -10,7 +10,7 @@ import ai.kilocode.client.session.model.SessionState
import ai.kilocode.client.session.model.ToolCallRef
import ai.kilocode.client.session.ui.style.SessionEditorStyle
import ai.kilocode.client.session.views.LoginRequiredView
import ai.kilocode.client.session.views.PermissionView
import ai.kilocode.client.session.views.permission.PermissionView
import ai.kilocode.client.session.views.question.QuestionResultView
import ai.kilocode.client.session.views.question.QuestionView
import ai.kilocode.client.session.views.TextView
@@ -1,4 +1,4 @@
package ai.kilocode.client.session.views
package ai.kilocode.client.session.views.permission
import ai.kilocode.client.session.model.Permission
import ai.kilocode.client.session.model.PermissionFileDiff
@@ -1,4 +1,4 @@
package ai.kilocode.client.ui
package ai.kilocode.client.ui.layout
import com.intellij.testFramework.fixtures.BasePlatformTestCase
import com.intellij.ui.components.JBLabel