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

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new e0ecb1cfe2a2 CAMEL-25426: camel-jbang - the monitor's tables keep 
names readable and cut text with an ellipsis (#27519)
e0ecb1cfe2a2 is described below

commit e0ecb1cfe2a254e7c505ac4ae3400338f91e33e3
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Oct 8 01:03:42 2026 +0200

    CAMEL-25426: camel-jbang - the monitor's tables keep names readable and cut 
text with an ellipsis (#27519)
    
    * CAMEL-25426: camel-jbang - the monitor's Overview keeps the NAME column 
on a narrow screen
    
    At 100 columns the Overview dropped the NAME column, so the apps showed by
    pid only. NAME now keeps room for a name: on a narrow screen the timings,
    the rate and the version give way first. A name that does not fit ends with
    an ellipsis (an opened project's "(project)" suffix was cut silently).
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
    
    * CAMEL-25426: camel-jbang - the monitor's Routes tables cut long text with 
an ellipsis
    
    The Routes table cut its TOTAL and FAIL columns before the closing
    parenthesis ("3 (18m30s"), and long route ids and URIs without an ellipsis,
    so a cut URI read as the whole URI. TOTAL and FAIL are now as wide as their
    longest value, the timing columns are fixed so the URI column gets the rest
    of the width, and route ids, URIs and processor ids end with an ellipsis
    where they are cut.
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
    
    * CAMEL-25426: camel-jbang - the monitor's Errors table has room for the 
message, and empty tables say so in full
    
    The Errors table gave the full exchange id 38 columns, which left the
    message 8 ("card dec" for "card declined"), and COUNT was blank for an
    error seen once. The id is now short, as on the Activity tab (the detail
    shows the full id), and COUNT shows 1.
    
    A table that is empty said so in its first column, which cut the message
    where that column is narrow ("No infligh"). These tables now say it in
    their widest column.
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
    
    * CAMEL-25426: camel-jbang - the monitor's Consumers tab shows the consumer 
type again
    
    The TYPE column was empty on every row: the consumers status names the
    class clazz, which the monitor did not read (producers too). REMOTE now uses
    the check mark the Endpoints tab uses for the same flag, and long route ids
    and URIs end with an ellipsis where they are cut.
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
    
    * CAMEL-25426: camel-jbang - the monitor's Open Project dialog is wide 
enough for a folder path
    
    The dialog was 70 columns wide, so a remembered folder showed cut at its
    start (s/davsclaus/workspace/...). It is now up to 100 columns wide.
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
    
    * CAMEL-25426: camel-jbang - the Errors tab tests expect the short exchange 
id
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
    
    ---------
    
    Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
---
 .../dsl/jbang/core/commands/tui/AbstractTab.java   | 13 +++--
 .../dsl/jbang/core/commands/tui/ActivityTab.java   |  2 +-
 .../dsl/jbang/core/commands/tui/ConsumersTab.java  |  9 ++--
 .../dsl/jbang/core/commands/tui/CveAuditTab.java   |  2 +-
 .../dsl/jbang/core/commands/tui/ErrorsTab.java     | 10 ++--
 .../jbang/core/commands/tui/FolderInputPopup.java  |  3 +-
 .../jbang/core/commands/tui/HeapHistogramTab.java  |  2 +-
 .../dsl/jbang/core/commands/tui/InflightTab.java   |  2 +-
 .../dsl/jbang/core/commands/tui/MetricsTab.java    |  2 +-
 .../dsl/jbang/core/commands/tui/OverviewTab.java   | 61 ++++++++++++++++------
 .../dsl/jbang/core/commands/tui/RoutesTab.java     | 47 ++++++++++++-----
 .../dsl/jbang/core/commands/tui/StatusParser.java  |  5 +-
 .../dsl/jbang/core/commands/tui/ThreadsTab.java    |  2 +-
 .../dsl/jbang/core/commands/tui/TuiHelper.java     | 25 +++++++++
 .../core/commands/tui/ErrorsTabRenderTest.java     | 14 ++---
 15 files changed, 144 insertions(+), 55 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AbstractTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AbstractTab.java
index 8b18bf09c37e..9f8c5b126a99 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AbstractTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AbstractTab.java
@@ -110,10 +110,17 @@ abstract class AbstractTab implements MonitorTab {
     }
 
     protected static Row emptyRow(String message, int columnCount) {
+        return emptyRow(message, columnCount, 0);
+    }
+
+    /**
+     * A row that says the table is empty, with the message in the given 
column: a table whose first column is narrow
+     * puts it in its widest column, so it is not cut ("No infligh").
+     */
+    protected static Row emptyRow(String message, int columnCount, int column) 
{
         Cell[] cells = new Cell[columnCount];
-        cells[0] = Cell.from(Span.styled(message, Style.EMPTY.dim()));
-        for (int i = 1; i < columnCount; i++) {
-            cells[i] = Cell.from("");
+        for (int i = 0; i < columnCount; i++) {
+            cells[i] = i == column ? Cell.from(Span.styled(message, 
Style.EMPTY.dim())) : Cell.from("");
         }
         return Row.from(cells);
     }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActivityTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActivityTab.java
index 59b5bbcccb9c..da3aa7c5b614 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActivityTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActivityTab.java
@@ -155,7 +155,7 @@ class ActivityTab extends AbstractTableTab {
         }
 
         if (rows.isEmpty()) {
-            rows.add(emptyRow("No activity captured", 7));
+            rows.add(emptyRow("No activity captured", 7, 6));
         }
 
         ActivityEntry selectedEntry = null;
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumersTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumersTab.java
index d3af7c3d1dc8..edcdb8744e22 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumersTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumersTab.java
@@ -78,16 +78,19 @@ class ConsumersTab extends AbstractTableTab {
                     ? hc.message
                     : (ci.uri != null ? ci.uri : "");
 
+            // the URI gets the rest of the width: the borders, ">> ", 8 gaps 
and the other columns; a long id or URI
+            // ends with an ellipsis where it is cut
+            int uriWidth = Math.max(0, area.width() - 2 - 3 - 8 - (20 + 10 + 
16 + 8 + 8 + 22 + 8 + 22));
             rows.add(Row.from(
-                    Cell.from(Span.styled(" " + (ci.id != null ? ci.id : ""), 
Style.EMPTY.fg(Theme.accent()))),
+                    Cell.from(Span.styled(" " + TuiHelper.truncate(ci.id, 19), 
Style.EMPTY.fg(Theme.accent()))),
                     Cell.from(Span.styled(statusText, statusStyle)),
                     Cell.from(type),
-                    Cell.from(ci.remote ? "x" : ""),
+                    Cell.from(ci.remote ? TuiIcons.CHECK : ""),
                     rightCell(String.valueOf(ci.inflight), 8),
                     Cell.from(schedule),
                     rightCell(ci.totalCounter != null ? 
String.valueOf(ci.totalCounter) : "", 8),
                     Cell.from(sinceLast),
-                    Cell.from(Span.styled(uri, healthDown ? Theme.error() : 
Style.EMPTY))));
+                    Cell.from(Span.styled(TuiHelper.truncate(uri, uriWidth), 
healthDown ? Theme.error() : Style.EMPTY))));
         }
 
         if (rows.isEmpty()) {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CveAuditTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CveAuditTab.java
index b161da30df73..3186fad5b0d3 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CveAuditTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CveAuditTab.java
@@ -251,7 +251,7 @@ class CveAuditTab extends AbstractTableTab {
         }
 
         if (rows.isEmpty() && dataLoaded) {
-            rows.add(emptyRow("No matching vulnerabilities", 5));
+            rows.add(emptyRow("No matching vulnerabilities", 5, 4));
         }
 
         String title = buildTitle();
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 0a4d1d7a874a..ad9478816b41 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
@@ -351,19 +351,21 @@ class ErrorsTab extends AbstractTableTab {
             String shortException = shortExceptionType(ei.exceptionType);
 
             rows.add(Row.from(
-                    Cell.from(ei.exchangeId != null ? ei.exchangeId : ""),
+                    // short, as on the Activity tab, so MESSAGE has room; the 
detail below has the full id
+                    Cell.from(ActivityTab.shortExchangeId(ei.exchangeId != 
null ? ei.exchangeId : "")),
                     Cell.from(ago),
                     Cell.from(Span.styled(ei.routeId != null ? ei.routeId : 
"", Style.EMPTY.fg(Theme.accent()))),
                     Cell.from(ei.nodeId != null ? ei.nodeId : ""),
                     Cell.from(Span.styled(handledStr, handledStyle)),
                     Cell.from(ei.repeatCount > 1
-                            ? Span.styled(Long.toString(ei.repeatCount), 
Theme.error().bold()) : Span.raw("")),
+                            ? Span.styled(Long.toString(ei.repeatCount), 
Theme.error().bold())
+                            : Span.raw(Long.toString(Math.max(1, 
ei.repeatCount)))),
                     Cell.from(shortException),
                     Cell.from(ei.exceptionMessage != null ? 
ei.exceptionMessage : "")));
         }
 
         if (rows.isEmpty()) {
-            rows.add(emptyRow("No errors captured", 8));
+            rows.add(emptyRow("No errors captured", 8, 7));
         }
 
         ErrorInfo selectedError = null;
@@ -390,7 +392,7 @@ class ErrorsTab extends AbstractTableTab {
                         Cell.from(Span.styled(sortLabel("EXCEPTION", 
"exception"), sortStyle("exception"))),
                         Cell.from(Span.styled("MESSAGE", Style.EMPTY.bold()))))
                 .widths(
-                        Constraint.length(38),
+                        Constraint.length(14),
                         Constraint.length(8),
                         Constraint.length(20),
                         Constraint.length(20),
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FolderInputPopup.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FolderInputPopup.java
index 58fad1cc8979..626386949b06 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FolderInputPopup.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FolderInputPopup.java
@@ -327,7 +327,8 @@ class FolderInputPopup {
     }
 
     private void renderInput(Frame frame, Rect area) {
-        int popupW = Math.min(70, area.width() - 4);
+        // wide enough for a folder path, so a remembered folder is not cut at 
its start
+        int popupW = Math.min(100, area.width() - 4);
         Rect popup = DialogHelper.centered(area, popupW, 
DialogHelper.INPUT_HEIGHT);
 
         frame.renderWidget(Clear.INSTANCE, popup);
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 52927e66acb9..eb066ebab35c 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
@@ -135,7 +135,7 @@ class HeapHistogramTab extends AbstractTableTab {
         }
 
         if (rows.isEmpty()) {
-            rows.add(emptyRow("No data", 4));
+            rows.add(emptyRow("No data", 4, 1));
         }
 
         long visibleInstances = 0;
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/InflightTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/InflightTab.java
index 323bc95e0029..9198259a6ace 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/InflightTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/InflightTab.java
@@ -103,7 +103,7 @@ class InflightTab extends AbstractTableTab {
         }
 
         if (rows.isEmpty()) {
-            rows.add(emptyRow("No inflight or blocked exchanges", 5));
+            rows.add(emptyRow("No inflight or blocked exchanges", 5, 2));
         }
 
         String title = " Inflight (" + sorted.size() + ") ";
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MetricsTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MetricsTab.java
index 1733228dbd60..f589615086ba 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MetricsTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MetricsTab.java
@@ -489,7 +489,7 @@ class MetricsTab extends AbstractTableTab {
         }
 
         if (rows.isEmpty()) {
-            rows.add(emptyRow("No " + filterType + " metrics", 4));
+            rows.add(emptyRow("No " + filterType + " metrics", 4, 3));
         }
 
         String title = " Metrics";
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 abd201df7753..82cfb767fafa 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
@@ -31,6 +31,7 @@ import dev.tamboui.layout.Rect;
 import dev.tamboui.style.Color;
 import dev.tamboui.style.Style;
 import dev.tamboui.terminal.Frame;
+import dev.tamboui.text.CharWidth;
 import dev.tamboui.text.Line;
 import dev.tamboui.text.Span;
 import dev.tamboui.text.Text;
@@ -326,7 +327,7 @@ class OverviewTab extends AbstractTab {
             vSplit.clearBorderPos();
         }
 
-        TableSpec spec = topMode ? buildTopModeTable(infos) : 
buildOverviewTable(infos);
+        TableSpec spec = topMode ? buildTopModeTable(infos) : 
buildOverviewTable(infos, area.width());
 
         String integrationTitle = infraCount > 0 ? " Integrations " : " 
Overview ";
         Style intBorderStyle = ctx.paneBorder(!infraFocused);
@@ -512,7 +513,7 @@ class OverviewTab extends AbstractTab {
     /**
      * Rows, header and column widths for the integrations table in the 
default overview mode.
      */
-    private TableSpec buildOverviewTable(List<IntegrationInfo> infos) {
+    private TableSpec buildOverviewTable(List<IntegrationInfo> infos, int 
areaWidth) {
         List<Row> rows = new ArrayList<>();
         int rowIndex = 0;
         boolean hasPercentiles = infos.stream().anyMatch(i -> i.p50Time >= 0);
@@ -523,6 +524,25 @@ class OverviewTab extends AbstractTab {
         // TOTAL and FAIL are as wide as the longest "count (since last)", so 
the since part is not cut
         int totalColWidth = 14;
         int failColWidth = 14;
+        for (IntegrationInfo info : infos) {
+            if (!info.phantom && !info.vanishing) {
+                int since = info.sinceLastCompleted != null ? 
CharWidth.of(info.sinceLastCompleted) + 3 : 0;
+                int failSince = info.sinceLastFailed != null ? 
CharWidth.of(info.sinceLastFailed) + 3 : 0;
+                totalColWidth = Math.max(totalColWidth, tw + since + 1);
+                failColWidth = Math.max(failColWidth, fw + failSince + 1);
+            }
+        }
+        // NAME tells the apps apart, so it is the column that stays: on a 
narrow screen the timings go first, then
+        // the rate and the version, so NAME keeps room for a name
+        int[] fixed = { 8, 16, 7, 10, 7, 8, totalColWidth, failColWidth, 20 };
+        int nameWidth = overviewNameWidth(areaWidth, fixed);
+        for (int drop : new int[] { 8, 5, 1 }) {
+            if (nameWidth >= MIN_NAME_WIDTH) {
+                break;
+            }
+            fixed[drop] = 0;
+            nameWidth = overviewNameWidth(areaWidth, fixed);
+        }
         for (IntegrationInfo info : infos) {
             boolean isEven = (rowIndex++ % 2 == 0);
             Style rowBg = isEven ? Style.EMPTY.bg(Theme.zebra()) : Style.EMPTY;
@@ -532,7 +552,7 @@ class OverviewTab extends AbstractTab {
                 String nameText = platformIcon + " " + (info.name != null ? 
info.name : "");
                 rows.add(Row.from(
                         Cell.from(Span.styled("-", Theme.muted())),
-                        Cell.from(Span.styled(nameText, Theme.info())),
+                        Cell.from(Span.styled(TuiHelper.truncate(nameText, 
nameWidth), Theme.info())),
                         Cell.from(Span.styled("", Theme.muted())),
                         Cell.from(Span.styled("", Theme.muted())),
                         Cell.from(projectStatus(info, 
System.currentTimeMillis())),
@@ -600,7 +620,7 @@ class OverviewTab extends AbstractTab {
                 if (hasDoc) {
                     nameSpans.add(Span.styled(" " + TuiIcons.README, 
Style.EMPTY));
                 }
-                Line nameLine = Line.from(nameSpans);
+                Line nameLine = TuiHelper.fitLine(nameSpans, nameWidth);
                 String throughputDisplay = info.throughput;
                 if (throughputDisplay == null || 
"0.00".equals(throughputDisplay)) {
                     LinkedList<Long> tpHist = throughputHistory.get(info.pid);
@@ -632,8 +652,6 @@ class OverviewTab extends AbstractTab {
                         ? Line.from(Span.styled(String.format("%" + fw + "d", 
info.failed), failStyle),
                                 Span.styled(" (" + info.sinceLastFailed + ")", 
Theme.muted()))
                         : Line.from(Span.styled(String.format("%" + fw + "d", 
info.failed), failStyle));
-                totalColWidth = Math.max(totalColWidth, totalCell.width() + 1);
-                failColWidth = Math.max(failColWidth, failCell.width() + 1);
 
                 rows.add(Row.from(
                         Cell.from(info.pid),
@@ -665,21 +683,34 @@ class OverviewTab extends AbstractTab {
                 Cell.from(""));
 
         Constraint[] widths = new Constraint[] {
-                Constraint.length(8),
+                Constraint.length(fixed[0]),
                 Constraint.fill(),
-                Constraint.length(16),
-                Constraint.length(7),
-                Constraint.length(10),
-                Constraint.length(7),
-                Constraint.length(8),
-                Constraint.length(totalColWidth),
-                Constraint.length(failColWidth),
-                Constraint.min(20),
+                Constraint.length(fixed[1]),
+                Constraint.length(fixed[2]),
+                Constraint.length(fixed[3]),
+                Constraint.length(fixed[4]),
+                Constraint.length(fixed[5]),
+                Constraint.length(fixed[6]),
+                Constraint.length(fixed[7]),
+                // fixed, so NAME gets the rest of the width, as 
overviewNameWidth counts it
+                Constraint.length(fixed[8]),
                 Constraint.length(0)
         };
         return new TableSpec(rows, header, widths);
     }
 
+    // the narrowest NAME before other columns give way: room for an icon and 
a name
+    static final int MIN_NAME_WIDTH = 20;
+
+    /** The width the NAME column gets beside the other columns: the table's 
borders, ">> " and 1 between columns. */
+    static int overviewNameWidth(int areaWidth, int[] fixed) {
+        int used = 2 + 3 + 10;
+        for (int w : fixed) {
+            used += w;
+        }
+        return Math.max(0, areaWidth - used);
+    }
+
     /**
      * Renders the throughput bar chart (ok/failed per second over time) with 
its y axis, time markers and the info
      * panel to its right.
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 e0f121cdc16d..1187897c2cb0 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
@@ -27,6 +27,7 @@ import dev.tamboui.layout.Layout;
 import dev.tamboui.layout.Rect;
 import dev.tamboui.style.Style;
 import dev.tamboui.terminal.Frame;
+import dev.tamboui.text.CharWidth;
 import dev.tamboui.text.Line;
 import dev.tamboui.text.Span;
 import dev.tamboui.text.Text;
@@ -564,7 +565,7 @@ class RoutesTab extends AbstractTab {
                 .split(area);
 
         // Routes table
-        Table routeTable = routeTopMode ? buildRouteTopTable(sortedRoutes) : 
buildRouteTable(sortedRoutes);
+        Table routeTable = routeTopMode ? buildRouteTopTable(sortedRoutes) : 
buildRouteTable(sortedRoutes, area.width());
 
         lastRouteTableArea = chunks.get(0);
         vSplit.setBorderPos(chunks.get(1).y());
@@ -681,7 +682,7 @@ class RoutesTab extends AbstractTab {
 
             routeRows.add(Row.from(
                     Cell.from(Span.styled(route.routeId != null ? 
route.routeId : "", Style.EMPTY.fg(Theme.accent()))),
-                    routeFromCell(route, aiDescriptions),
+                    routeFromCell(route, aiDescriptions, Integer.MAX_VALUE),
                     rightCell(route.total > 0 ? 
formatDurationMs(route.meanTime) : "", 8,
                             topTimeStyle(route.meanTime)),
                     rightCell(route.total > 0 ? 
formatDurationMs(route.maxTime) : "", 8,
@@ -767,13 +768,24 @@ class RoutesTab extends AbstractTab {
     /**
      * The routes table in the default mode (status, counters, timing).
      */
-    private Table buildRouteTable(List<RouteInfo> sortedRoutes) {
+    private Table buildRouteTable(List<RouteInfo> sortedRoutes, int areaWidth) 
{
         boolean hasPercentiles = sortedRoutes.stream().anyMatch(r -> r.p50Time 
>= 0);
 
         long maxTotal = sortedRoutes.stream().mapToLong(r -> 
r.total).max().orElse(0);
         long maxFailed = sortedRoutes.stream().mapToLong(r -> 
r.failed).max().orElse(0);
         int tw = Math.max(numWidth(maxTotal), 6);
         int fw = Math.max(numWidth(maxFailed), 6);
+        // TOTAL and FAIL are as wide as the longest "count (since last)", so 
the closing parenthesis is not cut
+        int totalColWidth = 14;
+        int failColWidth = 14;
+        for (RouteInfo route : sortedRoutes) {
+            int since = route.sinceLastCompleted != null ? 
CharWidth.of(route.sinceLastCompleted) + 3 : 0;
+            int failSince = route.sinceLastFailed != null ? 
CharWidth.of(route.sinceLastFailed) + 3 : 0;
+            totalColWidth = Math.max(totalColWidth, tw + since + 1);
+            failColWidth = Math.max(failColWidth, fw + failSince + 1);
+        }
+        // FROM gets the rest of the width: the borders, ">> ", 7 gaps and the 
other columns
+        int fromWidth = Math.max(0, areaWidth - 2 - 3 - 7 - (24 + 10 + 10 + 
totalColWidth + failColWidth + 20 + 12));
 
         List<Row> routeRows = new ArrayList<>();
         Map<String, String> aiDescriptions = aiDescriptions();
@@ -807,8 +819,8 @@ class RoutesTab extends AbstractTab {
                     : Line.from(Span.styled(String.format("%" + fw + "d", 
route.failed), failStyle));
 
             routeRows.add(Row.from(
-                    Cell.from(Span.styled(route.routeId != null ? 
route.routeId : "", Style.EMPTY.fg(Theme.accent()))),
-                    routeFromCell(route, aiDescriptions),
+                    Cell.from(Span.styled(TuiHelper.truncate(route.routeId, 
24), Style.EMPTY.fg(Theme.accent()))),
+                    routeFromCell(route, aiDescriptions, fromWidth),
                     Cell.from(Span.styled(route.state != null ? route.state : 
"", stateStyle)),
                     rightCell(formatThroughput(route.throughput), 8),
                     Cell.from(totalCell),
@@ -858,9 +870,9 @@ class RoutesTab extends AbstractTab {
                         Constraint.fill(),
                         Constraint.length(10),
                         Constraint.length(10),
-                        Constraint.length(14),
-                        Constraint.length(14),
-                        Constraint.min(20),
+                        Constraint.length(totalColWidth),
+                        Constraint.length(failColWidth),
+                        Constraint.length(20),
                         Constraint.length(12))
                 .highlightStyle(Theme.selectionBg())
                 .highlightSpacing(Table.HighlightSpacing.ALWAYS)
@@ -969,14 +981,16 @@ class RoutesTab extends AbstractTab {
      * The from or description cell. A route without a description of its own 
shows the one the AI project overview
      * suggested, marked and styled as AI-assisted so it is not taken for what 
the route says (CAMEL-25143).
      */
-    private Cell routeFromCell(RouteInfo route, Map<String, String> 
aiDescriptions) {
+    private Cell routeFromCell(RouteInfo route, Map<String, String> 
aiDescriptions, int width) {
         if (showDescription && (route.description == null || 
route.description.isBlank())) {
             String ai = route.routeId != null ? 
aiDescriptions.get(route.routeId) : null;
             if (ai != null) {
-                return Cell.from(Span.styled(IntegrationSummaryHints.MARK + 
ai, Theme.aiAssisted()));
+                return 
Cell.from(Span.styled(TuiHelper.truncate(IntegrationSummaryHints.MARK + ai, 
width),
+                        Theme.aiAssisted()));
             }
         }
-        return Cell.from(routeFromLabel(route));
+        // a long URI ends with an ellipsis where it is cut, so it does not 
read as the whole URI
+        return Cell.from(TuiHelper.truncate(routeFromLabel(route), width));
     }
 
     /** The AI-assisted descriptions of the selected integration's project, 
read once per table. */
@@ -1138,9 +1152,13 @@ class RoutesTab extends AbstractTab {
             } else {
                 routeTimingCol = "";
             }
+            // PROCESSOR gets the rest of the width: the borders, ">> ", 7 
gaps and the other columns; a long URI
+            // ends with an ellipsis where it is cut
+            int procWidth = Math.max(0, area.width() - 2 - 3 - 7 - (20 + 10 + 
8 + 6 + 8 + 20 + 12));
             rows.add(Row.from(
                     Cell.from("   route"),
-                    Cell.from(Span.styled(route.from != null ? 
TuiHelper.displayUri(route.from) : route.routeId,
+                    Cell.from(Span.styled(TuiHelper.truncate(
+                            route.from != null ? 
TuiHelper.displayUri(route.from) : route.routeId, procWidth),
                             routeStyle)),
                     rightCell(formatThroughput(route.throughput), 8),
                     rightCell(String.valueOf(route.total), 8),
@@ -1167,7 +1185,8 @@ class RoutesTab extends AbstractTab {
 
                 rows.add(Row.from(
                         Cell.from("   " + (proc.processor != null ? 
proc.processor : "")),
-                        Cell.from(Span.styled(indent + (proc.id != null ? 
proc.id : ""), nameStyle)),
+                        Cell.from(Span.styled(TuiHelper.truncate(indent + 
(proc.id != null ? proc.id : ""), procWidth),
+                                nameStyle)),
                         rightCell(formatThroughput(proc.throughput), 8),
                         rightCell(String.valueOf(proc.total), 8),
                         rightCell(String.valueOf(proc.failed), 6,
@@ -1196,7 +1215,7 @@ class RoutesTab extends AbstractTab {
                             Constraint.length(8),
                             Constraint.length(6),
                             Constraint.length(8),
-                            Constraint.min(20),
+                            Constraint.length(20),
                             Constraint.length(12))
                     
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
                             .title(" Processors [" + route.routeId + "] ")
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
index d9db4a4cc70e..4032f0b3a70e 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
@@ -404,7 +404,8 @@ final class StatusParser {
                     ci.id = cj.getString("id");
                     ci.uri = cj.getString("uri");
                     ci.state = cj.getString("state");
-                    ci.className = cj.getString("class");
+                    // the consumers console says clazz (class in older 
versions)
+                    ci.className = cj.getStringOrDefault("clazz", 
cj.getString("class"));
                     ci.scheduled = Boolean.TRUE.equals(cj.get("scheduled"));
                     ci.remote = Boolean.TRUE.equals(cj.get("remote"));
                     ci.inflight = cj.getIntegerOrDefault("inflight", 0);
@@ -478,7 +479,7 @@ final class StatusParser {
                     ProducerInfo pi = new ProducerInfo();
                     pi.uri = pj.getString("uri");
                     pi.state = pj.getString("state");
-                    pi.className = pj.getString("class");
+                    pi.className = pj.getStringOrDefault("clazz", 
pj.getString("class"));
                     pi.routeId = pj.getString("routeId");
                     pi.stepId = pj.getString("stepId");
                     pi.remote = Boolean.TRUE.equals(pj.get("remote"));
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ThreadsTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ThreadsTab.java
index b404fb19bf1a..15b93e9c7e1a 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ThreadsTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ThreadsTab.java
@@ -194,7 +194,7 @@ class ThreadsTab extends AbstractTableTab {
         }
 
         if (rows.isEmpty()) {
-            rows.add(emptyRow("No threads", 5));
+            rows.add(emptyRow("No threads", 5, 1));
         }
 
         String title = String.format(" Threads [%d/%d] peak:%d filter:%s ",
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiHelper.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiHelper.java
index 1a244a3ac33f..5b3940a11447 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiHelper.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiHelper.java
@@ -163,6 +163,31 @@ final class TuiHelper {
         }
     }
 
+    /**
+     * The spans cut to the given display width, ending with an ellipsis where 
text is cut, so a cell that is too narrow
+     * says so instead of losing its end silently.
+     */
+    static Line fitLine(List<Span> spans, int max) {
+        List<Span> answer = new ArrayList<>();
+        int used = 0;
+        for (Span span : spans) {
+            int w = CharWidth.of(span.content());
+            if (used + w <= max) {
+                answer.add(span);
+                used += w;
+            } else {
+                int room = max - used;
+                if (room > 0) {
+                    answer.add(Span.styled(
+                            CharWidth.truncateWithEllipsis(span.content(), 
room, CharWidth.TruncatePosition.END),
+                            span.style()));
+                }
+                break;
+            }
+        }
+        return Line.from(answer);
+    }
+
     static String truncate(String s, int max) {
         if (s == null) {
             return "";
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 3c42792afda3..6be4707ced8d 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
@@ -146,7 +146,7 @@ class ErrorsTabRenderTest {
         ErrorsTab tab = new ErrorsTab(ctx);
         String rendered = renderToString(tab, 160, 30);
         assertEquals(0, tab.tableState.selected());
-        assertTrue(rendered.contains("ID-001"), rendered);
+        assertTrue(rendered.contains(ActivityTab.shortExchangeId("ID-001")), 
rendered);
     }
 
     @Test
@@ -157,8 +157,8 @@ class ErrorsTabRenderTest {
         ErrorsTab tab = new ErrorsTab(ctx);
         String rendered = renderToString(tab, 160, 30);
 
-        assertTrue(rendered.contains("ID-AAA"), "Should render first error");
-        assertTrue(rendered.contains("ID-BBB"), "Should render second error");
+        assertTrue(rendered.contains(ActivityTab.shortExchangeId("ID-AAA")), 
"Should render first error");
+        assertTrue(rendered.contains(ActivityTab.shortExchangeId("ID-BBB")), 
"Should render second error");
         assertTrue(rendered.contains("IOException"), "Should render first 
exception type");
         assertTrue(rendered.contains("NullPointerException"), "Should render 
second exception type");
     }
@@ -240,14 +240,14 @@ class ErrorsTabRenderTest {
 
         // Default shows all
         String all = renderToString(tab, 160, 30);
-        assertTrue(all.contains("ID-001"), "All filter should show unhandled 
errors");
-        assertTrue(all.contains("ID-002"), "All filter should show handled 
errors");
+        assertTrue(all.contains(ActivityTab.shortExchangeId("ID-001")), "All 
filter should show unhandled errors");
+        assertTrue(all.contains(ActivityTab.shortExchangeId("ID-002")), "All 
filter should show handled errors");
 
         // Press 'f' to filter to handled=true
         tab.handleKeyEvent(KeyEvent.ofChar('f', KeyModifiers.NONE));
         String handledOnly = renderToString(tab, 160, 30);
-        assertTrue(handledOnly.contains("ID-002"), "handled filter should show 
handled error");
-        assertFalse(handledOnly.contains("ID-001"), "handled filter should 
hide unhandled error");
+        
assertTrue(handledOnly.contains(ActivityTab.shortExchangeId("ID-002")), 
"handled filter should show handled error");
+        
assertFalse(handledOnly.contains(ActivityTab.shortExchangeId("ID-001")), 
"handled filter should hide unhandled error");
     }
 
     @Test

Reply via email to