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 f57bf9309579 CAMEL-23831: Refactor Beans tab with always-visible 
detail panel and smart filtering in camel-tui CAMEL-23831: Add --scope option 
to camel cmd bean and / text filter to Beans tab in camel-tui CAMEL-23831: 
Auto-refresh Threads tab every 5 seconds in camel-tui CAMEL-23831: 
Always-visible stack trace detail panel in Threads tab in camel-tui 
CAMEL-23831: Fix BeansTabRenderTest for updated footer hints in camel-tui
f57bf9309579 is described below

commit f57bf93095797e6182cbf71762eedd1e033beb3d
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat Jul 4 13:37:51 2026 +0200

    CAMEL-23831: Refactor Beans tab with always-visible detail panel and smart 
filtering in camel-tui
    CAMEL-23831: Add --scope option to camel cmd bean and / text filter to 
Beans tab in camel-tui
    CAMEL-23831: Auto-refresh Threads tab every 5 seconds in camel-tui
    CAMEL-23831: Always-visible stack trace detail panel in Threads tab in 
camel-tui
    CAMEL-23831: Fix BeansTabRenderTest for updated footer hints in camel-tui
    
    Co-Authored-By: Claude <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../pages/jbang-commands/camel-jbang-get-bean.adoc |   1 +
 .../META-INF/camel-jbang-commands-metadata.json    |   2 +-
 .../jbang/core/commands/action/CamelBeanDump.java  |  65 +++-
 .../dsl/jbang/core/commands/tui/BeansTab.java      | 331 +++++++++++++--------
 .../dsl/jbang/core/commands/tui/CamelMonitor.java  |   5 +-
 .../dsl/jbang/core/commands/tui/TabRegistry.java   |   4 +
 .../dsl/jbang/core/commands/tui/ThreadsTab.java    | 133 +++------
 .../core/commands/tui/BeansTabRenderTest.java      |   8 +-
 8 files changed, 334 insertions(+), 215 deletions(-)

diff --git 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-get-bean.adoc 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-get-bean.adoc
index 32f601f4b709..d3574f8838e4 100644
--- 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-get-bean.adoc
+++ 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-get-bean.adoc
@@ -24,6 +24,7 @@ camel get bean [options]
 | `--internal` | Include internal Camel beans | false | boolean
 | `--nulls` | Include null values | true | boolean
 | `--properties` | Show bean properties | true | boolean
+| `--scope` | Filter beans by scope: all, user (excludes 
Camel/Spring/Quarkus/JDK), camel, spring, quarkus | all | String
 | `--sort` | Sort by name or type | name | String
 | `-h,--help` | Display the help and sub-commands |  | boolean
 |===
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
 
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
index d30315094616..9a674d88c01b 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
@@ -15,7 +15,7 @@
     { "name": "eval", "fullName": "eval", "description": "Evaluate Camel 
expressions and scripts", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.EvalCommand", "options": [ { "names": 
"-h,--help", "description": "Display the help and sub-commands", "javaType": 
"boolean", "type": "boolean" } ], "subcommands": [ { "name": "expression", 
"fullName": "eval expression", "description": "Evaluates Camel expression", 
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.EvalEx [...]
     { "name": "explain", "fullName": "explain", "description": "Explain what a 
Camel route does using AI\/LLM", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.Explain", "options": [ { "names": 
"--api-key", "description": "API key for authentication. Also reads 
ANTHROPIC_API_KEY, OPENAI_API_KEY, or LLM_API_KEY env vars", "javaType": 
"java.lang.String", "type": "string" }, { "names": "--api-type", "description": 
"API type: 'ollama', 'openai' (OpenAI-compatible), or 'anthropic' (A [...]
     { "name": "export", "fullName": "export", "description": "Export to other 
runtimes (Camel Main, Spring Boot, or Quarkus)", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.Export", "options": [ { "names": 
"--build-property", "description": "Maven build properties, ex. 
--build-property=prop1=foo", "javaType": "java.util.List", "type": "array" }, { 
"names": "--camel-spring-boot-version", "description": "Camel version to use 
with Spring Boot", "javaType": "java.lang.String", "ty [...]
-    { "name": "get", "fullName": "get", "description": "Get status of Camel 
integrations", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.process.CamelStatus", "options": [ { 
"names": "--watch", "description": "Execute periodically and showing output 
fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": 
"-h,--help", "description": "Display the help and sub-commands", "javaType": 
"boolean", "type": "boolean" } ], "subcommands": [ { "name": "bean", 
"fullName": "get  [...]
+    { "name": "get", "fullName": "get", "description": "Get status of Camel 
integrations", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.process.CamelStatus", "options": [ { 
"names": "--watch", "description": "Execute periodically and showing output 
fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": 
"-h,--help", "description": "Display the help and sub-commands", "javaType": 
"boolean", "type": "boolean" } ], "subcommands": [ { "name": "bean", 
"fullName": "get  [...]
     { "name": "harden", "fullName": "harden", "description": "Suggest security 
hardening for Camel routes using AI\/LLM", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.Harden", "options": [ { "names": 
"--api-key", "description": "API key for authentication. Also reads 
OPENAI_API_KEY or LLM_API_KEY env vars", "javaType": "java.lang.String", 
"type": "string" }, { "names": "--api-type", "description": "API type: 'ollama' 
or 'openai' (OpenAI-compatible)", "defaultValue": "ollama", [...]
     { "name": "hawtio", "fullName": "hawtio", "description": "Launch Hawtio 
web console", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.process.Hawtio", "options": [ { 
"names": "--host", "description": "Hostname to bind the Hawtio web console to", 
"defaultValue": "127.0.0.1", "javaType": "java.lang.String", "type": "string" 
}, { "names": "--openUrl", "description": "To automatic open Hawtio web console 
in the web browser", "defaultValue": "true", "javaType": "boolean", "type": 
[...]
     { "name": "infra", "fullName": "infra", "description": "List and Run 
external services for testing and prototyping", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.infra.InfraCommand", "options": [ { 
"names": "--json", "description": "Output in JSON Format", "javaType": 
"boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display 
the help and sub-commands", "javaType": "boolean", "type": "boolean" } ], 
"subcommands": [ { "name": "get", "fullName": "infra  [...]
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelBeanDump.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelBeanDump.java
index 4ffcff7cb5f9..11f68876e3c3 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelBeanDump.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelBeanDump.java
@@ -39,7 +39,8 @@ import picocli.CommandLine.Command;
          footer = {
                  "%nExamples:",
                  "  camel cmd bean",
-                 "  camel cmd bean --filter=myBean" })
+                 "  camel cmd bean --filter=myBean",
+                 "  camel cmd bean --scope=user" })
 public class CamelBeanDump extends ActionBaseCommand {
 
     public static class NameTypeCompletionCandidates implements 
Iterable<String> {
@@ -54,6 +55,18 @@ public class CamelBeanDump extends ActionBaseCommand {
 
     }
 
+    public static class ScopeCompletionCandidates implements Iterable<String> {
+
+        public ScopeCompletionCandidates() {
+        }
+
+        @Override
+        public Iterator<String> iterator() {
+            return List.of("all", "user", "camel", "spring", 
"quarkus").iterator();
+        }
+
+    }
+
     @CommandLine.Parameters(description = "Name or pid of running Camel 
integration", arity = "0..1")
     String name = "*";
 
@@ -73,6 +86,11 @@ public class CamelBeanDump extends ActionBaseCommand {
                         description = "Include null values", defaultValue = 
"true")
     boolean nulls;
 
+    @CommandLine.Option(names = { "--scope" }, completionCandidates = 
ScopeCompletionCandidates.class,
+                        description = "Filter beans by scope: all, user 
(excludes Camel/Spring/Quarkus/JDK), camel, spring, quarkus",
+                        defaultValue = "all")
+    String scope;
+
     @CommandLine.Option(names = { "--internal" },
                         description = "Include internal Camel beans", 
defaultValue = "false")
     boolean internal;
@@ -113,7 +131,7 @@ public class CamelBeanDump extends ActionBaseCommand {
         }
         root.put("properties", properties);
         root.put("nulls", nulls);
-        root.put("internal", internal);
+        root.put("internal", !"all".equals(scope) || internal);
         Path f = getActionFile(Long.toString(pid));
         PathUtils.writeTextSafely(root.toJson(), f);
 
@@ -157,6 +175,11 @@ public class CamelBeanDump extends ActionBaseCommand {
             return 1;
         }
 
+        // filter by scope
+        if (!"all".equals(scope)) {
+            rows.removeIf(r -> !matchesScope(r, scope));
+        }
+
         // sort rows
         rows.sort(this::sortRow);
         if (properties) {
@@ -225,6 +248,44 @@ public class CamelBeanDump extends ActionBaseCommand {
         return "null";
     }
 
+    private static boolean matchesScope(Row r, String scope) {
+        return switch (scope) {
+            case "user" -> !isFrameworkBean(r);
+            case "camel" -> isCamelBean(r);
+            case "spring" -> isSpringBean(r);
+            case "quarkus" -> isQuarkusBean(r);
+            default -> true;
+        };
+    }
+
+    private static boolean isFrameworkBean(Row r) {
+        if (isCamelBean(r) || isSpringBean(r) || isQuarkusBean(r)) {
+            return true;
+        }
+        return r.type != null && r.type.startsWith("java.");
+    }
+
+    private static boolean isCamelBean(Row r) {
+        if (r.name != null && (r.name.startsWith("camel-") || 
r.name.startsWith("org.apache.camel"))) {
+            return true;
+        }
+        return r.type != null && r.type.startsWith("org.apache.camel");
+    }
+
+    private static boolean isSpringBean(Row r) {
+        if (r.name != null && r.name.startsWith("org.springframework")) {
+            return true;
+        }
+        return r.type != null && r.type.startsWith("org.springframework");
+    }
+
+    private static boolean isQuarkusBean(Row r) {
+        if (r.name != null && r.name.startsWith("io.quarkus")) {
+            return true;
+        }
+        return r.type != null && r.type.startsWith("io.quarkus");
+    }
+
     protected int sortRow(Row o1, Row o2) {
         String s = sort;
         int negate = 1;
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 072850109e1a..35a7e3de6824 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
@@ -37,6 +37,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.input.TextInputState;
 import dev.tamboui.widgets.paragraph.Paragraph;
 import dev.tamboui.widgets.table.Cell;
 import dev.tamboui.widgets.table.Row;
@@ -51,9 +52,11 @@ class BeansTab extends AbstractTableTab {
 
     private final AtomicBoolean loading = new AtomicBoolean(false);
 
-    private boolean showInternal;
+    private int filterIndex;
+    private boolean filterInputActive;
+    private TextInputState filterInputState = new TextInputState("");
+    private String filterTerm;
     private List<BeanData> allBeans = Collections.emptyList();
-    private boolean showDetail;
     private int detailScroll;
     private String lastPid;
 
@@ -81,66 +84,67 @@ class BeansTab extends AbstractTableTab {
     @Override
     public void onIntegrationChanged() {
         allBeans = Collections.emptyList();
-        showDetail = false;
         detailScroll = 0;
         lastPid = null;
     }
 
     @Override
     public boolean handleKeyEvent(KeyEvent ke) {
-        if (showDetail) {
-            if (ke.isUp()) {
-                detailScroll = Math.max(0, detailScroll - 1);
-                return true;
-            }
-            if (ke.isDown()) {
-                detailScroll++;
-                return true;
-            }
-            if (ke.isPageUp() || ke.isKey(KeyCode.PAGE_UP)) {
-                detailScroll = Math.max(0, detailScroll - 20);
-                return true;
-            }
-            if (ke.isPageDown() || ke.isKey(KeyCode.PAGE_DOWN)) {
-                detailScroll += 20;
-                return true;
-            }
-            return false;
+        if (filterInputActive) {
+            return handleFilterInput(ke);
+        }
+        if (ke.isChar('/')) {
+            filterInputActive = true;
+            filterInputState = new TextInputState(filterTerm != null ? 
filterTerm : "");
+            return true;
         }
         return super.handleKeyEvent(ke);
     }
 
-    @Override
-    protected boolean handleTabKeyEvent(KeyEvent ke) {
-        if (ke.isConfirm()) {
-            showDetail = !showDetail;
-            detailScroll = 0;
-            return true;
-        }
-        if (ke.isCharIgnoreCase('i')) {
-            showInternal = !showInternal;
+    private boolean handleFilterInput(KeyEvent ke) {
+        if (ke.isKey(KeyCode.ESCAPE)) {
+            filterInputActive = false;
             return true;
         }
-        if (ke.isCharIgnoreCase('r')) {
-            loadBeans();
+        if (ke.isConfirm()) {
+            String text = filterInputState.text().trim();
+            filterTerm = text.isEmpty() ? null : text;
+            filterInputActive = false;
+            tableState.select(0);
+            detailScroll = 0;
             return true;
         }
-        return false;
+        FormHelper.handleTextInput(ke, filterInputState);
+        return true;
+    }
+
+    boolean isFilterInputActive() {
+        return filterInputActive;
     }
 
     @Override
-    public boolean handleMouseEvent(MouseEvent me, Rect area) {
-        if (!showDetail) {
-            List<BeanData> visible = sortedBeans();
-            return handleTableClick(me, lastTableArea, tableState, 
visible.size());
+    public boolean handleEscape() {
+        if (filterTerm != null) {
+            filterTerm = null;
+            tableState.select(0);
+            detailScroll = 0;
+            return true;
         }
         return false;
     }
 
     @Override
-    public boolean handleEscape() {
-        if (showDetail) {
-            showDetail = false;
+    protected boolean handleTabKeyEvent(KeyEvent ke) {
+        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;
             return true;
         }
         return false;
@@ -148,17 +152,24 @@ class BeansTab extends AbstractTableTab {
 
     @Override
     public void navigateUp() {
-        if (!showDetail) {
-            tableState.selectPrevious();
-        }
+        tableState.selectPrevious();
+        detailScroll = 0;
     }
 
     @Override
     public void navigateDown() {
-        if (!showDetail) {
-            List<BeanData> visible = sortedBeans();
-            tableState.selectNext(visible.size());
+        List<BeanData> visible = sortedBeans();
+        tableState.selectNext(visible.size());
+        detailScroll = 0;
+    }
+
+    @Override
+    public boolean handleMouseEvent(MouseEvent me, Rect area) {
+        boolean handled = super.handleMouseEvent(me, area);
+        if (handled) {
+            detailScroll = 0;
         }
+        return handled;
     }
 
     @Override
@@ -175,15 +186,11 @@ class BeansTab extends AbstractTableTab {
 
         List<BeanData> visible = sortedBeans();
 
-        if (showDetail) {
-            List<Rect> chunks = Layout.vertical()
-                    .constraints(Constraint.percentage(40), Constraint.fill())
-                    .split(area);
-            renderTable(frame, chunks.get(0), visible);
-            renderDetail(frame, chunks.get(1), visible);
-        } else {
-            renderTable(frame, area, visible);
-        }
+        List<Rect> chunks = Layout.vertical()
+                .constraints(Constraint.fill(), Constraint.percentage(30))
+                .split(area);
+        renderTable(frame, chunks.get(0), visible);
+        renderDetail(frame, chunks.get(1), visible);
     }
 
     private void renderTable(Frame frame, Rect area, List<BeanData> visible) {
@@ -195,28 +202,25 @@ class BeansTab extends AbstractTableTab {
 
             rows.add(Row.from(
                     Cell.from(Span.styled(b.name != null ? b.name : "", 
Style.EMPTY.fg(Color.CYAN))),
-                    Cell.from(Span.styled(shortType, Style.EMPTY)),
-                    Cell.from(Span.styled(type, Style.EMPTY.dim()))));
+                    Cell.from(Span.styled(shortType, Style.EMPTY))));
         }
 
         if (rows.isEmpty()) {
-            rows.add(emptyRow("No beans", 3));
+            rows.add(emptyRow("No beans", 2));
         }
 
-        String title = String.format(" Beans [%d] sort:%s ", visible.size(), 
sort);
-        if (showInternal) {
-            title = String.format(" Beans [%d] sort:%s internal:on ", 
visible.size(), sort);
-        }
+        String mode = filterModes()[filterIndex];
+        String title = filterTerm != null
+                ? String.format(" Beans [%d] sort:%s scope:%s filter:\"%s\" ", 
visible.size(), sort, mode, filterTerm)
+                : String.format(" Beans [%d] sort:%s scope:%s ", 
visible.size(), sort, mode);
 
         Table table = Table.builder()
                 .rows(rows)
                 .header(Row.from(
                         Cell.from(Span.styled(sortLabel("NAME", "name"), 
sortStyle("name"))),
-                        Cell.from(Span.styled(sortLabel("TYPE", "type"), 
sortStyle("type"))),
-                        Cell.from(Span.styled("PACKAGE", Style.EMPTY.bold()))))
+                        Cell.from(Span.styled(sortLabel("TYPE", "type"), 
sortStyle("type")))))
                 .widths(
-                        Constraint.length(30),
-                        Constraint.length(30),
+                        Constraint.percentage(55),
                         Constraint.fill())
                 .highlightStyle(Theme.selectionBg())
                 .highlightSpacing(Table.HighlightSpacing.ALWAYS)
@@ -234,52 +238,67 @@ class BeansTab extends AbstractTableTab {
             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("
 Properties ")
-                                    .build())
+                            
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                                    .title(" Bean Detail ").build())
                             .build(),
                     area);
             return;
         }
 
         BeanData bean = visible.get(sel);
-        String title = " " + bean.name + " (" + (bean.type != null ? bean.type 
: "") + ") ";
+        String title = " " + (bean.name != null ? bean.name : "") + " ";
 
-        if (bean.properties == null || bean.properties.isEmpty()) {
-            frame.renderWidget(
-                    Paragraph.builder()
-                            .text(Text.from(Line.from(Span.styled(" No 
properties", Style.EMPTY.dim()))))
-                            
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title(title).build())
-                            .build(),
-                    area);
-            return;
+        List<Line> lines = new ArrayList<>();
+
+        // full type
+        if (bean.type != null && !bean.type.isEmpty()) {
+            lines.add(Line.from(
+                    Span.styled("  Type: ", Style.EMPTY.dim()),
+                    Span.styled(bean.type, Style.EMPTY.fg(Color.WHITE))));
+        }
+
+        // properties
+        if (bean.properties != null && !bean.properties.isEmpty()) {
+            lines.add(Line.from(Span.raw("")));
+            lines.add(Line.from(Span.styled("  Properties:", 
Style.EMPTY.dim())));
+
+            int maxNameLen = 0;
+            for (BeanData.Property prop : bean.properties) {
+                if (prop.name != null) {
+                    maxNameLen = Math.max(maxNameLen, prop.name.length());
+                }
+            }
+            int nameWidth = Math.max(15, maxNameLen + 1);
+
+            for (BeanData.Property prop : bean.properties) {
+                String propType = prop.type != null ? prop.type : "";
+                int dot = propType.lastIndexOf('.');
+                String shortPropType = dot >= 0 ? propType.substring(dot + 1) 
: propType;
+                String value = prop.value != null ? prop.value : "null";
+
+                lines.add(Line.from(
+                        Span.styled("  " + String.format("%-" + nameWidth + 
"s", prop.name), Style.EMPTY.fg(Color.CYAN)),
+                        Span.styled(String.format("%-15s", shortPropType), 
Style.EMPTY.dim()),
+                        Span.styled(" = ", Style.EMPTY.dim()),
+                        Span.styled(value, "null".equals(value) ? 
Style.EMPTY.dim() : Style.EMPTY.fg(Color.WHITE))));
+            }
+        } else {
+            lines.add(Line.from(Span.raw("")));
+            lines.add(Line.from(Span.styled("  No properties", 
Style.EMPTY.dim())));
         }
 
         int visibleLines = area.height() - 2;
         if (visibleLines < 1) {
             visibleLines = 1;
         }
-        int maxScroll = Math.max(0, bean.properties.size() - visibleLines);
+        int maxScroll = Math.max(0, lines.size() - visibleLines);
         detailScroll = Math.min(detailScroll, maxScroll);
-
-        int end = Math.min(detailScroll + visibleLines, 
bean.properties.size());
-        List<Line> lines = new ArrayList<>();
-        for (int i = detailScroll; i < end; i++) {
-            BeanData.Property prop = bean.properties.get(i);
-            String propType = prop.type != null ? prop.type : "";
-            int dot = propType.lastIndexOf('.');
-            String shortPropType = dot >= 0 ? propType.substring(dot + 1) : 
propType;
-            String value = prop.value != null ? prop.value : "null";
-
-            lines.add(Line.from(
-                    Span.styled("  " + String.format("%-25s", prop.name), 
Style.EMPTY.fg(Color.CYAN)),
-                    Span.styled(String.format("%-15s", shortPropType), 
Style.EMPTY.dim()),
-                    Span.styled(" = ", Style.EMPTY.dim()),
-                    Span.styled(value, "null".equals(value) ? 
Style.EMPTY.dim() : Style.EMPTY.fg(Color.WHITE))));
-        }
+        int end = Math.min(detailScroll + visibleLines, lines.size());
+        List<Line> visibleContent = lines.subList(detailScroll, end);
 
         frame.renderWidget(
                 Paragraph.builder()
-                        .text(Text.from(lines))
+                        .text(Text.from(visibleContent))
                         
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title(title).build())
                         .build(),
                 area);
@@ -287,14 +306,23 @@ class BeansTab extends AbstractTableTab {
 
     @Override
     public void renderFooter(List<Span> spans) {
-        super.renderFooter(spans);
-        hint(spans, "i", "internal" + (showInternal ? " [on]" : ""));
-        hint(spans, "r", "refresh");
-        if (showDetail) {
-            hintLast(spans, "↑↓", "scroll");
+        if (filterInputActive) {
+            spans.add(Span.styled(" /", Style.EMPTY.fg(Color.YELLOW).bold()));
+            spans.add(Span.raw(filterInputState.text() + "█  "));
+            hint(spans, "Enter", "filter");
+            hintLast(spans, "Esc", "cancel");
+            return;
+        }
+        hint(spans, "Esc", filterTerm != null ? "clear" : "back");
+        hint(spans, "f", "scope [" + filterModes()[filterIndex] + "]");
+        if (filterTerm != null) {
+            spans.add(Span.styled("  /", Style.EMPTY.fg(Color.YELLOW).bold()));
+            spans.add(Span.raw("\"" + filterTerm + "\"  "));
         } else {
-            hintLast(spans, "Enter", "detail");
+            hint(spans, "/", "filter");
         }
+        hint(spans, "↑↓", "navigate");
+        hintLast(spans, "PgUp/Dn", "scroll");
     }
 
     @Override
@@ -308,10 +336,39 @@ class BeansTab extends AbstractTableTab {
         return new SelectionContext("table", items, sel != null ? sel : -1, 
items.size(), "Beans");
     }
 
+    private String[] filterModes() {
+        IntegrationInfo info = ctx.findSelectedIntegration();
+        String platform = info != null ? info.platform : null;
+        if ("Spring Boot".equals(platform)) {
+            return new String[] { "all", "user", "camel", "spring" };
+        } else if ("Quarkus".equals(platform)) {
+            return new String[] { "all", "user", "camel", "quarkus" };
+        }
+        return new String[] { "all", "user", "camel" };
+    }
+
     private List<BeanData> sortedBeans() {
+        String[] modes = filterModes();
+        if (filterIndex >= modes.length) {
+            filterIndex = 0;
+        }
+        String mode = modes[filterIndex];
+        String ft = filterTerm != null ? filterTerm.toLowerCase() : null;
         List<BeanData> result = new ArrayList<>();
         for (BeanData b : allBeans) {
-            if (!showInternal && b.internal) {
+            if ("user".equals(mode) && isFrameworkBean(b)) {
+                continue;
+            }
+            if ("camel".equals(mode) && !b.internal) {
+                continue;
+            }
+            if ("spring".equals(mode) && !isSpringBean(b)) {
+                continue;
+            }
+            if ("quarkus".equals(mode) && !isQuarkusBean(b)) {
+                continue;
+            }
+            if (ft != null && !matchesFilter(b, ft)) {
                 continue;
             }
             result.add(b);
@@ -400,6 +457,42 @@ class BeansTab extends AbstractTableTab {
         }
     }
 
+    private static boolean matchesFilter(BeanData b, String filter) {
+        if (b.name != null && b.name.toLowerCase().contains(filter)) {
+            return true;
+        }
+        return b.type != null && b.type.toLowerCase().contains(filter);
+    }
+
+    private static boolean isFrameworkBean(BeanData b) {
+        if (isInternalBean(b.name, b.type)) {
+            return true;
+        }
+        if (b.name != null && (b.name.startsWith("org.springframework") || 
b.name.startsWith("io.quarkus"))) {
+            return true;
+        }
+        if (b.type != null
+                && (b.type.startsWith("org.springframework") || 
b.type.startsWith("io.quarkus")
+                        || b.type.startsWith("java."))) {
+            return true;
+        }
+        return false;
+    }
+
+    private static boolean isSpringBean(BeanData b) {
+        if (b.name != null && b.name.startsWith("org.springframework")) {
+            return true;
+        }
+        return b.type != null && b.type.startsWith("org.springframework");
+    }
+
+    private static boolean isQuarkusBean(BeanData b) {
+        if (b.name != null && b.name.startsWith("io.quarkus")) {
+            return true;
+        }
+        return b.type != null && b.type.startsWith("io.quarkus");
+    }
+
     private static boolean isInternalBean(String name, String type) {
         if (name == null) {
             return false;
@@ -451,33 +544,33 @@ class BeansTab extends AbstractTableTab {
                 ## Table Columns
 
                 - **NAME** — Bean name used to look it up from routes. In a 
route you reference this with `.bean("myService")` or `${bean:myService}`
-                - **TYPE** — Java class of the bean (e.g., 
`com.example.MyService`, `java.util.HashMap`)
-
-                ## Example Screen
-
-                ```
-                 NAME              TYPE
-                 greeter           com.example.Greeter
-                 myDataSource      com.zaxxer.hikari.HikariDataSource
-                 restConfiguration org.apache.camel.spi.RestConfiguration
-                 json-jackson      
org.apache.camel.component.jackson.JacksonDataFormat
-                ```
+                - **TYPE** — Short Java class name of the bean (e.g., 
`Greeter`, `HikariDataSource`)
 
                 ## Detail View
 
-                Press `Enter` on a bean to inspect its properties and current 
values.
-                Camel uses Java bean introspection to read property values, 
showing
-                you the runtime state of each bean. This is useful for 
verifying
-                configuration — for example, checking that a database 
connection pool
-                has the correct URL, or that a data format is configured with 
the
+                The detail panel at the bottom shows the full type (including 
package)
+                and the bean's properties with their current values. This is 
useful for
+                verifying configuration — for example, checking that a 
database connection
+                pool has the correct URL, or that a data format is configured 
with the
                 right options.
 
+                ## Filter Modes
+
+                Press `f` to cycle through filter modes:
+
+                - **all** — show all beans (including framework internals)
+                - **user** — only your application beans (excludes Camel, 
Spring, Quarkus, and JDK beans)
+                - **camel** — only Camel internal beans
+                - **spring** — only Spring Framework beans (Spring Boot apps 
only)
+                - **quarkus** — only Quarkus beans (Quarkus apps only)
+
                 ## Keys
 
                 - `Up/Down` — select bean
-                - `Enter` — view bean properties
+                - `PgUp/PgDn` — scroll detail panel
                 - `s` — cycle sort column
                 - `S` — reverse sort order
+                - `f` — cycle filter
                 - `Esc` — back
                 """;
     }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
index 32a1d23a9dac..cd21ef80a14d 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
@@ -566,10 +566,13 @@ public class CamelMonitor extends CamelCommand {
         boolean spanFilterActive = tabRegistry.selectedTabIndex() == TAB_MORE
                 && tabRegistry.getActiveMoreTab() == tabRegistry.spansTab()
                 && tabRegistry.spansTab().isFilterInputActive();
+        boolean beanFilterActive = tabRegistry.selectedTabIndex() == TAB_MORE
+                && tabRegistry.getActiveMoreTab() == tabRegistry.beansTab()
+                && tabRegistry.beansTab().isFilterInputActive();
         boolean sqlInputActive = tabRegistry.selectedTabIndex() == TAB_MORE
                 && tabRegistry.getActiveMoreTab() == tabRegistry.sqlQueryTab()
                 && tabRegistry.sqlQueryTab().isInputActive();
-        boolean textEditing = probeEditing || logSearchActive || 
spanFilterActive || sqlInputActive;
+        boolean textEditing = probeEditing || logSearchActive || 
spanFilterActive || beanFilterActive || sqlInputActive;
         if (!textEditing && (ke.isCharIgnoreCase('q') || ke.isCtrlC())) {
             runner.quit();
             return true;
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistry.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistry.java
index d8fd97204900..db4d61526229 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistry.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistry.java
@@ -305,6 +305,10 @@ class TabRegistry {
         return errorsTab;
     }
 
+    BeansTab beansTab() {
+        return beansTab;
+    }
+
     SpansTab spansTab() {
         return spansTab;
     }
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 04aad8b40ad8..5d48490bde09 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
@@ -51,15 +51,17 @@ class ThreadsTab extends AbstractTableTab {
 
     private static final String[] FILTER_LABELS = { "camel", "all" };
 
+    private static final long AUTO_REFRESH_INTERVAL_MS = 5000;
+
     private final AtomicBoolean loading = new AtomicBoolean(false);
 
     private int filter; // 0=camel, 1=all
     private List<ThreadData> allThreads = Collections.emptyList();
     private int threadCount;
     private int peakThreadCount;
-    private boolean showTrace;
     private int traceScroll;
     private String lastPid;
+    private long lastRefreshTime;
 
     ThreadsTab(MonitorContext ctx) {
         super(ctx, "id", "name", "state");
@@ -85,48 +87,27 @@ class ThreadsTab extends AbstractTableTab {
     @Override
     public void onIntegrationChanged() {
         allThreads = Collections.emptyList();
-        showTrace = false;
         traceScroll = 0;
         lastPid = null;
     }
 
     @Override
     public boolean handleKeyEvent(KeyEvent ke) {
-        if (showTrace) {
-            if (ke.isUp()) {
-                traceScroll = Math.max(0, traceScroll - 1);
-                return true;
-            }
-            if (ke.isDown()) {
-                traceScroll++;
-                return true;
-            }
-            if (ke.isPageUp() || ke.isKey(KeyCode.PAGE_UP)) {
-                traceScroll = Math.max(0, traceScroll - 20);
-                return true;
-            }
-            if (ke.isPageDown() || ke.isKey(KeyCode.PAGE_DOWN)) {
-                traceScroll += 20;
-                return true;
-            }
-            return false;
-        }
         return super.handleKeyEvent(ke);
     }
 
     @Override
     protected boolean handleTabKeyEvent(KeyEvent ke) {
-        if (ke.isConfirm()) {
-            showTrace = !showTrace;
-            traceScroll = 0;
+        if (ke.isPageUp() || ke.isKey(KeyCode.PAGE_UP)) {
+            traceScroll = Math.max(0, traceScroll - 10);
             return true;
         }
-        if (ke.isCharIgnoreCase('f')) {
-            filter = (filter + 1) % FILTER_LABELS.length;
+        if (ke.isPageDown() || ke.isKey(KeyCode.PAGE_DOWN)) {
+            traceScroll += 10;
             return true;
         }
-        if (ke.isCharIgnoreCase('r')) {
-            loadThreads();
+        if (ke.isCharIgnoreCase('f')) {
+            filter = (filter + 1) % FILTER_LABELS.length;
             return true;
         }
         return false;
@@ -134,37 +115,32 @@ class ThreadsTab extends AbstractTableTab {
 
     @Override
     public boolean handleMouseEvent(MouseEvent me, Rect area) {
-        if (!showTrace) {
-            return super.handleMouseEvent(me, area);
-        }
-        return false;
-    }
-
-    @Override
-    public boolean handleEscape() {
-        if (showTrace) {
-            showTrace = false;
-            return true;
+        boolean handled = super.handleMouseEvent(me, area);
+        if (handled) {
+            traceScroll = 0;
         }
-        return false;
+        return handled;
     }
 
     @Override
     public void navigateUp() {
-        if (!showTrace) {
-            super.navigateUp();
-        }
+        super.navigateUp();
+        traceScroll = 0;
     }
 
     @Override
     public void navigateDown() {
-        if (!showTrace) {
-            super.navigateDown();
-        }
+        super.navigateDown();
+        traceScroll = 0;
     }
 
     @Override
     protected void renderContent(Frame frame, Rect area, IntegrationInfo info) 
{
+        long now = System.currentTimeMillis();
+        if (!allThreads.isEmpty() && now - lastRefreshTime >= 
AUTO_REFRESH_INTERVAL_MS) {
+            loadThreads();
+        }
+
         if (loading.get() && allThreads.isEmpty()) {
             frame.renderWidget(
                     Paragraph.builder()
@@ -178,33 +154,11 @@ class ThreadsTab extends AbstractTableTab {
 
         List<ThreadData> visible = sortedThreads();
 
-        if (showTrace) {
-            List<Rect> chunks = Layout.vertical()
-                    .constraints(Constraint.length(1), 
Constraint.percentage(40), Constraint.fill())
-                    .split(area);
-            renderSummary(frame, chunks.get(0), visible);
-            renderTable(frame, chunks.get(1), visible);
-            renderTrace(frame, chunks.get(2), visible);
-        } else {
-            List<Rect> chunks = Layout.vertical()
-                    .constraints(Constraint.length(1), Constraint.fill())
-                    .split(area);
-            renderSummary(frame, chunks.get(0), visible);
-            renderTable(frame, chunks.get(1), visible);
-        }
-    }
-
-    private void renderSummary(Frame frame, Rect area, List<ThreadData> 
visible) {
-        List<Span> spans = new ArrayList<>();
-        spans.add(Span.styled(" Threads: ", 
Style.EMPTY.fg(Color.YELLOW).bold()));
-        spans.add(Span.styled(String.valueOf(threadCount), 
Style.EMPTY.fg(Color.WHITE)));
-        spans.add(Span.raw("    "));
-        spans.add(Span.styled("Peak: ", Style.EMPTY.fg(Color.YELLOW).bold()));
-        spans.add(Span.styled(String.valueOf(peakThreadCount), 
Style.EMPTY.fg(Color.WHITE)));
-        spans.add(Span.raw("    "));
-        spans.add(Span.styled("Showing: ", 
Style.EMPTY.fg(Color.YELLOW).bold()));
-        spans.add(Span.styled(visible.size() + "/" + allThreads.size(), 
Style.EMPTY.fg(Color.WHITE)));
-        frame.renderWidget(Paragraph.from(Line.from(spans)), area);
+        List<Rect> chunks = Layout.vertical()
+                .constraints(Constraint.fill(), Constraint.percentage(40))
+                .split(area);
+        renderTable(frame, chunks.get(0), visible);
+        renderTrace(frame, chunks.get(1), visible);
     }
 
     private void renderTable(Frame frame, Rect area, List<ThreadData> visible) 
{
@@ -230,7 +184,8 @@ class ThreadsTab extends AbstractTableTab {
             rows.add(emptyRow("No threads", 5));
         }
 
-        String title = String.format(" Threads [%d] sort:%s filter:%s ", 
visible.size(), sort, FILTER_LABELS[filter]);
+        String title = String.format(" Threads [%d/%d] peak:%d sort:%s 
filter:%s ",
+                visible.size(), threadCount, peakThreadCount, sort, 
FILTER_LABELS[filter]);
 
         Table table = Table.builder()
                 .rows(rows)
@@ -314,12 +269,8 @@ class ThreadsTab extends AbstractTableTab {
         hint(spans, "Esc", "back");
         hint(spans, "s", "sort");
         hint(spans, "f", "filter [" + FILTER_LABELS[filter] + "]");
-        hint(spans, "r", "refresh");
-        if (showTrace) {
-            hintLast(spans, "↑↓", "scroll");
-        } else {
-            hintLast(spans, "Enter", "trace");
-        }
+        hint(spans, "↑↓", "navigate");
+        hintLast(spans, "PgUp/Dn", "scroll");
     }
 
     @Override
@@ -450,6 +401,7 @@ class ThreadsTab extends AbstractTableTab {
                 threadCount = tc;
                 peakThreadCount = peak;
                 lastPid = pid;
+                lastRefreshTime = System.currentTimeMillis();
             });
         }
     }
@@ -519,26 +471,31 @@ class ThreadsTab extends AbstractTableTab {
 
                 ## Stack Trace View
 
-                Press `Enter` to see the full stack trace of the selected 
thread.
-                The stack trace shows exactly what code the thread is 
executing (or
-                waiting in). Lines containing `org.apache.camel` are 
highlighted in
-                yellow to help you find Camel-related frames.
+                The detail panel at the bottom always shows the stack trace of 
the
+                currently selected thread. As you navigate threads with 
`Up/Down`,
+                the stack trace updates automatically. Lines containing
+                `org.apache.camel` are highlighted in yellow to help you find
+                Camel-related frames.
 
-                Use `Up/Down` or `PgUp/PgDn` to scroll through long stack 
traces.
+                Use `PgUp/PgDn` to scroll through long stack traces.
 
                 ## Filter Modes
 
                 - **camel** (default) — Show only Camel-related threads (names 
containing `camel`, `vertx`, or `netty`)
                 - **all** — Show all JVM threads including system threads, GC 
threads, JIT compiler threads
 
+                ## Auto-Refresh
+
+                Thread data is automatically refreshed every 5 seconds so you 
can
+                watch BLOCKED/WAITED counters change over time without manual 
action.
+
                 ## Keys
 
-                - `Up/Down` — select thread
-                - `Enter` — view/hide stack trace
+                - `Up/Down` — select thread (detail updates automatically)
+                - `PgUp/PgDn` — scroll stack trace detail
                 - `s` — cycle sort column
                 - `S` — reverse sort order
                 - `f` — toggle filter (camel / all)
-                - `r` — refresh thread data
                 - `Esc` — back
                 """;
     }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/BeansTabRenderTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/BeansTabRenderTest.java
index 3d5d2bcec75b..428a4ca38c5c 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/BeansTabRenderTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/BeansTabRenderTest.java
@@ -85,10 +85,10 @@ class BeansTabRenderTest {
                 .reduce("", String::concat);
 
         assertTrue(footer.contains("Esc"), "Footer should contain Esc hint");
-        assertTrue(footer.contains("sort"), "Footer should contain sort hint");
-        assertTrue(footer.contains("internal"), "Footer should contain 
internal toggle hint");
-        assertTrue(footer.contains("refresh"), "Footer should contain refresh 
hint");
-        assertTrue(footer.contains("detail"), "Footer should contain detail 
hint");
+        assertTrue(footer.contains("scope"), "Footer should contain scope 
hint");
+        assertTrue(footer.contains("filter"), "Footer should contain filter 
hint");
+        assertTrue(footer.contains("navigate"), "Footer should contain 
navigate hint");
+        assertTrue(footer.contains("scroll"), "Footer should contain scroll 
hint");
     }
 
 }


Reply via email to