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

davsclaus pushed a commit to branch obs3
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 2a54e4ef7cb04fc00057388715b91561cbea7bc3
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat May 30 15:49:28 2026 +0200

    CAMEL-23648: camel-jbang - TUI HTTP tab add probe view for sending requests
    
    Add a Postman-like HTTP Probe sub-view accessible via Enter on any endpoint
    in the HTTP tab. Features include method selector, path/headers/body input,
    response display with status/headers/body, request history with replay,
    pretty print toggle for JSON/XML responses, and proper key handling
    isolation from global shortcuts.
    
    Co-Authored-By: Claude <[email protected]>
---
 .../dsl/jbang/core/commands/tui/CamelMonitor.java  | 123 ++-
 .../camel/dsl/jbang/core/commands/tui/HttpTab.java | 976 ++++++++++++++++++++-
 2 files changed, 1063 insertions(+), 36 deletions(-)

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 e70e6364e3fd..f07a205ad802 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
@@ -19,6 +19,7 @@ package org.apache.camel.dsl.jbang.core.commands.tui;
 import java.io.File;
 import java.io.IOException;
 import java.io.RandomAccessFile;
+import java.net.URI;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -483,48 +484,56 @@ public class CamelMonitor extends CamelCommand {
                 }
                 return true;
             }
-            // Quit: q or Ctrl+c
-            if (ke.isCharIgnoreCase('q') || ke.isCtrlC()) {
+            // Quit: q or Ctrl+c (skip when probe is editing text)
+            boolean probeEditing = tabsState.selected() == TAB_HTTP && 
httpTab.isProbeMode();
+            if (!probeEditing && (ke.isCharIgnoreCase('q') || ke.isCtrlC())) {
                 runner.quit();
                 return true;
             }
-            // Tab switching with number keys
-            // When infra is selected, only Overview (1) and Log (2) are 
available
-            if (ke.isChar('1')) {
-                return handleTabKey(TAB_OVERVIEW);
-            }
-            if (ke.isChar('2')) {
-                return handleTabKey(TAB_LOG);
+            if (ke.isCtrlC()) {
+                runner.quit();
+                return true;
             }
-            if (!isInfraSelected()) {
-                if (ke.isChar('3')) {
-                    return handleTabKey(TAB_ROUTES);
-                }
-                if (ke.isChar('4')) {
-                    return handleTabKey(TAB_ENDPOINTS);
-                }
-                if (ke.isChar('5')) {
-                    return handleTabKey(TAB_HTTP);
-                }
-                if (ke.isChar('6')) {
-                    return handleTabKey(TAB_HEALTH);
-                }
-                if (ke.isChar('7')) {
-                    return handleTabKey(TAB_HISTORY);
-                }
-                if (ke.isChar('8')) {
-                    return handleTabKey(TAB_ERRORS);
+            // Tab switching with number keys (skip when probe is editing text)
+            // When infra is selected, only Overview (1) and Log (2) are 
available
+            if (!probeEditing) {
+                if (ke.isChar('1')) {
+                    return handleTabKey(TAB_OVERVIEW);
                 }
-                if (ke.isChar('9')) {
-                    return handleTabKey(TAB_METRICS);
+                if (ke.isChar('2')) {
+                    return handleTabKey(TAB_LOG);
                 }
-                if (ke.isChar('0')) {
-                    return handleTabKey(TAB_MORE);
+                if (!isInfraSelected()) {
+                    if (ke.isChar('3')) {
+                        return handleTabKey(TAB_ROUTES);
+                    }
+                    if (ke.isChar('4')) {
+                        return handleTabKey(TAB_ENDPOINTS);
+                    }
+                    if (ke.isChar('5')) {
+                        return handleTabKey(TAB_HTTP);
+                    }
+                    if (ke.isChar('6')) {
+                        return handleTabKey(TAB_HEALTH);
+                    }
+                    if (ke.isChar('7')) {
+                        return handleTabKey(TAB_HISTORY);
+                    }
+                    if (ke.isChar('8')) {
+                        return handleTabKey(TAB_ERRORS);
+                    }
+                    if (ke.isChar('9')) {
+                        return handleTabKey(TAB_METRICS);
+                    }
+                    if (ke.isChar('0')) {
+                        return handleTabKey(TAB_MORE);
+                    }
                 }
             }
 
             // Tab cycling (check Shift+Tab before Tab since Tab binding also 
matches Shift+Tab)
-            if (ke.isFocusPrevious()) {
+            // Skip tab cycling when HTTP probe is active (Tab navigates 
fields)
+            if (ke.isFocusPrevious() && !(tabsState.selected() == TAB_HTTP && 
httpTab.isProbeMode())) {
                 if (isInfraSelected()) {
                     // Cycle between Overview and Log only
                     int prev = tabsState.selected() == TAB_OVERVIEW ? TAB_LOG 
: TAB_OVERVIEW;
@@ -538,7 +547,7 @@ public class CamelMonitor extends CamelCommand {
                 }
                 return true;
             }
-            if (ke.isFocusNext()) {
+            if (ke.isFocusNext() && !(tabsState.selected() == TAB_HTTP && 
httpTab.isProbeMode())) {
                 if (isInfraSelected()) {
                     int next = tabsState.selected() == TAB_OVERVIEW ? TAB_LOG 
: TAB_OVERVIEW;
                     tabsState.select(next);
@@ -678,6 +687,10 @@ public class CamelMonitor extends CamelCommand {
                 actionsPopup.handlePaste(pe.text());
                 return true;
             }
+            if (httpTab.isProbeMode()) {
+                httpTab.handlePaste(pe.text());
+                return true;
+            }
         }
         if (event instanceof TickEvent) {
             long now = System.currentTimeMillis();
@@ -3361,6 +3374,9 @@ public class CamelMonitor extends CamelCommand {
             parseHttpEndpoints(phpObj, "managementEndpoints", true, info);
         }
 
+        // Fix REST DSL URLs that are missing the port by resolving from 
platform-http endpoints
+        fixRestUrlPorts(info);
+
         // Parse configuration properties (from PropertiesDevConsole)
         JsonObject propsObj = (JsonObject) root.get("properties");
         if (propsObj != null) {
@@ -3406,6 +3422,47 @@ public class CamelMonitor extends CamelCommand {
         }
     }
 
+    private static void fixRestUrlPorts(IntegrationInfo info) {
+        // Find a base URL with port from platform-http endpoints
+        String baseWithPort = null;
+        for (HttpEndpointInfo ep : info.httpEndpoints) {
+            if (!ep.fromRest && ep.url != null) {
+                try {
+                    URI uri = URI.create(ep.url);
+                    if (uri.getPort() > 0) {
+                        baseWithPort = uri.getScheme() + "://" + uri.getHost() 
+ ":" + uri.getPort();
+                        break;
+                    }
+                } catch (Exception e) {
+                    // ignore
+                }
+            }
+        }
+        if (baseWithPort == null) {
+            return;
+        }
+        // Fix REST DSL endpoints missing port
+        for (HttpEndpointInfo ep : info.httpEndpoints) {
+            if (ep.fromRest && ep.url != null) {
+                String path = extractPath(ep.url);
+                if (path != null) {
+                    // Check if URL has no port by looking at the host portion
+                    int schemeEnd = ep.url.indexOf("://");
+                    if (schemeEnd > 0) {
+                        String hostPart = ep.url.substring(schemeEnd + 3);
+                        int slash = hostPart.indexOf('/');
+                        if (slash > 0) {
+                            hostPart = hostPart.substring(0, slash);
+                        }
+                        if (!hostPart.contains(":")) {
+                            ep.url = baseWithPort + path;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
     private static String extractPath(String url) {
         if (url == null) {
             return null;
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpTab.java
index 06389239b243..1e9fbd0f83e8 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpTab.java
@@ -16,11 +16,17 @@
  */
 package org.apache.camel.dsl.jbang.core.commands.tui;
 
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
 import java.nio.file.Path;
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -38,11 +44,14 @@ import dev.tamboui.tui.event.KeyEvent;
 import dev.tamboui.widgets.block.Block;
 import dev.tamboui.widgets.block.BorderType;
 import dev.tamboui.widgets.block.Title;
+import dev.tamboui.widgets.input.TextInput;
+import dev.tamboui.widgets.input.TextInputState;
 import dev.tamboui.widgets.paragraph.Paragraph;
 import dev.tamboui.widgets.table.Cell;
 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.CamelCommandHelper;
 import org.apache.camel.dsl.jbang.core.common.PathUtils;
 import org.apache.camel.util.json.JsonArray;
 import org.apache.camel.util.json.JsonObject;
@@ -55,6 +64,16 @@ class HttpTab implements MonitorTab {
     private static final Set<String> OPENAPI_HTTP_VERBS
             = Set.of("get", "post", "put", "delete", "patch", "options", 
"head", "trace");
 
+    // Probe field constants
+    private static final int PROBE_METHOD = 0;
+    private static final int PROBE_PATH = 1;
+    private static final int PROBE_HEADERS = 2;
+    private static final int PROBE_BODY = 3;
+    private static final int PROBE_HISTORY = 4;
+
+    private static final String[] PROBE_METHODS = { "GET", "POST", "PUT", 
"DELETE", "PATCH", "HEAD", "OPTIONS" };
+    private static final int MAX_PROBE_HISTORY = 20;
+
     private final MonitorContext ctx;
     private final TableState tableState = new TableState();
     private final AtomicBoolean specLoading = new AtomicBoolean(false);
@@ -70,12 +89,43 @@ class HttpTab implements MonitorTab {
     private String specTitle;
     private int specScroll;
 
+    // Probe mode state
+    private boolean probeMode;
+    private String probeRouteId;
+    private String probeBaseUrl;
+    private int probeField = PROBE_PATH;
+    private int probeMethodIndex;
+    private final TextInputState probePathState = new TextInputState("");
+    private final TextInputState probeBodyState = new TextInputState("");
+    private List<HeaderEntry> probeHeaders;
+    private int probeSelectedHeader;
+    private boolean probeEditingHeaderKey;
+    private final AtomicBoolean probeSending = new AtomicBoolean(false);
+    private String probeResponseStatus;
+    private long probeResponseElapsed;
+    private List<String> probeResponseLines;
+    private boolean probeResponseError;
+    private int probeResponseScroll;
+    private final List<ProbeHistoryEntry> probeHistory = new ArrayList<>();
+    private int probeHistoryIndex;
+    private boolean probePrettyPrint;
+    private String probeResponseRawBody;
+    private List<String> probeResponseHeaderLines;
+
     HttpTab(MonitorContext ctx) {
         this.ctx = ctx;
     }
 
+    boolean isProbeMode() {
+        return probeMode;
+    }
+
     @Override
     public boolean handleKeyEvent(KeyEvent ke) {
+        if (probeMode) {
+            return handleProbeKeyEvent(ke);
+        }
+
         if (showSpec) {
             if (ke.isChar('c') || ke.isCancel()) {
                 showSpec = false;
@@ -97,6 +147,10 @@ class HttpTab implements MonitorTab {
             return true;
         }
 
+        if (ke.isConfirm()) {
+            enterProbeModeFromTable();
+            return true;
+        }
         if (ke.isChar('s')) {
             sortIndex = (sortIndex + 1) % SORT_COLUMNS.length;
             sort = SORT_COLUMNS[sortIndex];
@@ -124,6 +178,10 @@ class HttpTab implements MonitorTab {
 
     @Override
     public boolean handleEscape() {
+        if (probeMode) {
+            probeMode = false;
+            return true;
+        }
         if (showSpec) {
             showSpec = false;
             return true;
@@ -133,11 +191,23 @@ class HttpTab implements MonitorTab {
 
     @Override
     public void navigateUp() {
+        if (probeMode) {
+            if (probeField == PROBE_HISTORY && !probeHistory.isEmpty()) {
+                probeHistoryIndex = Math.max(0, probeHistoryIndex - 1);
+            }
+            return;
+        }
         tableState.selectPrevious();
     }
 
     @Override
     public void navigateDown() {
+        if (probeMode) {
+            if (probeField == PROBE_HISTORY && !probeHistory.isEmpty()) {
+                probeHistoryIndex = Math.min(probeHistory.size() - 1, 
probeHistoryIndex + 1);
+            }
+            return;
+        }
         List<HttpEndpointInfo> visible = 
sortedVisibleEndpoints(ctx.findSelectedIntegration());
         tableState.selectNext(visible.size());
     }
@@ -148,6 +218,7 @@ class HttpTab implements MonitorTab {
         specLines = Collections.emptyList();
         specTitle = null;
         specScroll = 0;
+        probeMode = false;
     }
 
     @Override
@@ -158,6 +229,11 @@ class HttpTab implements MonitorTab {
             return;
         }
 
+        if (probeMode) {
+            renderProbe(frame, area);
+            return;
+        }
+
         if (showSpec) {
             renderSpec(frame, area);
             return;
@@ -176,6 +252,19 @@ class HttpTab implements MonitorTab {
 
     @Override
     public void renderFooter(List<Span> spans) {
+        if (probeMode) {
+            hint(spans, "Esc", "back");
+            hint(spans, "Tab", "fields");
+            hint(spans, "Enter", "send");
+            hint(spans, "+", "header");
+            hint(spans, "p", "pretty" + (probePrettyPrint ? " [on]" : ""));
+            if (!probeHistory.isEmpty()) {
+                hintLast(spans, "↑↓", "history");
+            } else {
+                hintLast(spans, "1-9", "tabs");
+            }
+            return;
+        }
         if (showSpec) {
             hint(spans, "c/Esc", "close");
             hint(spans, "↑↓", "scroll");
@@ -183,7 +272,7 @@ class HttpTab implements MonitorTab {
             return;
         }
         hint(spans, "Esc", "back");
-        hint(spans, "↑↓", "navigate");
+        hint(spans, "Enter", "probe");
         hint(spans, "s", "sort");
         String[] filterLabels = { "all", "rest", "http" };
         hint(spans, "f", "filter [" + filterLabels[filter] + "]");
@@ -197,6 +286,852 @@ class HttpTab implements MonitorTab {
         }
     }
 
+    void handlePaste(String text) {
+        if (!probeMode || probeSending.get() || text == null || 
text.isEmpty()) {
+            return;
+        }
+        TextInputState target = probeActiveTextInput();
+        if (target != null) {
+            for (int i = 0; i < text.length(); i++) {
+                char ch = text.charAt(i);
+                if (ch != '\n' && ch != '\r') {
+                    target.insert(ch);
+                }
+            }
+        }
+    }
+
+    // ---- Probe mode ----
+
+    private void enterProbeModeFromTable() {
+        List<HttpEndpointInfo> visible = 
sortedVisibleEndpoints(ctx.findSelectedIntegration());
+        Integer sel = tableState.selected();
+        if (sel == null || sel < 0 || sel >= visible.size()) {
+            return;
+        }
+        enterProbeMode(visible.get(sel));
+    }
+
+    private void enterProbeMode(HttpEndpointInfo ep) {
+        probeMode = true;
+        probeField = PROBE_PATH;
+        probeRouteId = ep.routeId;
+        probeBaseUrl = extractBaseUrl(ep.url);
+
+        // Pre-fill method
+        probeMethodIndex = 0;
+        if (ep.method != null) {
+            String m = 
ep.method.split(",")[0].trim().toUpperCase(Locale.ENGLISH);
+            for (int i = 0; i < PROBE_METHODS.length; i++) {
+                if (PROBE_METHODS[i].equals(m)) {
+                    probeMethodIndex = i;
+                    break;
+                }
+            }
+        }
+
+        // Pre-fill path
+        String path = ep.path != null ? ep.path : (ep.url != null ? ep.url : 
"/");
+        probePathState.clear();
+        for (int i = 0; i < path.length(); i++) {
+            probePathState.insert(path.charAt(i));
+        }
+
+        // Pre-fill headers from endpoint metadata
+        probeHeaders = null;
+        if (ep.consumes != null && !ep.consumes.isEmpty()) {
+            addProbeHeader("Content-Type", ep.consumes);
+        }
+        if (ep.produces != null && !ep.produces.isEmpty()) {
+            addProbeHeader("Accept", ep.produces);
+        }
+        probeSelectedHeader = 0;
+        probeEditingHeaderKey = true;
+
+        // Clear body and response
+        probeBodyState.clear();
+        probeResponseStatus = null;
+        probeResponseElapsed = 0;
+        probeResponseLines = null;
+        probeResponseHeaderLines = null;
+        probeResponseRawBody = null;
+        probeResponseError = false;
+        probeResponseScroll = 0;
+        probeHistoryIndex = 0;
+    }
+
+    private void addProbeHeader(String key, String value) {
+        if (probeHeaders == null) {
+            probeHeaders = new ArrayList<>();
+        }
+        TextInputState keyState = new TextInputState("");
+        TextInputState valState = new TextInputState("");
+        for (int i = 0; i < key.length(); i++) {
+            keyState.insert(key.charAt(i));
+        }
+        for (int i = 0; i < value.length(); i++) {
+            valState.insert(value.charAt(i));
+        }
+        probeHeaders.add(new HeaderEntry(keyState, valState));
+    }
+
+    private boolean handleProbeKeyEvent(KeyEvent ke) {
+        if (probeSending.get()) {
+            return true;
+        }
+        if (ke.isConfirm()) {
+            if (probeField == PROBE_HISTORY && !probeHistory.isEmpty()) {
+                replayHistoryEntry(probeHistory.get(probeHistoryIndex));
+            } else {
+                doProbeRequest();
+            }
+            return true;
+        }
+
+        // PgUp/PgDn scroll response
+        if (ke.isPageUp() || ke.isKey(KeyCode.PAGE_UP)) {
+            probeResponseScroll = Math.max(0, probeResponseScroll - 10);
+            return true;
+        }
+        if (ke.isPageDown() || ke.isKey(KeyCode.PAGE_DOWN)) {
+            probeResponseScroll += 10;
+            return true;
+        }
+
+        // Toggle pretty print for response body
+        if (ke.isChar('p') && probeField != PROBE_PATH && probeField != 
PROBE_BODY
+                && probeField != PROBE_HEADERS) {
+            probePrettyPrint = !probePrettyPrint;
+            if (probeResponseLines != null) {
+                reformatResponseBody();
+            }
+            return true;
+        }
+
+        // Field-specific handling
+        if (probeField == PROBE_METHOD) {
+            if (ke.isKey(KeyCode.TAB) || ke.isDown()) {
+                probeField = PROBE_PATH;
+                return true;
+            }
+            if (ke.isUp()) {
+                if (!probeHistory.isEmpty()) {
+                    probeField = PROBE_HISTORY;
+                } else {
+                    probeField = PROBE_BODY;
+                }
+                return true;
+            }
+            if (ke.isLeft()) {
+                probeMethodIndex = (probeMethodIndex - 1 + 
PROBE_METHODS.length) % PROBE_METHODS.length;
+                return true;
+            }
+            if (ke.isRight()) {
+                probeMethodIndex = (probeMethodIndex + 1) % 
PROBE_METHODS.length;
+                return true;
+            }
+            return true;
+        }
+        if (probeField == PROBE_PATH) {
+            if (ke.isKey(KeyCode.TAB) || ke.isDown()) {
+                if (hasProbeHeaders()) {
+                    probeField = PROBE_HEADERS;
+                    probeSelectedHeader = 0;
+                    probeEditingHeaderKey = true;
+                } else {
+                    probeField = PROBE_BODY;
+                }
+                return true;
+            }
+            if (ke.isUp()) {
+                probeField = PROBE_METHOD;
+                return true;
+            }
+            if (ke.isChar('+')) {
+                addProbeHeaderEmpty();
+                return true;
+            }
+            handleTextInput(ke, probePathState);
+            return true;
+        }
+        if (probeField == PROBE_HEADERS) {
+            return handleProbeHeaderKeyEvent(ke);
+        }
+        if (probeField == PROBE_BODY) {
+            if (ke.isKey(KeyCode.TAB) || ke.isDown()) {
+                if (!probeHistory.isEmpty()) {
+                    probeField = PROBE_HISTORY;
+                } else {
+                    probeField = PROBE_METHOD;
+                }
+                return true;
+            }
+            if (ke.isUp()) {
+                if (hasProbeHeaders()) {
+                    probeField = PROBE_HEADERS;
+                    probeSelectedHeader = probeHeaders.size() - 1;
+                    probeEditingHeaderKey = false;
+                } else {
+                    probeField = PROBE_PATH;
+                }
+                return true;
+            }
+            if (ke.isChar('+')) {
+                addProbeHeaderEmpty();
+                return true;
+            }
+            handleTextInput(ke, probeBodyState);
+            return true;
+        }
+        if (probeField == PROBE_HISTORY) {
+            if (ke.isKey(KeyCode.TAB)) {
+                probeField = PROBE_METHOD;
+                return true;
+            }
+            if (ke.isUp()) {
+                if (probeHistoryIndex > 0) {
+                    probeHistoryIndex--;
+                } else {
+                    probeField = PROBE_BODY;
+                }
+                return true;
+            }
+            if (ke.isDown()) {
+                if (probeHistoryIndex < probeHistory.size() - 1) {
+                    probeHistoryIndex++;
+                }
+                return true;
+            }
+            return true;
+        }
+        return true;
+    }
+
+    private boolean handleProbeHeaderKeyEvent(KeyEvent ke) {
+        HeaderEntry current = probeHeaders.get(probeSelectedHeader);
+        TextInputState activeInput = probeEditingHeaderKey ? current.keyInput 
: current.valueInput;
+
+        if (ke.isChar('+')) {
+            addProbeHeaderEmpty();
+            return true;
+        }
+        if (ke.isKey(KeyCode.TAB) || ke.isDown()) {
+            if (probeEditingHeaderKey) {
+                probeEditingHeaderKey = false;
+            } else if (probeSelectedHeader < probeHeaders.size() - 1) {
+                probeSelectedHeader++;
+                probeEditingHeaderKey = true;
+            } else {
+                probeField = PROBE_BODY;
+            }
+            return true;
+        }
+        if (ke.isUp()) {
+            if (probeEditingHeaderKey) {
+                if (probeSelectedHeader > 0) {
+                    probeSelectedHeader--;
+                    probeEditingHeaderKey = false;
+                } else {
+                    probeField = PROBE_PATH;
+                }
+            } else {
+                probeEditingHeaderKey = true;
+            }
+            return true;
+        }
+        if (ke.isDeleteBackward()) {
+            if (probeEditingHeaderKey && current.keyInput.text().isEmpty()) {
+                probeHeaders.remove(probeSelectedHeader);
+                if (probeHeaders.isEmpty()) {
+                    probeHeaders = null;
+                    probeField = PROBE_PATH;
+                } else if (probeSelectedHeader >= probeHeaders.size()) {
+                    probeSelectedHeader = probeHeaders.size() - 1;
+                }
+                return true;
+            }
+            activeInput.deleteBackward();
+            return true;
+        }
+        if (ke.isDeleteForward()) {
+            activeInput.deleteForward();
+            return true;
+        }
+        if (ke.isLeft()) {
+            if (!probeEditingHeaderKey && activeInput.cursorPosition() == 0) {
+                probeEditingHeaderKey = true;
+            } else {
+                activeInput.moveCursorLeft();
+            }
+            return true;
+        }
+        if (ke.isRight()) {
+            if (probeEditingHeaderKey && activeInput.cursorPosition() == 
activeInput.text().length()) {
+                probeEditingHeaderKey = false;
+            } else {
+                activeInput.moveCursorRight();
+            }
+            return true;
+        }
+        if (ke.isHome()) {
+            activeInput.moveCursorToStart();
+            return true;
+        }
+        if (ke.isEnd()) {
+            activeInput.moveCursorToEnd();
+            return true;
+        }
+        if (ke.code() == KeyCode.CHAR) {
+            activeInput.insert(ke.character());
+            return true;
+        }
+        return true;
+    }
+
+    private void addProbeHeaderEmpty() {
+        if (probeHeaders == null) {
+            probeHeaders = new ArrayList<>();
+        }
+        probeHeaders.add(new HeaderEntry(new TextInputState(""), new 
TextInputState("")));
+        probeField = PROBE_HEADERS;
+        probeSelectedHeader = probeHeaders.size() - 1;
+        probeEditingHeaderKey = true;
+    }
+
+    private boolean hasProbeHeaders() {
+        return probeHeaders != null && !probeHeaders.isEmpty();
+    }
+
+    private TextInputState probeActiveTextInput() {
+        if (probeField == PROBE_PATH) {
+            return probePathState;
+        }
+        if (probeField == PROBE_BODY) {
+            return probeBodyState;
+        }
+        if (probeField == PROBE_HEADERS && hasProbeHeaders()) {
+            HeaderEntry he = probeHeaders.get(probeSelectedHeader);
+            return probeEditingHeaderKey ? he.keyInput : he.valueInput;
+        }
+        return null;
+    }
+
+    private void replayHistoryEntry(ProbeHistoryEntry entry) {
+        // Fill fields from history
+        for (int i = 0; i < PROBE_METHODS.length; i++) {
+            if (PROBE_METHODS[i].equals(entry.method)) {
+                probeMethodIndex = i;
+                break;
+            }
+        }
+        probePathState.clear();
+        for (int i = 0; i < entry.path.length(); i++) {
+            probePathState.insert(entry.path.charAt(i));
+        }
+        probeBodyState.clear();
+        if (entry.body != null) {
+            for (int i = 0; i < entry.body.length(); i++) {
+                probeBodyState.insert(entry.body.charAt(i));
+            }
+        }
+        probeHeaders = null;
+        if (entry.headers != null) {
+            for (HeaderEntry he : entry.headers) {
+                addProbeHeader(he.keyInput.text(), he.valueInput.text());
+            }
+        }
+        probeField = PROBE_BODY;
+        doProbeRequest();
+    }
+
+    private void doProbeRequest() {
+        if (ctx.runner == null || probeBaseUrl == null) {
+            return;
+        }
+        if (!probeSending.compareAndSet(false, true)) {
+            return;
+        }
+
+        probeResponseStatus = "Sending...";
+        probeResponseElapsed = 0;
+        probeResponseLines = null;
+        probeResponseError = false;
+        probeResponseScroll = 0;
+
+        String method = PROBE_METHODS[probeMethodIndex];
+        String path = probePathState.text();
+        String body = probeBodyState.text();
+        String baseUrl = probeBaseUrl;
+
+        // Snapshot headers
+        List<HeaderEntry> headerSnapshot = null;
+        if (hasProbeHeaders()) {
+            headerSnapshot = new ArrayList<>();
+            for (HeaderEntry he : probeHeaders) {
+                headerSnapshot.add(new HeaderEntry(
+                        new TextInputState(he.keyInput.text()),
+                        new TextInputState(he.valueInput.text())));
+            }
+        }
+        List<HeaderEntry> hdrs = headerSnapshot;
+
+        ctx.runner.scheduler().execute(() -> {
+            try {
+                doProbeRequestInBackground(baseUrl, method, path, body, hdrs);
+            } finally {
+                probeSending.set(false);
+            }
+        });
+    }
+
+    private void doProbeRequestInBackground(
+            String baseUrl, String method, String path, String body, 
List<HeaderEntry> hdrs) {
+
+        String url = baseUrl + path;
+        String statusText;
+        long elapsed = 0;
+        boolean error = false;
+        List<String> headerLines = new ArrayList<>();
+        String rawBody = null;
+        int httpStatus = 0;
+
+        try {
+            HttpClient client = HttpClient.newBuilder()
+                    .connectTimeout(Duration.ofSeconds(10))
+                    .build();
+
+            boolean hasBody = body != null && !body.isEmpty();
+            HttpRequest.BodyPublisher bodyPublisher = hasBody
+                    ? HttpRequest.BodyPublishers.ofString(body)
+                    : HttpRequest.BodyPublishers.noBody();
+
+            HttpRequest.Builder reqBuilder = HttpRequest.newBuilder()
+                    .uri(URI.create(url))
+                    .timeout(Duration.ofSeconds(20))
+                    .method(method, bodyPublisher);
+
+            // Add user headers
+            if (hdrs != null) {
+                for (HeaderEntry he : hdrs) {
+                    String k = he.keyInput.text().trim();
+                    String v = he.valueInput.text();
+                    if (!k.isEmpty()) {
+                        reqBuilder.header(k, v);
+                    }
+                }
+            }
+
+            long start = System.currentTimeMillis();
+            HttpResponse<String> response = client.send(reqBuilder.build(),
+                    HttpResponse.BodyHandlers.ofString());
+            elapsed = System.currentTimeMillis() - start;
+
+            httpStatus = response.statusCode();
+            statusText = String.valueOf(httpStatus);
+
+            // Response headers
+            for (Map.Entry<String, List<String>> entry : 
response.headers().map().entrySet()) {
+                String k = entry.getKey();
+                if (k == null || k.startsWith(":")) {
+                    continue;
+                }
+                for (String v : entry.getValue()) {
+                    headerLines.add(k + ": " + v);
+                }
+            }
+
+            // Response body
+            String responseBody = response.body();
+            if (responseBody != null && !responseBody.isEmpty()) {
+                rawBody = responseBody;
+            }
+
+            if (httpStatus >= 400) {
+                error = true;
+            }
+        } catch (Exception e) {
+            statusText = "Error";
+            error = true;
+            String msg = e.getMessage();
+            headerLines.add(msg != null ? msg : e.getClass().getSimpleName());
+        }
+
+        // Build history entry
+        List<HeaderEntry> histHeaders = null;
+        if (hdrs != null && !hdrs.isEmpty()) {
+            histHeaders = new ArrayList<>(hdrs);
+        }
+        ProbeHistoryEntry histEntry = new ProbeHistoryEntry(
+                method, path, histHeaders, body,
+                httpStatus, elapsed, statusText, error);
+
+        // Apply results on render thread
+        String finalStatus = statusText;
+        long finalElapsed = elapsed;
+        boolean finalError = error;
+        List<String> finalHeaderLines = headerLines;
+        String finalRawBody = rawBody;
+
+        if (ctx.runner != null) {
+            ctx.runner.runOnRenderThread(() -> {
+                probeResponseStatus = finalStatus;
+                probeResponseElapsed = finalElapsed;
+                probeResponseError = finalError;
+                probeResponseHeaderLines = finalHeaderLines;
+                probeResponseRawBody = finalRawBody;
+                probeResponseScroll = 0;
+                rebuildResponseLines();
+
+                // Add to history (most recent first)
+                probeHistory.add(0, histEntry);
+                if (probeHistory.size() > MAX_PROBE_HISTORY) {
+                    probeHistory.remove(probeHistory.size() - 1);
+                }
+                probeHistoryIndex = 0;
+            });
+        }
+    }
+
+    private static String extractBaseUrl(String url) {
+        if (url == null) {
+            return "http://localhost:8080";;
+        }
+        // Extract scheme + host + port from full URL like 
http://0.0.0.0:8080/api/hello
+        try {
+            URI uri = URI.create(url);
+            String host = uri.getHost();
+            if ("0.0.0.0".equals(host)) {
+                host = "localhost";
+            }
+            int port = uri.getPort();
+            String scheme = uri.getScheme() != null ? uri.getScheme() : "http";
+            if (port > 0) {
+                return scheme + "://" + host + ":" + port;
+            }
+            return scheme + "://" + host;
+        } catch (Exception e) {
+            return "http://localhost:8080";;
+        }
+    }
+
+    private void rebuildResponseLines() {
+        List<String> lines = new ArrayList<>();
+        if (probeResponseHeaderLines != null) {
+            lines.addAll(probeResponseHeaderLines);
+        }
+        if (probeResponseRawBody != null && !probeResponseRawBody.isEmpty()) {
+            lines.add("");
+            String body = probeResponseRawBody;
+            if (probePrettyPrint) {
+                body = prettyFormat(body);
+            }
+            for (String line : body.split("\n", -1)) {
+                lines.add(line);
+            }
+        }
+        probeResponseLines = lines;
+    }
+
+    private void reformatResponseBody() {
+        rebuildResponseLines();
+        probeResponseScroll = 0;
+    }
+
+    private static String prettyFormat(String text) {
+        if (text == null || text.isEmpty()) {
+            return text;
+        }
+        return CamelCommandHelper.valueAsStringPretty(text, false);
+    }
+
+    // ---- Probe rendering ----
+
+    private void renderProbe(Frame frame, Rect area) {
+        int headerCount = hasProbeHeaders() ? probeHeaders.size() : 0;
+        int requestHeight = 7 + headerCount + (headerCount > 0 ? 1 : 0);
+        int historyHeight = Math.min(4 + 2, probeHistory.size() + 2);
+        if (historyHeight < 3) {
+            historyHeight = 3;
+        }
+
+        List<Rect> chunks = Layout.vertical()
+                .constraints(
+                        Constraint.length(requestHeight),
+                        Constraint.fill(),
+                        Constraint.length(historyHeight))
+                .split(area);
+
+        renderProbeRequest(frame, chunks.get(0));
+        renderProbeResponse(frame, chunks.get(1));
+        renderProbeHistory(frame, chunks.get(2));
+    }
+
+    private void renderProbeRequest(Frame frame, Rect area) {
+        String method = PROBE_METHODS[probeMethodIndex];
+        Title title = Title.from(Line.from(
+                Span.styled(" HTTP Probe ", Style.EMPTY.bold()),
+                Span.styled(method, methodStyle(method).bold()),
+                Span.raw(" " + probePathState.text() + " ")));
+
+        Block block = 
Block.builder().borderType(BorderType.ROUNDED).title(title).build();
+        frame.renderWidget(block, area);
+
+        int innerX = area.left() + 2;
+        int innerW = area.width() - 4;
+        int labelW = 10;
+        int fieldW = innerW - labelW;
+        int row = area.top() + 1;
+
+        // Method selector
+        renderProbeLabel(frame, innerX, row, labelW, "Method:", probeField == 
PROBE_METHOD);
+        Rect methodArea = new Rect(innerX + labelW, row, fieldW, 1);
+        Style methodSt = methodStyle(method);
+        String leftArr = probeField == PROBE_METHOD ? "◀ " : "  ";
+        String rightArr = probeField == PROBE_METHOD ? " ▶" : "";
+        frame.renderWidget(Paragraph.from(Line.from(
+                Span.styled(leftArr, methodSt),
+                Span.styled(method, methodSt.bold()),
+                Span.styled(rightArr, methodSt))), methodArea);
+
+        // Full URL (read-only)
+        row++;
+        renderProbeLabel(frame, innerX, row, labelW, "URL:", false);
+        String fullUrl = (probeBaseUrl != null ? probeBaseUrl : "") + 
probePathState.text();
+        Rect urlArea = new Rect(innerX + labelW, row, fieldW, 1);
+        frame.renderWidget(Paragraph.from(Line.from(
+                Span.styled(fullUrl, Style.EMPTY.dim()))), urlArea);
+
+        // Path input
+        row++;
+        renderProbeLabel(frame, innerX, row, labelW, "Path:", probeField == 
PROBE_PATH);
+        Rect pathArea = new Rect(innerX + labelW, row, fieldW, 1);
+        if (probeField == PROBE_PATH && !probeSending.get()) {
+            TextInput textInput = 
TextInput.builder().cursorStyle(Style.EMPTY.reversed()).build();
+            frame.renderStatefulWidget(textInput, pathArea, probePathState);
+        } else {
+            String pathText = probePathState.text();
+            frame.renderWidget(Paragraph.from(Line.from(
+                    Span.styled(pathText.isEmpty() ? "/" : pathText,
+                            pathText.isEmpty() ? Style.EMPTY.dim() : 
Style.EMPTY))),
+                    pathArea);
+        }
+
+        // Headers
+        if (hasProbeHeaders()) {
+            int keyW = Math.min(20, fieldW / 3);
+            int valW = fieldW - keyW - 3;
+            for (int i = 0; i < probeHeaders.size(); i++) {
+                row++;
+                boolean isSelected = probeField == PROBE_HEADERS && 
probeSelectedHeader == i;
+                String label = i == 0 ? "Headers:" : "";
+                renderProbeLabel(frame, innerX, row, labelW, label,
+                        isSelected || (i == 0 && probeField == PROBE_HEADERS));
+
+                HeaderEntry he = probeHeaders.get(i);
+                int fieldX = innerX + labelW;
+
+                Rect keyArea = new Rect(fieldX, row, keyW, 1);
+                if (isSelected && probeEditingHeaderKey && 
!probeSending.get()) {
+                    TextInput keyInput = 
TextInput.builder().cursorStyle(Style.EMPTY.reversed()).build();
+                    frame.renderStatefulWidget(keyInput, keyArea, he.keyInput);
+                } else {
+                    String keyText = he.keyInput.text();
+                    Style keyStyle = keyText.isEmpty() ? Style.EMPTY.dim()
+                            : isSelected ? Style.EMPTY.bold() : Style.EMPTY;
+                    frame.renderWidget(Paragraph.from(Line.from(
+                            Span.styled(keyText.isEmpty() ? "<key>" : keyText, 
keyStyle))), keyArea);
+                }
+
+                Rect sepArea = new Rect(fieldX + keyW, row, 3, 1);
+                frame.renderWidget(Paragraph.from(Line.from(
+                        Span.styled(" : ", Style.EMPTY.dim()))), sepArea);
+
+                Rect valArea = new Rect(fieldX + keyW + 3, row, valW, 1);
+                if (isSelected && !probeEditingHeaderKey && 
!probeSending.get()) {
+                    TextInput valInput = 
TextInput.builder().cursorStyle(Style.EMPTY.reversed()).build();
+                    frame.renderStatefulWidget(valInput, valArea, 
he.valueInput);
+                } else {
+                    String valText = he.valueInput.text();
+                    Style valStyle = valText.isEmpty() ? Style.EMPTY.dim()
+                            : isSelected ? Style.EMPTY.bold() : Style.EMPTY;
+                    frame.renderWidget(Paragraph.from(Line.from(
+                            Span.styled(valText.isEmpty() ? "<value>" : 
valText, valStyle))), valArea);
+                }
+            }
+        }
+
+        // Body input
+        row++;
+        renderProbeLabel(frame, innerX, row, labelW, "Body:", probeField == 
PROBE_BODY);
+        Rect bodyArea = new Rect(innerX + labelW, row, fieldW, 1);
+        if (probeField == PROBE_BODY && !probeSending.get()) {
+            TextInput textInput = 
TextInput.builder().cursorStyle(Style.EMPTY.reversed()).build();
+            frame.renderStatefulWidget(textInput, bodyArea, probeBodyState);
+        } else {
+            String bodyText = probeBodyState.text();
+            frame.renderWidget(Paragraph.from(Line.from(
+                    Span.styled(bodyText.isEmpty() ? "—" : bodyText,
+                            bodyText.isEmpty() ? Style.EMPTY.dim() : 
Style.EMPTY))),
+                    bodyArea);
+        }
+    }
+
+    private void renderProbeResponse(Frame frame, Rect area) {
+        String titleStr;
+        Style titleStyle = Style.EMPTY.bold();
+        if (probeResponseStatus == null) {
+            titleStr = " Response ";
+        } else if (probeResponseError) {
+            titleStr = " Response " + probeResponseStatus + " ";
+            titleStyle = Style.EMPTY.fg(Color.LIGHT_RED).bold();
+        } else if ("Sending...".equals(probeResponseStatus)) {
+            titleStr = " Sending... ";
+            titleStyle = Style.EMPTY.fg(Color.YELLOW).bold();
+        } else {
+            titleStr = " Response " + probeResponseStatus;
+            if (probeResponseElapsed > 0) {
+                titleStr += " (" + probeResponseElapsed + "ms)";
+            }
+            titleStr += " ";
+            titleStyle = statusStyle(probeResponseStatus);
+        }
+
+        Title title = Title.from(Line.from(Span.styled(titleStr, titleStyle)));
+
+        if (probeResponseLines == null || probeResponseLines.isEmpty()) {
+            String placeholder = probeResponseStatus == null
+                    ? " Press Enter to send request"
+                    : " No response content";
+            frame.renderWidget(
+                    Paragraph.builder()
+                            .text(Text.from(Line.from(Span.styled(placeholder, 
Style.EMPTY.dim()))))
+                            
.block(Block.builder().borderType(BorderType.ROUNDED).title(title).build())
+                            .build(),
+                    area);
+            return;
+        }
+
+        int visibleLines = area.height() - 2;
+        if (visibleLines < 1) {
+            visibleLines = 1;
+        }
+        int maxScroll = Math.max(0, probeResponseLines.size() - visibleLines);
+        probeResponseScroll = Math.min(probeResponseScroll, maxScroll);
+
+        int end = Math.min(probeResponseScroll + visibleLines, 
probeResponseLines.size());
+        List<Line> lines = new ArrayList<>();
+        for (int i = probeResponseScroll; i < end; i++) {
+            String line = probeResponseLines.get(i);
+            if (line.isEmpty()) {
+                lines.add(Line.from(Span.raw("")));
+            } else if (line.contains(": ") && !line.startsWith(" ") && 
!line.startsWith("{")
+                    && !line.startsWith("[") && !line.startsWith("\"")) {
+                // Header line
+                int colon = line.indexOf(": ");
+                lines.add(Line.from(
+                        Span.styled(line.substring(0, colon + 1), 
Style.EMPTY.fg(Color.YELLOW)),
+                        Span.raw(line.substring(colon + 1))));
+            } else {
+                lines.add(Line.from(Span.raw(line)));
+            }
+        }
+
+        frame.renderWidget(
+                Paragraph.builder()
+                        .text(Text.from(lines))
+                        
.block(Block.builder().borderType(BorderType.ROUNDED).title(title).build())
+                        .build(),
+                area);
+    }
+
+    private void renderProbeHistory(Frame frame, Rect area) {
+        String title = " History [" + probeHistory.size() + "] ";
+
+        if (probeHistory.isEmpty()) {
+            frame.renderWidget(
+                    Paragraph.builder()
+                            .text(Text.from(Line.from(
+                                    Span.styled(" No requests yet", 
Style.EMPTY.dim()))))
+                            
.block(Block.builder().borderType(BorderType.ROUNDED).title(title).build())
+                            .build(),
+                    area);
+            return;
+        }
+
+        int visibleLines = area.height() - 2;
+        if (visibleLines < 1) {
+            visibleLines = 1;
+        }
+
+        // Ensure selected index is visible
+        int start = 0;
+        if (probeHistoryIndex >= visibleLines) {
+            start = probeHistoryIndex - visibleLines + 1;
+        }
+        int end = Math.min(start + visibleLines, probeHistory.size());
+
+        List<Line> lines = new ArrayList<>();
+        for (int i = start; i < end; i++) {
+            ProbeHistoryEntry entry = probeHistory.get(i);
+            boolean selected = probeField == PROBE_HISTORY && i == 
probeHistoryIndex;
+            String pointer = selected ? "► " : "  ";
+            String methodStr = String.format("%-8s", entry.method);
+            String statusStr = entry.error ? "ERR" : entry.statusText;
+            String elapsedStr = entry.elapsed > 0 ? entry.elapsed + "ms" : "";
+            String bodySnippet = entry.body != null && !entry.body.isEmpty()
+                    ? " " + truncate(entry.body, 30)
+                    : "";
+
+            Style lineStyle = selected ? Style.EMPTY.bold() : Style.EMPTY;
+            lines.add(Line.from(
+                    Span.styled(pointer, lineStyle),
+                    Span.styled(methodStr, methodStyle(entry.method)),
+                    Span.styled(entry.path + "  ", lineStyle),
+                    Span.styled(statusStr, selected ? statusStyle(statusStr) : 
statusStyle(statusStr).dim()),
+                    Span.styled("  " + elapsedStr, Style.EMPTY.dim()),
+                    Span.styled(bodySnippet, Style.EMPTY.dim())));
+        }
+
+        frame.renderWidget(
+                Paragraph.builder()
+                        .text(Text.from(lines))
+                        
.block(Block.builder().borderType(BorderType.ROUNDED).title(title).build())
+                        .build(),
+                area);
+    }
+
+    private void renderProbeLabel(Frame frame, int x, int y, int w, String 
label, boolean selected) {
+        Style style = selected ? Style.EMPTY.bold() : Style.EMPTY.dim();
+        Rect labelArea = new Rect(x, y, w, 1);
+        frame.renderWidget(Paragraph.from(Line.from(Span.styled(label, 
style))), labelArea);
+    }
+
+    private static Style statusStyle(String status) {
+        if (status == null) {
+            return Style.EMPTY.bold();
+        }
+        try {
+            int code = Integer.parseInt(status);
+            if (code >= 200 && code < 300) {
+                return Style.EMPTY.fg(Color.GREEN).bold();
+            }
+            if (code >= 300 && code < 400) {
+                return Style.EMPTY.fg(Color.CYAN).bold();
+            }
+            if (code >= 400 && code < 500) {
+                return Style.EMPTY.fg(Color.YELLOW).bold();
+            }
+            if (code >= 500) {
+                return Style.EMPTY.fg(Color.LIGHT_RED).bold();
+            }
+        } catch (NumberFormatException e) {
+            // not a number — treat as error
+        }
+        return Style.EMPTY.fg(Color.LIGHT_RED).bold();
+    }
+
+    // ---- Existing table/spec methods ----
+
     List<HttpEndpointInfo> sortedVisibleEndpoints(IntegrationInfo info) {
         List<HttpEndpointInfo> visible = visibleEndpoints(info);
         visible.sort((a, b) -> {
@@ -263,7 +1198,7 @@ class HttpTab implements MonitorTab {
         frame.renderWidget(Paragraph.from(Line.from(spans)), area);
     }
 
-    private static Style methodStyle(String method) {
+    static Style methodStyle(String method) {
         if (method == null) {
             return Style.EMPTY;
         }
@@ -466,7 +1401,7 @@ class HttpTab implements MonitorTab {
         root.put("filter", specUri);
 
         Path actionFile = ctx.getActionFile(pid);
-        
org.apache.camel.dsl.jbang.core.common.PathUtils.writeTextSafely(root.toJson(), 
actionFile);
+        PathUtils.writeTextSafely(root.toJson(), actionFile);
 
         JsonObject jo = pollJsonResponse(outputFile, 5000);
         PathUtils.deleteFile(outputFile);
@@ -594,6 +1529,31 @@ class HttpTab implements MonitorTab {
         return MonitorContext.sortStyle(column, sort);
     }
 
+    private static void handleTextInput(KeyEvent ke, TextInputState state) {
+        if (ke.isDeleteBackward()) {
+            state.deleteBackward();
+        } else if (ke.isDeleteForward()) {
+            state.deleteForward();
+        } else if (ke.isLeft()) {
+            state.moveCursorLeft();
+        } else if (ke.isRight()) {
+            state.moveCursorRight();
+        } else if (ke.isHome()) {
+            state.moveCursorToStart();
+        } else if (ke.isEnd()) {
+            state.moveCursorToEnd();
+        } else if (ke.code() == KeyCode.CHAR) {
+            state.insert(ke.character());
+        }
+    }
+
+    private static String truncate(String s, int max) {
+        if (s == null) {
+            return "";
+        }
+        return s.length() <= max ? s : s.substring(0, max - 1) + "…";
+    }
+
     @Override
     public SelectionContext getSelectionContext() {
         IntegrationInfo info = ctx.findSelectedIntegration();
@@ -607,4 +1567,14 @@ class HttpTab implements MonitorTab {
         Integer sel = tableState.selected();
         return new SelectionContext("table", items, sel != null ? sel : -1, 
items.size(), "HTTP");
     }
+
+    record HeaderEntry(TextInputState keyInput, TextInputState valueInput) {
+    }
+
+    record ProbeHistoryEntry(
+            String method, String path,
+            List<HeaderEntry> headers, String body,
+            int statusCode, long elapsed,
+            String statusText, boolean error) {
+    }
 }

Reply via email to