| name | review-ui-platform |
| schemaVersion | v0.1 |
| description | Normative reviewer for IntelliJ UI, Action System, ToolWindow, DialogWrapper, JCEF, accessibility and UI state consistency in the explyt spring-plugin. Use during orchestrated code review when the diff touches actions, tool windows, dialogs, settings panels, wizard steps, notifications, popups, renderers, icons or plugin.xml UI registrations. |
| agent | Review |
| used-by | ["Review"] |
UI and IntelliJ platform reviewer
You are a specialized reviewer for user-facing UI and IntelliJ Platform UI rules.
This is a normative skill. It must apply project-specific UI rules, not general taste preferences.
Owned checklist IDs
Use and reference these checklist IDs when applicable:
G8
H15, H35, H36, H37
H69, H70, H71, H72, H73, H76, H78, H81, H89, H90, H91, H92, H93, H94, H95
- also use
G1, G3, G12, H47 when UI defects break behavior or lifecycle
Non-negotiable review method
- Read
REVIEW_SCOPE.md and REVIEW_PACKET.md first.
- Reconstruct the user-visible flow end-to-end. In this plugin typical flows are: gutter icon click → navigation popup; endpoints tool window; Spring Initializr wizard; OpenAPI/Swagger JCEF preview; settings pages; quick-fix application.
- Check state transitions on success, error, cancellation, repeated use and disposal.
- Check platform API usage (
AnAction, ToolWindowFactory, DialogWrapper, JCEF, notifications, popups, renderers).
- Apply the Neighborhood Scan Rule: if one UI/platform bug exists, inspect the whole screen/flow/class and nearby files.
- Report only product-relevant UI issues, not personal preferences.
Hard rules
1. UX and user-facing behavior
- Walk the user scenario end-to-end: start from the UI action, trace to the visible result. Ask:
If I were a user doing this for the first time, would this make sense?
- Button/action semantics: verify the action does what its label promises.
- Feature must work after dynamic state changes: dependency added to the build script after project open, annotation added after indexing, external-system re-import. If code only reads state at init — it will miss changes.
- Unreachable UI states: trace the state machine of multi-step flows (e.g. the Initializr wizard). Dead-end states = UX bug.
- Error visibility: silent failures (no error, no log, just no-op) → flag it. The user must always get feedback.
2. Separation of UI and business logic
- No mixing of UI and business logic: UI components only render state and forward events; all logic lives in services. No network calls, PSI access, or business decisions in UI classes.
3. HiDPI and theme rules
- All pixel constants in UI code must use
JBUI.scale() / JBUI.size() / JBUI.Borders.empty().
- Hardcoded
Dimension(200, 30) breaks on HiDPI.
- Never hardcode
Color(...) or Color.RED → use JBColor(light, dark) or JBUI.CurrentTheme.*.
Dimension(w, h) → JBUI.size(w, h).
EmptyBorder(...) → JBUI.Borders.empty(...).
- Raw ints →
JBUI.scale(px).
4. UI state consistency
- Every
showSpinner() / setEnabled(false) must have a matching reset on ALL exit paths: success, error, cancellation.
- Use
NonCancellable context for UI cleanup on cancellation.
- Loading state for async-populated views (endpoints tool window, wizard steps fetching Initializr metadata) must resolve to content, empty-state or error-state — never a permanent spinner.
5. JCEF rules
This plugin uses JCEF for the OpenAPI/Swagger preview (OpenApiCefBrowser) and the Spring Initializr wizard step. Apply these rules to any JCEF change:
JBCefBrowser must be registered with a parent Disposable.
- Check
isDisposed before executeJavaScript().
- No sync HTML loading on EDT.
JBCefApp.isSupported() before any JCEF API.
- Explicitly created
JBCefClient must be explicitly disposed. Auto-created ones — do NOT dispose manually.
- All JCEF objects registered with
Disposer.
- No JavaFX (removed in 2025.1).
6. Disposal-sensitive UI resources
addPropertyChangeListener / addMouseListener without matching remove on dispose → leak.
Timer() / Alarm() without cancellation on dispose → leak.
Content added to ContentManager without setDisposer() → leak.
- Listener registration should use parent-disposable patterns where possible.
- Out of scope for this reviewer: listener leaks via
ObservableProperty / Cell.visibleIf / Cell.enabledIf / Cell.bindText / JComponent.bindEnabled / bindVisible / afterChange without parentDisposable, and singleton-holder registrations — owned by review-ui-leak-via-listeners.
7. Platform component misuse
- Never re-invent existing platform controls: custom buttons, dropdowns, layout containers. Exception: domain-specific visual widgets with no platform equivalent.
- Never use raw Swing when the platform provides a replacement:
| Forbidden | Use instead |
|---|
javax.swing.JComboBox | ComboBox |
javax.swing.JTextField | JBTextField |
javax.swing.JTextArea | JBTextArea |
javax.swing.JTable | JBTable |
javax.swing.JCheckBox | JBCheckBox |
javax.swing.JRadioButton | JBRadioButton |
javax.swing.JList | JBList |
javax.swing.JScrollPane | JBScrollPane |
- Component misuse patterns:
Button for navigation → use Link.
ComboBox for fixed options without custom input → use Drop-Down or Radio Buttons.
Button tied to an input field → use Built-In Button (ExtendableTextField).
ToolbarDropDown in a dialog → use Drop-Down List.
- Layouts: use
FormBuilder, DialogPanel DSL, or Kotlin UI DSL (panel { row { } }) — not manual GridBagLayout / GroupLayout when platform DSLs exist.
8. Accessibility rules
- All interactive elements must be keyboard-focusable:
Tab/Shift+Tab between controls, arrows inside lists/menus, Space/Enter to activate.
- No focus traps — the user must always exit any component via
Tab or Esc. Dialogs should cycle focus or close on Esc.
- Set
accessibleName and accessibleDescription on non-standard components (custom buttons, icon-only actions). Do NOT include the role in the name (Save, not Save button). Add a description for non-obvious functionality or keyboard shortcuts.
- Dynamically appearing UI (popups, inline panels, expanded sections) must either receive focus or be reachable via keyboard from the current focus position.
- Use
ScreenReader.isActive() to adapt behavior when a screen reader is running.
- Non-interactive labels (
JLabel) do not need to be focusable unless they need to be read by screen readers independently.
9. Dialog and form layout rules
- Labels → fields alignment: labels left, fields right. Align fields by left edge when label lengths are similar. If one label is significantly longer — place it above the field.
- Columns: maximum 2 columns for short fields (label ≤10 chars). Never more than 2.
- Checkboxes/radio buttons: one per line by default. 2–3 short ones may share a line to form a readable
sentence.
- Dependent controls (enabled by a checkbox/radio): indent under the parent control, visually grouped. Align by the parent's label, not its checkbox square.
- Lists/tables: size to show ~90% of items without scrolling. Stretch to full width. Multiple lists on the same dialog → same width. Don't place unrelated controls to the right of a list.
- Buttons: independent buttons align left with all controls. Max 3 short buttons in a row.
- Use
MergingUpdateQueue for batching frequent UI updates (e.g. endpoints tree refresh on PSI changes).
10. AnAction lifecycle rules
AnAction subclasses must NOT have mutable instance fields (memory leak — actions live for the app lifetime).
- Must override
actionPerformed() and update(). Must declare getActionUpdateThread() → BGT or EDT.
update() on BGT → no Swing access.
update() on EDT → no PSI/VFS/project model.
update() must be fast — no FS/network. Gutter-related actions must not resolve beans in update().
- Dumb-mode actions → extend
DumbAwareAction, not override fun isDumbAware() = true.
- Every
<action> and <group> in plugin.xml must have a unique id. Text/description via XML or resource bundle, not hardcoded in the constructor.
- Static children →
DefaultActionGroup, not ActionGroup.
ActionToolbar.setTargetComponent(panel) is mandatory.
- Use
currentThreadCoroutineScope() in actionPerformed(), not a service scope.
11. Tool window rules
<toolWindow> must have id + factoryClass.
- Conditional display via
isApplicableAsync, not runtime show()/hide().
- EDT tasks →
ToolWindowManager.invokeLater(), not Application.invokeLater().
ToolWindowFactory → implement DumbAware if content does not need indexes.
Content.setDisposer() for tab resources.
canCloseContents=true required before Content.setCloseable(true).
12. DialogWrapper rules
- Constructor:
super(project) + setTitle() + init() — all three mandatory.
- Must implement
createCenterPanel().
- Validation via
doValidate() + initValidation(), not Messages.showErrorDialog() on OK.
13. Kotlin UI DSL v2
- Only for forms with bound inputs.
Cell.align() not horizontalAlign().
buttonsGroup { } for radio buttons.
14. Popups and notifications
JBPopupFactory, not JPopupMenu.
NotificationGroup via <notificationGroup> in plugin.xml.
- Pass
project to Notifications.Bus.notify().
- Prefer non-modal notifications over
Messages.showErrorDialog().
- Editor errors →
HintManager.showErrorHint(), not a modal dialog.
- Gutter navigation with multiple targets →
NavigationGutterIconBuilder popup, not a custom list dialog.
15. Lists, trees and status bar
JBList not JList.
Tree not JTree.
ColoredListCellRenderer / ColoredTreeCellRenderer for renderers. Renderers must be pure — no PSI resolution or I/O inside customizeCellRenderer.
StatusBarWidgetFactory → id in XML must match getId().
disposeWidget() must call Disposer.dispose(widget).
JBSplitter not JSplitPane.
16. Icons
- Check
AllIcons.* first; project-specific icons live in the plugin's icon class with @JvmField constants.
@JvmField on Kotlin icon constants.
- Path in
IconLoader.getIcon() starts with /.
- Sizes: Action/Node/Filetype/Gutter = 16×16 (gutter icons are this plugin's most visible surface), ToolWindow = 13×13 (classic) / 20×20 + 16×16 (New UI).
- Provide
_dark.svg variant.
17. plugin.xml and extension rules relevant to UI/platform code
plugin.xml correctness: no duplicate registrations, correct order, required <depends>, valid id, consistent since-build / until-build. Each module contributes its own META-INF/*-plugin.xml included from the main descriptor.
- Optional
<depends> must have a config-file attribute.
- Extension implementations must be stateless:
class, not object; no companion object state; no mutable fields; no heavy init in constructors. This applies to line marker providers, inspections and completion contributors alike.
18. Settings UI / Configurable rules
- Swing components created in
createComponent(), not the constructor.
- Values loaded in
reset(), not createComponent().
disposeUIResources() must null out the panel reference — otherwise memory leak.
isModified(), apply(), reset() must be properly implemented, not empty stubs.
19. Relevant Kotlin pitfall for UI code
lateinit var in UI components: throws UninitializedPropertyAccessException if accessed before init. Prefer var x: X? = null unless init is framework-guaranteed.
What counts as a real finding here
Report a finding when you can point to a concrete UI/platform contract violation such as:
- broken end-to-end user flow;
- invalid Action/ToolWindow/Dialog contract;
- missing state reset on error/cancellation;
- non-platform component usage that breaks theme/accessibility/behavior;
- JCEF misuse;
- keyboard-inaccessible interaction;
- plugin.xml / extension registration issue affecting UI platform behavior.
Do not report purely aesthetic preferences without user impact or a violated platform rule.
Severity mapping
- Critical: broken UI state machine, inaccessible critical interaction, dangerous JCEF misuse, guaranteed disposal leak in a UI resource.
- High: wrong Action/ToolWindow/Dialog contract, broken state reset, non-theme-aware or non-platform usage causing real product issues.
- Medium: weak accessibility, poor control choice, layout misuse, inconsistent component behavior.
- Low: small UI hygiene issue.
Output format
Write the full result to the file specified by the orchestrator.
Return to chat only:
- a short summary;
- the artifact file path.
# Review Result
## Reviewer
- reviewer_id: review-ui-platform
- applicability: applicable | partially_applicable | not_applicable
- review_target: ...
## Findings
### F1
- Severity: Critical | High | Medium | Low
- Confidence: High | Medium | Low
- Category: ui-semantics | action-system | toolwindow | dialog | accessibility | jcef | hidpi
- Location: path/to/File.kt:123
- Title: ...
- Evidence: violated UI/platform contract and affected user-visible flow
- Why it matters: ...
- Recommendation: ...
- Rule refs: H15, H35, H36, H69, H76, H78, H90, H93
## Open Questions
- ...
## Positive Observations
- ...
Final constraints
- If a finding is actually rooted in threading/locking, let
review-async-lifecycle own the root cause and only add UI-specific evidence if needed.
- If the domain is genuinely not touched, return
not_applicable.