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 610731580ffe camel-tui: Use TAB to switch focus between master/detail 
panels
610731580ffe is described below

commit 610731580ffe95af6437b810407cffc32fab58e5
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Jul 28 13:30:10 2026 +0200

    camel-tui: Use TAB to switch focus between master/detail panels
    
    Add consistent TAB focus switching to all tabs with master/detail
    panels. The focused panel gets an accent border and the unfocused
    panel gets a muted border. Up/Down and PgUp/PgDn operate on
    whichever panel has focus.
    
    Also fix BrowseTab to correctly parse the dev console JSON format
    where headers are a JsonArray of {key,value,type} objects and body
    is a JsonObject with {type,value} fields.
    
    camel-tui: Expose detailFocused panel state to MCP tools
    
    AI agents can now tell which panel has focus in master/detail tabs
    via the detailFocused field in tui_get_state, tui_get_screen,
    tui_navigate, and tui_send_keys responses.
    
    camel-tui: Clean up dead infra service files so stopped services disappear
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../dsl/jbang/core/commands/tui/BeansTab.java      |  66 ++++++++---
 .../dsl/jbang/core/commands/tui/BrowseTab.java     | 126 ++++++++++++++------
 .../jbang/core/commands/tui/ConfigurationTab.java  |  62 +++++++---
 .../core/commands/tui/DataRefreshService.java      |   7 ++
 .../dsl/jbang/core/commands/tui/EndpointsTab.java  |  77 ++++++++----
 .../dsl/jbang/core/commands/tui/HistoryTab.java    | 132 +++++++++++++++++----
 .../dsl/jbang/core/commands/tui/McpFacade.java     |   5 +
 .../dsl/jbang/core/commands/tui/MonitorTab.java    |   4 +
 .../dsl/jbang/core/commands/tui/ThreadsTab.java    |  64 +++++++---
 .../jbang/core/commands/tui/TuiToolRegistry.java   |  12 +-
 10 files changed, 429 insertions(+), 126 deletions(-)

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 6ff0dde6a8c4..200eedef6b7e 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
@@ -35,6 +35,7 @@ import dev.tamboui.tui.event.MouseEvent;
 import dev.tamboui.widgets.block.Block;
 import dev.tamboui.widgets.block.BorderType;
 import dev.tamboui.widgets.block.Borders;
+import dev.tamboui.widgets.block.Title;
 import dev.tamboui.widgets.input.TextInputState;
 import dev.tamboui.widgets.paragraph.Paragraph;
 import dev.tamboui.widgets.table.Cell;
@@ -55,6 +56,7 @@ class BeansTab extends AbstractTableTab {
     private String filterTerm;
     private List<BeanData> allBeans = Collections.emptyList();
     private int detailScroll;
+    private boolean detailFocused;
     private String lastPid;
 
     BeansTab(MonitorContext ctx) {
@@ -132,13 +134,19 @@ class BeansTab extends AbstractTableTab {
 
     @Override
     protected boolean handleTabKeyEvent(KeyEvent ke) {
-        if (ke.isPageUp() || ke.isKey(KeyCode.PAGE_UP)) {
-            detailScroll = Math.max(0, detailScroll - 10);
+        if (ke.isKey(KeyCode.TAB)) {
+            detailFocused = !detailFocused;
             return true;
         }
-        if (ke.isPageDown() || ke.isKey(KeyCode.PAGE_DOWN)) {
-            detailScroll += 10;
-            return true;
+        if (detailFocused) {
+            if (ke.isPageUp() || ke.isKey(KeyCode.PAGE_UP)) {
+                detailScroll = Math.max(0, detailScroll - 10);
+                return true;
+            }
+            if (ke.isPageDown() || ke.isKey(KeyCode.PAGE_DOWN)) {
+                detailScroll += 10;
+                return true;
+            }
         }
         if (ke.isCharIgnoreCase('f')) {
             filterIndex = (filterIndex + 1) % filterModes().length;
@@ -149,15 +157,23 @@ class BeansTab extends AbstractTableTab {
 
     @Override
     public void navigateUp() {
-        tableState.selectPrevious();
-        detailScroll = 0;
+        if (detailFocused) {
+            detailScroll = Math.max(0, detailScroll - 1);
+        } else {
+            tableState.selectPrevious();
+            detailScroll = 0;
+        }
     }
 
     @Override
     public void navigateDown() {
-        List<BeanData> visible = sortedBeans();
-        tableState.selectNext(visible.size());
-        detailScroll = 0;
+        if (detailFocused) {
+            detailScroll++;
+        } else {
+            List<BeanData> visible = sortedBeans();
+            tableState.selectNext(visible.size());
+            detailScroll = 0;
+        }
     }
 
     @Override
@@ -211,6 +227,9 @@ class BeansTab extends AbstractTableTab {
                 ? String.format(" Beans [%d] scope:%s filter:\"%s\" ", 
visible.size(), mode, filterTerm)
                 : String.format(" Beans [%d] scope:%s ", visible.size(), mode);
 
+        Style tableBorderStyle = detailFocused ? Theme.muted() : 
Style.EMPTY.fg(Theme.accent());
+        Style tableTitleStyle = detailFocused ? Style.EMPTY.fg(Theme.accent()) 
: Theme.title();
+
         Table table = Table.builder()
                 .rows(rows)
                 .header(Row.from(
@@ -219,9 +238,11 @@ class BeansTab extends AbstractTableTab {
                 .widths(
                         Constraint.percentage(55),
                         Constraint.fill())
-                .highlightStyle(Theme.selectionBg())
+                .highlightStyle(detailFocused ? Theme.selectionBg().dim() : 
Theme.selectionBg())
                 .highlightSpacing(Table.HighlightSpacing.ALWAYS)
-                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title(title).build())
+                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                        .borderStyle(tableBorderStyle)
+                        .title(Title.from(Line.from(Span.styled(title, 
tableTitleStyle)))).build())
                 .build();
 
         lastTableArea = area;
@@ -230,13 +251,17 @@ class BeansTab extends AbstractTableTab {
     }
 
     private void renderDetail(Frame frame, Rect area, List<BeanData> visible) {
+        Style detailBorderStyle = detailFocused ? 
Style.EMPTY.fg(Theme.accent()) : Theme.muted();
+        Style detailTitleStyle = detailFocused ? Theme.title() : 
Style.EMPTY.fg(Theme.accent());
+
         Integer sel = tableState.selected();
         if (sel == null || sel < 0 || sel >= visible.size()) {
             frame.renderWidget(
                     Paragraph.builder()
                             .text(Text.from(Line.from(Span.styled(" Select a 
bean", Style.EMPTY.dim()))))
                             
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
-                                    .title(" Bean Detail ").build())
+                                    .borderStyle(detailBorderStyle)
+                                    .title(Title.from(Line.from(Span.styled(" 
Bean Detail ", detailTitleStyle)))).build())
                             .build(),
                     area);
             return;
@@ -296,7 +321,9 @@ class BeansTab extends AbstractTableTab {
         frame.renderWidget(
                 Paragraph.builder()
                         .text(Text.from(visibleContent))
-                        
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title(title).build())
+                        
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                                .borderStyle(detailBorderStyle)
+                                .title(Title.from(Line.from(Span.styled(title, 
detailTitleStyle)))).build())
                         .build(),
                 area);
     }
@@ -318,6 +345,7 @@ class BeansTab extends AbstractTableTab {
         } else {
             hint(spans, "/", "filter");
         }
+        hint(spans, "Tab", detailFocused ? "table" : "detail");
         hint(spans, TuiIcons.HINT_SCROLL, "navigate");
         hintLast(spans, "PgUp/Dn", "scroll");
     }
@@ -333,6 +361,11 @@ class BeansTab extends AbstractTableTab {
         return new SelectionContext("table", items, sel != null ? sel : -1, 
items.size(), "Beans");
     }
 
+    @Override
+    public Boolean isDetailFocused() {
+        return detailFocused;
+    }
+
     private String[] filterModes() {
         IntegrationInfo info = ctx.findSelectedIntegration();
         String platform = info != null ? info.platform : null;
@@ -556,8 +589,9 @@ class BeansTab extends AbstractTableTab {
 
                 ## Keys
 
-                - `Up/Down` — select bean
-                - `PgUp/PgDn` — scroll detail panel
+                - `Tab` — switch focus between table and detail panel
+                - `Up/Down` — navigate in focused panel
+                - `PgUp/PgDn` — page in focused panel
                 - `s` — cycle sort column
                 - `S` — reverse sort order
                 - `f` — cycle filter
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/BrowseTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/BrowseTab.java
index 52ba8db3713f..0e39410bbf67 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/BrowseTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/BrowseTab.java
@@ -41,6 +41,7 @@ import dev.tamboui.tui.event.MouseEvent;
 import dev.tamboui.widgets.block.Block;
 import dev.tamboui.widgets.block.BorderType;
 import dev.tamboui.widgets.block.Borders;
+import dev.tamboui.widgets.block.Title;
 import dev.tamboui.widgets.paragraph.Paragraph;
 import dev.tamboui.widgets.scrollbar.ScrollbarState;
 import dev.tamboui.widgets.table.Cell;
@@ -74,6 +75,7 @@ class BrowseTab extends AbstractTab {
     private int sortIndex;
     private boolean sortReversed;
     private int view = VIEW_ENDPOINTS;
+    private boolean detailFocused = true;
     private int detailScroll;
     private boolean prettyPrint;
 
@@ -112,20 +114,28 @@ class BrowseTab extends AbstractTab {
     @Override
     public boolean handleKeyEvent(KeyEvent ke) {
         if (view == VIEW_DETAIL) {
-            if (ke.isUp()) {
-                detailScroll = Math.max(0, detailScroll - 1);
-                return true;
-            }
-            if (ke.isDown()) {
-                detailScroll++;
+            if (ke.isKey(KeyCode.TAB)) {
+                detailFocused = !detailFocused;
                 return true;
             }
             if (ke.isPageUp() || ke.isKey(KeyCode.PAGE_UP)) {
-                detailScroll = Math.max(0, detailScroll - 20);
+                if (detailFocused) {
+                    detailScroll = Math.max(0, detailScroll - 20);
+                } else {
+                    for (int i = 0; i < 20 && messageTableState.selected() != 
null && messageTableState.selected() > 0; i++) {
+                        messageTableState.selectPrevious();
+                    }
+                }
                 return true;
             }
             if (ke.isPageDown() || ke.isKey(KeyCode.PAGE_DOWN)) {
-                detailScroll += 20;
+                if (detailFocused) {
+                    detailScroll += 20;
+                } else {
+                    for (int i = 0; i < 20; i++) {
+                        messageTableState.selectNext(messages.size());
+                    }
+                }
                 return true;
             }
             if (ke.isChar('p')) {
@@ -138,6 +148,7 @@ class BrowseTab extends AbstractTab {
         if (view == VIEW_MESSAGES) {
             if (ke.isConfirm()) {
                 view = VIEW_DETAIL;
+                detailFocused = true;
                 detailScroll = 0;
                 return true;
             }
@@ -236,7 +247,14 @@ class BrowseTab extends AbstractTab {
 
     @Override
     public void navigateUp() {
-        if (view == VIEW_ENDPOINTS) {
+        if (view == VIEW_DETAIL) {
+            if (detailFocused) {
+                detailScroll = Math.max(0, detailScroll - 1);
+            } else {
+                messageTableState.selectPrevious();
+                detailScroll = 0;
+            }
+        } else if (view == VIEW_ENDPOINTS) {
             endpointTableState.selectPrevious();
         } else if (view == VIEW_MESSAGES) {
             messageTableState.selectPrevious();
@@ -245,7 +263,14 @@ class BrowseTab extends AbstractTab {
 
     @Override
     public void navigateDown() {
-        if (view == VIEW_ENDPOINTS) {
+        if (view == VIEW_DETAIL) {
+            if (detailFocused) {
+                detailScroll++;
+            } else {
+                messageTableState.selectNext(messages.size());
+                detailScroll = 0;
+            }
+        } else if (view == VIEW_ENDPOINTS) {
             List<EndpointData> sorted = sortedEndpoints();
             endpointTableState.selectNext(sorted.size());
         } else if (view == VIEW_MESSAGES) {
@@ -324,12 +349,17 @@ class BrowseTab extends AbstractTab {
     }
 
     private void renderMessages(Frame frame, Rect area) {
+        renderMessages(frame, area, Style.EMPTY, Theme.selectionBg());
+    }
+
+    private void renderMessages(Frame frame, Rect area, Style borderStyle, 
Style highlightStyle) {
         if (loading.get() && messages.isEmpty()) {
             String title = " " + (selectedEndpoint != null ? 
selectedEndpoint.uri : "Messages") + " ";
             frame.renderWidget(
                     Paragraph.builder()
                             .text(Text.from(Line.from(Span.styled(" Loading 
messages...", Style.EMPTY.dim()))))
-                            
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title(title).build())
+                            
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                                    
.borderStyle(borderStyle).title(title).build())
                             .build(),
                     area);
             return;
@@ -368,9 +398,10 @@ class BrowseTab extends AbstractTab {
                         Constraint.length(40),
                         Constraint.length(10),
                         Constraint.fill())
-                .highlightStyle(Theme.selectionBg())
+                .highlightStyle(highlightStyle)
                 .highlightSpacing(Table.HighlightSpacing.ALWAYS)
-                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title(title).build())
+                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                        .borderStyle(borderStyle).title(title).build())
                 .build();
 
         lastMessageTableArea = area;
@@ -380,12 +411,19 @@ class BrowseTab extends AbstractTab {
     }
 
     private void renderDetail(Frame frame, Rect area) {
+        Style tableBorderStyle = detailFocused ? Theme.muted() : 
Style.EMPTY.fg(Theme.accent());
+        Style tableHighlight = detailFocused ? Theme.selectionBg().dim() : 
Theme.selectionBg();
+        Style detailBorderStyle = detailFocused ? 
Style.EMPTY.fg(Theme.accent()) : Theme.muted();
+        Style detailTitleStyle = detailFocused ? Theme.title() : 
Style.EMPTY.fg(Theme.accent());
+
         Integer sel = messageTableState.selected();
         if (sel == null || sel < 0 || sel >= messages.size()) {
             frame.renderWidget(
                     Paragraph.builder()
                             .text(Text.from(Line.from(Span.styled(" Select a 
message", Style.EMPTY.dim()))))
-                            
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title("
 Message ")
+                            
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                                    .borderStyle(detailBorderStyle)
+                                    .title(Title.from(Line.from(Span.styled(" 
Message ", detailTitleStyle))))
                                     .build())
                             .build(),
                     area);
@@ -394,11 +432,11 @@ class BrowseTab extends AbstractTab {
 
         MessageData msg = messages.get(sel);
 
-        // Split: message list (40%) + detail (fill)
+        // Split: message list (35%) + detail (fill)
         List<Rect> chunks = Layout.vertical()
                 .constraints(Constraint.percentage(35), Constraint.fill())
                 .split(area);
-        renderMessages(frame, chunks.get(0));
+        renderMessages(frame, chunks.get(0), tableBorderStyle, tableHighlight);
 
         String title = " Message " + msg.position + " [" + (msg.exchangeId != 
null ? msg.exchangeId : "") + "] ";
 
@@ -457,7 +495,9 @@ class BrowseTab extends AbstractTab {
         frame.renderWidget(
                 Paragraph.builder()
                         .text(Text.from(visible))
-                        
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title(title).build())
+                        
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                                .borderStyle(detailBorderStyle)
+                                .title(Title.from(Line.from(Span.styled(title, 
detailTitleStyle)))).build())
                         .build(),
                 chunks.get(1));
     }
@@ -466,8 +506,9 @@ class BrowseTab extends AbstractTab {
     public void renderFooter(List<Span> spans) {
         hint(spans, "Esc", "back");
         if (view == VIEW_DETAIL) {
-            hint(spans, "p", "pretty" + (prettyPrint ? " [on]" : ""));
-            hintLast(spans, TuiIcons.HINT_SCROLL, "scroll");
+            hint(spans, "Tab", detailFocused ? "messages" : "detail");
+            hint(spans, TuiIcons.HINT_SCROLL, "navigate");
+            hintLast(spans, "p", "pretty" + (prettyPrint ? " [on]" : ""));
         } else if (view == VIEW_MESSAGES) {
             hint(spans, "r", "refresh");
             hintLast(spans, "Enter", "detail");
@@ -498,6 +539,14 @@ class BrowseTab extends AbstractTab {
         return new SelectionContext("table", items, sel != null ? sel : -1, 
items.size(), "Browse");
     }
 
+    @Override
+    public Boolean isDetailFocused() {
+        if (view != VIEW_DETAIL) {
+            return null;
+        }
+        return detailFocused;
+    }
+
     private List<EndpointData> sortedEndpoints() {
         List<EndpointData> result = new ArrayList<>(allEndpoints);
         result.sort((a, b) -> {
@@ -634,22 +683,30 @@ class BrowseTab extends AbstractTab {
                 }
                 md.exchangePattern = message.getString("exchangePattern");
 
-                // Timestamp from headers
-                JsonObject headers = message.getMap("headers");
-                if (headers != null) {
+                // headers are a JsonArray of {key, value, type} objects
+                JsonArray headerArr = message.getCollection("headers");
+                if (headerArr != null) {
                     md.headers = new LinkedHashMap<>();
-                    for (String key : headers.keySet()) {
-                        Object val = headers.get(key);
-                        md.headers.put(key, val != null ? val.toString() : 
"null");
-                    }
-                    Object tsObj = headers.get("CamelMessageTimestamp");
-                    if (tsObj instanceof Number) {
-                        md.timestamp = ((Number) tsObj).longValue();
+                    for (Object item : headerArr) {
+                        if (item instanceof JsonObject entry) {
+                            String key = entry.getString("key");
+                            Object val = entry.get("value");
+                            if (key != null) {
+                                md.headers.put(key, val != null ? 
val.toString() : "null");
+                            }
+                            if ("CamelMessageTimestamp".equals(key) && val 
instanceof Number n) {
+                                md.timestamp = n.longValue();
+                            }
+                        }
                     }
                 }
 
-                Object bodyObj = message.get("body");
-                md.body = bodyObj != null ? bodyObj.toString() : null;
+                // body is a JsonObject with {type, value}
+                JsonObject bodyObj = message.getMap("body");
+                if (bodyObj != null) {
+                    Object val = bodyObj.get("value");
+                    md.body = val != null ? val.toString() : null;
+                }
 
                 result.add(md);
             }
@@ -729,8 +786,11 @@ class BrowseTab extends AbstractTab {
                 ## Keys
 
                 - `Up/Down` — navigate endpoints and messages
-                - `Enter` — browse selected endpoint
-                - `Esc` — back to endpoint list
+                - `PgUp/PgDn` — page through list or detail
+                - `Tab` — switch focus between messages and detail (in detail 
view)
+                - `Enter` — browse selected endpoint / view message detail
+                - `p` — toggle pretty-print for message body
+                - `Esc` — back to previous view
                 """;
     }
 
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 4e4e1066b76c..084a8c555848 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
@@ -35,6 +35,7 @@ import dev.tamboui.tui.event.MouseEvent;
 import dev.tamboui.widgets.block.Block;
 import dev.tamboui.widgets.block.BorderType;
 import dev.tamboui.widgets.block.Borders;
+import dev.tamboui.widgets.block.Title;
 import dev.tamboui.widgets.paragraph.Paragraph;
 import dev.tamboui.widgets.table.Cell;
 import dev.tamboui.widgets.table.Row;
@@ -55,6 +56,7 @@ class ConfigurationTab extends AbstractTableTab {
     private static final Style SECRET_STYLE = Theme.muted();
 
     private int detailScroll;
+    private boolean detailFocused;
 
     private CamelCatalog catalog;
     private String catalogVersion;
@@ -77,29 +79,43 @@ class ConfigurationTab extends AbstractTableTab {
 
     @Override
     protected boolean handleTabKeyEvent(KeyEvent ke) {
-        if (ke.isPageUp() || ke.isKey(KeyCode.PAGE_UP)) {
-            detailScroll = Math.max(0, detailScroll - 10);
+        if (ke.isKey(KeyCode.TAB)) {
+            detailFocused = !detailFocused;
             return true;
         }
-        if (ke.isPageDown() || ke.isKey(KeyCode.PAGE_DOWN)) {
-            detailScroll += 10;
-            return true;
+        if (detailFocused) {
+            if (ke.isPageUp() || ke.isKey(KeyCode.PAGE_UP)) {
+                detailScroll = Math.max(0, detailScroll - 10);
+                return true;
+            }
+            if (ke.isPageDown() || ke.isKey(KeyCode.PAGE_DOWN)) {
+                detailScroll += 10;
+                return true;
+            }
         }
         return false;
     }
 
     @Override
     public void navigateUp() {
-        tableState.selectPrevious();
-        detailScroll = 0;
+        if (detailFocused) {
+            detailScroll = Math.max(0, detailScroll - 1);
+        } else {
+            tableState.selectPrevious();
+            detailScroll = 0;
+        }
     }
 
     @Override
     public void navigateDown() {
-        IntegrationInfo info = ctx.findSelectedIntegration();
-        int count = info != null ? info.configProperties.size() : 0;
-        tableState.selectNext(count);
-        detailScroll = 0;
+        if (detailFocused) {
+            detailScroll++;
+        } else {
+            IntegrationInfo info = ctx.findSelectedIntegration();
+            int count = info != null ? info.configProperties.size() : 0;
+            tableState.selectNext(count);
+            detailScroll = 0;
+        }
     }
 
     @Override
@@ -171,6 +187,9 @@ class ConfigurationTab extends AbstractTableTab {
 
         String title = String.format(" Configuration [%d] ", props.size());
 
+        Style tableBorderStyle = detailFocused ? Theme.muted() : 
Style.EMPTY.fg(Theme.accent());
+        Style tableTitleStyle = detailFocused ? Style.EMPTY.fg(Theme.accent()) 
: Theme.title();
+
         Table table = Table.builder()
                 .rows(rows)
                 .header(Row.from(
@@ -181,9 +200,11 @@ class ConfigurationTab extends AbstractTableTab {
                         Constraint.percentage(35),
                         Constraint.fill(),
                         Constraint.length(20))
-                .highlightStyle(Theme.selectionBg())
+                .highlightStyle(detailFocused ? Theme.selectionBg().dim() : 
Theme.selectionBg())
                 .highlightSpacing(Table.HighlightSpacing.ALWAYS)
-                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title(title).build())
+                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                        .borderStyle(tableBorderStyle)
+                        .title(Title.from(Line.from(Span.styled(title, 
tableTitleStyle)))).build())
                 .build();
 
         lastTableArea = area;
@@ -192,13 +213,17 @@ class ConfigurationTab extends AbstractTableTab {
     }
 
     private void renderDetail(Frame frame, Rect area, List<ConfigProperty> 
props) {
+        Style detailBorderStyle = detailFocused ? 
Style.EMPTY.fg(Theme.accent()) : Theme.muted();
+        Style detailTitleStyle = detailFocused ? Theme.title() : 
Style.EMPTY.fg(Theme.accent());
+
         Integer sel = tableState.selected();
         if (sel == null || sel < 0 || sel >= props.size()) {
             frame.renderWidget(
                     Paragraph.builder()
                             .text(Text.from(Line.from(Span.styled(" Select a 
property", Style.EMPTY.dim()))))
                             
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
-                                    .title(" Property Detail ").build())
+                                    .borderStyle(detailBorderStyle)
+                                    .title(Title.from(Line.from(Span.styled(" 
Property Detail ", detailTitleStyle)))).build())
                             .build(),
                     area);
             return;
@@ -268,7 +293,8 @@ class ConfigurationTab extends AbstractTableTab {
                 Paragraph.builder()
                         .text(Text.from(visible))
                         
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
-                                .title(title).build())
+                                .borderStyle(detailBorderStyle)
+                                .title(Title.from(Line.from(Span.styled(title, 
detailTitleStyle)))).build())
                         .build(),
                 area);
     }
@@ -401,6 +427,7 @@ class ConfigurationTab extends AbstractTableTab {
     @Override
     public void renderFooter(List<Span> spans) {
         super.renderFooter(spans);
+        hint(spans, "Tab", detailFocused ? "table" : "detail");
         hint(spans, TuiIcons.HINT_SCROLL, "navigate");
         hintLast(spans, "PgUp/Dn", "scroll");
     }
@@ -416,6 +443,11 @@ class ConfigurationTab extends AbstractTableTab {
         return new SelectionContext("table", items, sel != null ? sel : -1, 
items.size(), "Configuration");
     }
 
+    @Override
+    public Boolean isDetailFocused() {
+        return detailFocused;
+    }
+
     static int compareCamelFirst(ConfigProperty a, ConfigProperty b) {
         boolean aCamel = a.key.startsWith("camel.");
         boolean bCamel = b.key.startsWith("camel.");
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DataRefreshService.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DataRefreshService.java
index b7332b32b0cd..24cf3b3a220b 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DataRefreshService.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DataRefreshService.java
@@ -417,6 +417,13 @@ class DataRefreshService {
                             continue;
                         }
                         boolean alive = 
ProcessHandle.of(pid).map(ProcessHandle::isAlive).orElse(false);
+                        if (!alive) {
+                            Files.deleteIfExists(jsonFile);
+                            Path logFile = jsonFile.resolveSibling(
+                                    fn.substring(0, fn.lastIndexOf('.')) + 
".log");
+                            Files.deleteIfExists(logFile);
+                            continue;
+                        }
 
                         InfraInfo info = new InfraInfo();
                         info.pid = pidStr;
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 e68a0b6d292b..4fceee674992 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
@@ -33,6 +33,7 @@ import dev.tamboui.markdown.MarkdownView;
 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.tui.event.KeyCode;
 import dev.tamboui.tui.event.KeyEvent;
@@ -41,6 +42,7 @@ import dev.tamboui.tui.event.MouseEventKind;
 import dev.tamboui.widgets.block.Block;
 import dev.tamboui.widgets.block.BorderType;
 import dev.tamboui.widgets.block.Borders;
+import dev.tamboui.widgets.block.Title;
 import dev.tamboui.widgets.table.Cell;
 import dev.tamboui.widgets.table.Row;
 import dev.tamboui.widgets.table.Table;
@@ -80,6 +82,7 @@ class EndpointsTab extends AbstractTableTab {
     private int chartMode = CHART_ALL;
     private int panelMode = PANEL_CHART;
     private int detailScroll;
+    private boolean detailFocused;
     private Rect lastDetailArea;
     private int chartPanelHeight = 16;
     private final DragSplit vSplit = new DragSplit();
@@ -115,6 +118,10 @@ class EndpointsTab extends AbstractTableTab {
 
     @Override
     protected boolean handleTabKeyEvent(KeyEvent ke) {
+        if (ke.isKey(KeyCode.TAB) && panelMode == PANEL_DETAIL) {
+            detailFocused = !detailFocused;
+            return true;
+        }
         if (ke.isCharIgnoreCase('f')) {
             filter = (filter + 1) % 3;
             return true;
@@ -126,9 +133,10 @@ class EndpointsTab extends AbstractTableTab {
         if (ke.isCharIgnoreCase('d')) {
             panelMode = panelMode == PANEL_CHART ? PANEL_DETAIL : PANEL_CHART;
             detailScroll = 0;
+            detailFocused = false;
             return true;
         }
-        if (panelMode == PANEL_DETAIL) {
+        if (panelMode == PANEL_DETAIL && detailFocused) {
             if (ke.isPageUp() || ke.isKey(KeyCode.PAGE_UP)) {
                 detailScroll = Math.max(0, detailScroll - 10);
                 return true;
@@ -183,22 +191,30 @@ class EndpointsTab extends AbstractTableTab {
 
     @Override
     public void navigateUp() {
-        tableState.selectPrevious();
-        detailScroll = 0;
+        if (detailFocused) {
+            detailScroll = Math.max(0, detailScroll - 1);
+        } else {
+            tableState.selectPrevious();
+            detailScroll = 0;
+        }
     }
 
     @Override
     public void navigateDown() {
-        IntegrationInfo info = ctx.findSelectedIntegration();
-        if (info != null) {
-            List<EndpointInfo> filtered = new ArrayList<>(info.endpoints);
-            if (filter == 1) {
-                filtered.removeIf(ep -> !ep.remote);
-            } else if (filter == 2) {
-                filtered.removeIf(ep -> !ep.remote && !ep.stub);
+        if (detailFocused) {
+            detailScroll++;
+        } else {
+            IntegrationInfo info = ctx.findSelectedIntegration();
+            if (info != null) {
+                List<EndpointInfo> filtered = new ArrayList<>(info.endpoints);
+                if (filter == 1) {
+                    filtered.removeIf(ep -> !ep.remote);
+                } else if (filter == 2) {
+                    filtered.removeIf(ep -> !ep.remote && !ep.stub);
+                }
+                tableState.selectNext(filtered.size());
+                detailScroll = 0;
             }
-            tableState.selectNext(filtered.size());
-            detailScroll = 0;
         }
     }
 
@@ -275,16 +291,22 @@ class EndpointsTab extends AbstractTableTab {
         widths.add(Constraint.length(8));
         widths.add(Constraint.fill());
 
+        boolean showDetailFocus = panelMode == PANEL_DETAIL;
+        Style tableBorderStyle = showDetailFocus && detailFocused ? 
Theme.muted() : Style.EMPTY.fg(Theme.accent());
+        Style tableTitleStyle = showDetailFocus && detailFocused ? 
Style.EMPTY.fg(Theme.accent()) : Theme.title();
+        String tableTitle = " Endpoints"
+                            + (filter == 1 ? " filter:remote" : filter == 2 ? 
" filter:remote+stub" : "")
+                            + " ";
+
         Table table = Table.builder()
                 .rows(rows)
                 .header(Row.from(headerCells))
                 .widths(widths.toArray(Constraint[]::new))
-                .highlightStyle(Theme.selectionBg())
+                .highlightStyle(showDetailFocus && detailFocused ? 
Theme.selectionBg().dim() : Theme.selectionBg())
                 .highlightSpacing(Table.HighlightSpacing.ALWAYS)
                 
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
-                        .title(" Endpoints"
-                               + (filter == 1 ? " filter:remote" : filter == 2 
? " filter:remote+stub" : "")
-                               + " ")
+                        .borderStyle(tableBorderStyle)
+                        .title(Title.from(Line.from(Span.styled(tableTitle, 
tableTitleStyle))))
                         .build())
                 .build();
 
@@ -376,7 +398,8 @@ class EndpointsTab extends AbstractTableTab {
         hint(spans, "a", "chart " + chartLabel);
         hint(spans, "d", "detail " + (panelMode == PANEL_DETAIL ? "[on]" : 
"[off]"));
         if (panelMode == PANEL_DETAIL) {
-            hintLast(spans, "PgUp/Dn", "scroll");
+            hint(spans, "Tab", detailFocused ? "table" : "detail");
+            hintLast(spans, TuiIcons.HINT_SCROLL, "navigate");
         }
     }
 
@@ -510,13 +533,17 @@ class EndpointsTab extends AbstractTableTab {
     }
 
     private void renderDetail(Frame frame, Rect area, List<EndpointInfo> 
sortedEndpoints, IntegrationInfo info) {
+        Style detailBorderStyle = detailFocused ? 
Style.EMPTY.fg(Theme.accent()) : Theme.muted();
+        Style detailTitleStyle = detailFocused ? Theme.title() : 
Style.EMPTY.fg(Theme.accent());
+
         Integer sel = tableState.selected();
         if (sel == null || sel < 0 || sel >= sortedEndpoints.size()) {
             frame.renderWidget(
                     MarkdownView.builder()
                             .source("*Select an endpoint*")
                             
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
-                                    .title(" Endpoint Detail ").build())
+                                    .borderStyle(detailBorderStyle)
+                                    .title(Title.from(Line.from(Span.styled(" 
Endpoint Detail ", detailTitleStyle)))).build())
                             .styles(Theme.markdownStyles())
                             .build(),
                     area);
@@ -531,7 +558,8 @@ class EndpointsTab extends AbstractTableTab {
                     MarkdownView.builder()
                             .source("*No endpoint URI*")
                             
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
-                                    .title(" Endpoint Detail ").build())
+                                    .borderStyle(detailBorderStyle)
+                                    .title(Title.from(Line.from(Span.styled(" 
Endpoint Detail ", detailTitleStyle)))).build())
                             .styles(Theme.markdownStyles())
                             .build(),
                     area);
@@ -631,7 +659,8 @@ class EndpointsTab extends AbstractTableTab {
                         .source(md.toString())
                         .scroll(detailScroll)
                         
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
-                                .title(title).build())
+                                .borderStyle(detailBorderStyle)
+                                .title(Title.from(Line.from(Span.styled(title, 
detailTitleStyle)))).build())
                         .styles(Theme.markdownStyles())
                         .build(),
                 area);
@@ -681,6 +710,14 @@ class EndpointsTab extends AbstractTableTab {
         return new SelectionContext("table", items, sel != null ? sel : -1, 
items.size(), "Endpoints");
     }
 
+    @Override
+    public Boolean isDetailFocused() {
+        if (panelMode != PANEL_DETAIL) {
+            return null;
+        }
+        return detailFocused;
+    }
+
     @Override
     public String description() {
         return "Registered endpoints with usage statistics (hits, direction)";
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTab.java
index fbe5f491ae11..41be33d787b7 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTab.java
@@ -107,6 +107,7 @@ class HistoryTab extends AbstractTab {
 
     private boolean showDescription;
 
+    private boolean detailFocused;
     private boolean showWaterfall;
     private int waterfallScroll;
     private final ScrollbarState waterfallScrollState = new ScrollbarState();
@@ -264,9 +265,16 @@ class HistoryTab extends AbstractTab {
 
         boolean tracerActive = !traces.get().isEmpty();
 
+        if (ke.isKey(KeyCode.TAB)) {
+            if (!showWaterfall && ((tracerActive && traceDetailView) || 
!tracerActive)) {
+                detailFocused = !detailFocused;
+            }
+            return true;
+        }
+
         if (ke.isPageUp() || ke.isKey(KeyCode.PAGE_UP)) {
             if (tracerActive && traceDetailView) {
-                if (showWaterfall) {
+                if (showWaterfall || !detailFocused) {
                     for (int i = 0; i < 10; i++) {
                         traceStepTableState.selectPrevious();
                     }
@@ -274,7 +282,7 @@ class HistoryTab extends AbstractTab {
                     traceDetailScroll = Math.max(0, traceDetailScroll - 5);
                 }
             } else {
-                if (showWaterfall) {
+                if (showWaterfall || !detailFocused) {
                     for (int i = 0; i < 10; i++) {
                         historyTableState.selectPrevious();
                     }
@@ -286,7 +294,7 @@ class HistoryTab extends AbstractTab {
         }
         if (ke.isPageDown() || ke.isKey(KeyCode.PAGE_DOWN)) {
             if (tracerActive && traceDetailView) {
-                if (showWaterfall) {
+                if (showWaterfall || !detailFocused) {
                     List<TraceEntry> steps = 
getTraceStepsDepthFirst(traceSelectedExchangeId);
                     for (int i = 0; i < 10; i++) {
                         traceStepTableState.selectNext(steps.size());
@@ -295,7 +303,7 @@ class HistoryTab extends AbstractTab {
                     traceDetailScroll += 5;
                 }
             } else {
-                if (showWaterfall) {
+                if (showWaterfall || !detailFocused) {
                     for (int i = 0; i < 10; i++) {
                         historyTableState.selectNext(historyVisibleCount);
                     }
@@ -379,6 +387,7 @@ class HistoryTab extends AbstractTab {
             }
             if (ke.isCharIgnoreCase('g')) {
                 showWaterfall = !showWaterfall;
+                detailFocused = false;
                 waterfallScroll = 0;
                 return true;
             }
@@ -398,6 +407,7 @@ class HistoryTab extends AbstractTab {
                 if (sel != null && sel >= 0 && sel < 
traceSortedExchangeIds.size()) {
                     traceSelectedExchangeId = traceSortedExchangeIds.get(sel);
                     traceDetailView = true;
+                    detailFocused = false;
                     traceStepTableState.select(0);
                     traceDetailScroll = 0;
                 }
@@ -435,6 +445,7 @@ class HistoryTab extends AbstractTab {
             }
             if (ke.isCharIgnoreCase('g')) {
                 showWaterfall = !showWaterfall;
+                detailFocused = false;
                 waterfallScroll = 0;
                 return true;
             }
@@ -458,6 +469,7 @@ class HistoryTab extends AbstractTab {
         traceFilePositions.clear();
         traces.set(Collections.emptyList());
         traceDetailView = false;
+        detailFocused = false;
         traceSelectedExchangeId = null;
         traceDetailScroll = 0;
         traceDetailHScroll = 0;
@@ -479,6 +491,7 @@ class HistoryTab extends AbstractTab {
         }
         if (traceDetailView) {
             traceDetailView = false;
+            detailFocused = false;
             traceSelectedExchangeId = null;
             traceDetailScroll = 0;
             showWaterfall = false;
@@ -614,14 +627,22 @@ class HistoryTab extends AbstractTab {
     public void navigateUp() {
         if (!traces.get().isEmpty()) {
             if (traceDetailView) {
-                traceStepTableState.selectPrevious();
-                traceDetailScroll = 0;
+                if (detailFocused && !showWaterfall) {
+                    traceDetailScroll = Math.max(0, traceDetailScroll - 1);
+                } else {
+                    traceStepTableState.selectPrevious();
+                    traceDetailScroll = 0;
+                }
             } else {
                 traceTableState.selectPrevious();
             }
         } else {
-            historyTableState.selectPrevious();
-            historyDetailScroll = 0;
+            if (detailFocused && !showWaterfall) {
+                historyDetailScroll = Math.max(0, historyDetailScroll - 1);
+            } else {
+                historyTableState.selectPrevious();
+                historyDetailScroll = 0;
+            }
         }
     }
 
@@ -629,16 +650,24 @@ class HistoryTab extends AbstractTab {
     public void navigateDown() {
         if (!traces.get().isEmpty()) {
             if (traceDetailView) {
-                List<TraceEntry> steps = 
getTraceStepsDepthFirst(traceSelectedExchangeId);
-                traceStepTableState.selectNext(steps.size());
-                traceDetailScroll = 0;
+                if (detailFocused && !showWaterfall) {
+                    traceDetailScroll++;
+                } else {
+                    List<TraceEntry> steps = 
getTraceStepsDepthFirst(traceSelectedExchangeId);
+                    traceStepTableState.selectNext(steps.size());
+                    traceDetailScroll = 0;
+                }
             } else {
                 List<String> exchangeIds = getTraceExchangeIds();
                 traceTableState.selectNext(exchangeIds.size());
             }
         } else {
-            historyTableState.selectNext(historyVisibleCount);
-            historyDetailScroll = 0;
+            if (detailFocused && !showWaterfall) {
+                historyDetailScroll++;
+            } else {
+                historyTableState.selectNext(historyVisibleCount);
+                historyDetailScroll = 0;
+            }
         }
     }
 
@@ -770,8 +799,10 @@ class HistoryTab extends AbstractTab {
         boolean tracerActive = !traces.get().isEmpty();
         if (tracerActive && traceDetailView) {
             hint(spans, "Esc", "back");
+            if (!showWaterfall) {
+                hint(spans, "Tab", detailFocused ? "table" : "detail");
+            }
             hint(spans, TuiIcons.HINT_SCROLL, "navigate");
-            hint(spans, "PgUp/PgDn", "scroll");
             if (!showWaterfall && !traceWordWrap) {
                 hint(spans, TuiIcons.HINT_H, "h-scroll");
             }
@@ -792,8 +823,10 @@ class HistoryTab extends AbstractTab {
             hintLast(spans, "F5", "refresh");
         } else {
             hint(spans, "Esc", "back");
+            if (!showWaterfall) {
+                hint(spans, "Tab", detailFocused ? "table" : "detail");
+            }
             hint(spans, TuiIcons.HINT_SCROLL, "navigate");
-            hint(spans, "PgUp/PgDn", "scroll");
             if (!showWaterfall && !historyWordWrap) {
                 hint(spans, TuiIcons.HINT_H, "h-scroll");
             }
@@ -1264,12 +1297,16 @@ class HistoryTab extends AbstractTab {
 
         String stepTitle
                 = String.format(" Trace [%s] — %d steps ", 
TuiHelper.truncate(traceSelectedExchangeId, 30), steps.size());
+        boolean showFocus = !showWaterfall;
+        Style tableBorderStyle = showFocus && detailFocused ? Theme.muted() : 
Style.EMPTY.fg(Theme.accent());
+        Style tableHighlight = showFocus && detailFocused ? 
Theme.selectionBg().dim() : Theme.selectionBg();
         lastTraceStepArea = chunks.get(0);
         detailSplit.setBorderPos(chunks.get(1).y());
         int stepVisibleRows = Math.max(0, chunks.get(0).height() - 3);
         traceStepTableState.scrollToSelected(stepVisibleRows, rows);
         frame.renderStatefulWidget(
-                buildStepTable(rows, stepTitle, showDescription), 
chunks.get(0), traceStepTableState);
+                buildStepTable(rows, stepTitle, showDescription, 
tableBorderStyle, tableHighlight),
+                chunks.get(0), traceStepTableState);
         renderTableScrollbar(frame, lastTraceStepArea, traceStepTableState, 
traceStepScrollState,
                 steps.size());
 
@@ -1283,6 +1320,8 @@ class HistoryTab extends AbstractTab {
     }
 
     private void renderTraceStepDetail(Frame frame, Rect area, 
List<TraceEntry> steps) {
+        Style detailBorderStyle = detailFocused ? 
Style.EMPTY.fg(Theme.accent()) : Theme.muted();
+        Style detailTitleStyle = detailFocused ? Theme.title() : 
Style.EMPTY.fg(Theme.accent());
         Integer sel = traceStepTableState.selected();
 
         if (sel == null || sel < 0 || sel >= steps.size()) {
@@ -1291,7 +1330,8 @@ class HistoryTab extends AbstractTab {
                             .text(Text.from(Line.from(
                                     Span.styled(" Select a trace step to view 
details",
                                             Style.EMPTY.dim()))))
-                            
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).build())
+                            
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                                    .borderStyle(detailBorderStyle).build())
                             .build(),
                     area);
             return;
@@ -1327,7 +1367,8 @@ class HistoryTab extends AbstractTab {
 
         int[] scroll = { traceDetailScroll };
         int[] hScroll = { traceDetailHScroll };
-        renderDetailPanel(frame, area, lines, traceWordWrap, hScroll, scroll, 
traceDetailScrollState, " Detail ");
+        renderDetailPanel(frame, area, lines, traceWordWrap, hScroll, scroll, 
traceDetailScrollState,
+                " Detail ", detailBorderStyle, 
Title.from(Line.from(Span.styled(" Detail ", detailTitleStyle))));
         traceDetailScroll = scroll[0];
         traceDetailHScroll = hScroll[0];
     }
@@ -1542,12 +1583,16 @@ class HistoryTab extends AbstractTab {
         }
 
         Title historyTitle = buildHistoryTitle(current);
+        boolean showFocus = !showWaterfall;
+        Style tableBorderStyle = showFocus && detailFocused ? Theme.muted() : 
Style.EMPTY.fg(Theme.accent());
+        Style tableHighlight = showFocus && detailFocused ? 
Theme.selectionBg().dim() : Theme.selectionBg();
         lastHistoryTableArea = chunks.get(0);
         vSplit.setBorderPos(chunks.get(1).y());
         int histVisibleRows = Math.max(0, chunks.get(0).height() - 3);
         historyTableState.scrollToSelected(histVisibleRows, rows);
         frame.renderStatefulWidget(
-                buildStepTable(rows, historyTitle, showDescription), 
chunks.get(0), historyTableState);
+                buildStepTable(rows, historyTitle, showDescription, 
tableBorderStyle, tableHighlight),
+                chunks.get(0), historyTableState);
         renderTableScrollbar(frame, lastHistoryTableArea, historyTableState, 
historyTableScrollState,
                 current.size());
 
@@ -1561,6 +1606,8 @@ class HistoryTab extends AbstractTab {
     }
 
     private void renderHistoryDetail(Frame frame, Rect area, 
List<HistoryEntry> current) {
+        Style detailBorderStyle = detailFocused ? 
Style.EMPTY.fg(Theme.accent()) : Theme.muted();
+        Style detailTitleStyle = detailFocused ? Theme.title() : 
Style.EMPTY.fg(Theme.accent());
         Integer sel = historyTableState.selected();
 
         if (sel == null || sel < 0 || sel >= current.size()) {
@@ -1570,7 +1617,8 @@ class HistoryTab extends AbstractTab {
                                     Span.styled(" Select a history entry to 
view details",
                                             Style.EMPTY.dim()))))
                             
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
-                                    .title(" Detail ").build())
+                                    .borderStyle(detailBorderStyle)
+                                    .title(Title.from(Line.from(Span.styled(" 
Detail ", detailTitleStyle)))).build())
                             .build(),
                     area);
             return;
@@ -1606,7 +1654,8 @@ class HistoryTab extends AbstractTab {
 
         int[] scroll = { historyDetailScroll };
         int[] hScroll = { historyDetailHScroll };
-        renderDetailPanel(frame, area, lines, historyWordWrap, hScroll, 
scroll, historyDetailScrollState, " Detail ");
+        renderDetailPanel(frame, area, lines, historyWordWrap, hScroll, 
scroll, historyDetailScrollState,
+                " Detail ", detailBorderStyle, 
Title.from(Line.from(Span.styled(" Detail ", detailTitleStyle))));
         historyDetailScroll = scroll[0];
         historyDetailHScroll = hScroll[0];
     }
@@ -1964,6 +2013,12 @@ class HistoryTab extends AbstractTab {
     }
 
     private static Table buildStepTable(List<Row> rows, Object title, boolean 
descriptionMode) {
+        return buildStepTable(rows, title, descriptionMode, Style.EMPTY, 
Theme.selectionBg());
+    }
+
+    private static Table buildStepTable(
+            List<Row> rows, Object title, boolean descriptionMode,
+            Style borderStyle, Style highlightStyle) {
         Row header = Row.from(
                 rightCell("#", 3, Style.EMPTY.bold()),
                 Cell.from(Span.styled("", Style.EMPTY.bold())),
@@ -1974,8 +2029,9 @@ class HistoryTab extends AbstractTab {
                 Cell.from(Span.styled("BHPV", Style.EMPTY.bold())),
                 rightCell("ELAPSED", 10, Style.EMPTY.bold()));
         Block block = title instanceof Title t
-                ? 
Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title(t).build()
-                : 
Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title(title.toString()).build();
+                ? 
Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).borderStyle(borderStyle).title(t).build()
+                : 
Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).borderStyle(borderStyle)
+                        .title(title.toString()).build();
         return Table.builder()
                 .rows(rows)
                 .header(header)
@@ -1988,7 +2044,7 @@ class HistoryTab extends AbstractTab {
                         Constraint.fill(),
                         Constraint.length(4),
                         Constraint.length(11))
-                .highlightStyle(Theme.selectionBg())
+                .highlightStyle(highlightStyle)
                 .highlightSpacing(Table.HighlightSpacing.ALWAYS)
                 .block(block)
                 .build();
@@ -2188,8 +2244,17 @@ class HistoryTab extends AbstractTab {
     static void renderDetailPanel(
             Frame frame, Rect area, List<Line> lines,
             boolean wordWrap, int[] hScroll, int[] scroll, ScrollbarState 
scrollState, String title) {
-        Block.Builder bb = 
Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL);
-        if (title != null) {
+        renderDetailPanel(frame, area, lines, wordWrap, hScroll, scroll, 
scrollState, title, Style.EMPTY, null);
+    }
+
+    static void renderDetailPanel(
+            Frame frame, Rect area, List<Line> lines,
+            boolean wordWrap, int[] hScroll, int[] scroll, ScrollbarState 
scrollState,
+            String title, Style borderStyle, Title styledTitle) {
+        Block.Builder bb = 
Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).borderStyle(borderStyle);
+        if (styledTitle != null) {
+            bb.title(styledTitle);
+        } else if (title != null) {
             bb.title(title);
         }
         Block block = bb.build();
@@ -2313,6 +2378,21 @@ class HistoryTab extends AbstractTab {
         return new SelectionContext("table", items, sel != null ? sel : -1, 
items.size(), "History");
     }
 
+    @Override
+    public Boolean isDetailFocused() {
+        if (showWaterfall) {
+            return null;
+        }
+        boolean tracerActive = !traces.get().isEmpty();
+        if (tracerActive && traceDetailView) {
+            return detailFocused;
+        }
+        if (!tracerActive) {
+            return detailFocused;
+        }
+        return null;
+    }
+
     @Override
     public String description() {
         return "Message history trace showing route path, headers, body, and 
timing";
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java
index 5130e4767da6..60436bbcffae 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java
@@ -329,6 +329,11 @@ class McpFacade {
         return tab != null ? tab.getSelectionContext() : null;
     }
 
+    Boolean isDetailFocused() {
+        MonitorTab tab = bridge.activeTab();
+        return tab != null ? tab.isDetailFocused() : null;
+    }
+
     List<String> getIntegrationNames() {
         return data.get().stream()
                 .filter(i -> !i.vanishing)
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorTab.java
index 506767cc5d6a..c965a750fd79 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorTab.java
@@ -89,4 +89,8 @@ interface MonitorTab {
     default boolean isOverlayActive() {
         return false;
     }
+
+    default Boolean isDetailFocused() {
+        return null;
+    }
 }
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 a3333a65d253..ba959cc04d49 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
@@ -35,6 +35,7 @@ import dev.tamboui.tui.event.MouseEvent;
 import dev.tamboui.widgets.block.Block;
 import dev.tamboui.widgets.block.BorderType;
 import dev.tamboui.widgets.block.Borders;
+import dev.tamboui.widgets.block.Title;
 import dev.tamboui.widgets.paragraph.Paragraph;
 import dev.tamboui.widgets.table.Cell;
 import dev.tamboui.widgets.table.Row;
@@ -57,6 +58,7 @@ class ThreadsTab extends AbstractTableTab {
     private int threadCount;
     private int peakThreadCount;
     private int traceScroll;
+    private boolean detailFocused;
     private String lastPid;
     private long lastRefreshTime;
 
@@ -95,13 +97,19 @@ class ThreadsTab extends AbstractTableTab {
 
     @Override
     protected boolean handleTabKeyEvent(KeyEvent ke) {
-        if (ke.isPageUp() || ke.isKey(KeyCode.PAGE_UP)) {
-            traceScroll = Math.max(0, traceScroll - 10);
+        if (ke.isKey(KeyCode.TAB)) {
+            detailFocused = !detailFocused;
             return true;
         }
-        if (ke.isPageDown() || ke.isKey(KeyCode.PAGE_DOWN)) {
-            traceScroll += 10;
-            return true;
+        if (detailFocused) {
+            if (ke.isPageUp() || ke.isKey(KeyCode.PAGE_UP)) {
+                traceScroll = Math.max(0, traceScroll - 10);
+                return true;
+            }
+            if (ke.isPageDown() || ke.isKey(KeyCode.PAGE_DOWN)) {
+                traceScroll += 10;
+                return true;
+            }
         }
         if (ke.isCharIgnoreCase('f')) {
             filter = (filter + 1) % FILTER_LABELS.length;
@@ -121,14 +129,22 @@ class ThreadsTab extends AbstractTableTab {
 
     @Override
     public void navigateUp() {
-        super.navigateUp();
-        traceScroll = 0;
+        if (detailFocused) {
+            traceScroll = Math.max(0, traceScroll - 1);
+        } else {
+            super.navigateUp();
+            traceScroll = 0;
+        }
     }
 
     @Override
     public void navigateDown() {
-        super.navigateDown();
-        traceScroll = 0;
+        if (detailFocused) {
+            traceScroll++;
+        } else {
+            super.navigateDown();
+            traceScroll = 0;
+        }
     }
 
     @Override
@@ -184,6 +200,9 @@ class ThreadsTab extends AbstractTableTab {
         String title = String.format(" Threads [%d/%d] peak:%d filter:%s ",
                 visible.size(), threadCount, peakThreadCount, 
FILTER_LABELS[filter]);
 
+        Style tableBorderStyle = detailFocused ? Theme.muted() : 
Style.EMPTY.fg(Theme.accent());
+        Style tableTitleStyle = detailFocused ? Style.EMPTY.fg(Theme.accent()) 
: Theme.title();
+
         Table table = Table.builder()
                 .rows(rows)
                 .header(Row.from(
@@ -198,9 +217,11 @@ class ThreadsTab extends AbstractTableTab {
                         Constraint.length(16),
                         Constraint.length(14),
                         Constraint.length(15))
-                .highlightStyle(Theme.selectionBg())
+                .highlightStyle(detailFocused ? Theme.selectionBg().dim() : 
Theme.selectionBg())
                 .highlightSpacing(Table.HighlightSpacing.ALWAYS)
-                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title(title).build())
+                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                        .borderStyle(tableBorderStyle)
+                        .title(Title.from(Line.from(Span.styled(title, 
tableTitleStyle)))).build())
                 .build();
 
         lastTableArea = area;
@@ -209,12 +230,17 @@ class ThreadsTab extends AbstractTableTab {
     }
 
     private void renderTrace(Frame frame, Rect area, List<ThreadData> visible) 
{
+        Style detailBorderStyle = detailFocused ? 
Style.EMPTY.fg(Theme.accent()) : Theme.muted();
+        Style detailTitleStyle = detailFocused ? Theme.title() : 
Style.EMPTY.fg(Theme.accent());
+
         Integer sel = tableState.selected();
         if (sel == null || sel < 0 || sel >= visible.size()) {
             frame.renderWidget(
                     Paragraph.builder()
                             .text(Text.from(Line.from(Span.styled(" Select a 
thread", Style.EMPTY.dim()))))
-                            
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title("
 Stack Trace ")
+                            
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                                    .borderStyle(detailBorderStyle)
+                                    .title(Title.from(Line.from(Span.styled(" 
Stack Trace ", detailTitleStyle))))
                                     .build())
                             .build(),
                     area);
@@ -229,7 +255,9 @@ class ThreadsTab extends AbstractTableTab {
             frame.renderWidget(
                     Paragraph.builder()
                             .text(Text.from(Line.from(Span.styled(" No stack 
trace available", Style.EMPTY.dim()))))
-                            
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title(title).build())
+                            
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                                    .borderStyle(detailBorderStyle)
+                                    
.title(Title.from(Line.from(Span.styled(title, detailTitleStyle)))).build())
                             .build(),
                     area);
             return;
@@ -256,7 +284,9 @@ class ThreadsTab extends AbstractTableTab {
         frame.renderWidget(
                 Paragraph.builder()
                         .text(Text.from(lines))
-                        
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title(title).build())
+                        
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                                .borderStyle(detailBorderStyle)
+                                .title(Title.from(Line.from(Span.styled(title, 
detailTitleStyle)))).build())
                         .build(),
                 area);
     }
@@ -266,6 +296,7 @@ class ThreadsTab extends AbstractTableTab {
         hint(spans, "Esc", "back");
         hint(spans, "s", "sort");
         hint(spans, "f", "filter [" + FILTER_LABELS[filter] + "]");
+        hint(spans, "Tab", detailFocused ? "table" : "trace");
         hint(spans, TuiIcons.HINT_SCROLL, "navigate");
         hintLast(spans, "PgUp/Dn", "scroll");
     }
@@ -281,6 +312,11 @@ class ThreadsTab extends AbstractTableTab {
         return new SelectionContext("table", items, sel != null ? sel : -1, 
items.size(), "Threads");
     }
 
+    @Override
+    public Boolean isDetailFocused() {
+        return detailFocused;
+    }
+
     private List<ThreadData> sortedThreads() {
         List<ThreadData> result = new ArrayList<>();
         for (ThreadData t : allThreads) {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiToolRegistry.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiToolRegistry.java
index 5d6b5f9d77da..77c6b2fba371 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiToolRegistry.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiToolRegistry.java
@@ -155,7 +155,8 @@ class TuiToolRegistry {
                                   + "Shows exactly what the user sees in their 
terminal. "
                                   + "Use ansi=true to include ANSI color codes 
for color-related questions. "
                                   + "Also returns a 'selection' field with 
structured metadata about the active list/table "
-                                  + "(type, items, selectedIndex, totalItems, 
label) when available.",
+                                  + "(type, items, selectedIndex, totalItems, 
label) when available. "
+                                  + "On tabs with master/detail panels, 
includes 'detailFocused' (true=detail, false=table).",
                 Map.of("ansi", propDef("boolean", "Include ANSI color codes in 
the output (default false)")))));
         tools.add(toToolDef(toolDef(
                 "tui_get_events",
@@ -168,7 +169,10 @@ class TuiToolRegistry {
                                  + "and integration count. "
                                  + "Includes a 'selection' field with 
structured metadata about the active list/table. "
                                  + "captionVisible indicates if a caption 
overlay is on screen. "
-                                 + "keystrokesVisible indicates if the 
keystroke overlay is on; toggle with Ctrl+K.",
+                                 + "keystrokesVisible indicates if the 
keystroke overlay is on; toggle with Ctrl+K. "
+                                 + "detailFocused (boolean, present on tabs 
with master/detail panels) indicates "
+                                 + "which panel has focus: true=detail panel, 
false=table panel. "
+                                 + "Press Tab to toggle focus. Up/Down and 
PgUp/PgDn operate on the focused panel.",
                 Map.of())));
         tools.add(toToolDef(toolDef(
                 "tui_show_caption",
@@ -1619,6 +1623,10 @@ class TuiToolRegistry {
             sel.put("items", items);
             result.put("selection", sel);
         }
+        Boolean detailFocused = facade.isDetailFocused();
+        if (detailFocused != null) {
+            result.put("detailFocused", detailFocused);
+        }
     }
 
     private void addFooterActions(JsonObject result) {

Reply via email to