docs(jetbrains): consolidate agent guidance

This commit is contained in:
kirillk
2026-05-18 18:31:46 -04:00
parent 1c59315526
commit eeca40407e
3 changed files with 277 additions and 1904 deletions
+1 -2
View File
@@ -53,8 +53,7 @@ Extension-specific settings should live in the Kilo extension settings, not defa
## Package Instructions
- When a task primarily touches `packages/kilo-jetbrains/`, read `packages/kilo-jetbrains/AGENTS.md` before planning or editing.
- For JetBrains Kotlin/Swing UI work, also apply `packages/kilo-jetbrains/.kilo/skills/jetbrains-ui-style/SKILL.md`.
- When a task primarily touches `packages/kilo-jetbrains/`, read `packages/kilo-jetbrains/AGENTS.md` before planning or editing. It covers split-mode architecture, IntelliJ source lookup, threading fundamentals, UI guidelines, and session component architecture.
## Monorepo Structure
File diff suppressed because it is too large Load Diff
+276 -468
View File
@@ -1,8 +1,22 @@
# AGENTS.md — Kilo JetBrains Plugin
## Skills
## IntelliJ Platform Source Lookup
- Use `packages/kilo-jetbrains/.kilo/skills/jetbrains-ui-style/SKILL.md` when creating, modifying, or reviewing Kotlin/Swing UI code for this plugin. It covers IntelliJ UI conventions, platform components, theme-aware colors/fonts, spacing, manual Swing vs Kotlin UI DSL, session transcript styling, and retained Swing component-state patterns for hover/expand/update flows.
When looking for IntelliJ Platform API usage, implementation examples, extension points, services, actions, inspections, PSI/VFS/editor behavior, or plugin patterns, prefer real IntelliJ source code over Gradle caches, downloaded jars, generated parser artifacts, or decompiled classes.
Use this priority order:
1. Check whether `$INTELLIJ_REPO` is set and points to a readable IntelliJ Community checkout.
- If set, search source files under that directory first.
- Prefer implementation source files from `platform/`, `plugins/`, `java/`, `xml/`, `json/`, `jvm/`, and related modules.
- Do not assume the IntelliJ checkout is a sibling of the current repo or worktree.
2. If `$INTELLIJ_REPO` is unset, empty, unreadable, or does not appear to contain an IntelliJ source checkout, tell the user to set it up.
- Suggested instruction: `Set INTELLIJ_REPO to the path of a local intellij-community checkout, for example: export INTELLIJ_REPO=/path/to/intellij-community`
- Do not invent or hardcode a machine-specific absolute path.
3. If a local checkout is unavailable, fall back to the public IntelliJ Community repository: https://github.com/JetBrains/intellij-community
4. Only inspect Gradle caches, downloaded jars, generated parser jars, decompiled classes, or dependency internals as a last resort when neither a local IntelliJ source checkout nor the public GitHub repository provides the needed information.
Avoid starting searches in `~/.gradle/caches`, `.gradle/`, downloaded dependency jars, generated parser artifacts, or decompiled library sources.
## Architecture (Split Mode)
@@ -34,6 +48,56 @@
- Avoid chatty RPC. Debounce UI events, batch requests, cache results where appropriate, and page large datasets instead of sending everything at once.
- If a new split feature requires RPC support similar to the JetBrains template, mirror the template's wiring: `shared` and `frontend` use the RPC/serialization plugins, and the backend adds the required backend RPC platform modules.
## Fundamentals
### Threading and Coroutine Context Annotations
IntelliJ Platform provides method-level annotations to declare threading and coroutine context requirements. All annotations live in `com.intellij.util.concurrency.annotations`. Source in `$INTELLIJ_REPO`: `platform/core-api/src/com/intellij/util/concurrency/annotations/`.
| Annotation | Requirement |
|---|---|
| `@RequiresEdt` | Must run on EDT. Injects a runtime assertion by default. |
| `@RequiresBackgroundThread` | Must run off EDT. Injects a runtime assertion by default. |
| `@RequiresReadLock` | Must hold read or write lock. |
| `@RequiresWriteLock` | Must hold write lock. |
| `@RequiresReadLockAbsence` | Must not hold any read or write lock. |
| `@RequiresBlockingContext` | Must not be called from a `suspend` context. Source-retained only, no runtime assertion. |
All five `Requires*` thread/lock annotations accept `generateAssertion = false` to document intent without injecting a runtime check.
For custom dispatchers/context return types that are IO-safe or non-blocking, use `@BlockingExecutor` / `@NonBlockingExecutor` from `org.jetbrains.annotations`.
For blocking I/O in coroutines, move the dispatcher switch inside the callee using `withContext(Dispatchers.IO)`. There is no annotation that requires `Dispatchers.IO`; the `BlockingMethodInNonBlockingContextInspection` static analysis covers this.
**Decision guide:**
| Situation | Solution |
|---|---|
| Method must run on EDT | `@RequiresEdt` |
| Method must run off EDT | `@RequiresBackgroundThread` |
| Method requires read or write access | `@RequiresReadLock` |
| Method requires write access | `@RequiresWriteLock` |
| Method must not hold any lock | `@RequiresReadLockAbsence` |
| Blocking function, suspend alternative exists | `@RequiresBlockingContext` |
| Blocking I/O work in a coroutine | `withContext(Dispatchers.IO)` inside the callee |
| Custom IO-safe dispatcher | `@BlockingExecutor` on the return type or class |
| Custom non-blocking dispatcher | `@NonBlockingExecutor` on the return type or class |
### EDT Requirements for UI Updates
- **All Swing UI creation, mutation, and access must happen on the EDT.** This is not negotiable. The IntelliJ platform enforces it at runtime via `@RequiresEdt` and `ThreadingAssertions`.
- Annotate every method that touches Swing components or `SessionModel` with `@RequiresEdt`.
- Never create Swing components, update labels/colors/borders, or call `revalidate()`/`repaint()` from a background thread or coroutine. Use `ApplicationManager.getApplication().invokeLater { }` or `withContext(Dispatchers.Main)` to switch to the EDT.
- For tool-window-related EDT tasks, use `ToolWindowManager.invokeLater()` instead of `Application.invokeLater()`.
### Tests for EDT and UI Update Behavior
- Any code path that modifies UI state or depends on EDT threading must have tests that exercise the actual implementation.
- Extend `BasePlatformTestCase` to get a real IntelliJ Application and EDT in tests. The session package already uses `SessionControllerTestBase` which wraps this.
- Do not mock the EDT or threading assertions — test against the real threading model.
- For state-driven updates, assert that the component state matches after flushing coroutines and draining the EDT.
- For retained Swing components, assert that expand/collapse, update, and no-op paths work correctly without rebuilding the component tree.
## CLI Integration
- CLI process spawning, extraction, and lifecycle belong in `backend`.
@@ -230,11 +294,15 @@ Official references:
- [User Interface Components](https://plugins.jetbrains.com/docs/intellij/user-interface-components.html)
- [UI FAQ (colors, borders, icons)](https://plugins.jetbrains.com/docs/intellij/ui-faq.html)
### Do Not Use Kotlin UI DSL
**Do not use Kotlin UI DSL v2 (`com.intellij.ui.dsl.builder`) in this plugin.** Use standard Swing with IntelliJ Platform components for all UI layout. The JetBrains modular template and some older sections of the codebase reference the DSL, but Kilo should not introduce it.
### Do Not Use Kotlin Compose
**Do not use Kotlin Compose or `intellij.platform.compose` in this plugin.** The JetBrains modular template uses Compose for its demo tool window, but Kilo should use standard Swing with IntelliJ Platform components only. Keep all plugin UI in the existing Swing-based stack.
### Do Not Use JCEF (Embedded Browser)
### Do Not Use JCEF
**Do not use JCEF (`JBCefBrowser`) in this plugin.** JCEF does not work in JetBrains remote development (split mode): the frontend process runs on the client machine but JCEF requires a display on the host, making it effectively unusable for remote users. Use standard Swing with IntelliJ Platform components for all UI.
@@ -242,443 +310,86 @@ Official references:
| Need | API |
|---|---|
| Dialogs, settings pages, forms, any layout with components | **Preferred**: [Kotlin UI DSL v2](https://plugins.jetbrains.com/docs/intellij/kotlin-ui-dsl-version-2.html) (`com.intellij.ui.dsl.builder`) |
| Tool window panels, action-driven UI, custom components | Standard Swing with IntelliJ Platform component replacements (see below) |
| Any layout, forms, panels | Standard Swing with IntelliJ Platform component replacements |
| Tool windows | `SimpleToolWindowPanel` + `ToolWindow.contentManager` |
| Menus and toolbars | [Action System](https://plugins.jetbrains.com/docs/intellij/action-system.html) |
### Kotlin UI DSL v2 (Preferred)
**Use Kotlin UI DSL v2 as the default way to build UI** for dialogs, settings pages, forms, and any layout composed of standard components. It produces correct spacing, label alignment, HiDPI scaling, and accessibility automatically. Only fall back to manual Swing layout when you need a fully custom component (e.g. a canvas, rich list renderer, or tool-window chrome that the DSL can't express).
The top-level builder is `panel { }` (returns `DialogPanel`). Structure: **panel → row → cells**. Cell factory methods (`textField()`, `checkBox()`, `label()`, etc.) add components. The DSL lives in `com.intellij.ui.dsl.builder`.
To explore DSL capabilities interactively: **Tools → Internal Actions → UI → Kotlin UI DSL → UI DSL Showcase** (requires internal mode: `-Didea.is.internal=true`).
Reference: [Kotlin UI DSL v2 docs](https://plugins.jetbrains.com/docs/intellij/kotlin-ui-dsl-version-2.html)
#### Basics — Panel / Row / Cell
Rows occupy full width. The last cell in a row takes remaining space. Rows have a `layout` property (see Row Layout below).
```kotlin
panel {
row("Row1 label:") {
textField()
label("Some text")
}
row("Row2:") {
label("This text is aligned with previous row")
}
}
```
#### Row Layout
Every row uses one of three layouts. Default is `LABEL_ALIGNED` when a label is provided for the row, `INDEPENDENT` otherwise.
| Layout | Behavior |
|---|---|
| `LABEL_ALIGNED` | Label column + content columns, aligned across rows |
| `INDEPENDENT` | All cells are independent, no cross-row alignment |
| `PARENT_GRID` | Cells align with the parent grid columns across rows |
```kotlin
panel {
row("PARENT_GRID:") {
label("Col 1")
label("Col 2")
}.layout(RowLayout.PARENT_GRID)
row("PARENT_GRID:") {
textField()
textField()
}.layout(RowLayout.PARENT_GRID)
row("LABEL_ALIGNED (default with label):") {
textField()
}
row {
label("INDEPENDENT (default without label):")
textField()
}
}
```
#### Components Reference
All cell factory methods available inside `row { }`:
| Method | Description |
|---|---|
| `checkBox("text")` | Checkbox |
| `threeStateCheckBox("text")` | Three-state checkbox |
| `radioButton("text", value)` | Radio button (must be inside `buttonsGroup {}`) |
| `button("text") {}` | Push button |
| `actionButton(action)` | Icon button bound to an `AnAction` |
| `actionsButton(action1, action2, ...)` | Dropdown actions button |
| `segmentedButton(items) { text = it }` | Segmented control |
| `tabbedPaneHeader(items)` | Tab header strip |
| `label("text")` | Static label |
| `text("html")` | Rich text with links, icons, line-width control |
| `link("text") {}` | Focusable clickable link |
| `browserLink("text", "url")` | Opens URL in browser |
| `dropDownLink("default", listOf(...))` | Dropdown link selector |
| `icon(AllIcons.*)` | Icon display |
| `contextHelp("description", "title")` | Help icon with popup |
| `textField()` | Text input |
| `passwordField()` | Password input |
| `textFieldWithBrowseButton()` | Text field + browse dialog |
| `expandableTextField()` | Expandable multi-line text field |
| `extendableTextField()` | Text field with extension icons |
| `intTextField(range)` | Integer input with validation |
| `spinner(intRange)` / `spinner(doubleRange, step)` | Numeric spinner |
| `slider(min, max, minorTick, majorTick)` | Slider (use `.labelTable()` for tick labels) |
| `textArea()` | Multi-line text (use `.rows(n)` and `.align(AlignX.FILL)`) |
| `comboBox(items)` | Combo box / dropdown |
| `comment("text")` | Gray comment text (standalone) |
| `cell(component)` | Wrap any arbitrary Swing component |
| `scrollCell(component)` | Wrap component in a scroll pane |
| `cell()` | Empty placeholder cell for grid alignment |
Key component examples:
```kotlin
panel {
// Radio button group
var color = "grey"
buttonsGroup {
row("Color:") {
radioButton("White", "white")
radioButton("Grey", "grey")
}
}.bind({ color }, { color = it })
// Slider with tick labels
row("slider:") {
slider(0, 10, 1, 5)
.labelTable(mapOf(
0 to JLabel("0"),
5 to JLabel("5"),
10 to JLabel("10"),
))
}
// Text area with proper alignment
row {
label("textArea:")
.align(AlignY.TOP)
.gap(RightGap.SMALL)
textArea()
.rows(5)
.align(AlignX.FILL)
}.layout(RowLayout.PARENT_GRID)
}
```
#### Component Labels
Labels for modifiable components **must** be connected via one of two methods — this ensures correct spacing, mnemonic support, and accessibility:
- **Row label**: `row("&Label:") { textField() }` — mnemonic via `&`, label in left column
- **Cell label**: `textField().label("&Label:", LabelPosition.TOP)` — label attached to cell, optionally on top
```kotlin
panel {
row("&Row label:") {
textField()
textField()
.label("Cell label at &left:")
}
row {
textField()
.label("Cell label at &top:", LabelPosition.TOP)
}
}
```
Note: when a row contains a `checkBox` or `radioButton`, the DSL automatically increases space after the row label per [UI Guidelines](https://plugins.jetbrains.com/docs/intellij/layout.html#checkboxes-and-radio-buttons).
#### Comments
Three types of comments, each with different placement and semantics:
| Type | Method | Placement |
|---|---|---|
| Cell comment (bottom) | `cell.comment("text")` | Below the cell |
| Cell comment (right) | `cell.commentRight("text")` | Right of the cell |
| Cell context help | `cell.contextHelp("text", "title")` | Help icon with popup |
| Row comment | `row.rowComment("text")` | Below the entire row |
| Arbitrary comment | `comment("text")` | Standalone gray text |
```kotlin
panel {
row {
textField()
.comment("Bottom comment")
textField()
.commentRight("Right comment")
textField()
.contextHelp("Help popup text")
}
row("Label:") {
textField()
}.rowComment("This comment sits below the whole row")
row {
comment("Standalone comment, supports <a href='link'>links</a> and <icon src='AllIcons.General.Information'>&nbsp;icons")
}
}
```
Comments support HTML with clickable links (pass a lambda for the click handler), bundled icons via `<icon src='...'>`, and line width control via `maxLineLength` parameter. Use `MAX_LINE_LENGTH_NO_WRAP` to prevent wrapping.
#### Groups and Structure
| Method | Grid | Description |
|---|---|---|
| `panel {}` | Own grid | Sub-panel occupying full width |
| `rowsRange {}` | Parent grid | Grouped rows sharing parent grid — useful with `enabledIf` |
| `group("Title") {}` | Own grid | Titled section with vertical spacing before/after |
| `groupRowsRange("Title") {}` | Parent grid | Titled section sharing parent grid alignment |
| `collapsibleGroup("Title") {}` | Own grid | Expandable section (Tab-focusable, supports mnemonics) |
| `buttonsGroup("Title") {}` | — | Groups `radioButton` or `checkBox` under a title |
| `separator()` | — | Horizontal separator line |
| Row `panel {}` | Own grid | Sub-panel inside a cell |
```kotlin
panel {
group("Settings") {
row("Name:") { textField() }
row("Path:") { textFieldWithBrowseButton() }
}
collapsibleGroup("Advanced") {
row("Timeout:") { intTextField(0..1000) }
}
var enabled = true
buttonsGroup("Mode:") {
row { radioButton("Automatic", true) }
row { radioButton("Manual", false) }
}.bind({ enabled }, { enabled = it })
separator()
row {
label("Nested panels:")
panel {
row("Sub row 1:") { textField() }
row("Sub row 2:") { textField() }
}
}
}
```
#### Gaps and Spacing
- **Horizontal gaps**: `cell.gap(RightGap.SMALL)` between a label-like checkbox and its related field. Medium gap is the default between cells.
- **Two-column layout**: `twoColumnsRow({}, {})` or `gap(RightGap.COLUMNS)` with `.layout(RowLayout.PARENT_GRID)`.
- **Left indent**: `indent {}` for indented sub-content.
- **Vertical gaps**: `.topGap(TopGap.MEDIUM)` / `.bottomGap(BottomGap.MEDIUM)` on rows to separate unrelated groups. Attach gaps to the "related" row so hiding rows doesn't break layout.
```kotlin
panel {
group("Horizontal Gaps") {
row {
val cb = checkBox("Use mail:")
.gap(RightGap.SMALL)
textField()
.enabledIf(cb.selected)
}
row("Width:") {
textField()
.gap(RightGap.SMALL)
label("pixels")
}
}
group("Indent") {
row { label("Not indented") }
indent {
row { label("Indented row") }
}
}
group("Two Columns") {
twoColumnsRow({
checkBox("First column")
}, {
checkBox("Second column")
})
}
group("Vertical Gaps") {
row { checkBox("Option 1") }
row { checkBox("Option 2") }
row { checkBox("Unrelated option") }
.topGap(TopGap.MEDIUM)
}
}
```
#### Enabled / Visible (Reactive State)
Bind enabled/visible state to a checkbox or other observable. Works on rows, `indent {}` blocks, `rowsRange {}`, and individual cells.
```kotlin
panel {
group("Enabled") {
lateinit var cb: Cell<JBCheckBox>
row { cb = checkBox("Enable options") }
indent {
row { checkBox("Option 1") }
row { checkBox("Option 2") }
}.enabledIf(cb.selected)
}
group("Visible") {
lateinit var cb: Cell<JBCheckBox>
row { cb = checkBox("Show options") }
indent {
row { checkBox("Option 1") }
row { checkBox("Option 2") }
}.visibleIf(cb.selected)
}
}
```
#### Binding (Property Binding)
Bind component values to model properties. Values are applied on `DialogPanel.apply()`, checked with `.isModified()`, and reverted with `.reset()`.
| Method | Component |
|---|---|
| `bindSelected(model::prop)` | checkBox |
| `bindText(model::prop)` | textField |
| `bindIntText(model::prop)` | intTextField |
| `bindItem(model::prop.toNullableProperty())` | comboBox |
| `bindValue(model::prop)` | slider |
| `bindIntValue(model::prop)` | spinner |
| `buttonsGroup {}.bind(model::prop)` | radio group |
```kotlin
enum class Theme { LIGHT, DARK }
data class Settings(
var name: String = "",
var count: Int = 0,
var enabled: Boolean = false,
var theme: Theme = Theme.LIGHT,
)
val model = Settings()
val panel = panel {
row("Name:") {
textField().bindText(model::name)
}
row("Count:") {
intTextField(0..100).bindIntText(model::count)
}
row {
checkBox("Enabled").bindSelected(model::enabled)
}
buttonsGroup("Theme:") {
row { radioButton("Light", Theme.LIGHT) }
row { radioButton("Dark", Theme.DARK) }
}.bind(model::theme)
}
// Later:
panel.isModified() // true if UI differs from model
panel.apply() // write UI values to model
panel.reset() // revert UI to model values
```
#### Validation
Attach input validation rules to cells. Rules run continuously and display inline error/warning indicators.
```kotlin
panel {
row("Username:") {
textField()
.columns(COLUMNS_MEDIUM)
.cellValidation {
addInputRule("Must not be empty") {
it.text.isBlank()
}
}
}
row("Port:") {
textField()
.columns(COLUMNS_MEDIUM)
.cellValidation {
addInputRule("Contains non-numeric characters", level = Level.WARNING) {
it.text.contains(Regex("[^0-9]"))
}
}
}
}
```
Activate validators by calling `dialogPanel.registerValidators(disposable)` after creating the panel.
#### Tips and Common Patterns
| Pattern | Usage |
|---|---|
| `.bold()` | Bold text on any cell |
| `.columns(COLUMNS_MEDIUM)` | Set preferred width of textField / comboBox / textArea |
| `.text("initial")` | Set initial text on text components |
| `.resizableColumn()` | Column fills remaining horizontal space |
| `cell()` | Empty placeholder cell for grid alignment |
| `.widthGroup("name")` | Equalize widths across rows (cannot combine with `AlignX.FILL`) |
| `.align(AlignX.FILL)` | Stretch component to fill available width |
| `.align(AlignY.TOP)` | Top-align component in its cell |
| `.applyToComponent { }` | Direct access to the underlying Swing component |
| `.selected(true)` | Default-select a radioButton when no bound value matches |
| `.gap(RightGap.COLUMNS)` | Column-level gap for multi-column layouts |
```kotlin
panel {
row { label("Title").bold() }
row("Name:") {
textField()
.columns(COLUMNS_MEDIUM)
.resizableColumn()
.align(AlignX.FILL)
}
row("") {
textField()
}.rowComment("""Use row("") for an empty label column that aligns with labeled rows""")
row {
text("Comment-colored text")
.applyToComponent { foreground = JBUI.CurrentTheme.ContextHelp.FOREGROUND }
}
}
```
### Tool Windows
- Register declaratively in module XML via `com.intellij.toolWindow` extension point (already done in `kilo.jetbrains.frontend.xml`).
- Implement `ToolWindowFactory.createToolWindowContent()` — called lazily on first click (zero overhead if unused).
- Use `SimpleToolWindowPanel(vertical = true)` as a convenient base — supports toolbar + content layout.
- Add tabs via `ToolWindow.contentManager`: create content with `ContentFactory.getInstance().createContent(component, title, isLockable)`, then `contentManager.addContent()`.
- For conditional display, implement `ToolWindowFactory.isApplicableAsync(project)`.
- Always use `ToolWindowManager.invokeLater()` instead of `Application.invokeLater()` for tool-window-related EDT tasks.
### Dialogs
- Extend `DialogWrapper`. Call `init()` from the constructor. Override `createCenterPanel()` to return UI content — prefer Kotlin UI DSL v2 for the panel contents.
- Override `getPreferredFocusedComponent()` for initial focus, `getDimensionServiceKey()` for size persistence.
- Show with `showAndGet()` (modal, returns boolean) or `show()` (then use `getExitCode()`).
- Input validation: call `initValidation()` in constructor, override `doValidate()` → return `null` if valid or `ValidationInfo(message, component)` if not.
| Dialogs | Extend `DialogWrapper` |
### UiStyle — Point of Truth for Reusable Constants
**`UiStyle`** (`frontend/src/main/kotlin/ai/kilocode/client/ui/UiStyle.kt`) is the single source of truth for reusable UI constants in this plugin.
Before introducing any new reusable color, spacing value, border, size, font, or helper:
1. Check the IntelliJ source (`$INTELLIJ_REPO`) or public repository for an existing standard API or named key (e.g. `JBUI.CurrentTheme.*`, `UIUtil.*`, `NamedColorUtil.*`, `JBColor.namedColor(...)`, `JBFont.*`).
2. If a standard platform key exists, use it directly — do not copy the value into `UiStyle`.
3. If no standard key fits, add the new reusable token to `UiStyle`. Do not scatter constants into component-local fields, pass them through constructors, or duplicate them across files.
`UiStyle` contents:
- `UiStyle.Gap` — DPI-aware spacing primitives (`xs`, `sm`, `md`, `lg`, `pad`). Use for borders, gaps, and insets anywhere in the plugin.
- `UiStyle.Colors` — theme-aware generic colors (`bg`, `fg`, `weak`, `editorBackground`, `errorLabelForeground`, `warningLabelForeground`).
- `UiStyle.Components` — small reusable Swing helpers (e.g. `transparent()`).
**`SessionUiStyle`** (`frontend/src/main/kotlin/ai/kilocode/client/session/ui/style/SessionUiStyle.kt`) owns static tokens specific to the chat/session UI:
- `SessionUiStyle.SessionLayout` — transcript list geometry and scroll increments.
- `SessionUiStyle.View` — card sizing, card borders, surfaces, hover colors, and nested objects for `Prompt`, `Reasoning`, `Message`, and `Tool`.
- `SessionUiStyle.RecentSessions` — recent sessions list limits.
- `SessionUiStyle.Timeline` — activity-indicator colors for the session header timeline.
- `Dock` — border presets for question, permission, and connection dock panels.
Rules:
- Generic layout constants (gaps, generic colors, reusable helpers) → `UiStyle`.
- Session-specific constants (transcript layout, prompt chrome, card geometry, session colors) → `SessionUiStyle`.
- Do not create extra fields whose only purpose is to hold a constant value. Reference the constant object directly, e.g. `UiStyle.Gap.lg()` or `SessionUiStyle.View.Prompt.EDITOR_LINES`.
- Do not pass style constants through constructors or method parameters. Using the constant directly at the call site is correct and preferred. Only introduce parameters for values that are genuinely variable or test-controlled.
### Primary UI Rules
- Use IntelliJ platform components instead of raw Swing where an equivalent exists.
- Do not set default Swing properties explicitly. Avoid `isOpaque = false` unless the component default differs or there is a documented rendering reason.
- Avoid hardcoded dimensions. Prefer layout semantics, component defaults, and `JBUI` utilities.
- Do not add decorative helper functions, wrappers, or defensive UI code unless they materially improve clarity or correctness.
- Do not use hardcoded runtime colors in UI code.
- Derive UI colors from semantic IntelliJ theme APIs: `UIUtil`, `JBUI.CurrentTheme`, `NamedColorUtil`, `JBColor.namedColor`, or `JBColor.lazy`.
- Do not use hardcoded font sizes or font families.
- Use component defaults, `JBFont`, `RelativeFont`, or existing component fonts for typography.
- Put user-visible strings in `*.properties` files.
### Manual Swing Rules
- Use IntelliJ platform components instead of raw Swing components.
- Use layout managers and component defaults rather than fixed sizes.
- Use `JBUI.Borders.empty(...)`, `JBUI.insets(...)`, `JBUI.size(...)`, and `JBUI.scale(...)` instead of raw AWT/Swing sizing primitives.
- Do not set default properties explicitly.
- Keep custom components small and focused.
- For painting, use theme-aware colors and `JBUI.scale(...)` for pixel values.
### Retained Swing Component State
Swing is retained-mode UI. For dynamic Swing surfaces such as session cards, transcript parts, hover rows, and collapsible panels, build a stable component tree once and then mutate existing components in response to model or interaction changes.
- Do not use a React-style `state -> render()` loop for Swing components.
- Avoid card-level `render()` methods that remove/recreate headers, text areas, markdown views, controls, or scroll panes after every click, hover, or model update.
- Give each renderer an `update(model)` method that applies model changes directly to existing UI components.
- In `update(model)`, compare before assigning when practical: label text, icons, foregrounds, fonts, body text, visibility, cursor, and containment.
- Do not duplicate state in booleans when Swing component state already answers the question.
- Derive expanded state from containment (e.g. `scroll.parent === root`) rather than maintaining an `open` boolean.
- Derive hover state from the current header background or other component property rather than maintaining a `hover` boolean.
- Expand/collapse should attach or detach the existing body component and update controls only if attachment changed.
- Hover should update only the affected component (usually the header background) and repaint only that component when the effective color changed.
- Lazy-create expensive bodies such as `JBTextArea`, `JBScrollPane`, markdown panes, and HTML panes on first expansion or first direct access.
- Parent containers should refresh for add/remove/reorder operations, not automatically after every delegated child update or streaming delta.
- Child views should call `revalidate()`/`repaint()` only when they changed preferred size, visibility, containment, or paint output.
- Empty deltas, identical text, unchanged styles, repeated hover values, and no-op toggles should not repaint the whole card.
- Private helpers named `render()` in Swing views invite full tree rebuilds. Prefer names like `syncBody()`, `syncArrow()`, `syncHtml()`, or `applyModel()`.
Tests for retained Swing components should assert:
- Collapsed components start unattached; expensive bodies are not created until first expansion.
- First expansion creates the body once; collapse detaches it; re-expansion reuses the same instance.
- `isExpanded()` agrees with actual containment.
- `update(model)` changes existing labels/body text without duplicating components.
- Updates while collapsed do not eagerly create lazy bodies.
- No-op updates, empty deltas, repeated hover values, and toggling non-expandable cards do not repaint/revalidate the whole view.
### Platform Components — Always Use Instead of Raw Swing
@@ -694,11 +405,11 @@ panel {
| `JSplitPane` | `JBSplitter` | `com.intellij.ui` |
| `JTabbedPane` | `JBTabs` | `com.intellij.ui.tabs` |
| `JCheckBox` | `JBCheckBox` | `com.intellij.ui.components` |
| `Color` | `JBColor` | `com.intellij.ui` |
| Raw runtime colors | `UIUtil`, `JBUI.CurrentTheme`, `NamedColorUtil`, `JBColor.namedColor`, `JBColor.lazy` | `com.intellij.util.ui`, `com.intellij.ui` |
| `EmptyBorder` | `JBUI.Borders.empty()` | `com.intellij.util.ui` |
| Hardcoded pixel sizes | `JBUI.scale(px)` | `com.intellij.util.ui` |
Inspection `Plugin DevKit | Code | Undesirable class usage` highlights when you use raw Swing where a platform replacement exists.
Inspection `Plugin DevKit | Code | Undesirable class usage` highlights raw Swing usage where a platform replacement exists.
### Multi-line and Rich Text
@@ -707,49 +418,112 @@ Inspection `Plugin DevKit | Code | Undesirable class usage` highlights when you
| Rich HTML with modern CSS, icons, shortcuts | `JBHtmlPane` (`com.intellij.ui.components.JBHtmlPane`) |
| Simple multi-line label with HTML | `JBLabel` + `XmlStringUtil.wrapInHtml()` |
| Scrollable / wrapping HTML panel | `SwingHelper.createHtmlViewer()` |
| High-perf colored text fragments (trees/lists/tables) | `SimpleColoredComponent` |
| High-performance colored text fragments in trees/lists/tables | `SimpleColoredComponent` |
| Plain-text newline splitting | `MultiLineLabel` — legacy, do not use in new code |
- Build HTML programmatically with `HtmlChunk`/`HtmlBuilder` (`com.intellij.openapi.util.text.HtmlChunk`). Avoid raw HTML string concatenation — it risks injection and breaks localization.
- For simple wrapping/escaping: `XmlStringUtil.wrapInHtml(content)`, `XmlStringUtil.wrapInHtmlLines(lines...)`, `XmlStringUtil.escapeString(text)`.
- Selectable/copyable label text: `JBLabel.setCopyable(true)` (switches internally to `JEditorPane` while preserving label appearance). Use `setAllowAutoWrapping(true)` for auto-wrap.
- When creating a `JEditorPane` manually, always use `HTMLEditorKitBuilder` instead of constructing `HTMLEditorKit` directly: `editorPane.setEditorKit(HTMLEditorKitBuilder.simple())` or `.withWordWrapViewFactory().build()`.
- Single-line overflow/ellipsis: use `SwingTextTrimmer` — do not manually truncate strings.
- Selectable/copyable label text: `JBLabel.setCopyable(true)`. Use `setAllowAutoWrapping(true)` for auto-wrap.
- When creating a `JEditorPane` manually, always use `HTMLEditorKitBuilder` instead of constructing `HTMLEditorKit` directly.
- Single-line overflow/ellipsis: use `SwingTextTrimmer`. Do not manually truncate strings.
- All user-visible strings go in `*.properties` files; HTML markup in values is acceptable.
### Colors and Theming
### Theme-Derived Colors
- **Never** use `java.awt.Color` directly. Use `JBColor(lightColor, darkColor)` or `JBColor.namedColor("key", fallback)` for theme-aware colors.
- For lazy color retrieval (e.g. in painting), use `JBColor.lazy { UIManager.getColor("key") }`.
- Check current theme: `JBColor.isBright()` returns `true` for light themes.
- Generic UI colors: `UIUtil.getContextHelpForeground()`, `UIUtil.getLabelForeground()`, `UIUtil.getPanelBackground()`, etc.
Do not hardcode runtime colors. IntelliJ UI colors must come from the current theme, component state, editor color scheme, or a centralized semantic named color key.
Prefer semantic helpers for common UI roles:
| Need | Preferred API |
|---|---|
| Ordinary label text | `UIUtil.getLabelForeground()` |
| Secondary/help text | `UIUtil.getContextHelpForeground()` |
| Error label text | `UIUtil.getErrorForeground()` |
| Warning label text | `JBUI.CurrentTheme.Label.warningForeground()` |
| Inactive secondary text | `NamedColorUtil.getInactiveTextColor()` |
| Bounds and standard border color | `NamedColorUtil.getBoundsColor()` / `JBColor.border()` |
| Links | `JBUI.CurrentTheme.Link.Foreground.ENABLED` / `HOVERED` / `PRESSED` |
| List renderer text/background | `UIUtil.getListForeground(selected, focused)` / `UIUtil.getListBackground(selected, focused)` |
| Tree renderer text/background | `UIUtil.getTreeForeground(selected, focused)` / `UIUtil.getTreeBackground(selected, focused)` |
| Popup background | `JBUI.CurrentTheme.Popup.BACKGROUND` |
| Validation errors | `JBUI.CurrentTheme.Validator.errorBorderColor()` / `errorBackgroundColor()` |
| Validation warnings | `JBUI.CurrentTheme.Validator.warningBorderColor()` / `warningBackgroundColor()` |
Use `JBColor.lazy { ... }` for colors that depend on runtime state or the active editor color scheme. Use `JBColor.namedColor("Some.Semantic.Key", fallback)` when defining or consuming a semantic color key. For HTML/CSS snippets, compute the color from a theme API and convert it with `ColorUtil.toHtmlColor(...)`.
Avoid inline `Color(...)`, numeric `JBColor(...)`, `Gray.xNN`, `JBColor.GRAY`, and hex color literals in runtime UI code. The exception is a centralized semantic color definition with a named color key when no existing platform key exists.
### Theme-Derived Fonts
Do not hardcode font sizes or font families.
- Prefer component default fonts when no style change is needed.
- Use `JBFont.h1()` through `JBFont.h4()` for headings.
- Use `.asBold()`, `.asItalic()`, and `.asPlain()` for style changes on `JBFont` values.
- Use `JBFont.regular()`, `JBFont.medium()`, and `JBFont.small()` for regular and secondary text.
- Use `RelativeFont` when adjusting an existing component font relatively.
- For errors, grayed text, shortcuts, and links in renderers, prefer `SimpleTextAttributes.ERROR_ATTRIBUTES`, `GRAYED_ATTRIBUTES`, `SHORTCUT_ATTRIBUTES`, and `LINK_ATTRIBUTES`.
Avoid `Font("...")`, raw font sizes, and `deriveFont(14f)` style calls.
### Borders, Insets, and Spacing
- Always create via `JBUI.Borders.empty(top, left, bottom, right)` and `JBUI.insets()` — DPI-aware and auto-update on zoom.
- Always create borders via `JBUI.Borders.empty(top, left, bottom, right)` and insets via `JBUI.insets()` — DPI-aware and auto-update on zoom.
- Use `JBUI.scale(int)` for any pixel dimension to ensure proper HiDPI scaling.
- Do not use `EmptyBorder`, raw `Insets`, or raw `Dimension` unless there is no platform alternative.
- Theme-dependent borders, insets, colors, and corner arcs must be re-evaluated when the Look and Feel changes. Do not assign a theme-derived border once in a constructor for a long-lived component. Prefer overriding `updateUI()` or subscribing to `LafManagerListener.TOPIC`.
- Use `JBValue.UIInteger` for themeable arc and spacing values. Call `.get()` during layout and size calculation; do not cache the resolved `Int` in a constructor or property initializer.
### UI Style Constants
For common spacing lookups, prefer `JBUI.CurrentTheme` area-specific insets (e.g. `JBUI.CurrentTheme.ActionsList.cellPadding()`, `JBUI.CurrentTheme.Toolbar.toolbarButtonInsets()`, `JBUI.CurrentTheme.ToolWindow.headerLabelLeftRightInsets()`) over inventing numbers.
Style tokens and layout constants are split across three files. Put new constants in the right place — do not scatter them into ad hoc fields or pass them through constructors.
### Platform Spacing Utilities
**`UiStyle`** (`frontend/src/main/kotlin/ai/kilocode/client/ui/UiStyle.kt`) — generic Swing style tokens that are not tied to any single session component:
- `UiStyle.Gap` — DPI-aware spacing primitives (`xs`, `sm`, `md`, `lg`, `pad`). Use these for borders, gaps, and insets anywhere in the plugin.
- `UiStyle.Colors` — theme-aware generic colors (`bg`, `fg`, `weak`, `editorBackground`, `errorLabelForeground`, `warningLabelForeground`).
- `UiStyle.Components` — small reusable Swing helpers (e.g. `transparent()`).
| Need | Preferred source |
|---|---|
| Manual Swing empty padding | `JBUI.Borders.empty(...)` |
| Manual Swing insets | `JBUI.insets(...)`, `JBUI.emptyInsets()`, `JBUI.insetsTop(...)` |
| Manual Swing dimensions | `JBUI.size(...)`, `JBDimension`, `JBUI.scale(...)` |
| Side separators | `JBUI.Borders.customLineTop(...)`, `customLineBottom(...)` |
| Composed borders | `JBUI.Borders.compound(...)`, `JBUI.Borders.merge(...)` |
| Simple `BorderLayout` panels | `JBUI.Panels.simplePanel(...)`, `BorderLayoutPanel` |
| Simple vertical custom Swing groups | `VerticalLayout` |
| Fluent platform panels | `JBPanel.withBorder(...)`, `.andTransparent()`, `.andOpaque()`, `.withBackground(...)` |
**`SessionUiStyle`** (`frontend/src/main/kotlin/ai/kilocode/client/session/ui/style/SessionUiStyle.kt`) — static tokens that are specific to the chat/session UI:
- `SessionUiStyle.SessionLayout` — transcript list geometry and scroll increments.
- `SessionUiStyle.View` — card sizing, card borders, surfaces, hover colors, and nested objects for `Prompt`, `Reasoning`, `Message`, and `Tool`.
- `SessionUiStyle.RecentSessions` — recent sessions list limits.
- `SessionUiStyle.Timeline` — activity-indicator colors for the session header timeline.
- `Dock` — border presets for question, permission, and connection dock panels.
### Generic Platform Utilities
**Rules for adding new constants:**
- Generic layout constants (gaps, generic colors, reusable helpers) → `UiStyle`.
- Session-specific constants (transcript layout, prompt chrome, card geometry, session colors) → `SessionUiStyle`.
- Do not create extra fields whose only purpose is to hold a constant value. Reference the constant object directly, e.g. `UiStyle.Gap.lg()` or `SessionUiStyle.View.Prompt.EDITOR_LINES`.
- Do not pass style constants through constructors or method parameters. Using the constant directly at the call site is correct and preferred. Only introduce parameters for values that are genuinely variable or test-controlled.
Use existing platform utility classes instead of hand-rolling labels, renderers, borders, colors, fonts, and validation behavior.
| Need | Preferred API |
|---|---|
| Concise border layout | `BorderLayoutPanel`, `JBUI.Panels.simplePanel(...)` |
| Platform panel helpers | `JBPanel.withBorder(...)`, `.andTransparent()`, `.andOpaque()` |
| Platform label behavior | `JBLabel` |
| Context help | `ContextHelpLabel` |
| Links | `HyperlinkLabel`, `LinkLabel` |
| High-performance rich fragments | `SimpleColoredComponent` |
| List renderers | `ColoredListCellRenderer` |
| Tree renderers | `ColoredTreeCellRenderer` |
| Renderer text styles | `SimpleTextAttributes` |
| Editable list toolbar | `ToolbarDecorator` |
| Platform list | `JBList` |
| Platform tree | `Tree` |
### Tool Windows
- Register declaratively in module XML via `com.intellij.toolWindow` extension point (already done in `kilo.jetbrains.frontend.xml`).
- Implement `ToolWindowFactory.createToolWindowContent()` — called lazily on first click.
- Use `SimpleToolWindowPanel(vertical = true)` as a convenient base — supports toolbar + content layout.
- Add tabs via `ToolWindow.contentManager`: create content with `ContentFactory.getInstance().createContent(component, title, isLockable)`, then `contentManager.addContent()`.
- For conditional display, implement `ToolWindowFactory.isApplicableAsync(project)`.
- Always use `ToolWindowManager.invokeLater()` instead of `Application.invokeLater()` for tool-window-related EDT tasks.
### Dialogs
- Extend `DialogWrapper`. Call `init()` from the constructor. Override `createCenterPanel()` to return UI content.
- Override `getPreferredFocusedComponent()` for initial focus, `getDimensionServiceKey()` for size persistence.
- Show with `showAndGet()` (modal, returns boolean) or `show()` (then use `getExitCode()`).
- Input validation: call `initValidation()` in constructor, override `doValidate()` — return `null` if valid or `ValidationInfo(message, component)` if not.
- For hand-built Swing forms, use `ComponentValidator` with `withValidator`, `withFocusValidator`, and `andRegisterOnDocumentListener` instead of custom tooltip/error border logic.
### Icons
@@ -760,6 +534,19 @@ Style tokens and layout constants are split across three files. Put new constant
- **Dark variants**: `icon.svg` + `icon_dark.svg`. HiDPI: `icon@2x.svg` + `icon@2x_dark.svg`.
- **New UI support**: place New UI icons in `expui/` directory, create `*IconMappings.json`, register via `com.intellij.iconMapper` extension point. New UI icon colors: light `#6C707E`, dark `#CED0D6`.
### SVG Icon Assets
IntelliJ does not theme SVG icons with `currentColor`, CSS classes, CSS variables, `<style>` blocks, or inherited styles. `SVGLoader` patches icon colors by matching literal hex values in `fill` and `stroke` attributes against the active theme palette. Use hardcoded palette hex values in SVG assets and provide dark variants. This exception applies to icon asset files only; runtime Swing UI code must still derive colors from theme APIs.
| Do | Do not |
|---|---|
| `fill="#6E6E6E"` | `fill="currentColor"` |
| `stroke="#3574F0"` | CSS variables or classes |
| `icon.svg` plus `icon_dark.svg` | `<style>` blocks for theming |
| `fill-opacity="0.5"` | Inherited styling |
Themes can override palette colors through `icons.ColorPalette` in the theme JSON.
### Notifications
- Declare in module XML: `<notificationGroup id="Kilo Code" displayType="BALLOON"/>`.
@@ -782,3 +569,24 @@ Style tokens and layout constants are split across three files. Put new constant
- Custom renderers: `ColoredListCellRenderer` / `ColoredTreeCellRenderer` — `append()` for styled text, `setIcon()` for icons.
- Speed search: `ListSpeedSearch(list)` / `TreeSpeedSearch(tree)`.
- Editable list with add/remove/reorder toolbar: `ToolbarDecorator.createDecorator(list).setAddAction { }.setRemoveAction { }.createPanel()`.
- Use `ListUtil.installAutoSelectOnMouseMove(list)` for popup-like hover-selection behavior.
- Use `ScrollingUtil.installActions(list)` for keyboard navigation.
- Use `CollectionListModel<T>` for simple item storage; `FilteringListModel<T>` for filtering with speed search.
### Before Returning UI Code
Review generated UI code and remove:
- Explicit default property assignments such as unnecessary `isOpaque = false`
- Unnecessary `preferredSize`, `minimumSize`, or `maximumSize`
- Raw `Dimension`, `Insets`, `EmptyBorder`, or `Color`
- Inline runtime colors: `Color(...)`, numeric `JBColor(...)`, `Gray.xNN`, `JBColor.GRAY`, or hex color literals
- Raw CSS color literals (use `ColorUtil.toHtmlColor(themeColor)` when the source is theme-derived)
- Hardcoded font families, raw font sizes, or numeric-size `deriveFont(...)` calls
- Raw Swing components where IntelliJ replacements exist
- Hardcoded spacing that should be a `JBUI` value or `UiStyle.Gap` constant
- Cached `JBValue.UIInteger(...).get()` values in custom layouts — call `.get()` during layout/sizing instead
- Theme-derived borders, insets, colors, or arcs assigned once in constructors without `updateUI()` override
- SVG assets using `currentColor`, CSS variables, CSS classes, `<style>` blocks, or inherited styling
- Extra helpers that do not make the UI clearer or more reusable
- Any Kotlin UI DSL (`com.intellij.ui.dsl.builder`) introduced by accident