This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch topology-external-endpoints
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 4d117003520694aceae4226d79c88e083144bbca
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Jun 2 12:54:22 2026 +0200

    Add stub checkbox and failure log dialog to TUI run options
    
    Add a "Stub (no Docker needed)" checkbox to the run options form that
    passes --stub=all and skips infra service startup. When an example
    launch fails, show the full log output in a scrollable dialog instead
    of a brief notification. Use TuiHelper.ansiToLine for proper ANSI/control
    character handling and Overflow.CLIP to prevent rendering artifacts.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
 .../dsl/jbang/core/commands/tui/ActionsPopup.java  | 65 +++++++++++++++++-----
 .../jbang/core/commands/tui/RunOptionsForm.java    | 32 ++++++++---
 2 files changed, 74 insertions(+), 23 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 9c660417f9f9..1fab0476680f 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
@@ -35,6 +35,7 @@ import java.util.stream.Collectors;
 import dev.tamboui.layout.Rect;
 import dev.tamboui.markdown.MarkdownView;
 import dev.tamboui.style.Color;
+import dev.tamboui.style.Overflow;
 import dev.tamboui.style.Style;
 import dev.tamboui.terminal.Frame;
 import dev.tamboui.text.Line;
@@ -847,9 +848,18 @@ class ActionsPopup {
     private void renderDocViewer(Frame frame, Rect area) {
         frame.renderWidget(Clear.INSTANCE, area);
         Rect popup = new Rect(area.left() + 2, area.top() + 1, area.width() - 
4, area.height() - 2);
+        Title title;
+        if (docTitle != null && docTitle.startsWith("Failed:")) {
+            String rest = docTitle.substring("Failed:".length());
+            title = Title.from(Line.from(
+                    Span.styled(" Failed:", 
Style.EMPTY.fg(Color.LIGHT_RED).bold()),
+                    Span.raw(rest + " ")));
+        } else {
+            title = Title.from(" " + docTitle + " ");
+        }
         Block block = Block.builder()
                 .borderType(BorderType.ROUNDED)
-                .title(" " + docTitle + " ")
+                .title(title)
                 .titleBottom(Title.from(Line.from(
                         Span.styled(" ↑↓", MonitorContext.HINT_KEY_STYLE), 
Span.raw(" scroll │"),
                         Span.styled(" Esc", MonitorContext.HINT_KEY_STYLE), 
Span.raw(" back "))))
@@ -861,9 +871,13 @@ class ActionsPopup {
             int totalLines = docLines.size();
             int clampedScroll = Math.min(docScroll, Math.max(0, totalLines - 
visibleLines));
             int end = Math.min(clampedScroll + visibleLines, totalLines);
-            List<Line> visible = docLines.subList(clampedScroll, end);
+            List<Line> visible = new 
ArrayList<>(docLines.subList(clampedScroll, end));
+            while (visible.size() < visibleLines) {
+                visible.add(Line.from(""));
+            }
             frame.renderWidget(
-                    
Paragraph.builder().text(Text.from(visible.toArray(Line[]::new))).build(),
+                    
Paragraph.builder().text(Text.from(visible.toArray(Line[]::new)))
+                            .overflow(Overflow.CLIP).build(),
                     inner);
         } else {
             MarkdownView view = MarkdownView.builder()
@@ -1333,16 +1347,19 @@ class ActionsPopup {
             displayName = exampleName;
         }
         List<String> extraArgs = runOptionsForm.buildArgs();
+        boolean stub = runOptionsForm.isStubMode();
         runOptionsForm.close();
 
-        List<String> missing = findMissingInfraServices(selectedExample);
-        if (!missing.isEmpty()) {
-            if (!isContainerRuntimeAvailable()) {
-                setNotification("Docker/Podman required for infra services. 
Run Doctor for details", true);
+        if (!stub) {
+            List<String> missing = findMissingInfraServices(selectedExample);
+            if (!missing.isEmpty()) {
+                if (!isContainerRuntimeAvailable()) {
+                    setNotification("Docker/Podman required for infra 
services. Run Doctor for details", true);
+                    return;
+                }
+                startMissingInfraAndDeferExample(missing, exampleName, 
displayName, extraArgs);
                 return;
             }
-            startMissingInfraAndDeferExample(missing, exampleName, 
displayName, extraArgs);
-            return;
         }
 
         doLaunchExample(exampleName, displayName, extraArgs);
@@ -1916,11 +1933,7 @@ class ActionsPopup {
                     launchNotificationError = false;
                     launchNotificationExpiry = now + 5000;
                 } else {
-                    String detail = readFirstLine(pl.outputFile());
-                    launchNotification = "Failed: " + pl.name()
-                                         + (detail != null ? " - " + detail : 
"");
-                    launchNotificationError = true;
-                    launchNotificationExpiry = now + 10000;
+                    showFailureLog(pl.name(), pl.outputFile());
                 }
                 it.remove();
             } else if (now - pl.startTime() > 8000) {
@@ -1932,6 +1945,30 @@ class ActionsPopup {
         }
     }
 
+    private void showFailureLog(String name, Path logFile) {
+        List<String> logLines = readAllLines(logFile);
+        if (logLines.isEmpty()) {
+            setNotification("Failed: " + name + " (no output)", true);
+            return;
+        }
+        docTitle = "Failed: " + name;
+        docContent = null;
+        docLines = logLines.stream()
+                .map(line -> TuiHelper.ansiToLine(line.replace("\t", "        
"), 0))
+                .collect(Collectors.toList());
+        docScroll = 0;
+        showDocViewer = true;
+        docViewerFromExampleBrowser = false;
+    }
+
+    private static List<String> readAllLines(Path file) {
+        try {
+            return Files.readAllLines(file);
+        } catch (IOException e) {
+            return List.of();
+        }
+    }
+
     // ---- Utilities ----
 
     private static String readFirstLine(Path file) {
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 c481f6b030cd..12b4a05df719 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
@@ -49,7 +49,8 @@ class RunOptionsForm {
     private static final int ROW_DEV = 3;
     private static final int ROW_OBSERVE = 4;
     private static final int ROW_TRACE = 5;
-    private static final int ROW_COUNT = 6;
+    private static final int ROW_STUB = 6;
+    private static final int ROW_COUNT = 7;
 
     private boolean visible;
     private int page;
@@ -68,6 +69,7 @@ class RunOptionsForm {
     private boolean devMode;
     private boolean observe;
     private boolean backlogTrace;
+    private boolean stubMode;
 
     private String exampleTitle;
 
@@ -92,6 +94,7 @@ class RunOptionsForm {
         devMode = dev;
         observe = false;
         backlogTrace = false;
+        stubMode = false;
         selectedRow = ROW_NAME;
         page = PAGE_OPTIONS;
         selectedProperty = 0;
@@ -108,6 +111,10 @@ class RunOptionsForm {
         return nameInput != null ? nameInput.text().trim() : "";
     }
 
+    boolean isStubMode() {
+        return stubMode;
+    }
+
     boolean handleKeyEvent(KeyEvent ke) {
         if (!visible) {
             return false;
@@ -182,6 +189,9 @@ class RunOptionsForm {
         if (backlogTrace) {
             args.add("--backlog-trace");
         }
+        if (stubMode) {
+            args.add("--stub=all");
+        }
         if (properties != null) {
             for (PropertyEntry pe : properties) {
                 String current = pe.valueInput().text();
@@ -205,7 +215,7 @@ class RunOptionsForm {
             return true;
         }
         if (ke.isDown()) {
-            if (selectedRow == ROW_TRACE && hasProperties()) {
+            if (selectedRow == ROW_STUB && hasProperties()) {
                 page = PAGE_PROPERTIES;
                 selectedProperty = 0;
             } else {
@@ -214,7 +224,7 @@ class RunOptionsForm {
             return true;
         }
         if (ke.isFocusNext()) {
-            if (selectedRow == ROW_TRACE && hasProperties()) {
+            if (selectedRow == ROW_STUB && hasProperties()) {
                 page = PAGE_PROPERTIES;
                 selectedProperty = 0;
             } else {
@@ -226,7 +236,7 @@ class RunOptionsForm {
             selectedRow = (selectedRow - 1 + ROW_COUNT) % ROW_COUNT;
             return true;
         }
-        if (ke.isRight() && hasProperties() && selectedRow >= ROW_DEV) {
+        if (ke.isRight() && hasProperties() && selectedRow >= ROW_STUB) {
             page = PAGE_PROPERTIES;
             selectedProperty = 0;
             return true;
@@ -250,6 +260,7 @@ class RunOptionsForm {
                 case ROW_DEV -> devMode = !devMode;
                 case ROW_OBSERVE -> observe = !observe;
                 case ROW_TRACE -> backlogTrace = !backlogTrace;
+                case ROW_STUB -> stubMode = !stubMode;
             }
             return true;
         }
@@ -276,7 +287,7 @@ class RunOptionsForm {
             editingKey = false;
             if (selectedProperty == 0) {
                 page = PAGE_OPTIONS;
-                selectedRow = ROW_TRACE;
+                selectedRow = ROW_STUB;
             } else {
                 selectedProperty--;
             }
@@ -306,7 +317,7 @@ class RunOptionsForm {
             } else if (selectedProperty == 0) {
                 editingKey = false;
                 page = PAGE_OPTIONS;
-                selectedRow = ROW_TRACE;
+                selectedRow = ROW_STUB;
             } else {
                 editingKey = false;
                 selectedProperty--;
@@ -322,7 +333,7 @@ class RunOptionsForm {
                     return true;
                 }
                 page = PAGE_OPTIONS;
-                selectedRow = ROW_TRACE;
+                selectedRow = ROW_STUB;
                 editingKey = false;
                 return true;
             }
@@ -337,7 +348,7 @@ class RunOptionsForm {
                     }
                     if (properties.isEmpty()) {
                         page = PAGE_OPTIONS;
-                        selectedRow = ROW_TRACE;
+                        selectedRow = ROW_STUB;
                     }
                     return true;
                 }
@@ -365,7 +376,7 @@ class RunOptionsForm {
 
     private void renderOptionsPage(Frame frame, Rect area) {
         int popupW = Math.min(56, area.width() - 4);
-        int popupH = 10;
+        int popupH = 11;
         int x = area.left() + Math.max(0, (area.width() - popupW) / 2);
         int y = area.top() + Math.max(0, (area.height() - popupH) / 2);
         Rect popup = new Rect(x, y, Math.min(popupW, area.width()), 
Math.min(popupH, area.height()));
@@ -427,6 +438,9 @@ class RunOptionsForm {
         rowY++;
 
         renderCheckbox(frame, innerX, rowY, innerW, "Backlog trace", 
backlogTrace, selectedRow == ROW_TRACE);
+        rowY++;
+
+        renderCheckbox(frame, innerX, rowY, innerW, "Stub (no Docker needed)", 
stubMode, selectedRow == ROW_STUB);
     }
 
     private void renderPropertiesPage(Frame frame, Rect area) {

Reply via email to