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

davsclaus pushed a commit to branch fix/CAMEL-23672-info-panel
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 7292354c07d97eb05af3929f916371a83f72a4b8
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Jun 5 20:18:19 2026 +0200

    CAMEL-23672: camel-jbang - TUI diagram info panel with resizable sizing
    
    Add info panel to diagram view in both History and Errors tabs.
    The panel shows trace/error metadata (exchange, route, node, elapsed,
    thread, exception) and respects b/h/p/v/w toggles. Press 'i' to
    cycle through narrow/wide/full sizing. Wide mode hides minimap/tree
    overlay to give more space.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../jbang/core/commands/tui/DiagramSupport.java    |   8 +-
 .../dsl/jbang/core/commands/tui/ErrorsTab.java     | 191 +++++++++++++++++++--
 .../dsl/jbang/core/commands/tui/HistoryTab.java    |  65 ++++---
 3 files changed, 229 insertions(+), 35 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramSupport.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramSupport.java
index 9985da120c63..c31681f97321 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramSupport.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramSupport.java
@@ -1595,7 +1595,7 @@ class DiagramSupport {
         }
     }
 
-    void renderHistoryRouteDiagram(Frame frame, Rect area, Line title, String 
routeId) {
+    void renderHistoryRouteDiagram(Frame frame, Rect area, Line title, String 
routeId, boolean hideOverlays) {
         RouteDiagramLayoutEngine.LayoutRoute routeLayout = 
routeLayouts.get(routeId);
         if (routeLayout == null) {
             return;
@@ -1713,8 +1713,10 @@ class DiagramSupport {
                     vChunks.get(1), hScrollState);
         }
 
-        renderMinimap(frame, hChunks.get(0), routeId);
-        renderTreePreview(frame, hChunks.get(0), routeId);
+        if (!hideOverlays) {
+            renderMinimap(frame, hChunks.get(0), routeId);
+            renderTreePreview(frame, hChunks.get(0), routeId);
+        }
     }
 
     void loadAllDiagramsInBackground(
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java
index 759833063f33..e2c17215d367 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java
@@ -18,18 +18,22 @@ package org.apache.camel.dsl.jbang.core.commands.tui;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 
 import dev.tamboui.layout.Constraint;
 import dev.tamboui.layout.Layout;
 import dev.tamboui.layout.Rect;
 import dev.tamboui.style.Color;
+import dev.tamboui.style.Overflow;
 import dev.tamboui.style.Style;
 import dev.tamboui.terminal.Frame;
 import dev.tamboui.text.Line;
 import dev.tamboui.text.Span;
+import dev.tamboui.text.Text;
 import dev.tamboui.tui.event.KeyEvent;
 import dev.tamboui.widgets.block.Block;
 import dev.tamboui.widgets.block.BorderType;
+import dev.tamboui.widgets.paragraph.Paragraph;
 import dev.tamboui.widgets.scrollbar.ScrollbarState;
 import dev.tamboui.widgets.table.Cell;
 import dev.tamboui.widgets.table.Row;
@@ -62,7 +66,12 @@ class ErrorsTab implements MonitorTab {
     private boolean showHeaders = true;
     private boolean showBody = true;
 
+    private static final int INFO_NARROW = 0;
+    private static final int INFO_WIDE = 1;
+    private static final int INFO_FULL = 2;
+
     private final DiagramSupport diagram = new DiagramSupport();
+    private int infoPanelSize = INFO_NARROW;
 
     ErrorsTab(MonitorContext ctx) {
         this.ctx = ctx;
@@ -136,6 +145,30 @@ class ErrorsTab implements MonitorTab {
                 loadDiagramForSelectedError();
                 return true;
             }
+            if (ke.isChar('i')) {
+                infoPanelSize = (infoPanelSize + 1) % 3;
+                return true;
+            }
+            if (ke.isChar('b')) {
+                showBody = !showBody;
+                return true;
+            }
+            if (ke.isChar('h')) {
+                showHeaders = !showHeaders;
+                return true;
+            }
+            if (ke.isChar('p')) {
+                showProperties = !showProperties;
+                return true;
+            }
+            if (ke.isChar('v')) {
+                showVariables = !showVariables;
+                return true;
+            }
+            if (ke.isChar('w')) {
+                wordWrap = !wordWrap;
+                return true;
+            }
         }
         if (diagram.handleScrollKeys(ke)) {
             return true;
@@ -238,16 +271,34 @@ class ErrorsTab implements MonitorTab {
         }
 
         if (diagram.isShowDiagram() && diagram.isHistoryMode() && 
diagram.hasHistoryData()) {
-            if (diagram.isHistoryTopologyMode()) {
-                Line title = Line.from(Span.styled(
-                        String.format(" Error Topology — step %d/%d ",
-                                diagram.getHistoryStepIndex() + 1, 
diagram.getHistoryStepCount()),
-                        Style.EMPTY.fg(Color.WHITE)));
-                diagram.renderHistoryTopologyDiagram(frame, area, title);
+            if (infoPanelSize == INFO_FULL) {
+                renderDiagramInfoPanel(frame, area, info);
             } else {
-                String routeId = diagram.getHistoryDrillDownRouteId();
-                Line title = buildErrorBreadcrumbTitle();
-                diagram.renderHistoryRouteDiagram(frame, area, title, routeId);
+                Rect diagramArea = area;
+                Rect infoArea = null;
+                if (area.width() > 70) {
+                    int panelWidth = infoPanelSize == INFO_WIDE ? area.width() 
/ 2 : 35;
+                    List<Rect> hParts = Layout.horizontal()
+                            .constraints(Constraint.length(panelWidth), 
Constraint.fill())
+                            .split(area);
+                    infoArea = hParts.get(0);
+                    diagramArea = hParts.get(1);
+                }
+                boolean hideOverlays = infoPanelSize == INFO_WIDE;
+                if (diagram.isHistoryTopologyMode()) {
+                    Line title = Line.from(Span.styled(
+                            String.format(" Error Topology — step %d/%d ",
+                                    diagram.getHistoryStepIndex() + 1, 
diagram.getHistoryStepCount()),
+                            Style.EMPTY.fg(Color.WHITE)));
+                    diagram.renderHistoryTopologyDiagram(frame, diagramArea, 
title);
+                } else {
+                    String routeId = diagram.getHistoryDrillDownRouteId();
+                    Line title = buildErrorBreadcrumbTitle();
+                    diagram.renderHistoryRouteDiagram(frame, diagramArea, 
title, routeId, hideOverlays);
+                }
+                if (infoArea != null) {
+                    renderDiagramInfoPanel(frame, infoArea, info);
+                }
             }
             return;
         }
@@ -326,17 +377,29 @@ class ErrorsTab implements MonitorTab {
     public void renderFooter(List<Span> spans) {
         if (diagram.isShowDiagram()) {
             if (diagram.isHistoryMode() && diagram.hasHistoryData()) {
+                String infoLabel = switch (infoPanelSize) {
+                    case INFO_WIDE -> "info [wide]";
+                    case INFO_FULL -> "info [full]";
+                    default -> "info [narrow]";
+                };
                 if (diagram.isHistoryTopologyMode()) {
-                    hint(spans, "Esc", "close");
+                    hint(spans, "d", "close");
                     hint(spans, "↑↓←→", "navigate");
                     hint(spans, "Enter", "drill-down");
+                    hint(spans, "i", infoLabel);
                     hint(spans, "n", "description" + 
(diagram.isShowDescription() ? " [on]" : ""));
+                    hintShowBhpv(spans, showBody, showHeaders, showProperties, 
showVariables);
+                    hintLast(spans, "w", "wrap" + (wordWrap ? " [on]" : " 
[off]"));
                 } else {
+                    hint(spans, "d", "close");
                     hint(spans, "Esc", "back");
                     hint(spans, "↑↓", "step through path");
                     hint(spans, "←→", "h-scroll");
                     hint(spans, "t", "topology");
+                    hint(spans, "i", infoLabel);
                     hint(spans, "n", "description" + 
(diagram.isShowDescription() ? " [on]" : ""));
+                    hintShowBhpv(spans, showBody, showHeaders, showProperties, 
showVariables);
+                    hintLast(spans, "w", "wrap" + (wordWrap ? " [on]" : " 
[off]"));
                 }
                 return;
             }
@@ -510,6 +573,114 @@ class ErrorsTab implements MonitorTab {
         return Line.from(spans);
     }
 
+    private void renderDiagramInfoPanel(Frame frame, Rect area, 
IntegrationInfo info) {
+        List<ErrorInfo> sorted = applyFilter(info.errors);
+        Integer sel = tableState.selected();
+        ErrorInfo ei = null;
+        if (sel != null && sel >= 0 && sel < sorted.size()) {
+            ei = sorted.get(sel);
+        }
+
+        List<Line> lines = new ArrayList<>();
+        if (ei == null) {
+            frame.renderWidget(
+                    Paragraph.builder()
+                            .text(Text.from(Line.from(Span.styled("No error 
selected", Style.EMPTY.dim()))))
+                            
.block(Block.builder().borderType(BorderType.ROUNDED).title(" Info ").build())
+                            .build(),
+                    area);
+            return;
+        }
+
+        lines.add(Line.from(
+                Span.styled(" Exchange: ", 
Style.EMPTY.fg(Color.YELLOW).bold()),
+                Span.raw(ei.exchangeId != null ? ei.exchangeId : "")));
+        lines.add(Line.from(
+                Span.styled(" Route:    ", 
Style.EMPTY.fg(Color.YELLOW).bold()),
+                Span.styled(ei.routeId != null ? ei.routeId : "", 
Style.EMPTY.fg(Color.CYAN))));
+        lines.add(Line.from(
+                Span.styled(" Node:     ", 
Style.EMPTY.fg(Color.YELLOW).bold()),
+                Span.raw(ei.nodeId != null ? ei.nodeId : "")));
+        if (ei.elapsed >= 0) {
+            lines.add(Line.from(
+                    Span.styled(" Elapsed:  ", 
Style.EMPTY.fg(Color.YELLOW).bold()),
+                    Span.raw(ei.elapsed + "ms")));
+        }
+        if (ei.threadName != null) {
+            lines.add(Line.from(
+                    Span.styled(" Thread:   ", 
Style.EMPTY.fg(Color.YELLOW).bold()),
+                    Span.raw(ei.threadName)));
+        }
+        Style handledStyle = ei.handled ? Style.EMPTY.fg(Color.GREEN) : 
Style.EMPTY.fg(Color.LIGHT_RED).bold();
+        lines.add(Line.from(
+                Span.styled(" Handled:  ", 
Style.EMPTY.fg(Color.YELLOW).bold()),
+                Span.styled(ei.handled ? "true" : "false", handledStyle)));
+
+        if (ei.exceptionType != null) {
+            lines.add(Line.from(Span.raw("")));
+            lines.add(Line.from(Span.styled(" Exception", 
Style.EMPTY.fg(Color.LIGHT_RED).bold())));
+            lines.add(Line.from(Span.raw(" " + ei.exceptionType)));
+            if (ei.exceptionMessage != null) {
+                lines.add(Line.from(Span.raw(" " + ei.exceptionMessage)));
+            }
+        }
+
+        if (showBody && ei.body != null) {
+            lines.add(Line.from(Span.raw("")));
+            lines.add(Line.from(
+                    Span.styled(" Body", Style.EMPTY.fg(Color.GREEN).bold()),
+                    ei.bodyType != null ? Span.styled(" (" + ei.bodyType + 
")", Style.EMPTY.dim()) : Span.raw("")));
+            for (String line : ei.body.split("\n")) {
+                lines.add(Line.from(Span.raw(" " + line)));
+            }
+        }
+
+        if (showHeaders && !ei.headers.isEmpty()) {
+            lines.add(Line.from(Span.raw("")));
+            lines.add(Line.from(Span.styled(" Headers", 
Style.EMPTY.fg(Color.GREEN).bold())));
+            addKvLines(lines, ei.headers);
+        }
+
+        if (showProperties && !ei.properties.isEmpty()) {
+            lines.add(Line.from(Span.raw("")));
+            lines.add(Line.from(Span.styled(" Properties", 
Style.EMPTY.fg(Color.GREEN).bold())));
+            addKvLines(lines, ei.properties);
+        }
+
+        if (showVariables && !ei.variables.isEmpty()) {
+            lines.add(Line.from(Span.raw("")));
+            lines.add(Line.from(Span.styled(" Variables", 
Style.EMPTY.fg(Color.GREEN).bold())));
+            addKvLines(lines, ei.variables);
+        }
+
+        Paragraph.Builder pb = Paragraph.builder()
+                .text(Text.from(lines))
+                .block(Block.builder().borderType(BorderType.ROUNDED).title(" 
Info ").build());
+        if (wordWrap) {
+            pb.overflow(Overflow.WRAP_WORD);
+        }
+        frame.renderWidget(pb.build(), area);
+    }
+
+    private static void addKvLines(List<Line> lines, Map<String, Object> map) {
+        for (var entry : map.entrySet()) {
+            String val = entry.getValue() != null ? 
entry.getValue().toString() : "null";
+            lines.add(Line.from(
+                    Span.styled(" " + entry.getKey(), 
Style.EMPTY.fg(Color.CYAN)),
+                    Span.raw(" = " + val)));
+        }
+    }
+
+    private static void hintShowBhpv(List<Span> spans, boolean body, boolean 
headers, boolean props, boolean vars) {
+        spans.add(Span.styled(" show", HINT_KEY_STYLE));
+        spans.add(Span.raw(" "));
+        spans.add(Span.styled(body ? "B" : "b", body ? 
Style.EMPTY.fg(Color.WHITE).bold() : Style.EMPTY.dim()));
+        spans.add(Span.styled(headers ? "H" : "h", headers ? 
Style.EMPTY.fg(Color.WHITE).bold() : Style.EMPTY.dim()));
+        spans.add(Span.styled(props ? "P" : "p", props ? 
Style.EMPTY.fg(Color.WHITE).bold() : Style.EMPTY.dim()));
+        spans.add(Span.styled(vars ? "V" : "v", vars ? 
Style.EMPTY.fg(Color.WHITE).bold() : Style.EMPTY.dim()));
+        spans.add(Span.raw("  "));
+    }
+
     // ---- Diagram ----
 
     private void loadDiagramForSelectedError() {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTab.java
index 1ee32be68e9f..b0a3120083c8 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTab.java
@@ -105,8 +105,13 @@ class HistoryTab implements MonitorTab {
 
     private final DiagramSupport diagram = new DiagramSupport();
 
+    private static final int INFO_NARROW = 0;
+    private static final int INFO_WIDE = 1;
+    private static final int INFO_FULL = 2;
+
     private List<TraceEntry> diagramTraceSteps = Collections.emptyList();
     private List<HistoryEntry> diagramHistorySteps = Collections.emptyList();
+    private int infoPanelSize = INFO_NARROW;
 
     volatile List<HistoryEntry> historyEntries = Collections.emptyList();
     private final TableState historyTableState = new TableState();
@@ -229,6 +234,10 @@ class HistoryTab implements MonitorTab {
                 }
                 return true;
             }
+            if (ke.isChar('i')) {
+                infoPanelSize = (infoPanelSize + 1) % 3;
+                return true;
+            }
         }
         if (diagram.handleScrollKeys(ke)) {
             return true;
@@ -504,29 +513,34 @@ class HistoryTab implements MonitorTab {
         }
 
         if (diagram.isShowDiagram() && diagram.isHistoryMode() && 
diagram.hasHistoryData()) {
-            Rect diagramArea = area;
-            Rect infoArea = null;
-            if (area.width() > 70) {
-                int panelWidth = 35;
-                List<Rect> hParts = Layout.horizontal()
-                        .constraints(Constraint.length(panelWidth), 
Constraint.fill())
-                        .split(area);
-                infoArea = hParts.get(0);
-                diagramArea = hParts.get(1);
-            }
-            if (diagram.isHistoryTopologyMode()) {
-                Line title = Line.from(Span.styled(
-                        String.format(" History Topology — step %d/%d ",
-                                diagram.getHistoryStepIndex() + 1, 
diagram.getHistoryStepCount()),
-                        Style.EMPTY.fg(Color.WHITE)));
-                diagram.renderHistoryTopologyDiagram(frame, diagramArea, 
title);
+            if (infoPanelSize == INFO_FULL) {
+                renderDiagramInfoPanel(frame, area);
             } else {
-                String routeId = diagram.getHistoryDrillDownRouteId();
-                Line title = buildHistoryBreadcrumbTitle();
-                diagram.renderHistoryRouteDiagram(frame, diagramArea, title, 
routeId);
-            }
-            if (infoArea != null) {
-                renderDiagramInfoPanel(frame, infoArea);
+                Rect diagramArea = area;
+                Rect infoArea = null;
+                if (area.width() > 70) {
+                    int panelWidth = infoPanelSize == INFO_WIDE ? area.width() 
/ 2 : 35;
+                    List<Rect> hParts = Layout.horizontal()
+                            .constraints(Constraint.length(panelWidth), 
Constraint.fill())
+                            .split(area);
+                    infoArea = hParts.get(0);
+                    diagramArea = hParts.get(1);
+                }
+                boolean hideOverlays = infoPanelSize == INFO_WIDE;
+                if (diagram.isHistoryTopologyMode()) {
+                    Line title = Line.from(Span.styled(
+                            String.format(" History Topology — step %d/%d ",
+                                    diagram.getHistoryStepIndex() + 1, 
diagram.getHistoryStepCount()),
+                            Style.EMPTY.fg(Color.WHITE)));
+                    diagram.renderHistoryTopologyDiagram(frame, diagramArea, 
title);
+                } else {
+                    String routeId = diagram.getHistoryDrillDownRouteId();
+                    Line title = buildHistoryBreadcrumbTitle();
+                    diagram.renderHistoryRouteDiagram(frame, diagramArea, 
title, routeId, hideOverlays);
+                }
+                if (infoArea != null) {
+                    renderDiagramInfoPanel(frame, infoArea);
+                }
             }
             return;
         }
@@ -560,10 +574,16 @@ class HistoryTab implements MonitorTab {
                 boolean sp = isTraceMode ? showTraceProperties : 
showHistoryProperties;
                 boolean sv = isTraceMode ? showTraceVariables : 
showHistoryVariables;
                 boolean sw = isTraceMode ? traceWordWrap : historyWordWrap;
+                String infoLabel = switch (infoPanelSize) {
+                    case INFO_WIDE -> "info [wide]";
+                    case INFO_FULL -> "info [full]";
+                    default -> "info [narrow]";
+                };
                 if (diagram.isHistoryTopologyMode()) {
                     hint(spans, "d", "close");
                     hint(spans, "↑↓←→", "navigate");
                     hint(spans, "Enter", "drill-down");
+                    hint(spans, "i", infoLabel);
                     hint(spans, "n", "description" + (showDescription ? " 
[on]" : ""));
                     hintShowBhpv(spans, sb, sh, sp, sv);
                     hintLast(spans, "w", "wrap" + (sw ? " [on]" : " [off]"));
@@ -573,6 +593,7 @@ class HistoryTab implements MonitorTab {
                     hint(spans, "↑↓", "step through path");
                     hint(spans, "←→", "h-scroll");
                     hint(spans, "t", "topology");
+                    hint(spans, "i", infoLabel);
                     hint(spans, "n", "description" + (showDescription ? " 
[on]" : ""));
                     hintShowBhpv(spans, sb, sh, sp, sv);
                     hintLast(spans, "w", "wrap" + (sw ? " [on]" : " [off]"));

Reply via email to