From 4203ced5f4167916fe60fc8cf3f0e3eb6a8cb3c1 Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 11:58:07 +0000 Subject: [PATCH 1/2] docs: trim AGENTS.md duplication into skills - Strip ~530 lines of UI guide duplication from packages/kilo-jetbrains/AGENTS.md; the jetbrains-ui-style skill is more comprehensive and is now the source of truth. - Move GitHub Issues guidance into a new .kilo/skills/gh-issues/SKILL.md so it loads only when needed. - Collapse duplicated kilocode_change marker examples in root AGENTS.md to a brief summary plus pointer to the kilocode-merge-minimizer skill. --- .kilo/skills/gh-issues/SKILL.md | 68 ++++ AGENTS.md | 58 +--- packages/kilo-jetbrains/AGENTS.md | 541 +----------------------------- 3 files changed, 83 insertions(+), 584 deletions(-) create mode 100644 .kilo/skills/gh-issues/SKILL.md diff --git a/.kilo/skills/gh-issues/SKILL.md b/.kilo/skills/gh-issues/SKILL.md new file mode 100644 index 00000000000..9a5bfefdf30 --- /dev/null +++ b/.kilo/skills/gh-issues/SKILL.md @@ -0,0 +1,68 @@ +--- +name: gh-issues +description: Use when creating, triaging, or commenting on GitHub issues for the Kilo VS Code extension or JetBrains plugin via `gh`. Covers issue templates, project board assignment, title conventions, and required `gh` scopes. +--- + +# GitHub Issues + +Use this skill whenever you create or manage a GitHub issue with `gh` for either the VS Code extension or the JetBrains plugin. + +## Templates + +The repo defines issue templates in `.github/ISSUE_TEMPLATE/`. Pick the matching template instead of opening a blank issue: + +| Template | When to use | +|---|---| +| `Bug report` (`bug-report.yml`) | Reproducible defects with steps, expected, and actual behavior | +| `Feature Request` (`feature-request.yml`) | New capabilities, enhancements, or behavior changes | +| `Question` (`question.yml`) | Usage or design questions that aren't obviously bugs or feature requests | + +Pass the template title to `gh issue create --template`. + +## Title Conventions + +- Use a plain, descriptive title that reads cleanly as a standalone sentence. +- Do not add platform-specific prefixes such as `[JetBrains]`, `[Jetbrains]`, `[JB]`, `[VS Code]`, `[VSCode]`, or similar. Routing happens through project boards, not the title. + +## Project Boards + +Every new issue must land on the correct project board: + +| Surface | Project | URL | +|---|---|---| +| VS Code extension | `VS Code Extension` | https://github.com/orgs/Kilo-Org/projects/25 | +| JetBrains plugin | `Jetbrains Plugin` | https://github.com/orgs/Kilo-Org/projects/39 | + +Pass the project title to `gh issue create --project`. + +## Recipes + +Create a VS Code extension bug report and add it to the board: + +```bash +gh issue create \ + --template "Bug report" \ + --project "VS Code Extension" \ + --title "Sidebar chat fails to render after reload" \ + --body "..." +``` + +Create a JetBrains feature request: + +```bash +gh issue create \ + --template "Feature Request" \ + --project "Jetbrains Plugin" \ + --title "Support Kotlin Multiplatform target detection" \ + --body "..." +``` + +## Scope Errors + +If `gh` reports a missing scope when assigning a project, refresh the auth token and retry: + +```bash +gh auth refresh -s project +``` + +After the refresh succeeds, re-run the original `gh issue create` command. Do not fall back to creating the issue without the project — the board assignment is required. diff --git a/AGENTS.md b/AGENTS.md index cc5eda0d054..34fec96cc6a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -223,12 +223,7 @@ PR descriptions should be 2-3 lines covering **what** changed and **why**. Focus ## GitHub Issues -- When creating a GitHub issue for the VS Code extension or JetBrains plugin, use the repo's existing issue templates in `.github/ISSUE_TEMPLATE/`. Pick the matching template (`Bug report`, `Feature Request`, or `Question`) instead of opening a blank issue. -- Do not add platform-specific title prefixes such as `[JetBrains]`, `[Jetbrains]`, `[JB]`, `[VS Code]`, `[VSCode]`, or similar. Use a plain, descriptive title. -- Always add VS Code extension issues to the GitHub project `VS Code Extension`: https://github.com/orgs/Kilo-Org/projects/25 -- Always add JetBrains plugin issues to the GitHub project `Jetbrains Plugin`: https://github.com/orgs/Kilo-Org/projects/39 -- When using `gh`, prefer `gh issue create --template "..." --project "..."` with the matching project title. -- If project assignment fails because `gh` is missing the required scope, run `gh auth refresh -s project` and retry. +When creating or managing GitHub issues for the VS Code extension or JetBrains plugin via `gh`, load `.kilo/skills/gh-issues/SKILL.md`. It covers templates, project boards (`VS Code Extension`, `Jetbrains Plugin`), title conventions, and the `gh auth refresh -s project` recovery path. ## Fork Merge Process @@ -262,50 +257,13 @@ The goal is to keep our diff from upstream as small as possible, making regular ### Kilocode Change Markers -To minimize merge conflicts when syncing with upstream, mark Kilo Code-specific changes in shared code with `kilocode_change` comments. +When editing shared upstream files, mark Kilo-specific lines with `kilocode_change` comments so future merges can find them. The basic forms are: -**Single line:** +- Single line: `const value = 42 // kilocode_change` +- Multi-line block: wrap with `// kilocode_change start` / `// kilocode_change end` +- New file in a shared path: `// kilocode_change - new file` at the top +- JSX/TSX: use `{/* kilocode_change */}` (and `{/* kilocode_change start */}` / `end`) -```typescript -const value = 42 // kilocode_change -``` - -**Multi-line:** - -```typescript -// kilocode_change start -const foo = 1 -const bar = 2 -// kilocode_change end -``` - -**New files:** - -```typescript -// kilocode_change - new file -``` - - -**JSX/TSX (inside JSX templates):** - - -```tsx -{/* kilocode_change */} -``` - - -```tsx -{/* kilocode_change start */} - -{/* kilocode_change end */} -``` - -#### When markers are NOT needed - -Code in these paths is Kilo Code-specific and does NOT need `kilocode_change` markers: - -- `packages/opencode/src/kilocode/` - All files in this directory -- `packages/opencode/test/kilocode/` - All test files for kilocode -- Any other path containing `kilocode` in filename or directory name +Markers are NOT needed in paths that contain `kilocode` in the name (e.g. `packages/opencode/src/kilocode/`, `packages/opencode/test/kilocode/`) — these are entirely Kilo Code additions and won't conflict with upstream. -These paths are entirely Kilo Code additions and won't conflict with upstream. +For decision rules on when to keep changes inline vs. extract Kilo logic, marker placement guidance, and verification commands, load `.kilo/skills/kilocode-merge-minimizer/SKILL.md`. diff --git a/packages/kilo-jetbrains/AGENTS.md b/packages/kilo-jetbrains/AGENTS.md index e9d9d3f71f1..22acf8a0053 100644 --- a/packages/kilo-jetbrains/AGENTS.md +++ b/packages/kilo-jetbrains/AGENTS.md @@ -209,539 +209,12 @@ Sections are separated by `---`. Only non-empty sections appear. The status line ## UI Design Guidelines -Official references: +For Kotlin/Swing UI work — components, layouts, dialogs, tool windows, theming, icons, popups, lists, trees, validation, session styling — load `packages/kilo-jetbrains/.kilo/skills/jetbrains-ui-style/SKILL.md`. It is the single source of truth for plugin UI conventions and is more comprehensive than the previous inline guide that lived here. -- [IntelliJ Platform UI Guidelines](https://jetbrains.design/intellij/) -- [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) +The non-negotiables (kept inline so they apply even without loading the skill): -### 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 (`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. - -### When to Use What - -| 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) | -| 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 links and  icons") - } -} -``` - -Comments support HTML with clickable links (pass a lambda for the click handler), bundled icons via ``, 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 - row { cb = checkBox("Enable options") } - indent { - row { checkBox("Option 1") } - row { checkBox("Option 2") } - }.enabledIf(cb.selected) - } - - group("Visible") { - lateinit var cb: Cell - 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. - -### Platform Components — Always Use Instead of Raw Swing - -| Instead of | Use | Package | -|---|---|---| -| `JLabel` | `JBLabel` | `com.intellij.ui.components` | -| `JTextField` | `JBTextField` | `com.intellij.ui.components` | -| `JTextArea` | `JBTextArea` | `com.intellij.ui.components` | -| `JList` | `JBList` | `com.intellij.ui.components` | -| `JScrollPane` | `JBScrollPane` | `com.intellij.ui.components` | -| `JTable` | `JBTable` | `com.intellij.ui.table` | -| `JTree` | `Tree` | `com.intellij.ui.treeStructure` | -| `JSplitPane` | `JBSplitter` | `com.intellij.ui` | -| `JTabbedPane` | `JBTabs` | `com.intellij.ui.tabs` | -| `JCheckBox` | `JBCheckBox` | `com.intellij.ui.components` | -| `Color` | `JBColor` | `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. - -### Multi-line and Rich Text - -| Need | Component | -|---|---| -| 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` | -| 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. -- All user-visible strings go in `*.properties` files; HTML markup in values is acceptable. - -### Colors and Theming - -- **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. - -### Borders, Insets, and Spacing - -- Always create via `JBUI.Borders.empty(top, left, bottom, right)` and `JBUI.insets()` — DPI-aware and auto-update on zoom. -- Use `JBUI.scale(int)` for any pixel dimension to ensure proper HiDPI scaling. - -### Icons - -- **Reuse platform icons**: browse at https://intellij-icons.jetbrains.design. Access via `AllIcons.*` constants. -- Custom icons: SVG files in `resources/icons/`. Load via `IconLoader.getIcon("/icons/foo.svg", MyClass::class.java)`. -- Organize in an `icons` package or a `*Icons` object with `@JvmField` on each constant. -- **Sizing**: actions/nodes = 16×16, tool window = 13×13 (classic) or 20×20 + 16×16 compact (New UI), editor gutter = 12×12 (classic) / 14×14 (New UI). -- **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`. - -### Notifications - -- Declare in module XML: ``. -- Show: `Notification("Kilo Code", "message", NotificationType.INFORMATION).notify(project)`. -- Add actions: `.addAction(NotificationAction.createSimpleExpiring("Label") { ... })`. -- Sticky (user must dismiss): `displayType="STICKY_BALLOON"` + `.setSuggestionType(true)`. -- Tool-window-bound: `displayType="TOOL_WINDOW" toolWindowId="Kilo Code"`. -- Prefer non-modal notifications over `Messages.show*()` dialogs. - -### Popups - -- Use `JBPopupFactory.getInstance()` for lightweight floating UI (no chrome, auto-dismiss on focus loss). -- `createComponentPopupBuilder(component, focusable)` for arbitrary Swing content; `createPopupChooserBuilder(list)` for item selection; `createActionGroupPopup()` for action menus. -- Show with `showInBestPositionFor(editor)`, `showUnderneathOf(component)`, or `showInCenterOf(component)`. - -### Lists and Trees - -- `JBList` not `JList` — adds empty text, busy indicator, tooltip truncation. -- `Tree` not `JTree` — adds wide selection painting, auto-scroll on DnD. -- 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()`. +- Do not use Kotlin Compose or `intellij.platform.compose`. +- Do not use JCEF (`JBCefBrowser`) — it does not work in split-mode remote dev. +- Prefer Kotlin UI DSL v2 (`com.intellij.ui.dsl.builder`) for dialogs, settings, forms, and structured layouts. Use manual Swing only for surfaces the DSL cannot express. +- Use IntelliJ platform components (`JBLabel`, `JBTextField`, `JBList`, `Tree`, etc.) instead of raw Swing. +- Never hardcode colors, fonts, or pixel sizes — derive from `JBUI.CurrentTheme`, `UIUtil`, `JBFont`, `JBColor.namedColor`, and `JBUI.scale`. From fed8cd3e96830bf6e92d84262b54dff55b51ef65 Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 12:16:13 +0000 Subject: [PATCH 2/2] revert: keep packages/kilo-jetbrains/AGENTS.md as-is Per feedback, drop the jetbrains UI section trim from this PR. The root AGENTS.md cleanup and the new gh-issues skill stay. --- packages/kilo-jetbrains/AGENTS.md | 541 +++++++++++++++++++++++++++++- 1 file changed, 534 insertions(+), 7 deletions(-) diff --git a/packages/kilo-jetbrains/AGENTS.md b/packages/kilo-jetbrains/AGENTS.md index 22acf8a0053..e9d9d3f71f1 100644 --- a/packages/kilo-jetbrains/AGENTS.md +++ b/packages/kilo-jetbrains/AGENTS.md @@ -209,12 +209,539 @@ Sections are separated by `---`. Only non-empty sections appear. The status line ## UI Design Guidelines -For Kotlin/Swing UI work — components, layouts, dialogs, tool windows, theming, icons, popups, lists, trees, validation, session styling — load `packages/kilo-jetbrains/.kilo/skills/jetbrains-ui-style/SKILL.md`. It is the single source of truth for plugin UI conventions and is more comprehensive than the previous inline guide that lived here. +Official references: -The non-negotiables (kept inline so they apply even without loading the skill): +- [IntelliJ Platform UI Guidelines](https://jetbrains.design/intellij/) +- [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 Compose or `intellij.platform.compose`. -- Do not use JCEF (`JBCefBrowser`) — it does not work in split-mode remote dev. -- Prefer Kotlin UI DSL v2 (`com.intellij.ui.dsl.builder`) for dialogs, settings, forms, and structured layouts. Use manual Swing only for surfaces the DSL cannot express. -- Use IntelliJ platform components (`JBLabel`, `JBTextField`, `JBList`, `Tree`, etc.) instead of raw Swing. -- Never hardcode colors, fonts, or pixel sizes — derive from `JBUI.CurrentTheme`, `UIUtil`, `JBFont`, `JBColor.namedColor`, and `JBUI.scale`. +### 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 (`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. + +### When to Use What + +| 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) | +| 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 links and  icons") + } +} +``` + +Comments support HTML with clickable links (pass a lambda for the click handler), bundled icons via ``, 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 + row { cb = checkBox("Enable options") } + indent { + row { checkBox("Option 1") } + row { checkBox("Option 2") } + }.enabledIf(cb.selected) + } + + group("Visible") { + lateinit var cb: Cell + 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. + +### Platform Components — Always Use Instead of Raw Swing + +| Instead of | Use | Package | +|---|---|---| +| `JLabel` | `JBLabel` | `com.intellij.ui.components` | +| `JTextField` | `JBTextField` | `com.intellij.ui.components` | +| `JTextArea` | `JBTextArea` | `com.intellij.ui.components` | +| `JList` | `JBList` | `com.intellij.ui.components` | +| `JScrollPane` | `JBScrollPane` | `com.intellij.ui.components` | +| `JTable` | `JBTable` | `com.intellij.ui.table` | +| `JTree` | `Tree` | `com.intellij.ui.treeStructure` | +| `JSplitPane` | `JBSplitter` | `com.intellij.ui` | +| `JTabbedPane` | `JBTabs` | `com.intellij.ui.tabs` | +| `JCheckBox` | `JBCheckBox` | `com.intellij.ui.components` | +| `Color` | `JBColor` | `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. + +### Multi-line and Rich Text + +| Need | Component | +|---|---| +| 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` | +| 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. +- All user-visible strings go in `*.properties` files; HTML markup in values is acceptable. + +### Colors and Theming + +- **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. + +### Borders, Insets, and Spacing + +- Always create via `JBUI.Borders.empty(top, left, bottom, right)` and `JBUI.insets()` — DPI-aware and auto-update on zoom. +- Use `JBUI.scale(int)` for any pixel dimension to ensure proper HiDPI scaling. + +### Icons + +- **Reuse platform icons**: browse at https://intellij-icons.jetbrains.design. Access via `AllIcons.*` constants. +- Custom icons: SVG files in `resources/icons/`. Load via `IconLoader.getIcon("/icons/foo.svg", MyClass::class.java)`. +- Organize in an `icons` package or a `*Icons` object with `@JvmField` on each constant. +- **Sizing**: actions/nodes = 16×16, tool window = 13×13 (classic) or 20×20 + 16×16 compact (New UI), editor gutter = 12×12 (classic) / 14×14 (New UI). +- **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`. + +### Notifications + +- Declare in module XML: ``. +- Show: `Notification("Kilo Code", "message", NotificationType.INFORMATION).notify(project)`. +- Add actions: `.addAction(NotificationAction.createSimpleExpiring("Label") { ... })`. +- Sticky (user must dismiss): `displayType="STICKY_BALLOON"` + `.setSuggestionType(true)`. +- Tool-window-bound: `displayType="TOOL_WINDOW" toolWindowId="Kilo Code"`. +- Prefer non-modal notifications over `Messages.show*()` dialogs. + +### Popups + +- Use `JBPopupFactory.getInstance()` for lightweight floating UI (no chrome, auto-dismiss on focus loss). +- `createComponentPopupBuilder(component, focusable)` for arbitrary Swing content; `createPopupChooserBuilder(list)` for item selection; `createActionGroupPopup()` for action menus. +- Show with `showInBestPositionFor(editor)`, `showUnderneathOf(component)`, or `showInCenterOf(component)`. + +### Lists and Trees + +- `JBList` not `JList` — adds empty text, busy indicator, tooltip truncation. +- `Tree` not `JTree` — adds wide selection painting, auto-scroll on DnD. +- 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()`.