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);