This is an automated email from the ASF dual-hosted git repository.

lukaszlenart pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/struts-intellij-plugin.git


The following commit(s) were added to refs/heads/main by this push:
     new 14f162e  Show Diagram: use compact icon+label nodes (#120) (#123)
14f162e is described below

commit 14f162eb99380f1b29117bec57b1c510940b655d
Author: Lukasz Lenart <[email protected]>
AuthorDate: Sat Jul 25 22:30:31 2026 +0200

    Show Diagram: use compact icon+label nodes (#120) (#123)
    
    * docs: add design for Show Diagram compact label nodes (#120)
    
    Capture the approved approach: CommonDiagramExtras.createLabelNode
    chrome, Swing tab untouched, automated component-type smoke tests.
    
    Co-authored-by: Cursor <[email protected]>
    
    * docs: add implementation plan for Show Diagram compact nodes (#120)
    
    Co-authored-by: Cursor <[email protected]>
    
    * test(diagram): assert Show Diagram uses compact label nodes (#120)
    
    Co-authored-by: Cursor <[email protected]>
    
    * feat(diagram): use compact icon+label nodes in Show Diagram (#120)
    
    Co-authored-by: Cursor <[email protected]>
    
    * docs: note compact Show Diagram nodes in changelog (#120)
    
    Co-authored-by: Cursor <[email protected]>
    
    * fix(diagram): cover NodeRealizer node chrome path and keep zoom 
animations off (#120)
    
    Co-authored-by: Cursor <[email protected]>
    
    ---------
    
    Co-authored-by: Cursor <[email protected]>
---
 CHANGELOG.md                                       |   1 +
 .../plans/2026-07-25-show-diagram-compact-nodes.md | 387 +++++++++++++++++++++
 ...2026-07-25-show-diagram-compact-nodes-design.md | 147 ++++++++
 .../diagram/provider/StrutsDiagramExtras.java      |  35 +-
 .../struts2/diagram/StrutsDiagramProviderTest.java | 118 +++++++
 5 files changed, 685 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index e88f23d..221d3b3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@
 - Dependencies - upgrade Qodana linter image to 
`jetbrains/qodana-jvm:2026.2-eap`
 - Dependencies - declare IntelliJ Platform 2026.2 v2 content modules 
`intellij.javascript.parser`, `intellij.javascript.backend`, 
`intellij.javaee.jsp.shared`, `intellij.css`, and `intellij.css.backend` 
([#103](https://github.com/apache/struts-intellij-plugin/issues/103))
 - Add IntelliJ **Show Diagram** support for Struts 2 configs via 
`com.intellij.diagram`, alongside the existing Diagram editor tab for 
comparison ([#117](https://github.com/apache/struts-intellij-plugin/issues/117))
+- Show Diagram Struts config nodes use compact icon+label chrome instead of 
empty UML class boxes 
([#120](https://github.com/apache/struts-intellij-plugin/issues/120))
 - Dependencies - hard-depend on `com.intellij.diagram` (Ultimate Diagrams)
 
 ## [261.19027.1] - 2026-07-13
diff --git a/docs/superpowers/plans/2026-07-25-show-diagram-compact-nodes.md 
b/docs/superpowers/plans/2026-07-25-show-diagram-compact-nodes.md
new file mode 100644
index 0000000..0b8ef1c
--- /dev/null
+++ b/docs/superpowers/plans/2026-07-25-show-diagram-compact-nodes.md
@@ -0,0 +1,387 @@
+# Show Diagram Compact Label Nodes Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use 
superpowers:subagent-driven-development (recommended) or 
superpowers:executing-plans to implement this plan task-by-task. Steps use 
checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Make IntelliJ Show Diagram Struts nodes render as compact platform 
icon+label chrome instead of empty UML class boxes.
+
+**Architecture:** Keep the existing Show Diagram provider/data model. Change 
only `StrutsDiagramExtras` to extend `CommonDiagramExtras` and route both 
`createNodeComponent` overloads to `createLabelNode`. Leave the Swing Diagram 
tab untouched.
+
+**Tech Stack:** IntelliJ IDEA Ultimate 2026.2 (262), `com.intellij.diagram` 
(`CommonDiagramExtras`, `DiagramExtras#createNodeComponent`), JUnit 4 light 
tests (`BasicLightHighlightingTestCase`), JDK dynamic proxies for a minimal 
`DiagramBuilder` stub (no Mockito in this project).
+
+**Spec:** 
`docs/superpowers/specs/2026-07-25-show-diagram-compact-nodes-design.md`
+
+## Global Constraints
+
+- Target platform remains IntelliJ IDEA **2026.2** / build **262** only 
(`pluginSinceBuild=262`, `pluginUntilBuild=262.*`).
+- Hard dependency on plugin id **`com.intellij.diagram`** already present — do 
not change dependency shape.
+- Touch **Show Diagram node chrome only** — do **not** modify or remove the 
Swing Diagram tab (`diagram.fileEditor` / `diagram.ui`).
+- Do **not** change `StrutsConfigDiagramModel` semantics, edge mapping, 
tooltips, navigation, or Dom refresh.
+- Do **not** implement #96–#100 or Swing tab removal in this plan.
+- Visual style is platform compact label (`createLabelNode` / 
`SimpleColoredComponent`), not custom colored chips.
+- Tests gate: `./gradlew test -x rat --tests "com.intellij.struts2.diagram.*"`.
+- Learn API details from local IU SDK jars under Gradle caches 
(`plugins/uml/lib/uml-support.jar`) if signatures drift.
+
+---
+
+## File Structure
+
+| File | Action | Responsibility |
+|---|---|---|
+| 
`src/main/java/com/intellij/struts2/diagram/provider/StrutsDiagramExtras.java` 
| Modify | Extend `CommonDiagramExtras`; override both `createNodeComponent` 
overloads → `createLabelNode` |
+| `src/test/java/com/intellij/struts2/diagram/StrutsDiagramProviderTest.java` 
| Modify | Add smoke test: `createNodeComponent` returns label chrome, not 
`DiagramNodeContainer` |
+| `CHANGELOG.md` | Modify | Unreleased note for compact Show Diagram nodes 
(#120) |
+
+No new production classes. No `plugin.xml` / Gradle dependency changes.
+
+---
+
+### Task 1: Failing test for compact node chrome
+
+**Files:**
+- Modify: 
`src/test/java/com/intellij/struts2/diagram/StrutsDiagramProviderTest.java`
+- Test: 
`src/test/java/com/intellij/struts2/diagram/StrutsDiagramProviderTest.java`
+
+**Interfaces:**
+- Consumes:
+  - `StrutsDiagramProvider.getExtras()`
+  - `DiagramExtras.createNodeComponent(DiagramNode, DiagramBuilder, Point, 
JPanel)`
+  - `StrutsDiagramApiNode(DiagramProvider, StrutsDiagramItem)`
+  - `StrutsConfigDiagramModel.build(XmlFile)`
+- Produces: failing assertion that node component is `SimpleColoredComponent` 
and not `DiagramNodeContainer`
+
+- [ ] **Step 1: Add imports and stub helpers to `StrutsDiagramProviderTest`**
+
+Add these imports (keep existing ones):
+
+```java
+import com.intellij.diagram.DiagramBuilder;
+import com.intellij.diagram.DiagramDataModel;
+import com.intellij.diagram.components.DiagramNodeContainer;
+import com.intellij.diagram.extras.DiagramExtras;
+import com.intellij.diagram.extras.custom.CommonDiagramExtras;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.graph.view.Graph2D;
+import com.intellij.ui.SimpleColoredComponent;
+
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import java.awt.Point;
+import java.lang.reflect.Proxy;
+```
+
+Add these private helpers at the bottom of the test class (before or after 
`getProvider()`):
+
+```java
+    /**
+     * Minimal DiagramBuilder for createLabelNode / setNodeBorders.
+     * No Mockito in this project — use JDK proxies.
+     */
+    private DiagramBuilder stubBuilderForLabelNodes(@NotNull 
StrutsDiagramProvider provider,
+                                                    @NotNull 
DiagramDataModel<StrutsDiagramItem> dataModel) {
+        var scheme = EditorColorsManager.getInstance().getGlobalScheme();
+        Graph2D graph = (Graph2D) Proxy.newProxyInstance(
+                Graph2D.class.getClassLoader(),
+                new Class<?>[]{Graph2D.class},
+                (proxy, method, args) -> {
+                    if ("isSelected".equals(method.getName())) {
+                        return false;
+                    }
+                    return proxyDefaultValue(method.getReturnType());
+                });
+        return (DiagramBuilder) Proxy.newProxyInstance(
+                DiagramBuilder.class.getClassLoader(),
+                new Class<?>[]{DiagramBuilder.class},
+                (proxy, method, args) -> switch (method.getName()) {
+                    case "getColorScheme" -> scheme;
+                    case "getProvider" -> provider;
+                    case "getDataModel" -> dataModel;
+                    case "getGraph" -> graph;
+                    case "getNode" -> null;
+                    case "toString" -> "StubDiagramBuilder";
+                    case "hashCode" -> System.identityHashCode(proxy);
+                    case "equals" -> proxy == args[0];
+                    default -> proxyDefaultValue(method.getReturnType());
+                });
+    }
+
+    private static @Nullable Object proxyDefaultValue(@NotNull Class<?> 
returnType) {
+        if (!returnType.isPrimitive()) {
+            return null;
+        }
+        if (returnType == boolean.class) return false;
+        if (returnType == byte.class) return (byte) 0;
+        if (returnType == short.class) return (short) 0;
+        if (returnType == int.class) return 0;
+        if (returnType == long.class) return 0L;
+        if (returnType == float.class) return 0f;
+        if (returnType == double.class) return 0d;
+        if (returnType == char.class) return '\0';
+        return null;
+    }
+```
+
+- [ ] **Step 2: Write the failing test method**
+
+Add to `StrutsDiagramProviderTest`:
+
+```java
+    public void testExtrasCreateCompactLabelNodeComponents() {
+        createStrutsFileSet("struts-diagram.xml");
+        VirtualFile file = myFixture.findFileInTempDir("struts-diagram.xml");
+        assertNotNull(file);
+        XmlFile xml = (XmlFile) 
PsiManager.getInstance(getProject()).findFile(file);
+        assertNotNull(xml);
+
+        StrutsConfigDiagramModel model = ReadAction.compute(() -> 
StrutsConfigDiagramModel.build(xml));
+        assertNotNull(model);
+        StrutsDiagramNode snapshotNode = model.getNodes().stream()
+                .filter(n -> n.getKind() == StrutsDiagramNode.Kind.ACTION)
+                .findFirst()
+                .orElseThrow();
+        assertNotNull(snapshotNode.getIcon());
+        assertFalse(snapshotNode.getName().isEmpty());
+
+        StrutsDiagramProvider provider = getProvider();
+        DiagramExtras<StrutsDiagramItem> extras = provider.getExtras();
+        assertInstanceOf(extras, CommonDiagramExtras.class);
+
+        StrutsDiagramItem item = StrutsDiagramItem.forNode(xml, snapshotNode);
+        StrutsDiagramApiNode apiNode = new StrutsDiagramApiNode(provider, 
item);
+
+        StrutsDiagramDataModel dataModel =
+                new StrutsDiagramDataModel(getProject(), provider, 
StrutsDiagramItem.forFile(xml));
+        try {
+            DiagramBuilder builder = stubBuilderForLabelNodes(provider, 
dataModel);
+            JPanel wrapper = new JPanel();
+            JComponent component = extras.createNodeComponent(apiNode, 
builder, new Point(0, 0), wrapper);
+
+            assertNotNull(component);
+            assertFalse(
+                    "Show Diagram nodes must not use UML DiagramNodeContainer 
chrome",
+                    component instanceof DiagramNodeContainer);
+            assertInstanceOf(component, SimpleColoredComponent.class);
+
+            SimpleColoredComponent label = (SimpleColoredComponent) component;
+            assertNotNull("Package/action/result icon must remain visible", 
label.getIcon());
+            assertTrue(
+                    "Node name must remain visible on the label",
+                    
label.getCharSequence(true).toString().contains(snapshotNode.getName()));
+        } finally {
+            com.intellij.openapi.util.Disposer.dispose(dataModel);
+        }
+    }
+```
+
+Also add:
+
+```java
+import com.intellij.struts2.diagram.provider.StrutsDiagramDataModel;
+```
+
+If `getCharSequence(true)` is unavailable on 262, fall back to asserting 
`label.toString()` contains the name, or that 
`provider.getElementManager().getElementTitle(item)` equals 
`snapshotNode.getName()` **and** the component is still a 
`SimpleColoredComponent` with a non-null icon.
+
+- [ ] **Step 3: Run test to verify it fails**
+
+Run:
+
+```bash
+./gradlew test -x rat --tests 
"com.intellij.struts2.diagram.StrutsDiagramProviderTest.testExtrasCreateCompactLabelNodeComponents"
+```
+
+Expected: FAIL because current `createNodeComponent` returns 
`DiagramNodeContainer` (or assertion `assertInstanceOf(..., 
SimpleColoredComponent.class)` fails). Also `assertInstanceOf(extras, 
CommonDiagramExtras.class)` may fail first — that is an acceptable first 
failure.
+
+- [ ] **Step 4: Commit the failing test**
+
+```bash
+git add 
src/test/java/com/intellij/struts2/diagram/StrutsDiagramProviderTest.java
+git commit -m "$(cat <<'EOF'
+test(diagram): assert Show Diagram uses compact label nodes (#120)
+
+EOF
+)"
+```
+
+---
+
+### Task 2: Implement `CommonDiagramExtras` label chrome
+
+**Files:**
+- Modify: 
`src/main/java/com/intellij/struts2/diagram/provider/StrutsDiagramExtras.java`
+- Test: 
`src/test/java/com/intellij/struts2/diagram/StrutsDiagramProviderTest.java`
+
+**Interfaces:**
+- Consumes:
+  - 
`com.intellij.diagram.extras.custom.CommonDiagramExtras#createLabelNode(DiagramNode,
 DiagramBuilder, JPanel)`
+  - Existing `EditNodeHandler` / `uiDataSnapshot` behavior
+- Produces:
+  - `JComponent createNodeComponent(DiagramNode<StrutsDiagramItem>, 
DiagramBuilder, NodeRealizer, JPanel)` → label node
+  - `JComponent createNodeComponent(DiagramNode<StrutsDiagramItem>, 
DiagramBuilder, Point, JPanel)` → label node
+
+- [ ] **Step 1: Change superclass and imports**
+
+In `StrutsDiagramExtras.java`, replace:
+
+```java
+import com.intellij.diagram.extras.DiagramExtras;
+```
+
+with:
+
+```java
+import com.intellij.diagram.extras.custom.CommonDiagramExtras;
+import com.intellij.openapi.graph.view.NodeRealizer;
+
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import java.awt.Point;
+```
+
+Change the class declaration from:
+
+```java
+public final class StrutsDiagramExtras extends 
DiagramExtras<StrutsDiagramItem> {
+```
+
+to:
+
+```java
+public final class StrutsDiagramExtras extends 
CommonDiagramExtras<StrutsDiagramItem> {
+```
+
+Update the class Javadoc to mention compact label chrome via 
`createLabelNode`, while keeping the existing note about `EditNodeHandler`.
+
+- [ ] **Step 2: Override both `createNodeComponent` overloads**
+
+Add these methods to `StrutsDiagramExtras` (keep existing 
`getEditNodeHandler`, `uiDataSnapshot`, `navigateNode`, `resolvePsiElement` 
unchanged):
+
+```java
+    @Override
+    public @NotNull JComponent createNodeComponent(@NotNull 
DiagramNode<StrutsDiagramItem> node,
+                                                   @NotNull DiagramBuilder 
builder,
+                                                   @NotNull NodeRealizer 
nodeRealizer,
+                                                   @NotNull JPanel wrapper) {
+        return createLabelNode(node, builder, wrapper);
+    }
+
+    @Override
+    public @NotNull JComponent createNodeComponent(@NotNull 
DiagramNode<StrutsDiagramItem> node,
+                                                   @NotNull DiagramBuilder 
builder,
+                                                   @NotNull Point basePoint,
+                                                   @NotNull JPanel wrapper) {
+        return createLabelNode(node, builder, wrapper);
+    }
+```
+
+Do **not** customize `createLabel` / `setNodeBorders` unless the test or 
`runIde` shows a hard failure; accept platform label defaults.
+
+If the 262 SDK uses a different `NodeRealizer` package or 
`createNodeComponent` parameter names, match the exact signatures from:
+
+```bash
+javap -classpath "$UML_SUPPORT_JAR" -public 
com.intellij.diagram.extras.DiagramExtras
+```
+
+- [ ] **Step 3: Run the new test to verify it passes**
+
+Run:
+
+```bash
+./gradlew test -x rat --tests 
"com.intellij.struts2.diagram.StrutsDiagramProviderTest.testExtrasCreateCompactLabelNodeComponents"
+```
+
+Expected: BUILD SUCCESSFUL / test PASS.
+
+If `createLabelNode` NPEs on the proxy stub (e.g. deferred icon evaluator / 
content manager), fix the stub first — do not weaken the production override. 
Only if the platform API requires a real builder, narrow assertions to the 
strongest feasible check that still proves non-UML chrome while keeping the 
override.
+
+- [ ] **Step 4: Run full diagram suite**
+
+Run:
+
+```bash
+./gradlew test -x rat --tests "com.intellij.struts2.diagram.*"
+```
+
+Expected: BUILD SUCCESSFUL; all diagram tests green.
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add 
src/main/java/com/intellij/struts2/diagram/provider/StrutsDiagramExtras.java \
+        
src/test/java/com/intellij/struts2/diagram/StrutsDiagramProviderTest.java
+git commit -m "$(cat <<'EOF'
+feat(diagram): use compact icon+label nodes in Show Diagram (#120)
+
+EOF
+)"
+```
+
+---
+
+### Task 3: Changelog + manual verification notes
+
+**Files:**
+- Modify: `CHANGELOG.md`
+- Test: full diagram suite (regression)
+
+**Interfaces:**
+- Consumes: Task 2 behavior
+- Produces: user-facing Unreleased changelog entry for #120
+
+- [ ] **Step 1: Update `CHANGELOG.md`**
+
+Under `## [Unreleased]` → `### Changed`, add:
+
+```markdown
+- Show Diagram Struts config nodes use compact icon+label chrome instead of 
empty UML class boxes 
([#120](https://github.com/apache/struts-intellij-plugin/issues/120))
+```
+
+Keep the existing #117 Show Diagram bullet as-is (hosts still coexist).
+
+- [ ] **Step 2: Re-run diagram tests**
+
+Run:
+
+```bash
+./gradlew test -x rat --tests "com.intellij.struts2.diagram.*"
+```
+
+Expected: BUILD SUCCESSFUL.
+
+- [ ] **Step 3: Manual smoke checklist** (`./gradlew runIde` on IU)
+
+Verify:
+
+1. Show Diagram on a Struts config → nodes are compact icon+label (no empty 
UML body).
+2. Package / action / result icons and names remain visible.
+3. Double-click navigates to XML; hover shows tooltips.
+4. Swing Diagram tab still opens and behaves as before.
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add CHANGELOG.md
+git commit -m "$(cat <<'EOF'
+docs: note compact Show Diagram nodes in changelog (#120)
+
+EOF
+)"
+```
+
+---
+
+## Spec coverage check
+
+| Spec requirement | Task |
+|---|---|
+| Compact icon+label via `createLabelNode` | Task 2 |
+| Package/action/result icons and names visible | Task 1 assertions + Task 3 
manual |
+| Swing tab unchanged | Global constraint; no Swing files in File Structure |
+| Navigation/tooltips/Dom refresh unchanged | Task 2 keeps extras handlers; no 
data-model edits |
+| Automated component-type smoke | Task 1 |
+| Manual `runIde` | Task 3 |
+| No custom colored chips / no Swing removal / no #96–#100 | Global 
constraints |
+
+## Manual fallback notes
+
+- If `NodeRealizer` import path differs on the resolved IU SDK, use the type 
from `DiagramExtras.createNodeComponent` via `javap`.
+- If `SimpleColoredComponent.getCharSequence(boolean)` is missing, keep the 
`SimpleColoredComponent` + non-null icon assertions and title check via 
`getElementManager().getElementTitle(item)`.
+- Do not add Mockito for this plan; prefer JDK `Proxy` stubs as shown.
diff --git 
a/docs/superpowers/specs/2026-07-25-show-diagram-compact-nodes-design.md 
b/docs/superpowers/specs/2026-07-25-show-diagram-compact-nodes-design.md
new file mode 100644
index 0000000..3b9d328
--- /dev/null
+++ b/docs/superpowers/specs/2026-07-25-show-diagram-compact-nodes-design.md
@@ -0,0 +1,147 @@
+# Show Diagram: Compact Icon+Label Nodes
+
+**Date:** 2026-07-25  
+**Status:** Approved for implementation planning  
+**Related:** 
[#120](https://github.com/apache/struts-intellij-plugin/issues/120); follow-up 
to [#117](https://github.com/apache/struts-intellij-plugin/issues/117) / 
[#119](https://github.com/apache/struts-intellij-plugin/pull/119); prior 
migration design `2026-07-25-diagrams-api-migration-design.md`
+
+## Problem
+
+After migrating to the IntelliJ **Show Diagram** host, Struts 
package/action/result nodes render with the default UML-style chrome 
(`DiagramNodeContainer`: header + empty body). Nodes look oversized and harder 
to read than the custom Swing Diagram tab, which paints compact icon + name 
chips.
+
+Root cause: `StrutsDiagramExtras` extends plain `DiagramExtras` and does not 
override `createNodeComponent`. The default implementation builds 
`DiagramNodeContainer`. `StrutsDiagramElementManager.getNodeItems` returns 
`EMPTY_ARRAY`, so the UML body is empty.
+
+Icons and titles already exist on the API path 
(`StrutsDiagramApiNode.getIcon()`, `getElementTitle`); only node chrome is 
wrong.
+
+## Goals
+
+1. Show Diagram nodes use compact platform icon+label chrome (no empty UML 
body).
+2. Package / action / result icons and names remain visible.
+3. Swing Diagram tab behavior stays unchanged while both hosts coexist.
+4. Navigation, tooltips, and Dom refresh stay unchanged.
+5. Automated smoke asserts label-style components (not 
`DiagramNodeContainer`), plus a short manual `runIde` check.
+
+## Non-Goals
+
+- Custom Swing-matching colored rounded chips for Show Diagram.
+- Removing the Swing Diagram tab / `PerspectiveFileEditor` path (separate from 
#120).
+- Merged multi-file view (#96), selection sync (#98), Structure tool window 
(#99), zoom/pan beyond platform chrome (#100).
+- Pixel/layout assertions or Robot UI e2e.
+- Changing snapshot model semantics or edge rendering.
+
+## Decisions
+
+| Question | Decision |
+|---|---|
+| Visual style | Platform compact label (`CommonDiagramExtras.createLabelNode` 
/ `SimpleColoredComponent`) |
+| Swing tab | Untouched for this issue |
+| Verification | Automated component-type smoke + manual `runIde` |
+| Implementation approach | Extend `CommonDiagramExtras`; override both 
`createNodeComponent` overloads to return `createLabelNode(...)` |
+
+### Alternatives considered
+
+| Approach | Verdict |
+|---|---|
+| Extend `CommonDiagramExtras`, `createNodeComponent` → `createLabelNode` | 
**Chosen** — matches issue proposal; least custom UI; same direction as Spring 
Integration diagrams |
+| Stay on `DiagramExtras`, hand-build `SimpleColoredComponent` | Rejected — 
duplicates platform label logic; higher break risk |
+| Keep UML box, hide empty body | Rejected — still UML chrome; fails compact 
icon+label goal |
+| Custom colored rounded chips matching Swing | Rejected for this issue — more 
maintenance; user chose platform label style |
+
+## Architecture
+
+Show Diagram already builds a toolkit-neutral snapshot and maps it to API 
nodes/edges. This change only swaps node chrome at the extras hook.
+
+```
+StrutsDiagramProvider
+  └── StrutsDiagramExtras  (extends CommonDiagramExtras)
+        createNodeComponent(...) → createLabelNode(...)
+          └── SimpleColoredComponent (icon + title)
+```
+
+**Unchanged:** `StrutsConfigDiagramModel`, `StrutsDiagramDataModel`, element 
manager title/tooltip APIs, API node/edge adapters, Dom refresh, Swing 
`fileEditor` / `ui`, `plugin.xml` registrations.
+
+## Components
+
+| Unit | Role |
+|---|---|
+| `StrutsDiagramExtras` | Extend `CommonDiagramExtras<StrutsDiagramItem>` 
instead of `DiagramExtras`. Override both `createNodeComponent` overloads 
(`NodeRealizer` and `Point`) to return `createLabelNode(node, builder, 
wrapper)`. Keep existing `EditNodeHandler` and `uiDataSnapshot`. |
+| `StrutsDiagramProvider` | No logic change; still returns the same extras 
instance. |
+| `StrutsDiagramElementManager` / `StrutsDiagramApiNode` | Unchanged — title 
via `getElementTitle`, icon via `DiagramNode.getIcon()`, tooltips unchanged. |
+| Swing tab (`diagram.fileEditor` / `diagram.ui`) | Untouched. |
+
+No new production classes. No `plugin.xml` changes for this issue.
+
+`CommonDiagramExtras` may bring platform helpers/categories (e.g. 
borders/selection). Accept platform defaults for label nodes; do not add custom 
category UI unless required for `createLabelNode` to work.
+
+## Data flow
+
+### Render (changed chrome only)
+
+1. User invokes Show Diagram on a Struts 2 config.
+2. Data model publishes snapshot-backed API nodes/edges (unchanged).
+3. Platform asks extras for a node component.
+4. `createNodeComponent` returns `createLabelNode(...)`.
+5. Platform paints a `SimpleColoredComponent` with:
+   - title from element manager presentable title / `getElementTitle`
+   - icon from the diagram node (`StrutsDiagramApiNode.getIcon()` via deferred 
evaluator)
+
+### Interact / refresh (unchanged)
+
+- Hover → precomputed tooltip HTML from the snapshot.
+- Double-click / navigate → `EditNodeHandler` / 
`StrutsDiagramPresentation.navigateToElement`.
+- Same-file DomEvents → debounced `refreshDataModel()`.
+
+## Error handling & edge cases
+
+| Scenario | Behavior |
+|---|---|
+| Package / action / result node | Compact icon + name; no empty UML body |
+| Missing icon | Label still shows name |
+| Missing / empty title | Platform label with empty/minimal text; no crash |
+| Root file item (not a graph node) | Unchanged; not rendered as a graph node 
(`isAcceptableAsNode` false) |
+| Navigation / tooltips / Dom refresh | Unchanged |
+| Swing Diagram tab | Unchanged |
+
+## Testing
+
+### Automated
+
+Extend `StrutsDiagramProviderTest` or add a focused extras test:
+
+1. Build a small Struts snapshot node (package/action/result) → 
`StrutsDiagramApiNode`.
+2. Call `StrutsDiagramExtras.createNodeComponent(...)` with the lightest 
`DiagramBuilder` / wrapper the 262 Diagrams test APIs allow.
+3. Assert the result is a label-style component (`SimpleColoredComponent` or 
equivalent), **not** `DiagramNodeContainer`.
+4. Assert icon and title are present for that node kind.
+
+If a full `DiagramBuilder` is impractical in light tests, use the smallest 
fixture that still exercises the real override (not a pure mock that never 
calls platform label code). Keep existing provider/mapping/dom-refresh tests 
green.
+
+Gate: `./gradlew test -x rat --tests "com.intellij.struts2.diagram.*"`
+
+### Manual (`./gradlew runIde` on IU)
+
+1. Show Diagram on a Struts config → nodes are compact icon+label.
+2. Package / action / result icons and names remain visible.
+3. Double-click → navigates to XML; hover → tooltips.
+4. Confirm Swing Diagram tab still behaves as before.
+
+### Out of scope
+
+- Pixel/layout assertions
+- Robot / UI e2e
+- Swing tab removal
+- Tests for #96–#100
+
+## Future work (out of scope)
+
+- Remove Swing Diagram tab once Show Diagram UX is solid (deferred from 
migration design; not part of #120)
+- [#96](https://github.com/apache/struts-intellij-plugin/issues/96) merged 
multi-file view
+- [#98](https://github.com/apache/struts-intellij-plugin/issues/98) selection 
sync
+- [#99](https://github.com/apache/struts-intellij-plugin/issues/99) Structure 
tool window
+- [#100](https://github.com/apache/struts-intellij-plugin/issues/100) zoom/pan 
beyond platform chrome
+
+## References
+
+- Issue [#120](https://github.com/apache/struts-intellij-plugin/issues/120)
+- `com.intellij.diagram.extras.DiagramExtras#createNodeComponent`
+- `com.intellij.diagram.extras.custom.CommonDiagramExtras#createLabelNode`
+- `com.intellij.struts2.diagram.provider.StrutsDiagramExtras`
+- `com.intellij.struts2.diagram.provider.StrutsDiagramProvider`
diff --git 
a/src/main/java/com/intellij/struts2/diagram/provider/StrutsDiagramExtras.java 
b/src/main/java/com/intellij/struts2/diagram/provider/StrutsDiagramExtras.java
index 0ee8f34..512bca0 100644
--- 
a/src/main/java/com/intellij/struts2/diagram/provider/StrutsDiagramExtras.java
+++ 
b/src/main/java/com/intellij/struts2/diagram/provider/StrutsDiagramExtras.java
@@ -19,11 +19,12 @@ package com.intellij.struts2.diagram.provider;
 import com.intellij.diagram.DiagramBuilder;
 import com.intellij.diagram.DiagramNode;
 import com.intellij.diagram.DiagramPresentationModel;
-import com.intellij.diagram.extras.DiagramExtras;
 import com.intellij.diagram.extras.EditNodeHandler;
+import com.intellij.diagram.extras.custom.CommonDiagramExtras;
 import com.intellij.openapi.actionSystem.CommonDataKeys;
 import com.intellij.openapi.actionSystem.DataSink;
 import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.graph.view.NodeRealizer;
 import com.intellij.pom.Navigatable;
 import com.intellij.psi.PsiElement;
 import com.intellij.psi.SmartPsiElementPointer;
@@ -34,19 +35,47 @@ import com.intellij.util.containers.ContainerUtil;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import java.awt.Point;
 import java.util.List;
 
 /**
- * Diagrams host hooks for Struts config diagrams.
+ * Diagrams host hooks for Struts config diagrams, including compact label 
chrome
+ * provided by {@link #createLabelNode}.
  * <p>
  * Double-click in the diagram <em>editor</em> (non-popup) goes through
  * {@link EditNodeHandler}, not {@link DiagramNode}'s {@link Navigatable} API.
  * Popup mode and Jump to Source use {@link #uiDataSnapshot}.
  */
-public final class StrutsDiagramExtras extends 
DiagramExtras<StrutsDiagramItem> {
+public final class StrutsDiagramExtras extends 
CommonDiagramExtras<StrutsDiagramItem> {
 
     private final EditNodeHandler<StrutsDiagramItem> editNodeHandler = 
this::navigateNode;
 
+    /**
+     * Preserves the pre-#120 {@code DiagramExtras} default.
+     */
+    @Override
+    public boolean isZoomAnimationsEnabled() {
+        return false;
+    }
+
+    @Override
+    public @NotNull JComponent createNodeComponent(@NotNull 
DiagramNode<StrutsDiagramItem> node,
+                                                   @NotNull DiagramBuilder 
builder,
+                                                   @NotNull NodeRealizer 
nodeRealizer,
+                                                   @NotNull JPanel wrapper) {
+        return createLabelNode(node, builder, wrapper);
+    }
+
+    @Override
+    public @NotNull JComponent createNodeComponent(@NotNull 
DiagramNode<StrutsDiagramItem> node,
+                                                   @NotNull DiagramBuilder 
builder,
+                                                   @NotNull Point basePoint,
+                                                   @NotNull JPanel wrapper) {
+        return createLabelNode(node, builder, wrapper);
+    }
+
     @Override
     public @NotNull EditNodeHandler<StrutsDiagramItem> getEditNodeHandler() {
         return editNodeHandler;
diff --git 
a/src/test/java/com/intellij/struts2/diagram/StrutsDiagramProviderTest.java 
b/src/test/java/com/intellij/struts2/diagram/StrutsDiagramProviderTest.java
index 5ca6956..9d11f16 100644
--- a/src/test/java/com/intellij/struts2/diagram/StrutsDiagramProviderTest.java
+++ b/src/test/java/com/intellij/struts2/diagram/StrutsDiagramProviderTest.java
@@ -16,13 +16,21 @@
  */
 package com.intellij.struts2.diagram;
 
+import com.intellij.diagram.DiagramBuilder;
+import com.intellij.diagram.DiagramDataModel;
 import com.intellij.diagram.DiagramProvider;
 import com.intellij.diagram.DiagramVfsResolver;
+import com.intellij.diagram.components.DiagramNodeContainer;
+import com.intellij.diagram.extras.DiagramExtras;
 import com.intellij.diagram.extras.EditNodeHandler;
+import com.intellij.diagram.extras.custom.CommonDiagramExtras;
 import com.intellij.openapi.actionSystem.CommonDataKeys;
 import com.intellij.openapi.actionSystem.DataContext;
 import com.intellij.openapi.actionSystem.impl.SimpleDataContext;
 import com.intellij.openapi.application.ReadAction;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.graph.view.Graph2D;
+import com.intellij.openapi.graph.view.NodeRealizer;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.psi.PsiElement;
 import com.intellij.psi.PsiManager;
@@ -31,10 +39,17 @@ import com.intellij.struts2.BasicLightHighlightingTestCase;
 import com.intellij.struts2.diagram.model.StrutsConfigDiagramModel;
 import com.intellij.struts2.diagram.model.StrutsDiagramNode;
 import com.intellij.struts2.diagram.provider.StrutsDiagramApiNode;
+import com.intellij.struts2.diagram.provider.StrutsDiagramDataModel;
 import com.intellij.struts2.diagram.provider.StrutsDiagramExtras;
 import com.intellij.struts2.diagram.provider.StrutsDiagramItem;
 import com.intellij.struts2.diagram.provider.StrutsDiagramProvider;
+import com.intellij.ui.SimpleColoredComponent;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import java.lang.reflect.Proxy;
 
 public class StrutsDiagramProviderTest extends BasicLightHighlightingTestCase {
 
@@ -130,6 +145,109 @@ public class StrutsDiagramProviderTest extends 
BasicLightHighlightingTestCase {
         assertTrue(apiNode.canNavigate());
     }
 
+    public void testExtrasCreateCompactLabelNodeComponents() {
+        createStrutsFileSet("struts-diagram.xml");
+        VirtualFile file = myFixture.findFileInTempDir("struts-diagram.xml");
+        assertNotNull(file);
+        XmlFile xml = (XmlFile) 
PsiManager.getInstance(getProject()).findFile(file);
+        assertNotNull(xml);
+
+        StrutsConfigDiagramModel model = ReadAction.compute(() -> 
StrutsConfigDiagramModel.build(xml));
+        assertNotNull(model);
+        StrutsDiagramNode snapshotNode = model.getNodes().stream()
+                .filter(n -> n.getKind() == StrutsDiagramNode.Kind.ACTION)
+                .findFirst()
+                .orElseThrow();
+        assertNotNull(snapshotNode.getIcon());
+        assertFalse(snapshotNode.getName().isEmpty());
+
+        StrutsDiagramProvider provider = getProvider();
+        DiagramExtras<StrutsDiagramItem> extras = provider.getExtras();
+        assertInstanceOf(extras, CommonDiagramExtras.class);
+
+        StrutsDiagramItem item = StrutsDiagramItem.forNode(xml, snapshotNode);
+        StrutsDiagramApiNode apiNode = new StrutsDiagramApiNode(provider, 
item);
+
+        StrutsDiagramDataModel dataModel =
+                new StrutsDiagramDataModel(getProject(), provider, 
StrutsDiagramItem.forFile(xml));
+        try {
+            DiagramBuilder builder = stubBuilderForLabelNodes(provider, 
dataModel);
+            JPanel wrapper = new JPanel();
+            NodeRealizer nodeRealizer = (NodeRealizer) Proxy.newProxyInstance(
+                    NodeRealizer.class.getClassLoader(),
+                    new Class<?>[]{NodeRealizer.class},
+                    (proxy, method, args) -> 
proxyDefaultValue(method.getReturnType()));
+            JComponent component = extras.createNodeComponent(apiNode, 
builder, nodeRealizer, wrapper);
+
+            assertNotNull(component);
+            assertFalse(
+                    "Show Diagram nodes must not use UML DiagramNodeContainer 
chrome",
+                    component instanceof DiagramNodeContainer);
+            assertInstanceOf(component, SimpleColoredComponent.class);
+
+            SimpleColoredComponent label = (SimpleColoredComponent) component;
+            assertNotNull("Package/action/result icon must remain visible", 
label.getIcon());
+            assertTrue(
+                    "Node name must remain visible on the label",
+                    
label.getCharSequence(true).toString().contains(snapshotNode.getName()));
+        } finally {
+            com.intellij.openapi.util.Disposer.dispose(dataModel);
+        }
+    }
+
+    public void testExtrasKeepZoomAnimationsDisabled() {
+        assertFalse(
+                "Preserve the pre-#120 DiagramExtras zoom-animation default",
+                getProvider().getExtras().isZoomAnimationsEnabled());
+    }
+
+    /**
+     * Minimal DiagramBuilder for createLabelNode / setNodeBorders.
+     * No Mockito in this project — use JDK proxies.
+     */
+    private DiagramBuilder stubBuilderForLabelNodes(@NotNull 
StrutsDiagramProvider provider,
+                                                    @NotNull 
DiagramDataModel<StrutsDiagramItem> dataModel) {
+        var scheme = EditorColorsManager.getInstance().getGlobalScheme();
+        Graph2D graph = (Graph2D) Proxy.newProxyInstance(
+                Graph2D.class.getClassLoader(),
+                new Class<?>[]{Graph2D.class},
+                (proxy, method, args) -> {
+                    if ("isSelected".equals(method.getName())) {
+                        return false;
+                    }
+                    return proxyDefaultValue(method.getReturnType());
+                });
+        return (DiagramBuilder) Proxy.newProxyInstance(
+                DiagramBuilder.class.getClassLoader(),
+                new Class<?>[]{DiagramBuilder.class},
+                (proxy, method, args) -> switch (method.getName()) {
+                    case "getColorScheme" -> scheme;
+                    case "getProvider" -> provider;
+                    case "getDataModel" -> dataModel;
+                    case "getGraph" -> graph;
+                    case "getNode" -> null;
+                    case "toString" -> "StubDiagramBuilder";
+                    case "hashCode" -> System.identityHashCode(proxy);
+                    case "equals" -> proxy == args[0];
+                    default -> proxyDefaultValue(method.getReturnType());
+                });
+    }
+
+    private static @Nullable Object proxyDefaultValue(@NotNull Class<?> 
returnType) {
+        if (!returnType.isPrimitive()) {
+            return null;
+        }
+        if (returnType == boolean.class) return false;
+        if (returnType == byte.class) return (byte) 0;
+        if (returnType == short.class) return (short) 0;
+        if (returnType == int.class) return 0;
+        if (returnType == long.class) return 0L;
+        if (returnType == float.class) return 0f;
+        if (returnType == double.class) return 0d;
+        if (returnType == char.class) return '\0';
+        return null;
+    }
+
     private StrutsDiagramProvider getProvider() {
         DiagramProvider<?> provider = 
DiagramProvider.findByID(StrutsDiagramProvider.ID);
         assertNotNull(provider);


Reply via email to