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 d9751d7 Show Diagram: left-to-right hierarchic layout (#122) (#124)
d9751d7 is described below
commit d9751d79a0a526f4085110ea7f1175a98bc73ab4
Author: Lukasz Lenart <[email protected]>
AuthorDate: Sun Jul 26 05:05:34 2026 +0200
Show Diagram: left-to-right hierarchic layout (#122) (#124)
* docs: design Show Diagram left-to-right layout (#122)
Capture the refreshed #122 approach: Maven-style custom hierarchic
LTR layouter with soft preference for user-selected layout algorithms.
Co-authored-by: Cursor <[email protected]>
* docs: plan Show Diagram left-to-right layout (#122)
Co-authored-by: Cursor <[email protected]>
* test(diagram): require Show Diagram LTR custom layouter (#122)
Co-authored-by: Cursor <[email protected]>
* feat(diagram): use LTR hierarchic Show Diagram layouter (#122)
Co-authored-by: Cursor <[email protected]>
* docs: changelog Show Diagram LTR layout (#122)
Co-authored-by: Cursor <[email protected]>
* test(diagram): assert useDefaultLayouter stays false for LTR (#122)
Co-authored-by: Cursor <[email protected]>
---------
Co-authored-by: Cursor <[email protected]>
---
CHANGELOG.md | 1 +
.../plans/2026-07-26-show-diagram-ltr-layout.md | 309 +++++++++++++++++++++
.../2026-07-26-show-diagram-ltr-layout-design.md | 162 +++++++++++
.../diagram/provider/StrutsDiagramExtras.java | 22 ++
.../struts2/diagram/StrutsDiagramProviderTest.java | 28 ++
5 files changed, 522 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 221d3b3..8e0fb18 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,7 @@
- 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))
+- Show Diagram uses a left-to-right hierarchic layout by default for Struts
config graphs
([#122](https://github.com/apache/struts-intellij-plugin/issues/122))
- Dependencies - hard-depend on `com.intellij.diagram` (Ultimate Diagrams)
## [261.19027.1] - 2026-07-13
diff --git a/docs/superpowers/plans/2026-07-26-show-diagram-ltr-layout.md
b/docs/superpowers/plans/2026-07-26-show-diagram-ltr-layout.md
new file mode 100644
index 0000000..dcdd87b
--- /dev/null
+++ b/docs/superpowers/plans/2026-07-26-show-diagram-ltr-layout.md
@@ -0,0 +1,309 @@
+# Show Diagram Left-to-Right Layout 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 for Struts configs use a hierarchic
left-to-right custom layouter by default so package → action → result reads LTR.
+
+**Architecture:** Keep the existing Show Diagram provider/data model. Override
only `StrutsDiagramExtras.getCustomLayouter` to return a Maven/Gradle-style
hierarchic group layouter oriented `LEFT_TO_RIGHT`. Soft preference is already
satisfied because Dom refresh does not call `GraphSettings.setCurrentLayouter`
— do not add that. Leave Swing Diagram tab and snapshot model untouched.
+
+**Tech Stack:** IntelliJ IDEA Ultimate 2026.2 (262), `com.intellij.diagram`
(`DiagramExtras#getCustomLayouter`), `com.intellij.openapi.graph`
(`GraphManager`, `HierarchicGroupLayouter`, `LayoutOrientation`), JUnit 4 light
tests (`BasicLightHighlightingTestCase`).
+
+**Spec:** `docs/superpowers/specs/2026-07-26-show-diagram-ltr-layout-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 layouter extras only** — do **not** modify or remove
the Swing Diagram tab (`diagram.fileEditor` / `diagram.ui`).
+- Do **not** change `StrutsConfigDiagramModel` semantics, edge mapping,
tooltips, navigation, compact node chrome, or Dom refresh logic.
+- Custom layouter orientation is always **`LEFT_TO_RIGHT`** (Maven pattern).
Soft preference = do not reset a user-selected non-custom toolbar layout via
`GraphSettings.setCurrentLayouter`.
+- Do **not** read `GraphSettings.getCurrentLayoutOrientation()` for the custom
layouter (platform default is top-to-bottom and would defeat LTR).
+- Do **not** implement #96–#100 or Swing tab removal in this plan.
+- Tests gate: `./gradlew test -x rat --tests "com.intellij.struts2.diagram.*"`.
+- Learn API details from local IU SDK jars under Gradle caches
(`lib/intellij.platform.graph.jar`, `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 | Override `getCustomLayouter` → hierarchic LTR |
+| `src/test/java/com/intellij/struts2/diagram/StrutsDiagramProviderTest.java`
| Modify | Assert custom layouter is hierarchic and `LEFT_TO_RIGHT` |
+| `CHANGELOG.md` | Modify | Unreleased note for Show Diagram LTR layout (#122)
|
+
+No new production classes. No `plugin.xml` / Gradle dependency changes. No Dom
refresh changes (verify no `setCurrentLayouter`).
+
+---
+
+### Task 1: Failing test for LTR custom layouter
+
+**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.getCustomLayouter(GraphSettings, Project)`
+ - `com.intellij.openapi.graph.settings.GraphSettings`
+ -
`com.intellij.openapi.graph.layout.CanonicMultiStageLayouter#getLayoutOrientation()`
+ - `com.intellij.openapi.graph.layout.LayoutOrientation#LEFT_TO_RIGHT`
+ - `com.intellij.openapi.graph.layout.hierarchic.HierarchicGroupLayouter`
+- Produces: failing assertion that `getCustomLayouter` returns hierarchic LTR
(currently returns `null` from `DiagramExtras` default)
+
+- [ ] **Step 1: Add imports to `StrutsDiagramProviderTest`**
+
+Add these imports (keep existing ones):
+
+```java
+import com.intellij.openapi.graph.layout.CanonicMultiStageLayouter;
+import com.intellij.openapi.graph.layout.Layouter;
+import com.intellij.openapi.graph.layout.LayoutOrientation;
+import com.intellij.openapi.graph.layout.hierarchic.HierarchicGroupLayouter;
+import com.intellij.openapi.graph.settings.GraphSettings;
+```
+
+- [ ] **Step 2: Add failing test method**
+
+Add this test method to `StrutsDiagramProviderTest` (near the other extras
tests):
+
+```java
+ public void testCustomLayouterIsHierarchicLeftToRight() {
+ StrutsDiagramProvider provider = getProvider();
+ DiagramExtras<StrutsDiagramItem> extras = provider.getExtras();
+ GraphSettings settings = new GraphSettings();
+
+ Layouter layouter = extras.getCustomLayouter(settings, getProject());
+ assertNotNull("Show Diagram must provide a custom layouter for LTR
hierarchy", layouter);
+ assertInstanceOf(layouter, HierarchicGroupLayouter.class);
+
+ CanonicMultiStageLayouter multiStage = (CanonicMultiStageLayouter)
layouter;
+ assertEquals("Custom layouter must be left-to-right (Maven/Gradle
pattern)",
+ LayoutOrientation.LEFT_TO_RIGHT,
+ multiStage.getLayoutOrientation());
+
+ // Stable custom path — soft preference is "don't reset
GraphSettings", not reading orientation
+ Layouter again = extras.getCustomLayouter(settings, getProject());
+ assertNotNull(again);
+ assertEquals(LayoutOrientation.LEFT_TO_RIGHT,
+ ((CanonicMultiStageLayouter) again).getLayoutOrientation());
+ }
+```
+
+- [ ] **Step 3: Run test to verify it fails**
+
+Run:
+
+```bash
+./gradlew test -x rat --tests
"com.intellij.struts2.diagram.StrutsDiagramProviderTest.testCustomLayouterIsHierarchicLeftToRight"
+```
+
+Expected: FAIL because current `DiagramExtras.getCustomLayouter` returns
`null` (assertion `assertNotNull(...)` fails).
+
+- [ ] **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): require Show Diagram LTR custom layouter (#122)
+
+EOF
+)"
+```
+
+---
+
+### Task 2: Implement Maven-style LTR `getCustomLayouter`
+
+**Files:**
+- Modify:
`src/main/java/com/intellij/struts2/diagram/provider/StrutsDiagramExtras.java`
+- Test:
`src/test/java/com/intellij/struts2/diagram/StrutsDiagramProviderTest.java`
+
+**Interfaces:**
+- Consumes:
+ - `GraphManager.getGraphManager()`
+ - `GraphManager.createHierarchicGroupLayouter()`
+ - `GraphManager.createOrientationLayouter(byte)`
+ - `HierarchicGroupLayouter.setOrientationLayouter(LayoutStage)`
+ - `LayoutOrientation.LEFT_TO_RIGHT`
+- Produces: `StrutsDiagramExtras.getCustomLayouter(GraphSettings, Project)`
returning hierarchic LTR
+
+- [ ] **Step 1: Add imports to `StrutsDiagramExtras`**
+
+Add these imports (keep existing ones):
+
+```java
+import com.intellij.openapi.graph.GraphManager;
+import com.intellij.openapi.graph.layout.Layouter;
+import com.intellij.openapi.graph.layout.LayoutOrientation;
+import com.intellij.openapi.graph.layout.hierarchic.HierarchicGroupLayouter;
+import com.intellij.openapi.graph.settings.GraphSettings;
+import com.intellij.openapi.project.Project;
+```
+
+- [ ] **Step 2: Override `getCustomLayouter`**
+
+Add this method to `StrutsDiagramExtras` (after `isZoomAnimationsEnabled()` is
a good place):
+
+```java
+ /**
+ * Prefer package → action → result left-to-right on Show Diagram.
+ * Soft preference for user-selected non-custom toolbar layouts is handled
by
+ * not mutating {@link GraphSettings#setCurrentLayouter} on Dom refresh.
+ */
+ @Override
+ public @NotNull Layouter getCustomLayouter(@NotNull GraphSettings settings,
+ @NotNull Project project) {
+ GraphManager graphManager = GraphManager.getGraphManager();
+ HierarchicGroupLayouter layouter =
graphManager.createHierarchicGroupLayouter();
+ layouter.setOrientationLayouter(
+
graphManager.createOrientationLayouter(LayoutOrientation.LEFT_TO_RIGHT));
+ return layouter;
+ }
+```
+
+Notes for the implementer:
+
+- Match the Maven/Gradle UML extras pattern (`createHierarchicGroupLayouter` +
`createOrientationLayouter(LEFT_TO_RIGHT)`).
+- Do **not** call `settings.setCurrentLayouter(...)`.
+- Do **not** read `settings.getCurrentLayoutOrientation()`.
+- Optional Maven tweaks (`setMinimalNodeDistance`,
`setLayerer(createBFSLayerer())`) are **out of scope** unless the minimal
override fails manual LTR smoke — YAGNI.
+- If `@NotNull` on the override conflicts with the platform signature
(`Layouter` nullable), drop `@NotNull` on the method return and keep `return
layouter;` non-null in practice.
+- Confirm parameter nullability against `javap` on `DiagramExtras` if the IDE
complains:
+
+```bash
+UML_JAR=$(find ~/.gradle/caches -path
'*idea-2026.2*/plugins/uml/lib/uml-support.jar' | head -1)
+javap -classpath "$UML_JAR" -public com.intellij.diagram.extras.DiagramExtras
| rg getCustomLayouter
+```
+
+- [ ] **Step 3: Run the focused test to verify it passes**
+
+Run:
+
+```bash
+./gradlew test -x rat --tests
"com.intellij.struts2.diagram.StrutsDiagramProviderTest.testCustomLayouterIsHierarchicLeftToRight"
+```
+
+Expected: PASS.
+
+- [ ] **Step 4: Run the diagram test suite**
+
+Run:
+
+```bash
+./gradlew test -x rat --tests "com.intellij.struts2.diagram.*"
+```
+
+Expected: BUILD SUCCESSFUL; all `com.intellij.struts2.diagram.*` tests green.
+
+- [ ] **Step 5: Confirm Dom refresh still does not reset layouter**
+
+Run:
+
+```bash
+rg -n "setCurrentLayouter" src/main/java/com/intellij/struts2/diagram
+```
+
+Expected: no matches (soft preference preserved by omission).
+
+- [ ] **Step 6: Commit the implementation**
+
+```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 LTR hierarchic Show Diagram layouter (#122)
+
+EOF
+)"
+```
+
+---
+
+### Task 3: Changelog + issue framing note
+
+**Files:**
+- Modify: `CHANGELOG.md`
+- Related issue:
[#122](https://github.com/apache/struts-intellij-plugin/issues/122) (PR
description / optional issue comment)
+
+**Interfaces:**
+- Consumes: Keep a Changelog `[Unreleased]` → `### Changed` section style
already used for #117/#120
+- Produces: Unreleased changelog bullet for #122
+
+- [ ] **Step 1: Add Unreleased changelog entry**
+
+Under `## [Unreleased]` → `### Changed`, add (near the other Show Diagram
bullets):
+
+```markdown
+- Show Diagram uses a left-to-right hierarchic layout by default for Struts
config graphs
([#122](https://github.com/apache/struts-intellij-plugin/issues/122))
+```
+
+- [ ] **Step 2: Commit changelog**
+
+```bash
+git add CHANGELOG.md
+git commit -m "$(cat <<'EOF'
+docs: changelog Show Diagram LTR layout (#122)
+
+EOF
+)"
+```
+
+- [ ] **Step 3: Manual `runIde` smoke (implementer / reviewer)**
+
+Run:
+
+```bash
+./gradlew runIde
+```
+
+Check on IU:
+
+1. Open a small Struts config → **Show Diagram** → package → action → result
reads left-to-right.
+2. Edge label (e.g. `success`) remains readable.
+3. Pick a different layout algorithm from the diagram toolbar, edit the XML so
Dom refresh runs → the user’s layout choice remains (not forced back to custom
LTR).
+
+- [ ] **Step 4: When opening the PR, refresh #122 framing**
+
+In the PR body (and optionally an issue comment), note:
+
+- Swing Diagram tab LTR is no longer the acceptance baseline.
+- Implementation follows Maven/Gradle `getCustomLayouter` + `LEFT_TO_RIGHT`.
+- Soft preference: Dom refresh does not reset a user-selected non-custom
layout algorithm.
+
+Suggested PR acceptance checklist:
+
+```markdown
+## Summary
+- Show Diagram Struts configs use hierarchic left-to-right custom layouter via
`StrutsDiagramExtras.getCustomLayouter`
+- Soft preference: Dom refresh does not reset user-selected non-custom toolbar
layouts
+- Closes #122
+
+## Test plan
+- [ ] `./gradlew test -x rat --tests "com.intellij.struts2.diagram.*"`
+- [ ] Manual `runIde`: small config reads package → action → result LTR;
`success` label readable
+- [ ] Manual: change layout algorithm, edit XML → choice preserved after Dom
refresh
+```
+
+---
+
+## Spec coverage checklist
+
+| Spec requirement | Task |
+|---|---|
+| Initial LTR hierarchic layout via `getCustomLayouter` | Task 2 |
+| Soft preference (no `setCurrentLayouter` on Dom refresh) | Task 2 Step 5
(verify omission) |
+| Edge labeling left at platform default (`true`) | Task 2 (no
`doEdgeLabeling` override) |
+| No `StrutsConfigDiagramModel` changes | All tasks (extras/tests/changelog
only) |
+| Automated orientation smoke test | Task 1 |
+| Manual `runIde` checks | Task 3 Step 3 |
+| Changelog | Task 3 |
+| Update #122 framing | Task 3 Step 4 |
+| No Swing tab / pixel grid / #96–#100 | Global constraints |
+
+## Plan self-review
+
+1. **Spec coverage:** All goals mapped to tasks above; no gaps.
+2. **Placeholder scan:** No TBD/TODO; concrete code and commands included.
+3. **Type consistency:** `getCustomLayouter(GraphSettings, Project) ->
Layouter` / `HierarchicGroupLayouter` / `LayoutOrientation.LEFT_TO_RIGHT` used
consistently across tasks.
diff --git
a/docs/superpowers/specs/2026-07-26-show-diagram-ltr-layout-design.md
b/docs/superpowers/specs/2026-07-26-show-diagram-ltr-layout-design.md
new file mode 100644
index 0000000..0eec7b1
--- /dev/null
+++ b/docs/superpowers/specs/2026-07-26-show-diagram-ltr-layout-design.md
@@ -0,0 +1,162 @@
+# Show Diagram: Left-to-Right Hierarchical Layout
+
+**Date:** 2026-07-26
+**Status:** Approved for implementation planning
+**Related:**
[#122](https://github.com/apache/struts-intellij-plugin/issues/122); follow-up
to [#117](https://github.com/apache/struts-intellij-plugin/issues/117) /
[#119](https://github.com/apache/struts-intellij-plugin/pull/119) and
[#120](https://github.com/apache/struts-intellij-plugin/issues/120) /
[#123](https://github.com/apache/struts-intellij-plugin/pull/123)
+
+## Problem
+
+Show Diagram for a Struts config uses the platform default hierarchic
layouter. For a small file-local graph that often places packages toward the
bottom and results toward the top, so the package → action → result flow reads
bottom-up instead of left-to-right.
+
+Issue [#122](https://github.com/apache/struts-intellij-plugin/issues/122)
correctly states the UX goal, but its framing is partly stale:
+
+- It compares against the custom Swing Diagram tab (`Struts2DiagramComponent`)
as the layout source of truth. After the Show Diagram migration, that tab is no
longer the primary UX reference.
+- The cited hooks (`DiagramExtras#getCustomLayouter`, `useDefaultLayouter`)
remain valid on IntelliJ Platform 2026.2 and are still used by JetBrains
diagram providers (e.g. Maven/Gradle UML extras).
+
+## Goals
+
+1. Initial Show Diagram layout for typical Struts configs reads left-to-right
(package → action → result flow).
+2. Soft preference: if the user picks a different layout algorithm via diagram
chrome, Dom refresh does not reset that choice back to custom LTR.
+3. Edge labels (e.g. `success`) remain readable.
+4. No changes to `StrutsConfigDiagramModel` semantics.
+5. Automated smoke coverage for the extras layouter orientation behavior, plus
a short manual `runIde` check.
+6. Changelog documents the Show Diagram layout default.
+
+## Non-Goals
+
+- Pixel-perfect three-column grid matching the old Swing host.
+- Removing leftover Swing Diagram tab / `PerspectiveFileEditor` code (separate
cleanup).
+- Locking orientation so toolbar layout/orientation actions are ignored.
+- Pixel/layout geometry assertions or Robot UI e2e.
+- Changes to compact node chrome (#120), Dom refresh, navigation, or tooltips.
+
+## Decisions
+
+| Question | Decision |
+|---|---|
+| Layout style | Platform hierarchic group layouter oriented left-to-right by
default |
+| Soft vs fixed | Soft preference at the **layout algorithm** level — custom
layouter is always hierarchic LTR; do not reset a user-chosen non-custom
`DiagramLayout` on refresh |
+| Swing tab comparison | Dropped as acceptance baseline; Maven/Gradle
`getCustomLayouter` pattern is the reference |
+| Model changes | None — extras only |
+| Verification | Unit tests on extras orientation + manual `runIde` |
+
+### Alternatives considered
+
+| Approach | Verdict |
+|---|---|
+| `getCustomLayouter` always hierarchic LTR (Maven/Gradle pattern); soft =
preserve user-selected alternate `DiagramLayout` | **Chosen** — reliable
initial LTR; soft without fighting platform default orientation
(`TOP_TO_BOTTOM`) |
+| `useDefaultLayouter() = true` only | Rejected — weak/no control of initial
orientation; unlikely to fix bottom-up graphs |
+| Custom three-column `Layouter` (port Swing columns) | Rejected for this
issue — higher maintenance; fights platform layout chrome; overkill for soft
LTR preference |
+| Always force LTR and ignore toolbar orientation | Rejected — conflicts with
soft-preference requirement |
+
+## Architecture
+
+Show Diagram already builds a toolkit-neutral snapshot and maps it to API
nodes/edges. This change only supplies a custom layouter from extras.
+
+```
+StrutsDiagramProvider
+ └── StrutsDiagramExtras (CommonDiagramExtras)
+ └── getCustomLayouter(settings, project)
+ ├── GraphManager.createHierarchicGroupLayouter()
+ ├── OrientationLayouter = LEFT_TO_RIGHT (always, Maven-like)
+ └── keep spacing/layerer tweaks minimal (only if needed for
readability)
+```
+
+**Why not read `GraphSettings.getCurrentLayoutOrientation()` for the custom
layouter?**
+Platform default orientation is top-to-bottom. Honoring settings blindly would
keep the broken bottom-up/top-to-bottom graphs. Soft preference is instead: if
the user picks another toolbar layout algorithm (e.g. Organic or platform
Hierarchic), Dom refresh must leave that choice alone; only our custom layouter
path is LTR.
+
+**Unchanged:** `StrutsConfigDiagramModel`, `StrutsDiagramDataModel`, API
node/edge adapters, Dom refresh, compact label chrome, Swing `fileEditor` /
`ui`, `plugin.xml` registrations.
+
+## Components
+
+| Unit | Role |
+|---|---|
+| `StrutsDiagramExtras` | Override `getCustomLayouter`. Build a hierarchic
group layouter and always set orientation to `LEFT_TO_RIGHT` (Maven/Gradle
pattern). Leave `useDefaultLayouter()` at platform default (`false`). Keep
`doEdgeLabeling()` default (`true`). Do not mutate `GraphSettings` current
layouter on Dom refresh. |
+| `StrutsDiagramProvider` | No logic change; still returns the same extras
instance. |
+| Snapshot / presentation | Untouched. |
+| Swing tab (`diagram.fileEditor` / `diagram.ui`) | Untouched. |
+
+No new production classes. No `plugin.xml` changes for this issue.
+
+Implementation should follow the public IntelliJ graph APIs used by
Maven/Gradle extras (`GraphManager`, `HierarchicGroupLayouter`,
`LayoutOrientation`), not raw yFiles types.
+
+## Data flow
+
+### Initial open
+
+1. User invokes Show Diagram on a Struts 2 config.
+2. Platform asks extras for a custom layouter (or resolves
`DiagramLayout.CUSTOM` / matching custom class).
+3. Extras returns a hierarchic group layouter oriented `LEFT_TO_RIGHT`.
+4. Graph lays out with main flow left-to-right; edge labels remain enabled.
+
+### After user picks another layout algorithm
+
+1. User selects a non-custom layout from the diagram toolbar (e.g. Organic,
platform Hierarchic).
+2. Platform stores that layouter on `GraphSettings`.
+3. Dom refresh / data reload must not call `setCurrentLayouter` back to our
custom LTR layouter.
+4. `getCustomLayouter` may still return LTR hierarchic for the CUSTOM path,
but the active toolbar choice remains whatever the user selected.
+
+## Error handling & edge cases
+
+| Scenario | Behavior |
+|---|---|
+| Default / first open / CUSTOM path | Hierarchic LTR |
+| User selects another layout algorithm | That algorithm stays active across
Dom refresh |
+| User wants top-to-bottom | Choose platform Hierarchic (or equivalent) from
toolbar — not our custom LTR path |
+| Multi-package / chain / redirect edges | Same model semantics; hierarchic
LTR only changes placement |
+| Empty / unavailable model | Unchanged — no graph to lay out |
+| Edge label `success` / similar | Remains readable (`doEdgeLabeling` stays
on) |
+| Swing Diagram tab | Unchanged |
+
+## Testing
+
+### Automated
+
+Extend `StrutsDiagramProviderTest` (or a focused extras test):
+
+1. Call `StrutsDiagramExtras.getCustomLayouter(...)` → assert a non-null
hierarchic layouter oriented `LEFT_TO_RIGHT` (via the public orientation API
available on 262).
+2. Optionally assert a second call still returns LTR (custom path is stable;
soft preference is “don’t reset GraphSettings,” not “read orientation from
settings”).
+3. Do not assert pixel coordinates or node bounding boxes.
+
+Gate: `./gradlew test -x rat --tests "com.intellij.struts2.diagram.*"`
+
+If constructing a real `GraphSettings` instance is awkward in light tests, use
the smallest platform-supported fixture or stub that still exercises the real
extras branch (prefer real graph settings APIs over brittle mocks).
+
+### Manual (`./gradlew runIde` on IU)
+
+1. Show Diagram on a small Struts config → package → action → result reads
left-to-right.
+2. Confirm edge labels (e.g. `success`) remain readable.
+3. Pick a different layout algorithm from the toolbar, edit the XML so Dom
refresh runs → the user’s layout choice remains (not forced back to custom LTR).
+
+### Out of scope
+
+- Pixel/layout assertions
+- Robot / UI e2e
+- Swing tab removal
+- Tests for #96–#100
+
+## Issue framing update
+
+When implementing, update
[#122](https://github.com/apache/struts-intellij-plugin/issues/122) (comment or
PR description) so acceptance criteria match this design:
+
+- Drop Swing tab LTR as the comparison baseline.
+- Keep the LTR hierarchical goal and model non-change constraint.
+- Add soft-preference acceptance: a user-selected non-custom layout algorithm
is not reset on Dom refresh.
+
+## Future work (out of scope)
+
+- Remove Swing Diagram tab once Show Diagram UX is solid
+- [#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 [#122](https://github.com/apache/struts-intellij-plugin/issues/122)
+- `com.intellij.diagram.extras.DiagramExtras#getCustomLayouter`
+- `com.intellij.diagram.extras.DiagramExtras#useDefaultLayouter`
+- `com.intellij.openapi.graph.layout.LayoutOrientation`
+- `com.intellij.openapi.graph.GraphManager#createHierarchicGroupLayouter`
+- Maven/Gradle UML extras `getCustomLayouter` (platform reference pattern)
+- `com.intellij.struts2.diagram.provider.StrutsDiagramExtras`
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 512bca0..0f4292d 100644
---
a/src/main/java/com/intellij/struts2/diagram/provider/StrutsDiagramExtras.java
+++
b/src/main/java/com/intellij/struts2/diagram/provider/StrutsDiagramExtras.java
@@ -24,7 +24,13 @@ 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.GraphManager;
+import com.intellij.openapi.graph.layout.Layouter;
+import com.intellij.openapi.graph.layout.LayoutOrientation;
+import com.intellij.openapi.graph.layout.hierarchic.HierarchicGroupLayouter;
+import com.intellij.openapi.graph.settings.GraphSettings;
import com.intellij.openapi.graph.view.NodeRealizer;
+import com.intellij.openapi.project.Project;
import com.intellij.pom.Navigatable;
import com.intellij.psi.PsiElement;
import com.intellij.psi.SmartPsiElementPointer;
@@ -60,6 +66,22 @@ public final class StrutsDiagramExtras extends
CommonDiagramExtras<StrutsDiagram
return false;
}
+ /**
+ * Prefer package → action → result left-to-right on Show Diagram.
+ * {@code settings} is deliberately not consulted: the platform default
orientation is
+ * top-to-bottom, and the soft preference on Dom refresh is not resetting
the user's
+ * toolbar layout choice rather than reading orientation from {@link
GraphSettings}.
+ */
+ @Override
+ public @NotNull Layouter getCustomLayouter(GraphSettings settings,
+ Project project) {
+ GraphManager graphManager = GraphManager.getGraphManager();
+ HierarchicGroupLayouter layouter =
graphManager.createHierarchicGroupLayouter();
+ layouter.setOrientationLayouter(
+
graphManager.createOrientationLayouter(LayoutOrientation.LEFT_TO_RIGHT));
+ return layouter;
+ }
+
@Override
public @NotNull JComponent createNodeComponent(@NotNull
DiagramNode<StrutsDiagramItem> node,
@NotNull DiagramBuilder
builder,
diff --git
a/src/test/java/com/intellij/struts2/diagram/StrutsDiagramProviderTest.java
b/src/test/java/com/intellij/struts2/diagram/StrutsDiagramProviderTest.java
index 9d11f16..324297d 100644
--- a/src/test/java/com/intellij/struts2/diagram/StrutsDiagramProviderTest.java
+++ b/src/test/java/com/intellij/struts2/diagram/StrutsDiagramProviderTest.java
@@ -29,6 +29,11 @@ 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.layout.CanonicMultiStageLayouter;
+import com.intellij.openapi.graph.layout.Layouter;
+import com.intellij.openapi.graph.layout.LayoutOrientation;
+import com.intellij.openapi.graph.layout.hierarchic.HierarchicGroupLayouter;
+import com.intellij.openapi.graph.settings.GraphSettings;
import com.intellij.openapi.graph.view.Graph2D;
import com.intellij.openapi.graph.view.NodeRealizer;
import com.intellij.openapi.vfs.VirtualFile;
@@ -201,6 +206,29 @@ public class StrutsDiagramProviderTest extends
BasicLightHighlightingTestCase {
getProvider().getExtras().isZoomAnimationsEnabled());
}
+ public void testCustomLayouterIsHierarchicLeftToRight() {
+ StrutsDiagramProvider provider = getProvider();
+ DiagramExtras<StrutsDiagramItem> extras = provider.getExtras();
+ assertFalse("Custom layouter is only consulted when
useDefaultLayouter() is false",
+ extras.useDefaultLayouter());
+ GraphSettings settings = new GraphSettings();
+
+ Layouter layouter = extras.getCustomLayouter(settings, getProject());
+ assertNotNull("Show Diagram must provide a custom layouter for LTR
hierarchy", layouter);
+ assertInstanceOf(layouter, HierarchicGroupLayouter.class);
+
+ CanonicMultiStageLayouter multiStage = (CanonicMultiStageLayouter)
layouter;
+ assertEquals("Custom layouter must be left-to-right (Maven/Gradle
pattern)",
+ LayoutOrientation.LEFT_TO_RIGHT,
+ multiStage.getLayoutOrientation());
+
+ // Stable custom path — soft preference is "don't reset
GraphSettings", not reading orientation
+ Layouter again = extras.getCustomLayouter(settings, getProject());
+ assertNotNull(again);
+ assertEquals(LayoutOrientation.LEFT_TO_RIGHT,
+ ((CanonicMultiStageLayouter) again).getLayoutOrientation());
+ }
+
/**
* Minimal DiagramBuilder for createLabelNode / setNodeBorders.
* No Mockito in this project — use JDK proxies.