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 357ca829ba44 chore: camel-jbang - first row selected on Beans, 
Configuration, Heap Histogram and Threads, the Timeline cursor hides the axis 
label it is on, a JFR hint, and MCP waiting for a client is no error (#29477)
357ca829ba44 is described below

commit 357ca829ba44d1439aab360c284d2b5fab79a86b
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Nov 9 14:49:30 2026 +0100

    chore: camel-jbang - first row selected on Beans, Configuration, Heap 
Histogram and Threads, the Timeline cursor hides the axis label it is on, a JFR 
hint, and MCP waiting for a client is no error (#29477)
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../ROOT/pages/camel-jbang-tui-ai-agents.adoc      |  2 +-
 .../dsl/jbang/core/commands/tui/BeansTab.java      |  6 +++++
 .../dsl/jbang/core/commands/tui/CamelMonitor.java  |  5 ++--
 .../jbang/core/commands/tui/CircuitBreakerTab.java |  3 ++-
 .../jbang/core/commands/tui/ConfigurationTab.java  |  6 +++++
 .../jbang/core/commands/tui/HeapHistogramTab.java  |  6 +++++
 .../camel/dsl/jbang/core/commands/tui/JfrTab.java  | 13 +++++++++-
 .../dsl/jbang/core/commands/tui/ThreadsTab.java    |  6 +++++
 .../dsl/jbang/core/commands/tui/TimelineTab.java   | 23 +++++++++++-------
 .../commands/tui/ConfigurationTabRenderTest.java   | 16 +++++++++++++
 .../jbang/core/commands/tui/JfrTabRenderTest.java  | 28 ++++++++++++++++++++++
 .../core/commands/tui/TimelineTabRenderTest.java   | 18 ++++++++++++++
 12 files changed, 118 insertions(+), 14 deletions(-)

diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-ai-agents.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-ai-agents.adoc
index 44fd28832b43..bd067e88f971 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-ai-agents.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-ai-agents.adoc
@@ -49,7 +49,7 @@ The server is bound to `127.0.0.1` only -- it never listens 
on external interfac
 
 When MCP is active, the TUI footer shows the connection status: the port and 
the name of the connected client,
 with ● while it calls tools and ○ while it is idle (it stays connected while 
idle, as an MCP client only sends a
-request when it uses a tool), or ✗ when no client has connected yet or the 
client closed its session.
+request when it uses a tool), or _no client_ when no client has connected yet 
or the client closed its session.
 Use *F2* -> _MCP Info_ to see server details and _MCP Log_ to view the tool 
call history.
 
 == Status document resources
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/BeansTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/BeansTab.java
index 25ca8a0d6c78..426f77ec21de 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/BeansTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/BeansTab.java
@@ -65,6 +65,12 @@ class BeansTab extends AbstractTableTab {
         return sortedBeans().size();
     }
 
+    @Override
+    protected boolean selectsFirstRow() {
+        // the detail panel shows the selected row
+        return true;
+    }
+
     @Override
     public void onTabSelected() {
         String pid = ctx.selectedPid;
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
index d53884bacde7..204eb57dac71 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
@@ -2348,9 +2348,10 @@ public class CamelMonitor extends CamelCommand {
                 labelStyle = Theme.success();
                 suffixStyle = active ? Theme.success() : Theme.muted();
             } else {
-                suffix = " " + TuiIcons.CROSS;
+                // the server runs and waits for an agent: not an error
+                suffix = " no client";
                 labelStyle = Theme.muted();
-                suffixStyle = Theme.error();
+                suffixStyle = Theme.muted();
             }
             rightSpans.add(Span.styled(mcpLabel, labelStyle));
             rightSpans.add(Span.styled(suffix, suffixStyle));
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 cf5f1c612a5f..544d980ae293 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
@@ -90,7 +90,8 @@ class CircuitBreakerTab extends AbstractTableTab {
             String fallback = cb.fallbackCalls > 0 ? 
String.valueOf(cb.fallbackCalls) : "";
             String timeout = cb.timedOutCalls > 0 ? 
String.valueOf(cb.timedOutCalls) : "";
             String rate = cb.failureRate >= 0 ? String.format("%.0f%%", 
cb.failureRate) : "";
-            String inflight = String.valueOf(cb.inflight);
+            // blank when zero, as the other counts, so the ones that are not 
stand out
+            String inflight = cb.inflight > 0 ? String.valueOf(cb.inflight) : 
"";
             String sinceLast = formatSinceLast(cb.sinceLastStarted, 
cb.sinceLastSuccess, cb.sinceLastFail);
 
             rows.add(Row.from(
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConfigurationTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConfigurationTab.java
index e098ea368f89..2cbe60b01b1e 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConfigurationTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConfigurationTab.java
@@ -81,6 +81,12 @@ class ConfigurationTab extends AbstractTableTab {
         return info != null ? info.configProperties.size() : 0;
     }
 
+    @Override
+    protected boolean selectsFirstRow() {
+        // the detail panel shows the selected row
+        return true;
+    }
+
     @Override
     protected boolean handleTabKeyEvent(KeyEvent ke) {
         if (ke.isKey(KeyCode.TAB)) {
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 036fe978593b..629c81905ec2 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
@@ -67,6 +67,12 @@ class HeapHistogramTab extends AbstractTableTab {
         return sortedEntries().size();
     }
 
+    @Override
+    protected boolean selectsFirstRow() {
+        // the detail panel shows the selected row
+        return true;
+    }
+
     @Override
     public void onTabSelected() {
         String pid = ctx.selectedPid;
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/JfrTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/JfrTab.java
index 11a221df6dfb..d03e1ffb0540 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/JfrTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/JfrTab.java
@@ -330,9 +330,20 @@ class JfrTab extends AbstractTab {
             String msg = loading.get()
                     ? "  Loading..."
                     : "  Press F5 to refresh JFR snapshot data";
+            List<Line> lines = new ArrayList<>();
+            lines.add(Line.from(Span.styled(msg, LABEL)));
+            if (statusLoaded && recordings.isEmpty() && !loading.get()) {
+                // a snapshot needs a recording: say how to start one
+                lines.add(Line.from(Span.raw("")));
+                lines.add(Line
+                        .from(Span.styled("  No JFR recording is active. Run 
the integration with Java Flight Recorder (JFR)"
+                                          + " checked in its run options (F2 → 
Open Project...),",
+                                LABEL)));
+                lines.add(Line.from(Span.styled("  or start one with: jcmd " + 
ctx.selectedPid + " JFR.start", LABEL)));
+            }
             frame.renderWidget(
                     Paragraph.builder()
-                            .text(Text.from(Line.from(Span.styled(msg, 
LABEL))))
+                            .text(Text.from(lines))
                             
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
                                     .borderStyle(ctx.paneBorder(true))
                                     .title(" " + activeView.label + " 
").build())
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 376e391aaa99..dc0b3b88c9a6 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
@@ -71,6 +71,12 @@ class ThreadsTab extends AbstractTableTab {
         return sortedThreads().size();
     }
 
+    @Override
+    protected boolean selectsFirstRow() {
+        // the detail panel shows the selected row
+        return true;
+    }
+
     @Override
     public void onTabSelected() {
         String pid = ctx.selectedPid;
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineTab.java
index ac381233f9ac..641d1231a954 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineTab.java
@@ -654,15 +654,14 @@ class TimelineTab extends AbstractTableTab {
         Arrays.fill(axis, ' ');
         String start = "-" + axisTime(seconds());
         String middle = "-" + axisTime(seconds() / 2);
-        String end = "now";
-        put(axis, 0, start);
-        put(axis, columns / 2 - middle.length() / 2, middle);
-        put(axis, columns - end.length(), pausedAt >= 0 ? "" : end);
-        if (pausedAt >= 0) {
-            long ago = Math.max(0, nowSecond() - pausedAt);
-            String at = ago > 0 ? "-" + ChaosTab.shortTime(ago * 1000) : end;
-            put(axis, columns - at.length(), at);
-        }
+        long pausedAgo = pausedAt >= 0 ? Math.max(0, nowSecond() - pausedAt) : 
0;
+        String end = pausedAgo > 0 ? "-" + ChaosTab.shortTime(pausedAgo * 
1000) : "now";
+        // a label the cursor is on is left out: the time of the cursor is at 
the right of the axis
+        int from = cursor >= 0 ? cursor * width : -1;
+        int to = cursor >= 0 ? from + width : -1;
+        label(axis, 0, start, from, to);
+        label(axis, columns / 2 - middle.length() / 2, middle, from, to);
+        label(axis, columns - end.length(), end, from, to);
         if (cursor >= 0) {
             // the cursor under its column, the time of it at the right of the 
axis
             for (int i = 0; i < width; i++) {
@@ -690,6 +689,12 @@ class TimelineTab extends AbstractTableTab {
         return seconds <= 90 ? seconds + "s" : ChaosTab.shortTime(seconds * 
1000L);
     }
 
+    private static void label(char[] axis, int at, String s, int from, int to) 
{
+        if (at >= to || at + s.length() <= from) {
+            put(axis, at, s);
+        }
+    }
+
     private static void put(char[] axis, int at, String s) {
         for (int i = 0; i < s.length() && at + i < axis.length; i++) {
             if (at + i >= 0) {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ConfigurationTabRenderTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ConfigurationTabRenderTest.java
index b10edea7d037..19a6e9875089 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ConfigurationTabRenderTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ConfigurationTabRenderTest.java
@@ -65,6 +65,8 @@ class ConfigurationTabRenderTest {
     @Test
     void renderPropertyKeyInCyan() {
         addProperty("camel.main.name", "my-app", "application.properties");
+        // a second row: the first is selected, and the selection highlight 
hides its colors
+        addProperty("camel.main.shutdownTimeout", "30", 
"application.properties");
 
         ConfigurationTab tab = new ConfigurationTab(ctx);
 
@@ -77,6 +79,18 @@ class ConfigurationTabRenderTest {
                 "Property key should be rendered in CYAN");
     }
 
+    @Test
+    void theFirstPropertyIsSelectedSoItsDetailShows() {
+        addProperty("camel.main.name", "my-app", "application.properties");
+        addProperty("camel.main.shutdownTimeout", "30", 
"application.properties");
+
+        ConfigurationTab tab = new ConfigurationTab(ctx);
+        String rendered = TuiTestHelper.renderToString(tab, 120, 30);
+
+        assertEquals(0, tab.tableState.selected());
+        assertTrue(!rendered.contains("Select a property"), rendered);
+    }
+
     @Test
     void renderEmptyShowsPlaceholder() {
         ConfigurationTab tab = new ConfigurationTab(ctx);
@@ -125,6 +139,8 @@ class ConfigurationTabRenderTest {
     @Test
     void renderSecretValueInDarkGray() {
         addProperty("camel.vault.password", "xxxxxx", 
"application.properties");
+        // a row before it: the first is selected, and the selection highlight 
hides its colors
+        addProperty("camel.main.name", "my-app", "application.properties");
 
         ConfigurationTab tab = new ConfigurationTab(ctx);
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/JfrTabRenderTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/JfrTabRenderTest.java
index 96bb71294a66..c6a18163c332 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/JfrTabRenderTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/JfrTabRenderTest.java
@@ -85,6 +85,34 @@ class JfrTabRenderTest {
                 .contains("JFR runtime instrumentation is not available"));
     }
 
+    @Test
+    void withoutARecordingTheTabSaysHowToStartOne() {
+        JsonObject noRecording = new JsonObject();
+        noRecording.put("runtimeEvents", false);
+        noRecording.put("recordings", new JsonArray());
+        TestMonitorContext statusCtx = new TestMonitorContext(dataWith(info), 
noRecording);
+        statusCtx.selectedPid = "1234";
+        JfrTab tab = new JfrTab(statusCtx, Runnable::run);
+
+        tab.onTabSelected();
+
+        await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> 
assertThat(TuiTestHelper.renderToString(tab, 160, 20))
+                .contains("No JFR recording is active").contains("Java Flight 
Recorder (JFR)")
+                .contains("jcmd 1234 JFR.start"));
+    }
+
+    @Test
+    void withARecordingThereIsNoHint() {
+        TestMonitorContext statusCtx = new TestMonitorContext(dataWith(info), 
statusResponse());
+        statusCtx.selectedPid = "1234";
+        JfrTab tab = new JfrTab(statusCtx, Runnable::run);
+
+        tab.onTabSelected();
+
+        await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> 
assertThat(TuiTestHelper.renderToString(tab, 160, 20))
+                .contains("Runtime Events: Enabled").doesNotContain("No JFR 
recording is active"));
+    }
+
     @Test
     void renderFooterHints() {
         JfrTab tab = new JfrTab(ctx);
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineTabRenderTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineTabRenderTest.java
index 7a3a3cbf862c..c5b277032565 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineTabRenderTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineTabRenderTest.java
@@ -285,6 +285,24 @@ class TimelineTabRenderTest {
         assertThat(recorded.stream().filter(v -> ((Number) v).longValue() == 
2).count()).isEqualTo(10);
     }
 
+    @Test
+    void theCursorHidesTheLabelOfTheAxisItIsOn() {
+        TimelineTab tab = new TimelineTab(ctx, now::get);
+        TuiTestHelper.renderToString(tab, 160, 40);
+        // paused, the cursor is on the last second, where the axis says now
+        tab.handleKeyEvent(KeyEvent.ofChar(' '));
+        String axis = TuiTestHelper.renderToString(tab, 160, 
40).lines().filter(l -> l.contains("-60s")).findFirst()
+                .orElseThrow();
+        // no piece of a label next to the cursor; its time is at the right
+        assertThat(axis).doesNotContainPattern("[^ ▲]▲").contains("▲ now");
+        // the cursor on the start of the axis
+        for (int i = 0; i < 59; i++) {
+            tab.handleKeyEvent(KeyEvent.ofKey(KeyCode.LEFT));
+        }
+        axis = TuiTestHelper.renderToString(tab, 160, 40).lines().filter(l -> 
l.contains("-30s")).findFirst().orElseThrow();
+        assertThat(axis).doesNotContain("-60s").doesNotContainPattern("▲[^ 
▲]");
+    }
+
     @Test
     void enterShowsTheExchangesOfTheSecondAtTheCursorAndOneInDetail() {
         TimelineTab tab = new TimelineTab(ctx, now::get);

Reply via email to