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 ae27d8e5a630 chore: camel-jbang - clicks on About, Heap Histogram, the 
JFR views and the Timeline seconds, and the wheel scrolls the panel under the 
mouse (#29468)
ae27d8e5a630 is described below

commit ae27d8e5a63098d108ff747a0b517649be97e01e
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Nov 9 17:13:21 2026 +0100

    chore: camel-jbang - clicks on About, Heap Histogram, the JFR views and the 
Timeline seconds, and the wheel scrolls the panel under the mouse (#29468)
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../dsl/jbang/core/commands/tui/AboutView.java     | 58 ++++++++++++++++++----
 .../jbang/core/commands/tui/AbstractTableTab.java  | 48 +++++++++++++++++-
 .../dsl/jbang/core/commands/tui/BeansTab.java      | 20 +++++---
 .../jbang/core/commands/tui/ConfigurationTab.java  | 20 +++++---
 .../dsl/jbang/core/commands/tui/EndpointsTab.java  |  1 -
 .../dsl/jbang/core/commands/tui/ErrorsTab.java     | 49 +++++++-----------
 .../jbang/core/commands/tui/HeapHistogramTab.java  |  1 +
 .../camel/dsl/jbang/core/commands/tui/JfrTab.java  | 20 ++++++++
 .../dsl/jbang/core/commands/tui/OverviewTab.java   |  4 ++
 .../dsl/jbang/core/commands/tui/ThreadsTab.java    | 20 +++++---
 .../jbang/core/commands/tui/TimelineExchanges.java | 28 ++++++++++-
 .../dsl/jbang/core/commands/tui/TimelineTab.java   | 27 +++++++++-
 .../src/main/resources/tui/help/beans.md           |  1 +
 .../src/main/resources/tui/help/configuration.md   |  1 +
 .../src/main/resources/tui/help/errors.md          |  1 +
 .../src/main/resources/tui/help/jfr.md             |  2 +-
 .../src/main/resources/tui/help/overview.md        |  2 +-
 .../src/main/resources/tui/help/threads.md         |  1 +
 .../src/main/resources/tui/help/timeline.md        |  2 +
 .../dsl/jbang/core/commands/tui/AboutViewTest.java | 24 +++++++++
 .../commands/tui/ConfigurationTabRenderTest.java   | 43 ++++++++++++++++
 .../core/commands/tui/ErrorsTabRenderTest.java     | 45 +++++++++++++++++
 .../jbang/core/commands/tui/JfrTabRenderTest.java  | 20 ++++++++
 .../core/commands/tui/TimelineTabRenderTest.java   | 44 ++++++++++++++++
 24 files changed, 413 insertions(+), 69 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AboutView.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AboutView.java
index 4aba7b02e35f..61a3aede2c51 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AboutView.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AboutView.java
@@ -27,6 +27,7 @@ import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.function.Consumer;
 
@@ -100,6 +101,9 @@ final class AboutView {
     private int focus = -1;
     private volatile List<Target> targets = List.of();
     private volatile List<String> targetLabels = List.of();
+    // what In, Routes and Out show and where their lines are on the screen, 
for a click
+    private Columns shown;
+    private final Rect[] columnAreas = new Rect[3];
 
     /**
      * What a line of In, Routes or Out is about, for Enter: a route (opened 
on the Diagram) or an endpoint a route
@@ -357,6 +361,42 @@ final class AboutView {
         }
     }
 
+    /**
+     * A click on In, Routes or Out puts the cursor on the entry clicked: its 
name, or the line under it (its exchanges,
+     * or the route that sends to it). False when the click was on none.
+     */
+    boolean click(int x, int y) {
+        Columns c = shown;
+        if (c == null) {
+            return false;
+        }
+        List<List<Line>> lines = List.of(c.in(), c.routes(), c.out());
+        List<List<Target>> columns = List.of(c.inTargets(), c.routeTargets(), 
c.outTargets());
+        int before = 0;
+        for (int i = 0; i < columns.size(); i++) {
+            List<Target> col = columns.get(i);
+            Rect r = columnAreas[i];
+            if (r != null && r.contains(x, y)) {
+                int at = y - r.y();
+                if (at > 0 && at < col.size() && col.get(at) == null && 
col.get(at - 1) != null
+                        && !lines.get(i).get(at).rawContent().isBlank()) {
+                    at--;
+                }
+                if (at >= col.size() || col.get(at) == null) {
+                    return false;
+                }
+                cursor = before + count(col.subList(0, at));
+                return true;
+            }
+            before += count(col);
+        }
+        return false;
+    }
+
+    private static int count(List<Target> targets) {
+        return (int) targets.stream().filter(Objects::nonNull).count();
+    }
+
     /** The line the cursor is on, or null. */
     Target selected() {
         List<Target> t = targets;
@@ -516,14 +556,13 @@ final class AboutView {
         List<Rect> cols = List.of(new Rect(area.x(), area.y(), w0, 
area.height()),
                 new Rect(area.x() + w0 + spacing, area.y(), w1, area.height()),
                 new Rect(area.x() + w0 + w1 + 2 * spacing, area.y(), w2, 
area.height()));
-        
frame.renderWidget(Paragraph.builder().text(Text.from(columns.in())).block(block("
 In ", padding, focus == 0)).build(),
-                cols.get(0));
-        frame.renderWidget(
-                
Paragraph.builder().text(Text.from(columns.routes())).block(block(" Routes ", 
padding, focus == 1)).build(),
-                cols.get(1));
-        frame.renderWidget(
-                
Paragraph.builder().text(Text.from(columns.out())).block(block(" Out ", 
padding, focus == 2)).build(),
-                cols.get(2));
+        List<List<Line>> texts = List.of(columns.in(), columns.routes(), 
columns.out());
+        String[] titles = { " In ", " Routes ", " Out " };
+        for (int i = 0; i < titles.length; i++) {
+            Block b = block(titles[i], padding, focus == i);
+            columnAreas[i] = b.inner(cols.get(i));
+            
frame.renderWidget(Paragraph.builder().text(Text.from(texts.get(i))).block(b).build(),
 cols.get(i));
+        }
     }
 
     /** What the integration is, and its purpose: the comment at the top of 
its main file. */
@@ -573,12 +612,13 @@ final class AboutView {
         }
         targets = all;
         targetLabels = labels;
+        shown = c;
         if (cursor >= all.size()) {
             cursor = all.size() - 1;
         }
         focus = -1;
         for (int i = 0, before = 0; i < columns.size() && cursor >= 0; i++) {
-            int n = (int) columns.get(i).stream().filter(t -> t != 
null).count();
+            int n = count(columns.get(i));
             if (cursor < before + n) {
                 focus = i;
                 break;
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AbstractTableTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AbstractTableTab.java
index b839273fadbd..ef93a08b84fc 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AbstractTableTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AbstractTableTab.java
@@ -25,6 +25,7 @@ import dev.tamboui.text.Span;
 import dev.tamboui.tui.event.KeyCode;
 import dev.tamboui.tui.event.KeyEvent;
 import dev.tamboui.tui.event.MouseEvent;
+import dev.tamboui.tui.event.MouseEventKind;
 import dev.tamboui.widgets.scrollbar.ScrollbarState;
 import dev.tamboui.widgets.table.Table;
 import dev.tamboui.widgets.table.TableState;
@@ -36,6 +37,11 @@ abstract class AbstractTableTab extends AbstractTab {
     protected final TableState tableState = new TableState();
     protected final ScrollbarState tableScrollState = new ScrollbarState();
     protected Rect lastTableArea;
+    // the detail panel of the selected row, for the mouse: null when the tab 
has none
+    protected Rect lastDetailArea;
+
+    // the lines a turn of the mouse wheel scrolls a detail panel
+    protected static final int WHEEL_LINES = 3;
 
     private final String[] sortColumns;
     protected String sort;
@@ -113,9 +119,49 @@ abstract class AbstractTableTab extends AbstractTab {
         tableState.selectNext(getRowCount());
     }
 
+    /**
+     * A click on a row selects it. On a tab with a detail panel ({@link 
#lastDetailArea}), the panel the mouse clicks
+     * or scrolls gets the keys, and the wheel scrolls the panel under the 
mouse: the rows of the table, the lines of
+     * the detail.
+     */
     @Override
     public boolean handleMouseEvent(MouseEvent me, Rect area) {
-        return handleTableClick(me, lastTableArea, tableState, getRowCount());
+        boolean up = me.kind() == MouseEventKind.SCROLL_UP;
+        boolean wheel = up || me.kind() == MouseEventKind.SCROLL_DOWN;
+        if (lastDetailArea != null && lastDetailArea.contains(me.x(), me.y()) 
&& (wheel || me.isClick())) {
+            focusDetail(true);
+            if (wheel) {
+                scrollDetail(up ? -WHEEL_LINES : WHEEL_LINES);
+            }
+            return true;
+        }
+        if (lastDetailArea != null && wheel && lastTableArea != null && 
lastTableArea.contains(me.x(), me.y())) {
+            focusDetail(false);
+            if (up) {
+                navigateUp();
+            } else {
+                navigateDown();
+            }
+            return true;
+        }
+        if (handleTableClick(me, lastTableArea, tableState, getRowCount())) {
+            focusDetail(false);
+            rowClicked();
+            return true;
+        }
+        return false;
+    }
+
+    /** The mouse gave the keys to the detail panel, or back to the table; a 
tab where Tab moves the keys keeps it. */
+    protected void focusDetail(boolean focused) {
+    }
+
+    /** The wheel over the detail panel: scrolls it by the lines, up when 
negative. */
+    protected void scrollDetail(int lines) {
+    }
+
+    /** A click selected a row: the detail of another row starts at its top. */
+    protected void rowClicked() {
     }
 
     @Override
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 426f77ec21de..b1589490e721 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
@@ -31,7 +31,6 @@ import dev.tamboui.text.Span;
 import dev.tamboui.text.Text;
 import dev.tamboui.tui.event.KeyCode;
 import dev.tamboui.tui.event.KeyEvent;
-import dev.tamboui.tui.event.MouseEvent;
 import dev.tamboui.widgets.block.Block;
 import dev.tamboui.widgets.block.BorderType;
 import dev.tamboui.widgets.block.Borders;
@@ -163,12 +162,18 @@ class BeansTab extends AbstractTableTab {
     }
 
     @Override
-    public boolean handleMouseEvent(MouseEvent me, Rect area) {
-        boolean handled = super.handleMouseEvent(me, area);
-        if (handled) {
-            detailScroll = 0;
-        }
-        return handled;
+    protected void focusDetail(boolean focused) {
+        detailFocused = focused;
+    }
+
+    @Override
+    protected void scrollDetail(int lines) {
+        detailScroll = Math.max(0, detailScroll + lines);
+    }
+
+    @Override
+    protected void rowClicked() {
+        detailScroll = 0;
     }
 
     @Override
@@ -237,6 +242,7 @@ class BeansTab extends AbstractTableTab {
     }
 
     private void renderDetail(Frame frame, Rect area, List<BeanData> visible) {
+        lastDetailArea = area;
         Style detailBorderStyle = ctx.paneBorder(detailFocused);
         Style detailTitleStyle = detailFocused ? Theme.title() : 
Style.EMPTY.fg(Theme.accent());
 
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 2cbe60b01b1e..444218243f6d 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
@@ -33,7 +33,6 @@ import dev.tamboui.text.Span;
 import dev.tamboui.text.Text;
 import dev.tamboui.tui.event.KeyCode;
 import dev.tamboui.tui.event.KeyEvent;
-import dev.tamboui.tui.event.MouseEvent;
 import dev.tamboui.widgets.block.Block;
 import dev.tamboui.widgets.block.BorderType;
 import dev.tamboui.widgets.block.Borders;
@@ -129,12 +128,18 @@ class ConfigurationTab extends AbstractTableTab {
     }
 
     @Override
-    public boolean handleMouseEvent(MouseEvent me, Rect area) {
-        boolean handled = super.handleMouseEvent(me, area);
-        if (handled) {
-            detailScroll = 0;
-        }
-        return handled;
+    protected void focusDetail(boolean focused) {
+        detailFocused = focused;
+    }
+
+    @Override
+    protected void scrollDetail(int lines) {
+        detailScroll = Math.max(0, detailScroll + lines);
+    }
+
+    @Override
+    protected void rowClicked() {
+        detailScroll = 0;
     }
 
     @Override
@@ -232,6 +237,7 @@ class ConfigurationTab extends AbstractTableTab {
     }
 
     private void renderDetail(Frame frame, Rect area, List<ConfigProperty> 
props) {
+        lastDetailArea = area;
         Style detailBorderStyle = ctx.paneBorder(detailFocused);
         Style detailTitleStyle = detailFocused ? Theme.title() : 
Style.EMPTY.fg(Theme.accent());
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java
index 71672ff5813c..a1d28ce58da3 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java
@@ -80,7 +80,6 @@ class EndpointsTab extends AbstractTableTab {
     private int panelMode = PANEL_CHART;
     private int detailScroll;
     private boolean detailFocused;
-    private Rect lastDetailArea;
     private int chartPanelHeight = 16;
     private final DragSplit vSplit = new DragSplit();
     private int flowPanelWidth = 38;
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 4a6e16d1b2c3..4386fcfa9304 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
@@ -37,7 +37,6 @@ import dev.tamboui.text.Text;
 import dev.tamboui.tui.event.KeyCode;
 import dev.tamboui.tui.event.KeyEvent;
 import dev.tamboui.tui.event.MouseEvent;
-import dev.tamboui.tui.event.MouseEventKind;
 import dev.tamboui.widgets.block.Block;
 import dev.tamboui.widgets.block.BorderType;
 import dev.tamboui.widgets.block.Borders;
@@ -66,8 +65,6 @@ class ErrorsTab extends AbstractTableTab {
     private boolean showHeaders = true;
     private boolean showBody = true;
 
-    private static final int MOUSE_SCROLL_LINES = 3;
-
     private static final int INFO_NARROW = 0;
     private static final int INFO_WIDE = 1;
     private static final int INFO_FULL = 2;
@@ -269,41 +266,30 @@ class ErrorsTab extends AbstractTableTab {
     @Override
     public boolean handleMouseEvent(MouseEvent me, Rect area) {
         if (!diagram.isShowDiagram()) {
-            if (handleTableClick(me, lastTableArea, tableState, 
filteredSize())) {
-                detailScroll = 0;
-                return true;
-            }
+            // the wheel scrolls the errors over the table, the detail over 
the detail
+            return super.handleMouseEvent(me, area);
         }
-        if (diagram.isShowDiagram()) {
-            if (diagram.handleMouseScroll(me)) {
-                return true;
-            }
-            if (me.isClick()) {
-                if (diagram.isHistoryMode() && 
diagram.isHistoryTopologyMode()) {
-                    int clicked = diagram.handleNodeClick(me);
-                    if (clicked >= 0) {
-                        return true;
-                    }
-                } else {
-                    int clicked = diagram.handleEipNodeClick(me);
-                    if (clicked >= 0) {
-                        return true;
-                    }
-                }
-            }
-            return false;
-        }
-        if (me.kind() == MouseEventKind.SCROLL_UP) {
-            detailScroll = Math.max(0, detailScroll - MOUSE_SCROLL_LINES);
+        if (diagram.handleMouseScroll(me)) {
             return true;
         }
-        if (me.kind() == MouseEventKind.SCROLL_DOWN) {
-            detailScroll += MOUSE_SCROLL_LINES;
-            return true;
+        if (me.isClick()) {
+            int clicked = diagram.isHistoryMode() && 
diagram.isHistoryTopologyMode()
+                    ? diagram.handleNodeClick(me) : 
diagram.handleEipNodeClick(me);
+            return clicked >= 0;
         }
         return false;
     }
 
+    @Override
+    protected void scrollDetail(int lines) {
+        detailScroll = Math.max(0, detailScroll + lines);
+    }
+
+    @Override
+    protected void rowClicked() {
+        detailScroll = 0;
+    }
+
     @Override
     public void navigateUp() {
         detailScroll = 0;
@@ -419,6 +405,7 @@ class ErrorsTab extends AbstractTableTab {
                 .build();
 
         lastTableArea = chunks.get(0);
+        lastDetailArea = showDetail ? chunks.get(1) : null;
         frame.renderStatefulWidget(table, chunks.get(0), tableState);
         renderScrollbar(frame, table, filteredSize());
 
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 629c81905ec2..4a2acf52d232 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
@@ -174,6 +174,7 @@ class HeapHistogramTab extends AbstractTableTab {
                         .title(title).build())
                 .build();
 
+        lastTableArea = area;
         frame.renderStatefulWidget(table, area, tableState);
     }
 
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 d03e1ffb0540..93810fd2a9e9 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
@@ -93,6 +93,9 @@ class JfrTab extends AbstractTab {
     private final TableState processorTableState = new TableState();
     private final ScrollbarState processorScrollState = new ScrollbarState();
     private Rect lastTableArea;
+    // where the names of the views are on the screen, for a click: the row, 
and the columns of each name
+    private int viewsRow = -1;
+    private final int[] viewColumns = new int[View.values().length];
     private int topPanelHeight = -1;
 
     // sort state per view
@@ -228,6 +231,16 @@ class JfrTab extends AbstractTab {
 
     @Override
     public boolean handleMouseEvent(MouseEvent me, Rect area) {
+        if (me.isClick() && me.y() == viewsRow) {
+            // a click on the name of a view shows it, as Space does
+            View[] views = View.values();
+            for (int i = 0; i < views.length; i++) {
+                if (me.x() >= viewColumns[i] && me.x() < viewColumns[i] + 
views[i].label.length()) {
+                    switchView(views[i]);
+                    return true;
+                }
+            }
+        }
         return handleTableClick(me, lastTableArea, activeTableState(), 
getRowCount());
     }
 
@@ -258,6 +271,7 @@ class JfrTab extends AbstractTab {
     public void render(Frame frame, Rect area) {
         IntegrationInfo info = ctx.findSelectedIntegration();
         if (info == null) {
+            viewsRow = -1;
             renderNoSelection(frame, area);
             return;
         }
@@ -304,10 +318,16 @@ class JfrTab extends AbstractTab {
         lines.add(Line.from(line1));
 
         List<Span> line2 = new ArrayList<>();
+        // the second line inside the border
+        viewsRow = area.y() + 2;
+        int x = area.x() + 1;
         for (View v : View.values()) {
             if (v.ordinal() > 0) {
                 line2.add(Span.styled(" | ", LABEL));
+                x += 3;
             }
+            viewColumns[v.ordinal()] = x;
+            x += v.label.length();
             boolean active = v == activeView;
             line2.add(Span.styled(v.label, active ? VALUE : LABEL));
         }
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 2774d4fd5e72..8d8e42995e13 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
@@ -289,6 +289,10 @@ class OverviewTab extends AbstractTab {
 
     @Override
     public boolean handleMouseEvent(MouseEvent me, Rect area) {
+        if (showsAbout()) {
+            // About has no table: a click puts its cursor on an entry of In, 
Routes or Out, the wheel moves it
+            return me.isClick() && about.click(me.x(), me.y());
+        }
         if (vSplit.handleMouse(me, me.y())) {
             if (vSplit.isDragging() && me.kind() == MouseEventKind.DRAG) {
                 bottomPanelHeight = Math.max(5, Math.min(area.y() + 
area.height() - me.y(), area.height() - 5));
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 dc0b3b88c9a6..a3c114d2ce40 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
@@ -31,7 +31,6 @@ import dev.tamboui.text.Span;
 import dev.tamboui.text.Text;
 import dev.tamboui.tui.event.KeyCode;
 import dev.tamboui.tui.event.KeyEvent;
-import dev.tamboui.tui.event.MouseEvent;
 import dev.tamboui.widgets.block.Block;
 import dev.tamboui.widgets.block.BorderType;
 import dev.tamboui.widgets.block.Borders;
@@ -125,12 +124,18 @@ class ThreadsTab extends AbstractTableTab {
     }
 
     @Override
-    public boolean handleMouseEvent(MouseEvent me, Rect area) {
-        boolean handled = super.handleMouseEvent(me, area);
-        if (handled) {
-            traceScroll = 0;
-        }
-        return handled;
+    protected void focusDetail(boolean focused) {
+        detailFocused = focused;
+    }
+
+    @Override
+    protected void scrollDetail(int lines) {
+        traceScroll = Math.max(0, traceScroll + lines);
+    }
+
+    @Override
+    protected void rowClicked() {
+        traceScroll = 0;
     }
 
     @Override
@@ -236,6 +241,7 @@ class ThreadsTab extends AbstractTableTab {
     }
 
     private void renderTrace(Frame frame, Rect area, List<ThreadData> visible) 
{
+        lastDetailArea = area;
         Style detailBorderStyle = detailFocused ? 
Style.EMPTY.fg(Theme.accent()) : Theme.muted();
         Style detailTitleStyle = detailFocused ? Theme.title() : 
Style.EMPTY.fg(Theme.accent());
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineExchanges.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineExchanges.java
index 63640fc969fc..22ffe20e9c93 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineExchanges.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineExchanges.java
@@ -91,6 +91,31 @@ final class TimelineExchanges {
         selected = Math.max(0, Math.min(Math.max(0, count - 1), selected + 
delta));
     }
 
+    /**
+     * A click on a line of the list selects the exchange on it; true when it 
was on one.
+     *
+     * @param line  the line clicked, from the top of the list
+     * @param count the exchanges kept
+     * @param rows  the lines the list has
+     */
+    boolean click(int line, int count, int rows) {
+        if (!open || detail || line < 0 || line >= rows) {
+            return false;
+        }
+        int i = first(count, rows) + line;
+        if (i >= count) {
+            return false;
+        }
+        selected = i;
+        return true;
+    }
+
+    /** The first exchange the list shows: the list scrolls to keep the 
selected one in view. */
+    private int first(int count, int rows) {
+        int sel = Math.min(selected, count - 1);
+        return Math.max(0, Math.min(sel - rows + 1, count - rows));
+    }
+
     Timeline.Kept selected(List<Timeline.Kept> kept) {
         return kept.isEmpty() ? null : kept.get(Math.min(selected, kept.size() 
- 1));
     }
@@ -114,8 +139,7 @@ final class TimelineExchanges {
             return detailLines(selected(kept), rows);
         }
         int sel = Math.min(selected, kept.size() - 1);
-        int first = Math.max(0, Math.min(sel - rows + 1, kept.size() - rows));
-        for (int i = first; i < kept.size() && lines.size() < rows; i++) {
+        for (int i = first(kept.size(), rows); i < kept.size() && lines.size() 
< rows; i++) {
             lines.add(row(kept.get(i), i == sel));
         }
         return lines;
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 641d1231a954..6afbb89d9f13 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
@@ -84,6 +84,9 @@ class TimelineTab extends AbstractTableTab {
     private int routeTop = -1;
     private int routePitch = 1;
     private int routeStrip = 1;
+    // the column of the screen the seconds start at, and the lines of the 
exchanges of a second, for the clicks
+    private int stripLeft = -1;
+    private Rect drillArea;
     private final TimelineExchanges drill = new TimelineExchanges();
 
     TimelineTab(MonitorContext ctx) {
@@ -296,7 +299,13 @@ class TimelineTab extends AbstractTableTab {
 
     @Override
     public boolean handleMouseEvent(MouseEvent me, Rect area) {
-        if (routeTop < 0 || !me.isClick() || lastTableArea == null || 
!lastTableArea.contains(me.x(), me.y())) {
+        if (!me.isClick()) {
+            return false;
+        }
+        if (drill.isOpen() && drillArea != null && drillArea.contains(me.x(), 
me.y())) {
+            return drill.click(me.y() - drillArea.y(), keptAtCursor().size(), 
drillArea.height());
+        }
+        if (routeTop < 0 || lastTableArea == null || 
!lastTableArea.contains(me.x(), me.y())) {
             return false;
         }
         // a click on a strip of a route selects it, not on the blank line 
between two routes
@@ -304,6 +313,14 @@ class TimelineTab extends AbstractTableTab {
         int i = dy / routePitch;
         if (dy >= 0 && dy % routePitch < routeStrip && i < getRowCount()) {
             tableState.select(i);
+            // paused, the cursor goes to the second clicked, and the 
exchanges open show that second
+            int column = me.x() >= stripLeft ? (me.x() - stripLeft) / width : 
-1;
+            if (pausedAt >= 0 && column >= 0 && column < columnCount() && 
column != cursor) {
+                cursor = column;
+                if (drill.isOpen()) {
+                    drill.open(cursorFrom(), cursorTo());
+                }
+            }
             return true;
         }
         return false;
@@ -423,13 +440,19 @@ class TimelineTab extends AbstractTableTab {
         routePitch = room.strip() + (room.spaced() ? 1 : 0);
         routeStrip = room.strip();
         lastTableArea = new Rect(top.x(), routeTop, top.width(), Math.max(1, 
routeHeight));
+        stripLeft = top.x() + 1 + gap + label;
+        drillArea = null;
         Padding padding = room.roomy() ? Padding.uniform(1) : Padding.NONE;
         frame.renderWidget(
                 
Paragraph.builder().text(Text.from(lines)).block(block(title(info), padding, 
!drill.isOpen())).build(), top);
 
         if (drill.isOpen()) {
             List<Timeline.Kept> kept = keptAtCursor();
-            
frame.renderWidget(Paragraph.builder().text(Text.from(drill.lines(kept, 
detailHeight - 2 - 2 * gap)))
+            Rect d = rows.get(2);
+            drillArea = new Rect(
+                    d.x() + 1 + gap, d.y() + 1 + gap, Math.max(0, d.width() - 
2 - 2 * gap),
+                    Math.max(0, detailHeight - 2 - 2 * gap));
+            
frame.renderWidget(Paragraph.builder().text(Text.from(drill.lines(kept, 
drillArea.height())))
                     .block(block(drill.title(kept, ctx.timeline.done(info.pid, 
drill.from(), drill.to())), padding, true))
                     .build(),
                     rows.get(2));
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/beans.md 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/beans.md
index 13df2295d85b..b1339720846c 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/beans.md
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/beans.md
@@ -38,6 +38,7 @@ Press `f` to cycle through filter modes:
 ## Keys
 
 - `Tab` — switch focus between table and detail panel
+- Mouse — a click or the wheel gives the panel under the mouse the focus; the 
wheel scrolls it
 - `Up/Down` — navigate in focused panel
 - `PgUp/PgDn` — page in focused panel
 - `s` — cycle sort column
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/configuration.md
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/configuration.md
index 7f833cce4951..b21d2556fc32 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/configuration.md
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/configuration.md
@@ -39,6 +39,7 @@ detail panel shows:
 - `Up/Down` — select property
 - `Enter` — view property detail / documentation
 - `Tab` — switch focus between the table and the detail panel
+- Mouse — a click or the wheel gives the panel under the mouse the focus; the 
wheel scrolls it
 - `PgUp/PgDn` — scroll the detail panel
 - `s` — cycle sort column
 - `S` — reverse sort order
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/errors.md 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/errors.md
index aa22d1fdf428..160d865a4fa4 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/errors.md
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/errors.md
@@ -128,3 +128,4 @@ Press `Esc` to navigate back one route in drill-down mode.
 - `S` — reverse sort order
 - `PgUp/PgDn` — scroll the detail panel
 - `Home/End` — jump to the top/end of the detail panel
+- Mouse — a click selects an error; the wheel scrolls the errors over the 
table and the detail over the detail panel
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/jfr.md 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/jfr.md
index 17a5a38a303d..6e925adece3b 100644
--- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/jfr.md
+++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/jfr.md
@@ -18,7 +18,7 @@ data is aggregated into five views:
 ## Controls
 
 - `F5` — refresh (take new JFR snapshot)
-- `Space` — cycle view
+- `Space` — cycle view, or click the name of a view
 - `s` / `S` — cycle sort column / reverse sort direction
 - `Esc` — back
 
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 8dcd6594f3c9..95bbac06a9b2 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
@@ -43,7 +43,7 @@ happened to it, with no AI:
 
 Keys of About:
 
-- **Up/Down** — Select a line of In, Routes or Out
+- **Up/Down** — Select a line of In, Routes or Out, or click it
 - **Enter** — Open the selected line: a route drilled down on the
   Diagram, an endpoint a route sends to with its details on the Endpoint
   tab
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/threads.md 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/threads.md
index cccdc8f33859..260aa052136f 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/threads.md
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/threads.md
@@ -66,6 +66,7 @@ watch BLOCKED/WAITED counters change over time without manual 
action.
 - `Up/Down` — select thread (detail updates automatically)
 - `PgUp/PgDn` — scroll stack trace detail
 - `Tab` — switch focus between the table and the stack trace
+- Mouse — a click or the wheel gives the panel under the mouse the focus; the 
wheel scrolls it
 - `s` — cycle sort column
 - `S` — reverse sort order
 - `f` — toggle filter (camel / all)
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/timeline.md
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/timeline.md
index 59ce27f2c213..2bd6c270c465 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/timeline.md
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/timeline.md
@@ -92,6 +92,8 @@ were done then, of which these were kept.
   the window moves
 - **Shift+Left/Right** — While paused, go back and forth in the 5
   minutes kept
+- **Click** — Selects the route of a strip; paused, the cursor goes to the
+  second clicked. A click on an exchange of the list selects it
 - **Enter** — The exchanges of the second at the cursor (it pauses
   first), or the selected exchange in detail
 - **Esc** — Close the exchange, then the list, then follow now again
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/AboutViewTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/AboutViewTest.java
index a2b942acd9f6..e70c554163ad 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/AboutViewTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/AboutViewTest.java
@@ -379,5 +379,29 @@ class AboutViewTest {
         assertThat(about.selected()).isEqualTo(new AboutView.Target("orders", 
"http:localhost:8765/ship"));
         assertThat(about.handleKeyEvent(KeyEvent.ofKey(KeyCode.ENTER), 
info)).isTrue();
         assertThat(opened).containsExactly(new AboutView.Target("orders", 
"http:localhost:8765/ship"));
+
+        // a click puts the cursor on an entry: a route, an endpoint by the 
line under it, an entry of In
+        List<String> screen = render(about, info).lines().toList();
+        assertThat(click(about, screen, "↳ stock")).isTrue();
+        assertThat(about.selected()).isEqualTo(new AboutView.Target("stock", 
null));
+        assertThat(click(about, screen, "timer:orders")).isTrue();
+        assertThat(about.selectionContext().selectedIndex()).isZero();
+        assertThat(click(about, screen, "from orders")).isTrue();
+        assertThat(about.selected()).isEqualTo(new AboutView.Target("orders", 
"http:localhost:8765/ship"));
+        // the head and What happened are no entries
+        assertThat(click(about, screen, "Every order is checked")).isFalse();
+        assertThat(click(about, screen, "What happened")).isFalse();
+        assertThat(about.selected()).isEqualTo(new AboutView.Target("orders", 
"http:localhost:8765/ship"));
+    }
+
+    /** Clicks on the first line that has the text, on its second character. */
+    private static boolean click(AboutView about, List<String> screen, String 
text) {
+        for (int y = 0; y < screen.size(); y++) {
+            int x = screen.get(y).indexOf(text);
+            if (x >= 0) {
+                return about.click(x + 1, y);
+            }
+        }
+        throw new AssertionError(text + " is not on the screen");
     }
 }
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 19a6e9875089..e579279daf72 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
@@ -16,6 +16,7 @@
  */
 package org.apache.camel.dsl.jbang.core.commands.tui;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -23,6 +24,9 @@ import dev.tamboui.buffer.Buffer;
 import dev.tamboui.layout.Rect;
 import dev.tamboui.style.Color;
 import dev.tamboui.terminal.Frame;
+import dev.tamboui.text.Span;
+import dev.tamboui.tui.event.MouseButton;
+import dev.tamboui.tui.event.MouseEvent;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
@@ -181,6 +185,45 @@ class ConfigurationTabRenderTest {
         assertEquals("", ConfigurationTab.sourceOf(prop));
     }
 
+    @Test
+    void thePanelTheMouseClicksOrScrollsGetsTheKeys() {
+        addProperty("camel.main.name", "my-app", "application.properties");
+        addProperty("camel.main.shutdownTimeout", "30", 
"application.properties");
+        addProperty("camel.main.tracing", "false", "application.properties");
+        ConfigurationTab tab = new ConfigurationTab(ctx);
+        Rect area = new Rect(0, 0, 120, 30);
+        String rendered = TuiTestHelper.renderToString(tab, 120, 30);
+        int detail = 
rendered.lines().toList().indexOf(rendered.lines().filter(l -> 
l.contains("Property Detail"))
+                .findFirst().orElseThrow());
+        int timeout = 
rendered.lines().toList().indexOf(rendered.lines().filter(l -> 
l.contains("shutdownTimeout"))
+                .findFirst().orElseThrow());
+
+        // a click on a row selects it, and the keys stay with the table
+        tab.handleMouseEvent(MouseEvent.press(MouseButton.LEFT, 20, timeout), 
area);
+        assertEquals(1, tab.tableState.selected());
+        assertTrue(footer(tab).contains("detail"), footer(tab));
+
+        // the wheel over the detail gives it the keys, and leaves the row 
selected
+        tab.handleMouseEvent(MouseEvent.scrollDown(20, detail + 2), area);
+        assertEquals(1, tab.tableState.selected());
+        assertTrue(footer(tab).contains("table"), footer(tab));
+
+        // the wheel over the table selects the next row, and gives the keys 
back to the table
+        tab.handleMouseEvent(MouseEvent.scrollDown(20, timeout), area);
+        assertEquals(2, tab.tableState.selected());
+        assertTrue(footer(tab).contains("detail"), footer(tab));
+
+        // a click on the detail gives it the keys
+        tab.handleMouseEvent(MouseEvent.press(MouseButton.LEFT, 20, detail + 
2), area);
+        assertTrue(footer(tab).contains("table"), footer(tab));
+    }
+
+    private static String footer(ConfigurationTab tab) {
+        List<Span> spans = new ArrayList<>();
+        tab.renderFooter(spans);
+        return spans.stream().map(Span::content).reduce("", String::concat);
+    }
+
     private ConfigurationTab.ConfigProperty addProperty(String key, String 
value, String source) {
         ConfigurationTab.ConfigProperty prop = new 
ConfigurationTab.ConfigProperty();
         prop.key = key;
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 f102251cebe1..924d60898a9d 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
@@ -28,6 +28,8 @@ import dev.tamboui.terminal.Frame;
 import dev.tamboui.text.Span;
 import dev.tamboui.tui.event.KeyEvent;
 import dev.tamboui.tui.event.KeyModifiers;
+import dev.tamboui.tui.event.MouseButton;
+import dev.tamboui.tui.event.MouseEvent;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
@@ -150,6 +152,49 @@ class ErrorsTabRenderTest {
         assertTrue(rendered.contains(ErrorsTab.shortExchangeId("ID-001")), 
rendered);
     }
 
+    @Test
+    void theWheelScrollsThePanelUnderTheMouse() {
+        for (int i = 1; i <= 3; i++) {
+            addError("ID-00" + i, "route1", "to1", "java.io.IOException", 
"fail " + i, false);
+        }
+        StringBuilder trace = new StringBuilder("java.io.IOException: fail 1");
+        for (int i = 0; i < 60; i++) {
+            trace.append("\n\tat 
org.example.Frame").append(i).append(".call(Frame.java:").append(i).append(')');
+        }
+        info.errors.get(0).stackTrace = trace.toString();
+        ErrorsTab tab = new ErrorsTab(ctx);
+        Rect area = new Rect(0, 0, 160, 40);
+        String before = renderToString(tab, 160, 40);
+        int detail = row(before, " Detail ");
+        assertTrue(detail > 0, before);
+
+        // over the detail: its lines scroll, the error stays selected
+        tab.handleMouseEvent(MouseEvent.scrollDown(40, detail + 5), area);
+        tab.handleMouseEvent(MouseEvent.scrollDown(40, detail + 5), area);
+        String scrolled = renderToString(tab, 160, 40);
+        assertEquals(0, tab.tableState.selected());
+        assertFalse(scrolled.contains("Exchange: ID-001"), scrolled);
+
+        // over the table: the next error
+        tab.handleMouseEvent(MouseEvent.scrollDown(40, 6), area);
+        assertEquals(1, tab.tableState.selected());
+
+        // a click on a row selects it, and its detail starts at the top
+        String rows = renderToString(tab, 160, 40);
+        tab.handleMouseEvent(MouseEvent.press(MouseButton.LEFT, 40, row(rows, 
ErrorsTab.shortExchangeId("ID-003"))), area);
+        assertEquals(2, tab.tableState.selected());
+    }
+
+    private static int row(String rendered, String text) {
+        String[] lines = rendered.split("\n");
+        for (int y = 0; y < lines.length; y++) {
+            if (lines[y].contains(text)) {
+                return y;
+            }
+        }
+        return -1;
+    }
+
     @Test
     void renderMultipleErrorsAllAppear() {
         addError("ID-AAA", "route-a", "to1", "IOException", "fail1", false);
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 c6a18163c332..bf4ca7633284 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
@@ -21,10 +21,13 @@ import java.util.List;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 
+import dev.tamboui.layout.Rect;
 import dev.tamboui.text.Span;
 import dev.tamboui.tui.event.KeyCode;
 import dev.tamboui.tui.event.KeyEvent;
 import dev.tamboui.tui.event.KeyModifiers;
+import dev.tamboui.tui.event.MouseButton;
+import dev.tamboui.tui.event.MouseEvent;
 import org.apache.camel.util.json.JsonArray;
 import org.apache.camel.util.json.JsonObject;
 import org.junit.jupiter.api.BeforeEach;
@@ -217,6 +220,23 @@ class JfrTabRenderTest {
         });
     }
 
+    @Test
+    void aClickOnTheNameOfAViewShowsIt() {
+        JfrTab tab = new JfrTab(ctx);
+        Rect area = new Rect(0, 0, 140, 30);
+        List<String> lines = TuiTestHelper.renderToString(tab, 140, 
30).lines().toList();
+        int y = 0;
+        while (!lines.get(y).contains("Redeliveries")) {
+            y++;
+        }
+        int x = lines.get(y).indexOf("Failures");
+
+        assertThat(tab.handleMouseEvent(MouseEvent.press(MouseButton.LEFT, x + 
2, y), area)).isTrue();
+        assertThat(TuiTestHelper.renderToString(tab, 140, 30)).contains("╭ 
Failures ");
+        // the space between two names is no view
+        assertThat(tab.handleMouseEvent(MouseEvent.press(MouseButton.LEFT, x - 
2, y), area)).isFalse();
+    }
+
     @Test
     void promptToSnapshotWhenNoData() {
         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 c5b277032565..d55de400d4c6 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
@@ -403,6 +403,50 @@ class TimelineTabRenderTest {
         
assertThat(tab.getTableDataAsJson().getInteger("selectedIndex")).isEqualTo(2);
     }
 
+    @Test
+    void pausedAClickOnASecondPutsTheCursorThere() {
+        TimelineTab tab = new TimelineTab(ctx, now::get);
+        Rect area = new Rect(0, 0, 160, 40);
+        List<String> lines = TuiTestHelper.renderToString(tab, 160, 
40).lines().toList();
+        int shipping = indexOf(lines, "   shipping");
+        // the seconds start where the time axis does
+        int left = lines.get(indexOf(lines, "-60s")).indexOf("-60s");
+
+        // following now, a click selects the route and leaves the timeline 
following
+        assertThat(tab.handleMouseEvent(MouseEvent.press(MouseButton.LEFT, 
left + 30, shipping), area)).isTrue();
+        assertThat(tab.isPaused()).isFalse();
+
+        tab.handleKeyEvent(KeyEvent.ofChar(' '));
+        assertThat(tab.handleMouseEvent(MouseEvent.press(MouseButton.LEFT, 
left + 30, shipping), area)).isTrue();
+        assertThat(tab.cursorFrom()).isEqualTo(T0 / 1000 + 30);
+        
assertThat(tab.getTableDataAsJson().getInteger("selectedIndex")).isEqualTo(1);
+        // the label of the route is no second
+        assertThat(tab.handleMouseEvent(MouseEvent.press(MouseButton.LEFT, 
left - 3, shipping), area)).isTrue();
+        assertThat(tab.cursorFrom()).isEqualTo(T0 / 1000 + 30);
+    }
+
+    @Test
+    void aClickOnAnExchangeOfTheSecondsSelectsIt() {
+        // narrow: a column is two seconds, and has two exchanges
+        TimelineTab tab = new TimelineTab(ctx, now::get);
+        Rect area = new Rect(0, 0, 100, 40);
+        TuiTestHelper.renderToString(tab, 100, 40);
+        tab.handleKeyEvent(KeyEvent.ofChar(' '));
+        while (tab.cursorFrom() > T0 / 1000 + 30) {
+            tab.handleKeyEvent(KeyEvent.ofKey(KeyCode.LEFT));
+        }
+        assertThat(tab.cursorTo()).isEqualTo(T0 / 1000 + 31);
+        tab.handleKeyEvent(KeyEvent.ofKey(KeyCode.ENTER));
+        List<String> lines = TuiTestHelper.renderToString(tab, 100, 
40).lines().toList();
+
+        assertThat(tab.handleMouseEvent(MouseEvent.press(MouseButton.LEFT, 20, 
indexOf(lines, "H31")), area)).isTrue();
+        // below the last exchange is none
+        assertThat(tab.handleMouseEvent(MouseEvent.press(MouseButton.LEFT, 20, 
indexOf(lines, "H31") + 1), area))
+                .isFalse();
+        tab.handleKeyEvent(KeyEvent.ofKey(KeyCode.ENTER));
+        assertThat(TuiTestHelper.renderToString(tab, 100, 
40)).contains("H31").doesNotContain("H30");
+    }
+
     @Test
     void thePanelTheKeysGoToHasTheFocusBorder() {
         TimelineTab tab = new TimelineTab(ctx, now::get);

Reply via email to