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

davsclaus pushed a commit to branch CAMEL-26864-tui-walk-fixes
in repository https://gitbox.apache.org/repos/asf/camel.git

commit a357f174dbe93c2853e98dd57b95cde15398599a
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Nov 11 14:53:53 2026 +0100

    CAMEL-26864: camel-jbang - Startup total and step parents, tui_get_history 
on Inspect, tab names, small display fixes
    
    - Startup: the total is from the first step to the end of the last one;
      each step is under its own parent (depth-first by id); a step with no
      name has no leading space.
    - tui_get_history reads the Inspect tab (it looked up "History"), and
      with an exchangeId returns its steps without changing the user's tab.
    - tui_get_table names the tabs as tui_get_options lists them; sub-views
      are given as "view".
    - Log title "level: INFO"; TOTAL (AGO) / FAIL (AGO) headers; internal
      entry points "timer:tick (period=2000, internal)"; Errors empty state
      in the first column.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../core/commands/ai/ProjectCapabilities.java      |  14 ++-
 .../core/commands/ai/ProjectCapabilitiesTest.java  |  13 ++
 .../dsl/jbang/core/commands/tui/BrowseTab.java     |   5 +-
 .../jbang/core/commands/tui/CircuitBreakerTab.java |   2 +-
 .../dsl/jbang/core/commands/tui/ErrorsTab.java     |   3 +-
 .../jbang/core/commands/tui/HeapHistogramTab.java  |   2 +-
 .../dsl/jbang/core/commands/tui/HistoryTab.java    | 136 +++++++++++----------
 .../camel/dsl/jbang/core/commands/tui/LogTab.java  |   2 +-
 .../dsl/jbang/core/commands/tui/McpFacade.java     |   6 +-
 .../dsl/jbang/core/commands/tui/MemoryLeakTab.java |   3 +-
 .../dsl/jbang/core/commands/tui/MemoryTab.java     |   2 +-
 .../dsl/jbang/core/commands/tui/OverviewTab.java   |   5 +-
 .../dsl/jbang/core/commands/tui/RoutesTab.java     |   5 +-
 .../dsl/jbang/core/commands/tui/StartupTab.java    |  95 ++++++++++++--
 .../jbang/core/commands/tui/TransformersTab.java   |   2 +-
 .../core/commands/tui/TuiToolDefinitions.java      |   4 +-
 .../jbang/core/commands/tui/TuiToolRegistry.java   |  17 ++-
 .../src/main/resources/tui/help/overview.md        |   4 +-
 .../src/main/resources/tui/help/routes.md          |   4 +-
 .../core/commands/tui/ErrorsTabRenderTest.java     |   6 +-
 .../core/commands/tui/HistoryTabRenderTest.java    |  19 +++
 .../jbang/core/commands/tui/LogTabRenderTest.java  |   2 +-
 .../core/commands/tui/StartupTabRenderTest.java    |  45 +++++++
 23 files changed, 290 insertions(+), 106 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ProjectCapabilities.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ProjectCapabilities.java
index e777e08093ec..56081997352f 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ProjectCapabilities.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ProjectCapabilities.java
@@ -302,10 +302,22 @@ public final class ProjectCapabilities {
     private static List<String> entryPoints(Overview overview, List<String> 
routes) {
         List<String> answer = new ArrayList<>();
         overview.entryPoints().stream().filter(e -> e.route() != null && 
routes.contains(e.route()))
-                .forEach(e -> answer.add(ProjectOverview.isInternal(e) ? 
e.label() + " (internal)" : e.label()));
+                .forEach(e -> answer.add(entryPointLabel(e)));
         return answer;
     }
 
+    /**
+     * An entry point as a group's box shows it: internal ones say so, inside 
the brackets of the label when it has some
+     * ("timer:tick (period=2000, internal)").
+     */
+    static String entryPointLabel(ProjectOverview.EntryPoint e) {
+        String label = e.label();
+        if (!ProjectOverview.isInternal(e)) {
+            return label;
+        }
+        return label.endsWith(")") ? label.substring(0, label.length() - 1) + 
", internal)" : label + " (internal)";
+    }
+
     private static List<String> systems(Overview overview, List<String> 
routes) {
         Set<String> answer = new LinkedHashSet<>();
         overview.systems().stream().filter(s -> 
routes.contains(s.route())).forEach(s -> answer.add(s.name()));
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/ProjectCapabilitiesTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/ProjectCapabilitiesTest.java
index 1cfd23101788..447406a85051 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/ProjectCapabilitiesTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/ProjectCapabilitiesTest.java
@@ -36,6 +36,19 @@ import static org.assertj.core.api.Assertions.assertThat;
  */
 class ProjectCapabilitiesTest {
 
+    @Test
+    void anInternalEntryPointSaysSoInsideTheBracketsOfItsLabel() {
+        assertThat(ProjectCapabilities.entryPointLabel(
+                new ProjectOverview.EntryPoint("timer", "timer:tick 
(period=2000)", "hello", false)))
+                .isEqualTo("timer:tick (period=2000, internal)");
+        assertThat(ProjectCapabilities.entryPointLabel(
+                new ProjectOverview.EntryPoint("timer", "timer:tick", "hello", 
false)))
+                .isEqualTo("timer:tick (internal)");
+        assertThat(ProjectCapabilities.entryPointLabel(
+                new ProjectOverview.EntryPoint("http", 
"platform-http:/orders", "order", true)))
+                .isEqualTo("platform-http:/orders");
+    }
+
     static final String ROUTES = """
             - route:
                 id: invoice
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/BrowseTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/BrowseTab.java
index 36ae33785b10..395f06925294 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/BrowseTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/BrowseTab.java
@@ -747,7 +747,8 @@ class BrowseTab extends AbstractTab {
     public JsonObject getTableDataAsJson() {
         if (selectedEndpoint != null && !messages.isEmpty()) {
             JsonObject result = new JsonObject();
-            result.put("tab", "Browse Messages");
+            result.put("tab", "Browse Endpoints");
+            result.put("view", "Messages");
             result.put("endpoint", selectedEndpoint.uri);
             JsonArray rows = new JsonArray();
             for (MessageData m : messages) {
@@ -775,7 +776,7 @@ class BrowseTab extends AbstractTab {
             return null;
         }
         JsonObject result = new JsonObject();
-        result.put("tab", "Browse");
+        result.put("tab", "Browse Endpoints");
         JsonArray rows = new JsonArray();
         for (EndpointData e : endpoints) {
             JsonObject row = new JsonObject();
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTab.java
index 4550c9580a34..c5869c5b9be1 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTab.java
@@ -414,7 +414,7 @@ class CircuitBreakerTab extends AbstractTableTab {
             return null;
         }
         JsonObject result = new JsonObject();
-        result.put("tab", "CircuitBreaker");
+        result.put("tab", "Circuit Breaker");
         JsonArray rows = new JsonArray();
         List<CircuitBreakerInfo> sorted = new 
ArrayList<>(info.circuitBreakers);
         sorted.sort(this::sortCb);
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 33cac7128064..58760c6bd024 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
@@ -365,7 +365,8 @@ class ErrorsTab extends AbstractTableTab {
         }
 
         if (rows.isEmpty()) {
-            rows.add(emptyRow("No errors captured", 8, 7));
+            // in the first column, as the empty state of the other tabs, 
short enough not to be cut there
+            rows.add(emptyRow("No errors", 8));
         }
 
         ErrorInfo selectedError = shownError(info);
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HeapHistogramTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HeapHistogramTab.java
index 4a2acf52d232..b07424246436 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HeapHistogramTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HeapHistogramTab.java
@@ -304,7 +304,7 @@ class HeapHistogramTab extends AbstractTableTab {
             return null;
         }
         JsonObject result = new JsonObject();
-        result.put("tab", "Heap Histogram");
+        result.put("tab", "Heap Memory Histogram");
         JsonArray rows = new JsonArray();
         for (HeapEntry e : entries) {
             JsonObject row = new JsonObject();
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 6345bae4b9dd..d4d8355be238 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
@@ -2327,15 +2327,6 @@ class HistoryTab extends AbstractTab {
         return DocHelper.loadHelpText("history");
     }
 
-    void selectTraceExchange(String exchangeId) {
-        if (exchangeId != null && traceSortedExchangeIds.contains(exchangeId)) 
{
-            traceSelectedExchangeId = exchangeId;
-            traceDetailView = true;
-            traceStepTableState.select(0);
-            traceDetailScroll = 0;
-        }
-    }
-
     String toggleDisplaySection(String section, Boolean enabled) {
         boolean tracerActive = !traces.get().isEmpty();
         boolean newValue;
@@ -2392,71 +2383,83 @@ class HistoryTab extends AbstractTab {
         return section + "=" + (newValue ? "on" : "off");
     }
 
+    /**
+     * The trace steps of an exchange with all their details, as 
tui_get_history returns them, without changing what the
+     * tab shows.
+     */
+    JsonObject traceStepsAsJson(String exchangeId) {
+        JsonObject result = new JsonObject();
+        result.put("tab", "Inspect");
+        result.put("view", "Trace Steps");
+        List<TraceEntry> steps = getTraceStepsDepthFirst(exchangeId);
+        JsonArray rows = new JsonArray();
+        for (TraceEntry t : steps) {
+            JsonObject row = new JsonObject();
+            row.put("exchangeId", t.exchangeId);
+            row.put("routeId", t.routeId);
+            row.put("nodeId", t.nodeId);
+            row.put("processor", t.processor);
+            row.put("elapsed", t.elapsed);
+            row.put("timestamp", t.timestamp);
+            row.put("direction", t.direction);
+            row.put("status", t.status);
+            row.put("failed", t.failed);
+            row.put("first", t.first);
+            row.put("last", t.last);
+            if (t.body != null) {
+                row.put("body", t.body);
+            }
+            if (t.bodyType != null) {
+                row.put("bodyType", t.bodyType);
+            }
+            if (t.exception != null) {
+                row.put("exception", t.exception);
+            }
+            if (t.nodeLabel != null) {
+                row.put("nodeLabel", t.nodeLabel);
+            }
+            if (t.nodeShortName != null) {
+                row.put("nodeShortName", t.nodeShortName);
+            }
+            if (t.location != null) {
+                row.put("location", t.location);
+            }
+            if (t.threadName != null) {
+                row.put("threadName", t.threadName);
+            }
+            row.put("nodeLevel", t.nodeLevel);
+            if (t.headers != null && !t.headers.isEmpty()) {
+                row.put("headers", new JsonObject(t.headers));
+            }
+            if (t.headerTypes != null && !t.headerTypes.isEmpty()) {
+                row.put("headerTypes", new JsonObject(t.headerTypes));
+            }
+            if (t.exchangeProperties != null && 
!t.exchangeProperties.isEmpty()) {
+                row.put("exchangeProperties", new 
JsonObject(t.exchangeProperties));
+            }
+            if (t.exchangeVariables != null && !t.exchangeVariables.isEmpty()) 
{
+                row.put("exchangeVariables", new 
JsonObject(t.exchangeVariables));
+            }
+            rows.add(row);
+        }
+        result.put("rows", rows);
+        result.put("totalRows", steps.size());
+        result.put("exchangeId", exchangeId);
+        return result;
+    }
+
     @Override
     public JsonObject getTableDataAsJson() {
         JsonObject result = new JsonObject();
         boolean tracerActive = !traces.get().isEmpty();
         if (tracerActive) {
             if (traceDetailView && traceSelectedExchangeId != null) {
-                result.put("tab", "Trace Steps");
-                List<TraceEntry> steps = 
getTraceStepsDepthFirst(traceSelectedExchangeId);
-                JsonArray rows = new JsonArray();
-                for (TraceEntry t : steps) {
-                    JsonObject row = new JsonObject();
-                    row.put("exchangeId", t.exchangeId);
-                    row.put("routeId", t.routeId);
-                    row.put("nodeId", t.nodeId);
-                    row.put("processor", t.processor);
-                    row.put("elapsed", t.elapsed);
-                    row.put("timestamp", t.timestamp);
-                    row.put("direction", t.direction);
-                    row.put("status", t.status);
-                    row.put("failed", t.failed);
-                    row.put("first", t.first);
-                    row.put("last", t.last);
-                    if (t.body != null) {
-                        row.put("body", t.body);
-                    }
-                    if (t.bodyType != null) {
-                        row.put("bodyType", t.bodyType);
-                    }
-                    if (t.exception != null) {
-                        row.put("exception", t.exception);
-                    }
-                    if (t.nodeLabel != null) {
-                        row.put("nodeLabel", t.nodeLabel);
-                    }
-                    if (t.nodeShortName != null) {
-                        row.put("nodeShortName", t.nodeShortName);
-                    }
-                    if (t.location != null) {
-                        row.put("location", t.location);
-                    }
-                    if (t.threadName != null) {
-                        row.put("threadName", t.threadName);
-                    }
-                    row.put("nodeLevel", t.nodeLevel);
-                    if (t.headers != null && !t.headers.isEmpty()) {
-                        row.put("headers", new JsonObject(t.headers));
-                    }
-                    if (t.headerTypes != null && !t.headerTypes.isEmpty()) {
-                        row.put("headerTypes", new JsonObject(t.headerTypes));
-                    }
-                    if (t.exchangeProperties != null && 
!t.exchangeProperties.isEmpty()) {
-                        row.put("exchangeProperties", new 
JsonObject(t.exchangeProperties));
-                    }
-                    if (t.exchangeVariables != null && 
!t.exchangeVariables.isEmpty()) {
-                        row.put("exchangeVariables", new 
JsonObject(t.exchangeVariables));
-                    }
-                    rows.add(row);
-                }
-                result.put("rows", rows);
-                result.put("totalRows", steps.size());
-                result.put("exchangeId", traceSelectedExchangeId);
+                result = traceStepsAsJson(traceSelectedExchangeId);
                 Integer sel = traceStepTableState.selected();
                 result.put("selectedIndex", sel != null ? sel : -1);
             } else {
-                result.put("tab", "Traces");
+                result.put("tab", "Inspect");
+                result.put("view", "Traces");
                 List<String> exchangeIds = getTraceExchangeIds();
                 JsonArray rows = new JsonArray();
                 for (String eid : exchangeIds) {
@@ -2470,7 +2473,8 @@ class HistoryTab extends AbstractTab {
                 result.put("selectedIndex", sel != null ? sel : -1);
             }
         } else {
-            result.put("tab", "History");
+            result.put("tab", "Inspect");
+            result.put("view", "History");
             List<HistoryEntry> entries = 
reorderHistoryDepthFirst(historyEntries);
             JsonArray rows = new JsonArray();
             for (int i = 0; i < entries.size(); i++) {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java
index 8442ece204b3..5b77c1478fed 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java
@@ -350,7 +350,7 @@ class LogTab extends AbstractTab {
             titleSpans.add(Span.styled(ctx.selectedName(), 
Theme.label().bold()));
             titleSpans.add(Span.raw("] Log "));
             if (!ctx.logPinVisible && info != null && info.rootLogLevel != 
null) {
-                titleSpans.add(Span.raw("level:" + info.rootLogLevel + " "));
+                titleSpans.add(Span.raw("level: " + info.rootLogLevel + " "));
             }
         } else {
             titleSpans.add(Span.raw(" Log "));
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java
index 486c1750122e..073f6de05e15 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java
@@ -807,8 +807,10 @@ class McpFacade {
         return tabRegistry.diagramTab().getTableDataAsJson();
     }
 
-    void selectTraceExchange(String exchangeId) {
-        tabRegistry.historyTab().selectTraceExchange(exchangeId);
+    /** The trace steps of an exchange, or null when it has none; the Inspect 
tab's view stays as it is. */
+    JsonObject getTraceSteps(String exchangeId) {
+        JsonObject steps = 
tabRegistry.historyTab().traceStepsAsJson(exchangeId);
+        return steps.getIntegerOrDefault("totalRows", 0) > 0 ? steps : null;
     }
 
     JsonObject getTopologyData() {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryLeakTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryLeakTab.java
index a40614f73a37..ff35827b07fd 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryLeakTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryLeakTab.java
@@ -1347,8 +1347,7 @@ class MemoryLeakTab extends AbstractTab {
     }
 
     private JsonObject getComparisonDataAsJson() {
-        JsonObject result = new JsonObject();
-        result.put("tab", "Memory Leak Comparison");
+        JsonObject result = new JsonObject(Map.of("tab", "Memory Leak", 
"view", "Comparison"));
         result.put("baselineDurationMs", baselineDurationMs);
         result.put("currentDurationMs", currentDurationMs);
         result.put("durationRatio", durationRatio);
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryTab.java
index 1e78cad795b7..59affed2cf6b 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MemoryTab.java
@@ -500,7 +500,7 @@ class MemoryTab extends AbstractTab {
             return null;
         }
         JsonObject result = new JsonObject();
-        result.put("tab", "Memory");
+        result.put("tab", "Memory Usage");
         JsonObject data = new JsonObject();
         data.put("heapMemUsed", info.heapMemUsed);
         data.put("heapMemCommitted", info.heapMemCommitted);
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java
index a40f6f5079f8..c9b4246981d6 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java
@@ -764,8 +764,9 @@ class OverviewTab extends AbstractTab {
                 Cell.from(Span.styled(sortLabel("STATUS", "status"), 
sortStyle("status"))),
                 rightCell("ROUTE", 7, Style.EMPTY.bold()),
                 rightCell(ctx.ratePerMinute ? "MSG/M" : "MSG/S", 8, 
Style.EMPTY.bold()),
-                centerCell(sortLabel("TOTAL", "total"), 14, 
sortStyle("total")),
-                centerCell(sortLabel("FAIL", "fail"), 14, sortStyle("fail")),
+                // the time in brackets is since the last one
+                centerCell(sortLabel("TOTAL (AGO)", "total"), 14, 
sortStyle("total")),
+                centerCell(sortLabel("FAIL (AGO)", "fail"), 14, 
sortStyle("fail")),
                 rightCell(timingHeader, 20, Style.EMPTY.bold()),
                 Cell.from(""));
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
index 1c841f510c32..c510691eb068 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
@@ -656,8 +656,9 @@ class RoutesTab extends AbstractDiagramTab {
                         Cell.from(Span.styled(routeSortLabel("FROM", "from"), 
routeSortStyle("from"))),
                         Cell.from(Span.styled(routeSortLabel("STATUS", 
"status"), routeSortStyle("status"))),
                         rightCell(ctx.ratePerMinute ? "MSG/M" : "MSG/S", 8, 
Style.EMPTY.bold()),
-                        centerCell(routeSortLabel("TOTAL", "total"), 14, 
routeSortStyle("total")),
-                        centerCell(routeSortLabel("FAIL", "failed"), 14, 
routeSortStyle("failed"))));
+                        // the time in brackets is since the last one
+                        centerCell(routeSortLabel("TOTAL (AGO)", "total"), 14, 
routeSortStyle("total")),
+                        centerCell(routeSortLabel("FAIL (AGO)", "failed"), 14, 
routeSortStyle("failed"))));
         List<Constraint> widths = new ArrayList<>(
                 List.of(
                         Constraint.length(24),
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StartupTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StartupTab.java
index a781bc0ad99e..fcd43aac9df7 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StartupTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StartupTab.java
@@ -16,10 +16,16 @@
  */
 package org.apache.camel.dsl.jbang.core.commands.tui;
 
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import dev.tamboui.layout.Constraint;
@@ -275,25 +281,98 @@ class StartupTab extends AbstractTab {
                 Span.styled("█".repeat(barWidth), bandStyle));
     }
 
-    private static String label(StartupStep step) {
-        String label = step.name != null ? step.name : "";
-        if (step.description != null && !step.description.isEmpty() && 
!step.description.equals(label)) {
-            label += " " + step.description;
+    static String label(StartupStep step) {
+        String name = step.name != null ? step.name : "";
+        String description = step.description != null ? step.description : "";
+        if (name.isEmpty() || description.equals(name)) {
+            // a step with no name (Init CamelContext) is its description 
alone, without a leading space
+            return name.isEmpty() ? description : name;
         }
-        return label;
+        return description.isEmpty() ? name : name + " " + description;
     }
 
-    /** The steps in the order they started (by begin time, the outer step 
first), or the slowest first. */
+    /**
+     * The steps in the order they started, each under its own parent 
(depth-first by parent id), or the slowest first.
+     */
     static List<StartupStep> ordered(List<StartupStep> steps, boolean 
slowestFirst) {
         List<StartupStep> ordered = new ArrayList<>(steps);
         if (slowestFirst) {
             ordered.sort(Comparator.comparingLong((StartupStep st) -> 
st.duration).reversed());
+        } else if (hasIds(steps)) {
+            ordered = depthFirst(steps);
         } else if (steps.stream().anyMatch(st -> st.beginTime > 0)) {
             ordered.sort(Comparator.comparingLong((StartupStep st) -> 
st.beginTime).thenComparingInt(st -> st.level));
         }
         return ordered;
     }
 
+    /** Whether every step has its own id, so the steps can be put under their 
parents. */
+    private static boolean hasIds(List<StartupStep> steps) {
+        Set<Integer> ids = new HashSet<>();
+        for (StartupStep st : steps) {
+            if (st.id <= 0 || !ids.add(st.id)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Each step followed by its children: the recorder gives the ids in the 
order the steps begin, so ordering by id
+     * keeps the begin order even where two steps began in the same 
millisecond, and a step whose parent is not in the
+     * list is a root.
+     */
+    private static List<StartupStep> depthFirst(List<StartupStep> steps) {
+        Set<Integer> ids = new HashSet<>();
+        steps.forEach(st -> ids.add(st.id));
+        List<StartupStep> byId = new ArrayList<>(steps);
+        byId.sort(Comparator.comparingInt(st -> st.id));
+        Map<Integer, List<StartupStep>> children = new HashMap<>();
+        List<StartupStep> roots = new ArrayList<>();
+        for (StartupStep st : byId) {
+            if (st.parentId != st.id && ids.contains(st.parentId)) {
+                children.computeIfAbsent(st.parentId, k -> new 
ArrayList<>()).add(st);
+            } else {
+                roots.add(st);
+            }
+        }
+        List<StartupStep> answer = new ArrayList<>(steps.size());
+        Set<Integer> visited = new HashSet<>();
+        Deque<StartupStep> stack = new ArrayDeque<>();
+        for (StartupStep root : roots) {
+            stack.push(root);
+            while (!stack.isEmpty()) {
+                StartupStep st = stack.pop();
+                if (visited.add(st.id)) {
+                    answer.add(st);
+                    List<StartupStep> kids = children.getOrDefault(st.id, 
List.of());
+                    for (int i = kids.size() - 1; i >= 0; i--) {
+                        stack.push(kids.get(i));
+                    }
+                }
+            }
+        }
+        if (answer.size() < byId.size()) {
+            // steps in a parent loop: after the others, in id order
+            byId.stream().filter(st -> 
!visited.contains(st.id)).forEach(answer::add);
+        }
+        return answer;
+    }
+
+    /**
+     * The time the startup took: from the first step to the end of the last 
one, or (without begin times, as an older
+     * Camel has them) the longest step.
+     */
+    static long totalDuration(List<StartupStep> steps) {
+        long t0 = steps.stream().mapToLong(st -> st.beginTime).filter(b -> b > 
0).min().orElse(0);
+        if (t0 > 0) {
+            return steps.stream().filter(st -> st.beginTime > 0).mapToLong(st 
-> st.beginTime + st.duration).max()
+                    .orElse(t0)
+                   - t0;
+        }
+        return steps.stream().mapToLong(s -> s.duration).max().orElse(0);
+    }
+
     private Style colorForDuration(long duration) {
         return TuiHelper.colorForDuration(duration, minDurationColor, 
maxDurationColor);
     }
@@ -366,8 +445,8 @@ class StartupTab extends AbstractTab {
             dataLoaded = true;
 
             if (!steps.isEmpty()) {
-                totalDuration = steps.stream().mapToLong(s -> 
s.duration).max().orElse(0);
-                maxDuration = totalDuration;
+                totalDuration = totalDuration(steps);
+                maxDuration = steps.stream().mapToLong(s -> 
s.duration).max().orElse(0);
                 minDurationColor
                         = steps.stream().filter(s -> s.level > 0).mapToLong(s 
-> s.duration).min().orElse(0);
                 maxDurationColor
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TransformersTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TransformersTab.java
index abb2ad200fda..7394d1c8feec 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TransformersTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TransformersTab.java
@@ -228,7 +228,7 @@ class TransformersTab extends AbstractTableTab {
             return null;
         }
         JsonObject result = new JsonObject();
-        result.put("tab", "Data Type Transformers");
+        result.put("tab", "Transformers");
         JsonArray rows = new JsonArray();
         for (TransformerData t : transformers) {
             JsonObject row = new JsonObject();
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiToolDefinitions.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiToolDefinitions.java
index 965dc9246727..465fb7370674 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiToolDefinitions.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiToolDefinitions.java
@@ -371,11 +371,11 @@ final class TuiToolDefinitions {
                 Map.of()), args -> r.callGetDiagram(), READ_ONLY));
         tools.add(tool(toolDef(
                 "tui_get_history",
-                "Trace and history data of the History tab with all exchange 
details (body and headers with types, "
+                "Trace and history data of the Inspect tab with all exchange 
details (body and headers with types, "
                                    + "properties, variables, thread, source 
location, node labels); richer than "
                                    + "tui_get_table. The shape follows the 
tab's mode: traces, trace steps or history.",
                 Map.of("exchangeId", propDef("string",
-                        "Trace steps of this exchange; otherwise the current 
History tab view"))),
+                        "Trace steps of this exchange (the tab's view does not 
change); otherwise the current Inspect tab view"))),
                 r::callGetHistory, READ_ONLY, CORE));
         tools.add(tool(toolDef(
                 "tui_get_topology",
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiToolRegistry.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiToolRegistry.java
index 2761f41ef244..bd1277247f67 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiToolRegistry.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiToolRegistry.java
@@ -1229,14 +1229,19 @@ class TuiToolRegistry {
     }
 
     String callGetHistory(Map<String, Object> args) {
-        String exchangeId = args.get("exchangeId") instanceof String s ? s : 
null;
-        if (exchangeId != null && !exchangeId.isBlank()) {
-            facade.navigateToTab("History");
-            facade.selectTraceExchange(exchangeId);
+        String exchangeId = args.get("exchangeId") instanceof String s && 
!s.isBlank() ? s : null;
+        if (exchangeId != null) {
+            // read only: the steps of that exchange, without changing the tab 
or the view the user sees
+            JsonObject steps = facade.getTraceSteps(exchangeId);
+            if (steps == null) {
+                return "No trace steps for exchange " + exchangeId + ". 
Message tracing must be on, and the exchange "
+                       + "one of the traced ones (tui_get_history without 
exchangeId lists them).";
+            }
+            return Jsoner.serialize(steps);
         }
-        JsonObject data = facade.getTableData("History");
+        JsonObject data = facade.getTableData("Inspect");
         if (data == null) {
-            return "No history data available. Ensure the History tab has 
data.";
+            return "No history data available. Ensure the Inspect tab has 
data.";
         }
         return Jsoner.serialize(data);
     }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/overview.md
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/overview.md
index 4ed570918470..2624bb9801ae 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/overview.md
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/overview.md
@@ -91,8 +91,8 @@ Each row represents one running Camel integration:
 - **STATUS** — Current lifecycle state: `Running` (processing messages), 
`Started` (ready), or `Stopped`; `No status` when it wrote no status for 15 
seconds while its process runs (its MSG/S is then `-`)
 - **AGE** — How long the integration has been running (e.g., `2m30s`, `1h15m`)
 - **MSG/S** or **MSG/M** — Messages processed per second or per minute 
(current throughput). The rate unit can be configured in settings. This is the 
overall rate across all routes
-- **TOTAL** — Total number of exchanges (messages) processed since the 
integration started
-- **FAIL** — Number of exchanges that ended with an unhandled error
+- **TOTAL (AGO)** — Total number of exchanges (messages) processed since the 
integration started, and in brackets how long ago the last one completed (`1128 
(1s)`)
+- **FAIL (AGO)** — Number of exchanges that ended with an unhandled error, and 
in brackets how long ago the last one failed
 - **INFLIGHT** — Exchanges currently being processed right now. A consistently 
high inflight count may indicate slow downstream services
 - **SINCE-LAST** — Time since the last exchange activity, shown as up to three 
values separated by `/`: started/completed/failed. For example, `1s/3s/1m14s` 
means the last exchange started 1s ago, the last completed 3s ago, and the last 
failure was 1m14s ago. Values are omitted when there is no activity of that type
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/routes.md 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/routes.md
index 84bce008e709..4bbdefa53e28 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/routes.md
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/routes.md
@@ -10,8 +10,8 @@ they are sent to. A typical integration has multiple routes 
working together.
 - **FROM** — The endpoint that triggers this route (e.g., `timer`, `kafka`, 
`file`). This is the source of messages
 - **STATUS** — Route state: `Started` (running), `Stopped` (not running), or 
`Suspended` (paused, can be resumed)
 - **MSG/S** or **MSG/M** — Current message throughput (per second or per 
minute) for this route. Configure in Settings (F2)
-- **TOTAL** — Total exchanges processed by this route since startup
-- **FAIL** — Exchanges that ended with an unhandled error in this route
+- **TOTAL (AGO)** — Total exchanges processed by this route since startup, and 
in brackets how long ago the last one completed (`201 (13s)`)
+- **FAIL (AGO)** — Exchanges that ended with an unhandled error in this route, 
and in brackets how long ago the last one failed
 - **BODY TYPES** — Shown when steps of a route have been reached with more 
than one body type, across the messages (with the message history on, such as 
`camel run --dev`): how many steps varied, and how many of them with bodies of 
more than one kind, which need attention (`⚠ 1 mixed · 2 varied`). The 
processors of the selected route then have a BODY TYPES column too, with the 
types each step has been reached with
 - **MIN** — Fastest exchange processing time in milliseconds. This is the time 
from when the exchange entered the route until it completed
 - **MEAN** — Average exchange processing time in milliseconds. A rising MEAN 
may indicate a downstream service getting slower
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTabRenderTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTabRenderTest.java
index 87c7baaf3c91..38f9ee0b031b 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTabRenderTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTabRenderTest.java
@@ -215,8 +215,10 @@ class ErrorsTabRenderTest {
         ErrorsTab tab = new ErrorsTab(ctx);
         String rendered = renderToString(tab, 160, 20);
 
-        assertTrue(rendered.contains("No errors captured"),
-                "Should show placeholder when no errors exist");
+        // in the first column, under ID, as the empty state of the other tabs 
(it was under MESSAGE)
+        String header = rendered.lines().filter(l -> 
l.contains("EXCEPTION")).findFirst().orElseThrow();
+        String empty = rendered.lines().filter(l -> l.contains("No 
errors")).findFirst().orElseThrow();
+        assertEquals(header.indexOf("ID"), empty.indexOf("No errors"), 
rendered);
     }
 
     @Test
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTabRenderTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTabRenderTest.java
index 92d762487bc7..9a75933dcf5f 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTabRenderTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTabRenderTest.java
@@ -28,6 +28,7 @@ import dev.tamboui.style.Color;
 import dev.tamboui.terminal.Frame;
 import dev.tamboui.text.Span;
 import dev.tamboui.tui.event.KeyEvent;
+import org.apache.camel.util.json.JsonObject;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
@@ -191,6 +192,24 @@ class HistoryTabRenderTest {
         assertTrue(rendered.contains("STATUS"), "Should show STATUS header");
     }
 
+    @Test
+    void theTableDataNamesTheInspectTabAndReadingStepsKeepsTheView() {
+        traces.set(List.of(createTrace("EX-010", "route1", "Done", true, 
true)));
+        HistoryTab tab = new HistoryTab(ctx, traces, new HashMap<>());
+        TuiTestHelper.renderToString(tab, 120, 30);
+
+        JsonObject table = tab.getTableDataAsJson();
+        assertEquals("Inspect", table.getString("tab"), "the name 
tui_get_options lists");
+        assertEquals("Traces", table.getString("view"));
+
+        // tui_get_history with an exchangeId: the steps of that exchange, 
while the tab still lists the traces
+        JsonObject steps = tab.traceStepsAsJson("EX-010");
+        assertEquals("Trace Steps", steps.getString("view"));
+        assertEquals("EX-010", steps.getString("exchangeId"));
+        assertEquals(1, steps.getInteger("totalRows"));
+        assertEquals("Traces", tab.getTableDataAsJson().getString("view"));
+    }
+
     // ---- Helper methods ----
 
     private TraceEntry createTrace(String exchangeId, String routeId, String 
status, boolean first, boolean last) {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTabRenderTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTabRenderTest.java
index 64e020e845a6..a871c6e9c700 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTabRenderTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTabRenderTest.java
@@ -164,7 +164,7 @@ class LogTabRenderTest {
         info.rootLogLevel = "INFO";
         LogTab tab = new LogTab(ctx);
         String rendered = TuiTestHelper.renderToString(tab, 120, 20);
-        assertTrue(rendered.contains("[test-app] Log level:INFO"), rendered);
+        assertTrue(rendered.contains("[test-app] Log level: INFO"), rendered);
     }
 
     @Test
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/StartupTabRenderTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/StartupTabRenderTest.java
index 0c709b0b983e..76ad5347c9c7 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/StartupTabRenderTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/StartupTabRenderTest.java
@@ -97,6 +97,51 @@ class StartupTabRenderTest {
         assertEquals(steps, StartupTab.ordered(steps, false));
     }
 
+    @Test
+    void eachStepIsUnderItsOwnParentWhenTwoBeganInTheSameMillisecond() {
+        // CAMEL-26864: "seven Create Route" (19) and "hello Init Route" (22) 
began in the same millisecond, and the
+        // children of 19 were shown under 22
+        StartupTab.StartupStep routes = step(18, 0, "Init Routes", 1, 100, 20);
+        StartupTab.StartupStep create = step(19, 18, "seven Create Route", 2, 
105, 5);
+        StartupTab.StartupStep endpoint = step(20, 19, "direct:seven Get 
Endpoint", 3, 105, 1);
+        StartupTab.StartupStep log = step(21, 19, "log2 Create Log Processor", 
3, 106, 1);
+        StartupTab.StartupStep init = step(22, 18, "hello Init Route", 2, 105, 
10);
+        List<StartupTab.StartupStep> steps = List.of(endpoint, log, create, 
init, routes);
+
+        assertEquals(List.of(routes, create, endpoint, log, init), 
StartupTab.ordered(steps, false));
+    }
+
+    @Test
+    void theTotalIsFromTheFirstStepToTheEndOfTheLast() {
+        StartupTab.StartupStep init = step(1, 0, "Init CamelContext", 0, 1000, 
222);
+        StartupTab.StartupStep start = step(2, 0, "Start CamelContext", 0, 
1282, 182);
+        assertEquals(464, StartupTab.totalDuration(List.of(init, start)));
+        // without begin times (an older Camel): the longest step
+        init.beginTime = 0;
+        start.beginTime = 0;
+        assertEquals(222, StartupTab.totalDuration(List.of(init, start)));
+    }
+
+    @Test
+    void aStepWithNoNameIsItsDescription() {
+        StartupTab.StartupStep st = step("", 0, 0, 1);
+        st.description = "Init CamelContext";
+        assertEquals("Init CamelContext", StartupTab.label(st));
+        st.name = null;
+        assertEquals("Init CamelContext", StartupTab.label(st));
+        st.name = "hello";
+        st.description = "Init Route";
+        assertEquals("hello Init Route", StartupTab.label(st));
+    }
+
+    private static StartupTab.StartupStep step(
+            int id, int parentId, String name, int level, long begin, long 
duration) {
+        StartupTab.StartupStep st = step(name, level, begin, duration);
+        st.id = id;
+        st.parentId = parentId;
+        return st;
+    }
+
     private static StartupTab.StartupStep step(String name, int level, long 
begin, long duration) {
         StartupTab.StartupStep st = new StartupTab.StartupStep();
         st.name = name;

Reply via email to