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 8e517ccdcf09 camel-jbang - TUI: free Ctrl+R for history search, move 
tape recording to F2 only
8e517ccdcf09 is described below

commit 8e517ccdcf0923db2f96504e519992597b766afe
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat Jul 25 18:38:22 2026 +0200

    camel-jbang - TUI: free Ctrl+R for history search, move tape recording to 
F2 only
    
    Remove the Ctrl+R keyboard shortcut for tape recording toggle so it can
    be used for history search. Tape recording is now only accessible via
    the F2 actions menu. When stopping a tape recording via F2, the menu
    interaction (F2 key, navigation, Enter) is rolled back from the tape to
    keep it clean.
    
    camel-jbang - TUI: add /clear-history slash command to AI prompt panel
    
    Add /clear-history command to the F8 AI prompt panel that clears the
    persisted prompt history (navigated with Up/Down arrows). Clears both
    the in-memory entries and the history file on disk.
    
    camel-jbang - TUI: implement Ctrl+R reverse-i-search in AI prompt panel
    
    Add JLine-style reverse incremental history search to the F8 AI prompt
    panel. Press Ctrl+R to enter search mode, type to narrow matches
    (case-insensitive substring), Ctrl+R again to cycle to older matches,
    Enter to accept, Escape to cancel. The search prompt shows
    "bck-i-search: query" or "failing bck-i-search: query" with a preview
    of the matched entry.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../dsl/jbang/core/commands/tui/ActionsPopup.java  |   4 +-
 .../camel/dsl/jbang/core/commands/tui/AiPanel.java | 126 +++++++++++++++++++++
 .../core/commands/tui/AiSlashCommandContext.java   |   2 +
 .../core/commands/tui/AiSlashCommandRegistry.java  |   6 +
 .../dsl/jbang/core/commands/tui/CamelMonitor.java  |  17 ++-
 .../jbang/core/commands/tui/DocViewerPopup.java    |   5 +-
 .../jbang/core/commands/tui/RecordingManager.java  |  19 ++++
 .../dsl/jbang/core/commands/tui/TapeRecorder.java  |  13 +++
 .../jbang/core/commands/tui/TuiPromptHistory.java  |  28 +++++
 .../dsl/jbang/core/commands/tui/AiPanelTest.java   |  20 +++-
 .../commands/tui/AiSlashCommandRegistryTest.java   |   8 +-
 11 files changed, 235 insertions(+), 13 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
index dc4a1e8d4c79..65905a55fbf6 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
@@ -1029,8 +1029,8 @@ class ActionsPopup {
                 ? TuiIcons.menuItem(TuiIcons.KEYSTROKES, "Hide Keystrokes")
                 : TuiIcons.menuItem(TuiIcons.KEYSTROKES, "Show Keystrokes");
         String tapeLabel = tapeRecordingActive.get()
-                ? TuiIcons.menuItem(TuiIcons.STOP, "Stop Tape Recording 
(Ctrl+R)")
-                : TuiIcons.menuItem(TuiIcons.RECORD, "Start Tape Recording 
(Ctrl+R)");
+                ? TuiIcons.menuItem(TuiIcons.STOP, "Stop Tape Recording")
+                : TuiIcons.menuItem(TuiIcons.RECORD, "Start Tape Recording");
         List<ListItem> items = new ArrayList<>();
         items.add(ListItem.from("  .."));
         items.add(ListItem.from(divider).style(Style.EMPTY.dim()));
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiPanel.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiPanel.java
index 9d2e93b10451..ed6f0daee7ac 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiPanel.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiPanel.java
@@ -100,6 +100,12 @@ class AiPanel {
     private int cursorPos;
     private TuiPromptHistory promptHistory;
 
+    // Reverse-i-search state (Ctrl+R)
+    private boolean historySearchActive;
+    private final StringBuilder searchTerm = new StringBuilder();
+    private int searchIndex = -1;
+    private String savedInput;
+
     // TAB completion cycle state. completionMatches holds the candidate 
command names for the active cycle;
     // completionSnapshot is the buffer text a TAB press last produced. The 
cycle continues only while the buffer still
     // equals that snapshot, so any other edit (typing, backspace, cursor 
move) transparently starts a fresh completion.
@@ -374,6 +380,9 @@ class AiPanel {
             }
             return true;
         }
+        if (historySearchActive) {
+            return handleSearchKeyEvent(ke);
+        }
         if (ke.isKey(KeyCode.F8)) {
             close();
             return true;
@@ -381,6 +390,11 @@ class AiPanel {
         if (isFunctionKey(ke)) {
             return false;
         }
+        if (!statsView && !thinking.get() && ke.hasCtrl() && 
ke.isCharIgnoreCase('r')
+                && promptHistory != null && promptHistory.isEnabled() && 
promptHistory.size() > 0) {
+            enterHistorySearch();
+            return true;
+        }
         if (ke.hasCtrl() && ke.isCharIgnoreCase('p') && !thinking.get() && 
activeCliCommand == null) {
             openProviderSwitch();
             return true;
@@ -495,6 +509,78 @@ class AiPanel {
         return true;
     }
 
+    // ---- Reverse-i-search (Ctrl+R) ----
+
+    private void enterHistorySearch() {
+        savedInput = inputBuffer.toString();
+        historySearchActive = true;
+        searchTerm.setLength(0);
+        searchIndex = -1;
+    }
+
+    private void exitHistorySearch(boolean accept) {
+        historySearchActive = false;
+        if (accept && searchIndex >= 0) {
+            replaceInputBuffer(promptHistory.get(searchIndex));
+        } else {
+            replaceInputBuffer(savedInput);
+        }
+        searchTerm.setLength(0);
+        searchIndex = -1;
+        savedInput = null;
+    }
+
+    private void performSearch(int fromIndex) {
+        if (searchTerm.isEmpty()) {
+            searchIndex = -1;
+            return;
+        }
+        searchIndex = promptHistory.searchBackward(searchTerm.toString(), 
fromIndex);
+    }
+
+    private boolean handleSearchKeyEvent(KeyEvent ke) {
+        if (ke.hasCtrl() && ke.isCharIgnoreCase('r')) {
+            if (searchIndex > 0) {
+                performSearch(searchIndex - 1);
+            }
+            return true;
+        }
+        if (ke.isKey(KeyCode.ENTER)) {
+            exitHistorySearch(true);
+            return true;
+        }
+        if (ke.isKey(KeyCode.ESCAPE) || ke.isCtrlC()) {
+            exitHistorySearch(false);
+            return true;
+        }
+        if (ke.isKey(KeyCode.BACKSPACE)) {
+            if (searchTerm.isEmpty()) {
+                exitHistorySearch(false);
+            } else {
+                searchTerm.deleteCharAt(searchTerm.length() - 1);
+                performSearch(promptHistory.size() - 1);
+            }
+            return true;
+        }
+        if (ke.code() == KeyCode.CHAR && !ke.hasCtrl() && !ke.hasAlt()) {
+            searchTerm.append(ke.character());
+            int from = searchIndex >= 0 ? searchIndex : promptHistory.size() - 
1;
+            performSearch(from);
+            return true;
+        }
+        // Any other key: accept match and let the key be processed normally
+        exitHistorySearch(true);
+        return handleKeyEvent(ke);
+    }
+
+    boolean isHistorySearchActive() {
+        return historySearchActive;
+    }
+
+    String searchTermForTesting() {
+        return searchTerm.toString();
+    }
+
     /**
      * Completes the slash command name at the cursor. With multiple matches, 
TAB first fills in the longest common
      * prefix; once no further prefix can be added it cycles forward through 
the matches (wrapping so every match is
@@ -1060,6 +1146,11 @@ class AiPanel {
     }
 
     private void renderInput(Frame frame, Rect area) {
+        if (historySearchActive) {
+            renderSearchInput(frame, area);
+            return;
+        }
+
         String prompt = INPUT_PROMPT;
         String text = inputBuffer.toString();
 
@@ -1105,6 +1196,33 @@ class AiPanel {
         frame.renderWidget(Paragraph.from(Line.from(spans)), area);
     }
 
+    private void renderSearchInput(Frame frame, Rect area) {
+        boolean failing = !searchTerm.isEmpty() && searchIndex < 0;
+        String label = failing ? "failing bck-i-search: " : "bck-i-search: ";
+        String query = searchTerm.toString();
+
+        List<Span> spans = new ArrayList<>();
+        Style labelStyle = failing
+                ? Theme.error()
+                : Style.EMPTY.fg(Theme.accent());
+        spans.add(Span.styled(label, labelStyle));
+        spans.add(Span.raw(query));
+        spans.add(Span.styled("_", Style.EMPTY.reversed()));
+
+        if (searchIndex >= 0) {
+            String matched = promptHistory.get(searchIndex);
+            int maxWidth = area.width() - label.length() - query.length() - 1;
+            if (maxWidth > 4) {
+                String preview = matched.length() > maxWidth
+                        ? matched.substring(0, maxWidth - 1) + "…"
+                        : matched;
+                spans.add(Span.styled("  " + preview, Style.EMPTY.dim()));
+            }
+        }
+
+        frame.renderWidget(Paragraph.from(Line.from(spans)), area);
+    }
+
     void renderFooter(List<Span> spans) {
         TuiHelper.hint(spans, "F8", "close");
         if (statsView) {
@@ -1120,6 +1238,7 @@ class AiPanel {
             TuiHelper.hint(spans, "Ctrl+P", "provider");
             if (!thinking.get()) {
                 TuiHelper.hint(spans, "Enter", "send");
+                TuiHelper.hint(spans, "Ctrl+R", "search");
             } else {
                 TuiHelper.hint(spans, "Esc/Ctrl+C", "interrupt");
             }
@@ -1602,6 +1721,13 @@ class AiPanel {
             AiPanel.this.clearConversation();
         }
 
+        @Override
+        public void clearHistory() {
+            if (promptHistory != null) {
+                promptHistory.clear();
+            }
+        }
+
         @Override
         public String currentModel() {
             return client != null && client.model() != null ? client.model() : 
"unknown";
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiSlashCommandContext.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiSlashCommandContext.java
index ceac4afd2ec3..98e6b7adee33 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiSlashCommandContext.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiSlashCommandContext.java
@@ -28,6 +28,8 @@ interface AiSlashCommandContext {
 
     void clearConversation();
 
+    void clearHistory();
+
     String currentModel();
 
     List<String> availableModels();
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiSlashCommandRegistry.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiSlashCommandRegistry.java
index d755b6c689c3..12b1a3f6481d 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiSlashCommandRegistry.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiSlashCommandRegistry.java
@@ -74,6 +74,12 @@ final class AiSlashCommandRegistry {
                     context.clearConversation();
                     return CommandResult.system("");
                 }));
+        commands.add(new Descriptor(
+                "clear-history", List.of(), "Clear the prompt history", null,
+                (context, arguments) -> {
+                    context.clearHistory();
+                    return CommandResult.system("Prompt history cleared");
+                }));
         commands.add(new Descriptor(
                 "close", List.of(), "Close the AI panel", null,
                 (context, arguments) -> {
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 7d4477a05f3a..403d094ff55b 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
@@ -588,7 +588,9 @@ public class CamelMonitor extends CamelCommand {
         if (event instanceof KeyEvent ke) {
             recordingManager.recordKey(ke, mcpInjectedKey);
             if (ke.hasCtrl() && ke.isChar('r')) {
-                recordingManager.toggleTapeRecording();
+                if (aiPanel.isOpen()) {
+                    return aiPanel.handleKeyEvent(ke);
+                }
                 return true;
             }
             if (captionOverlay.isVisible()) {
@@ -621,7 +623,11 @@ public class CamelMonitor extends CamelCommand {
                 return shellPanel.handleKeyEvent(ke);
             }
             if (actionsPopup.isVisible()) {
-                return actionsPopup.handleKeyEvent(ke);
+                boolean result = actionsPopup.handleKeyEvent(ke);
+                if (!actionsPopup.isVisible()) {
+                    recordingManager.rollbackMenu();
+                }
+                return result;
             }
             if (popupManager.handleKeyEvent(ke, 
tabRegistry.selectedTabIndex(), TAB_LOG)) {
                 return true;
@@ -831,6 +837,7 @@ public class CamelMonitor extends CamelCommand {
             return true;
         }
         if (ke.isKey(KeyCode.F2)) {
+            recordingManager.markMenuOpened();
             if (tabRegistry.selectedTabIndex() == TAB_ROUTES && 
tabRegistry.routesTab() != null) {
                 
actionsPopup.setPreSelectedRouteId(tabRegistry.routesTab().selectedRouteId());
             }
@@ -948,7 +955,11 @@ public class CamelMonitor extends CamelCommand {
 
         // Modal popups capture all mouse events (including tab bar and footer)
         if (actionsPopup.isVisible()) {
-            return actionsPopup.handleMouseEvent(me);
+            boolean result = actionsPopup.handleMouseEvent(me);
+            if (!actionsPopup.isVisible()) {
+                recordingManager.rollbackMenu();
+            }
+            return result;
         }
 
         // Tab bar clicks: detect which tab was clicked and switch to it
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DocViewerPopup.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DocViewerPopup.java
index c1b472d2c9c4..ee753c3ac59d 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DocViewerPopup.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DocViewerPopup.java
@@ -361,8 +361,7 @@ class DocViewerPopup {
                                              + "with timing. The tape can be 
replayed inside the TUI to produce\n"
                                              + "an Asciinema `.cast` 
recording.\n\n"
                                              + "## Starting and Stopping\n\n"
-                                             + "- Press **Ctrl+R** to 
start/stop recording at any time\n"
-                                             + "- Or use the **F2** actions 
menu → Start/Stop Tape Recording\n\n"
+                                             + "- Use the **F2** actions menu 
→ Start/Stop Tape Recording\n\n"
                                              + "When recording stops, the tape 
is saved to the current directory as\n"
                                              + 
"`camel-tui-tape-<timestamp>.tape`.\n\n"
                                              + "## Replaying a Tape\n\n"
@@ -376,7 +375,7 @@ class DocViewerPopup {
                                              + "    agg recording.cast 
demo.gif\n\n"
                                              + "Or upload to 
[asciinema.org](https://asciinema.org) for a shareable link.\n\n"
                                              + "## Tips\n\n"
-                                             + "- **Ctrl+R** is not captured 
in the tape, keeping the script clean\n"
+                                             + "- Tape recording toggle is not 
captured in the tape, keeping the script clean\n"
                                              + "- Natural pauses between 
keystrokes are preserved as `Sleep` commands\n"
                                              + "- Keep recordings focused — 
one workflow at a time works best\n");
     }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RecordingManager.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RecordingManager.java
index e6c2ad205ffd..86f62ebd3096 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RecordingManager.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RecordingManager.java
@@ -50,6 +50,7 @@ class RecordingManager {
     private volatile boolean pendingScreenshot;
     private final List<KeyRecord> recentKeys = new ArrayList<>();
     private TuiEventLog eventLog;
+    private int menuTapePosition = -1;
 
     RecordingManager(CaptionOverlay captionOverlay) {
         this.captionOverlay = captionOverlay;
@@ -151,6 +152,23 @@ class RecordingManager {
         return null;
     }
 
+    // ---- Tape menu rollback ----
+
+    void markMenuOpened() {
+        TapeRecorder tr = tapeRecorderRef.get();
+        if (tr != null && tr.isActive()) {
+            menuTapePosition = tr.preKeyPosition();
+        }
+    }
+
+    void rollbackMenu() {
+        TapeRecorder tr = tapeRecorderRef.get();
+        if (tr != null && tr.isActive() && menuTapePosition >= 0) {
+            tr.trimTo(menuTapePosition);
+        }
+        menuTapePosition = -1;
+    }
+
     // ---- Tape recording ----
 
     AtomicReference<TapeRecorder> tapeRecorderRef() {
@@ -179,6 +197,7 @@ class RecordingManager {
     void toggleTapeRecording() {
         TapeRecorder rec = tapeRecorderRef.get();
         if (rec != null && rec.isActive()) {
+            rollbackMenu();
             String tape = rec.stop();
             tapeRecorderRef.set(null);
             String timestamp = LocalDateTime.now()
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TapeRecorder.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TapeRecorder.java
index 15da52713f77..503965519fd9 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TapeRecorder.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TapeRecorder.java
@@ -33,6 +33,7 @@ class TapeRecorder {
     private long lastEventTime;
     private int keyCount;
     private boolean active;
+    private int preKeyPosition = -1;
 
     void start(String title) {
         lines.clear();
@@ -56,6 +57,7 @@ class TapeRecorder {
         if (!active || key == null) {
             return;
         }
+        preKeyPosition = lines.size();
         long now = System.currentTimeMillis();
         long elapsed = now - lastEventTime;
         if (elapsed > 200) {
@@ -71,6 +73,17 @@ class TapeRecorder {
         lastEventTime = now;
     }
 
+    int preKeyPosition() {
+        return preKeyPosition;
+    }
+
+    void trimTo(int position) {
+        if (position >= 0 && position < lines.size()) {
+            lines.subList(position, lines.size()).clear();
+            lastEventTime = System.currentTimeMillis();
+        }
+    }
+
     void recordKeys(List<String> keys, int delay) {
         if (!active) {
             return;
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiPromptHistory.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiPromptHistory.java
index 8be1dd760283..45df46d62e9d 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiPromptHistory.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiPromptHistory.java
@@ -112,6 +112,34 @@ final class TuiPromptHistory {
         return Optional.of(entries.get(browseIndex));
     }
 
+    int size() {
+        return entries.size();
+    }
+
+    String get(int index) {
+        return entries.get(index);
+    }
+
+    int searchBackward(String query, int fromIndex) {
+        if (!isEnabled() || query == null || query.isEmpty() || 
entries.isEmpty()) {
+            return -1;
+        }
+        String lower = query.toLowerCase(java.util.Locale.ROOT);
+        int start = Math.min(fromIndex, entries.size() - 1);
+        for (int i = start; i >= 0; i--) {
+            if 
(entries.get(i).toLowerCase(java.util.Locale.ROOT).contains(lower)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    void clear() {
+        entries.clear();
+        resetNavigation();
+        persist();
+    }
+
     void resetNavigation() {
         browseIndex = -1;
         draft = "";
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/AiPanelTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/AiPanelTest.java
index 850b4515a7d7..13f9a9189c50 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/AiPanelTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/AiPanelTest.java
@@ -663,7 +663,7 @@ class AiPanelTest {
         panel.open();
         type(panel, "/c");
 
-        // /c matches both /clear and /close, so the first TAB fills in their 
common prefix without choosing one.
+        // /c matches /clear, /clear-history, and /close, so the first TAB 
fills in their common prefix.
         tab(panel);
         assertEquals("/cl", panel.inputBufferForTesting());
 
@@ -671,6 +671,8 @@ class AiPanelTest {
         tab(panel);
         assertEquals("/clear", panel.inputBufferForTesting());
         tab(panel);
+        assertEquals("/clear-history", panel.inputBufferForTesting());
+        tab(panel);
         assertEquals("/close", panel.inputBufferForTesting());
         tab(panel);
         assertEquals("/clear", panel.inputBufferForTesting());
@@ -688,6 +690,8 @@ class AiPanelTest {
         shiftTab(panel);
         assertEquals("/close", panel.inputBufferForTesting());
         shiftTab(panel);
+        assertEquals("/clear-history", panel.inputBufferForTesting());
+        shiftTab(panel);
         assertEquals("/clear", panel.inputBufferForTesting());
     }
 
@@ -711,11 +715,17 @@ class AiPanelTest {
         tab(panel);
         assertEquals("/clear", panel.inputBufferForTesting());
 
-        // Backspacing breaks the cycle; the next TAB recomputes from the 
edited buffer instead of jumping to /close.
+        // Backspacing breaks the cycle; the next TAB recomputes from the 
edited buffer.
+        // /clea matches /clear and /clear-history, so TAB fills common prefix 
/clear.
         panel.handleKeyEvent(KeyEvent.ofKey(KeyCode.BACKSPACE, 
KeyModifiers.NONE));
         assertEquals("/clea", panel.inputBufferForTesting());
         tab(panel);
-        assertEquals("/clear ", panel.inputBufferForTesting());
+        assertEquals("/clear", panel.inputBufferForTesting());
+        // First cycle step lands on /clear (index 0, same as prefix), next 
advances to /clear-history.
+        tab(panel);
+        assertEquals("/clear", panel.inputBufferForTesting());
+        tab(panel);
+        assertEquals("/clear-history", panel.inputBufferForTesting());
     }
 
     private static void type(AiPanel panel, String text) {
@@ -825,6 +835,10 @@ class AiPanelTest {
         public void clearConversation() {
         }
 
+        @Override
+        public void clearHistory() {
+        }
+
         @Override
         public String currentModel() {
             return "test-model";
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/AiSlashCommandRegistryTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/AiSlashCommandRegistryTest.java
index 9c4be2a4e613..37f18f37375f 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/AiSlashCommandRegistryTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/AiSlashCommandRegistryTest.java
@@ -33,7 +33,7 @@ class AiSlashCommandRegistryTest {
     void descriptorsKeepStableOrder() {
         AiSlashCommandRegistry registry = AiSlashCommandRegistry.defaults();
 
-        assertEquals(List.of("help", "provider", "model", "clear", "close", 
"quit", "run", "infra", "send"),
+        assertEquals(List.of("help", "provider", "model", "clear", 
"clear-history", "close", "quit", "run", "infra", "send"),
                 
registry.descriptors().stream().map(AiSlashCommandRegistry.Descriptor::name).toList());
     }
 
@@ -73,7 +73,7 @@ class AiSlashCommandRegistryTest {
     void completionsIncludeAllCommandsForBareSlash() {
         AiSlashCommandRegistry registry = AiSlashCommandRegistry.defaults();
 
-        assertEquals(9, registry.completionsFor("/").size());
+        assertEquals(10, registry.completionsFor("/").size());
         assertFalse(registry.completionsFor("/").stream().anyMatch(descriptor 
-> "exit".equals(descriptor.name())));
     }
 
@@ -290,6 +290,10 @@ class AiSlashCommandRegistryTest {
         public void clearConversation() {
         }
 
+        @Override
+        public void clearHistory() {
+        }
+
         @Override
         public String currentModel() {
             return "test-model";

Reply via email to