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 3b121d08806d CAMEL-23648: camel-jbang - TUI improvements: run from 
folder, search, syntax highlighting, performance (#23671)
3b121d08806d is described below

commit 3b121d08806db72ab9a66408dabad328df388bff
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Jun 1 11:25:52 2026 +0200

    CAMEL-23648: camel-jbang - TUI improvements: run from folder, search, 
syntax highlighting, performance (#23671)
    
    * CAMEL-23648: camel-jbang - TUI Run from folder uses --source-dir and 
defaults dev mode on
    
    Co-Authored-By: Claude <[email protected]>
    
    * CAMEL-23648: camel-jbang - TUI Log tab find and highlight
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    * CAMEL-23648: camel-jbang - TUI Routes tab improve diagram label and help 
text
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    * CAMEL-23648: camel-jbang - TUI source code syntax highlighting
    
    Co-Authored-By: Claude <[email protected]>
    
    * CAMEL-23648: camel-jbang - TUI source view auto-scroll past license 
headers
    
    Co-Authored-By: Claude <[email protected]>
    
    * CAMEL-23648: camel-jbang - TUI Log tab loading and scroll improvements
    
    Co-Authored-By: Claude <[email protected]>
    
    * CAMEL-23648: camel-jbang - TUI performance: cache PIDs and optimize 
refresh
    
    Move log reading before the PID scan so logs appear instantly. Cache the
    PID list and only do a full ProcessHandle.allProcesses() scan on the
    Overview tab or F3 switch popup. On other tabs reuse cached PIDs with
    direct ProcessHandle.of() lookups, eliminating the 3-4 second delay.
    Also add stripAnsi fast-path for lines without escape sequences.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    * CAMEL-23648: camel-jbang - TUI Log tab avoid unnecessary render rebuilds
    
    Only reassign filteredLogEntries when log content actually changes (new
    lines arrived or older lines loaded). Previously a new ArrayList was
    created every refresh tick, causing the render to rebuild all styled
    Line objects every 100ms even when idle. Also only refresh the selected
    integration's status on non-Overview tabs, and skip infra discovery.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    * CAMEL-23648: camel-jbang - TUI throttle full PID scan and instant log load
    
    Throttle the full ProcessHandle.allProcesses() scan to every 2 seconds
    on the Overview tab instead of every 100ms tick. Between scans, reuse
    cached PIDs with fast status-only refreshes. Also load log data directly
    from the tick handler when a full refresh is in progress, so the Log tab
    appears instantly when switching from Overview instead of waiting 3-4s.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    * CAMEL-23648: camel-jbang - TUI load log data immediately on tab switch
    
    Call refreshLogData() synchronously when switching to the Log tab via
    ENTER on Overview or the 2 key shortcut, so log lines appear on the
    same render frame without waiting for the async refresh cycle.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    * CAMEL-23648: camel-jbang - TUI load log data immediately on F3 switch
    
    Call refreshLogData() after F3 integration switch when on the Log tab,
    same pattern as the ENTER and tab key handlers.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    * CAMEL-23648: camel-jbang - TUI show Stopped status when all routes are 
stopped
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    * CAMEL-23648: camel-jbang - TUI add F1 help hint to Overview tab footer
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    * CAMEL-23648: camel-jbang - TUI improve SINCE-LAST help text across tabs
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    * CAMEL-23648: camel-jbang - TUI clip and right-align property source in 
Configuration tab
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    ---------
    
    Co-authored-by: Claude <[email protected]>
---
 .../dsl/jbang/core/commands/tui/ActionsPopup.java  |   4 +-
 .../dsl/jbang/core/commands/tui/CamelMonitor.java  | 210 +++++++----
 .../jbang/core/commands/tui/CircuitBreakerTab.java |   2 +-
 .../jbang/core/commands/tui/ConfigurationTab.java  |  23 +-
 .../camel/dsl/jbang/core/commands/tui/LogTab.java  | 412 +++++++++++++++++++--
 .../dsl/jbang/core/commands/tui/OverviewTab.java   |  10 +-
 .../dsl/jbang/core/commands/tui/RoutesTab.java     | 105 +++++-
 .../jbang/core/commands/tui/RunOptionsForm.java    |   6 +-
 .../jbang/core/commands/tui/SyntaxHighlighter.java | 291 +++++++++++++++
 .../dsl/jbang/core/commands/tui/TuiHelper.java     |   3 +
 10 files changed, 957 insertions(+), 109 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
index 40fe91b32247..9c660417f9f9 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
@@ -1168,7 +1168,7 @@ class ActionsPopup {
         selectedFolder = folder;
         showFolderInput = false;
         String displayName = Path.of(folder).getFileName().toString();
-        runOptionsForm.open(displayName, displayName, false);
+        runOptionsForm.open(displayName, displayName, false, true);
     }
 
     private void navigateFolderHistory(int direction) {
@@ -1275,7 +1275,7 @@ class ActionsPopup {
         try {
             List<String> cmd = new 
ArrayList<>(LauncherHelper.getCamelCommand());
             cmd.add("run");
-            cmd.add(folder);
+            cmd.add("--source-dir=" + folder);
             cmd.add("--logging-color=true");
             cmd.addAll(extraArgs);
             Path outputFile = Files.createTempFile("camel-folder-", ".log");
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 dc8314a71d23..5db7e2fa6d69 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
@@ -197,6 +197,10 @@ public class CamelMonitor extends CamelCommand {
     private final Map<String, LoadAvg> cpuLoadAvg = new ConcurrentHashMap<>();
     private final Map<String, long[]> prevCpuSample = new 
ConcurrentHashMap<>();
 
+    // Cached PID list — full process scan throttled to every 2 seconds
+    private volatile List<Long> cachedPids = Collections.emptyList();
+    private volatile long lastFullScanTime;
+
     // Trace/history data — shared between CamelMonitor and tabs
     private final AtomicReference<List<TraceEntry>> traces = new 
AtomicReference<>(Collections.emptyList());
     private final Map<String, Long> traceFilePositions = new 
ConcurrentHashMap<>();
@@ -492,6 +496,9 @@ public class CamelMonitor extends CamelCommand {
                         ctx.selectedPid = chosen.pid;
                         ctx.lastSelectedName = chosen.name;
                         resetIntegrationTabState();
+                        if (tabsState.selected() == TAB_LOG) {
+                            refreshLogData();
+                        }
                     }
                     return true;
                 }
@@ -525,9 +532,11 @@ public class CamelMonitor extends CamelCommand {
                 }
                 return true;
             }
-            // Quit: q or Ctrl+c (skip when probe is editing text)
+            // Quit: q or Ctrl+c (skip when text input is active)
             boolean probeEditing = tabsState.selected() == TAB_HTTP && 
httpTab.isProbeMode();
-            if (!probeEditing && (ke.isCharIgnoreCase('q') || ke.isCtrlC())) {
+            boolean logSearchActive = tabsState.selected() == TAB_LOG && 
logTab.isSearchInputActive();
+            boolean textEditing = probeEditing || logSearchActive;
+            if (!textEditing && (ke.isCharIgnoreCase('q') || ke.isCtrlC())) {
                 runner.quit();
                 return true;
             }
@@ -535,9 +544,9 @@ public class CamelMonitor extends CamelCommand {
                 runner.quit();
                 return true;
             }
-            // Tab switching with number keys (skip when probe is editing text)
+            // Tab switching with number keys (skip when text input is active)
             // When infra is selected, only Overview (1) and Log (2) are 
available
-            if (!probeEditing) {
+            if (!textEditing) {
                 if (ke.isChar('1')) {
                     return handleTabKey(TAB_OVERVIEW);
                 }
@@ -573,8 +582,8 @@ public class CamelMonitor extends CamelCommand {
             }
 
             // Tab cycling (check Shift+Tab before Tab since Tab binding also 
matches Shift+Tab)
-            // Skip tab cycling when HTTP probe is active (Tab navigates 
fields)
-            if (ke.isFocusPrevious() && !(tabsState.selected() == TAB_HTTP && 
httpTab.isProbeMode())) {
+            // Skip tab cycling when text input is active (Tab navigates 
fields)
+            if (ke.isFocusPrevious() && !textEditing) {
                 if (isInfraSelected()) {
                     // Cycle between Overview and Log only
                     int prev = tabsState.selected() == TAB_OVERVIEW ? TAB_LOG 
: TAB_OVERVIEW;
@@ -588,7 +597,7 @@ public class CamelMonitor extends CamelCommand {
                 }
                 return true;
             }
-            if (ke.isFocusNext() && !(tabsState.selected() == TAB_HTTP && 
httpTab.isProbeMode())) {
+            if (ke.isFocusNext() && !textEditing) {
                 if (isInfraSelected()) {
                     int next = tabsState.selected() == TAB_OVERVIEW ? TAB_LOG 
: TAB_OVERVIEW;
                     tabsState.select(next);
@@ -706,6 +715,7 @@ public class CamelMonitor extends CamelCommand {
                 overviewTab.selectCurrentIntegration();
                 if (ctx.selectedPid != null) {
                     tabsState.select(TAB_LOG);
+                    refreshLogData();
                 }
                 return true;
             }
@@ -748,6 +758,10 @@ public class CamelMonitor extends CamelCommand {
                 httpTab.handlePaste(pe.text());
                 return true;
             }
+            if (logTab.isSearchInputActive()) {
+                logTab.handlePaste(pe.text());
+                return true;
+            }
         }
         if (event instanceof TickEvent) {
             long now = System.currentTimeMillis();
@@ -844,6 +858,7 @@ public class CamelMonitor extends CamelCommand {
             overviewTab.selectCurrentIntegration();
         }
         if (tab == TAB_LOG) {
+            refreshLogData();
             logTab.onTabSelected();
         }
         if (tab == TAB_HISTORY && ctx.selectedPid != null) {
@@ -1667,6 +1682,7 @@ public class CamelMonitor extends CamelCommand {
         // Insert F2/F3 after first hint (q) — each hint is 2 spans (key + 
label)
         int insertPos = Math.min(2, spans.size());
         List<Span> fKeySpans = new ArrayList<>();
+        hint(fKeySpans, "F1", "help");
         hint(fKeySpans, "F2", "actions");
         if (getNonVanishingIntegrations().size() > 1) {
             hint(fKeySpans, "F3", "switch");
@@ -1690,6 +1706,71 @@ public class CamelMonitor extends CamelCommand {
 
     // ---- Data Loading ----
 
+    private void refreshLogData() {
+        if (tabsState.selected() != TAB_LOG) {
+            return;
+        }
+        String logPid = null;
+        String logFileName = null;
+        InfraInfo selInfra = findSelectedInfra();
+        if (selInfra != null) {
+            logPid = selInfra.pid;
+            logFileName = "infra-" + selInfra.alias + "-" + selInfra.pid + 
".log";
+        } else {
+            IntegrationInfo selected = findSelectedIntegration();
+            if (selected != null) {
+                logPid = selected.pid;
+                logFileName = selected.pid + ".log";
+            }
+        }
+        if (logPid == null) {
+            return;
+        }
+        if (!logPid.equals(logTab.logFilePid)) {
+            logTab.mutableFilteredEntries.clear();
+            logTab.logFilePos = -1;
+            logTab.logTotalLinesRead = 0;
+            logTab.logLineBuffer.setLength(0);
+            logTab.logLoading = true;
+        }
+        // Load older lines when scrolled to the top or Home pressed
+        boolean changed = false;
+        boolean loadAll = logTab.loadAllRequested;
+        if (logTab.logFileStartPos > 0
+                && (loadAll || (!logTab.followMode && logTab.scroll == 0))) {
+            logTab.loadAllRequested = false;
+            List<String> olderLines = new ArrayList<>();
+            logTab.readOlderLogLines(logFileName, loadAll, olderLines);
+            if (!olderLines.isEmpty()) {
+                changed = true;
+                List<LogEntry> olderEntries = new ArrayList<>();
+                for (String line : olderLines) {
+                    olderEntries.add(LogTab.parseLogLine(line));
+                }
+                logTab.mutableFilteredEntries.addAll(0, olderEntries);
+                logTab.logTotalLinesRead += olderLines.size();
+                logTab.scroll = olderEntries.size();
+            }
+        }
+        List<String> newRawLines = new ArrayList<>();
+        logTab.readNewLogLinesFromFile(logPid, logFileName, newRawLines);
+        changed |= !newRawLines.isEmpty();
+        if (changed) {
+            logTab.logTotalLinesRead += newRawLines.size();
+            for (String line : newRawLines) {
+                logTab.mutableFilteredEntries.add(LogTab.parseLogLine(line));
+            }
+            if (logTab.mutableFilteredEntries.size() > MAX_LOG_LINES) {
+                logTab.mutableFilteredEntries.subList(0, 
logTab.mutableFilteredEntries.size() - MAX_LOG_LINES)
+                        .clear();
+            }
+        }
+        if (changed || logTab.logLoading) {
+            logTab.filteredLogEntries = new 
ArrayList<>(logTab.mutableFilteredEntries);
+        }
+        logTab.logLoading = false;
+    }
+
     private void refreshData() {
         if (runner == null) {
             refreshDataSync();
@@ -1712,24 +1793,60 @@ public class CamelMonitor extends CamelCommand {
     private void refreshDataSync() {
         lastRefresh = System.currentTimeMillis();
         try {
+            // Read log data early — before the heavy PID/status scan
+            refreshLogData();
+
             List<IntegrationInfo> infos = new ArrayList<>();
-            List<Long> pids = findPids(name);
-            ProcessHandle.allProcesses()
-                    .filter(ph -> pids.contains(ph.pid()))
-                    .forEach(ph -> {
-                        JsonObject root = loadStatus(ph.pid());
-                        if (root != null) {
-                            IntegrationInfo info = 
StatusParser.parseIntegration(ph, root);
-                            if (info != null) {
-                                infos.add(info);
-                                updateThroughputHistory(info);
-                                updateEndpointHistory(info);
-                                updateCbHistory(info);
-                                updateHeapHistory(info);
-                                updateLoadMetrics(ph, info);
-                            }
-                        }
-                    });
+            long now = System.currentTimeMillis();
+            boolean wantFullScan = tabsState.selected() == TAB_OVERVIEW || 
showSwitchPopup || cachedPids.isEmpty();
+            boolean fullScan = wantFullScan && (now - lastFullScanTime >= 
2000);
+            List<Long> pids;
+            if (fullScan) {
+                pids = findPids(name);
+                cachedPids = pids;
+                lastFullScanTime = now;
+            } else {
+                pids = cachedPids;
+            }
+
+            // On non-Overview tabs, only refresh the selected integration for 
speed
+            List<Long> refreshPids;
+            if (!fullScan && ctx.selectedPid != null) {
+                try {
+                    refreshPids = List.of(Long.parseLong(ctx.selectedPid));
+                } catch (NumberFormatException e) {
+                    refreshPids = pids;
+                }
+            } else {
+                refreshPids = pids;
+            }
+            for (Long pid : refreshPids) {
+                JsonObject root = loadStatus(pid);
+                if (root != null) {
+                    ProcessHandle ph = ProcessHandle.of(pid).orElse(null);
+                    if (ph == null) {
+                        continue;
+                    }
+                    IntegrationInfo info = StatusParser.parseIntegration(ph, 
root);
+                    if (info != null) {
+                        infos.add(info);
+                        updateThroughputHistory(info);
+                        updateEndpointHistory(info);
+                        updateCbHistory(info);
+                        updateHeapHistory(info);
+                        updateLoadMetrics(ph, info);
+                    }
+                }
+            }
+            // Carry forward non-selected integrations from previous data so 
they don't vanish
+            if (!fullScan && ctx.selectedPid != null) {
+                List<IntegrationInfo> previous = data.get();
+                for (IntegrationInfo prev : previous) {
+                    if (!prev.vanishing && !ctx.selectedPid.equals(prev.pid)) {
+                        infos.add(prev);
+                    }
+                }
+            }
 
             // Detect disappeared integrations and start vanishing
             Set<String> livePids = infos.stream().map(i -> 
i.pid).collect(Collectors.toSet());
@@ -1741,7 +1858,6 @@ public class CamelMonitor extends CamelCommand {
             }
 
             // Expire old vanishing entries
-            long now = System.currentTimeMillis();
             Iterator<Map.Entry<String, VanishingInfo>> it = 
vanishing.entrySet().iterator();
             while (it.hasNext()) {
                 Map.Entry<String, VanishingInfo> entry = it.next();
@@ -1831,8 +1947,10 @@ public class CamelMonitor extends CamelCommand {
                 }
             }
 
-            // Discover running infra services
-            refreshInfraData();
+            // Discover running infra services (only on Overview or switch 
popup)
+            if (fullScan) {
+                refreshInfraData();
+            }
 
             // Auto-select first infra service when no active integrations 
exist
             if (ctx.selectedPid == null && !infraData.get().isEmpty()
@@ -1845,43 +1963,7 @@ public class CamelMonitor extends CamelCommand {
                 }
             }
 
-            // Refresh log data only when the Log tab is visible
-            if (tabsState.selected() == TAB_LOG) {
-                String logPid = null;
-                String logFileName = null;
-                InfraInfo selInfra = findSelectedInfra();
-                if (selInfra != null) {
-                    logPid = selInfra.pid;
-                    logFileName = "infra-" + selInfra.alias + "-" + 
selInfra.pid + ".log";
-                } else {
-                    IntegrationInfo selected = findSelectedIntegration();
-                    if (selected != null) {
-                        logPid = selected.pid;
-                        logFileName = selected.pid + ".log";
-                    }
-                }
-                if (logPid != null) {
-                    if (!logPid.equals(logTab.logFilePid)) {
-                        logTab.mutableFilteredEntries.clear();
-                        logTab.logFilePos = -1;
-                        logTab.logTotalLinesRead = 0;
-                        logTab.logLineBuffer.setLength(0);
-                    }
-                    List<String> newRawLines = new ArrayList<>();
-                    logTab.readNewLogLinesFromFile(logPid, logFileName, 
newRawLines);
-                    if (!newRawLines.isEmpty()) {
-                        logTab.logTotalLinesRead += newRawLines.size();
-                        for (String line : newRawLines) {
-                            
logTab.mutableFilteredEntries.add(LogTab.parseLogLine(line));
-                        }
-                        if (logTab.mutableFilteredEntries.size() > 
MAX_LOG_LINES) {
-                            logTab.mutableFilteredEntries.subList(0, 
logTab.mutableFilteredEntries.size() - MAX_LOG_LINES)
-                                    .clear();
-                        }
-                        logTab.filteredLogEntries = new 
ArrayList<>(logTab.mutableFilteredEntries);
-                    }
-                }
-            }
+            // Log data is now refreshed at the top of refreshDataSync() via 
refreshLogData()
 
             // Scope history/error/trace refresh to the selected integration 
only
             List<Long> selectedPids = selectedPidAsList();
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTab.java
index 822e2001b1ee..d33f39a76617 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTab.java
@@ -476,7 +476,7 @@ class CircuitBreakerTab implements MonitorTab {
                 - **FAIL** — Total number of failed calls (exceptions thrown 
by the protected code)
                 - **RATE%** — Current failure rate percentage in the sliding 
window. When this exceeds the configured threshold, the circuit trips to OPEN
                 - **REJECT** — Calls rejected because the circuit is OPEN. 
These calls never reach the downstream service — they fail fast with a fallback
-                - **SINCE-LAST** — Time since last started/success/fail events
+                - **SINCE-LAST** — Time since the last circuit breaker 
activity, shown as up to two values separated by `/`: success/failed (e.g., 
`3s/1m14s`). Values are omitted when there is no activity of that type
 
                 ## Example Screen
 
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 867ed7921492..5bab0d7e6d57 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.widgets.block.BorderType;
 import dev.tamboui.widgets.paragraph.Paragraph;
 import dev.tamboui.widgets.scrollbar.Scrollbar;
 import dev.tamboui.widgets.scrollbar.ScrollbarState;
+import org.apache.camel.util.FileUtil;
 
 import static org.apache.camel.dsl.jbang.core.commands.tui.MonitorContext.*;
 
@@ -152,6 +153,11 @@ class ConfigurationTab implements MonitorTab {
         }
         int keyWidth = Math.min(maxKeyLen, inner.width() / 2);
 
+        List<Rect> hChunks = Layout.horizontal()
+                .constraints(Constraint.fill(), Constraint.length(1))
+                .split(inner);
+        int lineWidth = hChunks.get(0).width();
+
         // Build visible lines, inserting divider at the right display position
         List<Line> lines = new ArrayList<>();
         int displayRow = 0;
@@ -167,15 +173,11 @@ class ConfigurationTab implements MonitorTab {
                 }
             }
             if (displayRow >= scrollOffset) {
-                lines.add(renderProperty(props.get(i), keyWidth));
+                lines.add(renderProperty(props.get(i), keyWidth, lineWidth));
             }
             displayRow++;
         }
 
-        List<Rect> hChunks = Layout.horizontal()
-                .constraints(Constraint.fill(), Constraint.length(1))
-                .split(inner);
-
         frame.renderWidget(Paragraph.builder().text(Text.from(lines)).build(), 
hChunks.get(0));
 
         if (totalLines > visibleLines) {
@@ -187,7 +189,7 @@ class ConfigurationTab implements MonitorTab {
         }
     }
 
-    private Line renderProperty(ConfigProperty prop, int keyWidth) {
+    private Line renderProperty(ConfigProperty prop, int keyWidth, int 
lineWidth) {
         String key = prop.key;
         if (key.length() > keyWidth) {
             key = key.substring(0, keyWidth - 1) + "…";
@@ -200,10 +202,15 @@ class ConfigurationTab implements MonitorTab {
         String value = prop.value != null ? prop.value : "";
 
         List<Span> spans = new ArrayList<>();
-        spans.add(Span.styled("  " + key + "  ", KEY_STYLE));
+        String keyPart = "  " + key + "  ";
+        spans.add(Span.styled(keyPart, KEY_STYLE));
         spans.add(Span.styled(value, valStyle));
         if (prop.source != null && !prop.source.isEmpty()) {
-            spans.add(Span.styled("  [" + prop.source + "]", SOURCE_STYLE));
+            String displaySource = FileUtil.stripPath(prop.source);
+            String sourceText = "[" + displaySource + "]";
+            int used = keyPart.length() + value.length();
+            int gap = Math.max(2, lineWidth - used - sourceText.length());
+            spans.add(Span.styled(" ".repeat(gap) + sourceText, SOURCE_STYLE));
         }
 
         return Line.from(spans);
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java
index c2ccdf95e6e1..bb2bb659dfb9 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java
@@ -38,10 +38,13 @@ import dev.tamboui.text.CharWidth;
 import dev.tamboui.text.Line;
 import dev.tamboui.text.Span;
 import dev.tamboui.text.Text;
+import dev.tamboui.tui.event.KeyCode;
 import dev.tamboui.tui.event.KeyEvent;
 import dev.tamboui.widgets.Clear;
 import dev.tamboui.widgets.block.Block;
 import dev.tamboui.widgets.block.BorderType;
+import dev.tamboui.widgets.block.Title;
+import dev.tamboui.widgets.input.TextInputState;
 import dev.tamboui.widgets.list.ListItem;
 import dev.tamboui.widgets.list.ListState;
 import dev.tamboui.widgets.list.ListWidget;
@@ -70,7 +73,10 @@ class LogTab implements MonitorTab {
     private final ListState logLevelListState = new ListState();
 
     volatile List<LogEntry> filteredLogEntries = new ArrayList<>();
+    volatile boolean logLoading;
+    volatile boolean loadAllRequested;
     long logFilePos = -1;
+    long logFileStartPos = -1;
     long logTotalLinesRead;
     String logFilePid;
     final StringBuilder logLineBuffer = new StringBuilder();
@@ -80,19 +86,55 @@ class LogTab implements MonitorTab {
     private int cachedLogHSkip = -1;
     private int cachedLogMaxWidth;
     private List<Line> cachedLogLines = Collections.emptyList();
-    private int scroll;
+    int scroll;
     private long evictedSeen;
-    private boolean followMode = true;
+    boolean followMode = true;
     private boolean wordWrap = true;
     private int hScroll;
     private boolean showLogLevelPopup;
 
+    // Highlight mode: persistent keyword highlighting
+    private String highlightTerm;
+    private Pattern highlightPattern;
+
+    // Find mode: search with next/prev navigation
+    private boolean findInputActive;
+    private boolean highlightInputActive;
+    private TextInputState searchInputState = new TextInputState("");
+    private String findTerm;
+    private Pattern findPattern;
+    private int findMatchIndex = -1;
+    private List<Integer> findMatches = Collections.emptyList();
+
+    private static final Style HIGHLIGHT_STYLE = 
Style.EMPTY.fg(Color.BLACK).bg(Color.YELLOW);
+    private static final Style FIND_MATCH_STYLE = 
Style.EMPTY.fg(Color.BLACK).bg(Color.YELLOW);
+    private static final Style FIND_CURRENT_STYLE = 
Style.EMPTY.fg(Color.BLACK).bg(Color.LIGHT_GREEN);
+
     LogTab(MonitorContext ctx) {
         this.ctx = ctx;
     }
 
+    @Override
+    public void onIntegrationChanged() {
+        filteredLogEntries = new ArrayList<>();
+        logLoading = true;
+        loadAllRequested = false;
+        logFilePid = null;
+        logFilePos = -1;
+        logFileStartPos = -1;
+        logTotalLinesRead = 0;
+        logLineBuffer.setLength(0);
+        mutableFilteredEntries.clear();
+        cachedLogEntries = null;
+        cachedLogLines = Collections.emptyList();
+    }
+
     @Override
     public boolean handleKeyEvent(KeyEvent ke) {
+        if (findInputActive || highlightInputActive) {
+            return handleSearchInput(ke);
+        }
+
         if (showLogLevelPopup) {
             if (ke.isUp()) {
                 logLevelListState.selectPrevious();
@@ -113,6 +155,24 @@ class LogTab implements MonitorTab {
             return true;
         }
 
+        if (ke.isChar('/')) {
+            findInputActive = true;
+            searchInputState = new TextInputState("");
+            return true;
+        }
+        if (ke.isChar('h')) {
+            highlightInputActive = true;
+            searchInputState = new TextInputState("");
+            return true;
+        }
+        if (ke.isChar('n') && findTerm != null) {
+            navigateToNextMatch();
+            return true;
+        }
+        if (ke.isChar('N') && findTerm != null) {
+            navigateToPrevMatch();
+            return true;
+        }
         if (ke.isChar('l') && !ctx.isInfraSelected()) {
             showLogLevelPopup = true;
             logLevelListState.select(2);
@@ -151,6 +211,7 @@ class LogTab implements MonitorTab {
             followMode = false;
             scroll = 0;
             hScroll = 0;
+            loadAllRequested = true;
             return true;
         }
         if (ke.isEnd()) {
@@ -160,12 +221,61 @@ class LogTab implements MonitorTab {
         return false;
     }
 
+    private boolean handleSearchInput(KeyEvent ke) {
+        if (ke.isKey(KeyCode.ESCAPE)) {
+            findInputActive = false;
+            highlightInputActive = false;
+            return true;
+        }
+        if (ke.isConfirm()) {
+            String text = searchInputState.text().trim();
+            if (findInputActive) {
+                if (text.isEmpty()) {
+                    findTerm = null;
+                    findPattern = null;
+                    findMatches = Collections.emptyList();
+                    findMatchIndex = -1;
+                } else {
+                    findTerm = text;
+                    findPattern = Pattern.compile(Pattern.quote(text), 
Pattern.CASE_INSENSITIVE);
+                    buildFindMatches();
+                    jumpToNearestMatch();
+                }
+                findInputActive = false;
+            } else if (highlightInputActive) {
+                if (text.isEmpty()) {
+                    highlightTerm = null;
+                    highlightPattern = null;
+                } else {
+                    highlightTerm = text;
+                    highlightPattern = Pattern.compile(Pattern.quote(text), 
Pattern.CASE_INSENSITIVE);
+                }
+                highlightInputActive = false;
+            }
+            return true;
+        }
+        FormHelper.handleTextInput(ke, searchInputState);
+        return true;
+    }
+
     @Override
     public boolean handleEscape() {
+        if (findInputActive || highlightInputActive) {
+            findInputActive = false;
+            highlightInputActive = false;
+            return true;
+        }
         if (showLogLevelPopup) {
             showLogLevelPopup = false;
             return true;
         }
+        if (findTerm != null) {
+            findTerm = null;
+            findPattern = null;
+            findMatches = Collections.emptyList();
+            findMatchIndex = -1;
+            return true;
+        }
         return false;
     }
 
@@ -202,24 +312,44 @@ class LogTab implements MonitorTab {
             return;
         }
 
+        if (logLoading && filteredLogEntries.isEmpty()) {
+            Block loadingBlock = Block.builder()
+                    .borderType(BorderType.ROUNDED)
+                    .title(" Log ")
+                    .build();
+            frame.renderWidget(loadingBlock, area);
+            Rect loadingInner = loadingBlock.inner(area);
+            frame.renderWidget(
+                    
Paragraph.builder().text(Text.from(Line.from(Span.raw("(Loading logs...)"))))
+                            .build(),
+                    loadingInner);
+            return;
+        }
+
         List<LogEntry> entries = filteredLogEntries;
         int contentHeight = entries.size();
 
-        long totalRead = logTotalLinesRead;
-        String chunkSuffix = totalRead > entries.size()
-                ? " #" + (totalRead - entries.size() + 1) + "-" + totalRead
-                : "";
-        String logTitle;
+        boolean hasNew = !followMode && scroll < contentHeight - Math.max(1, 
area.height() - 2);
+        String logLabel;
         if (infraSel != null) {
-            logTitle = " Log [" + infraSel.alias + "]" + chunkSuffix + " ";
+            logLabel = " Log [" + infraSel.alias + "]";
         } else if (info != null && info.rootLogLevel != null) {
-            logTitle = " Log level:" + info.rootLogLevel + chunkSuffix + " ";
+            logLabel = " Log level:" + info.rootLogLevel;
         } else {
-            logTitle = " Log" + chunkSuffix + " ";
+            logLabel = " Log";
+        }
+        Line titleLine;
+        if (hasNew) {
+            titleLine = Line.from(
+                    Span.raw(logLabel + " "),
+                    Span.styled("(*)", Style.EMPTY.fg(Color.YELLOW).bold()),
+                    Span.raw(" "));
+        } else {
+            titleLine = Line.from(Span.raw(logLabel + " "));
         }
         Block block = Block.builder()
                 .borderType(BorderType.ROUNDED)
-                .title(logTitle)
+                .title(Title.from(titleLine))
                 .build();
         frame.renderWidget(block, area);
 
@@ -239,13 +369,14 @@ class LogTab implements MonitorTab {
 
         int hSkip = wordWrap ? 0 : hScroll;
 
-        if (entries != cachedLogEntries || hSkip != cachedLogHSkip) {
+        boolean entriesChanged = entries != cachedLogEntries;
+        if (entriesChanged || hSkip != cachedLogHSkip) {
             cachedLogEntries = entries;
             cachedLogHSkip = hSkip;
             List<Line> built = new ArrayList<>(entries.size());
             int maxW = 0;
-            for (LogEntry entry : entries) {
-                String raw = entry.raw != null ? entry.raw : "";
+            for (int i = 0; i < entries.size(); i++) {
+                String raw = entries.get(i).raw != null ? entries.get(i).raw : 
"";
                 if (!wordWrap) {
                     maxW = Math.max(maxW, 
CharWidth.of(TuiHelper.stripAnsi(raw)));
                 }
@@ -260,10 +391,25 @@ class LogTab implements MonitorTab {
             hScroll = Math.min(hScroll, Math.max(0, cachedLogMaxWidth - 
visibleWidth));
         }
 
+        if (findPattern != null && entriesChanged) {
+            buildFindMatches();
+        }
+
         List<Line> allLines = cachedLogLines;
         int start = Math.min(scroll, Math.max(0, allLines.size() - 
visibleHeight));
         List<Line> visibleLines = allLines.subList(start, 
Math.min(allLines.size(), start + visibleHeight));
 
+        // Apply highlights only to visible lines
+        if (highlightPattern != null || findPattern != null) {
+            int currentMatchLine = findMatchIndex >= 0 && findMatchIndex < 
findMatches.size()
+                    ? findMatches.get(findMatchIndex) : -1;
+            List<Line> highlighted = new ArrayList<>(visibleLines.size());
+            for (int i = 0; i < visibleLines.size(); i++) {
+                highlighted.add(applyHighlights(visibleLines.get(i), start + 
i, currentMatchLine));
+            }
+            visibleLines = highlighted;
+        }
+
         List<Rect> hChunks = Layout.horizontal()
                 .constraints(Constraint.fill(), Constraint.length(1))
                 .split(inner);
@@ -289,20 +435,43 @@ class LogTab implements MonitorTab {
 
     @Override
     public void renderFooter(List<Span> spans) {
+        if (findInputActive) {
+            spans.add(Span.styled(" /", HINT_KEY_STYLE));
+            spans.add(Span.raw(searchInputState.text() + "█  "));
+            hint(spans, "Enter", "search");
+            hintLast(spans, "Esc", "cancel");
+            return;
+        }
+        if (highlightInputActive) {
+            spans.add(Span.styled(" h:", HINT_KEY_STYLE));
+            spans.add(Span.raw(searchInputState.text() + "█  "));
+            hint(spans, "Enter", "set");
+            hintLast(spans, "Esc", "cancel");
+            return;
+        }
         if (showLogLevelPopup) {
             hint(spans, "↑↓", "navigate");
             hint(spans, "Enter", "set level");
             hintLast(spans, "Esc", "cancel");
             return;
         }
-        hint(spans, "Esc", "back");
+
+        if (findTerm != null) {
+            hint(spans, "Esc", "clear find");
+            hint(spans, "n", "next");
+            hint(spans, "N", "prev");
+            String pos = findMatches.isEmpty()
+                    ? "0/0"
+                    : (findMatchIndex + 1) + "/" + findMatches.size();
+            spans.add(Span.styled("  /", HINT_KEY_STYLE));
+            spans.add(Span.raw("\"" + findTerm + "\" [" + pos + "]  "));
+        } else {
+            hint(spans, "Esc", "back");
+        }
         hint(spans, "↑↓", "scroll");
-        hint(spans, "PgUp/PgDn", "page");
-        hint(spans, "Home/End", "top/end");
+        hint(spans, "/", "find");
+        hint(spans, "h", "highlight" + (highlightTerm != null ? " [" + 
highlightTerm + "]" : ""));
         hint(spans, "w", "wrap" + (wordWrap ? " [on]" : " [off]"));
-        if (!wordWrap) {
-            hint(spans, "←→", "h-scroll");
-        }
         if (!ctx.isInfraSelected()) {
             hint(spans, "l", "level");
         }
@@ -347,6 +516,130 @@ class LogTab implements MonitorTab {
         
org.apache.camel.dsl.jbang.core.common.PathUtils.writeTextSafely(root.toJson(), 
actionFile);
     }
 
+    private void buildFindMatches() {
+        List<Integer> matches = new ArrayList<>();
+        List<LogEntry> entries = filteredLogEntries;
+        for (int i = 0; i < entries.size(); i++) {
+            String plain = TuiHelper.stripAnsi(entries.get(i).raw != null ? 
entries.get(i).raw : "");
+            if (findPattern.matcher(plain).find()) {
+                matches.add(i);
+            }
+        }
+        findMatches = matches;
+    }
+
+    private void jumpToNearestMatch() {
+        if (findMatches.isEmpty()) {
+            findMatchIndex = -1;
+            return;
+        }
+        for (int i = 0; i < findMatches.size(); i++) {
+            if (findMatches.get(i) >= scroll) {
+                findMatchIndex = i;
+                scrollToMatch();
+                return;
+            }
+        }
+        findMatchIndex = 0;
+        scrollToMatch();
+    }
+
+    private void navigateToNextMatch() {
+        if (findMatches.isEmpty()) {
+            return;
+        }
+        findMatchIndex = (findMatchIndex + 1) % findMatches.size();
+        scrollToMatch();
+    }
+
+    private void navigateToPrevMatch() {
+        if (findMatches.isEmpty()) {
+            return;
+        }
+        findMatchIndex = findMatchIndex <= 0 ? findMatches.size() - 1 : 
findMatchIndex - 1;
+        scrollToMatch();
+    }
+
+    private void scrollToMatch() {
+        if (findMatchIndex >= 0 && findMatchIndex < findMatches.size()) {
+            followMode = false;
+            scroll = findMatches.get(findMatchIndex);
+        }
+    }
+
+    boolean isSearchInputActive() {
+        return findInputActive || highlightInputActive;
+    }
+
+    void handlePaste(String text) {
+        if (findInputActive || highlightInputActive) {
+            FormHelper.handlePaste(text, searchInputState);
+        }
+    }
+
+    private Line applyHighlights(Line line, int entryIndex, int 
currentMatchLine) {
+        String fullText = line.rawContent();
+        if (fullText.isEmpty()) {
+            return line;
+        }
+
+        // Collect all match ranges with their styles
+        List<int[]> ranges = new ArrayList<>();
+        List<Style> styles = new ArrayList<>();
+        if (highlightPattern != null) {
+            Matcher m = highlightPattern.matcher(fullText);
+            while (m.find()) {
+                ranges.add(new int[] { m.start(), m.end() });
+                styles.add(HIGHLIGHT_STYLE);
+            }
+        }
+        if (findPattern != null) {
+            boolean isCurrentLine = entryIndex == currentMatchLine;
+            Matcher m = findPattern.matcher(fullText);
+            while (m.find()) {
+                ranges.add(new int[] { m.start(), m.end() });
+                styles.add(isCurrentLine ? FIND_CURRENT_STYLE : 
FIND_MATCH_STYLE);
+            }
+        }
+        if (ranges.isEmpty()) {
+            return line;
+        }
+
+        // Rebuild spans with highlights applied
+        List<Span> original = line.spans();
+        List<Span> result = new ArrayList<>();
+        int charPos = 0;
+
+        for (Span span : original) {
+            String content = span.content();
+            Style baseStyle = span.style();
+            int spanStart = charPos;
+            int spanEnd = charPos + content.length();
+            int cursor = 0;
+
+            for (int r = 0; r < ranges.size(); r++) {
+                int matchStart = ranges.get(r)[0];
+                int matchEnd = ranges.get(r)[1];
+                if (matchEnd <= spanStart || matchStart >= spanEnd) {
+                    continue;
+                }
+                int localStart = Math.max(0, matchStart - spanStart);
+                int localEnd = Math.min(content.length(), matchEnd - 
spanStart);
+
+                if (localStart > cursor) {
+                    result.add(Span.styled(content.substring(cursor, 
localStart), baseStyle));
+                }
+                result.add(Span.styled(content.substring(localStart, 
localEnd), styles.get(r)));
+                cursor = localEnd;
+            }
+            if (cursor < content.length()) {
+                result.add(Span.styled(content.substring(cursor), baseStyle));
+            }
+            charPos = spanEnd;
+        }
+        return Line.from(result);
+    }
+
     void readNewLogLines(String pid, List<String> newLines) {
         readNewLogLinesFromFile(pid, pid + ".log", newLines);
     }
@@ -362,14 +655,17 @@ class LogTab implements MonitorTab {
         try (RandomAccessFile raf = new RandomAccessFile(logFile.toFile(), 
"r")) {
             long length = raf.length();
             if (logFilePos < 0 || logFilePos > length) {
-                logFilePos = Math.max(0, length - 1024 * 1024);
+                // Initial load: only read last 8KB for fast first render
+                logFilePos = Math.max(0, length - 8 * 1024);
+                logFileStartPos = logFilePos;
                 logLineBuffer.setLength(0);
             }
             if (logFilePos >= length) {
                 return;
             }
             raf.seek(logFilePos);
-            byte[] buf = new byte[(int) Math.min(length - logFilePos, 4 * 1024 
* 1024)];
+            // Cap per-tick read to 64KB to avoid blocking the refresh cycle
+            byte[] buf = new byte[(int) Math.min(length - logFilePos, 64 * 
1024)];
             raf.readFully(buf);
             logFilePos += buf.length;
 
@@ -392,6 +688,53 @@ class LogTab implements MonitorTab {
         }
     }
 
+    void readOlderLogLines(String fileName, boolean loadAll, List<String> 
olderLines) {
+        if (logFilePid == null || logFileStartPos <= 0) {
+            return;
+        }
+        Path logFile = CommandLineHelper.getCamelDir().resolve(fileName);
+        if (!Files.exists(logFile)) {
+            return;
+        }
+        try (RandomAccessFile raf = new RandomAccessFile(logFile.toFile(), 
"r")) {
+            long readEnd = logFileStartPos;
+            // Load all: read from start of file (cap at 2MB), otherwise 64KB 
chunk
+            long readStart;
+            if (loadAll) {
+                readStart = Math.max(0, readEnd - 2 * 1024 * 1024);
+            } else {
+                readStart = Math.max(0, readEnd - 64 * 1024);
+            }
+            if (readStart >= readEnd) {
+                return;
+            }
+            raf.seek(readStart);
+            byte[] buf = new byte[(int) (readEnd - readStart)];
+            raf.readFully(buf);
+            logFileStartPos = readStart;
+
+            String chunk = new String(buf, StandardCharsets.UTF_8);
+            // If we didn't read from the start of the file, skip the first 
partial line
+            int start = 0;
+            if (readStart > 0) {
+                int firstNewline = chunk.indexOf('\n');
+                if (firstNewline >= 0) {
+                    start = firstNewline + 1;
+                }
+            }
+            int end;
+            while ((end = chunk.indexOf('\n', start)) >= 0) {
+                String line = TuiHelper.fixControlChars(chunk.substring(start, 
end));
+                if (!line.isEmpty()) {
+                    olderLines.add(line);
+                }
+                start = end + 1;
+            }
+        } catch (IOException e) {
+            // ignore
+        }
+    }
+
     static LogEntry parseLogLine(String line) {
         LogEntry entry = new LogEntry();
         entry.raw = line;
@@ -466,6 +809,23 @@ class LogTab implements MonitorTab {
                 Filtering is useful when the log is noisy with INFO messages 
and you
                 want to focus on warnings and errors.
 
+                ## Find and Highlight
+
+                **Find** (`/`) — search for text in the log. Type a search 
term and
+                press Enter to jump to the first match. Use `n` to go to the 
next
+                match and `N` for the previous match. The current match is 
shown
+                with a green background, other matches with yellow. Press `Esc`
+                to clear the search.
+
+                **Highlight** (`h`) — persistently highlight all occurrences 
of a
+                word in the log. Type a word and press Enter — all occurrences
+                are highlighted with a yellow background while the log 
continues
+                scrolling in follow mode. Press `h` again and submit an empty
+                term to clear the highlight. Both find and highlight can be
+                active at the same time.
+
+                Both find and highlight are case-insensitive.
+
                 ## Thread Names
 
                 The thread name in square brackets (e.g., `[Camel (camel-demo) 
thread #2]`)
@@ -477,8 +837,14 @@ class LogTab implements MonitorTab {
                 - `Up/Down` — scroll log
                 - `PgUp/PgDn` — scroll by page
                 - `Home/End` — jump to beginning/end of log
+                - `/` — find (search for text)
+                - `n` — next match
+                - `N` — previous match
+                - `h` — highlight a word
                 - `l` — change log level filter
-                - `Esc` — back
+                - `f` — toggle follow mode
+                - `w` — toggle word wrap
+                - `Esc` — clear find / back
                 """;
     }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java
index 4d6bd5f034ca..505627de6223 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java
@@ -190,7 +190,11 @@ class OverviewTab implements MonitorTab {
                         Cell.from(Span.styled("", dimStyle)),
                         Cell.from(Span.styled("", dimStyle))));
             } else {
-                Style statusStyle = switch (extractState(info.state)) {
+                String stateText = extractState(info.state);
+                if ("Running".equals(stateText) && info.routeStarted == 0 && 
info.routeTotal > 0) {
+                    stateText = "Stopped";
+                }
+                Style statusStyle = switch (stateText) {
                     case "Started", "Running" -> Style.EMPTY.fg(Color.GREEN);
                     case "Stopped" -> Style.EMPTY.fg(Color.LIGHT_RED);
                     default -> Style.EMPTY.fg(Color.YELLOW);
@@ -211,7 +215,7 @@ class OverviewTab implements MonitorTab {
                         Cell.from(nameLine),
                         Cell.from(info.camelVersion != null ? 
info.camelVersion : ""),
                         centerCell(info.ready != null ? info.ready : "", 5),
-                        Cell.from(Span.styled(extractState(info.state), 
statusStyle)),
+                        Cell.from(Span.styled(stateText, statusStyle)),
                         Cell.from(info.ago != null ? info.ago : ""),
                         rightCell(info.routeStarted + "/" + info.routeTotal, 
7),
                         rightCell(info.throughput != null ? info.throughput : 
"", 8),
@@ -724,7 +728,7 @@ class OverviewTab implements MonitorTab {
                 - **TOTAL** — Total number of exchanges (messages) processed 
since the integration started
                 - **FAIL** — Number of exchanges that ended with an unhandled 
error
                 - **INFLIGHT** — Exchanges currently being processed right 
now. A consistently high inflight count may indicate slow downstream services
-                - **SINCE-LAST** — Time elapsed since the last exchange was 
processed. A long idle time might indicate that consumers have stopped 
receiving data
+                - **SINCE-LAST** — Time since the last exchange activity, 
shown as up to three values separated by `/`: started/completed/failed. For 
example, `1s/3s/1m14s` means the last exchange started 1s ago, the last 
completed 3s ago, and the last failure was 1m14s ago. Values are omitted when 
there is no activity of that type
 
                 ## Example Screen
 
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 21584f5e1d80..766c43e966a8 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
@@ -44,6 +44,8 @@ import dev.tamboui.widgets.table.Row;
 import dev.tamboui.widgets.table.Table;
 import dev.tamboui.widgets.table.TableState;
 import org.apache.camel.dsl.jbang.core.common.PathUtils;
+import org.apache.camel.support.LoggerHelper;
+import org.apache.camel.util.FileUtil;
 import org.apache.camel.util.json.JsonArray;
 import org.apache.camel.util.json.JsonObject;
 import org.apache.camel.util.json.Jsoner;
@@ -81,6 +83,7 @@ class RoutesTab implements MonitorTab {
     private boolean showSource;
     private List<String> sourceLines = Collections.emptyList();
     private String sourceTitle;
+    private SyntaxHighlighter.Language sourceLanguage = 
SyntaxHighlighter.Language.PLAIN;
     private int sourceScroll;
     private int sourceScrollX;
     private final ScrollbarState sourceVScrollState = new ScrollbarState();
@@ -480,7 +483,7 @@ class RoutesTab implements MonitorTab {
                 hint(spans, "c", "source");
                 hint(spans, "d", "diagram");
                 hint(spans, "D", "text diagram");
-                hint(spans, "a", diagramAllRoutes ? "all [on]" : "all [off]");
+                hint(spans, "a", "diagram " + (diagramAllRoutes ? "[all]" : 
"[single]"));
                 String routeState = selectedRouteState();
                 boolean supSus = selectedRouteSupportsSuspension();
                 if ("Started".equals(routeState)) {
@@ -732,7 +735,7 @@ class RoutesTab implements MonitorTab {
         List<Line> visible = new ArrayList<>();
         for (int i = sourceScroll; i < end; i++) {
             String raw = sourceLines.get(i);
-            visible.add(TuiHelper.ansiToLine(raw, sourceScrollX));
+            visible.add(highlightSourceLine(raw, sourceScrollX));
         }
         
frame.renderWidget(Paragraph.builder().text(Text.from(visible)).build(), inner);
 
@@ -748,6 +751,48 @@ class RoutesTab implements MonitorTab {
         }
     }
 
+    private Line highlightSourceLine(String raw, int hSkip) {
+        // Split line number prefix from code content
+        int prefixEnd = 0;
+        while (prefixEnd < raw.length() && (raw.charAt(prefixEnd) == ' ' || 
Character.isDigit(raw.charAt(prefixEnd)))) {
+            prefixEnd++;
+        }
+
+        String prefix = raw.substring(0, prefixEnd);
+        String code = raw.substring(prefixEnd);
+
+        Line highlighted = SyntaxHighlighter.highlightLine(code, 
sourceLanguage);
+
+        // Prepend dim line-number prefix
+        List<Span> spans = new ArrayList<>();
+        if (!prefix.isEmpty()) {
+            spans.add(Span.styled(prefix, Style.EMPTY.dim()));
+        }
+        spans.addAll(highlighted.spans());
+
+        Line full = Line.from(spans);
+
+        // Apply horizontal scroll by skipping characters from spans
+        if (hSkip <= 0) {
+            return full;
+        }
+        List<Span> scrolled = new ArrayList<>();
+        int skipped = 0;
+        for (Span span : full.spans()) {
+            String content = span.content();
+            if (skipped >= hSkip) {
+                scrolled.add(span);
+            } else if (skipped + content.length() > hSkip) {
+                int offset = hSkip - skipped;
+                scrolled.add(Span.styled(content.substring(offset), 
span.style()));
+                skipped = hSkip;
+            } else {
+                skipped += content.length();
+            }
+        }
+        return scrolled.isEmpty() ? Line.from(List.of(Span.raw(""))) : 
Line.from(scrolled);
+    }
+
     // ---- Sorting ----
 
     private int sortRoute(RouteInfo a, RouteInfo b) {
@@ -1065,7 +1110,12 @@ class RoutesTab implements MonitorTab {
             idx++;
         }
 
-        int scrollTo = matchLine > 0 ? Math.max(0, matchLine - 2) : 0;
+        int scrollTo;
+        if (matchLine > 0) {
+            scrollTo = Math.max(0, matchLine - 2);
+        } else {
+            scrollTo = findLicenseHeaderEnd(codeLines);
+        }
         applySourceResult(routeId, sourceLocation, lines, scrollTo);
     }
 
@@ -1081,12 +1131,48 @@ class RoutesTab implements MonitorTab {
             if (!showSource) {
                 return;
             }
-            sourceTitle = location != null ? routeId + "  " + location : 
routeId;
+            String displayLoc = location != null ? 
FileUtil.stripPath(LoggerHelper.sourceNameOnly(location)) : null;
+            sourceTitle = displayLoc != null ? routeId + "  " + displayLoc : 
routeId;
+            sourceLanguage = SyntaxHighlighter.detectLanguage(location);
             sourceLines = lines;
             sourceScroll = scrollTo;
         });
     }
 
+    private static int findLicenseHeaderEnd(List<JsonObject> codeLines) {
+        // Auto-scroll past leading license/comment headers
+        boolean inBlock = false;
+        int lastCommentLine = -1;
+        for (int i = 0; i < codeLines.size(); i++) {
+            String code = objToString(codeLines.get(i).get("code")).trim();
+            if (i == 0 && code.isEmpty()) {
+                continue;
+            }
+            if (!inBlock && code.startsWith("/*")) {
+                inBlock = true;
+            }
+            if (inBlock) {
+                lastCommentLine = i;
+                if (code.contains("*/")) {
+                    inBlock = false;
+                }
+                continue;
+            }
+            // YAML/shell comment lines or XML comment lines at the top
+            if (code.startsWith("#") || code.startsWith("##") || 
code.startsWith("<!--")) {
+                lastCommentLine = i;
+                continue;
+            }
+            // Empty line right after comment block
+            if (lastCommentLine >= 0 && code.isEmpty()) {
+                lastCommentLine = i;
+                continue;
+            }
+            break;
+        }
+        return lastCommentLine >= 0 ? lastCommentLine + 1 : 0;
+    }
+
     private static String objToString(Object o) {
         return o != null ? o.toString() : "";
     }
@@ -1126,7 +1212,7 @@ class RoutesTab implements MonitorTab {
                 - **MIN** — Fastest exchange processing time in milliseconds. 
This is the time from when the exchange entered the route until it completed
                 - **MEAN** — Average exchange processing time in milliseconds. 
A rising MEAN may indicate a downstream service getting slower
                 - **MAX** — Slowest exchange processing time in milliseconds. 
A very high MAX compared to MEAN suggests occasional slow outliers
-                - **SINCE-LAST** — Time since the last exchange was processed 
by this route
+                - **SINCE-LAST** — Time since the last exchange activity on 
this route, shown as up to three values separated by `/`: 
started/completed/failed (e.g., `1s/3s/1m14s`). Values are omitted when there 
is no activity of that type
 
                 ## Example Screen
 
@@ -1182,11 +1268,16 @@ class RoutesTab implements MonitorTab {
                 ## Keys
 
                 - `Up/Down` — select route
+                - `p` — start/stop selected route
+                - `P` — suspend/resume selected route
                 - `d` — show route diagram
-                - `s` — show route source / cycle sort column 
(context-dependent)
+                - `D` — show text diagram
+                - `a` — toggle diagram scope (single route or all routes)
+                - `c` — show route source code
+                - `m` — toggle metrics in diagram
+                - `s` — cycle sort column
                 - `S` — reverse sort order
                 - `t` — toggle Top mode
-                - `Enter` — view detailed route info
                 - `Esc` — back to route list
                 """;
     }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RunOptionsForm.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RunOptionsForm.java
index 62e12ecdbc20..c481f6b030cd 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RunOptionsForm.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RunOptionsForm.java
@@ -81,11 +81,15 @@ class RunOptionsForm {
     }
 
     void open(String defaultName, String exampleName, boolean bundled) {
+        open(defaultName, exampleName, bundled, false);
+    }
+
+    void open(String defaultName, String exampleName, boolean bundled, boolean 
dev) {
         nameInput = new TextInputState(defaultName != null ? defaultName : "");
         portInput = new TextInputState("");
         maxInput = new TextInputState("");
         maxMode = 0;
-        devMode = false;
+        devMode = dev;
         observe = false;
         backlogTrace = false;
         selectedRow = ROW_NAME;
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SyntaxHighlighter.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SyntaxHighlighter.java
new file mode 100644
index 000000000000..dbd3b68ccdd2
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SyntaxHighlighter.java
@@ -0,0 +1,291 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.jbang.core.commands.tui;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import dev.tamboui.style.Color;
+import dev.tamboui.style.Style;
+import dev.tamboui.text.Line;
+import dev.tamboui.text.Span;
+import org.apache.camel.util.FileUtil;
+
+class SyntaxHighlighter {
+
+    enum Language {
+        JAVA,
+        YAML,
+        XML,
+        PLAIN
+    }
+
+    // Java patterns (ordered by priority — comments first)
+    private static final Pattern JAVA_LINE_COMMENT = Pattern.compile("//.*");
+    private static final Pattern JAVA_STRING = 
Pattern.compile("\"(?:[^\"\\\\]|\\\\.)*\"");
+    private static final Pattern JAVA_ANNOTATION = Pattern.compile("@\\w+");
+    private static final Pattern JAVA_MODIFIER = Pattern.compile(
+            
"\\b(abstract|class|extends|final|implements|import|instanceof|interface|native|package|private|protected|public|static|strictfp|super|synchronized|throws|volatile|enum|record|sealed|permits|non-sealed)\\b");
+    private static final Pattern JAVA_KEYWORD = Pattern.compile(
+            
"\\b(break|case|catch|continue|default|do|else|finally|for|if|return|switch|throw|try|while|yield|var)\\b");
+    private static final Pattern JAVA_TYPE = Pattern.compile(
+            
"\\b(boolean|byte|char|double|float|int|long|new|short|this|transient|void)\\b");
+    private static final Pattern JAVA_BOOLEAN_NULL = 
Pattern.compile("\\b(true|false|null)\\b");
+    private static final Pattern JAVA_NUMBER = 
Pattern.compile("\\b\\d+\\.?\\d*[fFdDlL]?\\b");
+
+    // YAML patterns
+    private static final Pattern YAML_COMMENT = Pattern.compile("(^|\\s)#.*$");
+    private static final Pattern YAML_KEY = 
Pattern.compile("^(\\s*-?\\s*)([\\w./${}\\-]+)\\s*:");
+    private static final Pattern YAML_BOOLEAN_NULL = 
Pattern.compile(":\\s+(true|false|null)\\s*$");
+    private static final Pattern YAML_NUMBER = 
Pattern.compile(":\\s+(\\d+\\.?\\d*)\\s*$");
+    private static final Pattern YAML_STRING_VALUE = 
Pattern.compile("\"(?:[^\"\\\\]|\\\\.)*\"|'[^']*'");
+
+    // XML patterns
+    private static final Pattern XML_COMMENT = Pattern.compile("<!--.*?-->");
+    private static final Pattern XML_OPEN_TAG = 
Pattern.compile("</?[\\w:.-]+");
+    private static final Pattern XML_CLOSE_BRACKET = Pattern.compile("/?>|>");
+    private static final Pattern XML_ATTR_VALUE = 
Pattern.compile("=\"[^\"]*\"");
+    private static final Pattern XML_ATTR_NAME = 
Pattern.compile("\\s([\\w:.-]+)=");
+    private static final Pattern XML_ENTITY = Pattern.compile("&[^;]+;");
+
+    // Java styles
+    private static final Style JAVA_COMMENT_STYLE = 
Style.EMPTY.fg(Color.LIGHT_BLUE);
+    private static final Style JAVA_STRING_STYLE = Style.EMPTY.fg(Color.RED);
+    private static final Style JAVA_ANNOTATION_STYLE = 
Style.EMPTY.fg(Color.MAGENTA);
+    private static final Style JAVA_MODIFIER_STYLE = 
Style.EMPTY.fg(Color.CYAN);
+    private static final Style JAVA_KEYWORD_STYLE = Style.EMPTY.fg(Color.RED);
+    private static final Style JAVA_TYPE_STYLE = Style.EMPTY.fg(Color.GREEN);
+    private static final Style JAVA_BOOLEAN_STYLE = 
Style.EMPTY.fg(Color.YELLOW);
+    private static final Style JAVA_NUMBER_STYLE = 
Style.EMPTY.fg(Color.YELLOW);
+
+    // YAML styles
+    private static final Style YAML_COMMENT_STYLE = 
Style.EMPTY.fg(Color.LIGHT_BLUE);
+    private static final Style YAML_KEY_STYLE = Style.EMPTY.fg(Color.RED);
+    private static final Style YAML_VALUE_STYLE = Style.EMPTY.fg(Color.GREEN);
+    private static final Style YAML_SPECIAL_STYLE = 
Style.EMPTY.fg(Color.YELLOW);
+    private static final Style YAML_SEPARATOR_STYLE = 
Style.EMPTY.fg(Color.WHITE).bold();
+
+    // XML styles
+    private static final Style XML_COMMENT_STYLE = 
Style.EMPTY.fg(Color.YELLOW);
+    private static final Style XML_TAG_STYLE = Style.EMPTY.fg(Color.CYAN);
+    private static final Style XML_ATTR_NAME_STYLE = 
Style.EMPTY.fg(Color.MAGENTA);
+    private static final Style XML_ATTR_VALUE_STYLE = 
Style.EMPTY.fg(Color.GREEN);
+    private static final Style XML_ENTITY_STYLE = Style.EMPTY.fg(Color.RED);
+
+    private SyntaxHighlighter() {
+    }
+
+    static Language detectLanguage(String filename) {
+        if (filename == null || filename.isEmpty()) {
+            return Language.PLAIN;
+        }
+        // Strip line number suffixes (e.g., "MyRoute.java:42")
+        String name = filename;
+        int colon = name.lastIndexOf(':');
+        if (colon > 0) {
+            String after = name.substring(colon + 1);
+            if (!after.isEmpty() && 
after.chars().allMatch(Character::isDigit)) {
+                name = name.substring(0, colon);
+            }
+        }
+        String ext = FileUtil.onlyExt(name);
+        if (ext == null) {
+            return Language.PLAIN;
+        }
+        ext = ext.toLowerCase();
+        return switch (ext) {
+            case "java" -> Language.JAVA;
+            case "yaml", "yml", "camel.yaml", "camel.yml" -> Language.YAML;
+            case "xml", "camel.xml" -> Language.XML;
+            default -> Language.PLAIN;
+        };
+    }
+
+    static Line highlightLine(String text, Language lang) {
+        if (text == null || text.isEmpty() || lang == Language.PLAIN) {
+            return Line.from(List.of(Span.raw(text != null ? text : "")));
+        }
+
+        return switch (lang) {
+            case JAVA -> highlightJava(text);
+            case YAML -> highlightYaml(text);
+            case XML -> highlightXml(text);
+            default -> Line.from(List.of(Span.raw(text)));
+        };
+    }
+
+    private static Line highlightJava(String text) {
+        int len = text.length();
+        Style[] charStyles = new Style[len];
+
+        // Priority order: comments > strings > annotations > keywords > 
numbers
+        applyPattern(charStyles, text, JAVA_LINE_COMMENT, JAVA_COMMENT_STYLE);
+        applyPattern(charStyles, text, JAVA_STRING, JAVA_STRING_STYLE);
+        applyPattern(charStyles, text, JAVA_ANNOTATION, JAVA_ANNOTATION_STYLE);
+        applyPattern(charStyles, text, JAVA_MODIFIER, JAVA_MODIFIER_STYLE);
+        applyPattern(charStyles, text, JAVA_KEYWORD, JAVA_KEYWORD_STYLE);
+        applyPattern(charStyles, text, JAVA_TYPE, JAVA_TYPE_STYLE);
+        applyPattern(charStyles, text, JAVA_BOOLEAN_NULL, JAVA_BOOLEAN_STYLE);
+        applyPattern(charStyles, text, JAVA_NUMBER, JAVA_NUMBER_STYLE);
+
+        return buildLine(text, charStyles);
+    }
+
+    private static Line highlightYaml(String text) {
+        int len = text.length();
+        Style[] charStyles = new Style[len];
+
+        // Comments have highest priority
+        applyPattern(charStyles, text, YAML_COMMENT, YAML_COMMENT_STYLE);
+
+        // Key portion (before colon)
+        Matcher keyMatcher = YAML_KEY.matcher(text);
+        if (keyMatcher.find()) {
+            int keyStart = keyMatcher.start(2);
+            int keyEnd = keyMatcher.end(2);
+            for (int i = keyStart; i < keyEnd && i < len; i++) {
+                if (charStyles[i] == null) {
+                    charStyles[i] = YAML_KEY_STYLE;
+                }
+            }
+            // Colon separator
+            int colonIdx = text.indexOf(':', keyEnd);
+            if (colonIdx >= 0 && colonIdx < len && charStyles[colonIdx] == 
null) {
+                charStyles[colonIdx] = YAML_SEPARATOR_STYLE;
+            }
+        }
+
+        // String values
+        applyPattern(charStyles, text, YAML_STRING_VALUE, YAML_VALUE_STYLE);
+
+        // Special values (boolean, null, numbers) after colon
+        applyPatternGroup(charStyles, text, YAML_BOOLEAN_NULL, 1, 
YAML_SPECIAL_STYLE);
+        applyPatternGroup(charStyles, text, YAML_NUMBER, 1, 
YAML_SPECIAL_STYLE);
+
+        // List markers
+        Matcher listMarker = Pattern.compile("^(\\s*)(-)(\\s)").matcher(text);
+        if (listMarker.find()) {
+            int dashIdx = listMarker.start(2);
+            if (dashIdx < len && charStyles[dashIdx] == null) {
+                charStyles[dashIdx] = YAML_SEPARATOR_STYLE;
+            }
+        }
+
+        // Value text (after colon+space, non-special, non-quoted)
+        int colonPos = text.indexOf(':');
+        if (colonPos >= 0 && colonPos + 1 < len) {
+            int valueStart = colonPos + 1;
+            while (valueStart < len && text.charAt(valueStart) == ' ') {
+                valueStart++;
+            }
+            if (valueStart < len) {
+                boolean hasSpecial = false;
+                for (int i = valueStart; i < len; i++) {
+                    if (charStyles[i] != null) {
+                        hasSpecial = true;
+                        break;
+                    }
+                }
+                if (!hasSpecial) {
+                    for (int i = valueStart; i < len; i++) {
+                        charStyles[i] = YAML_VALUE_STYLE;
+                    }
+                }
+            }
+        }
+
+        return buildLine(text, charStyles);
+    }
+
+    private static Line highlightXml(String text) {
+        int len = text.length();
+        Style[] charStyles = new Style[len];
+
+        // Comments highest priority
+        applyPattern(charStyles, text, XML_COMMENT, XML_COMMENT_STYLE);
+
+        // Attribute values (before tag names so tags don't override)
+        applyPattern(charStyles, text, XML_ATTR_VALUE, XML_ATTR_VALUE_STYLE);
+
+        // Attribute names
+        Matcher attrMatcher = XML_ATTR_NAME.matcher(text);
+        while (attrMatcher.find()) {
+            int start = attrMatcher.start(1);
+            int end = attrMatcher.end(1);
+            for (int i = start; i < end; i++) {
+                if (charStyles[i] == null) {
+                    charStyles[i] = XML_ATTR_NAME_STYLE;
+                }
+            }
+        }
+
+        // Tag names
+        applyPattern(charStyles, text, XML_OPEN_TAG, XML_TAG_STYLE);
+        applyPattern(charStyles, text, XML_CLOSE_BRACKET, XML_TAG_STYLE);
+
+        // Entity references
+        applyPattern(charStyles, text, XML_ENTITY, XML_ENTITY_STYLE);
+
+        return buildLine(text, charStyles);
+    }
+
+    private static void applyPattern(Style[] charStyles, String text, Pattern 
pattern, Style style) {
+        Matcher m = pattern.matcher(text);
+        while (m.find()) {
+            for (int i = m.start(); i < m.end(); i++) {
+                if (charStyles[i] == null) {
+                    charStyles[i] = style;
+                }
+            }
+        }
+    }
+
+    private static void applyPatternGroup(Style[] charStyles, String text, 
Pattern pattern, int group, Style style) {
+        Matcher m = pattern.matcher(text);
+        while (m.find()) {
+            for (int i = m.start(group); i < m.end(group); i++) {
+                if (charStyles[i] == null) {
+                    charStyles[i] = style;
+                }
+            }
+        }
+    }
+
+    private static Line buildLine(String text, Style[] charStyles) {
+        List<Span> spans = new ArrayList<>();
+        int len = text.length();
+        int i = 0;
+
+        while (i < len) {
+            Style current = charStyles[i];
+            int start = i;
+            while (i < len && charStyles[i] == current) {
+                i++;
+            }
+            String segment = text.substring(start, i);
+            if (current != null) {
+                spans.add(Span.styled(segment, current));
+            } else {
+                spans.add(Span.raw(segment));
+            }
+        }
+
+        return spans.isEmpty() ? Line.from(List.of(Span.raw(text))) : 
Line.from(spans);
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiHelper.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiHelper.java
index ea967b02d759..c8ab629ca13c 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiHelper.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiHelper.java
@@ -183,6 +183,9 @@ final class TuiHelper {
         if (line == null || line.isEmpty()) {
             return line;
         }
+        if (line.indexOf('\u001B') < 0 && line.indexOf('\r') < 0 && 
line.indexOf('\t') < 0) {
+            return line;
+        }
         StringBuilder sb = new StringBuilder(line.length());
         int i = 0;
         while (i < line.length()) {

Reply via email to