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

commit 0bda8d2c504a87effe316d544f653dd96e99c4e2
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Jul 10 16:17:24 2026 +0200

    TUI: Fix various bugs in trace reading, MCP file handling, CVE caching, 
route actions, and thread safety
    
    - DataRefreshService: Remove incorrect readLine() skip that discarded 
complete trace lines on incremental reads
    - CamelMonitor: Skip writing .mcp.json when the file already exists to 
avoid overwriting user config
    - OsvClient: Only cache empty CVE results on successful API responses, 
leaving failed lookups for retry
    - RoutesTab: Use shared displayedRoutes() helper so Top-mode actions target 
the highlighted route
    - AiPanel/SendMessagePopup: Use CopyOnWriteArrayList for cross-thread 
conversation and history lists
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../camel/dsl/jbang/core/commands/tui/AiPanel.java |  5 +-
 .../dsl/jbang/core/commands/tui/CamelMonitor.java  |  4 ++
 .../core/commands/tui/DataRefreshService.java      |  7 +-
 .../dsl/jbang/core/commands/tui/OsvClient.java     | 16 +++--
 .../dsl/jbang/core/commands/tui/RoutesTab.java     | 83 ++++++++--------------
 .../jbang/core/commands/tui/SendMessagePopup.java  |  3 +-
 6 files changed, 50 insertions(+), 68 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiPanel.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiPanel.java
index 6f24b8b1df65..e1a33c508984 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiPanel.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiPanel.java
@@ -23,6 +23,7 @@ import java.util.ArrayList;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import dev.tamboui.layout.Constraint;
@@ -83,7 +84,7 @@ class AiPanel {
     private int cursorPos;
 
     // Conversation display
-    private final List<ConversationEntry> conversation = new ArrayList<>();
+    private final List<ConversationEntry> conversation = new 
CopyOnWriteArrayList<>();
     private int scrollOffset;
 
     // LLM state
@@ -103,7 +104,7 @@ class AiPanel {
     private Rect lastArea;
 
     // AI usage stats
-    private final List<AiUsageEntry> usageHistory = new ArrayList<>();
+    private final List<AiUsageEntry> usageHistory = new 
CopyOnWriteArrayList<>();
     private final TableState statsTableState = new TableState();
     private boolean statsView;
     private int statsScrollOffset;
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 8ef6eeda1ed5..7f357bd7e3ef 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
@@ -2105,6 +2105,10 @@ public class CamelMonitor extends CamelCommand {
 
     private static Path writeMcpJson(int port) {
         Path path = Path.of(".mcp.json");
+        if (Files.exists(path)) {
+            // do not overwrite an existing .mcp.json (may belong to Claude 
Code, Cursor, etc.)
+            return null;
+        }
         try {
             String json = """
                     {
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 729336259484..bc02ece17db6 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
@@ -510,14 +510,9 @@ class DataRefreshService {
             }
 
             raf.seek(lastPos);
-            // If we're resuming mid-file, skip any partial line
-            if (lastPos > 0) {
-                raf.readLine();
-            }
 
             // Read remaining bytes
-            long startPos = raf.getFilePointer();
-            byte[] remaining = new byte[(int) (length - startPos)];
+            byte[] remaining = new byte[(int) (length - lastPos)];
             raf.readFully(remaining);
             String content = new String(remaining, StandardCharsets.UTF_8);
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OsvClient.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OsvClient.java
index 0d346b94f33b..5c9bc0fb1ba5 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OsvClient.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OsvClient.java
@@ -90,6 +90,7 @@ class OsvClient {
         JsonObject requestBody = new JsonObject();
         requestBody.put("queries", queries);
 
+        boolean success = false;
         try {
             HttpClient client = HttpClient.newBuilder()
                     .connectTimeout(Duration.ofSeconds(5))
@@ -103,6 +104,7 @@ class OsvClient {
             HttpResponse<String> response = client.send(request, 
HttpResponse.BodyHandlers.ofString());
 
             if (response.statusCode() == 200) {
+                success = true;
                 JsonObject body = (JsonObject) 
Jsoner.deserialize(response.body());
                 JsonArray results = (JsonArray) body.get("results");
                 if (results != null) {
@@ -148,14 +150,16 @@ class OsvClient {
         } catch (InterruptedException e) {
             Thread.currentThread().interrupt();
         } catch (Exception e) {
-            // network errors — return whatever we have from cache
+            // network errors — return whatever we have from cache; leave 
entries uncached for retry
         }
 
-        // cache empty results for uncached entries that got no response
-        for (DependencyLoader.DepEntry entry : uncached) {
-            String gav = entry.display();
-            result.putIfAbsent(gav, Collections.emptyList());
-            CACHE.putIfAbsent(gav, Collections.emptyList());
+        if (success) {
+            // cache empty results only for entries the API actually processed
+            for (DependencyLoader.DepEntry entry : uncached) {
+                String gav = entry.display();
+                result.putIfAbsent(gav, Collections.emptyList());
+                CACHE.putIfAbsent(gav, Collections.emptyList());
+            }
         }
 
         return result;
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
index 975fb02b21e5..91d336ab245b 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
@@ -542,8 +542,7 @@ class RoutesTab extends AbstractTab {
         }
 
         // Normal table view
-        List<RouteInfo> sortedRoutes = new ArrayList<>(info.routes);
-        sortedRoutes.sort(this::sortRoute);
+        List<RouteInfo> sortedRoutes = displayedRoutes(info);
 
         if (topPanelHeight < 0) {
             topPanelHeight = area.height() * 45 / 100;
@@ -556,8 +555,6 @@ class RoutesTab extends AbstractTab {
         // Routes table
         Table routeTable;
         if (routeTopMode) {
-            sortedRoutes.sort(this::sortRouteTop);
-
             List<Row> routeRows = new ArrayList<>();
             for (RouteInfo route : sortedRoutes) {
                 Style failStyle = route.failed > 0
@@ -1430,58 +1427,49 @@ class RoutesTab extends AbstractTab {
 
     // ---- Route actions ----
 
-    String selectedRouteId() {
-        IntegrationInfo info = ctx.findSelectedIntegration();
-        if (info == null || info.routes.isEmpty()) {
-            return null;
+    private List<RouteInfo> displayedRoutes(IntegrationInfo info) {
+        List<RouteInfo> sorted = new ArrayList<>(info.routes);
+        sorted.sort(this::sortRoute);
+        if (routeTopMode) {
+            sorted.sort(this::sortRouteTop);
         }
-        List<RouteInfo> sortedRoutes = new ArrayList<>(info.routes);
-        sortedRoutes.sort(this::sortRoute);
-        Integer sel = routeTableState.selected();
-        RouteInfo route = (sel != null && sel >= 0 && sel < 
sortedRoutes.size())
-                ? sortedRoutes.get(sel) : sortedRoutes.get(0);
-        return route.routeId;
+        return sorted;
     }
 
-    private String selectedRouteState() {
+    private RouteInfo selectedRoute() {
         IntegrationInfo info = ctx.findSelectedIntegration();
         if (info == null || info.routes.isEmpty()) {
             return null;
         }
-        List<RouteInfo> sortedRoutes = new ArrayList<>(info.routes);
-        sortedRoutes.sort(this::sortRoute);
+        List<RouteInfo> sortedRoutes = displayedRoutes(info);
         Integer sel = routeTableState.selected();
-        RouteInfo route = (sel != null && sel >= 0 && sel < 
sortedRoutes.size())
+        return (sel != null && sel >= 0 && sel < sortedRoutes.size())
                 ? sortedRoutes.get(sel) : sortedRoutes.get(0);
-        return route.state;
+    }
+
+    String selectedRouteId() {
+        RouteInfo route = selectedRoute();
+        return route != null ? route.routeId : null;
+    }
+
+    private String selectedRouteState() {
+        RouteInfo route = selectedRoute();
+        return route != null ? route.state : null;
     }
 
     private boolean selectedRouteSupportsSuspension() {
-        IntegrationInfo info = ctx.findSelectedIntegration();
-        if (info == null || info.routes.isEmpty()) {
-            return false;
-        }
-        List<RouteInfo> sortedRoutes = new ArrayList<>(info.routes);
-        sortedRoutes.sort(this::sortRoute);
-        Integer sel = routeTableState.selected();
-        RouteInfo route = (sel != null && sel >= 0 && sel < 
sortedRoutes.size())
-                ? sortedRoutes.get(sel) : sortedRoutes.get(0);
-        return route.supportsSuspension;
+        RouteInfo route = selectedRoute();
+        return route != null && route.supportsSuspension;
     }
 
     private void toggleRouteStartStop() {
         if (ctx.selectedPid == null) {
             return;
         }
-        IntegrationInfo info = ctx.findSelectedIntegration();
-        if (info == null || info.routes.isEmpty()) {
+        RouteInfo route = selectedRoute();
+        if (route == null) {
             return;
         }
-        List<RouteInfo> sortedRoutes = new ArrayList<>(info.routes);
-        sortedRoutes.sort(this::sortRoute);
-        Integer sel = routeTableState.selected();
-        RouteInfo route = (sel != null && sel >= 0 && sel < 
sortedRoutes.size())
-                ? sortedRoutes.get(sel) : sortedRoutes.get(0);
         String command = "Started".equals(route.state) ? "stop" : "start";
         sendRouteCommand(ctx.selectedPid, route.routeId, command);
     }
@@ -1490,15 +1478,10 @@ class RoutesTab extends AbstractTab {
         if (ctx.selectedPid == null) {
             return;
         }
-        IntegrationInfo info = ctx.findSelectedIntegration();
-        if (info == null || info.routes.isEmpty()) {
+        RouteInfo route = selectedRoute();
+        if (route == null) {
             return;
         }
-        List<RouteInfo> sortedRoutes = new ArrayList<>(info.routes);
-        sortedRoutes.sort(this::sortRoute);
-        Integer sel = routeTableState.selected();
-        RouteInfo route = (sel != null && sel >= 0 && sel < 
sortedRoutes.size())
-                ? sortedRoutes.get(sel) : sortedRoutes.get(0);
         String command = switch (route.state != null ? route.state : "") {
             case "Started" -> "suspend";
             case "Suspended" -> "resume";
@@ -1549,16 +1532,11 @@ class RoutesTab extends AbstractTab {
     }
 
     private void loadSourceForSelectedRoute() {
-        IntegrationInfo info = ctx.findSelectedIntegration();
-        if (info == null || info.routes.isEmpty()) {
+        RouteInfo selected = selectedRoute();
+        if (selected == null) {
             return;
         }
-        List<RouteInfo> sortedRoutes = new ArrayList<>(info.routes);
-        sortedRoutes.sort(this::sortRoute);
-        Integer sel = routeTableState.selected();
-        RouteInfo selectedRoute = (sel != null && sel >= 0 && sel < 
sortedRoutes.size())
-                ? sortedRoutes.get(sel) : sortedRoutes.get(0);
-        String routeId = selectedRoute.routeId;
+        String routeId = selected.routeId;
         sourceViewer.setOnLineSelected(sourceLine -> {
             sourceViewer.hide();
             // Open drill-down diagram for this route
@@ -1682,8 +1660,7 @@ class RoutesTab extends AbstractTab {
         if (info == null || info.routes.isEmpty()) {
             return null;
         }
-        List<RouteInfo> sorted = new ArrayList<>(info.routes);
-        sorted.sort(this::sortRoute);
+        List<RouteInfo> sorted = displayedRoutes(info);
         List<String> items = sorted.stream().map(r -> r.routeId != null ? 
r.routeId : "").toList();
         Integer sel = routeTableState.selected();
         return new SelectionContext("table", items, sel != null ? sel : -1, 
items.size(), "Routes");
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SendMessagePopup.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SendMessagePopup.java
index 8f0841c75558..b08ca2f5d675 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SendMessagePopup.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SendMessagePopup.java
@@ -21,6 +21,7 @@ import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.ScheduledExecutorService;
 
 import dev.tamboui.layout.Constraint;
@@ -93,7 +94,7 @@ class SendMessagePopup {
     private boolean showResponseHeaders;
 
     // History
-    private final List<SendHistoryEntry> history = new ArrayList<>();
+    private final List<SendHistoryEntry> history = new 
CopyOnWriteArrayList<>();
     private int historyIndex;
 
     // File chooser

Reply via email to