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 230f6646be34 camel-jbang-tui: HTTP probe lightweight Postman
230f6646be34 is described below

commit 230f6646be34f1c66e4781a9d08d9e07c83fbead
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Jul 28 15:44:17 2026 +0200

    camel-jbang-tui: HTTP probe lightweight Postman
    
    Upgrade the HTTP tab probe into a lightweight Postman for testing
    REST services directly from the terminal:
    
    - F5 to send (Enter inserts newlines in body)
    - Multi-line body editing with TextArea
    - URL {xxx} placeholder support with fill-in fields
    - Query parameters section with URL encoding
    - Content-Type / Accept quick-pick cyclers
    - Full resolved URL displayed as hyperlink
    - Focus-highlighted panel borders
    - MCP tui_get_table returns probe state when active
    - Documentation section in TUI docs
    
    camel-jbang-tui: HTTP probe polish
    
    - Remove padding so probe uses full tab area
    - Focus-highlighted borders on request and history panels
    - Request/Response tab toggle (F4) in right panel
    - Live request preview showing raw HTTP call before sending
    - Scroll indicator on response (percentage and line count)
    - History shows resolved paths instead of templates
    - History replay restores all fields including path param values
    - History stores only manual headers (no Content-Type/Accept duplication)
    - Enter on history restores fields without auto-sending
    - Fix + key: types character in body, adds param/header from other fields
    - MCP tui_get_table returns probe state when active
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../modules/ROOT/pages/camel-jbang-tui.adoc        |   55 +
 .../camel/dsl/jbang/core/commands/tui/HttpTab.java | 1358 ++++++++++++++++----
 .../jbang/core/commands/tui/TuiToolRegistry.java   |    4 +-
 3 files changed, 1160 insertions(+), 257 deletions(-)

diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui.adoc
index c487a80122b5..076d2375ad64 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui.adoc
@@ -344,6 +344,61 @@ correlating Camel processing with external service calls 
(HTTP, database, messag
 The Process tab (under More, shortcut *p*) shows JVM process details for the 
selected
 integration: Java version, PID, uptime, command-line arguments, and system 
properties.
 
+== HTTP Probe
+
+The HTTP tab (under More > Routing > HTTP) lists all HTTP/REST endpoints in 
your integration.
+Press *Enter* on any endpoint to open the built-in HTTP probe -- a lightweight 
Postman for
+testing your REST services directly from the terminal.
+
+=== Probe Layout
+
+The probe screen is split into a request panel (left) and response panel 
(right):
+
+*Request panel* -- top to bottom:
+
+* *Method* -- HTTP verb cycler (`GET`, `POST`, `PUT`, `DELETE`, `PATCH`, 
`HEAD`, `OPTIONS`).
+  Use *Left* / *Right* arrows to cycle.
+* *Path* -- editable path field, pre-filled from the endpoint. Supports 
`\{xxx\}` placeholders.
+* *Path Parameters* -- if the path contains `\{id\}`, `\{name\}`, etc., a 
fill-in field appears
+  for each placeholder. Values are substituted into the URL when sending.
+* *Query Parameters* -- key-value pairs appended to the URL as 
`?key=value&key2=value2`.
+  Press *+* to add a parameter.
+* *Content-Type* -- cycler with common MIME types (`application/json`, 
`application/xml`,
+  `text/plain`, etc.). Pre-filled from endpoint metadata when available.
+* *Accept* -- cycler for the Accept header. Pre-filled from endpoint metadata.
+* *Headers* -- custom request headers as key-value pairs. Press *+* to add a 
header.
+* *Body* -- multi-line text area (6 rows) for request body. Supports JSON, 
XML, plain text,
+  or `file:payload.json` to load from a file. Press *Enter* to insert newlines.
+* *History* -- previously sent requests with status codes and elapsed times. 
Press *Enter*
+  to replay a request, restoring all fields.
+
+*Response panel* -- shows the response body with status code, elapsed time, 
and response headers.
+Press *p* to toggle pretty-printing for JSON and XML responses.
+
+The resolved URL is shown at the top of the request panel as a clickable 
hyperlink that you
+can copy for use with `curl` or other tools.
+
+=== Sending Requests
+
+Press *F5* to send the request (same pattern as the SQL tab). The response 
appears in the
+right panel. While sending, the status shows a spinner.
+
+=== Keyboard Shortcuts (Probe)
+
+[cols="2,5",options="header"]
+|===
+| Key | Action
+
+| F5 | Send request
+| Enter | Insert newline (body) or advance to next field
+| Tab / Shift+Tab | Next / previous field
+| Left / Right | Cycle method, Content-Type, or Accept values
+| + | Add query parameter or header
+| Backspace | Remove empty query parameter or header
+| p | Toggle pretty-print response
+| Esc | Exit probe mode
+|===
+
 == Actions Menu (F2)
 
 Press *F2* to open the actions menu with quick access to common operations:
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 257400ba3997..fe0f50517c21 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
@@ -18,6 +18,8 @@ package org.apache.camel.dsl.jbang.core.commands.tui;
 
 import java.io.IOException;
 import java.net.URI;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
@@ -26,6 +28,8 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import dev.tamboui.layout.Constraint;
 import dev.tamboui.layout.Layout;
@@ -44,6 +48,8 @@ import dev.tamboui.widgets.block.Block;
 import dev.tamboui.widgets.block.BorderType;
 import dev.tamboui.widgets.block.Borders;
 import dev.tamboui.widgets.block.Title;
+import dev.tamboui.widgets.input.TextArea;
+import dev.tamboui.widgets.input.TextAreaState;
 import dev.tamboui.widgets.input.TextInput;
 import dev.tamboui.widgets.input.TextInputState;
 import dev.tamboui.widgets.paragraph.Paragraph;
@@ -61,15 +67,27 @@ class HttpTab extends AbstractTableTab {
     private static final int MOUSE_SCROLL_LINES = 3;
     private static final Set<String> OPENAPI_HTTP_VERBS
             = Set.of("get", "post", "put", "delete", "patch", "options", 
"head", "trace");
+    private static final Pattern PATH_PARAM_PATTERN = 
Pattern.compile("\\{([^}]+)}");
 
     // 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 int PROBE_PATH_PARAMS = 2;
+    private static final int PROBE_QUERY_PARAMS = 3;
+    private static final int PROBE_CONTENT_TYPE = 4;
+    private static final int PROBE_ACCEPT = 5;
+    private static final int PROBE_HEADERS = 6;
+    private static final int PROBE_BODY = 7;
+    private static final int PROBE_HISTORY = 8;
 
     private static final String[] PROBE_METHODS = { "GET", "POST", "PUT", 
"DELETE", "PATCH", "HEAD", "OPTIONS" };
+    private static final String[] CONTENT_TYPES = {
+            "", "application/json", "application/xml", "text/plain",
+            "text/xml", "application/x-www-form-urlencoded", 
"multipart/form-data"
+    };
+    private static final String[] ACCEPT_TYPES = {
+            "", "application/json", "application/xml", "text/plain", "*/*"
+    };
     private static final int MAX_PROBE_HISTORY = 20;
 
     private final AtomicBoolean specLoading = new AtomicBoolean(false);
@@ -89,16 +107,24 @@ class HttpTab extends AbstractTableTab {
     private int probeField = PROBE_PATH;
     private int probeMethodIndex;
     private final TextInputState probePathState = new TextInputState("");
-    private final TextInputState probeBodyState = new TextInputState("");
+    private final TextAreaState probeBodyState = new TextAreaState("");
     private List<FormHelper.HeaderEntry> probeHeaders;
     private int probeSelectedHeader;
     private boolean probeEditingHeaderKey;
+    private List<PathParam> probePathParams;
+    private int probeSelectedPathParam;
+    private List<FormHelper.HeaderEntry> probeQueryParams;
+    private int probeSelectedQueryParam;
+    private boolean probeEditingQueryKey;
+    private int probeContentTypeIndex;
+    private int probeAcceptIndex;
     private final AtomicBoolean probeSending = new AtomicBoolean(false);
     private String probeResponseStatus;
     private long probeResponseElapsed;
     private List<String> probeResponseLines;
     private boolean probeResponseError;
     private int probeResponseScroll;
+    private boolean probeViewRequest;
     private final List<ProbeHistoryEntry> probeHistory = new ArrayList<>();
     private int probeHistoryIndex;
     private boolean probePrettyPrint;
@@ -225,9 +251,7 @@ class HttpTab extends AbstractTableTab {
     @Override
     public void navigateUp() {
         if (probeMode) {
-            if (probeField == PROBE_HISTORY && !probeHistory.isEmpty()) {
-                probeHistoryIndex = Math.max(0, probeHistoryIndex - 1);
-            }
+            // Arrow up is handled by handleProbeKeyEvent for all fields
             return;
         }
         tableState.selectPrevious();
@@ -236,9 +260,7 @@ class HttpTab extends AbstractTableTab {
     @Override
     public void navigateDown() {
         if (probeMode) {
-            if (probeField == PROBE_HISTORY && !probeHistory.isEmpty()) {
-                probeHistoryIndex = Math.min(probeHistory.size() - 1, 
probeHistoryIndex + 1);
-            }
+            // Arrow down is handled by handleProbeKeyEvent for all fields
             return;
         }
         List<HttpEndpointInfo> visible = 
sortedVisibleEndpoints(ctx.findSelectedIntegration());
@@ -283,8 +305,9 @@ class HttpTab extends AbstractTableTab {
         if (probeMode) {
             hint(spans, "Esc", "back");
             hint(spans, "Tab", "fields");
-            hint(spans, "Enter", "send");
-            hint(spans, "+", "header");
+            hint(spans, "F5", "send");
+            hint(spans, "F4", probeViewRequest ? "response" : "request");
+            hint(spans, "+", "param/header");
             hint(spans, "p", "pretty" + (probePrettyPrint ? " [on]" : ""));
             if (!probeHistory.isEmpty()) {
                 hintLast(spans, TuiIcons.HINT_SCROLL, "history");
@@ -314,7 +337,14 @@ class HttpTab extends AbstractTableTab {
         if (!probeMode || probeSending.get()) {
             return;
         }
-        FormHelper.handlePaste(text, probeActiveTextInput());
+        if (probeField == PROBE_BODY) {
+            probeBodyState.insert(text);
+        } else {
+            TextInputState target = probeActiveTextInput();
+            if (target != null) {
+                FormHelper.handlePaste(text, target);
+            }
+        }
     }
 
     // ---- Probe mode ----
@@ -353,17 +383,24 @@ class HttpTab extends AbstractTableTab {
             probePathState.insert(path.charAt(i));
         }
 
-        // Pre-fill headers from endpoint metadata
+        // Scan for {xxx} placeholders in path
+        probePathParams = scanPathParams(path);
+        probeSelectedPathParam = 0;
+
+        // Pre-fill Content-Type and Accept from endpoint metadata
+        probeContentTypeIndex = findPresetIndex(CONTENT_TYPES, ep.consumes);
+        probeAcceptIndex = findPresetIndex(ACCEPT_TYPES, ep.produces);
+
+        // Clear manual headers (Content-Type/Accept are handled by cyclers)
         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 query params
+        probeQueryParams = null;
+        probeSelectedQueryParam = 0;
+        probeEditingQueryKey = true;
+
         // Clear body and response
         probeBodyState.clear();
         probeResponseStatus = null;
@@ -395,11 +432,28 @@ class HttpTab extends AbstractTableTab {
         if (probeSending.get()) {
             return true;
         }
+
+        // F5 to send request
+        if (ke.code() == KeyCode.F5) {
+            doProbeRequest();
+            probeViewRequest = false;
+            return true;
+        }
+
+        // F4 to toggle request/response view
+        if (ke.code() == KeyCode.F4) {
+            probeViewRequest = !probeViewRequest;
+            return true;
+        }
+
+        // Enter: newline in body, replay in history, advance field otherwise
         if (ke.isConfirm()) {
-            if (probeField == PROBE_HISTORY && !probeHistory.isEmpty()) {
+            if (probeField == PROBE_BODY) {
+                probeBodyState.insert('\n');
+            } else if (probeField == PROBE_HISTORY && !probeHistory.isEmpty()) 
{
                 replayHistoryEntry(probeHistory.get(probeHistoryIndex));
             } else {
-                doProbeRequest();
+                advanceProbeField();
             }
             return true;
         }
@@ -416,7 +470,8 @@ class HttpTab extends AbstractTableTab {
 
         // Toggle pretty print for response body
         if (ke.isChar('p') && probeField != PROBE_PATH && probeField != 
PROBE_BODY
-                && probeField != PROBE_HEADERS) {
+                && probeField != PROBE_HEADERS && probeField != 
PROBE_PATH_PARAMS
+                && probeField != PROBE_QUERY_PARAMS) {
             probePrettyPrint = !probePrettyPrint;
             if (probeResponseLines != null) {
                 reformatResponseBody();
@@ -426,100 +481,296 @@ class HttpTab extends AbstractTableTab {
 
         // Field-specific handling
         if (probeField == PROBE_METHOD) {
-            if (ke.isKey(KeyCode.TAB) || ke.isDown()) {
+            return handleProbeMethodKeyEvent(ke);
+        }
+        if (probeField == PROBE_PATH) {
+            return handleProbePathKeyEvent(ke);
+        }
+        if (probeField == PROBE_PATH_PARAMS) {
+            return handleProbePathParamKeyEvent(ke);
+        }
+        if (probeField == PROBE_QUERY_PARAMS) {
+            return handleProbeQueryParamKeyEvent(ke);
+        }
+        if (probeField == PROBE_CONTENT_TYPE) {
+            return handleProbeCyclerKeyEvent(ke, CONTENT_TYPES, 
PROBE_CONTENT_TYPE);
+        }
+        if (probeField == PROBE_ACCEPT) {
+            return handleProbeCyclerKeyEvent(ke, ACCEPT_TYPES, PROBE_ACCEPT);
+        }
+        if (probeField == PROBE_HEADERS) {
+            return handleProbeHeaderKeyEvent(ke);
+        }
+        if (probeField == PROBE_BODY) {
+            return handleProbeBodyKeyEvent(ke);
+        }
+        if (probeField == PROBE_HISTORY) {
+            return handleProbeHistoryKeyEvent(ke);
+        }
+        return true;
+    }
+
+    private boolean handleProbeMethodKeyEvent(KeyEvent ke) {
+        if (ke.isKey(KeyCode.TAB) || ke.isDown()) {
+            probeField = PROBE_PATH;
+            return true;
+        }
+        if (ke.isUp()) {
+            goToLastProbeField();
+            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;
+        }
+        if (ke.isChar('+')) {
+            addProbeQueryParamEmpty();
+            return true;
+        }
+        return true;
+    }
+
+    private boolean handleProbePathKeyEvent(KeyEvent ke) {
+        if (ke.isKey(KeyCode.TAB) || ke.isDown()) {
+            goToNextProbeFieldFrom(PROBE_PATH);
+            return true;
+        }
+        if (ke.isUp()) {
+            probeField = PROBE_METHOD;
+            return true;
+        }
+        if (ke.isChar('+')) {
+            addProbeQueryParamEmpty();
+            return true;
+        }
+        FormHelper.handleTextInput(ke, probePathState);
+        rescanPathParams();
+        return true;
+    }
+
+    private boolean handleProbePathParamKeyEvent(KeyEvent ke) {
+        if (probePathParams == null || probePathParams.isEmpty()) {
+            goToNextProbeFieldFrom(PROBE_PATH_PARAMS);
+            return true;
+        }
+        PathParam current = probePathParams.get(probeSelectedPathParam);
+
+        if (ke.isKey(KeyCode.TAB) || ke.isDown()) {
+            if (probeSelectedPathParam < probePathParams.size() - 1) {
+                probeSelectedPathParam++;
+            } else {
+                goToNextProbeFieldFrom(PROBE_PATH_PARAMS);
+            }
+            return true;
+        }
+        if (ke.isUp()) {
+            if (probeSelectedPathParam > 0) {
+                probeSelectedPathParam--;
+            } else {
                 probeField = PROBE_PATH;
-                return true;
             }
-            if (ke.isUp()) {
-                if (!probeHistory.isEmpty()) {
-                    probeField = PROBE_HISTORY;
+            return true;
+        }
+        FormHelper.handleTextInput(ke, current.input);
+        return true;
+    }
+
+    private boolean handleProbeQueryParamKeyEvent(KeyEvent ke) {
+        if (probeQueryParams == null || probeQueryParams.isEmpty()) {
+            goToNextProbeFieldFrom(PROBE_QUERY_PARAMS);
+            return true;
+        }
+        FormHelper.HeaderEntry current = 
probeQueryParams.get(probeSelectedQueryParam);
+        TextInputState activeInput = probeEditingQueryKey ? current.keyInput() 
: current.valueInput();
+
+        if (ke.isChar('+')) {
+            addProbeQueryParamEmpty();
+            return true;
+        }
+        if (ke.isKey(KeyCode.TAB) || ke.isDown()) {
+            if (probeEditingQueryKey) {
+                probeEditingQueryKey = false;
+            } else if (probeSelectedQueryParam < probeQueryParams.size() - 1) {
+                probeSelectedQueryParam++;
+                probeEditingQueryKey = true;
+            } else {
+                probeField = PROBE_CONTENT_TYPE;
+            }
+            return true;
+        }
+        if (ke.isUp()) {
+            if (probeEditingQueryKey) {
+                if (probeSelectedQueryParam > 0) {
+                    probeSelectedQueryParam--;
+                    probeEditingQueryKey = false;
                 } else {
-                    probeField = PROBE_BODY;
+                    goToPrevProbeFieldFrom(PROBE_QUERY_PARAMS);
                 }
-                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;
+            } else {
+                probeEditingQueryKey = 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;
+        if (ke.isDeleteBackward()) {
+            if (probeEditingQueryKey && current.keyInput().text().isEmpty()) {
+                probeQueryParams.remove(probeSelectedQueryParam);
+                if (probeQueryParams.isEmpty()) {
+                    probeQueryParams = null;
+                    goToPrevProbeFieldFrom(PROBE_QUERY_PARAMS);
+                } else if (probeSelectedQueryParam >= probeQueryParams.size()) 
{
+                    probeSelectedQueryParam = probeQueryParams.size() - 1;
                 }
                 return true;
             }
-            if (ke.isUp()) {
-                probeField = PROBE_METHOD;
-                return true;
+            activeInput.deleteBackward();
+            return true;
+        }
+        if (ke.isDeleteForward()) {
+            activeInput.deleteForward();
+            return true;
+        }
+        if (ke.isLeft()) {
+            if (!probeEditingQueryKey && activeInput.cursorPosition() == 0) {
+                probeEditingQueryKey = true;
+            } else {
+                activeInput.moveCursorLeft();
             }
-            if (ke.isChar('+')) {
-                addProbeHeaderEmpty();
-                return true;
+            return true;
+        }
+        if (ke.isRight()) {
+            if (probeEditingQueryKey && activeInput.cursorPosition() == 
activeInput.text().length()) {
+                probeEditingQueryKey = false;
+            } else {
+                activeInput.moveCursorRight();
             }
-            FormHelper.handleTextInput(ke, probePathState);
             return true;
         }
-        if (probeField == PROBE_HEADERS) {
-            return handleProbeHeaderKeyEvent(ke);
+        if (ke.isHome()) {
+            activeInput.moveCursorToStart();
+            return true;
         }
-        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.isEnd()) {
+            activeInput.moveCursorToEnd();
+            return true;
+        }
+        if (ke.code() == KeyCode.CHAR) {
+            activeInput.insert(ke.string().charAt(0));
+            return true;
+        }
+        return true;
+    }
+
+    private boolean handleProbeCyclerKeyEvent(KeyEvent ke, String[] options, 
int field) {
+        if (ke.isKey(KeyCode.TAB) || ke.isDown()) {
+            goToNextProbeFieldFrom(field);
+            return true;
+        }
+        if (ke.isUp()) {
+            goToPrevProbeFieldFrom(field);
+            return true;
+        }
+        if (ke.isLeft()) {
+            if (field == PROBE_CONTENT_TYPE) {
+                probeContentTypeIndex = (probeContentTypeIndex - 1 + 
options.length) % options.length;
+            } else {
+                probeAcceptIndex = (probeAcceptIndex - 1 + options.length) % 
options.length;
             }
-            if (ke.isUp()) {
-                if (hasProbeHeaders()) {
-                    probeField = PROBE_HEADERS;
-                    probeSelectedHeader = probeHeaders.size() - 1;
-                    probeEditingHeaderKey = false;
-                } else {
-                    probeField = PROBE_PATH;
-                }
-                return true;
+            return true;
+        }
+        if (ke.isRight() || ke.isChar(' ')) {
+            if (field == PROBE_CONTENT_TYPE) {
+                probeContentTypeIndex = (probeContentTypeIndex + 1) % 
options.length;
+            } else {
+                probeAcceptIndex = (probeAcceptIndex + 1) % options.length;
             }
-            if (ke.isChar('+')) {
-                addProbeHeaderEmpty();
-                return true;
+            return true;
+        }
+        if (ke.isChar('+')) {
+            addProbeHeaderEmpty();
+            return true;
+        }
+        return true;
+    }
+
+    private boolean handleProbeBodyKeyEvent(KeyEvent ke) {
+        if (ke.isKey(KeyCode.TAB)) {
+            goToNextProbeFieldFrom(PROBE_BODY);
+            return true;
+        }
+        if (ke.isUp()) {
+            if (probeBodyState.cursorRow() == 0) {
+                goToPrevProbeFieldFrom(PROBE_BODY);
+            } else {
+                probeBodyState.moveCursorUp();
             }
-            FormHelper.handleTextInput(ke, probeBodyState);
             return true;
         }
-        if (probeField == PROBE_HISTORY) {
-            if (ke.isKey(KeyCode.TAB)) {
-                probeField = PROBE_METHOD;
-                return true;
+        if (ke.isDown()) {
+            if (probeBodyState.cursorRow() >= probeBodyState.lineCount() - 1) {
+                goToNextProbeFieldFrom(PROBE_BODY);
+            } else {
+                probeBodyState.moveCursorDown();
             }
-            if (ke.isUp()) {
-                if (probeHistoryIndex > 0) {
-                    probeHistoryIndex--;
-                } else {
-                    probeField = PROBE_BODY;
-                }
-                return true;
+            return true;
+        }
+        if (ke.isLeft()) {
+            probeBodyState.moveCursorLeft();
+            return true;
+        }
+        if (ke.isRight()) {
+            probeBodyState.moveCursorRight();
+            return true;
+        }
+        if (ke.isHome()) {
+            probeBodyState.moveCursorToLineStart();
+            return true;
+        }
+        if (ke.isEnd()) {
+            probeBodyState.moveCursorToLineEnd();
+            return true;
+        }
+        if (ke.isDeleteBackward()) {
+            probeBodyState.deleteBackward();
+            return true;
+        }
+        if (ke.isDeleteForward()) {
+            probeBodyState.deleteForward();
+            return true;
+        }
+        if (ke.code() == KeyCode.CHAR) {
+            probeBodyState.insert(ke.character());
+            return true;
+        }
+        return true;
+    }
+
+    private boolean handleProbeHistoryKeyEvent(KeyEvent ke) {
+        if (ke.isKey(KeyCode.TAB)) {
+            probeField = PROBE_METHOD;
+            return true;
+        }
+        if (ke.isUp()) {
+            if (probeHistoryIndex > 0) {
+                probeHistoryIndex--;
+            } else {
+                probeField = PROBE_BODY;
             }
-            if (ke.isDown()) {
-                if (probeHistoryIndex < probeHistory.size() - 1) {
-                    probeHistoryIndex++;
-                }
-                return true;
+            return true;
+        }
+        if (ke.isDown()) {
+            if (probeHistoryIndex < probeHistory.size() - 1) {
+                probeHistoryIndex++;
             }
             return true;
         }
+        if (ke.isChar('+')) {
+            addProbeQueryParamEmpty();
+            return true;
+        }
         return true;
     }
 
@@ -548,7 +799,7 @@ class HttpTab extends AbstractTableTab {
                     probeSelectedHeader--;
                     probeEditingHeaderKey = false;
                 } else {
-                    probeField = PROBE_PATH;
+                    probeField = PROBE_ACCEPT;
                 }
             } else {
                 probeEditingHeaderKey = true;
@@ -560,7 +811,7 @@ class HttpTab extends AbstractTableTab {
                 probeHeaders.remove(probeSelectedHeader);
                 if (probeHeaders.isEmpty()) {
                     probeHeaders = null;
-                    probeField = PROBE_PATH;
+                    probeField = PROBE_ACCEPT;
                 } else if (probeSelectedHeader >= probeHeaders.size()) {
                     probeSelectedHeader = probeHeaders.size() - 1;
                 }
@@ -618,12 +869,24 @@ class HttpTab extends AbstractTableTab {
         return probeHeaders != null && !probeHeaders.isEmpty();
     }
 
+    private boolean hasPathParams() {
+        return probePathParams != null && !probePathParams.isEmpty();
+    }
+
+    private boolean hasQueryParams() {
+        return probeQueryParams != null && !probeQueryParams.isEmpty();
+    }
+
     private TextInputState probeActiveTextInput() {
         if (probeField == PROBE_PATH) {
             return probePathState;
         }
-        if (probeField == PROBE_BODY) {
-            return probeBodyState;
+        if (probeField == PROBE_PATH_PARAMS && hasPathParams()) {
+            return probePathParams.get(probeSelectedPathParam).input;
+        }
+        if (probeField == PROBE_QUERY_PARAMS && hasQueryParams()) {
+            FormHelper.HeaderEntry qp = 
probeQueryParams.get(probeSelectedQueryParam);
+            return probeEditingQueryKey ? qp.keyInput() : qp.valueInput();
         }
         if (probeField == PROBE_HEADERS && hasProbeHeaders()) {
             FormHelper.HeaderEntry he = probeHeaders.get(probeSelectedHeader);
@@ -632,32 +895,267 @@ class HttpTab extends AbstractTableTab {
         return null;
     }
 
+    private void advanceProbeField() {
+        goToNextProbeFieldFrom(probeField);
+    }
+
+    private void goToNextProbeFieldFrom(int from) {
+        switch (from) {
+            case PROBE_METHOD:
+                probeField = PROBE_PATH;
+                break;
+            case PROBE_PATH:
+                if (hasPathParams()) {
+                    probeField = PROBE_PATH_PARAMS;
+                    probeSelectedPathParam = 0;
+                } else if (hasQueryParams()) {
+                    probeField = PROBE_QUERY_PARAMS;
+                    probeSelectedQueryParam = 0;
+                    probeEditingQueryKey = true;
+                } else {
+                    probeField = PROBE_CONTENT_TYPE;
+                }
+                break;
+            case PROBE_PATH_PARAMS:
+                if (hasQueryParams()) {
+                    probeField = PROBE_QUERY_PARAMS;
+                    probeSelectedQueryParam = 0;
+                    probeEditingQueryKey = true;
+                } else {
+                    probeField = PROBE_CONTENT_TYPE;
+                }
+                break;
+            case PROBE_QUERY_PARAMS:
+                probeField = PROBE_CONTENT_TYPE;
+                break;
+            case PROBE_CONTENT_TYPE:
+                probeField = PROBE_ACCEPT;
+                break;
+            case PROBE_ACCEPT:
+                if (hasProbeHeaders()) {
+                    probeField = PROBE_HEADERS;
+                    probeSelectedHeader = 0;
+                    probeEditingHeaderKey = true;
+                } else {
+                    probeField = PROBE_BODY;
+                }
+                break;
+            case PROBE_HEADERS:
+                probeField = PROBE_BODY;
+                break;
+            case PROBE_BODY:
+                if (!probeHistory.isEmpty()) {
+                    probeField = PROBE_HISTORY;
+                    probeHistoryIndex = 0;
+                } else {
+                    probeField = PROBE_METHOD;
+                }
+                break;
+            case PROBE_HISTORY:
+                probeField = PROBE_METHOD;
+                break;
+            default:
+                probeField = PROBE_METHOD;
+        }
+    }
+
+    private void goToPrevProbeFieldFrom(int from) {
+        switch (from) {
+            case PROBE_PATH:
+                probeField = PROBE_METHOD;
+                break;
+            case PROBE_PATH_PARAMS:
+                probeField = PROBE_PATH;
+                break;
+            case PROBE_QUERY_PARAMS:
+                if (hasPathParams()) {
+                    probeField = PROBE_PATH_PARAMS;
+                    probeSelectedPathParam = probePathParams.size() - 1;
+                } else {
+                    probeField = PROBE_PATH;
+                }
+                break;
+            case PROBE_CONTENT_TYPE:
+                if (hasQueryParams()) {
+                    probeField = PROBE_QUERY_PARAMS;
+                    probeSelectedQueryParam = probeQueryParams.size() - 1;
+                    probeEditingQueryKey = false;
+                } else if (hasPathParams()) {
+                    probeField = PROBE_PATH_PARAMS;
+                    probeSelectedPathParam = probePathParams.size() - 1;
+                } else {
+                    probeField = PROBE_PATH;
+                }
+                break;
+            case PROBE_ACCEPT:
+                probeField = PROBE_CONTENT_TYPE;
+                break;
+            case PROBE_HEADERS:
+                probeField = PROBE_ACCEPT;
+                break;
+            case PROBE_BODY:
+                if (hasProbeHeaders()) {
+                    probeField = PROBE_HEADERS;
+                    probeSelectedHeader = probeHeaders.size() - 1;
+                    probeEditingHeaderKey = false;
+                } else {
+                    probeField = PROBE_ACCEPT;
+                }
+                break;
+            case PROBE_HISTORY:
+                probeField = PROBE_BODY;
+                break;
+            default:
+                probeField = PROBE_METHOD;
+        }
+    }
+
+    private void goToLastProbeField() {
+        if (!probeHistory.isEmpty()) {
+            probeField = PROBE_HISTORY;
+            probeHistoryIndex = 0;
+        } else {
+            probeField = PROBE_BODY;
+        }
+    }
+
+    private List<PathParam> scanPathParams(String path) {
+        if (path == null || path.isEmpty()) {
+            return null;
+        }
+        Matcher m = PATH_PARAM_PATTERN.matcher(path);
+        List<PathParam> params = null;
+        while (m.find()) {
+            if (params == null) {
+                params = new ArrayList<>();
+            }
+            params.add(new PathParam(m.group(1), new TextInputState("")));
+        }
+        return params;
+    }
+
+    private void rescanPathParams() {
+        String path = probePathState.text();
+        List<PathParam> newParams = scanPathParams(path);
+        if (newParams == null) {
+            probePathParams = null;
+            return;
+        }
+        // Preserve existing values for matching param names
+        if (probePathParams != null) {
+            for (PathParam np : newParams) {
+                for (PathParam op : probePathParams) {
+                    if (np.name.equals(op.name) && !op.input.text().isEmpty()) 
{
+                        np.input.setText(op.input.text());
+                        break;
+                    }
+                }
+            }
+        }
+        probePathParams = newParams;
+    }
+
+    private void addProbeQueryParamEmpty() {
+        if (probeQueryParams == null) {
+            probeQueryParams = new ArrayList<>();
+        }
+        probeQueryParams.add(new FormHelper.HeaderEntry(new 
TextInputState(""), new TextInputState("")));
+        probeField = PROBE_QUERY_PARAMS;
+        probeSelectedQueryParam = probeQueryParams.size() - 1;
+        probeEditingQueryKey = true;
+    }
+
+    private static int findPresetIndex(String[] presets, String value) {
+        if (value == null || value.isEmpty()) {
+            return 0;
+        }
+        for (int i = 1; i < presets.length; i++) {
+            if (presets[i].equalsIgnoreCase(value) || 
value.contains(presets[i])) {
+                return i;
+            }
+        }
+        return 0;
+    }
+
     private void replayHistoryEntry(ProbeHistoryEntry entry) {
-        // Fill fields from history
+        // Method
         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));
-            }
-        }
+        // Path
+        probePathState.setText(entry.path);
+        // Path params: scan template then extract values from resolved path
+        rescanPathParams();
+        if (probePathParams != null && entry.resolvedPath != null) {
+            extractPathParamValues(entry.path, entry.resolvedPath);
+        }
+        // Query params
+        probeQueryParams = null;
+        if (entry.queryParams != null) {
+            probeQueryParams = new ArrayList<>();
+            for (FormHelper.HeaderEntry qp : entry.queryParams) {
+                probeQueryParams.add(new FormHelper.HeaderEntry(
+                        new TextInputState(qp.keyInput().text()),
+                        new TextInputState(qp.valueInput().text())));
+            }
+        }
+        probeSelectedQueryParam = 0;
+        probeEditingQueryKey = true;
+        // Content-Type and Accept
+        probeContentTypeIndex = entry.contentTypeIndex;
+        probeAcceptIndex = entry.acceptIndex;
+        // Headers
         probeHeaders = null;
+        probeSelectedHeader = 0;
+        probeEditingHeaderKey = true;
         if (entry.headers != null) {
             for (FormHelper.HeaderEntry he : entry.headers) {
                 addProbeHeader(he.keyInput().text(), he.valueInput().text());
             }
         }
-        probeField = PROBE_BODY;
-        doProbeRequest();
+        // Body
+        probeBodyState.setText(entry.body != null ? entry.body : "");
+        // Focus on method so user can review before sending
+        probeField = PROBE_METHOD;
+        probeViewRequest = true;
+    }
+
+    private void extractPathParamValues(String templatePath, String 
resolvedPath) {
+        if (probePathParams == null || probePathParams.isEmpty()) {
+            return;
+        }
+        // Build a regex from the template, capturing each {xxx} group
+        StringBuilder regex = new StringBuilder();
+        Matcher m = PATH_PARAM_PATTERN.matcher(templatePath);
+        int last = 0;
+        List<String> paramOrder = new ArrayList<>();
+        while (m.find()) {
+            regex.append(Pattern.quote(templatePath.substring(last, 
m.start())));
+            regex.append("([^/]*)");
+            paramOrder.add(m.group(1));
+            last = m.end();
+        }
+        regex.append(Pattern.quote(templatePath.substring(last)));
+        try {
+            Matcher rm = 
Pattern.compile(regex.toString()).matcher(resolvedPath);
+            if (rm.matches()) {
+                for (int i = 0; i < paramOrder.size() && i < rm.groupCount(); 
i++) {
+                    String name = paramOrder.get(i);
+                    String value = rm.group(i + 1);
+                    for (PathParam pp : probePathParams) {
+                        if (pp.name.equals(name)) {
+                            pp.input.setText(value);
+                            break;
+                        }
+                    }
+                }
+            }
+        } catch (Exception e) {
+            // ignore regex errors
+        }
     }
 
     private void doProbeRequest() {
@@ -676,10 +1174,39 @@ class HttpTab extends AbstractTableTab {
 
         String method = PROBE_METHODS[probeMethodIndex];
         String path = probePathState.text();
+
+        // Replace {xxx} placeholders with param values
+        if (hasPathParams()) {
+            for (PathParam pp : probePathParams) {
+                String val = pp.input.text();
+                if (!val.isEmpty()) {
+                    path = path.replace("{" + pp.name + "}", val);
+                }
+            }
+        }
+
+        // Append query params
+        if (hasQueryParams()) {
+            StringBuilder sb = new StringBuilder(path);
+            char sep = path.contains("?") ? '&' : '?';
+            for (FormHelper.HeaderEntry qp : probeQueryParams) {
+                String k = qp.keyInput().text().trim();
+                String v = qp.valueInput().text();
+                if (!k.isEmpty()) {
+                    sb.append(sep)
+                            .append(URLEncoder.encode(k, 
StandardCharsets.UTF_8))
+                            .append('=')
+                            .append(URLEncoder.encode(v, 
StandardCharsets.UTF_8));
+                    sep = '&';
+                }
+            }
+            path = sb.toString();
+        }
+
         String body = probeBodyState.text();
-        if (body != null && body.startsWith("file:")) {
+        if (body != null && body.trim().startsWith("file:")) {
             try {
-                body = Files.readString(Path.of(body.substring(5)));
+                body = Files.readString(Path.of(body.trim().substring(5)));
             } catch (IOException e) {
                 probeResponseStatus = "Error reading file: " + e.getMessage();
                 probeResponseError = true;
@@ -690,32 +1217,66 @@ class HttpTab extends AbstractTableTab {
         String sendBody = body;
         String baseUrl = probeBaseUrl;
 
-        // Snapshot headers
-        List<FormHelper.HeaderEntry> headerSnapshot = null;
+        // Build headers: Content-Type + Accept presets, then manual headers
+        List<FormHelper.HeaderEntry> allHeaders = new ArrayList<>();
+        if (probeContentTypeIndex > 0) {
+            allHeaders.add(new FormHelper.HeaderEntry(
+                    new TextInputState("Content-Type"),
+                    new TextInputState(CONTENT_TYPES[probeContentTypeIndex])));
+        }
+        if (probeAcceptIndex > 0) {
+            allHeaders.add(new FormHelper.HeaderEntry(
+                    new TextInputState("Accept"),
+                    new TextInputState(ACCEPT_TYPES[probeAcceptIndex])));
+        }
         if (hasProbeHeaders()) {
-            headerSnapshot = new ArrayList<>();
             for (FormHelper.HeaderEntry he : probeHeaders) {
-                headerSnapshot.add(new FormHelper.HeaderEntry(
+                allHeaders.add(new FormHelper.HeaderEntry(
                         new TextInputState(he.keyInput().text()),
                         new TextInputState(he.valueInput().text())));
             }
         }
-        List<FormHelper.HeaderEntry> hdrs = headerSnapshot;
+        List<FormHelper.HeaderEntry> hdrs = allHeaders.isEmpty() ? null : 
allHeaders;
+
+        // Snapshot manual headers, query params, and indices for history
+        List<FormHelper.HeaderEntry> manualHeaderSnapshot = 
snapshotEntries(probeHeaders);
+        List<FormHelper.HeaderEntry> qpSnapshot = 
snapshotEntries(probeQueryParams);
+        int ctIdx = probeContentTypeIndex;
+        int accIdx = probeAcceptIndex;
+        String originalPath = probePathState.text();
+        String resolvedPath = path;
 
         ctx.backgroundExecutor.execute(() -> {
             try {
-                doProbeRequestInBackground(baseUrl, method, path, sendBody, 
hdrs);
+                doProbeRequestInBackground(baseUrl, method, resolvedPath, 
originalPath, sendBody,
+                        hdrs, manualHeaderSnapshot, qpSnapshot, ctIdx, accIdx);
             } finally {
                 probeSending.set(false);
             }
         });
     }
 
+    private static List<FormHelper.HeaderEntry> 
snapshotEntries(List<FormHelper.HeaderEntry> entries) {
+        if (entries == null || entries.isEmpty()) {
+            return null;
+        }
+        List<FormHelper.HeaderEntry> snap = new ArrayList<>();
+        for (FormHelper.HeaderEntry he : entries) {
+            snap.add(new FormHelper.HeaderEntry(
+                    new TextInputState(he.keyInput().text()),
+                    new TextInputState(he.valueInput().text())));
+        }
+        return snap;
+    }
+
     private void doProbeRequestInBackground(
-            String baseUrl, String method, String path, String body, 
List<FormHelper.HeaderEntry> hdrs) {
+            String baseUrl, String method, String resolvedPath, String 
originalPath,
+            String body, List<FormHelper.HeaderEntry> allHeaders,
+            List<FormHelper.HeaderEntry> manualHeaders,
+            List<FormHelper.HeaderEntry> queryParams, int contentTypeIdx, int 
acceptIdx) {
 
-        String url = baseUrl + path;
-        HttpHelper.HttpResult result = HttpHelper.sendRequest(url, method, 
body, hdrs);
+        String url = baseUrl + resolvedPath;
+        HttpHelper.HttpResult result = HttpHelper.sendRequest(url, method, 
body, allHeaders);
 
         String statusText;
         boolean error;
@@ -735,13 +1296,9 @@ class HttpTab extends AbstractTableTab {
             headerLines = result.headerLines();
         }
 
-        // Build history entry
-        List<FormHelper.HeaderEntry> histHeaders = null;
-        if (hdrs != null && !hdrs.isEmpty()) {
-            histHeaders = new ArrayList<>(hdrs);
-        }
         ProbeHistoryEntry histEntry = new ProbeHistoryEntry(
-                method, path, histHeaders, body,
+                method, originalPath, resolvedPath, manualHeaders, body,
+                queryParams, contentTypeIdx, acceptIdx,
                 httpStatus, result.elapsed(), statusText, error);
 
         // Apply results on render thread
@@ -824,18 +1381,20 @@ class HttpTab extends AbstractTableTab {
     private void renderProbe(Frame frame, Rect area) {
         frame.renderWidget(Clear.INSTANCE, area);
 
-        int padX = 2;
-        int padY = 1;
-        Rect inner = new Rect(
-                area.left() + padX, area.top() + padY,
-                area.width() - padX * 2, area.height() - padY * 2);
+        Rect inner = area;
 
+        int pathParamCount = hasPathParams() ? probePathParams.size() : 0;
+        int queryParamCount = hasQueryParams() ? probeQueryParams.size() : 0;
         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;
-        }
+        int bodyHeight = 6;
+        int requestHeight = 3 // method + URL + path
+                            + pathParamCount
+                            + queryParamCount + (queryParamCount > 0 ? 1 : 0)
+                            + 2 // content-type + accept
+                            + headerCount + (headerCount > 0 ? 1 : 0)
+                            + bodyHeight
+                            + 3; // borders and padding
+        int historyHeight = probeHistory.isEmpty() ? 3 : Math.min(4, 
probeHistory.size()) + 2;
 
         List<Rect> chunks = Layout.vertical()
                 .constraints(
@@ -856,7 +1415,10 @@ class HttpTab extends AbstractTableTab {
                 Span.styled(method, methodStyle(method).bold()),
                 Span.raw(" " + probePathState.text() + " ")));
 
-        Block block = 
Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title(title).build();
+        boolean requestFocused = probeField != PROBE_HISTORY;
+        Style borderStyle = requestFocused ? Style.EMPTY.fg(Theme.accent()) : 
Style.EMPTY;
+        Block block = 
Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                .borderStyle(borderStyle).title(title).build();
         frame.renderWidget(block, area);
 
         int innerX = area.left() + 2;
@@ -879,7 +1441,7 @@ class HttpTab extends AbstractTableTab {
         // Full URL (read-only)
         row++;
         FormHelper.renderLabel(frame, innerX, row, labelW, "URL:", false);
-        String fullUrl = (probeBaseUrl != null ? probeBaseUrl : "") + 
probePathState.text();
+        String fullUrl = buildFullUrl();
         Rect urlArea = new Rect(innerX + labelW, row, fieldW, 1);
         frame.renderWidget(Paragraph.from(Line.from(
                 Span.styled(fullUrl, Theme.info().dim().hyperlink(fullUrl)))), 
urlArea);
@@ -899,98 +1461,218 @@ class HttpTab extends AbstractTableTab {
                     pathArea);
         }
 
-        // Headers
-        if (hasProbeHeaders()) {
-            int keyW = Math.min(20, fieldW / 3);
-            int valW = fieldW - keyW - 3;
-            for (int i = 0; i < probeHeaders.size(); i++) {
+        // Path parameters ({xxx} placeholders)
+        if (hasPathParams()) {
+            for (int i = 0; i < probePathParams.size(); i++) {
                 row++;
-                boolean isSelected = probeField == PROBE_HEADERS && 
probeSelectedHeader == i;
-                String label = i == 0 ? "Headers:" : "";
+                PathParam pp = probePathParams.get(i);
+                boolean isSelected = probeField == PROBE_PATH_PARAMS && 
probeSelectedPathParam == i;
+                String label = i == 0 ? "Params:" : "";
                 FormHelper.renderLabel(frame, innerX, row, labelW, label,
-                        isSelected || (i == 0 && probeField == PROBE_HEADERS));
+                        isSelected || (i == 0 && probeField == 
PROBE_PATH_PARAMS));
 
-                FormHelper.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);
+                // Show param name as prefix
+                String paramLabel = "{" + pp.name + "}: ";
+                int paramLabelW = Math.min(paramLabel.length(), 15);
+                Rect paramLabelArea = new Rect(fieldX, row, paramLabelW, 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());
+                        Span.styled(paramLabel, isSelected ? 
Theme.label().bold() : Theme.label()))),
+                        paramLabelArea);
+
+                Rect paramInputArea = new Rect(fieldX + paramLabelW, row, 
fieldW - paramLabelW, 1);
+                if (isSelected && !probeSending.get()) {
+                    TextInput textInput = TextInput.builder()
+                            .cursorStyle(Style.EMPTY.reversed())
+                            .placeholder("value for {" + pp.name + "}")
+                            .build();
+                    frame.renderStatefulWidget(textInput, paramInputArea, 
pp.input);
                 } else {
-                    String valText = he.valueInput().text();
-                    Style valStyle = valText.isEmpty() ? Style.EMPTY.dim()
-                            : isSelected ? Style.EMPTY.bold() : Style.EMPTY;
+                    String val = pp.input.text();
                     frame.renderWidget(Paragraph.from(Line.from(
-                            Span.styled(valText.isEmpty() ? "<value>" : 
valText, valStyle))), valArea);
+                            Span.styled(val.isEmpty() ? "—" : val,
+                                    val.isEmpty() ? Style.EMPTY.dim() : 
Style.EMPTY))),
+                            paramInputArea);
                 }
             }
         }
 
-        // Body input
+        // Query parameters
+        if (hasQueryParams()) {
+            row = renderKeyValueSection(frame, row, innerX, labelW, fieldW, 
"Query:",
+                    probeQueryParams, PROBE_QUERY_PARAMS, 
probeSelectedQueryParam, probeEditingQueryKey);
+        }
+
+        // Content-Type cycler
+        row++;
+        renderCycler(frame, innerX, row, labelW, fieldW, "Content:",
+                CONTENT_TYPES, probeContentTypeIndex, probeField == 
PROBE_CONTENT_TYPE);
+
+        // Accept cycler
+        row++;
+        renderCycler(frame, innerX, row, labelW, fieldW, "Accept:",
+                ACCEPT_TYPES, probeAcceptIndex, probeField == PROBE_ACCEPT);
+
+        // Headers
+        if (hasProbeHeaders()) {
+            row = renderKeyValueSection(frame, row, innerX, labelW, fieldW, 
"Headers:",
+                    probeHeaders, PROBE_HEADERS, probeSelectedHeader, 
probeEditingHeaderKey);
+        }
+
+        // Body input (multi-line TextArea)
         row++;
+        int bodyHeight = 6;
         FormHelper.renderLabel(frame, innerX, row, labelW, "Body:", probeField 
== PROBE_BODY);
-        Rect bodyArea = new Rect(innerX + labelW, row, fieldW, 1);
+        Rect bodyArea = new Rect(innerX + labelW, row, fieldW, bodyHeight);
+        Style bodyBorderStyle = probeField == PROBE_BODY ? 
Style.EMPTY.fg(Theme.accent()) : Theme.muted();
+        Block bodyBlock = Block.builder()
+                .borders(Borders.ALL)
+                .borderType(BorderType.ROUNDED)
+                .borderStyle(bodyBorderStyle)
+                .build();
+        Rect bodyInner = bodyBlock.inner(bodyArea);
+        frame.renderWidget(bodyBlock, bodyArea);
+        TextArea textArea = TextArea.builder()
+                .cursorStyle(Style.EMPTY.reversed())
+                .placeholder("body text, JSON, XML, or file:payload.json")
+                .build();
         if (probeField == PROBE_BODY && !probeSending.get()) {
-            TextInput textInput = TextInput.builder()
-                    .cursorStyle(Style.EMPTY.reversed())
-                    .placeholder("body text or file:payload.json")
-                    .build();
-            frame.renderStatefulWidget(textInput, bodyArea, probeBodyState);
+            textArea.renderWithCursor(bodyInner, frame.buffer(), 
probeBodyState, frame);
         } else {
-            String bodyText = probeBodyState.text();
+            textArea.render(bodyInner, frame.buffer(), probeBodyState);
+        }
+    }
+
+    private String buildFullUrl() {
+        String base = probeBaseUrl != null ? probeBaseUrl : "";
+        String path = probePathState.text();
+        if (hasPathParams()) {
+            for (PathParam pp : probePathParams) {
+                String val = pp.input.text();
+                if (!val.isEmpty()) {
+                    path = path.replace("{" + pp.name + "}", val);
+                }
+            }
+        }
+        return base + path;
+    }
+
+    private void renderCycler(
+            Frame frame, int x, int row, int labelW, int fieldW,
+            String label, String[] options, int activeIndex, boolean selected) 
{
+
+        FormHelper.renderLabel(frame, x, row, labelW, label, selected);
+        Rect fieldArea = new Rect(x + labelW, row, fieldW, 1);
+        String display = activeIndex == 0 ? "(none)" : options[activeIndex];
+        String leftArr = selected ? TuiIcons.ARROW_LEFT + " " : "  ";
+        String rightArr = selected ? " " + TuiIcons.ARROW_RIGHT : "";
+        Style style = activeIndex == 0 ? Style.EMPTY.dim() : Style.EMPTY;
+        frame.renderWidget(Paragraph.from(Line.from(
+                Span.styled(leftArr, style),
+                Span.styled(display, selected ? style.bold() : style),
+                Span.styled(rightArr, style))), fieldArea);
+    }
+
+    private int renderKeyValueSection(
+            Frame frame, int startRow, int innerX, int labelW, int fieldW,
+            String sectionLabel, List<FormHelper.HeaderEntry> entries,
+            int fieldId, int selectedIdx, boolean editingKey) {
+
+        int keyW = Math.min(20, fieldW / 3);
+        int valW = fieldW - keyW - 3;
+        int row = startRow;
+        for (int i = 0; i < entries.size(); i++) {
+            row++;
+            boolean isSelected = probeField == fieldId && selectedIdx == i;
+            String label = i == 0 ? sectionLabel : "";
+            FormHelper.renderLabel(frame, innerX, row, labelW, label,
+                    isSelected || (i == 0 && probeField == fieldId));
+
+            FormHelper.HeaderEntry he = entries.get(i);
+            int fieldX = innerX + labelW;
+
+            Rect keyArea = new Rect(fieldX, row, keyW, 1);
+            if (isSelected && editingKey && !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(bodyText.isEmpty() ? "—" : bodyText,
-                            bodyText.isEmpty() ? Style.EMPTY.dim() : 
Style.EMPTY))),
-                    bodyArea);
+                    Span.styled(" : ", Style.EMPTY.dim()))), sepArea);
+
+            Rect valArea = new Rect(fieldX + keyW + 3, row, valW, 1);
+            if (isSelected && !editingKey && !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);
+            }
         }
+        return row;
     }
 
     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 = Theme.error().bold();
-        } else if ("Sending...".equals(probeResponseStatus)) {
-            titleStr = " Sending... ";
-            titleStyle = Theme.warning().bold();
+        boolean hasResponse = probeResponseStatus != null;
+
+        // Build tab header title
+        Title title;
+        if (!hasResponse) {
+            title = Title.from(Line.from(Span.styled(" Request ", 
Style.EMPTY.bold())));
         } else {
-            titleStr = " Response " + probeResponseStatus;
-            if (probeResponseElapsed > 0) {
-                titleStr += " (" + probeResponseElapsed + "ms)";
+            Style reqStyle = probeViewRequest ? Style.EMPTY.bold() : 
Style.EMPTY.dim();
+            Style resStyle = probeViewRequest ? Style.EMPTY.dim() : 
Style.EMPTY.bold();
+
+            String statusSuffix = "";
+            Style statusColor = Style.EMPTY;
+            if (probeResponseError) {
+                statusSuffix = " " + probeResponseStatus;
+                statusColor = Theme.error();
+            } else if ("Sending...".equals(probeResponseStatus)) {
+                statusSuffix = " Sending...";
+                statusColor = Theme.warning();
+            } else {
+                statusSuffix = " " + probeResponseStatus;
+                if (probeResponseElapsed > 0) {
+                    statusSuffix += " (" + probeResponseElapsed + "ms)";
+                }
+                statusColor = statusStyle(probeResponseStatus);
             }
-            titleStr += " ";
-            titleStyle = statusStyle(probeResponseStatus);
+
+            title = Title.from(Line.from(
+                    Span.styled(" Request ", reqStyle),
+                    Span.styled("|", Style.EMPTY.dim()),
+                    Span.styled(" Response", resStyle),
+                    Span.styled(statusSuffix + " ", statusColor.bold())));
         }
 
-        Title title = Title.from(Line.from(Span.styled(titleStr, titleStyle)));
+        // Show request preview (before first send, or when request tab is 
active)
+        if (!hasResponse || probeViewRequest) {
+            List<Line> preview = buildRequestPreview();
+            frame.renderWidget(
+                    Paragraph.builder()
+                            .text(Text.from(preview))
+                            
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title(title).build())
+                            .build(),
+                    area);
+            return;
+        }
 
+        // Show response
         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()))))
+                            .text(Text.from(Line.from(Span.styled(" No 
response content", Style.EMPTY.dim()))))
                             
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title(title).build())
                             .build(),
                     area);
@@ -1001,10 +1683,11 @@ class HttpTab extends AbstractTableTab {
         if (visibleLines < 1) {
             visibleLines = 1;
         }
-        int maxScroll = Math.max(0, probeResponseLines.size() - visibleLines);
+        int totalLines = probeResponseLines.size();
+        int maxScroll = Math.max(0, totalLines - visibleLines);
         probeResponseScroll = Math.min(probeResponseScroll, maxScroll);
 
-        int end = Math.min(probeResponseScroll + visibleLines, 
probeResponseLines.size());
+        int end = Math.min(probeResponseScroll + visibleLines, totalLines);
         List<Line> lines = new ArrayList<>();
         for (int i = probeResponseScroll; i < end; i++) {
             String line = probeResponseLines.get(i);
@@ -1012,7 +1695,6 @@ class HttpTab extends AbstractTableTab {
                 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), 
Theme.label()),
@@ -1022,23 +1704,98 @@ class HttpTab extends AbstractTableTab {
             }
         }
 
+        Block.Builder blockBuilder = 
Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title(title);
+        if (totalLines > visibleLines) {
+            int pct = (int) ((probeResponseScroll + visibleLines) * 100L / 
totalLines);
+            String scrollInfo = " " + pct + "% (" + totalLines + " lines) ";
+            
blockBuilder.titleBottom(Title.from(Line.from(Span.styled(scrollInfo, 
Style.EMPTY.dim()))));
+        }
+
         frame.renderWidget(
                 Paragraph.builder()
                         .text(Text.from(lines))
-                        
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title(title).build())
+                        .block(blockBuilder.build())
                         .build(),
                 area);
     }
 
+    private List<Line> buildRequestPreview() {
+        List<Line> lines = new ArrayList<>();
+        String method = PROBE_METHODS[probeMethodIndex];
+        String fullUrl = buildFullUrl();
+
+        // Add query params to the preview URL
+        if (hasQueryParams()) {
+            StringBuilder sb = new StringBuilder(fullUrl);
+            char sep = fullUrl.contains("?") ? '&' : '?';
+            for (FormHelper.HeaderEntry qp : probeQueryParams) {
+                String k = qp.keyInput().text().trim();
+                String v = qp.valueInput().text();
+                if (!k.isEmpty()) {
+                    sb.append(sep).append(k).append('=').append(v);
+                    sep = '&';
+                }
+            }
+            fullUrl = sb.toString();
+        }
+
+        lines.add(Line.from(
+                Span.styled(" " + method, methodStyle(method).bold()),
+                Span.raw(" " + fullUrl)));
+        lines.add(Line.from(Span.raw("")));
+
+        // Headers
+        String ct = CONTENT_TYPES[probeContentTypeIndex];
+        if (!ct.isEmpty()) {
+            lines.add(Line.from(
+                    Span.styled(" Content-Type: ", Theme.label()),
+                    Span.raw(ct)));
+        }
+        String acc = ACCEPT_TYPES[probeAcceptIndex];
+        if (!acc.isEmpty()) {
+            lines.add(Line.from(
+                    Span.styled(" Accept: ", Theme.label()),
+                    Span.raw(acc)));
+        }
+        if (hasProbeHeaders()) {
+            for (FormHelper.HeaderEntry h : probeHeaders) {
+                String k = h.keyInput().text().trim();
+                String v = h.valueInput().text();
+                if (!k.isEmpty()) {
+                    lines.add(Line.from(
+                            Span.styled(" " + k + ": ", Theme.label()),
+                            Span.raw(v)));
+                }
+            }
+        }
+
+        // Body
+        String body = probeBodyState.text();
+        if (body != null && !body.isEmpty()) {
+            lines.add(Line.from(Span.raw("")));
+            for (String bl : body.split("\n", -1)) {
+                lines.add(Line.from(Span.raw(" " + bl)));
+            }
+        }
+
+        if (lines.size() <= 1) {
+            lines.add(Line.from(Span.styled(" Press F5 to send", 
Style.EMPTY.dim())));
+        }
+        return lines;
+    }
+
     private void renderProbeHistory(Frame frame, Rect area) {
         String title = " History [" + probeHistory.size() + "] ";
+        boolean historyFocused = probeField == PROBE_HISTORY;
+        Style histBorderStyle = historyFocused ? 
Style.EMPTY.fg(Theme.accent()) : Style.EMPTY;
 
         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).borders(Borders.ALL).title(title).build())
+                            
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                                    
.borderStyle(histBorderStyle).title(title).build())
                             .build(),
                     area);
             return;
@@ -1059,7 +1816,7 @@ class HttpTab extends AbstractTableTab {
         List<Line> lines = new ArrayList<>();
         for (int i = start; i < end; i++) {
             ProbeHistoryEntry entry = probeHistory.get(i);
-            boolean selected = probeField == PROBE_HISTORY && i == 
probeHistoryIndex;
+            boolean selected = historyFocused && i == probeHistoryIndex;
             String pointer = selected ? TuiIcons.POINTER + " " : "  ";
             String methodStr = String.format("%-8s", entry.method);
             String statusStr = entry.error ? "ERR" : entry.statusText;
@@ -1072,7 +1829,7 @@ class HttpTab extends AbstractTableTab {
             lines.add(Line.from(
                     Span.styled(pointer, lineStyle),
                     Span.styled(methodStr, methodStyle(entry.method)),
-                    Span.styled(entry.path + "  ", lineStyle),
+                    Span.styled(entry.resolvedPath + "  ", lineStyle),
                     Span.styled(statusStr, selected ? statusStyle(statusStr) : 
statusStyle(statusStr).dim()),
                     Span.styled("  " + elapsedStr, Style.EMPTY.dim()),
                     Span.styled(bodySnippet, Style.EMPTY.dim())));
@@ -1081,7 +1838,8 @@ class HttpTab extends AbstractTableTab {
         frame.renderWidget(
                 Paragraph.builder()
                         .text(Text.from(lines))
-                        
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title(title).build())
+                        
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                                
.borderStyle(histBorderStyle).title(title).build())
                         .build(),
                 area);
     }
@@ -1521,16 +2279,21 @@ class HttpTab extends AbstractTableTab {
         return new SelectionContext("table", items, sel != null ? sel : -1, 
items.size(), "HTTP");
     }
 
+    record PathParam(String name, TextInputState input) {
+    }
+
     record ProbeHistoryEntry(
-            String method, String path,
+            String method, String path, String resolvedPath,
             List<FormHelper.HeaderEntry> headers, String body,
+            List<FormHelper.HeaderEntry> queryParams,
+            int contentTypeIndex, int acceptIndex,
             int statusCode, long elapsed,
             String statusText, boolean error) {
     }
 
     @Override
     public String description() {
-        return "HTTP endpoint probe — send requests and inspect responses";
+        return "HTTP endpoint probe — lightweight Postman for testing 
REST/HTTP endpoints";
     }
 
     @Override
@@ -1539,66 +2302,61 @@ class HttpTab extends AbstractTableTab {
                 # HTTP
 
                 The HTTP tab shows all HTTP endpoints exposed by this 
integration and
-                lets you send test requests interactively. This includes REST 
API
-                endpoints, management endpoints (health, metrics), and any 
other
-                HTTP routes.
+                lets you send test requests interactively — a lightweight 
Postman built
+                into the terminal. This includes REST API endpoints, 
management endpoints
+                (health, metrics), and any other HTTP routes.
 
                 ## Endpoint List
 
-                - **METHOD** — HTTP method: `GET`, `POST`, `PUT`, `DELETE`, 
`PATCH`, etc. Some endpoints support multiple methods
-                - **PATH** — URL path for this endpoint (e.g., `/api/users`, 
`/observe/health`)
-                - **TOTAL** — Number of HTTP requests received by this 
endpoint since startup
-                - **CONSUMES** — Content-Type this endpoint accepts (e.g., 
`application/json`, `text/xml`). Empty means any content type
-                - **PRODUCES** — Content-Type this endpoint returns in 
responses
-                - **SOURCE** — How the endpoint was registered (see below)
-                - **STATE** — Endpoint state: `Started` (accepting requests) 
or `Stopped`
-
-                ## Example Screen
-
-                ```
-                 METHOD  PATH                  TOTAL  CONSUMES          
PRODUCES          SOURCE        STATE
-                 GET     /api/users            142    application/json  
application/json  REST(code)    Started
-                 POST    /api/users            38     application/json  
application/json  REST(code)    Started
-                 GET     /observe/health       97                              
           Management          Started
-                 GET     /observe/metrics      52                       
text/plain        Management          Started
-                 GET     /q/openapi            15                       
application/json  REST(contract) Started
-                ```
-
-                ## SOURCE Types
-
-                The SOURCE column tells you how each endpoint was created:
-
-                - **REST(code)** — Defined in Camel REST DSL code. The 
developer wrote `rest("/api").get("/users").to("direct:getUsers")` in the route 
definition
-                - **REST(contract)** — Generated from an OpenAPI/Swagger 
specification file (contract-first approach). The API structure comes from a 
`.json` or `.yaml` spec file
-                - **HTTP** — Registered directly via the `platform-http` 
component using `from("platform-http:/path")`
-                - **Management** — Management endpoint added automatically by 
Camel for observability: health checks, metrics, OpenAPI specs, developer 
console
+                - **METHOD** — HTTP method: `GET`, `POST`, `PUT`, `DELETE`, 
`PATCH`, etc.
+                - **PATH** — URL path for this endpoint
+                - **TOTAL** — Number of HTTP requests received since startup
+                - **CONSUMES** — Content-Type this endpoint accepts
+                - **PRODUCES** — Content-Type this endpoint returns
+                - **SOURCE** — How the endpoint was registered: REST(code), 
REST(contract), HTTP, Management
+                - **STATE** — Endpoint state: `Started` or `Stopped`
 
                 ## HTTP Probe
 
                 Press `Enter` on an endpoint to open the interactive HTTP 
probe.
-                This is a built-in HTTP client that lets you send test 
requests and
-                inspect responses without leaving the TUI.
 
-                The probe screen has these sections:
+                The probe has these sections:
 
-                - **Method**: Use `Left/Right` arrows to cycle through HTTP 
methods (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS)
-                - **Path**: Editable URL path — modify it to test different 
routes or add query parameters
-                - **Headers**: Custom request headers. Press `+` to add a new 
header, then edit the key and value fields
-                - **Body**: Request body text. For file-based bodies, type 
`file:data.json` to load content from a file on disk
-                - **Response**: Shows the HTTP status code, response headers, 
and response body after sending
-                - **History**: Recent requests you have sent, with the ability 
to replay any previous request
+                - **Method**: `Left/Right` arrows to cycle through HTTP methods
+                - **URL**: Read-only full URL (with resolved placeholders). 
Clickable as hyperlink
+                - **Path**: Editable URL path template (e.g., 
`/api/users/{id}`)
+                - **Path Params**: Auto-detected from `{xxx}` placeholders in 
the path. Fill in values and they get substituted in the URL
+                - **Query Params**: Key-value pairs appended to the URL as 
`?key=value`. Press `+` to add
+                - **Content-Type**: Cycle through common content types with 
`Left/Right` arrows
+                - **Accept**: Cycle through common accept types with 
`Left/Right` arrows
+                - **Headers**: Custom request headers. Press `+` to add
+                - **Body**: Multi-line request body (Enter for newline). 
Supports `file:payload.json` to load from disk
+                - **Response**: HTTP status code, elapsed time, response 
headers, and body
+                - **History**: Recent requests with replay support
 
-                Press `Enter` to send the request. Press `p` to toggle 
pretty-print
-                for JSON responses — this formats the response body with 
indentation
-                for easier reading.
+                Press `F5` to send the request. Press `p` to toggle 
pretty-print for JSON responses.
 
                 ## Keys
 
+                ### Endpoint List
                 - `Up/Down` — select endpoint
                 - `Enter` — open HTTP probe for selected endpoint
                 - `s` — cycle sort column
                 - `S` — reverse sort order
-                - `Esc` — close probe view
+                - `f` — cycle filter (all / rest / http)
+                - `m` — toggle management endpoints
+                - `c` — view OpenAPI spec (when available)
+
+                ### HTTP Probe
+                - `F5` — send request
+                - `Tab/Down` — next field
+                - `Up` — previous field
+                - `Enter` — newline in body, advance in other fields
+                - `Left/Right` — cycle method, content-type, accept; cursor in 
text fields
+                - `+` — add query param or header
+                - `p` — toggle pretty-print
+                - `PgUp/PgDn` — scroll response
+                - `Esc` — close probe
                 """;
     }
 
@@ -1610,12 +2368,31 @@ class HttpTab extends AbstractTableTab {
         String v = value != null ? value : "";
         if ("path".equals(field)) {
             probePathState.setText(v);
+            rescanPathParams();
             return true;
         }
         if ("body".equals(field)) {
             probeBodyState.setText(v);
             return true;
         }
+        if ("method".equals(field)) {
+            String upper = v.toUpperCase(Locale.ENGLISH);
+            for (int i = 0; i < PROBE_METHODS.length; i++) {
+                if (PROBE_METHODS[i].equals(upper)) {
+                    probeMethodIndex = i;
+                    return true;
+                }
+            }
+            return false;
+        }
+        if ("content-type".equals(field)) {
+            probeContentTypeIndex = findPresetIndex(CONTENT_TYPES, v);
+            return true;
+        }
+        if ("accept".equals(field)) {
+            probeAcceptIndex = findPresetIndex(ACCEPT_TYPES, v);
+            return true;
+        }
         return false;
     }
 
@@ -1627,6 +2404,77 @@ class HttpTab extends AbstractTableTab {
         }
         JsonObject result = new JsonObject();
         result.put("tab", "HTTP");
+
+        if (probeMode) {
+            result.put("probeMode", true);
+            result.put("method", PROBE_METHODS[probeMethodIndex]);
+            result.put("path", probePathState.text());
+            String ct = CONTENT_TYPES[probeContentTypeIndex];
+            if (!ct.isEmpty()) {
+                result.put("contentType", ct);
+            }
+            String acc = ACCEPT_TYPES[probeAcceptIndex];
+            if (!acc.isEmpty()) {
+                result.put("accept", acc);
+            }
+            if (hasPathParams()) {
+                JsonObject pp = new JsonObject();
+                for (PathParam p : probePathParams) {
+                    pp.put(p.name, p.input.text());
+                }
+                result.put("pathParams", pp);
+            }
+            if (hasQueryParams()) {
+                JsonArray qp = new JsonArray();
+                for (FormHelper.HeaderEntry e : probeQueryParams) {
+                    JsonObject entry = new JsonObject();
+                    entry.put("key", e.keyInput().text());
+                    entry.put("value", e.valueInput().text());
+                    qp.add(entry);
+                }
+                result.put("queryParams", qp);
+            }
+            if (hasProbeHeaders()) {
+                JsonArray hd = new JsonArray();
+                for (FormHelper.HeaderEntry e : probeHeaders) {
+                    JsonObject entry = new JsonObject();
+                    entry.put("key", e.keyInput().text());
+                    entry.put("value", e.valueInput().text());
+                    hd.add(entry);
+                }
+                result.put("headers", hd);
+            }
+            String body = probeBodyState.text();
+            if (body != null && !body.isEmpty()) {
+                result.put("body", body);
+            }
+            if (probeResponseStatus != null) {
+                result.put("responseStatus", probeResponseStatus);
+            }
+            if (probeResponseElapsed > 0) {
+                result.put("responseElapsed", probeResponseElapsed);
+            }
+            if (probeResponseRawBody != null && 
!probeResponseRawBody.isEmpty()) {
+                result.put("responseBody", probeResponseRawBody);
+            }
+            if (!probeHistory.isEmpty()) {
+                JsonArray hist = new JsonArray();
+                for (ProbeHistoryEntry he : probeHistory) {
+                    JsonObject entry = new JsonObject();
+                    entry.put("method", he.method);
+                    entry.put("path", he.path);
+                    entry.put("statusCode", he.statusCode);
+                    entry.put("elapsed", he.elapsed);
+                    if (he.error) {
+                        entry.put("error", true);
+                    }
+                    hist.add(entry);
+                }
+                result.put("history", hist);
+            }
+            return result;
+        }
+
         JsonArray rows = new JsonArray();
         for (HttpEndpointInfo hi : info.httpEndpoints) {
             JsonObject row = new JsonObject();
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiToolRegistry.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiToolRegistry.java
index 77c6b2fba371..043e21ef9de6 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiToolRegistry.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiToolRegistry.java
@@ -485,10 +485,10 @@ class TuiToolRegistry {
                 "Sets the value of a text input field on a TUI tab directly, 
without simulating keystrokes. "
                                  + "The text appears in the TUI input widget 
so the user can see it. "
                                  + "Supported fields by tab: SQL Query 
(field='sql'), "
-                                 + "HTTP probe (field='path' or 'body'), "
+                                 + "HTTP probe (field='path', 'body', 
'method', 'content-type', or 'accept'), "
                                  + "Spans (field='filter'), Classpath 
(field='filter').",
                 Map.of("field", propDef("string",
-                        "Field name to set: 'sql', 'path', 'body', or 
'filter'"),
+                        "Field name to set: 'sql', 'path', 'body', 'method', 
'content-type', 'accept', or 'filter'"),
                         "value", propDef("string",
                                 "The text value to set in the input field"),
                         "tab", propDef("string",

Reply via email to